Back

コンテキストエンジニアリング:誰も教えてくれない、最も重要なAIスキル

どのチュートリアルもプロンプトエンジニアリングを教えてくれますよね。明確な指示を書きましょう。Few-shotの例を入れましょう。システムメッセージを追加しましょう。シンプルなデモなら、それで十分です。

でも、実際のプロダクトを作ろうとしたらどうでしょう。会話履歴を覚えるカスタマーサポートのエージェント、コードベース全体を理解するコーディングアシスタント、200ページのPDFを分析するパイプライン。こういうものを作るとき、プロンプトエンジニアリングだけじゃ壁にぶつかるんです。プロンプトが悪いわけじゃない。そもそも解いている問題自体が違うんです。

本当の課題は、モデルに何を言うかじゃなかったんです。モデルが応答を生成する時に、どの情報にアクセスできるか。これが核心なんですよね。この領域には名前があります。コンテキストエンジニアリングです。

プロンプトエンジニアリングが「正しい言葉を選ぶこと」なら、コンテキストエンジニアリングは「正しい知識を選ぶこと」です。優秀なコンサルタントに手ぶらで質問するのと、関連文書や会話履歴、ツールを全部準備してから質問するのとでは、結果が全然違いますよね。

この記事では、コンテキストエンジニアリングとは何か、なぜプロンプトエンジニアリングより大事なのか、そしてプロダクションで実際に使われているテクニックを全部解説します。

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

コンテキストエンジニアリングとは、LLMのコンテキストウィンドウに入れる情報を選び、構造化し、管理する体系的なプラクティスなんです。トークン制限とコスト予算の範囲内で、出力品質を最大化するのが目的です。

LLMのコンテキストウィンドウをデスクにたとえるとわかりやすいです。プロンプトエンジニアリングは、そのデスクに置く良いメモを書くこと。コンテキストエンジニアリングは、デスク上のすべてをキュレーションすることなんです。参照文書、ツール、会話履歴、例文まで全部。そこに座る人が最高の回答を出せるように整備します。

┌─────────────────────────────────────────┐
│           コンテキストウィンドウ          │
│                                         │
│  ┌─────────────────────────────────┐    │
│  │  システム指示                    │    │
│  │  (役割、制約、フォーマット)      │    │
│  └─────────────────────────────────┘    │
│  ┌─────────────────────────────────┐    │
│  │  検索された知識(RAG)           │    │
│  │  (ドキュメント、コード、データ)  │    │
│  └─────────────────────────────────┘    │
│  ┌─────────────────────────────────┐    │
│  │  ツール定義                     │    │
│  │  (利用可能な関数/API)          │    │
│  └─────────────────────────────────┘    │
│  ┌─────────────────────────────────┐    │
│  │  会話履歴                       │    │
│  │  (過去のメッセージ + コンテキスト)│    │
│  └─────────────────────────────────┘    │
│  ┌─────────────────────────────────┐    │
│  │  現在のユーザークエリ             │    │
│  │  (今聞かれたこと)               │    │
│  └─────────────────────────────────┘    │
│                                         │
│  合計:Nトークン以内に収める必要あり    │
│  (例:128K、200K、1M、2M)             │
└─────────────────────────────────────────┘

すべてのコンポーネントが同じ有限のスペースを奪い合います。会話履歴を増やすと検索知識が圧迫される。ツール定義を多く入れると例文のスペースがなくなる。全部入れるとトークン予算(とAPI請求書)が吹き飛びます。

コンテキストエンジニアリングは、このトレードオフをうまく捕る技術なんです。

プロンプトエンジニアリングだけじゃなぜダメなのか

具体的なシナリオで見てみましょう。

ECサイトのカスタマーサポートエージェントを作っているとします。顧客がこう書きます:「先週注文した商品がまだ届きません。これで3回目です。返金してください。」

プロンプトエンジニアリングがしっかりしていれば、トーンは問題ないでしょう。共感的で、専門的で、解決志向。でも、コンテキストエンジニアリングができていないと、こういうことがわからないんです:

  • どの注文のことを指しているのか
  • 実際の注文履歴に何が記録されているか
  • 過去に問い合わせ履歴があるか(どう解決されたか)
  • 繰り返し問題に対する返金ポリシーは何か
  • 配送業者の追跡データが何を示しているか

プロンプトは問題ない。コンテキストが空なのです。 モデルは的外れな一般的回答を生成し、顧客はさらに不満を募らせます。

コンテキストエンジニアリングは、モデルが生成する前に正しい情報がウィンドウ内にあることを保証します:

async function buildSupportContext(customerId: string, message: string) { // 1. 顧客データを取得 const customer = await db.customers.findUnique({ where: { id: customerId } }); const recentOrders = await db.orders.findMany({ where: { customerId, createdAt: { gte: thirtyDaysAgo } }, include: { shipments: true }, }); // 2. 関連ポリシー文書を検索 const policies = await vectorStore.search(message, { namespace: 'support-policies', topK: 3, }); // 3. 過去のサポート履歴を取得 const pastTickets = await db.supportTickets.findMany({ where: { customerId }, orderBy: { createdAt: 'desc' }, take: 5, }); // 4. リアルタイム配送状況を確認 const trackingData = await shippingApi.getStatus( recentOrders[0]?.shipments[0]?.trackingNumber ); // 5. コンテキストを組み立てる return { systemPrompt: SUPPORT_AGENT_INSTRUCTIONS, context: [ { role: 'system', content: formatCustomerProfile(customer) }, { role: 'system', content: formatOrderHistory(recentOrders) }, { role: 'system', content: formatPolicies(policies) }, { role: 'system', content: formatPastTickets(pastTickets) }, { role: 'system', content: formatTrackingData(trackingData) }, ...conversationHistory, { role: 'user', content: message }, ], }; }

これでモデルに必要なものがすべて揃いました。正確な注文内容、配送追跡状況、リピート顧客への返金ポリシー、過去の対応履歴。同じプロンプトでも結果がまるで違います。

コンテキストエンジニアリングの5本柱

プロダクション品質のコンテキストエンジニアリングには、5つの連動する技術領域を押さえる必要があります。

1. 選択:何を入れるか

最初の問いは常にこれです:この特定のクエリに適切に応答するために、どの情報が必要か?

当たり前に聞こえますが、多くのチームは全部入れてしまう(トークン浪費+品質低下)か、少なすぎる(モデルが推測せざるを得ない)かのどちらかです。

信号対ノイズの原則: コンテキストウィンドウのすべてのトークンは、存在理由がなければなりません。無関係な情報はトークンを無駄にするだけでなく、積極的に出力品質を下げます。正確だが無関係なコンテキストを与えると、何も与えない場合より性能が低下することが研究で繰り返し確認されています。

// 悪い例:全部入れる const context = await db.documents.findMany(); // 50,000トークンのノイズ // 良い例:セマンティック検索 + 関連性フィルタリング const relevantDocs = await vectorStore.search(query, { topK: 10 }); const filtered = relevantDocs.filter(doc => doc.score > 0.78); // 2,000トークンのシグナル

動的ツールローディングも重要な選択技術です。エージェントが50個のツールを持っていても、現在のタスクに関連するのが3〜5個なら、50個分のツール定義を全部ロードするのは数千トークンの無駄であり、モデルを混乱させます:

// 悪い例:毎回すべてのツールをロード const tools = getAllTools(); // 50ツール、定義だけで~8,000トークン // 良い例:意図分類に基づいてツールを選択 const intent = await classifyIntent(userMessage); const tools = getToolsForIntent(intent); // 4ツール、~600トークン // さらに良い例:2段階アプローチ const selectedToolNames = await selectRelevantTools(userMessage, allToolNames); const tools = selectedToolNames.map(name => toolRegistry.get(name));

2. 構造化:どう配置するか

同じ情報でも、構造が違えば結果が大きく変わります。

位置バイアスは実在します。 モデルはコンテキストウィンドウの先頭と末尾に、中間より多くの注意を払います。「Lost in the Middle」問題と呼ばれ、研究で一貫して実証されています。10万トークンのコンテキストの真ん中に埋もれた重要情報は、事実上存在しないも同然です。

function buildContext(systemPrompt, retrievedDocs, history, query) { return [ // 先頭 — 高注意ゾーン { role: 'system', content: systemPrompt }, { role: 'system', content: '## 重要参照データ\n' + mostRelevantDoc }, // 中間 — 低注意ゾーン(重要度の低いコンテキストをここに) ...history.slice(0, -3), ...supplementaryDocs, // 末尾 — 高注意ゾーン ...history.slice(-3), { role: 'user', content: query }, ]; }

セクション区切りも大切です。 ポリシー、顧客データ、会話履歴など異なる種類の情報を混ぜるとき、明確な構造マーカーがモデルの識別能力を高めます:

const context = ` ## 顧客プロフィール 名前: ${customer.name} アカウントステータス: ${customer.tier} 累計注文数: ${customer.orderCount} ## 現在の注文状況 注文番号 #${order.id}${order.date}注文 ステータス: ${order.status} 配送追跡: ${tracking.status} — 最終更新: ${tracking.lastUpdate} ## 適用ポリシー ${policies.map(p => `- ${p.title}: ${p.summary}`).join('\n')} ## 対応権限 ${agent.refundLimit}円以下の返金はエスカレーション不要。 それ以上は上位担当者へ引き継いでください。 `;

3. メモリアーキテクチャ:過去と現在をつなぐ

実際のアプリケーションは、会話をまたいで記憶を保持する必要があります。月曜日にプロジェクトのアーキテクチャを詳しく説明したユーザーが、火曜日にまた最初から説明し直す必要があってはなりません。

3層メモリモデルというものがあります。ほとんどのプロダクションAIシステムが採用している構造です:

┌───────────────────────────────────────┐
│  ワーキングメモリ(コンテキストウィンドウ)│
│  現在の会話 + アクティブデータ          │
│  容量:モデルのトークン上限            │
│  速度:即座(ロード済み)              │
├───────────────────────────────────────┤
│  短期メモリ(セッションストア)          │
│  最近の会話要約                       │
│  現在のセッション状態・変数             │
│  容量:圧縮された10-50エントリ         │
│  速度:高速取得                       │
├───────────────────────────────────────┤
│  長期メモリ(永続ストア)               │
│  ユーザーの好みと過去の判断             │
│  複数セッション分の蓄積知識             │
│  容量:事実上無制限                    │
│  速度:検索+ランキングが必要           │
└───────────────────────────────────────┘
class MemoryManager { async buildMemoryContext(userId: string, currentQuery: string): Promise<string> { // 第1層:ワーキングメモリ — 最近の会話ターン const recentTurns = this.conversationBuffer.slice(-6); // 第2層:短期メモリ — セッション要約 const sessionContext = await this.sessionStore.get(userId); // 第3層:長期メモリ — セマンティック検索で取得した過去の知識 const longTermMemories = await this.vectorStore.search(currentQuery, { filter: { userId }, topK: 5, }); return this.assembleMemory(recentTurns, sessionContext, longTermMemories); } }

要約は不可欠です。 すべての会話ターンを永遠に保持することはできません。標準的なアプローチはローリング要約です:

async function compressConversation(messages: Message[]): Promise<string> { if (messages.length <= 6) return ''; const toSummarize = messages.slice(0, -6); const summary = await llm.generate({ messages: [{ role: 'system', content: `この会話を2-3文で要約してください。 注力: 下された決定、確立された事実、表明されたユーザーの好み。 省略: 挨拶、雑談、繰り返し情報。` }, { role: 'user', content: toSummarize.map(m => `${m.role}: ${m.content}`).join('\n') }], model: 'gpt-4o-mini', maxTokens: 200, }); return summary; }

4. コンテキスト圧縮:より少ないスペースにより多くのシグナルを

200万トークンのコンテキストウィンドウがあっても、圧縮は重要です。コンテキストが大きいほど遅く、高コストで、逆説的に「Lost in the Middle」問題のため結果が悪化することもあります。

async function compressDocuments(docs: Document[]): Promise<string> { const deduped = removeSimilarChunks(docs, similarityThreshold: 0.92); const compressed = await llm.generate({ messages: [{ role: 'system', content: `以下の文書を高密度な参照資料に圧縮してください。 保持:具体的な数値、日付、名前、コードスニペット、決定事項。 削除:導入文、つなぎ文、埋め草、繰り返しの説明。 目標:元の長さの30%。` }, { role: 'user', content: deduped.map(d => d.content).join('\n---\n') }], model: 'claude-3-5-haiku', }); return compressed; }

適応型予算配分:

function allocateContextBudget(totalTokens: number) { return { systemPrompt: Math.min(2000, totalTokens * 0.1), // 10% tools: Math.min(3000, totalTokens * 0.15), // 15% knowledge: Math.floor(totalTokens * 0.35), // 35% history: Math.floor(totalTokens * 0.30), // 30% query: 500, // 固定 buffer: Math.floor(totalTokens * 0.10), // 10%マージン }; }

5. 評価:効果を測定する

測定できないものは改善できません。コンテキストエンジニアリングには専用のメトリクスが必要です。

メトリクス測定対象目標
コンテキスト関連性応答に使用されたチャンクの割合> 70%
トークン効率有用な出力トークン / 入力トークン> 0.15
検索精度取得文書中の関連文書の割合> 80%
予算遵守率トークン予算内のリクエストの割合> 95%
回答の根拠性コンテキストに追跡可能な主張の割合> 90%
レイテンシ影響コンテキスト組み立てによる追加遅延< 500ms

関連性精度っていうのが大事で、提供したコンテキストのうち実際に応答に役立った割合を測ります:

async function measureContextRelevance( query: string, contextChunks: string[], expectedAnswer: string ): Promise<number> { const evaluations = await Promise.all( contextChunks.map(chunk => llm.generate({ messages: [{ role: 'system', content: `このコンテキストチャンクがクエリへの応答に 関連しているか評価してください。 0(無関係)または1(関連あり)で回答。 クエリ:「${query} 0か1のみで回答。` }, { role: 'user', content: chunk }], model: 'gpt-4o-mini', }) ) ); const relevant = evaluations.filter(e => e.trim() === '1').length; return relevant / contextChunks.length; }

よくあるアンチパターン

アンチパターン1:キッチンシンク(全部入れ)

問題: 「情報は多ければ多いほど良い」と、すべてをコンテキストに詰め込む。

なぜ失敗するか: 公称コンテキスト容量の約60-70%を超えると、パフォーマンスが測定可能に低下します。モデルが提供された情報を無視したり、混同したり、ハルシネーションを起こし始めます。

解決策: 関連性に容赦なくなること。現在のクエリに直接役立たない文書は含めないでください。

アンチパターン2:固定コンテキストテンプレート

問題: クエリのタイプや複雑さに関係なく、毎回同じコンテキスト構造を使う。

解決策: クエリ分類ベースの動的コンテキスト組み立て:

async function buildDynamicContext(query: string) { const queryType = await classifyQuery(query); switch (queryType) { case 'factual': return buildFactualContext(query); // 知識多め、履歴少なめ case 'conversational': return buildConversationalContext(query); // 履歴多め、知識少なめ case 'action': return buildActionContext(query); // ツールと状態多め } }

アンチパターン3:時間的重み付けの無視

問題: すべての会話履歴を、発生時期に関係なく等しく扱う。

解決策: 時間的重み付けを入れましょう。最近のコンテキストにはより多くのスペースを、古いものは圧縮するんです:

function buildTemporalContext(history: Message[]): Message[] { const result: Message[] = []; // 最近4ターン:原文のまま result.push(...history.slice(-4)); // 5-12ターン目:重要なメッセージのみ const middleHistory = history.slice(-12, -4); const important = middleHistory.filter( m => m.role === 'user' || containsDecision(m) || containsCodeChange(m) ); result.unshift(...important); // 13ターン以降:圧縮要約 if (history.length > 12) { const oldHistory = history.slice(0, -12); const summary = await compressConversation(oldHistory); result.unshift({ role: 'system', content: `以前のコンテキスト:${summary}` }); } return result; }

アンチパターン4:トークン予算なし

問題: 各コンポーネントの消費量制限がなく、一つ(通常は会話履歴)が他を圧迫する。

解決策: 明示的な予算配分と強制:

class ContextBudget { constructor(totalLimit: number) { this.allocations = new Map([ ['system', Math.floor(totalLimit * 0.10)], ['knowledge', Math.floor(totalLimit * 0.35)], ['tools', Math.floor(totalLimit * 0.10)], ['history', Math.floor(totalLimit * 0.30)], ['query', Math.floor(totalLimit * 0.05)], ['buffer', Math.floor(totalLimit * 0.10)], ]); } fit(component: string, content: string): string { const limit = this.allocations.get(component) ?? 0; if (countTokens(content) <= limit) return content; return truncateToTokens(content, limit); } }

AIエージェントのためのコンテキストエンジニアリング

エージェントシステムは複雑さがもう一段階上がります。複数ステップを実行するエージェントは、実行ライフサイクル全体を通じてコンテキストを管理する必要があります。

スクラッチパッドパターン

エージェントに中間推論用の専用スペースを設けて、メインコンテキストを汚染しないようにします:

class AgentScratchpad { private thoughts: string[] = []; addThought(thought: string) { this.thoughts.push(thought); if (this.thoughts.length > 10) { const toSummarize = this.thoughts.splice(0, 5); this.thoughts.unshift(`[以前の推論の要約:${toSummarize.join(';')}]`); } } }

ツール結果の圧縮

エージェントのツールは巨大なペイロードを返すことがあります。DBクエリが500行を返したり、APIレスポンスが50KBのJSONだったり。その生データをすべてコンテキストに注入するのは無駄であり逆効果です:

async function compressToolResult(toolName: string, result: unknown, query: string) { const rawString = JSON.stringify(result); if (countTokens(rawString) < 500) return rawString; const summary = await llm.generate({ messages: [{ role: 'system', content: `ツール「${toolName}」が以下のデータを返しました。 ${query}」の文脈で要約してください。 具体的な数値、ID、主要な事実は保持してください。` }, { role: 'user', content: rawString.slice(0, 10000) }], model: 'gpt-4o-mini', maxTokens: 500, }); return `[${toolName}の結果 — 要約済み]\n${summary}`; }

マルチエージェントのコンテキスト分離

複数のエージェントが協力するとき、各エージェントには自分専用の焦点を絞ったコンテキストが必要です。すべてを共有するとコンテキスト汚染が起きます:

class MultiAgentContextManager { async buildAgentContext(agent: Agent, task: Task, sharedState: SharedState) { return [ { role: 'system', content: agent.systemPrompt }, { role: 'system', content: this.filterSharedState(sharedState, agent.role) }, { role: 'system', content: this.formatTask(task) }, ...this.compressUpstreamResults(task.previousResults, agent.role), ]; } private filterSharedState(state: SharedState, role: string): string { // 各エージェントは自分の役割に関連する状態のみ参照 const relevantKeys = STATE_ROLE_MAP[role] ?? []; const filtered = Object.fromEntries( Object.entries(state).filter(([key]) => relevantKeys.includes(key)) ); return JSON.stringify(filtered, null, 2); } }

「実効コンテキストウィンドウ」問題

多くの開発者が気づいていない事実があります。モデルの公称コンテキストウィンドウと実効コンテキストウィンドウは大きく異なります。

Gemini 2.5 Proは100万トークンと謳っています。Claude 3.5は20万K。GPT-4.1は100万。しかし研究では、これらの上限よりかなり手前からモデルの性能が低下することが分かっています。ほとんどのモデルは公称容量の60-70%を超えると信頼性が落ちます。

実務的に意味するのは:

  • 20万トークンモデル → 実効約13万トークン
  • 100万トークンモデル → 実効約65万トークン
const EFFECTIVE_CONTEXT_RATIO = 0.65; // 公称限度の65%のみ使用 function getEffectiveLimit(model: string): number { const statedLimits: Record<string, number> = { 'gpt-4.1': 1_000_000, 'claude-3.5-sonnet': 200_000, 'gemini-2.5-pro': 1_000_000, 'gpt-4o': 128_000, }; return Math.floor((statedLimits[model] ?? 128_000) * EFFECTIVE_CONTEXT_RATIO); }

マインドセットの転換

このガイドから一つだけ持ち帰るなら、これです:

プロンプトを考えるのをやめてください。コンテキストを考えてください。

プロンプト(システム指示、Few-shot例、フォーマットガイドライン)が出力品質に与える影響はせいぜい5-10%なんです。残り90%は、モデルが適切なタイミングで、適切な構造で、適切な情報にアクセスできたかどうかなんですよね。

2026年最高のAIエンジニアは、最も巧みなプロンプトを書く人じゃないんです。最も洗練されたコンテキストパイプラインを構築する人です。各クエリに対して正確に必要な情報を動的に組み立て、予算内に圧縮し、そのコンテキストが実際に役立ったかを測定するシステムを作る人なんです。

プロンプトエンジニアリングは前菜でした。コンテキストエンジニアリングがメインディッシュです。これを先に見つけたチームが、プロダクションで本当に動くAI機能をリリースできるのです。

まとめ

コンテキストエンジニアリングは、一見シンプルな問いに答えます:「この応答を生成するとき、モデルは何を知っているべきか?」

しかし、この問いにうまく答えるには、完全なエンジニアリング分野が必要です:

  1. 各クエリに適した情報を選択する
  2. 位置認識で情報を構造化する
  3. 階層的メモリシステムで会話間の記憶を維持する
  4. シグナルを失わずに予算内に圧縮する
  5. コンテキストが実際に出力を改善したか測定する

ツールはすべて揃っています。検索用のベクターDB、要約・圧縮用のLLM、予算管理用のトークンカウンター、測定用の評価フレームワーク。

足りなかったのはフレームワーク、つまりこれらすべてのピースを一つの一貫した分野として捉える思考モデルだったんです。それがコンテキストエンジニアリングです。AIのデモとAIのプロダクトを分けるスキルなんです。

コンテキストパイプラインを構築してください。すべてを測定してください。容赦なくイテレーションしてください。それが本当に動くAIをリリースする方法です。

AILLMcontext-engineeringprompt-engineeringRAGAI-agentsproductionmachine-learning

関連ツールを見る

Pockitの無料開発者ツールを試してみましょう