@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中文网其他相关文章!