DSPy で現場プロンプトの改善ループを回す — metric と「所有権の境界」¶
作成日: 2026-06-27 出典 / きっかけ: 「現場の人が使うプロンプトを DSPy で改善したら、どう反映して改善ループを回す仕組みにすればいいか」という問いから派生した設計議論。自分(Ken)の harness / checker / proposer のメンタルモデルに DSPy をマッピングして整理した。 関連: ai駆動開発ループ_interview-dev-loop_整理 / llmops_データ基盤からの転用_整理 / agentic_reference_architecture_評価ループ / loop_engineering_入門
0. 要点(3行)¶
- DSPy を噛ませると「現場が使うプロンプト=編集対象の文字列」という前提が崩れる。最適化成果物はプロンプト文字列ではなく、コンパイル済み program(signature の instructions + few-shot demos)というシリアライズ可能なオブジェクト(
program.save()で JSON 化)。「改善をどう反映するか」は文字列書き換えではなくランタイムがロードする成果物の差し替え。 - 本当のボトルネックは optimizer ではなく metric とラベル。コンパイルは安い。詰まるのは「現場の入出力の良し悪しをどう数値化するか」。設計の重心は最適化器ではなく「現場利用からどれだけ安く良い signal を回収できるか」に置く。
- 核心の問い「どう反映するか」は所有権の切り分けで答えが出る。signature(意図)=人が所有、demos/instruction の最終 phrasing = DSPy が所有。現場の手直しは artifact に直接マージせず、新しい候補として同じ eval gate を通す(勝てば昇格・負ければ捨てる=人の直感もデータ検証してから入れる)。
1. metric とは — 「出力の良し悪しを1個の数値に変換する関数」¶
ループ全体が最大化しようとする目的関数。DSPy 的にはほぼこのシグネチャだけ:
example(入力+正解ラベル/期待)と prediction(program の出力)を受け取り score を返す。optimizer はこの数値を上げる方向に demos/instructions を選ぶので、metric が定義した「良い」に向かって全部が最適化される。だから metric がズレてると、自信満々で間違った方向に最適化されたゴミが出る。
metric は二役をやる(ここが効く)¶
metric が「良い」を 1 個の数値に変換する
─────────────────────────────────────
example ─────┐
(入力+gold) │
▼
┌─────────────────────┐
│ metric() │
prediction ─▶ (example, prediction)│──▶ score (0.0–1.0 / bool)
(programの │ → score を返す │ │
出力) └─────────────────────┘ │
│ 同じ score が二役
┌─────────────────────┴─────────────────────┐
▼ ▼
① compile 時 = 適応度関数 ② eval 時 = 昇格ゲート
候補の demos / instructions を hold-out set の score で
score で評価して採用 昇格するか判定
(≈ proposer 選別) (≈ checker gate)
- ① compile 時=適応度関数: optimizer が候補を評価して採用(≒ 自分の harness の proposer 選別)。
- ② eval 時=昇格ゲート: hold-out set の score で昇格判定(
dspy.Evaluate)(≒ checker gate)。
metric の中身(実装の選択肢)¶
| 種類 | 内容 | 特徴 |
|---|---|---|
| ① exact match | answer == gold(厳密一致) |
安い |
| ② rule / checker | 構造・制約を満たすか | 自分の checker gate と同じ発想 |
| ③ LLM-as-judge | 別の Claude が採点 | 品質・主観を数値化 |
| ④ composite | 上記の重み付き合成 | 複数観点をまとめる |
⚠ optimizer は metric が報酬を与えるものを何でも exploit する。compile は安い。本当のボトルネックは metric の質。
- 現場ケースでは ②rule + ③judge の合成(④composite)が現実的なことが多い。「形式・必須項目を満たすか」は rule で安く弾き、「中身の質」は judge に回す二段。
- judge を入れるなら judge 自体を人手ラベルの小さなセットで検証しておく(しないとループが judge の癖/バイアスに収束する)。
2. ループの形 — 自分の harness/checker と同じ構造¶
自分のメンタルモデルへのマッピング:
| DSPy ループ | 自分の harness 用語 |
|---|---|
| optimizer | proposer |
| eval gate | checker |
| registry + rollout | deploy |
→ そのまま乗る。だから新規概念というより「既に持っている改善ループの、プロンプト版」。
3. どう反映するか = 所有権の切り分け¶
| 対象 | 所有者 | 説明 |
|---|---|---|
| signature(タスクの docstring +各 field の説明=意図) | 人 | 現場/FDE が「こういう出力にしたい」を書くのはここ |
| demos + instruction の最終 phrasing | DSPy | 人が直接コンパイル済み artifact を手パッチしない |
- 人がコンパイル済み artifact を手で直すと、次の compile で上書きされて drift する。だからやらない。
- 現場が「この方がいい」と手で直したもの → artifact に直接マージせず、新しい候補(signature 編集 or seed demo)として同じ eval gate を通す。勝ったら昇格、負けたら捨てる。
- これで人の直感もデータで検証されてから入ってくる。
4. Gate と rollout — プロンプトも deploy 扱い¶
- offline: held-out eval set で baseline を一定マージン上回る + regression なし を昇格条件に(
dspy.Evaluate)。 - online: いきなり全切替せず shadow → canary → A/B でロールバック可能に。
- registry: バージョン付き artifact を置く場所。git に save JSON を commit が一番素直。
- Langfuse を使うなら trace(現場ログ回収)+ dataset + prompt versioning + score が一気通貫で乗る(前に触っていたので相性良い)。
5. ハマりどころ4つ¶
- metric の質が全て — LLM-judge にするなら judge 自体を別途検証しないと、ループが judge のバイアスに収束する。
- demos の overfit — 現場の癖に過学習するので、held-out は分布をずらして取る。
- eval set の陳腐化 — 一度作って凍結すると数ヶ月で陳腐化。定期的に現場ログから入れ替える前提にする。
- 組織面 — 現場が「自分のプロンプトを取り上げられた」感覚になると signal が枯れる。人が読める intent 層(signature)は現場に残し、最適化は裏で回す見せ方が大事。
6. 未決の分岐(設計前に決める)¶
regime の分岐: 現場の人は誰か¶
- (a) プロンプトを自分で書く人 → signature をそのまま現場の編集面にできる。
- (b) 出来上がったツールを使うだけの人 → 現場は data provider / judge に徹してもらい、DSPy program は platform 側で持つ。ループの UI が変わる。
metric の分岐: 現場の出力は測れる種類か¶
- rule で機械的に正誤判定できる(フォーマット・必須項目・分類が当たってるか等)→ ②rule 中心。
- 文章の良し悪しのように人の主観でしか測れない → ③judge が必要(+judge 検証)。
- ここで metric の組み方が大きく変わる。この2分岐を先に確定させると後段が全部それに従う。
7. 一言で(自分用の腹落ち)¶
「現場のプロンプトを改善する」という問いは、突き詰めると「現場の良し悪しをどの metric で数値化するか」を決める問題に化ける。thumbs / 人手修正の diff / 下流の成功シグナル / LLM-judge のどれを一次ソースにするかを先に決めろ、というのは、それがそのまま metric の定義になるから。最適化器は後からどうにでもなる。
8. artifact とは — 「最適化の出力=配る単位」¶
ビルド/最適化の結果として出てくる、保存・配布できる“成果物ファイル”。ソースコードでも動いてるプロセスでもなく、その中間の「凍結された出力物」。
| ソースコード | artifact | 実行プロセス | |
|---|---|---|---|
| 何 | 人が書く・編集する | ビルド/最適化の出力。差し替えてデプロイ | 動いている状態 |
| DSPy だと | signature(意図を人が書く) | program.save() の JSON(instruction+選ばれた demos+設定) |
ランタイムにロードされ推論中 |
→ DSPy の artifact =「コンパイル済み program を固めた1個の JSON」。レジストリに版付きで積まれ、ランタイムがロードする。人が手で書き換える物ではなく compile が吐く物。だから「人は signature を所有、artifact は DSPy が所有」になる。
9. 核心の問い — 「DSPy が考えた良いプロンプトを、どう現場に伝えるか」¶
混乱の正体¶
「DSPy の良いプロンプトを現場に伝える」という絵そのものが半分間違い。DSPy の成果物は人が読んでコピペする1文ではない(instruction + few-shot demos の塊)。だから「伝える」には全く違う2つの意味がある。
| 「伝える」の意味 | 誰が実行する? | 現場に渡すもの |
|---|---|---|
| ① 実行に流す(人は経由しない) | システム(ランタイム) | 何も渡さない。出力が良くなるだけ |
| ② 人が読んで使う/編集する | 人(プロンプトを打つ) | artifact を人間可読に逆翻訳したテンプレ |
決定的な分岐:人は実行経路にいるか?¶
- Path A — システムが program を実行(現場はプロンプトに触れない)= DSPy の本来の姿。最適化済み artifact をツール/API の裏に置き、現場は UI 操作だけ。改善はサーバ側で artifact を差し替えるだけ。現場への伝達はゼロ(「最近精度上がったね」と感じるだけ)。配り方=レジストリにバージョン付き artifact、ランタイムが最新をロード。DSPy 成果物(長い few-shot・スコアが上がるだけの不自然な言い回し)は人のクリップボード向きでない=置き場所はランタイム。
- Path B — 人がプロンプトを打つ(プロンプトが UI)= artifact をそのまま渡せないので人間可読に逆翻訳が要る。やり方: (1) instruction だけ手渡す(demos はサーバ側)、(2) テンプレ/スニペットとしてツール登録(現場は“書く”でなく“選ぶ”)、(3) 穴埋め方式(最適化済み外枠+人は変数だけ)。
実務で効く分割:instruction は人へ、demos は機械へ¶
| 要素 | 人間可読性 | 配り先 |
|---|---|---|
| instruction(タスク指示文) | 読める・短い | 現場に見せる/手渡せる(=「新プロンプト」として伝えるのはこれ) |
| few-shot demos(最適化で選ばれた実例集) | 長い・冗長・人には不要 | ツールが裏で注入(人には見せない) |
→ 「伝えられるもの(instruction)」と「伝えなくていいもの(demos)」を分離するのが鍵。
10. 設計判断 — artifact A と B は「別個でいい」(1タスク1レジーム)¶
A/B を1つの artifact で両立させる必要はない。ユースケースごとに A か B を選び、それぞれ独立した artifact で end-to-end に作る。これは割り切りであり、利点がある。
別個にする利点:消費者に合わせて別々に最適化できる¶
| A 用 artifact(機械が食う) | B 用 artifact(人が読む) | |
|---|---|---|
| 最適化の狙い | スコア最大化一辺倒 | 可読性・編集しやすさ込み |
| demos | 長くて冗長でもOK | 少なめ or 人にも効く厳選版 |
| optimizer | bootstrap-fewshot 等(demos 厚め) | instruction 中心(MIPRO/COPRO の instruction 寄り) |
| 成果物の形 | JSON をランタイムが load | instruction を抜いたプロンプトカード |
→ 「機械向けの不格好な artifact を無理に人へ見せる」矛盾自体が消える。
別個でも“1個だけ”共有すべきもの¶
- gate の規律(metric で評価してから昇格) — A/B で metric の中身は違ってよいが、「直したものは必ず eval を通してから出す」作法だけは両方に効かせる。無いと B が「人が手で直してそのまま配る」に退化して drift する。
- 整理: artifact は別、gate は各自で持つ、でも“gate を通す”文化は共通。
境界条件(割り切りが崩れる時)¶
別個でいいのは 「1タスクは A か B のどちらか一方で提供する」限り。同じタスクを「ツールで使う人」と「自分で打つ人」の両方に出すと、同じ意図の artifact が2つでき片方だけ改善されて食い違う。今は「両立しなくていい=1タスク1レジーム」なのでこの問題は起きない。
到達点(この議論の結論)¶
ユースケースごとに A か B を選び、それぞれ独立した artifact+metric+gate で end-to-end に作る。共有するのは「gate を通してから配る」という作法だけ。
次に詰める候補¶
- B 用アダプタの具体化 — artifact(compile 結果)から「人が読むプロンプトカード」をどう生成するか(instruction 抽出の実装イメージ)。
- A 用配信の具体化 — ランタイムが版付き artifact をどう load/切替するか(registry → shadow/canary)。
作成: 2026-06-27 / 最終更新: 2026-06-27