首頁 >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