0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LangChain / LangGraph AgentにおけるContext圧縮戦略 ─ 長時間動くAgentを壊さない設計

0
Posted at

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の制限ではなく、設計対象そのものだと考えると、全体のアーキテクチャが整理しやすくなります。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?