03 发布并发现问题
发布带有已知策略盲点的选定代理,然后使用运营评估和真实用户反馈来发现该问题。
起点
模块 02 已完成。记录从实际 Arena 运行中选出的获胜 config_id,然后从实验室根目录
(ClickHouse_Demos/workshops/agent_arena)导出它:
source .env
export WINNER_CONFIG_ID="${WINNER_CONFIG_ID:-qwen3.7-flash__P2_fewshot}"已验证的工坊运行选出了 qwen3.7-flash__P2_fewshot;如果你所在场次的获胜者不同,请
保留你自己的结果。你将使用你测量过的同一个模型和提示词——不是一个专门制造失败的
特殊代理。
如果你的获胜者是 Qwen,则必须在 OpenRouter 中禁用 Settings → Privacy → Data Policies → Zero Data Retention → Non-frontier。当强制执行 non-frontier ZDR 时, 可用的 Alibaba 路由会被拒绝。在为真实数据更改此设置之前,请先审查你的隐私要求。
为什么一个通过的评估器仍会错过用户价值
在线评估器只衡量它被设计用来衡量的那个维度。这里,sql-execution-success 回答的
是一个重要的运营问题:代理生成的 SQL 是否能被 ClickHouse 执行?它并不知道这段 SQL
是否遵循了当前业务定义的活跃客户。
这就造成了一个真实存在的监控盲区:
| 信号 | 它回答的问题 | 在此事件中的期望值 |
|---|---|---|
sql-execution-success | 生成的 SQL 是否成功执行? | true |
user-thumbs | 这个答案是否满足了该用户的需求? | false |
运营评估能捕获损坏的 SQL、超时和执行错误。语义或用户评估问的是:一个可执行的答案 是否有用,是否符合业务含义。两者互不替代。点差评是一个优先级信号,不是最终结论; 人类会在模块 04 中对它展开调查。
目标
创建一条真实的 chat_turn 追踪记录,在其中运营评估器通过,但用户对答案给出了差评。
记录这条追踪记录以及两个相互矛盾的计数结果,供人工调查使用。
步骤 1 — 证明种子事件可复现
在启动演示服务器之前,从实验室根目录运行预检:
source .env
export WINNER_CONFIG_ID="${WINNER_CONFIG_ID:-qwen3.7-flash__P2_fewshot}"
.venv/bin/python -m schema.gen_schema_context
.venv/bin/python -m scripts.check_online_eval_scenario \
--config-id "$WINNER_CONFIG_ID"该命令会执行两种定义,并向所选配置提出三个不同的措辞变体。它必须打印出不同的
stale_count 和 current_count 值、三行 classification_N=policy-v1,以及:
如果某个措辞变体返回 ok/unknown,预检只会使用相同的措辞和配置重试一次。
它不会重试 policy-v2,也不会重试提供商、模型或 Agent 故障;若第二次结果仍为
ok/unknown,流程依然会被阻断。
OK: seeded online-evaluation incident is reproducible如果两个计数相等,或任意一次分类结果不是 policy-v1,请停止:在当前这份数据/模型
运行中,这种对比不会显现出来。
当前的业务定义正是这条准确的 SQL:
SELECT uniqExact(customer_id) FROM v_orders
WHERE order_ts >= now() - INTERVAL 30 DAY
AND status NOT IN ('cancelled', 'returned')而旧的 policy-v1 定义则统计的是过去 90 天内注册的客户。因此,模型在本模块中生成
的 SQL,相对于明确的 policy-v1 指令而言是有效的。重点不在于模型不够聪明;而是
已部署的策略上下文过时了,而 SQL 执行评估器又过于狭窄,无法察觉这一点。
步骤 2 — 配置运营评估器
配置是幂等的,因此重新运行是安全的:
source .env
.venv/bin/python -m scripts.provision_online_evaluators --operational预期输出会指明评估器 sql-execution-success 以及已启用的规则
agent-arena-sql-execution-online。
步骤 3 — 启动种子过时发布版本
在第一个终端中,从实验室根目录出发,用明确选定的旧策略启动服务并让它保持运行:
source .env
AGENT_ARENA_POLICY_VERSION=policy-v1 \
.venv/bin/uvicorn serving.api:app --port 8100不要省略 AGENT_ARENA_POLICY_VERSION。否则该服务会默认使用当前的 policy-v2,
它会正确地排除已取消和已退货的订单。
步骤 4 — 通过 Chat 提问并评分
在另一个终端中,如果控制面板尚未运行,启动它:
scripts/arena.sh serve打开 http://localhost:5174,选择 Chat 标签页和 $WINNER_CONFIG_ID,然后提问:
How many active customers do we have?阅读生成的 SQL 和结果。它应该遵循来自 policy-v1 的种子 90 天注册定义。点击这个
答案上的 👎,并等待界面显示 feedback sent。
这条 Chat 根追踪记录就是你将要打分并交给模块 04 的唯一权威事件。
步骤 5 — 找到并验证 Chat 追踪记录
在 Langfuse 中打开 Tracing,筛选 user-thumbs = false。打开最新的一条根
chat_turn,其问题为 How many active customers do we have?,配置匹配
$WINNER_CONFIG_ID,且元数据显示 policyversion=policy-v1。复制它的追踪 ID 和
追踪 URL,然后在本地设置该 ID:
export CHAT_TRACE_ID="<paste the Chat trace ID>"
.venv/bin/python -m scripts.verify_online_scores "$CHAT_TRACE_ID" \
sql-execution-success=true user-thumbs=false验证 user-thumbs 是一个布尔值 false,而不是数值或文本分数。服务端源代码将该
元数据字段称为 policy_version;OpenTelemetry 适配器会将其净化为发出的 Langfuse
键 policyversion。
步骤 6 — 用未评分的 curl 复现问题
这次原始 API 调用是一项强制的命令级复现与诊断步骤。它会产生一条独立的追踪记录, 但它不是反馈事件本身,也绝不能被评分:
source .env
export WINNER_CONFIG_ID="${WINNER_CONFIG_ID:-qwen3.7-flash__P2_fewshot}"
ASK_BODY=$(.venv/bin/python -c \
'import json,sys; print(json.dumps({"question": sys.argv[1], "config_id": sys.argv[2]}))' \
"How many active customers do we have?" "$WINNER_CONFIG_ID")
CURL_RESPONSE=$(curl -fsS http://localhost:8100/ask \
-H 'content-type: application/json' -d "$ASK_BODY")
printf '%s\n' "$CURL_RESPONSE" | .venv/bin/python -m json.tool
CURL_TRACE_ID=$(printf '%s\n' "$CURL_RESPONSE" | .venv/bin/python -c \
'import json,sys; data=json.load(sys.stdin); assert data.get("policy_version") == "policy-v1"; assert data.get("outcome") == "ok"; trace_id=data.get("trace_id"); assert isinstance(trace_id, str) and trace_id; print(trace_id)')响应必须包含 outcome: "ok"、一个非空的 trace_id,以及
policy_version: "policy-v1"。
等待异步评估器完成,然后只验证它的运营分数:
.venv/bin/python -m scripts.verify_online_scores "$CURL_TRACE_ID" \
sql-execution-success=true不要为 CURL_TRACE_ID 调用 /feedback,也不要把它放进工作表。它只是一个可复现
的 API 诊断记录;CHAT_TRACE_ID 才始终是交接事件。
步骤 7 — 与当前策略进行比较
通过与代理相同的只读 ClickHouse 客户端执行当前策略的 SQL,然后将它的单一结果与
CURL_RESPONSE 中过时的结果进行比较:
.venv/bin/python - <<'PY'
from arena.config import load_config
from agents.chclient import ROClickHouseClient
sql = """SELECT uniqExact(customer_id) FROM v_orders
WHERE order_ts >= now() - INTERVAL 30 DAY
AND status NOT IN ('cancelled', 'returned')"""
result = ROClickHouseClient(load_config().clickhouse).query(sql)
print(result.rows[0][0])
PY这正是你刚刚执行的确切查询:
SELECT uniqExact(customer_id) FROM v_orders
WHERE order_ts >= now() - INTERVAL 30 DAY
AND status NOT IN ('cancelled', 'returned')不同的计数结果就是用户可见的失败之处。SQL 成功运行了;它回答的却是错误的业务 定义。将这个计数记录在权威 Chat 追踪证据旁边。
调查工作表
保留这份交接材料供模块 04 使用:
| 证据 | 你的值 |
|---|---|
获胜者 config_id | |
| 权威 Chat 追踪 ID | |
| 权威 Chat 追踪 URL | |
过时的 policy-v1 计数 | |
当前的 policy-v2 计数 | |
sql-execution-success | true |
user-thumbs | false |
如何验证你已完成
- 预检显示了不同的过时/当前计数,且全部三个种子问题都被分类为
policy-v1。 - 服务以
AGENT_ARENA_POLICY_VERSION=policy-v1运行,且/ask返回了outcome: "ok"。 - 权威 Chat 追踪记录在 Langfuse 中显示
sql-execution-success=true且user-thumbs=false。 - 强制的 curl 诊断返回了
outcome: "ok",产生了一个独立的CURL_TRACE_ID,且 未被评分或交接。 - 你在未泄露凭据的情况下记录了权威 Chat 追踪 ID/URL 以及两个计数。
- 你能解释为什么一次成功的 SQL 执行并不能证明语义正确性。
继续前往 模块 04 — 调查,将这个信号 转化为一次人工评审的诊断。