第 8 章 可观测性与评测¶
一个 harness 改一行提示、换一个模型、调一次截断长度,行为都可能变。没有轨迹,变化只能靠体感;没有任务集,改进只能靠印象。本章把"看见一次运行"与"判断一次改动"接起来:先让每一步可归因,再让每次改动可判分。
1. 一次运行是一棵树¶
一次运行(run)是一棵调用树的根,子节点包括模型调用、工具调用、子智能体运行、压缩事件、权限判定。每个节点带父子关系与时间区间,任何一步都能顺着树找到它的输入、输出与决策依据。
树靠标识符连起来:run_id 在一轮对话内不变,span_id 每个节点唯一,子节点携带父 span_id。跨进程边界(子智能体、工具服务、MCP 服务端)要把这两个标识随请求传递,否则跨服务的节点会变成漂浮的孤点。节点以开始与结束两条事件成对记录,只有开始事件而未见结束,本身就是“进程被杀或超时未收尾”的证据。
判据:任一步都能从轨迹复现其输入。复现不了,说明关键输入没有记录(例如压缩掉了哪几轮、权限规则命中了哪一条);这种缺失在排障时会被误读成"模型抽风"。
2. 事件模型与最小字段¶
最小字段集足以支撑归因与会计:运行标识、节点标识与父标识、类型、起止时间、状态、模型或工具名、输入输出 token、成本、错误类别、版本三元组(harness、协议、模型)。敏感内容不落原文,记摘要与引用。
def emit(run_id, span_id, parent, kind, **fields):
event = {
"run": run_id, "span": span_id, "parent": parent, "kind": kind,
"ts": time.time(), **fields,
}
LOG.write(json.dumps(event, ensure_ascii=False) + "\n")
字段宁少勿滥,但版本三元组不能省:同一个失败在不同 harness 版本、协议版本或模型上含义不同,缺一项就无法判断是回归还是环境变化。
事件按行写(JSON Lines)、只追加不修改,一行一个事件;头部带 event_version,字段增删走版本号,不在旧事件上猜字段。输入输出只存摘要与内容引用(哈希或存储路径),原文另存并单独脱敏——轨迹会被检索、导出、上传到看板,把凭据留在轨迹里等同于泄露。高基数维度(完整 URL、文件路径、用户输入)进标签前先聚成模板(/repos/{id}/issues/{n}),否则看板会被无限维度拖垮。
三种工具链载体。第 2 节的事件模型落到实现有三块:追踪用 OpenTelemetry SDK 打 span(TracerProvider + OTLP exporter → Jaeger 看树),字段按第 2 节的最小集;指标用 prometheus_client 暴露计数/直方图 → Grafana 看板(成本、p99、失败率);调用级追踪可用 LangSmith 一类 SaaS(字段与第 2 节同构,省自建但数据出域)。选型按团队已有基础设施定,不追求一套通吃;三者都只承载事件模型,判断口径仍按第 3、5 节。
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
trace.set_tracer_provider(TracerProvider())
tracer = trace.get_tracer("agent")
with tracer.start_as_current_span("model_call") as span:
span.set_attribute("genai.operation", "chat")
3. 成本、步数与时延的会计¶
成本按调用累加:输入 token、输出 token、缓存命中部分分别计;工具调用的成本是它自身消耗的计算与外部费用,不与模型 token 混算。步数按"模型调用次数"与"工具调用次数"分开记,两者增长方式不同。
时延要拆成三段:排队与首 token、生成、工具执行。只报端到端总时延,无法判断优化该落在哪一段。口径必须写清起点与终点——"停手到结果送达"与"请求发出到首 token"是两个指标,混用会得出互相矛盾的结论。
成本还要区分名义值与实付:缓存命中部分按缓存价计、未命中按标准价计,两者混在一起就算不出"提高命中率能省多少"。步数预算按任务类型定,不按绝对值——开放式任务允许多步,单点问答超过几步就应告警。分位数取 p50 与 p95,样本窗口写进指标名(p95_latency_7d),否则同一名字在不同窗口下不可比。
判据:分母与口径写在指标名里。成功率要写清分母是任务数还是尝试数,时延要写清分位数与采样窗口;口径含糊的指标等于没有指标。
4. 录制与回放¶
回放要录三样:发给模型的请求与响应、工具调用的输入与输出、影响分支的外部状态(时间、随机种子、文件版本)。只录最终答复,回放时无法重建中间决策;只录模型侧,工具侧的变化会被误判成模型问题。
全录成本高,按用途取样:失败运行全录,成功运行按比例抽样,长运行另保留头尾。回放必须隔离副作用:工具调用默认进试运行(dry-run)或指向沙箱,否则一次回放会真的再发一遍消息、再写一遍文件。录制内容同样要脱敏,且脱敏发生在写入前而不是导出时。
回放的用途是复现失败与做回归对照,不是证明质量。采样本身有随机性,外部状态也可能变化;回放一致只说明这条路径可复现,不说明它在真实环境里稳定。
5. 任务集与判据¶
任务集从真实失败里长出来,而不是从"想得到的场景"里编。每条任务要有外部可判的验收:代码类跑测试或类型检查,数据类校验 schema 与守恒,机器人任务读回状态或回仿真。验收断言要打在外部消费者碰到的行为上,而不是打在内部分支上。
任务集分层:冒烟集(十几条,每次提交跑)、回归集(从线上失败反推,每周增补)、压力集(长任务与边界输入,发布前跑)。每条任务带版本与所属层,修复一个线上失败就把它固化成回归用例——不固化的失败会以同样形状再来一次。任务本身也会过期:依赖的外部服务下线、模型升级后旧用例失去区分度,定期清理比只增不减更可持续。
指标至少三个维度:成功率、成本、时延。只看成功率会鼓励"多跑几步总能成",只看成本会鼓励"少做少错"。一致性用重复运行衡量:同一任务多次运行都成功,比单次通过更有意义。
判据:验收不能由被测对象自己给出。让 harness 自己声明"完成了",等于让考生自己判卷;外部判据哪怕粗糙,也比自证可靠。
6. LLM-as-judge 的边界¶
用模型给模型打分可以降低人工成本,但引入三类偏差:偏爱冗长、偏爱特定位置的内容、偏爱与自己风格相近的输出。未校准的打分器会把"话多"当成"做得好"。
降低偏差靠任务形式:成对比较比绝对打分稳(让 judge 在两个候选里选,而不是给 1–10 分),交换左右顺序后两次结论不一致的比例就是位置偏差的度量。评分维度要拆开(正确性、完整性、格式),合在一个总分里无法定位失败原因。校准用一批人工标注样本,报告一致率与分歧类型;一致率低于人工互评水平时,judge 只能做初筛。
可用范围:风格与格式的筛查、回归对照中的相对比较、人工抽检前的初筛。不可用范围:不可逆动作的最终验收、安全判据、需要精确数值的结论。judge 自身也要有评测——用一批人工标注样本校准,报告一致率与分歧类型。
判据:judge 是筛子,不是门。它可以减少人工工作量,不能替代最终验收。
7. 回归与变异检验¶
每次改 harness(提示、截断、权限规则、工具 schema)都跑同一套任务集,对比成功率、成本与时延。差异要能归因到改动,而不是归因到"机器忙"——对照组与实验组用同一任务、同一模型、同一时段。
变异检验回答"测试是否真的覆盖":故意破坏一处(丢掉工具结果、跳过权限门、把截断长度设为零),对应用例必须变红。变不红说明用例没有触达那条路径,或者触达了却被另一分支顺带保护。
变异算子按被测机制选:删一个事件字段(验轨迹完整性)、关闭一次重试(验幂等)、把截断阈值调到极小(验大输出路径)、让工具返回空(验空集与错误的区分)。等价变异不算——改了实现但不改变外部行为的,本就不应让用例变红。变异通过率写成比例(被杀死的变异 / 总变异),不写成单次结论。
判据:从不触发的参数等于没被测过。变异没红时,先怀疑场景触发不了,再怀疑断言写错。
8. 失败分类与归因¶
按第一个失败的节点归类:模型(选错工具、参数无效)、工具(报错、超时、返回空)、上下文(缺信息、被截断、被污染)、权限(被拒且无替代路径)、环境(服务不可用、配额耗尽)。归因看轨迹里的第一个异常节点,而不是最后一个报错。
第一个异常节点要靠时间与状态共同判定:超时节点可能只是被上游拖死,真正异常发生在前一个未收尾的节点;返回空的工具可能是参数命中了空集,而不是服务故障。归因结论要带置信度标记:单次现象记作"疑似",只有可重复的失败才写进发布门。
同一现象可能有多种成因:工具返回空,可能是工具坏了,也可能是参数让查询命中空集。区分方法是看输入是否有效、同类调用是否稳定失败、换参数是否恢复。
9. 发布门与 A/B¶
发布门是一组硬阈值:任务成功率不低于基线、安全违规为零、成本与时延的分位数在预算内、失败分类没有新增高频项。阈值写进配置,改动要留记录。
门分三级:阻断(安全违规、回归集失败)、告警(成本或时延超出预算但成功率不降)、记录(新增低频失败分类)。三级混成一个布尔值,要么门形同虚设,要么一次小波动就阻断发布。A/B 的样本量要事先估算——任务集只有几十条时,几个百分点的差异落在噪声里,靠看曲线判断等于掷骰子。
A/B 对照要用同一任务集与同一模型,避免把版本差异混进效果差异。灰度发布先放小流量,观察失败分类与成本曲线;回滚条件预先写死,不靠临场判断。
10. 要点¶
- 一次运行是一棵树;任一步都要能从轨迹复现输入。
- 最小字段之外,版本三元组不能省。
- 成本按调用累加,时延拆成排队、生成、工具三段;口径写进指标名。
- 回放录请求、工具与外部状态;用于复现,不用于证明质量。
- 任务集从真实失败长出,验收由外部判据给出。
- judge 是筛子不是门,自身需要人工样本校准。
- 改动跑同一任务集;变异检验确保用例真的触达路径。
- 归因看第一个异常节点;发布门用硬阈值,回滚条件预先写死。