可观测与线上评测
离线 JSONL 证明「这天评测集过了」;线上 trace 证明这一次请求发生了什么。离线脚本骨架见 工具调用与评测实践;面试答法见 AI 应用测试与效果评估;单请求成本怎么算见 Token 计算与成本;验收指标要能从本篇字段算出来,见 FDE 交付闭环实践。
一次请求的最小字段
轨迹与评测共用同一 schema,避免「评测一套、线上一套对不上」。
| 字段 | 含义 |
|---|---|
request_id | 一次用户请求(可含多步、多次 LLM) |
feature | 产品功能:如 order_qa |
user_id | 脱敏后的主体(哈希或内部号,不是明文邮箱) |
thread_id | 会话 / LangGraph thread |
step | 本请求内第几步 |
model | 实际调用的模型名 |
tool | 工具名;无则空 |
ok | 本步是否成功 |
prompt_tokens / completion_tokens | 本步 LLM 实计 |
latency_ms | 本步耗时 |
retrieved_ids | RAG chunk id 列表 |
outcome | 请求级:success / reject / handoff / error / cancelled |
{"request_id":"r1","feature":"order_qa","user_id":"u_9f3","thread_id":"t1","step":1,"model":"mock","tool":null,"ok":true,"prompt_tokens":410,"completion_tokens":28,"latency_ms":320,"retrieved_ids":["p-12"],"outcome":null}
{"request_id":"r1","feature":"order_qa","user_id":"u_9f3","thread_id":"t1","step":2,"model":"mock","tool":"get_order","ok":true,"prompt_tokens":480,"completion_tokens":12,"latency_ms":90,"retrieved_ids":[],"outcome":null}
{"request_id":"r1","feature":"order_qa","user_id":"u_9f3","thread_id":"t1","step":3,"model":"mock","tool":null,"ok":true,"prompt_tokens":520,"completion_tokens":64,"latency_ms":410,"retrieved_ids":["p-12"],"outcome":"success"}
请求结束那一行才写 outcome。人审等待中不要提前标 success。取消、流式中断的取值见 流式取消与人审。
平台用 Langfuse、LangSmith 还是自建 APM 不重要;字段对不齐,换产品也救不了排障。
版本四元组
效果漂移时先问「改了哪一版」,再调参。
| 维度 | 记什么 |
|---|---|
| prompt | 模板 id + hash |
| tools | 工具 schema 的 hash(裁剪后的那一份) |
| index | 检索索引 / 数据快照版本 |
| model | 模型名 + 日期(同一名字会变) |
四元组任一变更:必须跑对照评测集(工具评测篇的 JSONL),再决定发版。CI 分层仍见测试评估篇:Mock 单测 → smoke → nightly。
线上怎么看
| 手段 | 口径 |
|---|---|
| Trace | 默认全量;高压 feature 可按比例采样,但 error / handoff / cancelled 建议全留 |
| 效果抽样 | 1%~5% 人工或规则断言;开放题再上判分模型 |
| 用户信号 | 转人工率、点踩、同一问题重复问 |
| 告警 | 工具失败率升、单请求 token 异常、拒答率骤降(可能闸门被绕过) |
离线评「能不能发版」;线上评「有没有漂移」。两者都要,测试评估篇已讲答法,本篇只钉字段。
看板四类(能从字段算)
| 看板 | 怎么算 | 给谁 |
|---|---|---|
| 任务成功率 | outcome=success / 结束请求数(分母不含纯 cancelled 可视产品定) | 产品 / FDE 验收 |
| 平均步数 | 每 request_id 的 max(step) | 工程(死循环、工具抖) |
| P95 延迟 | 请求级 Σ latency_ms 或首 token 另记 | 体验 |
| 单请求成本 | Σ token × 单价,算法见 Token 篇 | FinOps |
FDE 验收表里的「20 条集通过率」应对同一 feature 的离线集;上线后看同一 feature 的成功率是否掉出约定带。对不上字段,就无法向客户复盘。
# board.py — 从 JSONL 算四类看板(只看带 outcome 的结束行)
import json
from collections import defaultdict
def board(path: str, feature: str) -> dict:
req_steps, req_tokens, req_lat, outcomes = defaultdict(int), defaultdict(int), defaultdict(int), {}
for line in open(path, encoding="utf-8"):
r = json.loads(line)
if r.get("feature") != feature:
continue
rid = r["request_id"]
req_steps[rid] = max(req_steps[rid], r["step"])
req_tokens[rid] += (r.get("prompt_tokens") or 0) + (r.get("completion_tokens") or 0)
req_lat[rid] += r.get("latency_ms") or 0
if r.get("outcome"):
outcomes[rid] = r["outcome"]
ended = [rid for rid, o in outcomes.items() if o != "cancelled"]
ok = sum(1 for rid in ended if outcomes[rid] == "success")
lats = sorted(req_lat[rid] for rid in ended)
p95 = lats[int(len(lats) * 0.95)] if lats else 0
return {
"success_rate": (ok / len(ended)) if ended else 0,
"avg_steps": (sum(req_steps[rid] for rid in ended) / len(ended)) if ended else 0,
"p95_ms": p95,
"avg_tokens": (sum(req_tokens[rid] for rid in ended) / len(ended)) if ended else 0,
}
单价把 avg_tokens 换成钱,仍走 Token 篇;本函数只负责从字段聚合。
和离线评测怎么接
同一 schema
├─ 离线:eval_set.jsonl 驱动 Agent,写 trace,算通过率
└─ 线上:真实流量写 trace,抽样打标,回流进评测集
线上 bad case(outcome=error 或人工点踩)要能一键变成离线用例——至少带上 retrieved_ids、tool、脱敏后的输入。不要只存最终字符串。
常见坑
- 只 log 最后一轮 LLM: Agent 成本与步数全部失真(Token 篇已警告)。
- 明文 PII 进 trace: 安全篇的泄漏面。
- 评测字段和线上字段不一致: 发版通过、线上却对不齐。
- 用产品名代替字段设计: 「我们上了 Langfuse」不等于能回答失败在哪一步。
- cancelled 算失败: 用户点停止不是模型崩了;分母要单独看。
实践要点
- 先定最小字段表,离线与线上共用。
- 请求级
outcome只在结束时写;人审中、取消另算。 - prompt / tools / index / model 四元组变更必跑对照集。
- 看板只选四类能从字段算出的:成功率、步数、P95、成本。
- 面试挂钩:离线证明发版、线上证明漂移;trace 全量(或错误全留)+ 效果抽样。