はじめに
「コンテキストウィンドウが大きいほど高性能」と思っていませんか?
実は、LLMに 情報を与えれば与えるほど、回答精度が下がる という逆説的な現象が起きることがあります。これが コンテキストロット(Context Rot) です。
本記事では、AIエージェント開発において避けて通れないこの問題について、原因から対策まで解説します。
コンテキストロットとは
コンテキストロットとは、LLMが処理する入力コンテキストが長くなるにつれて、パフォーマンスが劣化する現象です。
技術的な上限(例:100万トークン)の範囲内であっても、コンテキストウィンドウが埋まるにつれてLLMの性能は低下します。
Stanfordの研究では、わずか20件のドキュメント(約4,000トークン)を与えただけで、LLMの精度が 70-75%から55-60%に低下 したと報告されています。情報が間違っているわけでも欠けているわけでもなく、モデルが単に注意を払わなくなるのです。
なぜ起こるのか?
Transformerアーキテクチャの制約
現代のLLMはTransformerアーキテクチャに基づいており、すべてのトークンが他のすべてのトークンに対して注意(Attention)を向けます。
\text{計算量} = O(n^2)
つまり、トークン数に対して 二乗のオーダー で計算量が増加します。
| トークン数 | 追跡すべき関係の数 |
|---|---|
| 10,000 | 1億 |
| 100,000 | 100億 |
| 1,000,000 | 1兆 |
コンテキストが長くなるほど、モデルはペアワイズの関係を捕捉する能力が希薄化し、重要な情報への注意が散漫になります。
Lost-in-the-Middle問題
研究者たちは 「Lost-in-the-Middle(中間で失われる)」問題 を発見しました。
モデルは、コンテキストウィンドウの 最初と最後 に置かれた情報には高い精度で応答しますが、中間に埋もれた情報 は見落としやすくなります。同じ情報でも、配置位置によって精度が大きく変わるのです。
AIエージェントにおける影響
エージェントシステムでは、コンテキストロットの影響が特に深刻です。
エージェントが複数のステップを経てタスクを実行すると、コンテキストウィンドウは累積的に成長し、「注意の希薄化」が起こります。重要な制約が埋もれ、ツール選択がドリフトし始めます。
実際に起こる問題
- ツール選択のドリフト - 最初に与えられた指示を忘れ、不適切なツールを選択
- 制約の無視 - セキュリティ制約や出力形式の指定を見落とす
- フォーカスの喪失 - 本来のタスクから逸脱した行動を取る
対策
1. JIT(Just-in-Time)検索
すべての情報を事前にロードするのではなく、必要な時に必要な情報だけを取得 する戦略です。
# ❌ Bad: すべての情報を事前にロード
context = load_all_documents()
response = llm.generate(context + query)
# ✅ Good: 必要な時に必要な情報だけを取得
def jit_retrieve(query):
relevant_docs = vector_db.search(query, top_k=3)
return llm.generate(relevant_docs + query)
Claude Codeもこの戦略を採用しており、情報が関連するときにのみ取得することで、モデルの推論空間をクリーンに保っています。
2. コンテキスト圧縮(Compaction)
冗長な情報を取り除き、参照だけを保持する方法です。
# ❌ Bad: ファイル内容をすべて履歴に保持
chat_history.append({
"role": "assistant",
"content": f"Created file with content:\n{file_content}" # 500行のコード
})
# ✅ Good: パスのみを保持(可逆的)
chat_history.append({
"role": "assistant",
"content": "Output saved to /src/main.py"
})
Compactionは 可逆的 です。後でコードを読む必要があれば、ツールを使ってファイルを読み取れます。
3. コンテキスト要約(Summarization)
一定のトークン数(例:128kトークン)に達したら、LLMを使って履歴を要約します。
def summarize_if_needed(context, threshold=128000):
if count_tokens(context) > threshold:
# 最新のツール呼び出しは詳細を保持
recent = context[-5:]
# 古い部分は要約
old_summary = llm.summarize(context[:-5])
return old_summary + recent
return context
要約は 非可逆的(Lossy) です。重要な情報が失われる可能性があるため、最新のやり取りは生のまま保持することが推奨されます。
4. マルチエージェント構成
複雑なタスクを小さなサブタスクに分割し、専門のサブエージェントに処理させます。
各エージェントは クリーンなコンテキスト で動作し、必要な結果のみを親エージェントに返します。これにより、コンテキストの肥大化を防げます。
5. Pre-Rot Threshold の設定
コンテキストロットが発生する前に、積極的に対策を実行します。
# モデルの公称上限ではなく、実効的な上限を設定
PRE_ROT_THRESHOLD = 256_000 # 1Mウィンドウでも256k以下で管理
def process_with_context_management(context, query):
if count_tokens(context) > PRE_ROT_THRESHOLD:
context = compact_or_summarize(context)
return llm.generate(context + query)
モデルが100万トークンのコンテキストウィンドウを持っていても、性能劣化は多くの場合256k以下で始まります。APIエラーを待つのではなく、事前に対策を打ちましょう。
まとめ
| 問題 | 対策 |
|---|---|
| 情報過多による注意散漫 | JIT検索で必要最小限の情報だけを提供 |
| 履歴の肥大化 | Compactionで参照のみ保持 |
| 長期タスクでの性能劣化 | 要約とPre-Rot Thresholdの設定 |
| 複雑なワークフロー | マルチエージェント構成で責務分離 |
コンテキストロットは、AIエージェント開発における「静かなる脅威」です。大きなコンテキストウィンドウを持つモデルでも、適切なコンテキストエンジニアリング なしには、その能力を十分に発揮できません。
「情報は多ければ多いほど良い」のではなく、「適切な情報を適切なタイミングで」 が、信頼性の高いAIエージェント構築の鍵です。