@herbertbeckman - LinkedIn
@rndtavares - LinkedIn
使用 Java Quarkus Langchain4j 生產的可靠 AI 代理 - 第 1 部分 - AI 即服務
Java Quarkus Langchain4j 產品中的可靠 AI 代理 - 第 2 部分 - 記憶體(本文)
使用 Java Quarkus Langchain4j 生產的可靠 AI 代理 - 第 3 部分 - RAG(即將推出)
使用 Java Quarkus Langchain4j 生產的可靠 AI 代理 - 第 4 部分 - Guardrails(即將推出)
當我們建立代理時,我們必須記住LLM不儲存任何類型的信息,也就是說,它們是無狀態的。為了讓我們的智能體具有「記住」資訊的能力,我們必須實現記憶體管理。 Quarkus 已經為我們提供了配置的預設內存,但是,如果沒有採取適當的措施,它實際上可以透過炸毀可用的 RAM 內存來關閉您的代理,如 Quarkus 文件中所述。為了不再出現這個問題,並且能夠在可擴展的環境中使用我們的代理,我們需要一個 ChatMemoryStore。
我們使用聊天與我們的代理進行交互,我們必須了解一些重要的概念,以便我們與他的交互能夠以最好的方式進行,並且不會導致生產中的錯誤。首先我們需要知道與他互動時使用的訊息類型,它們是:
用戶訊息:最終客戶發送的訊息或請求。當我們在 Quarkus DevUI 中發送訊息時,我們總是發送 UserMessage。此外,它也用在我們之前看到的工具呼叫結果中。
AI訊息(AiMessage):來自模型的回應訊息。每當 LLM 回覆我們的代理商時,他都會收到這樣的訊息。此類訊息在文字回應和工具執行請求之間交替其內容。
SystemMessage:此訊息只能定義一次,且僅在開發時有效。
現在您已經了解了我們擁有的 3 種訊息類型,讓我們解釋一下它們在某些圖形中的表現方式。所有圖形均取自 Deandrea、Andrianakis、Escoffier 的簡報《Java meet AI: Build LLM-Powered Apps with LangChain4j》,我強烈推薦該影片。
第一張圖示範了 3 種類型的訊息的使用。 UserMessage 為藍色,SystemMessage 為紅色,AiMessage 為綠色。
第二張圖示範如何管理「記憶體」。一個有趣的細節是,我們必須保持訊息中的一定順序,並且必須尊重一些前提。
您應該注意的另一個重要細節是您的 ChatMemory 的大小。互動的記憶體越大,代幣成本就越高,因為法學碩士需要處理更多文字才能提供回應。然後建立一個最適合您的用例的記憶體視窗。一個技巧是檢查客戶的平均訊息數,以了解互動的規模。我們將透過 MessageWindowChatMemory 展示實現,該類別專門在 Langchain4j 中為我們管理此操作。
現在我們已經了解了所有這些概念和前提,讓我們開始動手吧!
這裡我們將使用 MongoDB 作為 ChatMemoryStore。我們使用 MongoDB 文件並將實例上傳到 Docker。請隨意配置它。
讓我們先加入必要的依賴項以使用 Quarkus 連接到 MongoDB。
<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-mongodb-panache</artifactId> </dependency>
依賴關係之後,我們需要在 src/main/resources/application.properties 中新增連線設定。
quarkus.mongodb.connection-string=mongodb://${MONGODB_USER}:${MONGODB_PASSWORD}@localhost:27017 quarkus.mongodb.database=chat_memory
我們仍然無法測試與基礎的連接,因為我們需要先建立實體和儲存庫。
現在讓我們實現我們的互動實體。該實體將製作我們的訊息清單。每當有新客戶連線時,就會產生新的互動。如果我們需要重複使用此交互,我們只需輸入相同的交互標識符即可。
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); } }
我們現在可以建立我們的儲存庫。
<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-mongodb-panache</artifactId> </dependency>
現在我們將實作一些langchain4j元件,ChatMemoryStore和ChatMemoryProvider。 ChatMemoryProvider 是我們將在代理程式中使用的類別。在其中,我們將新增一個 ChatMemoryStore,它將使用我們的儲存庫將訊息儲存在 MongoDB 中。關注 ChatMemoryStore:
quarkus.mongodb.connection-string=mongodb://${MONGODB_USER}:${MONGODB_PASSWORD}@localhost:27017 quarkus.mongodb.database=chat_memory
ChatMemoryProvider 將如下圖所示:
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); } }
注意 MessageWindowChatMemory。這是我們實作文章開頭提到的訊息視窗的地方。在 maxMessages() 方法中,您必須將其變更為您認為最適合您的場景的數字。我建議使用場景中曾經存在的最大數量的消息,或者使用平均值。這裡我們定義任意數100。
現在讓我們更改代理程式以使用新的 ChatMemoryProvider 並新增 MemoryId。它應該看起來像這樣:
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); } }
這應該會破壞我們的 AgentWSEndpoint。讓我們對其進行更改,以便它接收交互標識符,並且可以將其用作 MemoryId:
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()); } }
我們現在可以再次測試我們的代理。為此,我們只需在需要時傳遞 UUID 即可連接到 Websocket。您可以在這裡產生新的 UUID,或在 Linux 中使用 uuidgen 指令。
當我們進行測試時,您將不會收到代理的任何回應。發生這種情況是因為代理在將訊息寫入 MongoDB 時遇到問題,它會透過異常向您顯示這一點。為了檢查是否發生了此異常,我們必須在 src/main/resources/application.properties 中新增一個屬性,這是我們希望在 Quarkus 中看到的日誌等級。然後,在其中加入以下行:
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(); } }
現在測試代理程式。例外應該是這樣的:
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); }
出現這個異常是因為MongoDB無法處理Langchain4j的ChatMessage接口,所以我們必須實作一個編解碼器來實現這一點。 Quarkus 本身已經為我們提供了一個編解碼器,但我們需要明確地說明我們想要使用它。然後,我們將建立 ChatMessageCodec 和 ChatMessageCodecProvider 類,如下所示:
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
準備好了!現在我們可以測試並驗證 MongoDB 中的消息。查詢時,我們可以在文件的messages數組中查看這3種訊息。
我們系列的第二部分到此結束。我們希望您喜歡它並在第 3 部分中見到您。
以上是使用 Java Quarkus Langchain 建立可靠的 AI 代理程式 - 記憶體部分的詳細內容。更多資訊請關注PHP中文網其他相關文章!