メインコンテンツへスキップ

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': 'まず期間の条件です'}

このコードはシンプルですが、すでにとても重要です

ここで学べる本質は次のことです。

短期記憶はまず、「どのメッセージを残すか」を選ぶ問題です。

すべての履歴が、そのまま持ち続ける価値があるわけではありません。


でも、メッセージウィンドウだけでは足りない

なぜ足りないのか?

次のような会話を考えてみましょう。

  1. ユーザーが「返金ポリシーを調べたい」と言う
  2. その後、別の細かい質問が何度か続く
  3. 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': '返金ポリシー'}}

短期記憶 snapshot 結果図

この例が「履歴を保存するだけ」より強いのはなぜ?

短期記憶を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 は複数ステップのタスクで一気に不安定になります。


まとめ

この節でいちばん大事なのは、「ウィンドウ」や「要約」という言葉を覚えることではなく、次の主線をつかむことです。

短期記憶の目的は、履歴を無限に保存することではなく、限られたコンテキストの中で現在のタスクの一貫性を保つこと。

本当にうまく設計された短期記憶は、たいてい直近のメッセージとタスク状態の両方を持ち、必要に応じて要約圧縮も加えます。


練習

  1. この節の ShortTermMemory を拡張して、summary フィールドをサポートしてみましょう。
  2. 最大メッセージウィンドウを 3 から 5 に変えて、snapshot() の出力がどう変わるか観察しましょう。
  3. 考えてみましょう:ある Agent が「今どのツールをすでに呼んだか」をよく忘れるなら、まずメッセージウィンドウを強化しますか、それとも構造化状態を補いますか?
  4. 自分の言葉で説明してみましょう。なぜ短期記憶は「現在のタスクの一貫性」を解決するのであって、「長期的なユーザープロフィール」を解決するのではないのでしょうか?