ホームページ >Java >&#&チュートリアル >Java Quarkus Langchain を使用した本番環境の信頼性の高い AI エージェント - メモリ部分

Java Quarkus Langchain を使用した本番環境の信頼性の高い AI エージェント - メモリ部分

DDD
DDDオリジナル
2024-11-18 01:26:02700ブラウズ

著者

@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 - ガードレール (近日公開予定)

導入

エージェントを作成するときは、LLM はいかなる種類の情報も保存しない、つまりステートレスであることに留意する必要があります。エージェントが情報を「記憶」できるようにするには、メモリ管理を実装する必要があります。 Quarkus は設定済みのデフォルト メモリをすでに提供していますが、十分な注意を払わないと、この Quarkus ドキュメントで説明されているように、利用可能な RAM メモリを爆破してエージェントを文字通りダウンさせる可能性があります。この問題を解決し、スケーラブルな環境でエージェントを使用できるようにするには、ChatMemoryStore が必要です。

コンセプト

私たちはエージェントと対話するためにチャットを使用します。エージェントとの対話が可能な限り最良の方法で行われ、本番環境でバグが発生しないようにするために、知っておく必要がある重要な概念があります。まず、彼とやり取りするときに使用するメッセージの種類を知る必要があります。

  • ユーザーメッセージ: エンドカスタマーによって送信されたメッセージまたはリクエスト。 Quarkus DevUI でメッセージを送信するときは、常に UserMessage を送信します。さらに、以前見たツール呼び出しの結果にも使用されています。

  • AI メッセージ (AiMessage): モデルからの応答メッセージ。 LLM がエージェントに応答すると、次のようなメッセージが届きます。このタイプのメッセージは、テキスト応答とツール実行リクエストの間で内容を交互に返します。

  • SystemMessage: このメッセージは開発時にのみ 1 回だけ定義できます。

3 種類のメッセージがわかったので、いくつかのグラフィックを使用してメッセージがどのように動作するかを説明しましょう。すべてのグラフィックは、Deandrea、Andrianakis、Escoffier によるプレゼンテーション「Java meets 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

この 2 番目のグラフは、「メモリ」をどのように管理すべきかを示しています。興味深い詳細は、メッセージ内で特定の順序を維持する必要があり、いくつかの前提条件を尊重する必要があるということです。

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

  • SystemMessage タイプのメッセージは 1 つだけである必要があります;
  • SystemMessage の後、メッセージは常に UserMessage と AiMessage の間でこの順序で切り替わる必要があります。 AiMessage の後に AiMessage がある場合、例外がスローされます。連続する UserMessage についても同様です。

注意すべきもう 1 つの重要な詳細は、ChatMemory のサイズです。インタラクションのメモリが大きければ大きいほど、LLM が応答を提供するためにより多くのテキストを処理する必要があるため、トークンのコストが高くなります。次に、ユースケースに最適なメモリウィンドウを確立します。ヒントの 1 つは、顧客からのメッセージの平均数を確認して、インタラクションの規模を把握することです。 Langchain4j でこれを管理することに特化したクラスである MessageWindowChatMemory を介して実装を示します。

これらの概念と前提をすべて理解したので、実際に手を動かしてみましょう!

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

最初にエンティティとリポジトリを作成する必要があるため、ベースへの接続をテストすることはできません。

エンティティとリポジトリの作成

次に、Interaction エンティティを実装しましょう。このエンティティには、メッセージのリストが作成されます。新しい顧客が接続するたびに、新しいインタラクションが生成されます。このインタラクションを再利用する必要がある場合は、同じインタラクション識別子を入力するだけです。

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 は、エージェントで使用するクラスです。その中に、リポジトリを使用してメッセージを MongoDB に保存する ChatMemoryStore を追加します。 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 内のメッセージをテストして検証できるようになりました。クエリを実行すると、ドキュメントのメッセージ配列内の 3 種類のメッセージを確認できます。

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

これでシリーズの第 2 部は終了です。楽しんでいただければ幸いです。パート 3 でお会いしましょう。

以上がJava Quarkus Langchain を使用した本番環境の信頼性の高い AI エージェント - メモリ部分の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。