チャットメモリ
ChatMessageを手動で維持・管理することは面倒です。
そのため、LangChain4jはChatMemory抽象化と複数の組み込み実装を提供しています。
ChatMemoryは、スタンドアロンの低レベルコンポーネントとして、
またはAIサービスのような高レベルコンポーネントの一部として使用できます。
ChatMemoryはChatMessageのコンテナ(Listによってバックアップされる)として機能し、以下のような追加機能があります:
- 退避ポリシー
- 永続性
SystemMessageの特別な扱い- ツールメッセージの特別な扱い
メモリと履歴
「メモリ」と「履歴」は似ているが異なる概念であることに注意してください。
- 履歴はユーザーとAIの間のすべてのメッセージをそのまま保持します。履歴はユーザーがUIで見るものです。実際に言われたことを表します。
- メモリは一部の情報を保持し、LLMに提示して会話を「記憶」しているかのように振る舞わせます。 メモリは履歴とはかなり異なります。使用されるメモリアルゴリズムによって、履歴をさまざまな方法で変更できます: 一部のメッセージを退避する、複数のメッセージを要約する、個別のメッセージを要約する、メッセージから重要でない詳細を削除する、 メッセージに追加情報(RAGの場合など)や指示(構造化出力の場合など)を挿入するなどです。
LangChain4jは現在「メモリ」のみを提供し、「履歴」は提供していません。完全な履歴を保持する必要がある場合は、手動で行ってください。
退避ポリシー
退避ポリシーはいくつかの理由で必要です:
- LLMのコンテキストウィンドウに収めるため。LLMが一度に処理できるトークン数には上限があります。 ある時点で、会話がこの制限を超える可能性があります。そのような場合、一部のメッセー ジを退避する必要があります。 通常、最も古いメッセージが退避されますが、必要に応じてより洗練されたアルゴリズムを実装することもできます。
- コストを制御するため。各トークンにはコストがあり、LLMへの各呼び出しが徐々に高価になります。 不要なメッセージを退避することでコストを削減できます。
- レイテンシーを制御するため。LLMに送信されるトークンが多いほど、処理に時間がかかります。
現在、LangChain4jは2つの組み込み実装を提供しています:
- よりシンプルな
MessageWindowChatMemoryは、スライディングウィンドウとして機能し、 最新のN個のメッセージを保持し、もはや収まらない古いメッセージを退避します。 ただし、各メッセージには様々な数のトークンが含まれる可能性があるため、MessageWindowChatMemoryは主に迅速なプロトタイピングに役立ちます。 - より洗練されたオプションは
TokenWindowChatMemoryで、 これもスライディングウィンドウとして機能しますが、最新のN個のトークンを保持することに焦点を当て、 必要に応じて古いメッセージを退避します。 メッセージは分割できません。メッセージが収まらない場合、完全に退避されます。TokenWindowChatMemoryは各ChatMessageのトークンを数えるためのTokenCountEstimatorを必要とします。
永続性
デフォルトでは、ChatMemory実装はメモリ内にChatMessageを保存します。
永続性が必要な場合、カスタムChatMemoryStoreを実装して、
選択した任意の永続ストアにChatMessageを保存できます:
class PersistentChatMemoryStore implements ChatMemoryStore {
@Override
public List<ChatMessage> getMessages(Object memoryId) {
// TODO: メモリIDによって永続ストアからすべてのメッセージを取得する実装。
// ChatMessageDeserializer.messageFromJson(String)と
// ChatMessageDeserializer.messagesFromJson(String)ヘルパーメソッドを使用して、
// JSONからチャットメッセージを簡単にデシリアライズできます。
}
@Override
public void updateMessages(Object memoryId, List<ChatMessage> messages) {
// TODO: メモリIDによって永続ストア内のすべてのメッセージを更新する実装。
// ChatMessageSerializer.messageToJson(ChatMessage)と
// ChatMessageSerializer.messagesToJson(List<ChatMessage>)ヘルパーメソッドを使用して、
// チャッ トメッセージをJSONに簡単にシリアライズできます。
}
@Override
public void deleteMessages(Object memoryId) {
// TODO: メモリIDによって永続ストア内のすべてのメッセージを削除する実装。
}
}
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.id("12345")
.maxMessages(10)
.chatMemoryStore(new PersistentChatMemoryStore())
.build();
updateMessages()メソッドは、新しいChatMessageがChatMemoryに追加されるたびに呼び出されます。
これは通常、LLMとの各対話中に2回発生します:
新しいUserMessageが追加されたときと、新しいAiMessageが追加されたときです。
updateMessages()メソッドは、指定されたメモリIDに関連付けられたすべてのメッセージを更新することが期待されています。
ChatMessageは、個別に(例:メッセージごとに1つのレコード/行/オブジェクト)
または一緒に(例:ChatMemory全体に対して1つのレコード/行/オブジェクト )保存できます。
ChatMemoryから退避されたメッセージはChatMemoryStoreからも退避されることに注意してください。
メッセージが退避されると、updateMessages()メソッドが呼び出され、
退避されたメッセージを含まないメッセージのリストが渡されます。
getMessages()メソッドは、ChatMemoryのユーザーがすべてのメッセージをリクエストするたびに呼び出されます。
これは通常、LLMとの各対話中に1回発生します。
Object memoryId引数の値は、ChatMemoryの作成時に指定されたidに対応します。
これを使用して、複数のユーザーや会話を区別できます。
getMessages()メソッドは、指定されたメモリIDに関連付けられたすべてのメッセージを返すことが期待されています。
deleteMessages()メソッドは、ChatMemory.clear()が呼び出されるたびに呼び出されます。
この機能を使用しない場合は、このメソッドを空のままにしておくこと ができます。
SystemMessageの特別な扱い
SystemMessageは特別なタイプのメッセージであるため、他のメッセージタイプとは異なる扱いを受けます:
- 一度追加されると、
SystemMessageは常に保持されます。 - 一度に保持できる
SystemMessageは1つだけです。 - 同じ内容の新しい
SystemMessageが追加された場合、それは無視されます。 - 異なる内容の新しい
SystemMessageが追加された場合、それは前のものを置き換えます。
ツールメッセージの特別な扱い
ToolExecutionRequestを含むAiMessageが退避された場合、
以下の孤立したToolExecutionResultMessageも自動的に退避されます。
これは、リクエストに孤立したToolExecutionResultMessageを送信することを禁止している
一部のLLMプロバイダー(OpenAIなど)との問題を回避するためです。
例
AiServicesを使用:- レガシーな
Chainを使用: