Agent ArenaClickHouse Workshops

05 闭合改进循环

将一次经过审查的生产失败案例转化为黄金数据、一个经过校准的业务策略评估器,并保护未来的流量。

起点

模块 04 结束时,已针对权威的模块 03 chat_turn 完成了一次人工标注。请保留其修正后的 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;已完成的标注则给出了诊断结果和修正后的标准答案。

持续评估与改进循环

本模块闭合了这个循环中的一轮:

  1. 用户反馈暴露了当前在线评估器的一个盲点;
  2. 人工进行调查并批准一项修正;
  3. 该经过审查的事件扩充了黄金数据集;
  4. 基线版本和候选版本在同一个扩充后的数据集上运行;
  5. 一个通用评估器在启用为在线评估之前先经过离线校准;并且
  6. 未来的流量会持续收集评估器分数和用户反馈。

最后一步很关键:部署一个更好的评估器并不意味着用户反馈就此结束。评估器只能衡量 其策略目录和提示词中已体现的维度。未来的一次 👎 仍可能揭示另一个缺失的策略、 一个含糊的请求,或一种新的失败模式,并重新开启同一个循环。

目标

通过五个证据关卡:提升(promote)、基线(baseline)、候选(candidate)、校准 (calibrate),然后启用并重放(enable and replay)。两个实验都使用模块 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, 请将这三个值都设置为对应的模型/提示词,并在整个流程的每个关卡中保持不变。

证据关卡 1 —— 提升经审查的事件

在实验根目录下创建 reviewed.json,包含以下三条记录。在运行提升操作之前,请将 所有出现的两个占位值替换掉。如果 Langfuse 没有暴露标注任务 ID,请从这三条记录中 移除 annotation_id 字段,而不要留一个占位值;该字段是可选的,而其他生产溯源 字段是必需的。

[
  {
    "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>"
  }
]

只有 prod-active-001 是来自用户反馈 trace 的原始问题。prod-active-002 和 prod-active-003 是审查者根据同一个已调查事件撰写的改写版本。为了便于审计,它们 使用相同的来源 trace 和相同的已完成标注;它们并不是另外两条独立的生产反馈 trace。这三条输入都特意调用了受治理的活跃客户指标,这样基线版本就无法通过测试 不相关的计数而看起来一切正常。

提升这批经审查的记录:

source .env
.venv/bin/python -m scripts.promote_to_golden reviewed.json

预期会看到三行 prepared prod-active-*,随后是:

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

打开 Langfuse → Datasets → arena-golden,检查每个新条目的元数据。确认 source=production-feedback、相同的真实 source_trace_id、 failure_category=stale-business-policy 以及 source_policy_version=policy-v1。

仓库的源语料共有 20 个 YAML 问题。q019 与 q020 是 few-shot 保留样例, 所以干净项目中的 arena-golden 从 18 个 Experiment 条目开始;提升这三条记录后, 应当有 21 个条目。如果你复用了已有项目,条目数可能更高:保留并核对溯源元数据, 不要为了凑数而删除旧条目。基线与候选实验必须使用完全相同的条目 ID。

reviewed.json 是被忽略的可变操作者状态,是工作坊的主要路径。已纳入版本控制的 --synthetic-fixture 只是一个可复现的排练后备方案。它不代表人工标注,也不能 满足本模块的证据关卡要求。这两种模式是互斥的;在完成真实提升之后,绝不要再运行 合成后备方案。

提升操作会在查询 ClickHouse 或写入数据集条目之前,先验证整批数据的完整性、只读 SQL,以及必需的溯源信息。随后它会读取已有的数据集元数据,并拒绝在生产溯源不同的 情况下写入一个发生 ID 冲突的条目。如果这个经过身份验证的预检无法安全地确认 溯源,它会停止并不进行写入。只有当发生冲突的生产溯源完全一致时,重复执行一次 真实的提升操作才是安全的。

证据关卡 2 —— 运行 policy-v1 基线

首先为实验配置目录驱动的评判器。这会创建其在线观测规则,并使其处于禁用状态:

source .env
.venv/bin/python -m scripts.provision_online_evaluators \
  --business-policy-experiments

预期看到 experiment 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 上出现三个确切 的 Experiment 分数名称:correctness、agent-arena-llm-judge 和 business-policy-adherence。如果运行超时或任何分数缺失,不要继续。

在 Langfuse Experiments 中,记录基线版本的数据集条目数量和汇总正确率。干净项目 提升后应有 21 个条目;复用项目可能更多。不同的模型服务商响应可能有所不同,因此 发布关卡依据的是下面的配对比较,而不是要求一个固定的正确数。

证据关卡 3 —— 运行 policy-v2 候选版本

在不改变数据集、模型、提示词或运行后缀的情况下,运行候选版本:

.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

在 Langfuse 中比较这两次运行,并要求满足:

  • 数据集条目 ID 和条目数量完全一致;
  • 全部三个 prod-active-* 条目在 policy-v1 下的 correctness=0 转变为 policy-v2 下的 correctness=1;
  • 每一个早于 prod-active-* 存在的条目都逐项比较,不出现从 correctness=1 退化为 correctness=0 的情况;并且
  • 候选版本的汇总正确率不低于基线版本的正确率。

如果任何一个既有条目发生退化,就停止。一个通过破坏已知行为来修复该事件的候选 版本,并没有通过发布关卡。

证据关卡 4 —— 校准一个通用策略评判器

business-policy-adherence 并不是一个“活跃客户评估器”。它接收问题、生成的 SQL 以及完整的 policy-v2 指标目录。它会判断适用哪个受治理的指标,并返回 PASS、 FAIL 或 NOT_APPLICABLE。同一套设计可以检查活跃客户、营收、转化率和毛利率, 而不需要为每一种问题表述都创建一个独立的评估器。

在为生产观测启用它之前,请检查以下 Experiment 条目:

校准探针运行/条目要求的 business-policy-adherence
陈旧的活跃客户 SQL基线版本 prod-active-001FAIL
修正后的活跃客户 SQL候选版本 prod-active-001PASS
营收策略候选版本 q005PASS
浏览到购买转化率策略候选版本 q018PASS
普通客户计数候选版本 q001NOT_APPLICABLE

对 prod-active-002 和 prod-active-003 重复相同的活跃客户检查。除了分类结果 之外,也要阅读评判器的推理过程:它应当指出适用的目录策略,并据此评估生成的 SQL。 一个普通计数问题必须保持 NOT_APPLICABLE,这说明该评判器不会把每一个计数问题都 强行归入活跃客户策略。

如果任何分类结果有误、任何必需的分数缺失、结构化输出格式不正确,或者正确率比较 出现退化,就保持在线规则处于禁用状态。离线的 Experiment 校准要先行,因为它能让 你在评估器影响生产监控之前,针对已知样例检查误通过和误失败的情况。

证据关卡 5 —— 启用并在 policy-v2 上重放

只有在所有校准关卡都通过之后,才启用观测规则:

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 分数时,会以失败关闭(fail closed); 你上面所做的人工校准检查依然是质量关卡。

停止 policy-v1 服务器。在第一个终端中,启动候选版本并让它保持运行:

source .env
AGENT_ARENA_POLICY_VERSION=policy-v2 \
  .venv/bin/uvicorn serving.api:app --port 8100

在第二个终端中,定义一个辅助函数,它接受一个问题,并在确认收到成功的 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"])'
}

将活跃客户问题和营收问题各问一次。在线观测分数使用的是规则名称 agent-arena-business-policy-online,而不是 Experiment 分数名称:

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

转化率问题存在一个经过验证的随机边界情况。将其问一次并保留该 trace。如果服务 返回的 outcome 不是 ok,或者其必需的确切分数缺失或失败,则以相同的问题和配置 最多重试一次。以下代码块会让两次尝试都保持可见:

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,让结果保持可见,并将 这项新证据纳入人工标注、黄金数据改进,以及同一套配对校准循环。

只有在转化率检查通过之后,才将普通计数问题问一次:

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

在线评估器是异步运行的。校验器默认最多轮询 180 秒;分数仍处于待定状态并不等同于 一个失败的分数。

让循环持续运转

上线之后继续保留 👍/👎。留意诸如 agent-arena-business-policy-online=PASS 与 user-thumbs=false 并存这样的 分歧情况:它们是下一轮标注队列中的高价值候选项。人工审查将决定要修正的是策略、 提示词、数据,还是评估器本身。被批准的案例会回流到 arena-golden,然后下一个 候选版本重复同样的基线 → 候选 → 校准 → 受控启用流程。

工作坊的规则对所有符合条件的 trace 采样 100%,以便每个学员都能看到证据。这是一 个教学设置,而不是生产环境的默认做法。真实的采样比例应当反映流量、评估器成本、 延迟、风险,以及你所需要覆盖的事件类型。

完成证据

  • 生产根 trace 仍显示 sql-execution-success=true 以及布尔值 user-thumbs=false。
  • production-investigation-<session> 人工标注任务已完成,带有一个经过验证的 修正结果和 approved-for-golden=true。
  • 全部三个黄金条目都存在,并带有真实的 production-feedback 溯源信息;你能够 区分出那一个用户提出的问题和另外两个审查者撰写的改写版本。
  • 基线版本和候选版本使用了同一个扩充后的数据集、同一个模型和同一套提示词;候选 版本修复了全部三个已提升的条目,且没有引入任何既有正确率的退化。
  • Experiment 校准产生了 FAIL、PASS 和 NOT_APPLICABLE,分数名称确切为 business-policy-adherence。
  • 观测规则在校准期间保持禁用,只有在所有关卡通过之后才被启用。
  • 活跃客户、营收和转化率的 trace 都得到了 agent-arena-business-policy-online=PASS;普通商品计数得到了 agent-arena-business-policy-online=NOT_APPLICABLE。
  • 你能够解释为什么在部署之后,在线评估和用户反馈仍会持续地相互改进。

本页内容

Track your progress?

Optional. We email a link to confirm your address; progress records once you open it.

Please use your work email address, not a personal one.

Progress tracking also requires accepting the current Terms of Service in Privacy settings.

ZH