Agent ArenaClickHouse Workshops

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 :

  1. le feedback révèle un angle mort de l'evaluator en ligne ;
  2. un humain enquête et approuve une correction ;
  3. l'incident revu enrichit le dataset doré ;
  4. référence et candidat s'exécutent sur le même dataset enrichi ;
  5. un evaluator général est calibré hors ligne avant son activation en ligne ; et
  6. 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.json

Attendez trois lignes prepared prod-active-*, puis :

promoted 3 question(s) into the 'arena-golden' dataset

Ouvrez 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-experiments

Attendez 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-adherence

Le 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-adherence

Notez l'agrégat du candidat, comparez les expériences et exigez :

  • mêmes ID et nombres d'éléments ;
  • les trois prod-active-* passent de correctness=0 sous policy-v1 à correctness=1 sous policy-v2 ;
  • tous les éléments antérieurs à prod-active-* sont comparés individuellement sans régression correctness=1 vers correctness=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 :

SondeExécution/élémentbusiness-policy-adherence requis
SQL obsolète des clients actifsréférence prod-active-001FAIL
SQL corrigé des clients actifscandidat prod-active-001PASS
politique de chiffre d'affairescandidat q005PASS
politique de conversion vue-achatcandidat q018PASS
simple décompte de clientscandidat q001NOT_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-online

Attendez 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 8100

Dans 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=PASS

La 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
fi

Ne 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_APPLICABLE

L'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=true et le Boolean user-thumbs=false.
  • La tâche humaine production-investigation-<session> est terminée avec correction vérifiée et approved-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, PASS et NOT_APPLICABLE sous le nom exact business-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 a agent-arena-business-policy-online=NOT_APPLICABLE.
  • Vous pouvez expliquer pourquoi évaluation en ligne et feedback continuent de s'améliorer mutuellement après publication.

Sur cette page

Suivre votre progression ?

Facultatif. Nous envoyons un lien par e-mail pour confirmer votre adresse ; la progression est enregistrée après son ouverture.

Utilisez votre adresse e-mail professionnelle, et non une adresse personnelle.

Le suivi de la progression exige aussi d’accepter les Conditions d’utilisation actuelles dans les Paramètres de confidentialité.

FR