はじめに#
AI開発の現場で「コンテキストエンジニアリング(Context Engineering)」という用語が急速に注目を集めています。従来の「プロンプトエンジニアリング」がチャットボットへの短い指示文を連想させるのに対し、コンテキストエンジニアリングは、LLM(Large Language Model)が適切なタスクを遂行するために必要な情報を体系的に設計・管理する技術体系を指します。
Shopify CEOのTobi Lütke氏やAI研究者のAndrej Karpathy氏がこの用語を推奨したことで、業界全体でこの概念が浸透しつつあります。Karpathy氏は「産業レベルのLLMアプリケーションでは、タスク記述、説明、少数ショット例、RAG、マルチモーダルデータ、ツール、状態、履歴、圧縮など、コンテキストウィンドウに適切な情報を詰め込む繊細な技術と科学が必要」と述べています。
本記事では、コンテキストエンジニアリングの概念から実践的な活用方法まで、AI開発に携わるエンジニアが今すぐ活用できる知識を体系的に解説します。
対象読者と前提条件#
対象読者#
- LLMを活用したアプリケーション開発に携わるエンジニア
- AIエージェントの設計・開発を行うアーキテクト
- プロンプトエンジニアリングの次のステップを模索している開発者
- Claude、GPT、Geminiなどのモデルを業務で活用している方
前提条件#
- 生成AIの基本的な仕組みを理解していること
- プロンプトエンジニアリングの基礎知識があること
- APIを使ったLLM呼び出しの経験があることが望ましい
コンテキストエンジニアリングとは#
定義と本質#
コンテキストエンジニアリングとは、LLMのコンテキストウィンドウに「次のステップを解決するために必要な情報」を適切に配置する技術です。単にプロンプトを書くだけでなく、以下の要素を統合的に設計します。
| 要素 |
説明 |
具体例 |
| システムプロンプト |
モデルの役割と振る舞いの定義 |
「シニアエンジニアとして回答」 |
| タスク記述 |
実行すべき具体的な指示 |
「コードレビューを実施」 |
| 少数ショット例 |
期待する入出力のパターン |
入力コードと修正後のコード例 |
| RAG(検索拡張) |
外部から取得した関連情報 |
ドキュメント、ナレッジベース |
| マルチモーダルデータ |
画像、音声、動画などの非テキスト情報 |
設計図、スクリーンショット |
| ツール定義 |
利用可能な外部機能の仕様 |
API呼び出し、ファイル操作 |
| 状態・履歴 |
対話や処理の進行状況 |
過去の会話、中間結果 |
プロンプトエンジニアリングとの違い#
プロンプトエンジニアリングとコンテキストエンジニアリングは対立する概念ではなく、後者が前者を包含する関係にあります。
1
2
3
4
5
6
7
8
9
10
11
12
|
プロンプトエンジニアリング
├── 指示文の書き方
├── 役割設定(ペルソナ)
└── 出力形式の指定
コンテキストエンジニアリング(上位概念)
├── プロンプトエンジニアリング
├── コンテキストウィンドウ管理
├── 情報の取捨選択と優先順位付け
├── 外部データ統合(RAG、MCP)
├── ツール設計とドキュメント
└── 状態管理と履歴圧縮
|
プロンプトエンジニアリングが「何を指示するか」に焦点を当てるのに対し、コンテキストエンジニアリングは「どのような情報をどのように提供するか」という、より広範な設計プロセスを扱います。
コンテキストエンジニアリングが重要な理由#
LLMの能力を最大化する#
現代のLLMは、コンテキストウィンドウに含まれる情報に基づいて応答を生成します。モデル自体の知識は学習データに依存しますが、コンテキストとして提供された情報は、その場で「学習」したかのように活用されます。
graph LR
A[ユーザー入力] --> B[コンテキストエンジニアリング]
B --> C[システムプロンプト]
B --> D[関連ドキュメント]
B --> E[ツール定義]
B --> F[履歴・状態]
C --> G[LLM]
D --> G
E --> G
F --> G
G --> H[高品質な応答]AIエージェントの自律性を支える#
Anthropicの「Building Effective Agents」では、エージェントを「LLMが動的に自らのプロセスとツール使用を制御するシステム」と定義しています。エージェントが適切な判断を下すためには、必要な情報がコンテキストに含まれている必要があります。コンテキストエンジニアリングは、エージェントの「認知能力」を設計する行為といえます。
コスト・レイテンシの最適化#
コンテキストウィンドウのサイズはトークン数で制限されており、トークン数は課金やレイテンシに直結します。不要な情報を排除し、必要な情報を効率的に配置することで、品質を維持しながらコストを削減できます。
コンテキストエンジニアリングの構成要素#
1. システムプロンプトの設計#
システムプロンプト(開発者指示)は、モデルの振る舞いを規定する最上位のコンテキストです。OpenAIのモデルではdeveloperロール、Claudeではsystemロールで指定します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
<system>
<role>
あなたはTypeScriptとReactに精通したシニアフロントエンドエンジニアです。
10年以上の実務経験を持ち、コードレビューとリファクタリングを専門としています。
</role>
<guidelines>
1. コードの可読性と保守性を最優先する
2. 型安全性を確保し、any型の使用を避ける
3. パフォーマンスへの影響を考慮する
4. セキュリティ上の問題を指摘する
</guidelines>
<output_format>
レビュー結果は以下の形式で出力してください:
- 問題の重要度(Critical / High / Medium / Low)
- 該当箇所(ファイル名と行番号)
- 問題の説明
- 修正案
</output_format>
</system>
|
2. コンテキストの階層構造#
OpenAIのモデルスペックでは、メッセージに優先順位が定義されています。
| 優先度 |
ロール |
説明 |
| 高 |
developer / system |
アプリケーション開発者が設定する指示 |
| 中 |
user |
エンドユーザーからの入力 |
| 低 |
assistant |
モデルが生成した過去の応答 |
この階層を理解し、重要な制約やルールはシステムプロンプトに、タスク固有の情報はユーザープロンプトに配置します。
3. 関連情報の取得と統合(RAG)#
RAG(Retrieval-Augmented Generation)は、外部データソースから関連情報を取得してコンテキストに含める手法です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
# RAGパイプラインの概念的な実装
def build_context(user_query: str, max_tokens: int = 4000) -> str:
# 1. クエリをベクトル化
query_embedding = embed(user_query)
# 2. 関連ドキュメントを検索
relevant_docs = vector_db.search(
query_embedding,
top_k=5,
threshold=0.7
)
# 3. トークン数を考慮してコンテキストを構築
context_parts = []
current_tokens = 0
for doc in relevant_docs:
doc_tokens = count_tokens(doc.content)
if current_tokens + doc_tokens <= max_tokens:
context_parts.append(f"<document source=\"{doc.source}\">\n{doc.content}\n</document>")
current_tokens += doc_tokens
else:
break
return "\n".join(context_parts)
|
4. ツール定義とACI設計#
AIエージェントにおいて、ツール(関数)の定義はコンテキストの重要な構成要素です。Anthropicは、HCI(Human-Computer Interface)と同等の労力をACI(Agent-Computer Interface)の設計に投資すべきと提言しています。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
{
"name": "search_codebase",
"description": "プロジェクト内のソースコードを検索します。ファイル名、関数名、クラス名、変数名、コメントを対象に全文検索を実行します。検索結果は関連度順にソートされます。",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "検索キーワード。複数語の場合はスペース区切りでAND検索"
},
"file_pattern": {
"type": "string",
"description": "対象ファイルのglobパターン(例: '*.ts', 'src/**/*.tsx')"
},
"max_results": {
"type": "integer",
"description": "取得する最大件数(デフォルト: 10)",
"default": 10
}
},
"required": ["query"]
}
}
|
ツール設計のベストプラクティスとして、以下が挙げられます。
- パラメータ名を直感的にする
- 説明文にユースケースと制限事項を含める
- エッジケースの挙動を明記する
- 類似ツールとの違いを明確にする
5. 状態管理と履歴圧縮#
長時間の対話やマルチステップのタスクでは、コンテキストウィンドウの限界に達する可能性があります。効率的な状態管理が必要です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
class ConversationManager:
def __init__(self, max_tokens: int = 128000):
self.max_tokens = max_tokens
self.messages = []
self.summary = ""
def add_message(self, role: str, content: str):
self.messages.append({"role": role, "content": content})
self._compact_if_needed()
def _compact_if_needed(self):
total_tokens = self._count_total_tokens()
if total_tokens > self.max_tokens * 0.8:
# 古いメッセージを要約に圧縮
old_messages = self.messages[:-10] # 最新10件は保持
summary_prompt = f"""
以下の会話履歴を簡潔に要約してください。
重要な決定事項、コンテキスト、未解決の問題を含めてください。
{self._format_messages(old_messages)}
"""
self.summary = self._generate_summary(summary_prompt)
self.messages = self.messages[-10:]
def get_context(self) -> list:
context = []
if self.summary:
context.append({
"role": "system",
"content": f"<conversation_summary>\n{self.summary}\n</conversation_summary>"
})
context.extend(self.messages)
return context
|
コンテキストエンジニアリングの実践パターン#
パターン1: プロジェクトコンテキストの注入#
開発支援AIでは、プロジェクト固有のコンテキストを提供することで、一貫性のあるコード生成が可能になります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
<project_context>
プロジェクト: ECサイトのバックエンドAPI
技術スタック:
- Node.js 20 + TypeScript 5.3
- NestJS 10
- PostgreSQL 15 + Prisma ORM
- Jest(テスト)
コーディング規約:
- 関数名はcamelCase
- 型定義は interface を優先(type は複合型のみ)
- エラーはカスタム例外クラスで処理
- すべての公開メソッドにJSDocコメント必須
ディレクトリ構造:
src/
├── modules/ # 機能モジュール
│ └── [module]/
│ ├── dto/
│ ├── entities/
│ ├── [module].controller.ts
│ ├── [module].service.ts
│ └── [module].module.ts
├── common/ # 共通ユーティリティ
└── config/ # 設定ファイル
</project_context>
|
パターン2: 既存コードの参照#
新しいコードを生成する際、既存の実装パターンを参照させることで、プロジェクトの一貫性を保てます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
<reference_implementation>
以下は既存のUserServiceの実装です。同じパターンでOrderServiceを実装してください。
```typescript
// src/modules/user/user.service.ts
@Injectable()
export class UserService {
constructor(private readonly prisma: PrismaService) {}
/**
* ユーザーを作成します
* @param dto ユーザー作成DTO
* @returns 作成されたユーザー
* @throws ConflictException メールアドレスが既に存在する場合
*/
async create(dto: CreateUserDto): Promise<User> {
const existing = await this.prisma.user.findUnique({
where: { email: dto.email },
});
if (existing) {
throw new ConflictException('Email already exists');
}
return this.prisma.user.create({
data: {
...dto,
password: await hash(dto.password, 10),
},
});
}
}
\`\`\`
</reference_implementation>
<task>
注文管理機能(OrderService)を実装してください。
- 注文の作成(create)
- 注文の取得(findById)
- ユーザー別注文一覧(findByUserId)
- 注文ステータスの更新(updateStatus)
</task>
|
パターン3: エラー情報を含むデバッグコンテキスト#
デバッグ支援では、エラー情報とともに関連するコードを提供します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
<debug_context>
<error_message>
TypeError: Cannot read properties of undefined (reading 'map')
at OrderService.findByUserId (order.service.ts:45:32)
at OrderController.getUserOrders (order.controller.ts:28:35)
</error_message>
<related_code file="order.service.ts" lines="40-50">
async findByUserId(userId: string): Promise<Order[]> {
const user = await this.prisma.user.findUnique({
where: { id: userId },
include: { orders: true },
});
return user.orders.map(order => ({ // Line 45: エラー発生箇所
...order,
formattedTotal: this.formatCurrency(order.total),
}));
}
</related_code>
<test_input>
userId: "non-existent-user-id"
</test_input>
</debug_context>
<task>
上記のエラーの原因を特定し、修正案を提示してください。
</task>
|
パターン4: マルチステップワークフロー#
複雑なタスクを段階的に処理する場合、各ステップの結果をコンテキストに蓄積します。
graph TD
A[Step 1: 要件分析] --> B[Step 2: 設計]
B --> C[Step 3: 実装]
C --> D[Step 4: レビュー]
D --> E{問題あり?}
E -->|Yes| C
E -->|No| F[完了]
subgraph コンテキスト蓄積
A --> G[要件の理解]
B --> H[設計ドキュメント]
C --> I[実装コード]
D --> J[レビュー指摘]
endコンテキストウィンドウの最適化戦略#
トークン効率を高める#
限られたコンテキストウィンドウを有効活用するため、以下の戦略を適用します。
1. 重要度に基づく優先順位付け
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
def prioritize_context(items: list, max_tokens: int) -> list:
"""重要度スコアに基づいてコンテキストアイテムを選択"""
# 重要度でソート(降順)
sorted_items = sorted(items, key=lambda x: x.importance, reverse=True)
selected = []
current_tokens = 0
for item in sorted_items:
if current_tokens + item.tokens <= max_tokens:
selected.append(item)
current_tokens += item.tokens
return selected
|
2. 関連性フィルタリング
タスクに直接関係しない情報は除外します。
1
2
3
4
5
6
7
8
9
10
11
|
def filter_by_relevance(documents: list, query: str, threshold: float = 0.5) -> list:
"""意味的類似度に基づいて関連ドキュメントをフィルタリング"""
query_embedding = embed(query)
relevant = []
for doc in documents:
similarity = cosine_similarity(query_embedding, doc.embedding)
if similarity >= threshold:
relevant.append((doc, similarity))
return [doc for doc, _ in sorted(relevant, key=lambda x: x[1], reverse=True)]
|
3. 情報の圧縮
冗長な情報を要約や構造化データに変換します。
| 圧縮前 |
圧縮後 |
| 長文のドキュメント |
要約 + キーポイント |
| 完全なソースファイル |
関連関数のみ抽出 |
| 詳細なログ |
エラー箇所と直前のログのみ |
| 会話履歴全体 |
要約 + 直近N件 |
プロンプトキャッシュの活用#
OpenAIやAnthropicが提供するプロンプトキャッシュ機能を活用することで、繰り返し使用するコンテキストのコストを削減できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
# キャッシュ効率を高める構成
def build_request(user_input: str) -> dict:
return {
# キャッシュされやすい部分(前方に配置)
"system": STATIC_SYSTEM_PROMPT, # 変更頻度が低い
"context": [
PROJECT_CONTEXT, # プロジェクト情報
CODING_STANDARDS, # コーディング規約
],
# 動的な部分(後方に配置)
"messages": [
{"role": "user", "content": user_input}
]
}
|
注意点とアンチパターン#
避けるべきパターン#
1. 情報過多(Context Overload)
関係のない情報を大量に含めると、モデルの注意が分散し、品質が低下します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
# 悪い例
<context>
プロジェクトの全ファイル一覧(1000件)
全データベーステーブルのスキーマ
過去1年分のコミット履歴
...(省略)...
</context>
# 良い例
<context>
<relevant_files>
- src/services/order.service.ts(編集対象)
- src/types/order.types.ts(参照する型定義)
</relevant_files>
<relevant_schema>
orders, order_items テーブルのみ
</relevant_schema>
</context>
|
2. 矛盾する指示
システムプロンプトとユーザープロンプトで矛盾する指示を与えると、予測不能な動作を引き起こします。
1
2
3
4
5
6
7
|
# 悪い例
System: 必ずJSON形式で回答してください
User: Markdown形式で結果を整形してください
# 良い例
System: 出力形式はユーザーの指示に従ってください
User: 結果をJSON形式で出力してください
|
3. コンテキストの断片化
関連する情報が離れた位置に配置されると、モデルが関係性を把握しにくくなります。
1
2
3
4
5
6
7
8
9
10
|
# 悪い例
<types>型定義A</types>
<other_info>無関係な情報</other_info>
<implementation>型Aを使う実装</implementation>
# 良い例
<feature_a>
<types>型定義A</types>
<implementation>型Aを使う実装</implementation>
</feature_a>
|
セキュリティ上の考慮事項#
コンテキストに機密情報を含める場合、以下に注意が必要です。
- APIキーやパスワードをコンテキストに含めない
- 個人情報はマスキングまたは匿名化する
- 社外秘の情報は適切なアクセス制御を実装する
- ログやモニタリングでコンテキスト内容が露出しないよう配慮する
ツールとフレームワーク#
コンテキストエンジニアリングを支援するツール#
| ツール/フレームワーク |
用途 |
特徴 |
| LangChain |
LLMアプリ構築 |
RAG、チェーン、エージェント機能 |
| LlamaIndex |
データインデックス |
ドキュメントのベクトル化と検索 |
| Model Context Protocol (MCP) |
ツール連携 |
標準化されたツールインターフェース |
| Anthropic Claude Agent SDK |
エージェント開発 |
Claudeに最適化されたエージェント構築 |
開発環境での活用#
GitHub CopilotやCursorなどのAI開発ツールでも、コンテキストエンジニアリングの原則が適用されています。
.github/copilot-instructions.md: プロジェクト固有の指示を定義
@file参照: 関連ファイルを明示的にコンテキストに追加
- プロンプトファイル(
.prompt.md): 再利用可能なプロンプトテンプレート
まとめ#
コンテキストエンジニアリングは、プロンプトエンジニアリングを包含する上位概念であり、LLMアプリケーションの品質を決定づける重要な技術領域です。
主要なポイント#
| 観点 |
要点 |
| 定義 |
コンテキストウィンドウに適切な情報を配置する技術体系 |
| 構成要素 |
システムプロンプト、RAG、ツール定義、状態管理 |
| 最適化 |
重要度による優先順位付け、情報の圧縮、キャッシュ活用 |
| 注意点 |
情報過多の回避、矛盾する指示の排除、セキュリティ配慮 |
今後の展望#
コンテキストウィンドウのサイズは年々拡大しており(Geminiは最大200万トークン)、より多くの情報を含められるようになっています。しかし、サイズの拡大に伴い「何を含めるか」「どう構造化するか」という設計判断の重要性はさらに増しています。
AIエージェントの自律性が高まるにつれ、コンテキストエンジニアリングは「AIの認知能力を設計する」行為として、ますます重要になるでしょう。
参考リンク#