LangChain / LangGraph を使ってAgentを開発していると、比較的早い段階で直面する問題があります。
それが Contextの肥大化 です。
Agentが会話履歴、RAG検索結果、Tool実行結果、コード、計画、ユーザー要件などを保持し続けると、Context Windowはすぐに大きくなります。
単純に考えると、
古い会話を要約すればよい
となりがちですが、実際のAgent開発ではそれだけでは不十分です。
特に長時間動作するAgentでは、
- 会話履歴
- Tool結果
- RAG検索結果
- ユーザー要件
- Agentの計画
- 過去に確定した判断
- 現在の作業状態
をすべて同じ messages に詰め込むと、Contextの肥大化だけでなく、重要情報の消失や判断の不安定化につながります。
この記事では、LangChain / LangGraphを使ったAgent開発において、どのようにContextを圧縮・分離・管理すべきかを整理します。
Context圧縮は「要約」だけではない
AgentにおけるContext管理では、単純なSummarizationだけではなく、Contextを用途ごとに分けて考えることが重要です。
大きく分けると、以下の4種類があります。
Context
├── System / Rules
├── Recent Conversation
├── Old Conversation
└── Tool / RAG Results
それぞれに対して、同じ圧縮戦略を使うべきではありません。
例えばSystem Promptまで要約してしまうと、Agentの制約やセキュリティルールが失われる可能性があります。
一方で、数万Tokenある古いTool結果を永久に保持する必要もありません。
そのため、Contextごとに扱いを変えます。
| Context | 基本戦略 |
|---|---|
| System / Rules | 原則圧縮しない |
| Recent Conversation | 生データで保持 |
| Old Conversation | 要約 |
| Tool Results | 削除または圧縮 |
| RAG Results | Relevant部分だけ抽出 |
| Long-term Knowledge | 外部Memoryへ移動 |
1. Sliding Windowで古い会話を削除する
最も単純なContext圧縮は、古いメッセージを削除して、直近の履歴だけ保持する方法です。
例えば以下の履歴があるとします。
System
User1
Assistant1
User2
Assistant2
User3
Assistant3
...
User100
Assistant100
これを、
System
User95
Assistant95
User96
Assistant96
...
User100
Assistant100
のようにします。
LangChainでは trim_messages() を利用できます。
from langchain_core.messages import trim_messages
trimmed_messages = trim_messages(
messages,
max_tokens=8000,
strategy="last",
token_counter=model,
include_system=True,
start_on="human",
)
非常にシンプルで、Token消費を確実に抑えられるというメリットがあります。
一方で大きな問題があります。
例えば会話の最初に、
ユーザー:
データベースはPostgreSQLを使ってください。
JWT認証にしてください。
という重要な条件があった場合、それまで削除される可能性があります。
そのため、長時間動作するAgentではSliding Windowだけに依存する設計はおすすめできません。
2. 古いConversationをSummarizationする
より実践的なのが、
古いConversation
↓
Summary
最近のConversation
↓
Raw Message
という構成です。
例えば、
System Prompt
[Conversation Summary]
ユーザーはLaravelを使用している。
DBはMariaDB。
API認証にはJWTを採用。
Refresh TokenはHttpOnly Cookieに保存する方針。
[Recent Messages]
User:
Refresh Tokenの有効期限はどうしますか?
Assistant:
30日程度が候補です。
User:
30日にします。
というContextを作ります。
古い会話をすべて削除するのではなく、重要な内容だけSummaryとして残します。
LangChainではAgent MiddlewareとしてSummarizationを組み込むことができます。
例えば概念的には以下のような構成です。
from langchain.agents import create_agent
from langchain.agents.middleware import SummarizationMiddleware
agent = create_agent(
model=model,
tools=tools,
middleware=[
SummarizationMiddleware(
model=model,
trigger=("tokens", 20_000),
keep=("tokens", 5_000),
)
],
)
イメージとしては、
20,000 tokensを超える
↓
古いConversationを要約
↓
直近5,000 tokensはそのまま保持
という動きになります。
3. AgentではTool Resultの圧縮が非常に重要
Agent開発では、Conversation以上にTool ResultがContextを圧迫することがあります。
例えばAgentが、
search()
read_file()
query_database()
search()
read_file()
のようにToolを何度も実行したとします。
それぞれの結果が、
Tool Result 1: 15,000 tokens
Tool Result 2: 8,000 tokens
Tool Result 3: 20,000 tokens
となれば、数回のTool Callだけで数万Tokenを消費します。
しかし多くの場合、Agentが必要なのはToolの生データではありません。
必要なのは、
Tool実行結果から何が分かったのか
です。
例えば、
Before
User
Agent
Tool Result 20KB
Agent
Tool Result 30KB
Agent
Tool Result 10KB
User
という履歴を、
After
User
Agent
[Old Tool Result Removed]
Agent
[Old Tool Result Removed]
Agent
Tool Result 10KB
User
のようにします。
重要なのは、
Tool Resultそのものを記憶するのではなく、Tool Resultから得られた結論を記憶する
という考え方です。
Tool結果を永久保存する設計は、長時間動くAgentではほぼ確実にContext肥大化を引き起こします。
4. Semantic Compressionで「必要な情報だけ」を残す
Context圧縮では、単純に古い情報を削除するだけでなく、
現在のTaskに必要な情報だけを抽出する
という方法も重要です。
例えばRAGで10,000 Token分の検索結果が返ってきたとします。
しかしユーザーの質問が、
LaravelでJWT Middlewareを実装する方法
であれば、その10,000 TokenすべてをAgentに渡す必要はありません。
そこで、
Raw Search Result
↓
Retriever
↓
Reranker
↓
Relevant Extraction
↓
Agent
という処理を行います。
例えば、
10,000 tokens
↓ Relevant information only
1,500 tokens
まで圧縮します。
概念的には、別のLLMに以下のような依頼をします。
以下の検索結果から、
「Laravel JWT Middlewareの実装」
に必要な情報だけを抽出してください。
これによりContextを大幅に削減できます。
5. Long-term Memoryへ情報を逃がす
長時間動くAgentでは、
全部をConversation Historyに保存する
という設計自体を避けるべきです。
例えばユーザーについて、
Pythonを使用している
PostgreSQLを利用している
AWSを使っている
JWTを採用している
API prefixは /api/v1
という情報があるとします。
これらを毎回Conversation Historyとして保持する必要はありません。
代わりに、
Conversation
↓
Memory Extraction
↓
Long-term Memory
↓
必要なときだけRetrieval
という構成にします。
Agentへ渡すContextは、
System Prompt
Relevant Memory:
- Python
- PostgreSQL
- JWT
- API prefix: /api/v1
Recent Conversation:
...
という形になります。
つまり、
Short-term Memory
と、
Long-term Memory
を分離します。
6. Agent Stateをmessagesから分離する
ここが特に重要です。
Agent開発では、すべての状態を messages に保存する設計を避けた方が安定します。
例えばLangGraphのStateを以下のようにします。
from typing import TypedDict
class AgentState(TypedDict):
messages: list
goal: str
plan: list[str]
decisions: list[str]
findings: list[str]
modified_files: list[str]
summary: str
こうすると、
messages
は圧縮できます。
しかし、
goal
plan
decisions
findings
modified_files
は構造化されたStateとして保持できます。
例えば、
messages
↓
Summarization可能
goal
↓
保持
plan
↓
保持
decisions
↓
保持
modified_files
↓
保持
という設計です。
これにより、Conversationが圧縮されてもAgentが現在の目的を見失いにくくなります。
7. Coding Agentでは「会話」より「現在状態」を重要視する
Coding Agentではさらに重要なポイントがあります。
例えばAgentが、
auth.pyを修正しました
というAssistant Messageを持っていたとしても、その情報を信用し続けるべきではありません。
本当に重要なのは、
現在のauth.py
です。
つまり、
Conversation History
ではなく、
Filesystem
Git
Database
Current Application State
をSource of Truthにします。
例えば、
Agent:
auth.pyを変更しました
↓
Conversation Summary:
auth.pyをJWT対応に変更済み
としてしまうと、その後別のToolやユーザー操作によってコードが変わった場合に情報が古くなります。
そこでCoding Agentでは、
Filesystem
↓
Current Code Retrieval
↓
Agent
という構成にします。
Conversation Summaryは補助情報です。
8. Token Budgetを最初から設計する
Context Windowが128Kあるからといって、128Kをすべて自由に使う設計にするべきではありません。
例えば以下のように、用途別にToken Budgetを決めます。
System / Rules 5K
Long-term Memory 5K
Conversation Summary 10K
Recent Conversation 15K
RAG / Tool Results 40K
Working Context 20K
Output Reserve 15K
合計:
110K
残りをBufferとして確保します。
このように、
Context Window
ではなく、
Context Budget
として設計することが重要です。
9. 圧縮する優先順位を決める
すべてのContextを同じ優先度で扱うべきではありません。
実運用では、例えば以下の順番で削除・圧縮します。
1. 古いTool Result
↓
2. 重複したRAG Document
↓
3. 現在のTaskと無関係なConversation
↓
4. 古いConversationをSummary化
↓
5. Summary自体を再要約
逆に、なるべく削除しない情報は以下です。
System Prompt
Security Rules
User Requirements
Current Goal
Current Plan
Important Decisions
Current State
この区別を明確にしておくと、Agentの安定性がかなり変わります。
10. Fixed Thresholdだけで圧縮しない
典型的な実装では、
if token_count > 100_000:
summarize()
のような処理を実装しがちです。
しかし、この方法には問題があります。
例えばAgentが大規模なリファクタリングを行っている途中にSummaryが発生すると、
調査中の仮説
変更予定ファイル
未解決事項
途中の検証結果
などが失われる可能性があります。
そのため、より高度なAgentでは、
Tokenが増えたから圧縮する
だけではなく、
Taskの区切りで圧縮する
という方法も有効です。
例えば、
調査フェーズ完了
↓
圧縮
実装フェーズ完了
↓
圧縮
テストフェーズ完了
↓
圧縮
という考え方です。
Agent自身に、
現在の作業フェーズは圧縮して安全か?
を判断させる設計も可能です。
推奨するContext Architecture
実運用では、以下のような構成が扱いやすいです。
User Input
│
▼
Long-term Memory
Retrieval
│
▼
┌───────────────────┐
│ Context Builder │
│ │
│ System Prompt │
│ User Requirements │
│ Current Goal │
│ Important Facts │
│ Relevant Memory │
│ Summary │
│ Recent Messages │
│ Relevant Tools │
└─────────┬─────────┘
│
▼
LLM
│
▼
Tools
│
▼
Tool Compression
│
▼
LLM
ポイントは、
LLMへ渡すContext
と、
Agentが保持しているState
を分離することです。
LangGraphで設計するなら
個人的には、長時間動くAgentでは以下のような構成が扱いやすいです。
class AgentState(TypedDict):
messages: list
goal: str
current_task: str
plan: list[str]
decisions: list[str]
findings: list[str]
artifacts: list[str]
modified_files: list[str]
conversation_summary: str
LLM呼び出し前に、
context = build_context(state)
を行います。
例えば、
def build_context(state: AgentState):
return [
system_prompt,
format_goal(state["goal"]),
format_plan(state["plan"]),
format_decisions(state["decisions"]),
format_findings(state["findings"]),
format_summary(state["conversation_summary"]),
*get_recent_messages(state["messages"]),
]
という形です。
重要なのは、
State全部
を毎回LLMに渡さないことです。
必要な情報だけContext Builderが選択します。
実践的な構成
LangChain / LangGraphで本番Agentを作るのであれば、以下の組み合わせが扱いやすいです。
Summarization
+
Sliding Window
+
Tool Result Compression
+
Semantic Retrieval
+
Long-term Memory
+
Structured Agent State
+
Token Budget
つまり、単一のContext圧縮アルゴリズムを使うのではありません。
複数の戦略を組み合わせます。
Context Engineeringという考え方
最終的には、
Context Compression
というより、
Context Engineering
として考えた方がよいです。
重要なのは、
Context Windowにどれだけ情報を詰め込めるか
ではありません。
重要なのは、
今この瞬間のLLMに、何を見せるべきか
です。
Agentに必要な情報は、
保持する情報
検索する情報
圧縮する情報
捨てる情報
外部Stateとして管理する情報
に分ける必要があります。
まとめ
LangChain / LangGraphでAgentを開発する場合、Context圧縮を単純なConversation Summarizationとして考えると、長時間動作するAgentでは限界があります。
おすすめする考え方は次の通りです。
System / Rules
→ 原則保持
Recent Conversation
→ Rawで保持
Old Conversation
→ Summary化
Tool Results
→ 古いものを削除
RAG Results
→ Relevant部分のみ抽出
Important Facts
→ Structured State
User / Project Knowledge
→ Long-term Memory
Current Code / Files
→ Source of Truthから再取得
そしてLangGraphでは、
messages
だけでAgentを設計せず、
goal
plan
decisions
findings
artifacts
modified_files
summary
などをStructured Stateとして分離しておくと、Context圧縮によって重要情報を失いにくくなります。
長時間動くAgentを安定させるために重要なのは、
Contextを増やすこと
ではなく、
必要なContextだけを、その都度構築すること
です。
Agent開発において、Context Windowは単なるLLMの制限ではなく、設計対象そのものだと考えると、全体のアーキテクチャが整理しやすくなります。