はじめに
「さっき言ったこと、覚えてる?」 「申し訳ありませんが、以前の会話の内容を…」
この経験、ありませんか?
AIが「忘れる」のは、バグでも性能の問題でもない。 コンテキストウィンドウという仕組みを理解していないだけだ。
この記事では、コンテキストウィンドウの本質と、それを踏まえた効率的なAI開発の方法を解説する。
コンテキストウィンドウとは
LLMの「記憶」の正体
LLMには記憶がない。 毎回、入力されたテキスト全体を見て回答を生成している。
graph TB
subgraph Context["コンテキストウィンドウ<br/>← この枠に収まる範囲しか「見えない」 →"]
A["システムプロンプト"]
B["過去の会話履歴"]
C["今回の入力"]
end
A --> B
B --> C
style Context fill:#f0f0f0,stroke:#333,stroke-width:3px
style A fill:#e1f5ff
style B fill:#fff3e0
style C fill:#e8f5e9
つまり、「覚えている」のではなく、「毎回全部読んでいる」。 そして、枠を超えた部分は物理的に見えない。
2025年現在の主要モデル
| モデル | コンテキスト長 | 概算文字数 |
|---|---|---|
| Claude Sonnet 4 | 200K tokens | 約15万文字 |
| Claude Opus 4 | 200K tokens | 約15万文字 |
| GPT-4o | 128K tokens | 約10万文字 |
| GPT-4 Turbo | 128K tokens | 約10万文字 |
| Gemini 1.5 Pro | 2M tokens | 約150万文字 |
| Gemini 2.0 Flash | 1M tokens | 約75万文字 |
注意: 大きければいいわけではない。後述する「Lost in the Middle」問題がある。
なぜ「忘れる」のか
コンテキストの消費内訳
Claude Codeでの典型的なセッション:
消費されるトークン(概算):
CLAUDE.md(プロジェクト設定) : 2,000 tokens
会話履歴(10往復) : 15,000 tokens
読み込んだソースコード : 50,000 tokens
ツール実行結果 : 20,000 tokens
──────────────────────────────────────────
合計 : 87,000 tokens
200Kのうち、すでに半分近くを消費している。 会話を続けるほど、履歴が積み上がり、限界に近づく。
限界を超えると何が起きるか
会話1: 「ユーザー認証を実装して」
会話2: 「次にカート機能を」
会話3: 「決済処理を追加して」
...
会話15: 「最初に作った認証の修正が必要」
→ 「認証機能の詳細を教えていただけますか?」
古い会話履歴が切り捨てられるか、要約されている。 これが「忘れた」の正体。
Lost in the Middle問題
中間の情報は忘れやすい
LLMは最初と最後の情報を重視し、中間を軽視する傾向がある。
graph LR
subgraph Attention["LLMの注意度分布"]
Start["最初<br/>⬆️⬆️⬆️<br/>注意度: 高"]
Middle["中間<br/>⬇️⬇️⬇️<br/>注意度: 低"]
End["最後<br/>⬆️⬆️⬆️<br/>注意度: 高"]
end
Start -.->|軽視される| Middle
Middle -.->|重視される| End
style Start fill:#4caf50,color:#fff
style Middle fill:#ff9800,color:#fff
style End fill:#4caf50,color:#fff
style Attention fill:#fafafa,stroke:#666
10,000行のコードを渡して「バグを探して」と言っても、 中間にあるバグは見落とされる可能性が高い。
対策
1. 重要な指示は最初か最後に置く
2. 長いコードは分割して渡す
3. 「特に〇〇の部分を見て」と明示する
4. 不要な情報は含めない
効率的なプロンプト設計
悪い例:コンテキストの無駄遣い
えーっと、昨日から考えてたんですけど、
ちょっと難しいかもしれないんですが、
ユーザー登録機能を作りたくて、
あ、言い忘れましたがNext.jsを使ってます。
データベースはPostgreSQLで、認証はNextAuthです。
メール確認機能も欲しいかな...どう思います?
問題点:
- 冗長な表現でトークンを浪費
- 情報が散らばっている
- 質問なのか依頼なのか不明確
良い例:構造化された指示
# ユーザー登録機能の実装
## 環境
- Next.js 14 (App Router)
- PostgreSQL + Prisma
- NextAuth.js v5
## 要件
1. メールアドレス + パスワードで登録
2. メール確認(トークン方式)
3. パスワードはbcryptでハッシュ化
## 成果物
- app/api/auth/register/route.ts
- lib/mail.ts
- prisma/schema.prismaへの追加
効果:
- 必要な情報だけを伝達
- 構造化されていて理解しやすい
- 成果物が明確
セッション管理の実践
セッションを分割すべきタイミング
□ 10往復以上の会話になった
□ 別のタスクに移る
□ 大きなファイルを複数読み込んだ
□ AIの回答が不正確になってきた
引き継ぎプロンプト
新しいセッションを始めるときは、必要な文脈だけを伝える:
# 前回のセッションからの引き継ぎ
## 完了した作業
- ユーザー認証(JWT + HttpOnly Cookie)
- Prismaスキーマ定義
- メール送信(SendGrid)
## 今回の作業
パスワードリセット機能の実装
## 関連ファイル
- src/lib/auth.ts
- src/routes/auth.ts
- prisma/schema.prisma
全ての会話履歴を引き継ぐのではなく、結果だけを引き継ぐ。
Claude Codeでの活用
CLAUDE.mdを活用する
プロジェクトルートに CLAUDE.md を置くと、Claude Codeが自動で読み込む。
# CLAUDE.md
## プロジェクト
ECサイトのバックエンドAPI
## 技術スタック
- Node.js 20 + TypeScript
- Hono(Webフレームワーク)
- Prisma + PostgreSQL
## コマンド
- pnpm dev: 開発サーバー
- pnpm test: テスト
- pnpm build: ビルド
## コーディング規約
- any禁止
- 関数は単一責任
- テストカバレッジ80%以上
これにより、毎回同じ説明をする必要がなくなる。
効率的な指示の出し方
# 商品検索APIの実装
## エンドポイント
GET /api/products/search
## パラメータ
- q: 検索キーワード
- category: カテゴリID(任意)
- minPrice, maxPrice: 価格範囲(任意)
- limit, offset: ページネーション
## 実装ファイル
1. src/routes/products.ts
2. src/services/product-search.ts
3. tests/product-search.test.ts
「いい感じに作って」ではなく、具体的に指定する。
並行開発のテクニック
なぜ並行開発が有効か
graph TB
subgraph Serial["直列作業(合計時間: A + B + C)"]
A1[タスクA] --> B1[タスクB]
B1 --> C1[タスクC]
end
subgraph Parallel["並行作業(合計時間: max A, B, C)"]
A2[タスクA]
B2[タスクB]
C2[タスクC]
end
style Serial fill:#ffebee
style Parallel fill:#e8f5e9
style A1 fill:#2196f3,color:#fff
style B1 fill:#2196f3,color:#fff
style C1 fill:#2196f3,color:#fff
style A2 fill:#4caf50,color:#fff
style B2 fill:#4caf50,color:#fff
style C2 fill:#4caf50,color:#fff
独立したタスクは、複数のClaude Codeセッションで同時に進められる。
並行開発に適したケース
適している:
- ユーザー認証 と 商品カタログ(別ドメイン)
- APIエンドポイント と フロントエンドUI
- 新機能実装 と 既存コードのリファクタリング
適していない:
- 認証機能 と 認証が必要なAPI(依存関係あり)
- 同じファイルを触る作業(コンフリクト)
実践方法
ターミナル1: claude(認証機能)
ターミナル2: claude(商品機能)
ターミナル3: 開発サーバー + git操作
各ターミナルで独立したセッションを持ち、別々のタスクを進行。
定期的に git status でコンフリクトを確認。
よくある失敗と対策
失敗1: 一度に大量の依頼
❌ 「認証、商品、カート、決済、全部作って」
✅ 「まず認証機能を作って。完成したら次を依頼する」
一度に大量の依頼をすると、コンテキストが溢れて中途半端な結果になる。
失敗2: 曖昧な指示
❌ 「もっといい感じにして」
✅ 「エラーハンドリングを追加して。特にDB接続失敗時の処理」
具体的に何をしてほしいか明示する。
失敗3: 過去の会話に依存
❌ 「さっき作った関数を修正して」
✅ 「src/lib/auth.ts の validateToken 関数を修正して」
ファイルパスと関数名を明示する。「さっき」は忘れている可能性がある。
まとめ
コンテキストウィンドウの本質
LLMは「記憶」しているのではなく、
毎回「全部読んでいる」。
枠を超えたら、物理的に見えない。
だから「忘れる」。
効率的なAI開発のポイント
| ポイント | 具体的な行動 |
|---|---|
| 構造化された指示 | Markdown形式で要件を整理 |
| セッション分割 | 10往復を目安に新規セッション |
| 引き継ぎ | 結果だけを伝える、履歴は不要 |
| CLAUDE.md活用 | プロジェクト設定を永続化 |
| 並行開発 | 独立タスクは複数セッションで |
最後に
AIは道具だ。 道具の特性を理解して使えば、生産性は劇的に上がる。
コンテキストウィンドウを意識するだけで、 「AIが忘れる」「思った結果が出ない」という問題の多くは解決する。
コンテキストを制する者が、AI開発を制す。