05 Boucler la boucle
Transformez un échec de production revu en données dorées, evaluator calibré de politique métier et protection du trafic futur.
Point de départ
Le Module 04 s'est achevé par une annotation humaine terminée pour le chat_turn de référence du Module 03. Gardez le SQL corrigé et la feuille de provenance :
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>La trace de production originale a sql-execution-success=true et user-thumbs=false. Le 👎 a repéré une trace à examiner ; l'annotation terminée a fourni le diagnostic et la vérité corrigée.
Boucle continue d'évaluation et d'amélioration
Ce module clôt un tour de la boucle :
- le feedback révèle un angle mort de l'evaluator en ligne ;
- un humain enquête et approuve une correction ;
- l'incident revu enrichit le dataset doré ;
- référence et candidat s'exécutent sur le même dataset enrichi ;
- un evaluator général est calibré hors ligne avant son activation en ligne ; et
- le trafic futur continue de recueillir scores et feedback utilisateur.
La dernière étape compte : un meilleur evaluator ne met pas fin au feedback. Il ne mesure que les dimensions représentées dans son catalogue et son prompt. Un prochain 👎 peut révéler une politique absente, une demande ambiguë ou un nouveau mode d'échec et relancer la boucle.
Objectif
Franchir cinq portes de preuves : promotion, référence, candidat, calibration, puis activation et replay. Utilisez le gagnant du Module 02 dans les deux expériences afin que la version de politique soit le seul changement prévu.
Exécutez toutes les commandes depuis 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}"Les valeurs par défaut correspondent au gagnant vérifié. Si votre salle a choisi un autre config_id, définissez les trois valeurs selon ce modèle et prompt et ne les changez plus.
Porte de preuves 1 — Promouvoir l'incident revu
Créez reviewed.json à la racine avec les trois enregistrements suivants. Remplacez partout les deux placeholders avant la promotion. Si Langfuse n'expose aucun ID de tâche, retirez annotation_id des trois objets au lieu de conserver un placeholder ; ce champ est facultatif, les autres champs de provenance sont obligatoires.
[
{
"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>"
}
]Seul prod-active-001 est la question exacte de la trace. prod-active-002 et prod-active-003 sont des paraphrases du relecteur issues du même incident. Elles utilisent la même trace et annotation pour l'auditabilité ; ce ne sont pas deux feedback de production supplémentaires. Les trois invoquent volontairement la métrique gouvernée des clients actifs, afin que la référence ne paraisse pas saine en testant des décomptes sans rapport.
Promouvez le lot :
source .env
.venv/bin/python -m scripts.promote_to_golden reviewed.jsonAttendez trois lignes prepared prod-active-*, puis :
promoted 3 question(s) into the 'arena-golden' datasetOuvrez Langfuse → Datasets → arena-golden et inspectez les métadonnées. Vérifiez source=production-feedback, le même source_trace_id réel, failure_category=stale-business-policy et source_policy_version=policy-v1.
Le corpus contient 20 questions YAML, mais q019 et q020 sont des exemples few-shot réservés. Un projet propre commence avec 18 éléments d'expérience dans arena-golden et atteint 21 après la promotion. Un projet réutilisé peut en contenir davantage ; consignez leur provenance plutôt que de les supprimer pour forcer un nombre, et exigez les mêmes ID pour référence et candidat.
reviewed.json est l'état opérateur mutable et ignoré, la voie principale de l'atelier. Le --synthetic-fixture suivi n'est qu'un repli reproductible de répétition. Il ne représente pas une annotation humaine et ne franchit pas cette porte. Les deux modes s'excluent ; n'exécutez jamais le repli synthétique après une vraie promotion.
La promotion valide tout le lot, le SQL en lecture seule et la provenance avant d'interroger ClickHouse ou d'écrire. Elle lit ensuite les métadonnées existantes et refuse un ID en collision avec une provenance différente. Si ce preflight authentifié ne peut établir la provenance, il s'arrête sans écriture. Une répétition n'est sûre que si la provenance en collision est identique.
Porte de preuves 2 — Exécuter la référence policy-v1
Provisionnez d'abord le juge piloté par catalogue pour les expériences. Sa règle d'observation en ligne est créée désactivée :
source .env
.venv/bin/python -m scripts.provision_online_evaluators \
--business-policy-experimentsAttendez experiment rule enabled=True; online rule enabled=False, puis confirmez dans Langfuse que la règle en ligne reste désactivée.
Attribuez un suffixe unique et exécutez le modèle et le prompt sur le dataset enrichi avec la politique obsolète :
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-adherenceLe harness ajoute --policy-v1 à l'ID de publication. Il attend trois scores exacts sur chaque trace : correctness, agent-arena-llm-judge et business-policy-adherence. Ne continuez pas en cas de timeout ou de score absent.
Dans Experiments, notez le nombre d'éléments et la correction agrégée. Attendez 21 éléments dans un projet propre. Les projets réutilisés et les réponses fournisseur peuvent varier ; la porte est la comparaison appariée, pas un agrégat codé en dur.
Porte de preuves 3 — Exécuter le candidat policy-v2
Sans changer dataset, modèle, prompt ou suffixe :
.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-adherenceNotez l'agrégat du candidat, comparez les expériences et exigez :
- mêmes ID et nombres d'éléments ;
- les trois
prod-active-*passent decorrectness=0souspolicy-v1àcorrectness=1souspolicy-v2; - tous les éléments antérieurs à
prod-active-*sont comparés individuellement sans régressioncorrectness=1verscorrectness=0; et - la correction agrégée du candidat n'est pas inférieure.
Arrêtez si un élément existant régresse. Corriger l'incident en cassant un comportement connu ne franchit pas la porte de publication.
Porte de preuves 4 — Calibrer un juge général de politique
business-policy-adherence n'est pas un « evaluator de clients actifs ». Il reçoit la question, le SQL et le catalogue complet policy-v2, détermine la métrique applicable et renvoie PASS, FAIL ou NOT_APPLICABLE. Le même design couvre clients actifs, chiffre d'affaires, conversion et marge brute sans evaluator par formulation.
Avant l'activation en production, inspectez :
| Sonde | Exécution/élément | business-policy-adherence requis |
|---|---|---|
| SQL obsolète des clients actifs | référence prod-active-001 | FAIL |
| SQL corrigé des clients actifs | candidat prod-active-001 | PASS |
| politique de chiffre d'affaires | candidat q005 | PASS |
| politique de conversion vue-achat | candidat q018 | PASS |
| simple décompte de clients | candidat q001 | NOT_APPLICABLE |
Répétez pour prod-active-002 et prod-active-003. Lisez le raisonnement : il doit nommer la politique applicable et comparer le SQL. Le simple décompte reste NOT_APPLICABLE, preuve que le juge ne force pas toute question de comptage dans la politique des clients actifs.
Gardez la règle en ligne désactivée si une catégorie est fausse, un score manque, la sortie structurée est mal formée ou la correction régresse. La calibration hors ligne permet d'inspecter faux succès et faux échecs avant que l'evaluator n'affecte la surveillance.
Porte de preuves 5 — Activer et rejouer sur policy-v2
Après toutes les portes seulement, activez la règle d'observation :
source .env
.venv/bin/python -m scripts.provision_online_evaluators \
--enable-business-policy-onlineAttendez le nom exact agent-arena-business-policy-online avec enabled=True. La commande échoue de façon fermée sans score d'expérience business-policy-adherence limité au dataset ; vos contrôles manuels restent la porte qualité.
Arrêtez le serveur policy-v1. Dans le premier terminal, démarrez le candidat :
source .env
AGENT_ARENA_POLICY_VERSION=policy-v2 \
.venv/bin/uvicorn serving.api:app --port 8100Dans le second, définissez un utilitaire qui renvoie l'ID seulement après une réponse policy-v2 réussie :
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"])'
}Posez une fois les questions clients actifs et chiffre d'affaires. Les scores d'observation utilisent agent-arena-business-policy-online, pas le nom du score d'expérience :
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=PASSLa conversion présente une limite stochastique vérifiée. Posez-la une fois et conservez la trace. Si le résultat n'est pas ok, ou si les scores manquent ou échouent, réessayez la même question et configuration au maximum une fois. Ce bloc garde les deux essais visibles :
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
fiNe bouclez pas jusqu'au vert. Si les deux essais échouent, conservez les traces et faites passer ces nouvelles preuves par l'annotation humaine, l'amélioration des données et la même calibration appariée.
Après réussite seulement, posez une fois le simple décompte :
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_APPLICABLEL'evaluator s'exécute de façon asynchrone. Le vérificateur attend jusqu'à 180 secondes ; un score en attente n'est pas un échec.
Maintenir la boucle
Laissez 👍/👎 activé. Surveillez les désaccords comme agent-arena-business-policy-online=PASS avec user-thumbs=false : ils sont de bons candidats à la prochaine file. La revue humaine décide s'il faut corriger politique, prompts, données ou evaluator. Les cas approuvés reviennent dans arena-golden, puis le candidat suivant répète référence → candidat → calibration → activation protégée.
Les règles de l'atelier échantillonnent 100 % des traces éligibles pour rendre les preuves visibles. Ce n'est pas un défaut de production. Le véritable échantillonnage dépend du trafic, du coût et de la latence, du risque et de la couverture nécessaire.
Preuves de finalisation
- La racine de production affiche toujours
sql-execution-success=trueet le Booleanuser-thumbs=false. - La tâche humaine
production-investigation-<session>est terminée avec correction vérifiée etapproved-for-golden=true. - Les trois éléments dorés ont une provenance authentique
production-feedback, et vous distinguez la question utilisateur des deux paraphrases du relecteur. - Référence et candidat ont utilisé les mêmes dataset, modèle et prompt ; le candidat a corrigé les trois éléments sans régression existante.
- La calibration a produit
FAIL,PASSetNOT_APPLICABLEsous le nom exactbusiness-policy-adherence. - La règle d'observation était désactivée pendant la calibration, puis activée seulement après les portes.
- Les traces clients actifs, chiffre d'affaires et conversion ont
agent-arena-business-policy-online=PASS; le décompte produits aagent-arena-business-policy-online=NOT_APPLICABLE. - Vous pouvez expliquer pourquoi évaluation en ligne et feedback continuent de s'améliorer mutuellement après publication.