05 ループを閉じる
人がレビューした 1 件の本番の失敗を、ゴールデンデータ、校正済みのビジネスポリシー evaluator、そして以後のトラフィックの保護へと変えます。
出発点
Module 04 は、正典となる Module 03 の chat_turn に
対する人による annotation の完了で終わりました。その修正済み SQL と由来のワークシートを
手元に用意しておいてください:
source=production-feedback
source_trace_id=<authoritative Chat trace ID>
failure_category=stale-business-policy
source_policy_version=policy-v1
annotation_id=<completed task ID when available>元の本番 trace は sql-execution-success=true と user-thumbs=false を持っています。
👎 がレビューに値する trace を見つけ、完了した annotation が診断と修正済みの正解データを
供給しました。
継続的な評価と改善のループ
このモジュールは、そのループの 1 周を閉じます:
- ユーザーフィードバックが現在のオンライン evaluator の盲点をあらわにする;
- 人が調査し、修正を承認する;
- そのレビュー済みインシデントがゴールデンデータセットを拡張する;
- ベースラインと候補のリリースを、同じ拡張済みデータセットの上で走らせる;
- 汎用の evaluator をオンラインで有効化する前にオフラインで校正する; そして
- 以後のトラフィックが evaluator の score とユーザーフィードバックの両方を集め続ける。
最後のステップが重要です。より良い evaluator をデプロイしても、ユーザーフィードバックは 終わりません。evaluator は、そのポリシーカタログとプロンプトに表現された次元しか測れません。 将来の 👎 は、別の欠けたポリシー、曖昧なリクエスト、あるいは別の失敗モードを明らかにし、 同じループをもう一度始めることができます。
ゴール
5 つのエビデンスゲートを通過します。昇格、ベースライン、候補、校正、そして有効化とリプレイです。 ポリシーのバージョンだけが意図した処理の変更になるよう、両方の experiment で Module 02 の勝者を 使ってください。
以下のコマンドはすべて ClickHouse_Demos/workshops/agent_arena から実行します:
cd ClickHouse_Demos/workshops/agent_arena
source .env
export WINNER_MODEL="${WINNER_MODEL:-qwen3.7-flash}"
export WINNER_PROMPT="${WINNER_PROMPT:-P2_fewshot}"
export WINNER_CONFIG_ID="${WINNER_CONFIG_ID:-qwen3.7-flash__P2_fewshot}"既定値は検証済みのワークショップ勝者です。教室が別の config_id を選んだ場合は、3 つの値すべてを
そのモデル/プロンプトに設定し、すべてのゲートを通じて変更しないでください。
エビデンスゲート 1 — レビュー済みインシデントを昇格する
ラボのルートに、次の 3 レコードを含む reviewed.json を作成します。昇格を実行する前に、
2 つのプレースホルダーの値をすべての箇所で置き換えてください。Langfuse が annotation タスクの ID を
見せていない場合は、プレースホルダーを残すのではなく annotation_id を 3 つのレコードすべてから
削除します。このフィールドは任意ですが、他の本番由来のフィールドは必須です。
[
{
"id": "prod-active-001",
"question": "How many active customers do we have?",
"golden_sql": "SELECT uniqExact(customer_id) FROM v_orders WHERE order_ts >= now() - INTERVAL 30 DAY AND status NOT IN ('cancelled', 'returned')",
"tier": 2,
"ordered": false,
"source": "production-feedback",
"source_trace_id": "<paste the Module 03 Chat trace ID>",
"failure_category": "stale-business-policy",
"source_policy_version": "policy-v1",
"annotation_id": "<paste the completed task ID>"
},
{
"id": "prod-active-002",
"question": "What is our active customer count right now?",
"golden_sql": "SELECT uniqExact(customer_id) FROM v_orders WHERE order_ts >= now() - INTERVAL 30 DAY AND status NOT IN ('cancelled', 'returned')",
"tier": 2,
"ordered": false,
"source": "production-feedback",
"source_trace_id": "<paste the Module 03 Chat trace ID>",
"failure_category": "stale-business-policy",
"source_policy_version": "policy-v1",
"annotation_id": "<paste the completed task ID>"
},
{
"id": "prod-active-003",
"question": "How many customers qualify as active under our business definition?",
"golden_sql": "SELECT uniqExact(customer_id) FROM v_orders WHERE order_ts >= now() - INTERVAL 30 DAY AND status NOT IN ('cancelled', 'returned')",
"tier": 2,
"ordered": false,
"source": "production-feedback",
"source_trace_id": "<paste the Module 03 Chat trace ID>",
"failure_category": "stale-business-policy",
"source_policy_version": "policy-v1",
"annotation_id": "<paste the completed task ID>"
}
]ユーザーフィードバックの trace にあった質問そのものは prod-active-001 だけです。
prod-active-002 と prod-active-003 は、調査したその同じインシデントから導かれた、
レビュアーが書いた言い換えです。監査可能性のために同じソース trace と完了した annotation を
使いますが、これらは本番フィードバックの trace が 2 件増えたということではありません。
3 つの入力はいずれも統制されたアクティブ顧客の指標を意図的に呼び出すので、ベースラインが
無関係なカウントを試して健全に見えることはありません。
レビュー済みのバッチを昇格します:
source .env
.venv/bin/python -m scripts.promote_to_golden reviewed.jsonprepared prod-active-* の 3 行のあとに、次が出ることを期待します:
promoted 3 question(s) into the 'arena-golden' datasetLangfuse → Datasets → arena-golden を開き、新しい各アイテムのメタデータを確認します。
source=production-feedback、同一の実際の source_trace_id、
failure_category=stale-business-policy、source_policy_version=policy-v1 を検証してください。
リポジトリのソースコーパスには YAML の質問が 20 個あります。q019 と q020 は
few-shot プロンプト用の holdout なので、クリーンな project の arena-golden は
Experiment item 18 個から始まります。この 3 アイテムを昇格すると、クリーンな dataset は
21 アイテムになります。再利用 project に別の承認済みアイテムがある場合は、件数を合わせるために
削除せず provenance を記録し、baseline と candidate が同じ item ID を使うことを要求してください。
reviewed.json は git 管理外の可変なオペレーター状態で、ワークショップの主要な経路です。
git 管理されている --synthetic-fixture は、再現可能な下稽古用のフォールバックにすぎません。
人による annotation を表すものではなく、このモジュールのエビデンスゲートを満たすことはできません。
2 つのモードは相互排他です。本物の昇格のあとに合成のフォールバックを実行してはいけません。
昇格は、ClickHouse への問い合わせやデータセットアイテムの書き込みの前に、バッチ全体、 読み取り専用の SQL、必須の由来を検証します。そのうえで既存のデータセットのメタデータを読み、 異なる本番由来を持つ ID の衝突は拒否します。その認証済みのプリフライトが由来を安全に確立 できない場合は、書き込みをせずに停止します。本物の昇格を繰り返すのが安全なのは、衝突する 本番由来が完全に同一である場合だけです。
エビデンスゲート 2 — policy-v1 のベースラインを実行する
まず、experiment 用にカタログ駆動の judge をプロビジョニングします。これはオンラインの observation ルールを無効の状態で作成します:
source .env
.venv/bin/python -m scripts.provision_online_evaluators \
--business-policy-experimentsexperiment rule enabled=True; online rule enabled=False が出ることを期待します。続ける前に、
Langfuse でオンラインのルールがまだ無効であることを確認してください。
このワークショップの試行に一意なサフィックスを与え、そのうえで選ばれたモデルとプロンプトを 古いポリシーで拡張済みデータセットに対して実行します:
export LOOP_RUN_SUFFIX="${LOOP_RUN_SUFFIX:-$(date +%Y%m%d-%H%M%S)}"
export BASELINE_RUN_ID="online-loop-baseline-${LOOP_RUN_SUFFIX}"
export CANDIDATE_RUN_ID="online-loop-candidate-${LOOP_RUN_SUFFIX}"
.venv/bin/python -m eval.harness --run-id "$BASELINE_RUN_ID" \
--policy-version policy-v1 --models "$WINNER_MODEL" --prompts "$WINNER_PROMPT" \
--wait-for-score business-policy-adherenceハーネスはリリース ID に --policy-v1 を付け足します。そして、すべての trace について
正確な 3 つの Experiment score 名を待ちます。correctness、agent-arena-llm-judge、
business-policy-adherence です。run がタイムアウトした場合、あるいはいずれかの score が
欠けている場合は先に進まないでください。
Langfuse の Experiments で、ベースラインのデータセットアイテム数と correctness の集計値を 記録します。昇格後のクリーンな project では 21 アイテムを期待します。再利用 project には承認済み アイテムがさらにある場合があり、プロバイダーの応答も変わり得ます。したがってリリースのゲートは 固定の集計 score ではなく、下の対比較です。
エビデンスゲート 3 — policy-v2 の候補を実行する
データセット、モデル、プロンプト、run のサフィックスを変えずに、候補を実行します:
.venv/bin/python -m eval.harness --run-id "$CANDIDATE_RUN_ID" \
--policy-version policy-v2 --models "$WINNER_MODEL" --prompts "$WINNER_PROMPT" \
--wait-for-score business-policy-adherencecandidate の実際の集計値を記録してから、Langfuse で 2 つの run を比較し、 次を必須としてください:
- データセットアイテムの ID とアイテム数が同一であること;
- 3 つの
prod-active-*アイテムがすべて、policy-v1でのcorrectness=0からpolicy-v2でのcorrectness=1へ移ること; prod-active-*より前から存在するすべてのアイテムがアイテム単位で比較され、correctness=1からcorrectness=0への退行がないこと; そして- 候補の correctness の集計値がベースラインの correctness を下回らないこと。
既存のアイテムが退行したら止めてください。既知の振る舞いを壊してインシデントを直した候補は、 リリースのゲートを通過していません。
エビデンスゲート 4 — 汎用のポリシー judge を 1 つ校正する
business-policy-adherence は「アクティブ顧客の evaluator」ではありません。これは質問、
生成された SQL、そして policy-v2 の指標カタログ全体を受け取ります。そして、どの統制された
指標が当てはまるかを判定し、PASS、FAIL、NOT_APPLICABLE のいずれかを返します。同じ設計で、
質問の言い回しごとに evaluator を作ることなく、アクティブ顧客、売上、コンバージョン、
粗利益を検査できます。
本番の observation に対して有効化する前に、次の Experiment アイテムを調べます:
| 校正プローブ | run/アイテム | 必要な business-policy-adherence |
|---|---|---|
| 古いアクティブ顧客の SQL | ベースラインの prod-active-001 | FAIL |
| 修正済みのアクティブ顧客の SQL | 候補の prod-active-001 | PASS |
| 売上のポリシー | 候補の q005 | PASS |
| 閲覧から購入へのコンバージョンのポリシー | 候補の q018 | PASS |
| 素の顧客数カウント | 候補の q001 | NOT_APPLICABLE |
アクティブ顧客の確認は prod-active-002 と prod-active-003 についても繰り返してください。
カテゴリだけでなく judge の理由付けも読みます。当てはまるカタログのポリシーを名指しし、
生成された SQL をそれに照らして評価しているべきです。素のカウントは NOT_APPLICABLE のまま
でなければなりません。judge があらゆるカウントの質問をアクティブ顧客のポリシーに押し込んでは
いないことを示すからです。
いずれかのカテゴリが間違っている場合、必要な score が欠けている場合、構造化出力が壊れている 場合、あるいは correctness の比較が退行した場合は、オンラインのルールを無効のままにしてください。 オフラインの experiment による校正を先に行うのは、evaluator が本番の監視に影響を与える前に、 既知の例に対する誤った合格と誤った不合格を調べられるからです。
エビデンスゲート 5 — policy-v2 で有効化してリプレイする
すべての校正ゲートを通過してから初めて、observation ルールを有効化します:
source .env
.venv/bin/python -m scripts.provision_online_evaluators \
--enable-business-policy-onlineルール名が正確に agent-arena-business-policy-online で、enabled=True になることを
期待します。このコマンドは、business-policy-adherence という名前のデータセットスコープの
Experiment score が見つからないときは失敗して閉じます。上のあなたによる手動の校正確認が、
引き続き品質のゲートです。
policy-v1 のサーバーを止めます。1 つ目のターミナルで候補を起動し、そのまま動かし続けます:
source .env
AGENT_ARENA_POLICY_VERSION=policy-v2 \
.venv/bin/uvicorn serving.api:app --port 81002 つ目のターミナルで、質問を受け取り、policy-v2 のレスポンスが成功したことを確認してから
その trace ID だけを返すヘルパーを定義します:
source .env
export WINNER_CONFIG_ID="${WINNER_CONFIG_ID:-qwen3.7-flash__P2_fewshot}"
ask_trace() {
local question="$1"
local body
body=$(.venv/bin/python -c \
'import json,sys; print(json.dumps({"question": sys.argv[1], "config_id": sys.argv[2]}))' \
"$question" "$WINNER_CONFIG_ID")
curl -fsS http://localhost:8100/ask \
-H 'content-type: application/json' -d "$body" | \
.venv/bin/python -c \
'import json,sys; data=json.load(sys.stdin); assert data["policy_version"] == "policy-v2" and data["outcome"] == "ok"; print(data["trace_id"])'
}アクティブ顧客と売上の質問を 1 回ずつ尋ねます。オンラインの observation の score は、Experiment の
score 名ではなくルール名 agent-arena-business-policy-online を使います:
ACTIVE_TRACE=$(ask_trace "How many active customers do we have?")
.venv/bin/python -m scripts.verify_online_scores "$ACTIVE_TRACE" \
sql-execution-success=true agent-arena-business-policy-online=PASS
REVENUE_TRACE=$(ask_trace "What was revenue in the last 30 days?")
.venv/bin/python -m scripts.verify_online_scores "$REVENUE_TRACE" \
sql-execution-success=true agent-arena-business-policy-online=PASSコンバージョンの質問には、検証済みの確率的な境界があります。1 回尋ねて、その trace を保存して
ください。serving の outcome が ok 以外の場合、あるいは必要な score が正確に揃わないか失敗した
場合は、同じ質問と構成で 最大 1 回だけ 再試行します。以下のブロックは両方の試行を可視に
保ちます:
ask_conversion() {
local body
body=$(.venv/bin/python -c \
'import json,sys; print(json.dumps({"question": sys.argv[1], "config_id": sys.argv[2]}))' \
"What is our view-to-purchase conversion rate for the last 7 days?" \
"$WINNER_CONFIG_ID")
curl -fsS http://localhost:8100/ask \
-H 'content-type: application/json' -d "$body" | \
.venv/bin/python -c \
'import json,sys; data=json.load(sys.stdin); assert data["policy_version"] == "policy-v2"; print("\t".join((data["trace_id"], data["outcome"])))'
}
IFS=$'\t' read -r CONVERSION_TRACE_1 CONVERSION_OUTCOME_1 <<< \
"$(ask_conversion)"
if .venv/bin/python -m scripts.verify_online_scores "$CONVERSION_TRACE_1" \
sql-execution-success=true agent-arena-business-policy-online=PASS; then
CONVERSION_SCORES_1=pass
else
CONVERSION_SCORES_1=fail
fi
if [ "$CONVERSION_OUTCOME_1" = ok ] && [ "$CONVERSION_SCORES_1" = pass ]; then
CONVERSION_RESULT_1=pass
else
CONVERSION_RESULT_1=fail
fi
printf 'conversion_attempt=1 trace_id=%s outcome=%s exact_scores=%s result=%s\n' \
"$CONVERSION_TRACE_1" "$CONVERSION_OUTCOME_1" \
"$CONVERSION_SCORES_1" "$CONVERSION_RESULT_1"
CONVERSION_TRACE_2=not-run
CONVERSION_OUTCOME_2=not-run
CONVERSION_SCORES_2=not-run
CONVERSION_RESULT_2=not-run
if [ "$CONVERSION_RESULT_1" != pass ]; then
IFS=$'\t' read -r CONVERSION_TRACE_2 CONVERSION_OUTCOME_2 <<< \
"$(ask_conversion)"
if .venv/bin/python -m scripts.verify_online_scores "$CONVERSION_TRACE_2" \
sql-execution-success=true agent-arena-business-policy-online=PASS; then
CONVERSION_SCORES_2=pass
else
CONVERSION_SCORES_2=fail
fi
if [ "$CONVERSION_OUTCOME_2" = ok ] && [ "$CONVERSION_SCORES_2" = pass ]; then
CONVERSION_RESULT_2=pass
else
CONVERSION_RESULT_2=fail
fi
fi
printf 'conversion_attempt=2 trace_id=%s outcome=%s exact_scores=%s result=%s\n' \
"$CONVERSION_TRACE_2" "$CONVERSION_OUTCOME_2" \
"$CONVERSION_SCORES_2" "$CONVERSION_RESULT_2"
if [ "$CONVERSION_RESULT_1" != pass ] && \
[ "$CONVERSION_RESULT_2" != pass ]; then
printf '%s\n' \
'STOP: conversion failed twice; preserve both traces and investigate.' >&2
false
fiグリーンになるまで再試行してはいけません。両方の試行が失敗した場合は、両方の trace を残し、 結果を可視に保ち、その新しいエビデンスを人による annotation、ゴールデンデータの改善、 そして同じ対になった校正ループへ回してください。
コンバージョンが通ってから初めて、素のカウントの質問を 1 回尋ねます:
PRODUCT_TRACE=$(ask_trace "How many products are there?")
.venv/bin/python -m scripts.verify_online_scores "$PRODUCT_TRACE" \
sql-execution-success=true agent-arena-business-policy-online=NOT_APPLICABLEオンラインの evaluator は非同期に動きます。検証ツールは既定で最大 180 秒ポーリングします。 まだ pending の score は、失敗した score と同じではありません。
ループを回し続ける
ロールアウト後も 👍/👎 を有効にしておいてください。
agent-arena-business-policy-online=PASS の隣に user-thumbs=false が並ぶような食い違いを
監視します。それらは次の annotation キューに入れる価値の高い候補です。ポリシー、プロンプト、
データ、それとも evaluator を直すのかを決めるのは人によるレビューです。承認されたケースは
arena-golden に戻り、次の候補が同じ ベースライン → 候補 → 校正 → ガード付きの有効化 という
順序を繰り返します。
ワークショップのルールは、すべての受講者がエビデンスを見られるように対象 trace の 100% を サンプリングします。これは教育のための設定であり、本番の既定値ではありません。実際の サンプリングは、トラフィック、evaluator のコスト、レイテンシ、リスク、そして必要なインシデントの カバレッジを反映すべきです。
完了のエビデンス
- 本番のルート trace が引き続き
sql-execution-success=trueと Boolean のuser-thumbs=falseを 示している。 production-investigation-<session>の人による annotation タスクが、検証済みの修正とapproved-for-golden=trueとともに完了している。- 3 つのゴールデンアイテムすべてが本物の
production-feedbackの由来を持ち、1 件のユーザーの 質問と、レビュアーが書いた 2 件の言い換えを区別できる。 - ベースラインと候補が同じ拡張済みデータセット、モデル、プロンプトを使い、候補が昇格した 3 アイテムすべてを直し、既存の correctness の退行を持ち込んでいない。
- Experiment の校正が、正確な score 名
business-policy-adherenceでFAIL、PASS、NOT_APPLICABLEを生んだ。 - observation ルールが校正中は無効で、ゲートを通過してから初めて有効化された。
- アクティブ顧客、売上、コンバージョンの trace が
agent-arena-business-policy-online=PASSを 持ち、素の製品数カウントがagent-arena-business-policy-online=NOT_APPLICABLEを持っている。 - デプロイ後もオンライン評価とユーザーフィードバックが互いを改善し続ける理由を説明できる。