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

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

Nov 18, 2024 am 01:26 AM

作者

@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

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

<🎜>:泡泡胶模拟器无穷大 - 如何获取和使用皇家钥匙
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系统,解释
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆树的耳语 - 如何解锁抓钩
3 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

热门话题

Java教程
1673
14
CakePHP 教程
1429
52
Laravel 教程
1333
25
PHP教程
1278
29
C# 教程
1257
24
公司安全软件导致应用无法运行?如何排查和解决? 公司安全软件导致应用无法运行?如何排查和解决? Apr 19, 2025 pm 04:51 PM

公司安全软件导致部分应用无法正常运行的排查与解决方法许多公司为了保障内部网络安全,会部署安全软件。...

如何将姓名转换为数字以实现排序并保持群组中的一致性? 如何将姓名转换为数字以实现排序并保持群组中的一致性? Apr 19, 2025 pm 11:30 PM

将姓名转换为数字以实现排序的解决方案在许多应用场景中,用户可能需要在群组中进行排序,尤其是在一个用...

如何使用MapStruct简化系统对接中的字段映射问题? 如何使用MapStruct简化系统对接中的字段映射问题? Apr 19, 2025 pm 06:21 PM

系统对接中的字段映射处理在进行系统对接时,常常会遇到一个棘手的问题:如何将A系统的接口字段有效地映�...

如何优雅地获取实体类变量名构建数据库查询条件? 如何优雅地获取实体类变量名构建数据库查询条件? Apr 19, 2025 pm 11:42 PM

在使用MyBatis-Plus或其他ORM框架进行数据库操作时,经常需要根据实体类的属性名构造查询条件。如果每次都手动...

IntelliJ IDEA是如何在不输出日志的情况下识别Spring Boot项目的端口号的? IntelliJ IDEA是如何在不输出日志的情况下识别Spring Boot项目的端口号的? Apr 19, 2025 pm 11:45 PM

在使用IntelliJIDEAUltimate版本启动Spring...

Java对象如何安全地转换为数组? Java对象如何安全地转换为数组? Apr 19, 2025 pm 11:33 PM

Java对象与数组的转换:深入探讨强制类型转换的风险与正确方法很多Java初学者会遇到将一个对象转换成数组的�...

电商平台SKU和SPU数据库设计:如何兼顾用户自定义属性和无属性商品? 电商平台SKU和SPU数据库设计:如何兼顾用户自定义属性和无属性商品? Apr 19, 2025 pm 11:27 PM

电商平台SKU和SPU表设计详解本文将探讨电商平台中SKU和SPU的数据库设计问题,特别是如何处理用户自定义销售属...

如何利用Redis缓存方案高效实现产品排行榜列表的需求? 如何利用Redis缓存方案高效实现产品排行榜列表的需求? Apr 19, 2025 pm 11:36 PM

Redis缓存方案如何实现产品排行榜列表的需求?在开发过程中,我们常常需要处理排行榜的需求,例如展示一个�...

See all articles