@herbertbeckman - LinkedIn
@rndtavares - LinkedIn
Ejen AI yang boleh dipercayai dalam produk dengan Java Quarkus Langchain4j - Bahagian 1 - AI sebagai Perkhidmatan
Ejen AI yang boleh dipercayai di Java Quarkus Langchain4j prod - Bahagian 2 - Memori (artikel ini)
Ejen AI yang boleh dipercayai dalam produk dengan Java Quarkus Langchain4j - Bahagian 3 - RAG (akan datang)
Ejen AI yang boleh dipercayai dalam produk dengan Java Quarkus Langchain4j - Bahagian 4 - Pengawal (akan datang)
Apabila kita mencipta ejen, kita mesti ingat bahawa LLM tidak menyimpan apa-apa jenis maklumat, iaitu, ia tidak mempunyai kewarganegaraan. Untuk ejen kami mempunyai keupayaan untuk "mengingat" maklumat, kami mesti melaksanakan pengurusan ingatan. Quarkus sudah menyediakan memori lalai yang dikonfigurasikan kepada kami, namun ia boleh menghapuskan ejen anda secara literal dengan meletupkan memori RAM yang disediakan untuknya, seperti yang diterangkan dalam dokumentasi Quarkus ini, jika penjagaan yang sewajarnya tidak diambil. Untuk tidak lagi menghadapi masalah ini dan juga boleh menggunakan ejen kami dalam persekitaran berskala, kami memerlukan ChatMemoryStore.
Kami menggunakan sembang untuk berinteraksi dengan ejen kami dan terdapat konsep penting yang mesti kami ketahui supaya interaksi kami dengannya dapat berlaku dengan cara yang terbaik dan tidak menyebabkan pepijat dalam pengeluaran. Mula-mula kita perlu tahu jenis-jenis mesej yang kita gunakan semasa berinteraksi dengannya, iaitu:
Mesej Pengguna: Mesej atau permintaan yang dihantar oleh pelanggan akhir. Apabila kami menghantar mesej dalam Quarkus DevUI, kami sentiasa menghantar Mesej Pengguna. Tambahan pula, ia juga digunakan dalam hasil panggilan alat yang kita lihat sebelum ini.
Mesej AI (AiMessage): Mesej respons daripada model. Setiap kali LLM membalas ejen kami, dia akan menerima mesej seperti ini. Mesej jenis ini menggantikan kandungannya antara respons teks dan permintaan pelaksanaan alat.
Mesej Sistem: Mesej ini hanya boleh ditakrifkan sekali dan hanya pada masa pembangunan.
Sekarang anda tahu 3 jenis mesej yang kami ada, mari jelaskan cara mesej itu harus berkelakuan dengan beberapa grafik. Semua grafik diambil daripada pembentangan Java meets AI: Bina Apl Dikuasakan LLM dengan LangChain4j oleh Deandrea, Andrianakis, Escoffier, saya sangat mengesyorkan video tersebut.
Graf pertama menunjukkan penggunaan 3 jenis mesej. UserMessage dalam warna biru, SystemMessage dalam warna merah dan AiMessage dalam warna hijau.
Graf kedua ini menunjukkan cara "memori" harus diurus. Perincian yang menarik ialah kita mesti mengekalkan susunan tertentu dalam mesej dan sesetengah premis mesti dihormati.
Satu lagi butiran penting yang perlu anda perhatikan ialah saiz ChatMemory anda. Lebih besar ingatan interaksi anda, lebih tinggi kos token, kerana LLM perlu memproses lebih banyak teks untuk memberikan respons. Kemudian wujudkan tetingkap memori yang paling sesuai dengan kes penggunaan anda. Satu petua ialah menyemak purata bilangan mesej daripada pelanggan anda untuk mendapatkan idea tentang saiz interaksi. Kami akan menunjukkan pelaksanaan melalui MessageWindowChatMemory, kelas yang mengkhusus dalam mengurus ini untuk kami di Langchain4j.
Sekarang kita tahu semua konsep dan premis ini, mari kita mengotorkan tangan kita!
Di sini kita akan menggunakan MongoDB sebagai ChatMemoryStore. Kami menggunakan dokumen MongoDB dan memuat naik contoh ke Docker. Jangan ragu untuk mengkonfigurasinya mengikut kehendak anda.
Mari kita mulakan dengan menambah kebergantungan yang diperlukan untuk menyambung ke MongoDB menggunakan Quarkus.
<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-mongodb-panache</artifactId> </dependency>
Selepas kebergantungan, kami perlu menambah tetapan sambungan dalam src/main/resources/application.properties kami.
quarkus.mongodb.connection-string=mongodb://${MONGODB_USER}:${MONGODB_PASSWORD}@localhost:27017 quarkus.mongodb.database=chat_memory
Kami masih tidak akan dapat menguji sambungan kami ke pangkalan, kerana kami perlu mencipta entiti dan repositori kami terlebih dahulu.
Sekarang mari kita laksanakan entiti Interaksi kami. Entiti ini akan membuat senarai mesej kami. Setiap kali pelanggan baharu berhubung, Interaksi baharu akan dijana. Jika kami perlu menggunakan semula Interaksi ini, kami hanya masukkan pengecam Interaksi yang sama.
package <seupacote>; import dev.langchain4j.data.message.ChatMessage; import io.quarkus.mongodb.panache.common.MongoEntity; import org.bson.codecs.pojo.annotations.BsonId; import java.util.List; import java.util.Objects; @MongoEntity(collection = "interactions") public class InteractionEntity { @BsonId private String interactionId; private List<ChatMessage> messages; public InteractionEntity() { } public InteractionEntity(String interactionId, List<ChatMessage> messages) { this.interactionId = interactionId; this.messages = messages; } public String getInteractionId() { return interactionId; } public void setInteractionId(String interactionId) { this.interactionId = interactionId; } public List<ChatMessage> getMessages() { return messages; } public void setMessages(List<ChatMessage> messages) { this.messages = messages; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; InteractionEntity that = (InteractionEntity) o; return Objects.equals(interactionId, that.interactionId); } @Override public int hashCode() { return Objects.hash(interactionId, messages); } }
Kini kami boleh mencipta repositori kami.
<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-mongodb-panache</artifactId> </dependency>
Kini kami akan melaksanakan beberapa komponen langchain4j, ChatMemoryStore dan ChatMemoryProvider. ChatMemoryProvider ialah kelas yang akan kami gunakan dalam Ejen kami. Di dalamnya kami akan menambah ChatMemoryStore yang akan menggunakan repositori kami untuk menyimpan mesej dalam MongoDB kami. Ikuti ChatMemoryStore:
quarkus.mongodb.connection-string=mongodb://${MONGODB_USER}:${MONGODB_PASSWORD}@localhost:27017 quarkus.mongodb.database=chat_memory
Pembekal ChatMemory akan kelihatan seperti ini:
package <seupacote>; import dev.langchain4j.data.message.ChatMessage; import io.quarkus.mongodb.panache.common.MongoEntity; import org.bson.codecs.pojo.annotations.BsonId; import java.util.List; import java.util.Objects; @MongoEntity(collection = "interactions") public class InteractionEntity { @BsonId private String interactionId; private List<ChatMessage> messages; public InteractionEntity() { } public InteractionEntity(String interactionId, List<ChatMessage> messages) { this.interactionId = interactionId; this.messages = messages; } public String getInteractionId() { return interactionId; } public void setInteractionId(String interactionId) { this.interactionId = interactionId; } public List<ChatMessage> getMessages() { return messages; } public void setMessages(List<ChatMessage> messages) { this.messages = messages; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; InteractionEntity that = (InteractionEntity) o; return Objects.equals(interactionId, that.interactionId); } @Override public int hashCode() { return Objects.hash(interactionId, messages); } }
Perhatikan MessageWindowChatMemory. Di sinilah kami melaksanakan tetingkap mesej yang kami nyatakan pada permulaan artikel. Dalam kaedah maxMessages(), anda mesti menukarnya kepada nombor yang anda fikir terbaik untuk senario anda. Perkara yang saya syorkan ialah menggunakan bilangan terbesar mesej yang pernah wujud dalam senario anda, atau menggunakan purata. Di sini kita mentakrifkan nombor arbitrari 100.
Mari kita tukar ejen kami untuk menggunakan ChatMemoryProvider baharu kami dan tambah MemoryId. Ia sepatutnya kelihatan seperti ini:
package <seupacote>; import dev.langchain4j.data.message.ChatMessage; import io.quarkus.mongodb.panache.PanacheMongoRepositoryBase; import java.util.List; public class InteractionRepository implements PanacheMongoRepositoryBase<InteractionEntity, String> { public InteractionEntity findByInteractionId(String interactionId) { return findById(interactionId); } public void updateMessages(String interactionId, List<ChatMessage> messages) { persistOrUpdate(new InteractionEntity(interactionId, messages)); } public void deleteMessages(String interactionId) { deleteById(interactionId); } }
Ini sepatutnya memecahkan AgentWSEndpoint kami. Mari kita ubahnya supaya ia menerima pengecam Interaksi dan kita boleh menggunakannya sebagai MemoryId kami:
package <seupacote>; import dev.langchain4j.data.message.ChatMessage; import dev.langchain4j.store.memory.chat.ChatMemoryStore; import java.util.List; import java.util.Objects; public class MongoDBChatMemoryStore implements ChatMemoryStore { private InteractionRepository interactionRepository = new InteractionRepository(); @Override public List<ChatMessage> getMessages(Object memoryId) { var interactionEntity = interactionRepository.findByInteractionId(memoryId.toString()); return Objects.isNull(interactionEntity) ? List.of() : interactionEntity.getMessages(); } @Override public void updateMessages(Object memoryId, List<ChatMessage> messages) { interactionRepository.updateMessages(memoryId.toString(), messages); } @Override public void deleteMessages(Object memoryId) { interactionRepository.deleteMessages(memoryId.toString()); } }
Kami kini boleh menguji ejen kami semula. Untuk melakukan ini, kami hanya menyambung ke soket web dengan menghantar UUID bila-bila masa yang kami mahu. Anda boleh menjana UUID baharu di sini atau gunakan perintah uuidgen dalam Linux.
Apabila kami menjalankan ujian anda tidak akan menerima sebarang maklum balas daripada ejen. Ini berlaku kerana ejen menghadapi masalah menulis mesej kami ke MongoDB dan ia akan menunjukkan ini kepada anda melalui pengecualian. Supaya kita boleh menyemak pengecualian ini berlaku, kita mesti menambah harta baharu pada src/main/resources/application.properties kita, iaitu tahap log yang ingin kita lihat dalam Quarkus. Kemudian, tambahkan baris berikut di dalamnya:
package <seupacote>; import dev.langchain4j.memory.chat.ChatMemoryProvider; import dev.langchain4j.memory.chat.MessageWindowChatMemory; import java.util.function.Supplier; public class MongoDBChatMemoryProvider implements Supplier<ChatMemoryProvider> { private MongoDBChatMemoryStore mongoDBChatMemoryStore = new MongoDBChatMemoryStore(); @Override public ChatMemoryProvider get() { return memoryId -> MessageWindowChatMemory.builder() .maxMessages(100) .id(memoryId) .chatMemoryStore(mongoDBChatMemoryStore) .build(); } }
Sekarang uji ejen. Pengecualian mestilah ini:
package <seupacote>; import dev.langchain4j.service.SystemMessage; import dev.langchain4j.service.UserMessage; import io.quarkiverse.langchain4j.RegisterAiService; import io.quarkiverse.langchain4j.ToolBox; import jakarta.enterprise.context.ApplicationScoped; @ApplicationScoped @RegisterAiService( chatMemoryProviderSupplier = MongoDBChatMemoryProvider.class ) public interface Agent { @ToolBox(AgentTools.class) @SystemMessage(""" Você é um agente especializado em futebol brasileiro, seu nome é FutAgentBR Você sabe responder sobre os principais títulos dos principais times brasileiros e da seleção brasileira Sua resposta precisa ser educada, você pode deve responder em Português brasileiro e de forma relevante a pergunta feita Quando você não souber a resposta, responda que você não sabe responder nesse momento mas saberá em futuras versões. """) String chat(@MemoryId String interactionId, @UserMessage String message); }
Pengecualian ini berlaku kerana MongoDB tidak dapat mengendalikan antara muka ChatMessage Langchain4j, jadi kami mesti melaksanakan codec untuk menjadikannya mungkin. Quarkus sendiri sudah menawarkan codec kepada kami, tetapi kami perlu menjelaskan dengan jelas bahawa kami mahu menggunakannya. Kami kemudiannya akan membuat kelas ChatMessageCodec dan ChatMessageCodecProvider seperti berikut:
package <seupacote>; import io.quarkus.websockets.next.OnTextMessage; import io.quarkus.websockets.next.WebSocket; import io.quarkus.websockets.next.WebSocketConnection; import jakarta.inject.Inject; import java.util.Objects; import java.util.UUID; @WebSocket(path = "/ws/{interactionId}") public class AgentWSEndpoint { private final Agent agent; private final WebSocketConnection connection; @Inject AgentWSEndpoint(Agent agent, WebSocketConnection connection) { this.agent = agent; this.connection = connection; } @OnTextMessage String reply(String message) { var interactionId = connection.pathParam("interactionId"); return agent.chat( Objects.isNull(interactionId) || interactionId.isBlank() ? UUID.randomUUID().toString() : interactionId, message ); } }
quarkus.log.level=DEBUG
Sedia! Kini kami boleh menguji dan mengesahkan mesej dalam MongoDB kami. Apabila membuat pertanyaan, kami boleh menyemak 3 jenis mesej dalam tatasusunan mesej dokumen.
Itu menamatkan bahagian kedua siri kami. Kami harap anda menikmatinya dan jumpa anda di bahagian 3.
Atas ialah kandungan terperinci Ejen AI yang boleh dipercayai dalam produk dengan Java Quarkus Langchain - Bahagian Memori. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!