プロンプトエンジニアリングの"次" — コンテキストエンジニアリングが変える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要素で構成された複合的な情報空間です。
- システムプロンプト — エージェントの振る舞いを定義する初期指示
- ユーザープロンプト — 即座のタスク・依頼
- 短期記憶 — 現在の会話履歴
- 長期記憶 — 過去の学習内容・ユーザー設定(外部DBに保存)
- 取得情報(RAG) — 外部データベース・APIからの情報
- 利用可能なツール — 関数定義・ツールスキーマ
- 構造化出力仕様 — 応答フォーマットの指定
コンテキストエンジニアリングとは、この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要素を埋める作業から始めてみてください。
コンテキストエンジニアリングは「魔法の呪文を見つける旅」じゃなく、「情報環境を設計する工学的プロセス」です。プロンプトに何千円も溶かす前に、渡す情報の設計を見直してみてください——そこに答えがある、というのが俺の結論ですよ、奥さん。📟
関連記事
RAGFlowで構築する高精度なオープンソースRAG:40代エンジニアが実践してみた完全ガイド
オープンソースのRAGFlowを使って高精度な検索拡張生成システムを構築する方法を、40代エンジニアが実際に試した経験をもとに詳しく解説します。