🐡

プロンプトからコンテキストへ:Anthropic流コンテキストエンジニアリングとLangGraph・ACEの実践入門

に公開

1. 導入:なぜ「プロンプトエンジニアリング」から「コンテキストエンジニアリング」へ?

2023年が「プロンプトエンジニアリング」の年だったとすれば、2024年から2025年にかけては「コンテキストエンジニアリング」が重要なキーワードになりつつあります。

Claude 3.5 Sonnet や Gemini 1.5 Pro など、LLM のコンテキストウィンドウは拡大の一途を辿っています。「20万トークン、100万トークン扱えるなら、全部突っ込めばいいのでは?」と最初は誰もが思いました。しかし、現場で開発を進めるエンジニアたちは気づき始めています。

  • 「全部入れると、逆に精度が落ちる(Context Rot)」
  • 「無関係な情報がハルシネーションを引き起こす」
  • 「コストとレイテンシが爆発する」

この課題に対し、Anthropic は "Effective context engineering for AI agents" という記事で体系的な解を示し、LangChain/LangGraph はそれを実装コードに落とし込み、さらにアカデミアからは Agentic Context Engineering (ACE) という「自己改善するコンテキスト」の概念が登場しました。

この記事では「プロンプトエンジニアリングの次のステップ」としてのコンテキスト設計の全体像と実装イメージを解説します。


2. Anthropic が語る「コンテキストエンジニアリング」の核心

まずは、Anthropic の技術記事(Effective context engineering for AI agents)をベースに概念を整理します。

2-1. コンテキストエンジニアリングとは何か

プロンプトエンジニアリングとコンテキストエンジニアリングは、似て非なるものです。

  • Prompt Engineering: 「どんな指示文(Instruction)を書くか」にフォーカスする技術。
  • Context Engineering: 「どの情報を、いつ、どれだけ見せるか」を設計する技術

背景としては、LLMアプリの初期はユーザの要求に対してただ返答するだけの所謂"点のような単発タスク"が中心であったが、ファイルの編集をしたり、APIを複数回たたいて帰ってきたデータから次の作業を決めるなど、"線のような複数のタスク"をこなすようなAIエージェント系アプリが登場し、コンテキスト全体の管理戦略が重要になったことが起因しています。

情報量(コンテキスト)が増えすぎると重要な指示を見つけ出すことが難しくなり性能が低下する「Context Rot(コンテキストの腐敗)」が発生します。
また、LLM の背後にある Transformerアーキテクチャは、各トークンペア同士で関連性を算出するため、コンテキスト長に対して計算量が二乗(n^2)で増加する特徴があるため、一つ一つの情報への注意力が落ちる「Attention Budget(注意力の予算)」も懸念されています。

つまり、「情報は多ければ多いほど良い」は間違いであり、エージェントの成功率を高めるには、必要な情報だけをピンポイントで供給する設計が必要です。

2-2. 効果的なコンテキストとは

効果的なコンテキストとは、以下の要素が挙げられます。

  1. System Prompt:
    • システムプロンプトは極めて明確で、エージェントにとって適切なレベルでアイデアを提示する、シンプルで直接的な言語を使用する必要があります。動作を効果的に導くのに必要十分な具体的さと、モデルに動作を導くための強力なヒューリスティックを提供できるほど柔軟性のあるバランスの取れたものが望ましいです。
    • <background_information>, <instructions>, ## Output Format のように XMLタグ等で明確に構造化する。
  2. Tools / MCP:
    • ツール定義もコンテキストの一部であり、意図された用途が極めて明確である必要があります。入力パラメータも同様に、説明的で明確であり、モデル固有の強みを活かすツール設計でなければなりません。
  3. Few-shot Examples:
    • 「すべてのケース」ではなく「代表的で多様なエッジケース」を少数含めるのが鉄則です。

2-3. 「Just-in-Time Search」へ

従来は、質問に関連しそうなドキュメントを事前に検索してプロンプトに埋め込んでいました(Pre-retrieval)。しかし、Anthropic はより動的な "Just-in-Time" アプローチを推奨しています。

  • 識別子だけ持たせる: 最初は軽量の識別子(ファイルパス、保存されたクエリ、Webリンクなど)だけを渡す。
  • 必要になったら取りに行く: エージェントが自律的に「ファイルAの中身を読む」「テーブルBをクエリする」というツールを実行する。

Claude Code などの高度なツールでは、ファイルの head だけを読んでから、必要に応じて全体を読むといった制御が行われています。

2-4. 長期タスクのための3つの戦略

会話やタスクが長引いた際、コンテキストを維持するための3つのテクニックがあります。

戦略 概要
Compaction 過去の会話を要約し、コンテキストを再構築する。
Memory / Notes NOTES.md や ToDo リストのような外部メモリに重要な事実を書き残す。
Sub-agents サブエージェントに探索させ、親には「結果のサマリ」だけを戻す。

3. LangGraph 実践:会話履歴『圧縮』を実装してみた

前項で述べたように、コンテキストウィンドウが大きくなっても「全部突っ込む」のは悪手です。特に会話ボットでは、挨拶や過去の無関係な発言が溜まり、トークンコストが高騰し、LLMの応答精度(注意力の予算)が分散してしまいます。

この課題を解決するため、ここでは LangGraph を使って 「会話履歴が一定量を超えたら、自動で要約・圧縮を行う」 仕組みを実装します。

3-1. LangGraph の構造: 条件付き圧縮の実現

「会話履歴の件数」をトリガーにして、summary ノード(圧縮処理)を通るか、スキップするかを自動で判断させる例を実装してみます。

ワークフローの構成は以下の通りです。

ノード/エッジ 役割
generate_reply LLMによる応答生成
summary 圧縮(Compress):会話履歴を要約し、要約メッセージに置き換える。
route_from_start 履歴が 5 件以上なら summary に、そうでなければ generate_reply に分岐させる。

3-2. 実装コードの解説

① グラフの State 定義と LLM の初期化

会話履歴を格納する MessagesState を定義します。

# --- State 定義: 会話履歴のみを持つ State ---
class MessagesState(TypedDict):
    # これまでの会話履歴(要約ノードが実行されると、要約メッセージ + 直近の会話に圧縮される)
    messages: List[BaseMessage]

# (中略: 環境変数とLLMの初期化)

# ワークフロー内で利用するチャットモデルを初期化
llm = init_chat_model("anthropic:claude-sonnet-4-20250514", temperature=0)

② 履歴数によるルーティング

**「会話履歴が 5 件以上の場合にのみ、summary ノードへ進む」**という条件分岐を route_from_start 関数で定義します。

def route_from_start(state: MessagesState) -> str:
    """
    START からのエッジ条件関数。
    - 会話履歴が 5 件以上たまっていれば 'compress' を返し summary_node へ分岐
    - 5 件未満であれば 'no_compress' を返し、直接 generate_reply へ進む
    """
    num_messages = len(state.get("messages", []))
    route = "compress" if num_messages >= 5 else "no_compress"

    # どちらのルートに進むかをログ出力
    print("\n[route_from_start] ==============================")
    print(f"[route_from_start] messages 数: {num_messages}")
    print(f"[route_from_start] 選択されたルート: {route}")
    print("[route_from_start] ==============================\n")

    return route

# (中略: グラフ構築部分)

workflow.add_conditional_edges(
    START,
    route_from_start,
    {
        "compress": "summary",      # 履歴 5 件以上: 要約 & 圧縮を実行
        "no_compress": "generate_reply",  # それ未満: そのまま応対ノードへ
    },
)

③ 会話履歴の圧縮(Compress/Compaction)

summary_node 関数が、コンテキスト圧縮のコアロジックです。
LLM に要約プロンプトを与えて要約文を生成させ、生の会話履歴をその要約メッセージ 1 件に置き換えることで、大幅なトークン削減を実現します。

def summary_node(state: MessagesState) -> dict:
    """
    会話履歴にもとづいて要約を生成し、
    その要約を含むメッセージリストに「置き換える」ノード。(Compress/Compaction)
    """
    messages = state["messages"]

    # 要約用のシステムプロンプト
    summarization_prompt = (
        "以下は、ユーザーとアシスタントのこれまでの会話です。\n"
        "重要な事実・ユーザーの好み・制約条件に注目して、日本語で 3〜5 行に要約してください。\n"
        "具体的な好み(例: サウナが好き、辛いものが好き、パクチーが苦手 など)があれば必ず含めてください。"
    )

    # 要約システムプロンプトを先頭に付けて LLM を呼び出す
    llm_messages = [SystemMessage(content=summarization_prompt)] + messages
    result = llm.invoke(llm_messages)
    summary_text = getattr(result, "content", str(result))

    # 要約メッセージ(HumanMessageとして埋め込む)
    summary_message = HumanMessage(
        content="これまでの会話の要約です。以下を踏まえて続きの会話に応答してください。\n\n"
        + summary_text
    )

    # 会話履歴を圧縮: 要約メッセージ 1 件のみを残し、生の履歴をすべて捨てる
    compressed_messages = [summary_message]

    # ログ出力で圧縮効果を確認
    print("\n[summary_node] ==============================")
    print(f"[summary_node] 元のメッセージ数: {len(messages)} -> 圧縮後: {len(compressed_messages)}")
    # ... (後略) ...

    return {
        "messages": compressed_messages,
    }

3-3. 実行結果:トークン圧縮の確認

初期の会話履歴(12件の会話 + 1件の最新発話で合計 13 件)を用意し、このワークフローを実行します。

# 実行結果: 圧縮ノードが実行され、履歴が 13 件から 1 件に圧縮されることを確認

# ... (コード内の実行部分) ...

final_state = chain.invoke(initial_state)

# ... (最終結果の表示) ...

ログ出力(コンソール)のハイライト

実行ログを見ると、summary ノードが実行され、コンテキストが劇的に削減されたことがわかります。

[route_from_start] ==============================
[route_from_start] messages 数: 13
[route_from_start] 選択されたルート: compress
[route_from_start] ==============================


[summary_node] ==============================
[summary_node] 元のメッセージ数: 13 -> 圧縮後: 1
[summary_node] 要約プレビュー(先頭3行):
**会話の要約:**
ユーザーは最近仕事が忙しく疲れ気味で、平日はほとんど在宅勤務をしている。サウナが好きで週末によく通い、韓国料理やタイ料理などの辛いものを好むが、パクチーは苦手。また、静かな雰囲気のカフェで読書することも好きで、在宅勤務のため家にこもりがちになることを気にしている。

[summary_node] ==============================


==============================
User: 今日は在宅勤務なんだけど、合間にできて、サウナや辛いもの好きにも合いそうなリフレッシュって何かある?
AI  : お疲れさまです!在宅勤務の合間にできるリフレッシュ方法をいくつかご提案させていただきましたが、いかがでしょうか?

特に今の季節でしたら、**辛いものでランチ**や**辛いお茶・スープ**は体も温まって一石二鳥かもしれませんね。キムチチゲなら簡単に作れますし、市販のトムヤムクンの素を使えば手軽にタイ料理気分も味わえます。

また、**自宅での温冷浴**も、サウナがお好きでしたら気に入っていただけるかもしれません。仕事の合間に5-10分程度でも、気分転換になりそうです。

どの提案が一番興味を引きましたか?それとも、他にも何かお困りのことや気になることがございましたら、お気軽にお聞かせください。あなたの在宅勤務がより快適になるよう、一緒に考えさせていただきます。
==============================

[final_state] ==============================
[final_state] messages 数: 1
[final_state] 先頭に要約メッセージはありません。
[final_state] ==============================

考察

  • トークン削減効果: 13 件のメッセージ(数百〜数千トークン)が、要約メッセージとして圧縮され、次のターン以降の LLM への入力コストと処理時間が大幅に削減の期待ができます。
  • 記憶の維持: 圧縮された要約には、「サウナが好き」「辛いものが好き」「パクチーが苦手」といった重要な事実が凝縮されており、LLM はこれを使って最新の質問(在宅勤務のリフレッシュ)に対してパーソナライズされた応答を生成できています。

このように LangGraph を使うことで、「プロンプト」という単なるテキストではなく、**「コンテキストの状態そのもの」**をコードで制御し、効率的なエージェント構築が可能になります。


4. Agentic Context Engineering (ACE) で「自己改善するコンテキスト」へ

最後に、最先端のアイデアである Agentic Context Engineering (ACE) を紹介します。これは、「人間がコンテキストを設計する」のではなく、「エージェント自身にコンテキスト(プレイブック)を進化させる」という発想です。

4-1. ACE とは:コンテキストを進化させる

ACE は、スタンフォード大学などの研究者(および Kayba AI)によって提唱されたフレームワークです。(Agentic Context Engineering: Evolving Contexts for Self-Improving Language Models
既存の代表的なコンテキスト手法とACEを比較した際にいずれもACEが性能が高いことが示されてます。

通常、コンテキスト(プロンプトや Few-shot 例)は固定されていますが、ACE ではタスクを実行するたびにコンテキストを更新・最適化します。

4-2. 3つの役割:Generator / Reflector / Curator

ACE は以下のループで構成されます。

  1. Generator(実行者): 現在の「プレイブック(戦略書)」を見てタスクを実行する。
  2. Reflector(反省者): 実行結果を見て、「何がうまくいったか」「何が失敗したか」を分析する。
  3. Curator(編集者): 反省結果をもとに、プレイブックを書き換える(例を追加する、注意事項を増やす、不要なルールを消す)。

コンテキスト内でも内容が重複する項目をテキスト埋め込みによって判定して削除することで肥大化を防止する。

コンテキスト全体を一括で書き換えず、部分的な編集によって更新する。
コンテキストの更新を担うCuratorは新しいコンテキスト全体を出力するのではなく、どのような項目を追記・削除すべきかを出力。変更差分はdelta entryと呼ばれる。


5. まとめ:コンテキストエンジニアリングの実践に向けて

本記事では、コンテキストエンジニアリングについての基本概念からgentic Context Engineering (ACE)まで紹介しました。

「プロンプトを書く」時代から、「コンテキストを設計し、育てる」時代へ。
ぜひ、コンテキストエンジニアリング技術を使って、より堅牢なエージェント・アーキテクチャに挑戦してみてください。


参考リンク

Discussion