9.4.3 短期記憶

「Agent の記憶」と聞くと、多くの人はまず「長期保存」を思い浮かべます。
でも、実際のシステムでは、最初に体験を左右するのは、むしろ短期記憶です。
システムが「今回のタスクで何が起きているか」を、しっかり覚えていられるか。
この節では、この“ワーキングメモリ”について学びます。
学習目標
- 短期記憶と長期記憶の違いを理解する
- なぜ会話履歴をすべて無限にモデルへ渡せないのかを理解する
- 会話ウィンドウ、実行時状態、要約記憶という3つの代表的な短期記憶の方法を押さえる
- 簡単な短期記憶マネージャーを読めるようになる
- 短期記憶でよくある失敗パターンを知る
短期記憶とは何か?
一言でいうと
短期記憶は、まず次のように理解できます。
システムが今このタスクを進めるために、一時的に保持しておくコンテキストと中間状態。
通常、次のようなものを含みます。
- 直近の数ターンの会話
- 現在のタスク目標
- 実行済みの手順
- 一時的な中間結果
長期記憶との違いは?
| 種類 | 何を重視するか |
|---|---|
| 短期記憶 | 今回のタスクで使う情報 |
| 長期記憶 | タスクをまたいでも、会話をまたいでも価値がある情報 |
例えば:
- 「ユーザーが前の発言で返金ポリシーを知りたいと言っていた」 -> 短期記憶
- 「このユーザーは簡潔な回答を好む」 -> どちらかというと長期記憶
なぜ履歴を全部ずっとモデルに入れられないのか?
コンテキストウィンドウは無限ではないから
モデルが見られるコンテキストの長さには限りがあります。
会話履歴をどんどん全部入れていくと、次の問題が起きます。
- token コストがどんどん増える
- 応答がどんどん遅くなる
- 重要な情報が埋もれる
情報が多いほど良いとは限らない
初心者の多くは、こう思いがちです。
「モデルに履歴を多めに渡しておけば、間違いは減るはず」
でも、必ずしもそうではありません。
コンテキストに関係ない内容が増えすぎると、モデルはむしろ次のように崩れやすくなります。
- 重点を取り違える
- 古い情報を繰り返す
- 本当に今やるべきことを見失う
だから短期記憶が本当に解決したいのは、「たくさん覚えること」ではなく、
限られた予算の中で、今いちばん役に立つ情報を残すこと。
短期記憶の代表的な3つの形
会話ウィンドウ(sliding window)
もっともシンプルな方法です。
- 直近 N ターンのメッセージだけを残す
メリット:
- シンプル
- 実装コストが低い
デメリット:
- 少し前の重要情報が押し出されて消える
実行時状態(task state)
チャット文だけを覚えるのではなく、次のような情報を明示的に持ちます。
- 現在のタスク目標
- どこまで調べたか
- 次に何をすべきか
この種の状態は、Agent にとって特に重要です。
要約記憶(summary memory)
履歴が長くなったら、すべて捨てるのではなく、まず要約に圧縮します。
例えば:
- 直近 4 ターンは原文のまま保持
- それより前は 1 段落の要約にする
これは非常によく使われる折衷案です。
もっとも簡単な短期記憶:スライディングウィンドウ
実行可能な例
messages = [
{"role": "user", "content": "こんにちは"},
{"role": "assistant", "content": "こんにちは、何をお手伝いしましょうか?"},
{"role": "user", "content": "返金ポリシーを知りたいです"},
{"role": "assistant", "content": "期間の条件と具体的な条件、どちらが気になりますか?"},
{"role": "user", "content": "まず期間の条件です"},
]
window_size = 3
short_term_memory = messages[-window_size:]
for msg in short_term_memory:
print(msg)
期待される出力:
{'role': 'user', 'content': '返金ポリシーを知りたいです'}
{'role': 'assistant', 'content': '期間の条件と具体的な条件、どちらが気になりますか?'}
{'role': 'user', 'content': 'まず期間の条件です'}
このコードはシンプルですが、すでにとても重要です
ここで学べる本質は次のことです。
短期記憶はまず、「どのメッセージを残すか」を選ぶ問題です。
すべての履歴が、そのまま持ち続ける価値があるわけではありません。
でも、メッセージウィンドウだけでは足りない
なぜ足りないのか?
次のような会話を考えてみましょう。
- ユーザーが「返金ポリシーを調べたい」と言う
- その後、別の細かい質問が何度か続く
- 10 ターン目にまた「この場合は返金できますか?」と聞く
もし直近 3 ターンだけを残していたら、システムはもう忘れているかもしれません。
- もともとのタスクはずっと「返金」に関することだった
だから Agent には構造化された状態も必要
例えば:
task_state = {
"goal": "ユーザーの返金条件を判断する",
"last_tool": "search_policy",
"latest_policy_result": "購入後 7 日以内かつ学習進捗が 20% 未満なら返金可"
}
print(task_state)
期待される出力:
{'goal': 'ユーザーの返金条件を判断する', 'last_tool': 'search_policy', 'latest_policy_result': '購入後 7 日以内かつ学習進捗が 20% 未満なら返金可'}
このような状態は、元の会話ログとは違って、もっとこういうものに近いです。
システムが今何をしているかを表す作業領域。
学習上わかりやすい短期記憶マネージャー
次の例では、以下の両方を管理します。
- 直近のメッセージ
- 現在のタスク状態
class ShortTermMemory:
def __init__(self, max_messages=4):
self.max_messages = max_messages
self.messages = []
self.state = {}
def add_message(self, role, content):
self.messages.append({"role": role, "content": content})
self.messages = self.messages[-self.max_messages:]
def update_state(self, **kwargs):
self.state.update(kwargs)
def snapshot(self):
return {
"messages": self.messages,
"state": self.state
}
memory = ShortTermMemory(max_messages=3)
memory.add_message("user", "返金ポリシーを調べたいです")
memory.add_message("assistant", "期間の条件と条件のどちらが気になりますか?")
memory.add_message("user", "まず期間の条件です")
memory.update_state(goal="返金資格の判断", topic="返金ポリシー")
print(memory.snapshot())
期待される出力:
{'messages': [{'role': 'user', 'content': '返金ポリシーを調べたいです'}, {'role': 'assistant', 'content': '期間の条件と条件のどちらが気になりますか?'}, {'role': 'user', 'content': 'まず期間の条件です'}], 'state': {'goal': '返金資格の判断', 'topic': '返金ポリシー'}}

この例が「履歴を保存するだけ」より強いのはなぜ?
短期記憶を2層に分けているからです。
- テキストのコンテキスト
- 構造化された状態
これは Agent システムではとても重要です。
要約記憶:メッセージが長くなってきたらどうする?
よくある戦略
実際のシステムでは、次のような方法がよく使われます。
- 直近の数ターンはそのまま保持する
- それ以前の履歴は要約に圧縮する
簡略化した例
old_messages = [
"ユーザーはまず返金ポリシーを質問した",
"その後、証明書の要件を質問した",
"最後にまた返金条件に戻った"
]
summary = "ユーザーの今回の主目的は、自分が返金条件を満たすかどうかを確認すること。途中で証明書についても少し質問した。"
recent_messages = [
{"role": "user", "content": "では、学習進捗が 30% なら返金できますか?"}
]
memory_package = {
"summary": summary,
"recent_messages": recent_messages
}
print(memory_package)
期待される出力:
{'summary': 'ユーザーの今回の主目的は、自分が返金条件を満たすかどうかを確認すること。途中で証明書についても少し質問した。', 'recent_messages': [{'role': 'user', 'content': 'では、学習進捗が 30% なら返金できますか?'}]}
これが、もっとも基本的な「要約 + 直近ウィンドウ」の考え方です。
短期記憶は Agent の中で何を解決するのか?
主に次の3つを解決します。
現在のタスクの一貫性を保つ
システムは、毎回ユーザーに初めて会ったかのようにやり直してはいけません。
複数ステップの実行で状態を失わない
例えば:
- どのツールを呼び出したか
- 何がわかったか
- あと何が足りないか
コンテキストコストを抑える
短期記憶は「覚えるため」だけのものではなく、次のためにもあります。
- 関係ない内容を減らす
- token コストを下げる
- 応答の安定性を上げる
短期記憶でよくある失敗パターン
少なすぎる
症状:
- システムが、ついさっき何を話していたかを突然忘れる
多すぎる
症状:
- コンテキストが長すぎて雑然とする
- 回答がそれる
- コストが上がる
メッセージだけ保存して、状態を保存しない
症状:
- 複数ステップのタスクで失敗しやすい
- ツール呼び出しの前後でつながりが悪い
状態だけ保存して、会話の原文を保存しない
症状:
- ユーザーの元の表現を失いやすい
- 口調、制約、細部が抜ける
だから短期記憶は、ふつう「1つだけ選べばいい」ものではなく、組み合わせて設計します。
初心者がよくハマる落とし穴
短期記憶と長期記憶を混同する
短期記憶が解決するのは、今のタスクです。ユーザープロフィール全体ではありません。
メッセージウィンドウは大きいほど安定すると考える
ウィンドウが大きすぎると、ノイズとコストも増えます。
構造化状態を無視する
これを無視すると、Agent は複数ステップのタスクで一気に不安定になります。
まとめ
この節でいちばん大事なのは、「ウィンドウ」や「要約」という言葉を覚えることではなく、次の主線をつかむことです。
短期記憶の目的は、履歴を無限に保存することではなく、限られたコンテキストの中で現在のタスクの一貫性を保つこと。
本当にうまく設計された短期記憶は、たいてい直近のメッセージとタスク状態の両方を持ち、必要に応じて要約圧縮も加えます。
練習
- この節の
ShortTermMemoryを拡張して、summaryフィールドをサポートしてみましょう。 - 最大メッセージウィンドウを 3 から 5 に変えて、
snapshot()の出力がどう変わるか観察しましょう。 - 考えてみましょう:ある Agent が「今どのツールをすでに呼んだか」をよく忘れるなら、まずメッセージウィンドウを強化しますか、それとも構造化状態を補いますか?
- 自分の言葉で説明してみましょう。なぜ短期記憶は「現在のタスクの一貫性」を解決するのであって、「長期的なユーザープロフィール」を解決するのではないのでしょうか?