首页 >Java >java教程 >使用 Java Quarkus Langchain 构建可靠的 AI 代理 - 内存部分

使用 Java Quarkus Langchain 构建可靠的 AI 代理 - 内存部分

DDD
DDD原创
2024-11-18 01:26:02691浏览

作者

@herbertbeckman - LinkedIn
@rndtavares - LinkedIn

文章部分内容

  1. 使用 Java Quarkus Langchain4j 生产的可靠 AI 代理 - 第 1 部分 - AI 即服务

  2. Java Quarkus Langchain4j 产品中的可靠 AI 代理 - 第 2 部分 - 内存(本文)

  3. 使用 Java Quarkus Langchain4j 生产的可靠 AI 代理 - 第 3 部分 - RAG(即将推出)

  4. 使用 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 为绿色。

Agente de IA confiável em prod com Java   Quarkus   Langchain- Parte  Memória

第二张图演示了如何管理“内存”。一个有趣的细节是,我们必须保持消息中的一定顺序,并且必须尊重一些前提。

Agente de IA confiável em prod com Java   Quarkus   Langchain- Parte  Memória

  • 只能有 1 条 SystemMessage 类型的消息;
  • 在 SystemMessage 之后,消息应始终按顺序在 UserMessage 和 AiMessage 之间切换。如果 AiMessage 之后有 AiMessage,我们将抛出异常。对于连续的 UserMessage 也是如此。

您应该注意的另一个重要细节是您的 ChatMemory 的大小。交互的内存越大,令牌成本就越高,因为法学硕士需要处理更多文本才能提供响应。然后建立一个最适合您的用例的内存窗口。一个技巧是检查来自客户的平均消息数,以了解互动的规模。我们将通过 MessageWindowChatMemory 展示实现,该类专门在 Langchain4j 中为我们管理此操作。

现在我们已经了解了所有这些概念和前提,让我们开始动手吧!

配置我们的 ChatMemoryStore

这里我们将使用 MongoDB 作为 ChatMemoryStore。我们使用 MongoDB 文档并将实例上传到 Docker。请随意配置它。

添加与 MongoDB 的连接

让我们首先添加必要的依赖项以使用 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种消息。

Agente de IA confiável em prod com Java   Quarkus   Langchain- Parte  Memória

我们系列的第二部分到此结束。我们希望您喜欢它并在第 3 部分中见到您。

以上是使用 Java Quarkus Langchain 构建可靠的 AI 代理 - 内存部分的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn