プロンプトエンジニアリングの"次" — コンテキストエンジニアリングが変えるAIエージェント設計の常識

約20分で読めます by ぽんたぬき
プロンプトエンジニアリングの"次" — コンテキストエンジニアリングが変えるAIエージェント設計の常識

プロンプトエンジニアリングの"次" — コンテキストエンジニアリングが変えるAIエージェント設計の常識


はじめに:なぜ今、コンテキストエンジニアリングなのか

「プロンプトを百回書き直したのに、エージェントが全然言うこと聞かない」

……奥さん、これ、あなたの話ですよね?📟

俺もやりました。Claude 3.5の頃、社内ツール用のエージェントを作って、プロンプトを毎晩こねくり回して、気づいたら2万円分のAPIクレジットを溶かしてました。妻に「また夜中に何してんの」と言われながらターミナル叩いてた40代の夜を、今でも鮮明に覚えています💾

でもね、問題はプロンプトじゃなかった。

コンテキストの設計が根本的に間違ってたんですよ。

2025年、Anthropic・Neo4j・Elastic・Weaviateといった主要プレイヤーが相次いで「コンテキストエンジニアリング(Context Engineering)」という概念を打ち出し始めました。「AIエージェントが失敗するのはモデルのせいじゃない、コンテキストのせいだ」というのが業界の共通認識になりつつある。

ファミコンのカセット吹いてた頃から「ゴミが詰まると動かない」って知ってましたが、LLMも一緒なんです。詰め込むものが悪ければ、どんなに高性能なモデルでも動きがおかしくなる。

この記事で学べること:

  • コンテキストエンジニアリングの定義と、プロンプトエンジニアリングとの違い
  • なぜ今この概念が必要なのか
  • 今日から使える実践パターン4選

第1章:コンテキストエンジニアリングとは何か

「言葉を磨く技術」から「情報環境を設計する技術」へ

HuggingFaceのPhilipp Schmidはこう定義してます。

「LLMがタスクを達成するために必要な情報とツールを、適切な形式・適切なタイミングで提供する動的システムを設計・構築する技術分野」

若い人はこう教わるでしょ、「プロンプトをうまく書け」って。でも現場で痛感するのは、プロンプトは「何をLLMに渡すか」の一部でしかないということ。コンテキストエンジニアリングはその上位概念——プロンプト設計を含む、情報環境全体の設計技術です。

観点 プロンプトエンジニアリング コンテキストエンジニアリング
焦点 単一の指示文の最適化 モデルが受け取る情報環境全体の設計
問い 「どう言葉を選ぶか?」 「今この瞬間、何を渡すべきか?」
性質 静的な文章最適化 動的システムの構築
用途 単発対話・チャット マルチステップ・エージェント

コンテキストウィンドウを構成する7つの要素

LLMが受け取る「コンテキスト」は、一枚のテキストじゃないんですよ。以下の7要素で構成された複合的な情報空間です。

  1. システムプロンプト — エージェントの振る舞いを定義する初期指示
  2. ユーザープロンプト — 即座のタスク・依頼
  3. 短期記憶 — 現在の会話履歴
  4. 長期記憶 — 過去の学習内容・ユーザー設定(外部DBに保存)
  5. 取得情報(RAG) — 外部データベース・APIからの情報
  6. 利用可能なツール — 関数定義・ツールスキーマ
  7. 構造化出力仕様 — 応答フォーマットの指定

コンテキストエンジニアリングとは、この7要素を「何を・いつ・どのような形で」渡すかを設計する技術。RAGはその「手法の1つ」にすぎない。よく誤解されるんですが、RAGとコンテキストエンジニアリングは同義語じゃないでしょ、これ。


第2章:コンテキストエンジニアリングが壊れるとき — 4つの失敗パターン

根本課題:コンテキスト劣化(Context Degradation)

「100万トークンのウィンドウがあればなんでも入れられる」——そう思いたい気持ち、わかります🍺

でも違う。容量の問題と品質の問題は別物なんですよ。

研究で繰り返し確認されている「ロスト・イン・ザ・ミドル(Lost-in-the-Middle)」問題があります。LLMの注意機構はU字型のカーブを描く。コンテキストの冒頭と末尾には注意が集中するが、中間部の情報は見落とされやすい。どんなに重要な指示でも、長大なコンテキストの真ん中に埋もれていれば、LLMはそれを無視する可能性がある。これは容量を増やしても解消されない構造的なバイアスです。

ねえ、聞いてよ。俺さ、システムプロンプトに注意書き100条を詰め込んだエージェントを見てたんだけど、案の定ぜんぶ無視されてたよ(笑) コンテキスト劣化の教科書みたいな事例でネ、なんか笑えなかったワ😅

だから「最小限の高シグナルトークン」を選別する設計が必要になる。

失敗パターン① Context Poisoning(コンテキスト汚染)

ハルシネーションで生成された誤情報が再利用コンテキストに混入し、次の推論にも伝播するリスク。エージェントが「嘘を本当と信じたまま走り続ける」状態です。

対策:出力の検証ステップを設け、再利用前に情報の信頼性を評価する仕組みを組み込む。

失敗パターン② Context Distraction(コンテキスト散漫)

会話が長くなるほど過去の情報が蓄積され、推論が圧迫される問題。「最初の指示を忘れた」みたいな挙動の正体はこれです。

対策:定期的なコンテキスト圧縮(Compaction)で会話履歴を要約・整理する。

失敗パターン③ Context Confusion(コンテキスト混乱)

無関係なツール定義やドキュメントがワークスペースを汚染するリスク。「使えるツールが多すぎて、どれを使えばいいかわからなくなる」状態。

対策:タスクに応じてツールセットを動的に絞り込む設計にする。

失敗パターン④ Context Clash(コンテキスト衝突)

システムプロンプトとユーザー指示、あるいは取得情報同士が矛盾し、意思決定が麻痺するメカニズム。

対策:情報の優先順位を明示し、矛盾検出ロジックをパイプラインに組み込む。


第3章:コンテキストエンジニアリング実践パターン4選 — 今日から設計に使える

パターン①:プログレッシブ・ディスクロージャ(段階的開示)

UIデザインの「段階的開示」原則をエージェント設計に応用したパターンです。「今必要な情報だけを渡し、詳細は必要になったときに取得する」という考え方。

第1層(インデックス)  → 起動時:タイトル・メタデータ・機能説明のみ
第2層(詳細)         → 関連性確認後:タスクに必要なフルコンテンツを読み込み
第3層(深掘り)       → 必要時:サポート資料・参考ドキュメントをオンデマンド取得

Claude Codeのスキルアーキテクチャが好例で、発見フェーズ→アクティベーションフェーズ→実行フェーズの3段階でコンテキストを段階的に展開します。RAG自体もこのパターンの典型実装——テラバイトの知識ベースから今のクエリに関連するチャンクだけを動的取得する、あれですよ。

メリット:コンテキストノイズ削減・トークン効率化・推論品質向上
トレードオフ:初回レイテンシの増加、ルーティング判断の複雑化

パターン②:メモリアーキテクチャ(3層記憶設計)

「すべてを忠実に記録するシステムが最悪」——3万円溶かして身体で覚えた教訓です💾

俺さ、最初のエージェントで全会話を完全保存したんだけど、3セッション目でもう応答がボロボロになっちゃってネ(笑) Context Distractionの洗礼ってやつ😅

記憶設計の正解は「何を捨てるか」を先に決めること。

メモリ種別 保存先 役割
短期記憶 コンテキストウィンドウ内 現在の会話・タスク状態
ワーキングメモリ 一時ファイル・スクラッチパッド 複雑タスクの中間状態
長期記憶 外部ベクターDB / KVストア ユーザープロファイル・学習済み事実

機能的には「エピソード記憶(過去のインタラクション)」「セマンティック記憶(知識・事実)」「手続き記憶(やり方・ガイドライン)」の3分類で考えると設計しやすい。

Claude Codeが使う NOTES.md パターンは、手続き記憶とエピソード記憶の組み合わせ。エージェントが複雑タスクの途中で進捗・決定事項をファイルに書き留め、次セッションで読み込む実装です。

import anthropic

client = anthropic.Anthropic()

SYSTEM_PROMPT = """
あなたはコード開発エージェントです。
複雑なタスクを実行する際、重要な決定や進捗を NOTES.md に記録してください。
新しいセッション開始時は必ず NOTES.md を参照し、前回の状態を把握してください。
"""

def load_long_term_memory(notes_path: str = "NOTES.md") -> str:
    """外部メモリ(長期記憶)からコンテキストを読み込む"""
    try:
        with open(notes_path, "r", encoding="utf-8") as f:
            return f.read()
    except FileNotFoundError:
        return ""

def save_episode_to_memory(content: str, notes_path: str = "NOTES.md"):
    """エピソード記憶として進捗を永続化する"""
    with open(notes_path, "w", encoding="utf-8") as f:
        f.write(content)

# セッション開始時:長期記憶をコンテキストに注入する
previous_notes = load_long_term_memory()
user_request = "前回のリファクタリング作業の続きをお願いします"

messages = []
if previous_notes:
    # 長期記憶を短期コンテキストへ橋渡し
    messages.append({
        "role": "user",
        "content": f"## 前回のメモ(長期記憶)\n{previous_notes}\n\n## 今回の依頼\n{user_request}"
    })
else:
    messages.append({"role": "user", "content": user_request})

response = client.messages.create(
    model="claude-opus-4-5",
    max_tokens=2048,
    system=SYSTEM_PROMPT,
    messages=messages
)

print(response.content[0].text)

設計の核心は「何を記録するか」ではなく「何を捨てるか」の判断。全部を忠実に保存すると、次のセッションで Context Distraction が起きる。選択的保存と要約圧縮がメモリ設計の肝です。

パターン③:マルチエージェントによるコンテキスト分散

単一エージェントにすべてのコンテキストを持たせるのは、一人の社員に全社情報を暗記させるのと同じ無理ゲーなんですよ🦝

オーケストレーター型:中央制御エージェントがタスクを分解し、専門サブエージェントに分散。各エージェントは「自分のタスクに必要なコンテキストだけ」を持つ。

ピアツーピア型:エージェント同士が水平連携し、必要な情報だけを相互に受け渡す。

階層型:役割に応じた階層構造でコンテキストを分担。戦略レイヤーは高レベル情報のみ、実行レイヤーは詳細情報を持つ。

どれを選ぶか:タスクが独立分解できる→オーケストレーター型、専門家の協調が必要→ピアツーピア型、権限・役割が明確→階層型。

以下はオーケストレーター型の最小実装例です。

import anthropic
import json

client = anthropic.Anthropic()


def run_specialist_agent(system_role: str, task: str) -> str:
    """専門サブエージェント:自分の領域のコンテキストだけを受け取って実行する"""
    response = client.messages.create(
        model="claude-haiku-4-5",
        max_tokens=512,
        system=system_role,
        messages=[{"role": "user", "content": task}]
    )
    return response.content[0].text


def orchestrator(task: str) -> str:
    """
    オーケストレーター:タスクを分解し、適切な専門エージェントに委譲する。
    各サブエージェントには「そのタスクに必要な最小限のコンテキスト」だけを渡す。
    """
    # タスク分解:どの専門エージェントに何を任せるか判断
    decompose_prompt = f"""
    以下のタスクを「code_reviewer」か「doc_writer」どちらかのエージェントに割り当て、
    そのエージェントへの指示を生成してください。

    タスク: {task}

    JSON形式で返してください: {{"agent": "...", "subtask": "..."}}
    """
    plan_response = client.messages.create(
        model="claude-haiku-4-5",
        max_tokens=256,
        messages=[{"role": "user", "content": decompose_prompt}]
    )

    plan = json.loads(plan_response.content[0].text)

    # 専門エージェントのシステムプロンプト(各自の関心領域に限定)
    agent_roles = {
        "code_reviewer": "あなたはコードレビュー専門エージェントです。コード品質・可読性・バグリスクのみに集中してください。",
        "doc_writer":    "あなたはドキュメント作成専門エージェントです。技術文書の構造と明確さのみに集中してください。",
    }

    role = agent_roles.get(plan["agent"], "汎用エージェントとして対応してください。")
    return run_specialist_agent(role, plan["subtask"])


# 実行例
result = orchestrator("このPython関数をレビューして、ドキュメントも整備してほしい")
print(result)

コンテキスト分散の効果は「各エージェントが余計な情報を持たない」点にあります。コードレビュアーはドキュメント規約を知る必要がないし、ドキュメントライターはテスト戦略を知る必要がない——それがコンテキスト品質を保つ設計です。

パターン④:コンテキスト最適化テクニック3つ

これが答えや。

import anthropic

client = anthropic.Anthropic()

def compact_conversation_history(
    messages: list[dict],
    model: str = "claude-haiku-4-5"
) -> list[dict]:
    """
    長くなった会話履歴をコンパクション(要約圧縮)する。
    Context Distraction 対策として定期的に実行する。
    """
    if len(messages) < 10:
        return messages  # 短い履歴は圧縮不要

    # 直近3件は保持、それ以前を要約
    recent_messages = messages[-3:]
    older_messages = messages[:-3]

    # 古い会話を要約
    summary_prompt = "以下の会話を重要ポイントだけ箇条書きで要約してください:\n\n"
    for msg in older_messages:
        summary_prompt += f"{msg['role'].upper()}: {msg['content']}\n"

    summary_response = client.messages.create(
        model=model,
        max_tokens=512,
        messages=[{"role": "user", "content": summary_prompt}]
    )
    summary_text = summary_response.content[0].text

    # 圧縮された履歴を返す
    compacted = [
        {
            "role": "user",
            "content": f"[これまでの会話要約]\n{summary_text}"
        },
        {
            "role": "assistant",
            "content": "了解しました。要約を確認しました。"
        }
    ] + recent_messages

    return compacted


def build_masked_context(
    full_context: dict,
    relevant_keys: list[str]
) -> dict:
    """
    マスキング:現在のタスクに不要な情報を一時的に非表示にする。
    Context Confusion 対策。
    """
    return {k: v for k, v in full_context.items() if k in relevant_keys}


# 使用例
conversation_history = [
    {"role": "user", "content": "Pythonでリストをソートする方法は?"},
    {"role": "assistant", "content": "sorted()またはlist.sort()を使います。"},
    # ... 長い会話履歴 ...
]

# コンパクション実行
compacted_history = compact_conversation_history(conversation_history)
print(f"圧縮前: {len(conversation_history)}件 → 圧縮後: {len(compacted_history)}件")

コンパクション(Compaction):会話履歴を要約・圧縮してコンテキストウィンドウを節約。長期タスクには必須の技術です。

マスキング(Masking):現在のタスクに無関係な情報を一時的に非表示にする。Context Confusion 対策の王道。

キャッシング(Caching):頻繁に参照するシステムプロンプトや知識ベースをキャッシュすることでレイテンシとコストを削減。AnthropicのPrompt Cachingが使えます。


第4章:コンテキストエンジニアリング設計の第一歩 — 今日から使える診断法

自分のエージェントを診断する:7要素チェックリスト

まず自分のシステムを7要素に当てはめて整理する。これをやるだけで「足りていないもの」と「過剰なもの」が見えてきます。

要素 チェック観点
① システムプロンプト 必要最小限に絞られているか?詰め込みすぎていないか?
② ユーザープロンプト タスクの意図が明確に伝わる構造か?
③ 短期記憶 会話履歴の長さは適切か?圧縮・要約のタイミングは設計されているか?
④ 長期記憶 永続化すべき情報が外部ストレージに設計されているか?
⑤ 取得情報(RAG) 検索精度・取得チャンクの品質は担保されているか?ノイズが混入していないか?
⑥ ツール定義 タスクに不要なツールが混入していないか?動的に絞り込めるか?
⑦ 構造化出力仕様 期待する出力形式が明示されているか?後続処理がパースできる形になっているか?

このチェックリストで「空白のマス」が多いほど、エージェントが不安定になる要因を抱えている可能性が高い。プロンプトを書き直す前に、まず7要素を埋める作業から始めてみてください。

コンテキストエンジニアリングは「魔法の呪文を見つける旅」じゃなく、「情報環境を設計する工学的プロセス」です。プロンプトに何千円も溶かす前に、渡す情報の設計を見直してみてください——そこに答えがある、というのが俺の結論ですよ、奥さん。📟

関連記事

コメント

0/2000