这篇学完你能回答什么
- 一次多轮 Agent 请求的 trace 里应该记哪些东西?prompt 原文存不存?
- 没有标准答案的线上场景,怎么定质量监控指标?
- 线上质量掉了,第一个动作是什么?点赞点踩怎么用起来?
从一个真实故障讲起
一个客服助手,线上有采样 LLM judge,每天出一条「回答有据率」曲线。周二早上这条曲线从 0.86 掉到 0.81,掉了 5 个点,告警响了。
值班同学做了教科书式的处置:查最近变更,发现周一晚上上游模型做过一次小版本升级,回滚。
分数没回来。
接着回滚了周一改的 prompt。还是没回来。
折腾到下午才有人去看输入端:周一上午市场部把一个新渠道接进来了,那个渠道的用户问题平均长度是原来的 1.8 倍,且大量涉及退款和赔付政策——一类本来就答得最差的问题。新渠道占了当天 14% 的流量。
系统一点没变坏,是进来的问题变难了。 而这半天里,团队回滚了两个其实没问题的东西,其中一个回滚本身还引入了新的缓存不一致。
这个故障的教训是一句可以直接背的话: 线上质量分数掉了,第一个要回答的问题不是「哪里坏了」,而是「到底是系统变差了,还是进来的问题变难了」。 这两个的处置方向完全相反,而分数本身分不出来。
后面会给出解这个问题的最小工具:金丝雀集。
有据率 0.86 → 0.81
采样 judge 的日曲线,告警响了
查最近变更
周一晚上上游模型做过小版本升级
连回滚两次:模型、prompt断点
分数都没回来,还引入了缓存不一致
下午才去看输入端
周一上午市场部接进来一个新渠道
新渠道占当天 14% 流量
问题平均长 1.8 倍,多涉退款赔付
核心概念:先打比方,再给定义
打比方:监控是仪表盘上的水温灯,可观测是能把发动机拆开看的能力。水温灯告诉你「有问题」,可观测让你回答「一个你上线时没想到的问题」。
LLM 应用之所以对可观测的要求比传统服务高,是因为它的失败大多不抛异常——HTTP 200、延迟正常、日志干净,但答案是错的。传统监控的全部手段在这里同时失效。
术语(首次出现,中英对照 + 人话):
- trace(链路 / 调用链):一次完整请求从进到出的全过程记录。人话:这一单从头到尾发生了什么。
- span(跨度 / 节点):trace 里的一个步骤,可以嵌套。人话:其中的一步。
- 语义约定(semantic conventions):大家约好某个字段该叫什么名字。人话:字段命名的行业普通话。
- 金丝雀集(canary set):一小撮固定输入,在线上按固定频率跑,用来当参照物。人话:随身带的那把尺子。
- 数据飞轮(data flywheel):线上失败 → 变成评测用例 → 门禁变严 → 同类不再复发。人话:让每次事故都留下点资产。
它只告诉你「有问题」
至于是哪一步、为什么,它一个字也答不出
看预先设好的那几条曲线
曲线动了你知道出事了,但它答不出这一单坏在哪一步
能回答上线时没想到的问题
前提是这台机器本来就拆得开 —— 事后装不上去
trace 记下这一单的全过程
span 是其中的一步、可以嵌套;语义约定 = 字段命名的行业普通话
gen_ai.conversation.id语义约定原理拆解
invoke_agent 套 chat 与 execute_toolgen_ai.agent.name / gen_ai.agent.idgen_ai.conversation.id 是把多轮串起来的那把钥匙 —— 没有它,多轮问题永远查不了gen_ai.request.model / provider.name子 span usage 记 input_tokens / output_tokens;模型 ID 要含小版本,上游静默升级只有这一栏能证明gen_ai.tool.name / .call.id / .arguments工具的真实入参与返回是离线复现的前提;只记 prompt 和 response,这一栏就是空的max_tokens 被截断 / 工具报错 / 超时gen_ai.client.token.usage、gen_ai.evaluation.score.value评测结果本身就是遥测数据:挂在 trace 上才能从曲线一键下钻到具体 span;另有 gen_ai.client.operation.duration| 类别 | 必记什么 | 没有它会怎样 |
|---|---|---|
| 身份与串联 | trace_id、conversation / session id、user id(脱敏)、请求入口与渠道 | 开篇那个故障永远定位不到「新渠道」 |
| 可重放的输入 | 系统提示的版本号、检索命中文档 ID + 分数、工具真实入参与返回、上下文总长度、是否触发过压缩 | 能不能把一条线上请求原样搬到离线跑,全看这一栏 |
| 模型与配置 | 模型 ID(含小版本)、effort 档位、max_tokens、是否命中 prompt 缓存、是否走了降级路由 | 上游静默升级、降级到小模型,只有这一栏能证明 |
| 结果与判定 | 结束原因(正常 / 打到 max_tokens 截断 / 工具报错 / 超时)、结构化输出是否解析成功、评测分数与 judge 版本 | 截断和解析失败最容易被当成「模型变笨」 |
飞轮怎么转起来:线上 trace → 用点踩 / 转人工 / 重问挑出候选失败 → 开放编码后归并成 5–10 个失败类别 → 每个新模式补 1 条最小可复现用例(不是整条 trace)→ 门禁变严。每次事故都要以「评测集加了一条」收尾。
点踩是采样器,不是标注。反馈率通常只有个位数百分比,且重度偏向不满的用户;更值钱的是隐式负反馈 —— 立刻重问、改写再问、中途放弃、转人工,覆盖率高一个数量级。反馈事件必须带 trace_id 落库。
一、一次 Agent 请求的 trace 长什么样
OpenTelemetry 的 GenAI 语义约定给了一套通用骨架,值得当成默认结构来记:
invoke_agent gen_ai.agent.name / gen_ai.agent.id │ gen_ai.conversation.id ← 把多轮串起来的那把钥匙 ├── chat gen_ai.operation.name / gen_ai.provider.name │ │ gen_ai.request.model │ └─ usage gen_ai.usage.input_tokens / output_tokens ├── execute_tool gen_ai.tool.name / gen_ai.tool.type │ │ gen_ai.tool.call.id / .arguments / .result ├── chat (带着工具结果的第二次调用) └── ... 指标:gen_ai.client.token.usage、gen_ai.client.operation.duration 评测:gen_ai.evaluation.name / .score.value / .score.label / .explanation
最后一行是最容易被忽略、也最重要的一行:评测结果本身就是遥测数据。把分数挂在 trace 上,你才能从「这周有据率掉了 3 个点」一键下钻到具体是哪几条 span;分数活在另一个系统里,你就只能靠人工对时间戳。
二、trace 里必须记什么
按「没有它就查不出问题」的标准,分四类:
| 类别 | 必记 | 为什么 |
|---|---|---|
| 身份与串联 | trace_id、conversation/session id、user id(脱敏)、请求入口/渠道 | 没有会话 ID,多轮问题永远查不了;没有渠道,开篇那个故障永远定位不到 |
| 可重放的输入 | 系统提示的版本号、检索命中的文档 ID + 分数、工具的真实入参与返回、上下文总长度、是否触发过压缩 | 能不能把一条线上请求原样搬到离线跑,全看这一栏记全没有(承接 Q5-02 第一刀) |
| 模型与配置 | 模型 ID(含小版本)、effort 档位、max_tokens、是否命中 prompt 缓存、是否走了降级路由 | 上游静默升级、降级到小模型,都只有这一栏能证明 |
| 结果与判定 | 结束原因(正常结束 / 打到 max_tokens 被截断 / 工具报错 / 超时)、结构化输出是否解析成功、评测分数与 judge 版本 | 截断和解析失败是两个最高发、最容易被当成「模型变笨」的假象 |
判据:trace 的验收标准不是「记得全不全」,是「能不能据此把这条请求在离线复现出来」。 做不到,它就只是一堆好看的瀑布图。
存不存 prompt / completion 原文,是本节最真实的一道取舍题:
| 存原文 | 不存原文 | |
|---|---|---|
| 收益 | 能排障、能做数据飞轮、能补评测用例 | — |
| 代价 | PII 合规风险 + 存储成本的绝对大头 | 出问题只能看到「有个 span 慢了」 |
实践口径:默认脱敏 + 按采样存原文 + 敏感字段哈希 + 保留期分层(原文 7–30 天,指标与评分长期保留)。这条几乎是所有做过合规评审的团队最后收敛到的位置。
三、线上无标注监控的三层,**顺序不能反**
① 不需要模型的信号 ← 先把这一层建全
用户侧:重问率/改写率、复制率、点踩率、会话中途放弃率、转人工率
系统侧:结构化输出解析失败率、工具报错率、超时率、重试率、
拒答率、截断率(打到 max_tokens)、上下文超限率
特点:不需要标注、不需要 judge、不会漂移、成本近乎为零
▼
② 需要模型但不需要标准答案
采样 judge(有据性 / 拒答合理性 / 格式合规)
输入侧 embedding 聚类:出现新簇 = 新意图或新渠道
约束:基线采样约 2%,高风险或刚改过的路由提到 20%;
必须异步、在响应返回之后跑,绝不进关键路径
▼
③ 业务终审
转化率、工单解决率、人工复核通过率、退款/投诉率
滞后,但唯一真实硬判据:没把第 ① 层建全就先上 LLM judge 的团队,是在用一把会漂的尺子,去量一个从来没测过的东西。 第 ① 层里最被低估的三个指标是转人工率、重问率、截断率——它们零成本、零标注、且和用户真实不满的相关性通常高于任何 judge 分数。
还有一条约束要说死:线上 judge 的分数只能看趋势和分布,不能看绝对值,因为线上不是固定集合,输入分布每天在动。也正因如此,它绝对不能当团队 KPI——一旦考核它,团队就会开始对着 judge 的已知偏差优化。
四、【本篇最关键】归因第一刀:金丝雀集
回到开篇的故障。分数掉了,可能是系统变差,也可能是输入变难,而线上分数天然分不出这两者——因为分子分母都在动。
解法是造一个分母不动的参照物:
金丝雀集 = 20–50 条固定输入,覆盖主要意图与高风险场景
运行方式 = 在生产环境、走同一条链路、同一份 prompt、同一个 judge,
按固定频率(如每小时)跑一遍,单独出一条曲线
线上曲线掉 + 金丝雀曲线不动 → 输入分布变了(新渠道/新活动/更难的问题)
系统没坏,该改的是覆盖面,不是回滚
线上曲线掉 + 金丝雀曲线也掉 → 系统真的变了
再往下切:模型版本?prompt?索引?工具依赖?它和 RAG 排障里「手工把正确文档塞进 prompt」是同一个手法:固定住一个变量,把问题一分为二。 前者固定「文档」以切开检索侧与生成侧,这里固定「输入」以切开流量侧与系统侧。
成本极低(几十条 × 每小时一次),但它把开篇那半天的盲目回滚压缩成一次看板对比。如果只允许你在本章带走一个工程实践,带这个。
五、数据飞轮:让每次失败都留下资产
线上 trace │ ├─ 采样 + 用户信号(点踩、转人工、重问)挑出候选失败 │ ├─ 错误分析:开放编码(每条失败写一句话)→ 轴心编码(归并成 5–10 个失败类别) │ ├─ 每个新失败模式补 1 条评测用例(最小可复现输入,不是整条 trace) │ └─ 评测集增长 → 门禁变严 → 同类问题不再复发
关于点赞点踩,有三件事必须讲清楚,否则这条飞轮转不起来:
- 点踩率是趋势信号,不是标注。 反馈率通常只有个位数百分比,且重度偏向不满的用户——直接拿它当质量指标会系统性偏低。它的正确用法是当采样器:把点踩样本优先送进人工复核队列。
- 点踩必须能定位到 trace。 反馈事件要带 trace_id 和 conversation_id 落库,否则你只知道「有人不满意」,不知道他不满意什么。这是产品和工程最常见的接口漏配。
- 点赞几乎没有信息量,点踩才有。 更值钱的是隐式负反馈:立刻重问、改写问题再问一次、中途放弃、转人工——这些不需要用户动手,覆盖率高一个数量级。
两条可背判据: ① 每一次线上事故都必须以「评测集里新增了一条用例」结束。 只回滚、只改 prompt、不补用例的,这个问题一定会换个形式回来。 ② 评测集不是只增不减。 定期把「最近所有版本都 100% 通过」的用例降级到冒烟集——它们已经失去区分度,只在消耗预算。
工程实践(截至 2026-08)
OpenTelemetry GenAI 语义约定的真实状态
这是本章最硬的时效锚点,也是一个很好的分水岭题材:
- 到 2026-08,没有任何一个 GenAI span / event / metric / attribute 达到 Stable,全部处于 Development。
- 2026-06 的 semantic-conventions v1.42.0 把全部
gen_ai.*内容移出核心仓库,迁到独立仓库,该仓库尚无版本化 release。 - 它改过好几轮:
prompt_tokens→input_tokens(v1.27.0,2024-08);gen_ai.system→gen_ai.provider.name(v1.37.0,2025-08);新增评测结果事件(v1.38.0,2025-10);检索 span 与缓存 token(v1.40.0,2026-02);agent span 拆分与 reasoning tokens(v1.41.0,2026-04)。
工程后果:同一条链路上不同框架很可能同时发多代属性(既发 gen_ai.system 又发 gen_ai.provider.name)。
对策:在采集层(Collector)做属性归一化,别让看板直接吃上游语义;并把语义约定版本当依赖钉死。 分水岭:只会说「我们用 OTel」→ 读过;能说「gen_ai.* 还没稳定、改过好几轮,所以我们在 Collector 里做了映射」→ 上过生产。
平台选型(本教程唯一一次点名,截至 2026-08,会变)
| 层 | 代表 | 定位 |
|---|---|---|
| 指标 / 断言库(进 CI) | DeepEval、RAGAS、promptfoo | 写在代码里,跟单测一起跑 |
| 评测 + 可观测一体 | Langfuse(开源可自托管)、LangSmith、Braintrust、Arize AX + Phoenix | trace 与评测同一份数据 |
| 传统可观测栈延伸 | Datadog LLM Observability、MLflow、W&B Weave | 与既有 APM / 实验管理打通 |
| 云厂商托管 | Vertex Gen AI Evaluation Service、Azure AI Evaluation | 与自家 Agent 运行时耦合 |
只记三条选型判据,产品会换、判据不会:
- 评测数据和 trace 数据必须在同一个系统里——否则「这条掉分的请求到底哪一步错了」永远要跨系统人工拼。
- 能自托管的优先——评测集是团队唯一 100% 保值的资产,不该锁在别人的 SaaS 里。
- CI 里跑的那一层必须是代码,不是平台 UI——UI 里点出来的评测无法 code review、无法回滚、无法跟着分支走。
避坑清单
- 别把 judge 判分放进关键路径。 同步判分会直接把 P99 翻倍。异步、离线、采样,三选一。
- 别只看均值。 质量问题几乎总是先出现在 P95 和某个细分渠道上,全局均值是最后才动的。开篇故障里 14% 的新渠道流量,就足以把全局曲线拉掉 5 个点。
- 别忘了记「结束原因」。 截断(打到 max_tokens)伪装成「模型变笨」,是最高发的假象之一。
- 别让 trace 采样率对所有路由一刀切。 高风险路由(涉钱、涉权限、涉外发)应该全采。
- 别把点踩当标注用,它是采样器不是真值。
- 告警要按细分维度设,不要只设全局阈值。 按渠道 / 意图 / 模型版本分组,才能在被稀释之前发现问题。
| 层 | 代表 | 定位 |
|---|---|---|
| 指标 / 断言库(进 CI) | DeepEval、RAGAS、promptfoo | 写在代码里,跟单测一起跑 |
| 评测 + 可观测一体第一条判据指向这层 | Langfuse(开源可自托管)、LangSmith、Braintrust、Arize AX + Phoenix | trace 与评测同一份数据 |
| 传统可观测栈延伸 | Datadog LLM Observability、MLflow、W&B Weave | 与既有 APM / 实验管理打通 |
| 云厂商托管 | Vertex Gen AI Evaluation Service、Azure AI Evaluation | 与自家 Agent 运行时耦合 |
- 三层的顺序
- ① 不要模型的信号 → ② 采样 judge → ③ 业务终审。顺序不能反,第 ① 层零标注零成本
- 第 ① 层最被低估
- 转人工率、重问率、截断率 —— 和用户真实不满的相关性通常高于任何 judge 分数
- judge 怎么跑
- 基线采样约 2%,高风险或刚改过的路由提到 20%;必须异步,同步判分直接把 P99 翻倍
- judge 分数怎么用
- 只看趋势和分布,不看绝对值;绝不能当 KPI —— 一考核就会对着它的已知偏差优化
- OTel GenAI 现状
- 到 2026-08
gen_ai.*全部 Development,无一 Stable;v1.42.0(2026-06)已移出核心仓库 - 所以要归一化
- 一条链路可能同时发
gen_ai.system与gen_ai.provider.name,在 Collector 层做映射
面试视角
- 先给结构trace 骨架
invoke_agent→chat/execute_tool,会话 ID 串多轮 - 再给判据记录的验收标准 = 能不能离线原样复现
- 讲监控分层先建不要模型的信号,再上采样 judge,最后对齐业务
- 给归因动作金丝雀集:切开「输入变难」与「系统变差」
- 谈闭环与取舍失败 → 补用例 → 门禁变严;存不存原文、采样率、PII
- 「我们接了 XX 平台,能看到 trace」—— 只有工具,没有字段清单
- 线上直接上 LLM judge 打分做监控 —— 跳过了零成本的第一层
- 「分数掉了就先回滚」—— 没有归因动作,开篇故障的标准复现
- 「点踩率是我们的质量指标」—— 把重度有偏的稀疏信号当真值
- trace 只记 prompt 和 response,不记小版本、结束原因、文档 ID
- 说得出验收标准是「能离线原样重放」,以及为此必须记哪几栏
- 先建零成本信号再上 judge,报得出 2% / 20% 这两个采样档
- 第一动作是金丝雀集,并说清它切开的是流量侧还是系统侧
- 知道隐式负反馈(重问、改写、放弃、转人工)覆盖率高一个数量级
- 说得出
gen_ai.*还没 Stable,自己在 Collector 里做了归一化
Q5-06、Q5-07、Q5-09、Q5-10。面试官会怎么问
- 「一次 Agent 请求的 trace 你会记哪些字段?」(工程向二面高频)
- 「线上没有标准答案,你怎么知道质量变差了?」(区分度最高的一问)
- 「线上分数掉了,你先干什么?」(考的是归因次序,不是知识点)
- 「用户点踩的数据你们怎么用?」(三面 / 业务负责人常问)
答题结构建议
① 先给结构 trace 骨架(invoke_agent → chat / execute_tool),会话 ID 串多轮 ② 再给判据 记录的验收标准 = 能不能离线原样复现 ③ 讲监控分层 先建不要模型的信号,再上采样 judge,最后对齐业务指标 ④ 给归因动作 金丝雀集:切开「输入变难」和「系统变差」 ⑤ 谈闭环 失败 → 错误分析 → 补评测用例 → 门禁变严 ⑥ 谈取舍 存不存原文、采样率、成本、PII
分水岭信号
暴露只读过:
- 「我们接了 XX 平台,能看到 trace」——只有工具,没有字段清单,也没有判据。
- 「线上用 LLM judge 打分做监控」——跳过了零成本的第一层信号。
- 「分数掉了就先回滚」——没有归因动作,开篇故障的标准复现。
- 「点踩率是我们的质量指标」——把一个重度有偏的稀疏信号当真值。
- 记 trace 只记 prompt 和 response,不记模型小版本、结束原因、检索文档 ID。
证明真做过:
- 说得出**「trace 要能支持离线原样重放」**这条验收标准,以及为此必须记哪几栏。
- 说得出存不存原文的取舍,并给出脱敏 + 采样 + 保留期分层的具体口径。
- 说得出金丝雀集(或任何等价的固定参照物),并解释它切开的是哪两件事。
- 说得出
gen_ai.*还没 Stable、改过好几轮,以及自己在采集层做了归一化。 - 说得出隐式负反馈(重问、改写、放弃、转人工)比点踩覆盖率高一个数量级。
- 说得出「评测分数应该挂在 trace 上」,而不是活在另一个系统里。
小结与延伸
三句话小结:
- LLM 应用的失败大多不抛异常,所以 trace 的验收标准不是「记得全」,而是「能不能据此在离线原样复现这条请求」。
- 线上监控分三层且顺序不能反:先建零成本的用户与系统信号,再上采样 judge(异步、2%/20%),最后对齐业务指标;judge 分数只看趋势,不看绝对值,更不能当 KPI。
- 分数掉了的第一个动作是用金丝雀集切开「输入变难」和「系统变差」;每次事故的收尾是往评测集里补一条用例。
延伸阅读(本教程内):
继续深入
本篇归属第 5 章「评测与可观测」,去做这一章的题。