AIエージェントを作っていて、こんな経験はありませんか。「最初の2〜3ステップは完璧に動くのに、ステップが進むにつれて指示を無視しはじめ、最後には全然関係ない行動を取る」——。多くの開発者が「プロンプトが悪いのでは」「モデルが弱いのでは」と疑いますが、原因はそのどちらでもないことがほとんどです。真犯人は、推論のたびに膨れ上がるコンテキストウィンドウの中身です。
2025年9月、Anthropicが「効果的なコンテキストエンジニアリング」という設計分野を提唱して以降、これはAIエージェント開発の最重要スキルとして急速に確立しました。プロンプトエンジニアリングが「1回の指示をどう書くか」だとすれば、コンテキストエンジニアリングは「推論1回ごとに、コンテキストウィンドウへ何を・どの順で・どれだけ載せるか」を最適化する技術です。
この記事では、長時間・複数ステップで動くエージェントが「動けば動くほど精度が落ちる」現象(Context Rot)を、ネットワークの帯域管理になぞらえて整理し、LangGraph/Difyでの実装手順まで落とし込みます。筆者は約25年間ネットワークエンジニア(NOC/TAC)として「有限の帯域をどう使い切るか」と格闘してきた経験があり、その視点がコンテキスト設計と驚くほど相性が良いことに気づきました。
なぜ「コンテキストを増やすほど賢くなる」は嘘なのか
「コンテキストウィンドウが100万トークンあるなら、関連しそうな情報を全部詰め込めばいい」——これは典型的な誤解です。実際には、コンテキストに情報を足すほど、モデルの注意(アテンション)が薄まり、重要な情報を見落とすようになります。
この現象はContext Rot(コンテキスト腐敗)と呼ばれます。「干し草の中の針(needle-in-a-haystack)」型のベンチマークで観測されたもので、コンテキスト内のトークン数が増えるほど、モデルがそこから正確に情報を取り出す能力が低下していくことがわかっています。つまり、ハードな上限(コンテキスト長)に達するはるか手前から、1トークンあたりの「効き」は落ちていくのです。
Context Rotの4つの兆候
あなたのエージェントが以下のような挙動を見せ始めたら、Context Rotを疑ってください。
- 指示の風化:最初に与えたシステムプロンプトの制約(「必ず日本語で」「JSONで返す」など)を、ステップが進むと無視しはじめる。
- 古い情報への固執:すでに解決済み・上書き済みの古いツール結果を引っ張り出して、最新状態と矛盾した判断をする。
- ループと脱線:同じツールを無意味に呼び続けたり、本来のタスクから関係ない方向へ逸脱する。
- 後半の精度低下:会話・タスクの前半は正確なのに、後半になるほど雑な出力になる。
共通する原因は1つ。有限のアテンション予算を、価値の低いトークンに食い荒らされていることです。
関連記事:エージェントが「何を永続的に覚えるか」を扱うメモリ設計・長期記憶の設計ガイドとは別のレイヤーの話です。メモリ設計が「保存」の技術なら、コンテキストエンジニアリングは「推論時にその場で組み立てる」技術です。両者を区別することが、設計の出発点になります。
コンテキストウィンドウを「帯域」として扱う——予算管理の発想
ここでネットワークエンジニアの発想が効いてきます。コンテキストウィンドウを、有限の帯域(バンド幅)を持つ通信リンクだと考えてみてください。
| ネットワーク(NOC的視点) | コンテキストエンジニアリング |
|---|---|
| リンクの帯域は有限 | コンテキストウィンドウのトークン数は有限 |
| 輻輳(ふくそう)すると全体のスループットが落ちる | トークンを詰め込むほどアテンションが希薄化し精度が落ちる |
| 古い・不要なパケットは破棄(drop)する | 消費済み・古いツール結果は破棄する |
| 圧縮でペイロードを小さくする | 要約(compaction)で履歴を圧縮する |
| 必要な時だけ取りに行く(オンデマンド) | Just-in-Time取得で必要時だけ引く |
| QoSで優先度の高いトラフィックを保護 | システムプロンプト・直近の指示を優先的に保持 |
輻輳制御で大事なのは「リンクをパケットで埋め尽くさないこと」です。コンテキストも同じで、埋め尽くすことではなく、最小限の高シグナルなトークンで目的を達成することがゴールになります。Anthropicはこれを「望ましい結果を生む確率を最大化する、最小の高シグナルなトークン集合を見つけること」と表現しています。
そこで必要になるのがトークン予算管理(token budgeting)です。まず「このエージェントは1推論あたり最大◯トークンまで」という予算を決め、その内訳(システムプロンプト/ツール定義/履歴/RAG結果/ツール結果)を設計します。コスト削減が主目的のトークン節約設計とは動機が逆で、こちらは精度維持のために予算を切るのがポイントです(結果的にコストも下がります)。
テクニック1:コンテキスト圧縮(Compaction)
最も基本的かつ効果的なのが、膨れ上がった履歴を圧縮することです。アプローチは大きく3つあります。
① 要約(Summarization)
古いやり取りを削除する代わりに、走る要約(running summary)として圧縮します。「これまでの経緯」を1つの要約に畳み込み、それと直近のメッセージだけをモデルに渡すことで、文脈の連続性を保ちながらトークンを大幅に節約できます。長時間の会話やエージェントの軌跡(trajectory)に特に有効です。
② 刈り込み(Trimming)
直近のN件だけを残し、古いメッセージを単純に切り落とす方法です。実装が軽く高速ですが、切り落とした部分の情報は失われるため、重要な情報は要約やメモリ(永続化)に逃がしておく必要があります。
③ 古いツール結果の破棄(Tool Result Clearing)
見落とされがちですが効果絶大なのがこれです。一度使い終わった検索結果やファイル読み込み結果は、コンテキストに残し続ける必要がありません。消費済みのツール結果は「処理済み」とマークして本文から外すだけで、コンテキストの大部分を解放できるケースが多々あります。NOC的に言えば「ACKが返ったパケットをバッファから捨てる」イメージです。
テクニック2:サブエージェントへのコンテキスト分離
1つの巨大なコンテキストにすべてを詰め込むのをやめ、役割ごとにコンテキストを分離するのがマルチエージェント/サブエージェントの発想です。
- 親(オーケストレーター):高レベルな計画と指示だけを保持する。個々の調査の生データは持たない。
- 子(サブエージェント):割り当てられたサブタスク専用の、まっさらなコンテキストで作業する。完了したら結果だけを凝縮して親に返す。
こうすると、子が作業中に発生させた大量の中間トークン(検索結果、試行錯誤、ツール出力)は子のコンテキスト内で完結し、親を汚染しません。親は「サブタスクAの結論はこれ」という凝縮された情報だけを受け取るので、長時間タスクでも親のコンテキストがクリーンに保たれます。
このパターンは、中断・再開や冪等性を扱うステート管理の設計と組み合わせると、本番運用に耐える堅牢なエージェントになります。コンテキスト分離が「推論時の情報の流れ」を整理し、ステート管理が「タスクの永続的な進行状況」を管理します。
テクニック3:Just-in-Time(JIT)取得
従来のRAGは「関連しそうな文書を事前に全部コンテキストへ載せる(プリロード)」発想でした。しかしこれはContext Rotの主犯になりがちです。代わりに推奨されるのがJust-in-Time取得——「必要になった時だけ、必要な分だけ引く」設計です。
具体的には、エージェントに軽量な参照(ファイルパス、レコードID、検索クエリ)だけを持たせ、中身が必要になった瞬間にツール経由で取りに行かせます。人間が「全資料を机に広げる」のではなく「索引を見て必要なファイルだけ開く」のと同じです。
| 観点 | プリロード型(従来RAG) | Just-in-Time取得 |
|---|---|---|
| 初期コンテキスト | 大きい(関連文書を全投入) | 小さい(参照のみ) |
| Context Rotリスク | 高い | 低い |
| 不要な情報の混入 | 多い | 少ない(必要時のみ) |
| レイテンシ | 低い(取得済み) | やや高い(都度取得) |
| 向いているタスク | 短い・定型 | 長時間・探索的 |
実務では両者のハイブリッドが現実解です。明らかに毎回必要な情報はプリロードし、使うかどうか不確実な情報はJITに回す——この切り分け自体がトークン予算管理の一部です。
テクニック4:ツール結果のトリミングとスキーマ設計
エージェントのコンテキストを最も汚すのは、たいていツールの戻り値です。APIのレスポンスをそのまま丸ごとコンテキストへ流し込むと、不要なメタデータやネストしたJSONで一瞬にして数千トークンが消えます。
対策は2つ。
- 戻り値のトリミング:ツール側で、エージェントが実際に必要とするフィールドだけに絞って返す。例えば検索APIなら、全文ではなくタイトル+要約+IDだけを返し、本文はJITで取りに行かせる。
- スキーマ設計:ツールの出力スキーマを「コンパクトかつ自己説明的」に設計する。冗長なキー名を避け、エージェントが解釈に迷わない構造にする。
そしてツールそのものを増やしすぎないこと。すべてのツールは、コンテキストウィンドウ内の自分の定義スペースを「正当化」できなければなりません。機能が重複した曖昧なツールが10個あるより、明確に役割分担された5個のほうが、エージェントは正確に動きます。
LangGraph/Difyでの実装パターン
ここからは具体的な実装に落とし込みます。
LangGraph:トークン数で刈り込む
LangChain/LangGraphには trim_messages と count_tokens_approximately が用意されており、トークン数ベースで履歴を刈り込めます。
from langchain_core.messages.utils import (
trim_messages,
count_tokens_approximately,
)
# LLM呼び出しの直前に、直近メッセージを予算内へ刈り込む
trimmed = trim_messages(
state["messages"],
strategy="last", # 新しい方から残す
token_counter=count_tokens_approximately,
max_tokens=4000, # ← ここが「履歴の帯域予算」
start_on="human", # 切り口を人間メッセージに揃える
include_system=True, # システムプロンプトは必ず保持
)
LangGraph:要約ノードで圧縮する
削除ではなく圧縮したい場合は、LangMemの SummarizationNode を使うと、しきい値を超えたら自動で走る要約を生成し、同じメッセージを何度も再要約しないよう制御してくれます。
from langgraph.graph import StateGraph, START, MessagesState
from langgraph.checkpoint.memory import InMemorySaver
from langmem.short_term import SummarizationNode, RunningSummary
from langchain.chat_models import init_chat_model
model = init_chat_model("anthropic:claude-sonnet-4-5")
# 履歴を context フィールドに走る要約として保持
class State(MessagesState):
context: dict # RunningSummary を格納
summarization_node = SummarizationNode(
model=model,
max_tokens=2048, # 要約後の目標トークン
max_tokens_before_summary=2048, # この閾値を超えたら要約が発火
max_summary_tokens=512,
)
LangGraph:コンテキスト使用率を「可視化」する
NOCで帯域使用率を常時モニタするように、エージェントでもコンテキスト使用率を計測しておくと、Context Rotの予兆を数字で捉えられます。
BUDGET = 8000 # 1推論あたりのコンテキスト予算(トークン)
def log_context_usage(state):
used = count_tokens_approximately(state["messages"])
ratio = used / BUDGET
print(f"context usage: {used}/{BUDGET} ({ratio:.0%})")
if ratio > 0.8:
print("⚠ 予算の80%超——要約/刈り込みの発火を検討")
return state
この log_context_usage を各ノードの前後に挟むだけで、「どのステップでコンテキストが膨張するか」が一目でわかります。問題の切り分け(トラブルシューティング)の第一歩は、まず計測することです。
Difyの場合
ローコードのDifyでは、ノードを直接書く代わりに設定で同等の効果を狙います。
- 会話変数(Conversation Variables)で「これまでの要約」を保持し、LLMノードのプロンプトに直近メッセージ+要約だけを渡す(手動コンパクション)。
- ナレッジ取得(Knowledge Retrieval)ノードのTop-Kと再ランクを絞り、RAG結果でコンテキストを膨らませすぎない。
- 長い処理は複数のワークフローへ分割し、サブワークフローには必要な変数だけを渡す(=サブエージェント分離の擬似実装)。
- ツールノードの出力を、後続ノードで使うフィールドだけに変数経由で絞り込む。
導入の優先順位——どれから手を付けるか
すべてを一度に入れる必要はありません。費用対効果の高い順に並べると、おおむね次のとおりです。
| 優先度 | 施策 | 効果 | 実装コスト |
|---|---|---|---|
| 1 | 古いツール結果の破棄 | 大 | 小 |
| 2 | トークン数ベースの刈り込み | 大 | 小 |
| 3 | コンテキスト使用率の計測・可視化 | 中(予防効果) | 小 |
| 4 | 要約(コンパクション) | 大 | 中 |
| 5 | Just-in-Time取得への移行 | 大 | 中 |
| 6 | サブエージェントへのコンテキスト分離 | 大 | 大 |
まず「破棄」と「刈り込み」と「計測」の3点セットから始めるのが、最小の労力で最大の改善を得る定石です。
よくある質問(Q&A)
Q1. メモリ設計とコンテキストエンジニアリングは何が違うの?
メモリ設計は「何を永続化して、後から思い出せるようにするか(保存側)」の話です。コンテキストエンジニアリングは「今この1回の推論で、コンテキストウィンドウに何を載せるか(組み立て側)」の話です。メモリに保存した情報も、最終的にはコンテキストへ”載せる”必要があり、その載せ方を最適化するのがコンテキストエンジニアリングだと考えると整理しやすいです。
Q2. コンテキストウィンドウが大きいモデルを使えば解決しない?
しません。Context Rotは「ハードな上限」の問題ではなく、「トークンが増えるほど1トークンあたりのアテンションが薄まる」という、もっと手前で起きる問題だからです。窓を広げても、詰め込めば詰め込むほど精度は落ちます。窓の広さは余裕にはなりますが、設計を不要にはしません。
Q3. 要約で重要な情報が失われないか心配です。
その懸念は正しいです。だからこそ「絶対に失ってはいけない情報」は要約任せにせず、メモリ(永続ストア)やステートの構造化フィールドに明示的に逃がしておきます。要約は「会話の流れ」を保つためのもので、「正確な事実」の保存先としては不適切、と役割を分けるのがコツです。
Q4. 小規模なチャットボットでもここまで必要?
数ターンで終わる短い対話なら、刈り込みだけで十分なことが多いです。本記事の手法が効くのは、長時間・複数ステップ・ツール多用のエージェントです。タスクが長くなるほど、コンテキスト設計の有無が成否を分けます。
Q5. プロンプトエンジニアリングはもう不要になる?
不要にはなりません。コンテキストエンジニアリングはプロンプトエンジニアリングの「自然な進化形」であり、上位互換ではなく包含関係です。良いシステムプロンプト(明確で簡潔、構造化された指示)は、依然としてコンテキスト設計の核です。
まとめ——「詰め込む」から「絞り込む」へ
AIエージェントが「長く動くほど精度が落ちる」のは、モデルの能力不足でもプロンプトの不備でもなく、多くの場合コンテキストウィンドウの設計不在が原因です。要点を3つに絞ります。
- コンテキストは有限の「帯域」。埋め尽くすのではなく、最小の高シグナルなトークンで目的を達成する。
- Context Rotは上限の手前で始まる。大きい窓は解決策ではない。破棄・刈り込み・要約で能動的に管理する。
- 分離と Just-in-Time。サブエージェントへ文脈を分け、情報は「必要な時だけ」引く。そして使用率を計測して予兆を数字で捉える。
プロンプトを磨く時代から、コンテキストという情報生態系をオーケストレーションする時代へ。「詰め込む」発想を捨て、「絞り込む」設計へ切り替えることが、2026年のエージェント開発者に求められる新しいリテラシーです。
参考リンク
- Anthropic「Effective context engineering for AI agents」(2025年9月29日)
- Claude Cookbook「Context engineering: memory, compaction, and tool clearing」
- LangChain Docs「Memory(trim / summarize / delete)」
- LangMem「How to Manage Long Context with Summarization」
あわせて読みたい:メモリ設計・長期記憶の設計ガイド/ステート管理の設計/トークン節約設計
免責事項:本記事は2026年5月時点の公開情報および各種ドキュメントに基づく情報提供です。コンテキストエンジニアリングの手法・各フレームワークのAPIは急速に変化しているため、実装時には必ず公式ドキュメントで最新の仕様をご確認ください。掲載したコードは概念を示すサンプルであり、本番投入前には十分な検証を行ってください。

コメント