エンジニアリングの 3 層¶
ここまで 14 章で使い方、1 章で中身、1 章で拡張を扱ってきた。この章では章 01-16 の知識を3 つのエンジニアリング領域という別の軸で整理し直す。
3 層とは何か¶
LLM エージェントの品質は 3 つの独立 (だが補完的) な craft から成り立つ:
| 層 | 何を触るか | 主な道具 | 一言で |
|---|---|---|---|
| プロンプトエンジニアリング | LLM に読ませる文字列の書き方 | system prompt / few-shot / CoT / 出力フォーマット指示 / tool description | 「どう書けば LLM が意図通りに動くか」 |
| コンテキストエンジニアリング | context window に何を詰めるかを動的に決める仕組み | RAG / 会話履歴の圧縮・要約 / 選択的保持 / プロンプトキャッシュ / tool 結果整形 | 「情報を過不足なく渡すための選別と成形」 |
| ハーネスエンジニアリング | LLM を包むコード設計 | createAgent / tool 実装 / state store / observability / guardrails / 停止条件 / retry / fallback | 「LLM 周りのパイプをどう組むか」 |
この 3 層は独立ではなく補完関係で、1 つだけ最適化しても全体は良くならない。
なぜ 3 つに分けるのか¶
2023 年頃まで「プロンプトエンジニアリング」が一人歩きしていた: 「良い system prompt を書けば良いエージェントができる」という空気感。
2024 年頃から「コンテキストエンジニアリング」という言葉が出てきた背景:
- RAG / 長い履歴 / マルチエージェントで context window の中身が複雑化
- 「どの情報をどのタイミングで入れるか」が独立した難しさになった
- プロンプトを書くだけでなく、動的に情報を選別する仕組みが要る
2025 年には「ハーネスエンジニアリング」が Claude Code 等を文脈に語られるように:
- 「単一の LLM 呼び出しではなく、それを包むシステム全体の設計」が品質を決める
- retry / fallback / observability / state management / guardrails といったソフトウェアエンジニアリング的な関心事が前面に
3 層に分けて語るのは、1 つの視点で全部を語ろうとすると破綻するほど関心事が増えたから。
既存章との対応¶
各エンジニアリング領域が、これまでの章のどこで扱ったかを整理する。
プロンプトエンジニアリング (Prompt Engineering)¶
| 章 | 対応 |
|---|---|
| 05 Tool calling | tool の description の書き方 = LLM が読む説明文 |
| 12 サンプリング | temperature / top_p 等 LLM の応答特性の制御 |
| 13 system prompt の設計 | system prompt の書き方そのもの |
| 14 ガード の一部 | 悪意あるプロンプト対策のうち「指示で守る」部分 |
関心事: LLM に「どう読ませ、どう判断させ、どう書かせるか」の書き方技術
コンテキストエンジニアリング (Context Engineering)¶
| 章 | 対応 |
|---|---|
| 03 トークンとコンテキストウィンドウ | 上限との付き合い方、超過対策 4 パターン |
| 04 Messages と state | messages 配列 = context の実体、状態の育ち方 |
| 07 記憶の多層モデル | L2 キャッシュ / L3 注入 / L4 外部ストレージの使い分け |
| 09 埋め込み | 意味的検索のための表現 |
| 10 RAG | 外部知識の取り出し → context 注入 |
関心事: 有限の context window に「何を、どれだけ、どの形で」詰めるかの設計
ハーネスエンジニアリング (Harness Engineering)¶
| 章 | 対応 |
|---|---|
| 02 LLM の 1 回の呼び出し | API の叩き方 |
| 05 Tool calling | tool の実装 (description は prompt 側、実装はこちら) |
| 06 エージェントループ | tool 実行 / 停止条件 / retry / fallback / 無限ループ対策 |
| 08 Observability | trace / span / 集計 / alert |
| 11 評価 | データセット / 回帰テスト / CI 組み込み |
| 14 ガード の大部分 | 4 層ガードの実装 |
関心事: LLM を包むソフトウェアアーキテクチャ。リトライ / フォールバック / 観測 / 評価 / セキュリティ / state 管理 etc.
3 層の interaction (依存関係)¶
プロンプト層
┌────────────────┐
│ 指示 / 例 / │
│ 出力フォーマット │
└────────┬───────┘
│ 「何を期待するか」
▼
コンテキスト層
┌────────────────┐
│ 履歴 / 検索結果 │
│ メモリ / ツール │
│ 定義 / RAG │
└────────┬───────┘
│ 「何を見せるか」
▼
ハーネス層
┌────────────────┐
│ createAgent / │
│ state / loop / │
│ tools 実行 / │
│ observe / retry │
└────────┬───────┘
│
▼
[ 実際の挙動 ]
│
▼
┌──────────────┐
│ 観測 / 評価 │
└──────┬───────┘
│ フィードバック (改善ループ)
▲
│
3 層それぞれへ
どの層から手を入れるか¶
エージェントに問題があったとき、3 層のどこを直すかの判断:
プロンプト層を疑うべきとき¶
- 出力フォーマットが崩れている
- 指示の順序を守らない
- 不必要に饒舌 / 無愛想
- tool をあるべき場面で使わない
- 対処: system prompt の書き直し / few-shot 追加 / tool description 強化
コンテキスト層を疑うべきとき¶
- 「知らないはず」のことを知らない (RAG 不足 / 履歴欠損)
- 会話が長くなると話が繋がらない (context window 超過 / 圧縮)
- 同じ質問で毎回違う結果 (context に入る情報が毎回違う)
- hallucination (情報不足 or 古い知識の補完)
- 対処: RAG 追加 / 履歴圧縮ロジック / 検索精度改善 / キャッシュ
ハーネス層を疑うべきとき¶
- 途中で落ちる / エラーで止まる
- レイテンシが読めない
- コストが爆発する
- 並列実行でデータ競合
- セキュリティインシデント
- 対処: retry / fallback 追加 / tool 実装修正 / 評価追加 / ガード追加
1 つの症状に対して複数層の原因が絡むことも多い。観測 (第 8 章) を使ってどの層で問題が起きているか特定するのが第一歩。
改善アプローチの違い¶
3 層は改善のやり方も違う:
プロンプト層: デモ駆動¶
- 失敗例を集める (Langfuse からトレース抽出)
- 失敗パターンごとに system prompt を追加
- 評価データセットで回帰を確認
- 余計になった指示を削る ("lost in the middle" 対策)
- A/B テストでプロンプト版を比較
コンテキスト層: データ駆動¶
- context に入っている情報を評価
- 「本当に必要だったか」を事後分析
- 不要なものを落とし、足りないものを入れる
- RAG の retrieval 精度 / 履歴圧縮精度を測る
- 埋め込みモデル / chunking 戦略の A/B テスト
ハーネス層: エンジニアリング駆動¶
- コードとしてユニットテスト可能に
- リトライ / フォールバック / 観測 / 評価を仕組みとして備える
- CI に評価を組み込む
- セキュリティ監査 / 依存更新 / デプロイ自動化
- ソフトウェア工学の伝統的な道具がそのまま適用できる
agent-demo の各要素を 3 層にマッピング¶
本リポジトリの examples/agent-demo/ を 3 層で分解すると:
| 要素 | プロンプト層 | コンテキスト層 | ハーネス層 |
|---|---|---|---|
setup.ts の BASE_PROMPT / CHAT_ADDENDUM |
✅ system prompt 本体 | ||
tools.ts の description / schema |
✅ LLM が読む tool 説明 | ||
tools.ts の各 tool 実装関数 |
✅ 実際の計算・検索・API 呼出 | ||
agent-chat.ts の messages 保持 |
✅ 履歴 = context の実体 | ||
selectTools() によるツール数絞り込み |
✅ 渡す tool の選別 | ||
createAgentInstance |
✅ LangChain agent のインスタンス化 | ||
| エージェントループ (createAgent 内部) | ✅ tool 連鎖の制御 | ||
consumeEndChatSignal() |
✅ 対話ループの終了処理 | ||
NodeTracerProvider / LangfuseSpanProcessor |
✅ observability | ||
.mise.toml のタスク定義 |
✅ 実行環境 | ||
.env の LANGFUSE_PUBLIC_KEY 等 |
✅ 認証 / 設定 | ||
LiteLLM の fallback 設定 |
✅ ハーネス (モデル切替) | ||
| (未実装) RAG / Qdrant 統合 | ⏳ 将来拡張 | ⏳ 実装側 | |
| (未実装) ガード 4 層 | ⏳ 将来拡張 |
agent-demo は学習目的で3 層の最小実装を提供している。どの層も極端に凝ってはいないが、3 層全てが揃っている (ガード以外)。本番運用するなら:
- プロンプト層: system prompt を本番用にチューニング / few-shot 追加
- コンテキスト層: RAG 統合 / 履歴圧縮
- ハーネス層: ガード追加 / 評価 CI / デプロイ自動化
3 層の罠¶
(1) プロンプト層だけで解決しようとする¶
最もよくある失敗。「プロンプトに書けば守ってくれる」という期待で、実際はガード不在で事故が起きる。プロンプトだけでは守れない (第 14 章)。
(2) ハーネスを作り込みすぎてプロンプトが雑¶
逆の罠。リトライ / ガード / 観測はしっかりしているが system prompt が 2 行しかない、という状態。LLM の判断がブレて回り道ばかりする。
(3) コンテキスト層を軽視¶
RAG を「情報を放り込むだけ」と捉えて、chunking / re-rank / フィルタ を雑にやる。結果として関連性の低い情報が context に入り LLM が混乱する。コンテキスト層は最もエンジニアリング的な craft が要る。
(4) 3 層を同じ人 / チームがやろうとする¶
規模が大きくなると、プロンプトはドメイン専門家 / データサイエンティスト、コンテキストは情報検索エンジニア、ハーネスはソフトウェアエンジニアに分化していく。最初は 1 人でも構わないが、各層の考え方が違うことを認識しておく。
まとめ¶
- LLM エージェントの品質は プロンプト / コンテキスト / ハーネス の 3 層から成る
- 3 層は独立だが補完的。1 つだけ最適化しても全体は良くならない
- プロンプト: LLM に読ませる文字列の書き方 (章 04 description / 11 サンプリング / 12 system prompt)
- コンテキスト: context window に動的に情報を詰める仕組み (章 02 / 03 / 06 / 08 / 09)
- ハーネス: LLM を包むコード設計 (章 01 / 04 実装 / 05 / 07 / 10 / 13)
- 改善の駆動力もそれぞれ違う: プロンプトはデモ駆動 / コンテキストはデータ駆動 / ハーネスはエンジニアリング駆動
- agent-demo は 3 層の最小実装。本番運用にはどの層も拡張が要る
- よくある罠: プロンプトだけで解決しようとする / ハーネス過剰 / コンテキスト軽視 / 1 人で全部やろうとする