🏆

ゼロから始めるContext Engineering

に公開

こんにちは!逆瀬川 (@gyakuse) です!

今日はContext EngineeringについてなぜContext Engineeringが大事かというのを最近のAI AgentのContext構造やキャッシュ機構の働きなどを見ながらまとめていきたいと思います。

Context Engineeringとは

定義としては

  • 推論において最適な情報群をキュレートしたり維持する一連の戦略 (Anthropic)

などが多くわりとふわっとしているのであんまり定義を振り返ることはありません。

Context Engineeringを考えるためには、現代のAI AgentがどのようなContext構造を持っているかを考えるのが一番です。実運用を見ることで、なぜContext Engineeringが必要になったのかを理解していきます。

AI AgentにおけるContext 構造

ここで現代のAI Agent (Coding Agentなど) のContext構造を振り返っていきましょう。

Coding Agentがどのように動いているかは先日まとめました:

https://zenn.dev/sakasegawa/articles/af8ede2e4d7da4

AI AgentはContextをずっと保持します。よくある形状は次のようなものです。

これがずっと連なっていきます。Coding Agentであれば、

となっていきます。Coding Agentを使った人ならよく分かると思います。

AI AgentにおけるContext Engineering

さて、このように積み重なるContextを見たときに、AI AgentにおけるContext Engineeringはより具体的になります。

  • System Instructionをどう書くか、何を入れて何を入れないか
  • ツールはどの粒度で・どのくらいの量(何個まで?)を与えればうまくいくか
  • 各ツール定義をどう書くか、どこまで詳細にするか
  • 実行の方針をどのように束縛するか (どのようにPlanを立ててContextの流れを拘束するか)
    • これは少しContext Engineeringから外れる感触もありますが、重要です
  • ツール実行時の観測結果をどう入れるか
  • Context Windowはどのくらいに制限すればいいかという問題
  • Context Caching をどう意識し、Context溢れをどうするか
    • ContextのReduction (削減) 戦略をどうするべきか

これ以外にも色んなトピックがありますが、結局は

  • 実行精度を上げつつ、Cacheを効かせるためのContextをうまく作る

ところに帰着するのだと個人的には思います。キャッシュめっちゃ大事。

Context Caching (Prompt Caching, Prefix Caching) による重大な制約

キャッシュはなぜ大事なのでしょうか?

めっちゃ安いからです (結論)

現代的なキャッシュ機構がどのように成立しているかはAppendixを参照してください。
主要なLLM ProviderのCacheについて見てみましょう(text, 1Mトークンあたりの価格)。

Model Input Input (Cache) Output
gemini-2.5-flash $0.300 $0.030 $2.500
gemini-3-pro-preview $2.000 $0.200 $12.000
claude-sonnet-4-5 $3.000 $0.300 $15.000
claude-opus-4-6 $5.000 $0.500 $25.000
gpt-5-mini $0.250 $0.025 $2.000
gpt-5.2 $1.750 $0.175 $14.000

わかりやすく、Cache Hitした場合は 1/10 の価格になっています。
これを例えばCoding Agentで考えてみます。以下のように前提を置いてみます。

  • 初期のToken数: 20k tokens
    • System Instruction + Tool定義 = 20k tokens
  • ユーザーリクエストからの処理ループ (1ループあたり): 1 inference, +300 token
    • 新規ツール呼び出しのための推論は100tokenの出力
    • ツール呼び出し結果のContextへの格納は200token
    • つまり1ループあたり1回推論が行われ、合計300token増えるとします
  • ループ数: 30回

このとき、以下のような結果となります。キャッシュを書き込むためのコストがあるのでシンプルな計算ではないですが、83-85%のコスト削減効果を生み、Cacheなしでは生きられないことがわかります。

Model Cacheなし Cacheあり 削減率
gemini-2.5-flash $0.2267 $0.0372 83.6%
gemini-3-pro-preview $1.4970 $0.2338 84.4%
claude-sonnet-4-5 $2.2365 $0.3632 83.8%
claude-opus-4-6 $3.7275 $0.6053 83.8%
gpt-5-mini $0.1886 $0.0307 83.7%
gpt-5.2 $1.3204 $0.2150 83.7%

さらにこれが2ターン目、3ターン目とループ回数が増えるごとに90%に漸近します (なお、Context Windowの実効制限があるため、だいたい85%安いというイメージを持つとよいでしょう)。キャッシュはスループットやレイテンシの恩恵もあるため、現代的なAI Agentでは必須のものとなります。

これはvLLM等でセルフホスティングした場合も同様です。(なお、細かい話ではありますが、vLLMではKVキャッシュをオフロードしたいときLMCacheを使うのがマストでしたが、コネクタが導入されCPUオフロードにも最近対応しました)

Context Engineeringのつらいところ

さて、このCacheの安さを見ることでContext Engineeringで避けたいことが見えてきます。それはコンテキストの逐次圧縮です。なぜかといえば、Context Cachingが効かなくなっちゃうからです。Contextはappend-onlyであるべきです。
しかし、最良の推論のためのコンテキストを考えると、逐次圧縮は重要です。
最良の推論のためのコンテキストを作るとき、以下のようなテクニックが思いつきます。

  • ツール定義情報を適応的に消す
    • これはとても消したい気持ちがあります
    • 途中でいらないと判断された不要なツール定義は削除したほうがAgentが迷わないと期待されるためです
  • ツール実行結果 (Observation) 情報を消す
    • これもクソデカJSONだったりもする上にハルシネーションの原因となったりするので不要なタイミングで消したいモチベーションが高いです。

Coding Agentの実装を見ると、その当たりの苦悩が見て取れます。
現代のCoding Agentは以下のような方針でコンテキストを作っています。

  • System Instrunction + ツール定義だけは絶対死守する (System Instructionを途中で書き換えたり、ツール定義情報を適応的に消すことは絶対しない)
  • ツール実行結果は不要になったタイミングで逐次消す実装もいくつかある

このため、不要になったタイミングで消す場合、実質的なキャッシュヒット率は30-50%程度に留まると思われます。

一方でCodexは基本的にAppend-only戦略を取っており、85%程度のキャッシュヒットを期待できます。個人的にはこっちのほうが気に入っています。

このコスト的な課題を改善するための研究は進んでおり、将来的にはContextWindowをブロック単位で制御し、これを消してこれを入れる、みたいなことをAPI Providerから提供されるLLMでもできるようになる未来は1-2年後には来ていそうです。
ただし、どのような実現方法でもこうした「入れ替え可能な」キャッシュはブロック間のCross-Attentionが弱くなるのが本質的に避けられないため、推論精度がそのまま向上するかはしっかり検証する必要があります。
Appendix: Context Cachingはどのように実現されているか の「先頭一致以外のキャッシュへの挑戦」項に論文等をまとめているので気になるかたはチェックしてみてください。

Coding AgentにおけるContext Engineering

このあたりは実は以前のCoding Agent回にもやっているのでますが、よりわかりやすくまとめられたらいいなと思います。

System InstructionのPrompt Engineering

モデルごとにプロンプトテンプレートを用意するのが標準的になっています。
基本的な構成としては以下のようになっています。

  • 基本的なプロンプト
  • 環境情報
    • 現在の作業ディレクトリ, OS/プラットフォーム情報, 日付, ファイルツリーの一部
  • ユーザー定義ルール ( AGENTS.md など)

ツール定義のPrompt Engineering

ツールのDescriptionには「どう使うべきか」「何をしてはいけないか」などのベストプラクティスを含めるように書かれます。AI Agentを作るとき、ここがめっちゃ大事です。AI Agentの主要資産はここにあります。

例としてOpenCodeのglobツールを見てみます。

基本説明:

  • あらゆる規模のコードベースで動作する高速なファイルパターンマッチングツール
  • "**/*.js" や "src/**/*.ts" のような glob パターンをサポート
  • 変更日時順にソートされた一致ファイルのパスを返す
  • 名前のパターンでファイルを探す必要がある場合にこのツールを使用する
  • 複数回の glob や grep を必要とする可能性があるオープンエンドな検索を行う場合は、代わりに Task ツールを使用する
  • 1 回のレスポンスで複数のツールを呼び出すことができる。潜在的に有用な検索は、まとめて推測的に複数実行する方が常に望ましい

パラメータの説明:

  • pattern
    • ファイルを照合するための glob パターン
  • path
    • 検索対象のディレクトリ。指定しない場合は、現在の作業ディレクトリが使用される。重要: デフォルトのディレクトリを使用する場合は、このフィールドを省略すること。"undefined" や "null" を入力してはならない。指定する場合は、有効なディレクトリパスでなければならない。

ツール実行結果 (Observation) の軽量化

  • Trunaction
    • read-file や shell 系ツールはクソデカ出力になることがあるのでContext Windowに入れる前に切り詰める必要があります。任意文字数を超えた場合、先頭xx文字までに切り詰めたり、中間を削除するような戦略が取られます (Head-Tail Truncation)。中間削除が一般的なようです

Context Windowの制限

  • Codex: モデルの95%を予算としています (最新モデルは272Kのため、258Kで発動)
  • gemini-cli: モデルの50%を予算としています (1Mトークン対応のため。0.5Mで発動)
  • Cline: おおむね使用するモデルのContext Windowの80%を予算としています (200Kなら160K)
  • OpenCode: 特定の割合リミットではなく、最大長-32000(あるいは出力上限)みたいなバッファをもった予算にしています (200Kモデルで8192tokenが最大出力の場合、191,808token以上で発動)

毎実行時のContext Reduction 戦略

ここでは gemini-cli に採用されている戦略を紹介します。これはOpenCodeなどでも実装されています。

  • Reverse Token Budget
    • まず古いツール結果のObservationを逐次切り詰めます。古いツール結果はTool Output Maskingされます
  • Tool Output Masking
    • ツール結果のObservationを tool-outputs 以下に保存し、<tool_output_masked>...Output too large. Full output available at: path/to/file...</tool_output_masked> というものを返却します

溢れそうになったときのContext Reduction 戦略

ここでも gemini-cli に採用されている戦略を紹介します。

  • 会話の削除
    • User/Assistant/Toolの長大なやりとりを直近を除いて削除します
  • サマリ (Snapshot) の作成
    • 「現在の全体目標」「アクティブな制約」「重要な知識(発見した事実)」「変更したファイルの履歴」「次のタスク」としてまとめます
  • Self-Correction
    • 上記のサマリに重要なファイルパスや制約が欠けていないか?等を自己批評させて必要なら修正します

まとめ

  • Context Engineeringはとても実践的で重要な部分なので実際にAI Agentを作って実感してみると楽しいです
  • Cacheはマジで大事です

Appendix: Context Cachingはどのように実現されているか

ここでは、なぜContext Caching (Prompt Caching) が可能なのか、その技術的背景を掘り下げていきます。

どうやってキャッシュしているか

Transformerの推論はprefillとdecodeの2段階で行われます。prefillではプロンプト全体を一括で処理し、decodeでは1トークンずつauto-regressiveで生成します。

Self-Attenの計算においてはQ, K, Vの行列が使われますが、Causal Attentionの特性により任意のトークンのKVはそれより前のトークンにのみ依存します。

この性質のおかげでdecode時に毎回すべてを再計算する必要がなくなります。嬉しみがあります。これをGPUメモリに保存するだけでいいじゃん、という発想からきたのがKV Cacheです。

そして、これはもちろん先程話したような複数回の推論を跨いでも成立し、使い回せます。これがContext CachingやPrompt CachingやPrefix Cachingの基本原理です。

キャッシュをどう保存するか

PagedAttention以前はキャッシュ領域をガッとメモリに確保していました。たとえば2048token分のメモリ確保するみたいな戦略です。しかしリクエストごとにtokenはもちろん変わります。リクエストが256tokenなら残りの1792トークン分のメモリは無駄です。

PagedAttentionはKV Cacheを固定サイズのページに分割します。ブロックサイズが50tokenなら、先の例だと6ブロックに収まり、最後のブロックだけ44token分無駄になりますが、だいぶ改善されます。

キャッシュをどう見つけるか

さて、キャッシュ化はされたのですが、うまく使われなきゃいけません。

vLLMのAPC(Automatic Prefix Caching)ではブロックをハッシュ化してそのハッシュテーブルを探索させる、というシンプルな方法を採用しています。

ここでのハッシュの作り方ですが、ブロック単位で独立ハッシュにしてはいけません。

なぜかというと先の例だとブロックが6つに分割されますが、2個目のブロックはCausal Attentionの特性により前のブロック(トークン列) に対して依存しているからです。よって、前のブロックのハッシュ + 今回のブロックのトークン列 に対してハッシュ化するべきなのです。

APCはこのような実装でハッシュを作り探索していますが、SGLangはRadixAttentionというまた異なるアプローチを取っています。

先頭一致以外のキャッシュへの挑戦

位置の制約がないキャッシュ機構に対するこころみはPosition-Independent Cachingと呼ばれ、もっとも研究がさかんな領域といえるでしょう。

CacheBlend (Yao et al., 2024) はすでにLMCacheに統合されており、各ブロックを独立して計算しておくのが特徴です。Cross-Attentionの欠落に対してはちょっとだけ再計算してカバーしています。EPIC (Hu et al., 2024) のLegoLinkアルゴリズムは再計算位置を静的に決め、小さいコストで決定するためのアイデアです。これ以外にもKVShare (Yang et al., 2025)やProphetKV (Wang et al., 2026) などどんどん新しい理論が出てきています。

References

Discussion