はじめに — 同じ質問をしたのに、AIの回答が全然違った
同じAIに、同じ質問をしているのに、返ってくる回答の質が全然違う。
そういう経験、ないですか。
ある日は的確なコードが返ってくるのに、別の日は見当違いの回答が来る。「AIガチャ」なんて呼ぶ人もいるけど、正直に言うと、あれはガチャじゃない。
渡している文脈が違うだけ なんです。
僕はここ半年くらい、開発業務のほぼ全てでAIを使ってきた。調査、設計、実装、レビュー、テスト...全フェーズで。その中で気づいたのは、 「プロンプトの書き方」を工夫するより、「何を・どんな形式で・どのタイミングでAIに渡すか」を設計する方が、はるかに効果がある ということ。
これが今「Context Engineering」と呼ばれている考え方で、Anthropicが公式に提唱し、Manus AIも実運用の知見として公開している。
この記事では、開発ライフサイクルの各フェーズで、具体的にどんなコンテキストを渡せばAIが本気を出してくれるのかを、プロンプト例とコード例つきで整理する。
「上手に聞く」より「上手に渡す」方が100倍大事
まず、Prompt EngineeringとContext Engineeringの違いを整理しておきたい。
| 観点 | Prompt Engineering | Context Engineering |
|---|---|---|
| 焦点 | 「どう聞くか」(質問文の書き方) | 「何を渡すか」(文脈全体の設計) |
| 対象 | 1回のリクエスト | 開発セッション全体 |
| 設計対象 | プロンプトテンプレート | System Prompt + ファイル + ツール + メモリ |
Prompt Engineeringは「上手な聞き方」のテクニック。これはこれで大事。
でも、Context Engineeringは そもそもAIが見ている世界そのものを設計する という、もう一段上のレイヤーの話になる。
Anthropicの公式ガイドでは、こう表現されている。
Context Engineering = LLMの有限なアテンション予算に対して、最小の高シグナルトークンで、最大の成果を引き出すこと
つまり、LLMには「一度に集中できる情報量の上限」がある。そこに何を入れるかを設計するのが、Context Engineering。
たとえるなら、Prompt Engineeringは「面接での受け答えの練習」で、Context Engineeringは「面接官の手元に置く資料一式の設計」 。面接官がどんな資料を見ながら判断するかで、結果は大きく変わる。
Context Engineeringで押さえておきたい3つの原則
ここから具体に入る前に、全フェーズに共通する3つの原則を押さえておきたい。
原則1: 最小高シグナルトークン
「とにかく全部渡せばいい」というのは、実は逆効果。
LLMには「Lost in the Middle」という現象がある。コンテキストが長すぎると、真ん中あたりの情報を見落とす。だから、 必要十分な情報を、優先度順に、コンパクトに渡す のが鉄則。
# ❌ やりがちなパターン
「このプロジェクトの全ファイルを読んで、改善点を教えて」
# ✅ 高シグナルなパターン
「以下の3ファイルを読んで、認証フローのエラーハンドリングの改善点を教えて
- src/auth/login.ts(認証ロジック本体)
- src/middleware/auth.ts(認証ミドルウェア)
- docs/auth-spec.md(認証仕様書)」
原則2: Just-in-Time Loading
全ての情報を最初から渡すのではなく、 必要な時に必要な情報を引き込む 設計にする。
これはManus AIが実運用で得た知見でもある。ファイルシステムやツールを通じて、エージェントが自分で必要な情報を取りに行く設計が、最もスケーラブル。
// Just-in-Time Loadingの考え方(疑似コード)
// ❌ 最初に全部読む
const allFiles = await readAllProjectFiles();
const prompt = `${allFiles}\n\n質問: ...`;
// ✅ 必要な時に必要なファイルだけ読む
const relevantFiles = await searchFiles("認証 エラー");
const prompt = `${relevantFiles}\n\n質問: ...`;
原則3: ファイルシステム = 外部メモリ
LLMのコンテキストウィンドウは有限だけど、ファイルシステムは事実上無限。
Anthropicが推奨する CLAUDE.md や、Cursorの .cursorrules は、まさにこの原則の実装例。 プロジェクトの前提条件、技術スタック、命名規則、判断基準をファイルに書いておき、AIがセッション開始時に自動で読む という設計。
# CLAUDE.md の例
## 技術スタック
- Next.js 15 (App Router)
- TypeScript strict mode
- MongoDB (Mongoose)
- Tailwind CSS
## コーディング規約
- 関数はアロー関数で統一
- エラーハンドリングは Result型パターン
- テストは Vitest + Testing Library
## 命名規則
- コンポーネント: PascalCase
- hooks: use + PascalCase
- API Routes: kebab-case
この3原則を頭に置いた上で、各開発フェーズでの実践を見ていこう。
Phase 1: 調査・要件定義 — 「○○について教えて」では、AIは本気を出せない
開発の最上流。ここのコンテキスト設計が甘いと、後工程全てがブレる。
渡すべきコンテキスト
| 要素 | 内容 | なぜ必要か |
|---|---|---|
| プロジェクトの目的 | 何のために作るのか | 調査の方向性を定める |
| 技術的制約 | 使える技術、使えない技術 | 実現不可能な提案を避ける |
| ユーザー像 | 誰が使うのか | 要件の優先度判断に影響 |
| 既存システムの情報 | 現状のアーキテクチャ | 互換性・移行パスの考慮 |
Before / After
# ❌ Before(コンテキストなし)
「Webアプリに認証機能を追加したい。どんな方法がある?」
→ OAuth, JWT, Session, Firebase Auth... と一般的な選択肢が羅列されるだけ
# ✅ After(コンテキストあり)
「以下の前提で、認証機能の技術選定を手伝ってほしい。
## プロジェクト概要
- Next.js 15 (App Router) + MongoDB のSaaSアプリ
- 月額課金制(Stripe Connect)
- 想定ユーザー: 100-1000人規模の中小企業
## 制約条件
- Supabaseは使わない(MongoDB統一方針)
- セルフホスト可能なソリューション優先
- 多要素認証(MFA)は将来対応したい
## 判断基準
1. MongoDB との統合のしやすさ
2. Next.js App Router との相性
3. 将来のMFA対応の容易さ
候補を3つ挙げて、上記の判断基準で比較表を作ってほしい。」
→ NextAuth.js v5, Lucia Auth, 自前JWT+MongoDB の3候補が、
判断基準に沿った比較表つきで返ってくる
ここでの人間の役割は「判断基準を決めること」 。選択肢を出すのはAIの方が速い。でも、何を基準に選ぶかを決められるのは人間だけ。
Phase 2: 設計 — 制約条件を渡さない設計相談は、ただの雑談になる
設計フェーズでは、 「やりたいこと」だけでなく「やれないこと」「やらないこと」を明示的に渡す のがポイント。
渡すべきコンテキスト
# 設計相談時のコンテキストテンプレート
## やりたいこと(Goal)
- [具体的な機能要件]
## やれないこと(Constraints)
- [技術的制約、予算制約、期限制約]
## やらないこと(Non-Goals)
- [スコープ外と決めたこと]
## 既存の設計(Current Architecture)
- [現在のディレクトリ構成、DB設計、API設計]
## 参考にしたい設計(References)
- [類似サービスのアーキテクチャ、参考にした記事]
プロンプト例: API設計の相談
「以下の前提で、注文管理APIの設計をレビューしてほしい。
## Goal
弁当ECサイトの注文管理API。オーナーが注文を受付→調理中→完了と
ステータス管理できる。
## Constraints
- Next.js API Routes (App Router)
- MongoDB + Mongoose
- 1オーナー = 1店舗(マルチテナントではない)
- リアルタイム通知は Phase 2 で対応(今は不要)
## Non-Goals
- 決済処理(Stripe側で完結)
- 在庫管理(別APIで対応済み)
- 配達管理
## Current Architecture
/api/
/orders/
route.ts # GET(一覧), POST(作成)
[id]/
route.ts # GET(詳細), PATCH(更新)
status/
route.ts # PATCH(ステータス変更)
## 確認してほしい観点
1. RESTfulな設計として適切か
2. ステータス遷移のバリデーション設計
3. エラーレスポンスの統一パターン」
この「Non-Goals」を渡すのが地味に大事で、 AIは渡された情報から「やれること全て」を提案しようとする傾向がある 。「これはやらない」と明示しないと、不要な複雑性を持ち込んだ設計が返ってくる。
Phase 3: 実装 — 「このコード書いて」の裏側で、渡すべき5つの文脈
実装フェーズが、Context Engineeringの効果が最も顕著に出るところ。
渡すべき5つのコンテキスト
-
プロジェクト設定ファイル —
CLAUDE.md/.cursorrules/AGENTS.md - 関連する既存コード — 変更対象のファイルと、依存関係にあるファイル
- テストコード — 既存のテストパターン(AIにスタイルを揃えさせる)
- 型定義 — TypeScriptの型 / APIスキーマ / DBスキーマ
- エラーログ — バグ修正時は、実際のエラーメッセージとスタックトレース
コード例: プロジェクト設定ファイルの実装
# CLAUDE.md(プロジェクトルートに配置)
## エラーハンドリング規約
全てのAPI Routeは以下のパターンで実装する:
\`\`\`typescript
// Result型パターン
type Result<T> = { success: true; data: T } | { success: false; error: string; code: number };
// API Route テンプレート
export async function POST(request: Request) {
try {
const body = await request.json();
// バリデーション
const validated = schema.safeParse(body);
if (!validated.success) {
return Response.json(
{ success: false, error: validated.error.message, code: 400 },
{ status: 400 }
);
}
// ビジネスロジック
const result = await doSomething(validated.data);
return Response.json({ success: true, data: result });
} catch (error) {
console.error('[POST /api/xxx]', error);
return Response.json(
{ success: false, error: 'Internal Server Error', code: 500 },
{ status: 500 }
);
}
}
\`\`\`
## テストの書き方
- Vitest + Testing Library
- テストファイルは `__tests__/` ディレクトリに配置
- ファイル名は `{対象}.test.ts`
- describe/it のネストは最大2階層
このファイルがプロジェクトルートにあるだけで、AIが生成するコードの品質が劇的に安定する。 毎回プロンプトで指示しなくても、設定ファイルが「暗黙の前提」をAIに共有してくれる 。
プロンプト例: 実装依頼
「以下のMongooseスキーマに基づいて、注文ステータス変更のAPI Routeを実装してほしい。
## 関連ファイル
- models/Order.ts(Orderスキーマ — 添付)
- lib/db.ts(DB接続ユーティリティ — 添付)
- CLAUDE.md のエラーハンドリング規約に従うこと
## 仕様
- PATCH /api/orders/[id]/status
- bodyに { status: "cooking" | "ready" | "completed" } を受け取る
- ステータス遷移ルール: pending → cooking → ready → completed(逆行不可)
- 遷移違反時は 400 エラー
## 既存テストのパターン
\`\`\`typescript
describe('PATCH /api/orders/[id]/status', () => {
it('should update status from pending to cooking', async () => {
// このパターンに合わせて実装してほしい
});
});
\`\`\`
」
Phase 4: レビュー・テスト — AIレビューの精度を決めるのは「プロンプト」ではなく「文脈の質」
レビューとテストは、実は最もContext Engineeringの恩恵を受けやすいフェーズかもしれない。
レビュー時に渡すべきコンテキスト
| レイヤー | 内容 | 例 |
|---|---|---|
| Persistent Context | プロジェクト全体のルール | CLAUDE.md、コーディング規約 |
| PR Context | このPRの目的と背景 | PR description、関連Issue |
| Diff Context | 変更内容そのもの | git diff、変更ファイル |
プロンプト例: コードレビュー依頼
「以下のPRをレビューしてほしい。
## PR概要
注文ステータス変更APIの新規実装。
関連Issue: #42(注文管理機能)
## レビュー観点(優先度順)
1. ステータス遷移ロジックの正しさ
2. エラーハンドリングがCLAUDE.mdの規約に準拠しているか
3. 型安全性(any型の使用がないか)
4. エッジケースの考慮漏れ
5. テストの網羅性
## 変更ファイル
(以下にdiffを添付)
## 注意
- 既存のOrder.tsのスキーマは変更しない方針
- パフォーマンス最適化は今回のスコープ外」
テスト生成時のコンテキスト
テストコードの生成で一番重要なのは、 「既存テストのスタイルを渡す」 こと。
「以下の仕様に基づいてテストコードを生成してほしい。
## テスト対象
src/app/api/orders/[id]/status/route.ts
## テストの書き方(既存パターンに合わせる)
- フレームワーク: Vitest
- DBモック: mongodb-memory-server
- 各テストケースで独立したDB状態を作る
- テストデータは factories/ のファクトリ関数を使う
## 既存テストの参考(スタイルを揃えてほしい)
\`\`\`typescript
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { createTestOrder } from '../factories/order';
import { setupTestDB, teardownTestDB } from '../helpers/db';
describe('PATCH /api/orders/[id]/status', () => {
beforeAll(async () => { await setupTestDB(); });
afterAll(async () => { await teardownTestDB(); });
it('正常系: pending → cooking への遷移', async () => {
const order = await createTestOrder({ status: 'pending' });
// ...
});
});
\`\`\`
## 必要なテストケース
1. 正常系: 各ステータス遷移(pending→cooking, cooking→ready, ready→completed)
2. 異常系: 逆行遷移の拒否(cooking→pending)
3. 異常系: 存在しないOrderID
4. 異常系: 不正なステータス値
5. 異常系: bodyなしリクエスト」
テストケースの「何をテストすべきか」を決めるのは人間の仕事。テストコードの実装はAIの方が速い。この切り分けが大事。
エンジニアの新しい役割 — 「コードを書く人」から「文脈を設計する人」へ
ここまで各フェーズを見てきて、気づくことがある。
どのフェーズでも、人間がやっているのは「判断」と「文脈の設計」 だということ。
- 調査フェーズ → 判断基準を決める
- 設計フェーズ → 制約条件とNon-Goalsを定義する
- 実装フェーズ → 設定ファイルとコーディング規約を整備する
- レビューフェーズ → レビュー観点と優先順位を設計する
- テストフェーズ → テストケースの仕様を決める
コードそのものを書く時間は、確実に減っている。代わりに増えているのは、 「AIが正しい出力を出せるような文脈を設計する時間」 。
これは衰退じゃない。進化だと思う。
電卓が発明された時、数学者の仕事がなくなったわけじゃない。計算という作業から解放されて、もっと本質的な「問いを立てる」「証明を設計する」という仕事に集中できるようになった。
エンジニアも同じ。「コードを書く」という作業から解放されて、 「何を作るか」「なぜ作るか」「どんな制約の中で作るか」 という、もっと本質的な設計に集中できるようになっている。
...ただ、ここで一つ大事なことがあって。
「文脈を設計する力」は、「コードを書いてきた経験」がないと身につかない 。
何をコンテキストとして渡すべきかがわかるのは、自分でコードを書いてきたからこそ。どんなエラーが起きやすいか、どんな設計が破綻しやすいか、どんなテストケースが漏れやすいか...そういう「痛み」の経験が、コンテキスト設計の精度を決める。
だから、「AIがコードを書くならコーディングの勉強は不要」というのは、ちょっと違うかなと思っている。
明日の朝から試せるContext Engineeringチェックリスト
最後に、すぐに始められるアクションを整理しておく。
-
プロジェクトルートに
CLAUDE.md/.cursorrulesを作る — 技術スタック、命名規則、エラーハンドリングパターンを書く - AIへの質問に「判断基準」を添える — 「どれがいい?」ではなく「この基準で比較して」と渡す
- 設計相談に「Non-Goals」を必ず入れる — やらないことを明示して、不要な複雑性を防ぐ
- 実装依頼に「関連する既存コード」を添付する — AIは既存コードのスタイルに合わせてくれる
- レビュー依頼に「レビュー観点と優先順位」を渡す — 漫然としたレビューを防ぐ
- テスト生成に「既存テストのサンプル」を渡す — スタイルの統一が自動で実現する
- 「全部渡す」より「必要なものだけ渡す」を意識する — Less is more
おわりに
Context Engineeringは、特別な技術じゃない。
「AIに渡す情報を、ちゃんと設計しよう」という、考えてみれば当たり前のこと。
でも、この「当たり前」を意識的にやるかやらないかで、AIの出力品質は驚くほど変わる。Anthropicの調査では、コンテキスト設計の改善だけでエージェントのタスク成功率が最大54%向上したという報告もある。
大事なのは、「AIが賢くなるのを待つ」んじゃなくて、 「AIが賢く振る舞えるような文脈を、人間が設計する」 ということ。
Context Engineeringは、エンジニアの新しい必須スキルになっていくと思う。
...というか、正直に言うと、これまでも優秀なエンジニアは無意識にやっていたことなんですよね。仕様書を丁寧に書く、PRの説明をちゃんと書く、コードレビューの観点を明確にする。全部、「相手(AI or 人間)に適切な文脈を渡す」という行為。
Context Engineeringは、その行為に名前がついて、体系化されただけ。
だから、恐れることはない。 あなたが普段やっている「丁寧な仕事」が、そのままAI時代の最強スキルになる 。
そう考えると、ちょっとワクワクしませんか。
まずは、プロジェクトルートに CLAUDE.md を1つ作ることから始めてみてほしい。技術スタック、命名規則、エラーハンドリングのパターン。たった20行でいい。それだけで、AIの出力が見違えるほど変わるはず。