跳到主要内容

OpenTelemetry 尾部采样器丢弃了复盘最需要的 LLM Span

· 阅读需 12 分钟
Tian Pan
Software Engineer

用户联系支持:“助手让我注销服务来更新地址,这太疯狂了。”你的团队开启了故障处理程序,询问对话 ID,将其放入追踪(tracing)UI,结果得到了一句礼貌的 “未发现此 trace 的 spans”。24 小时的保留窗口在一小时前刚刚关闭。尾部采样器(tail sampler)判定这次对话是常规成功,因为响应是一个语法正确的 JSON 对象,返回状态码 200,耗时 1.4 秒。根据你的采集器所能理解的所有信号,什么都没发生。

模型返回的一句话毁掉了一段客户关系,而你的可观测性流水线却将其归类为无事发生。这不是采样器的 bug。采样器完全按照你的配置执行。问题在于,你编写的策略是为“请求-响应”的世界设计的,在那个世界里,“成功”与“值得保留”几乎是同一回事,而你未经修改就将其移植到了一个并非如此的系统中。

为错误的故障模式设计的采样策略

尾部采样(Tail-based sampling)是 OpenTelemetry 生态系统提供的最有用工具之一。你让 trace 完成,观察其完整形态,然后决定保留什么。典型的配置大致如下:保留所有报错的,保留长延迟的离群值,保留其余部分的一小部分概率样本,丢弃大部分。对于支付 API 或订单流水线,这是正确的答案,因为你最想调试的 trace 群体恰好是那些返回非 200 或超过 p99 边界的群体。

该策略建立在一个隐含的契约之上:系统可以发出自身故障的信号。5xx 是故障。应该耗时 200 毫秒却耗时 10 秒的响应是故障。trace 通过它发出的数值告诉你,它值得你的关注。采样器的工作就是倾听这些信号,并保留应用已经标记的内容。

LLM 工作负载以一种你能想象到的最无聊的方式打破了这一契约:它们看起来很正常。模型返回了一个值。值解析成功。函数调用层接受了它。下游服务毫无怨言地消费了它。延迟落在正常区间内,因为无论模型生成的 Token 是正确答案还是自信的胡说八道,其生成速度都大致相同。span 属性中没有任何信息能表明输出是幻觉、偏离策略的拒绝、针对错误标识符的工具调用,或是领域专家一眼就能看出的错误通用建议。

你的采样器看到 trace,看到没有错误,看到延迟在绿色区间内,于是根据它仅有的规则对其进行分类。规则规定:这是一个成功的 trace,保留其中的 1%,其余的在 24 小时边界处过期。你即将撰写的复盘报告现在受到了三个季度前为一个完全不同的系统编写的配置文件所做的决定的限制。

当 “200 OK” 不再传递相同的信息时

LLM 系统中令人感兴趣的故障模式存在于传输层协议无法察觉的层面。提供商返回 200 是因为推理完成了。你的工具层返回 200 是因为 JSON 符合 schema。你的下游服务返回 200 是因为被指示执行的操作没有抛出异常。链条中的每一个环节都报告成功,而用户体验却是助手告诉了他们不真实的事情、调用了错误的工具,或者给出了一个表面正确但在结构上与他们实际提出的问题不一致的答案。

这是可观测性团队尚未完成的架构讨论部分。在“请求-响应”的世界中,状态码是“系统是否完成了这件事”的压缩摘要。这个摘要已经足够好,以至于整个行业都同意在此基础上构建采样、告警和 SLO 框架。在概率输出的世界中,状态码仅总结了“推理是否完成”——这是一个弱得多的断言。一个已经漂移的模型、一个过时且不再相关的提示词、一个误导规划器的工具描述、一个在三轮对话前就耗尽了有效信号的上下文窗口——这些都不会表现为 5xx。它们表现为用户不再信任该产品。

实际后果是,你的 AI 功能中最代价昂贵的故障,恰恰是那些在为出错时返回错误而调优的服务采样器看来最普通的故障。你明天调试对话所需的样本,正是你今天最能预见地丢弃的那些。

将语义质量纳入采样决策

解决办法不是保留更多的 trace。存储预算是现实存在的,任何严肃的 Agent 系统中推理 span 的数量都使得 100% 的保留策略变得不可行。解决办法是将采样器对“有趣”的定义扩大到状态和延迟之外,让来自应用质量层的信号参与决策。

最直接的版本是评判器驱动采样(judge-driven sampling)。在摄入时,你针对对话运行一个廉价的评估器——一个快速分类器、一个小型的评判器模型、或者针对最终输出的结构化量表——并将得分写入 span 属性。尾部采样器随后会有一个新策略:除了错误和延迟策略外,保留任何评分低于某个阈值的 trace。运行评判器的成本是在推理上支付的,而不是存储,而且推理是以针对你已经产生的输出的小模型费率进行的。这样做的好处是策略现在对应用可以生成但传输协议无法生成的信号变得敏感。

评判器不必很复杂。响应相关性检查、“输出是否符合问题形式”、“函数调用是否引用了提示词中出现的实体”——这些都是微小、廉价且足够确定性的准则,可以过滤掉真正平凡的流量,并显现出人类想要查看的候选项。一旦 span 中有了信号,采样器就会执行它最初被设计用来做的工作:保留异常,丢弃常规,而此时的“异常”已经包含了一类协议无法命名的范畴。

一种补充模式是给评判器评分的保留策略一个升级路径。几个平台参与者已经采用了这样一种惯例:当在线评估器针对某个 trace 运行时,该 trace 会自动升级为延长保留期。机制在精神上是一致的:评估 trace 的行为本身就是一个信号,表明该 trace 值得保留,保留层应该对此做出反应,而不是将评估和存储视为独立的事宜。

对话范围的保留与用户反馈循环

评判员驱动的采样能捕捉到模型自身可以识别的一类故障。但它无法捕捉到只有用户才能识别的故障——那些技术上正确但偏离重点的回答,那些孤立看很合理但对特定客户完全错误的推荐,以及那些听起来没问题直到用户实际尝试使用时才发现行不通的解释。对于这些问题,信号往往延迟到达,通常是在用户标记回复为无帮助或向客服投诉数小时后。

对话范围的保留弥补了这一差距。保留的单位不再是单个 span 或 trace,而是会话(session)。收集器会为对话中的每个 span 标记一个稳定的会话标识符,将其全量保存在以天而非小时计的延长保留窗口中,并且只有在该窗口关闭且未收到负面反馈时,才最终做出采样决定。如果在窗口期内收到反馈,整个会话都会被升级为永久保留,无论其中的单个 span 在产生时看起来多么无害。

这种模式的成本受限于处于延迟决策窗口内的会话比例。对于大多数产品来说,这个比例足够小,是可以承受的。其收益在于,事后复盘(post-mortem)对话不再以“trace 已过期”开头。它始于产生投诉的完整会话,包括在糟糕输出之前模型上下文已经开始漂移的轮次,以及那些在结构上成功但引导了错误计划的工具调用。

这里更深层的转变是将用户的反馈视为一等可观测性信号,而非单纯的分析信号。工程团队历来将点赞/点踩和“这没有帮助”的链接视为产品遥测数据,并将其路由到与存储 trace 数据不同的系统中。结果导致复盘时需要关联两个并非为联接而设计的存储库,而所依赖的标识符在系统间的传输过程中可能无法存续。最简单的修复方法是将反馈事件作为 span 写入原始 trace,让现有的 trace 保留策略同时承载两者。

挖掘采样器从未被告知要识别的信号

除了评判员评分和用户反馈,AI 工作负载还会发出许多传统采样器从未考虑过的信号。单个对话中的工具调用拒绝率——模型针对同一个工具不断尝试相同的参数形式直到放弃——所揭示的信息是延迟直方图无法体现的。Token 消耗远高于给定提示词类别的中位数,通常预示着智能体(agent)进入了某种退化循环(degenerate loop),即使它最终成功终止了。拒绝模式的漂移,即模型在之前会回答的地方开始推诿或拒绝,预示着你底层的模型版本发生了变化,而你的发布日志并未对此做出说明。

这些都不是标准的尾部采样(tail-sampling)策略。它们是派生信号,必须在摄入时计算,写入 span 属性,并暴露给采样器。OpenTelemetry 中的 GenAI 语义规范正在收敛到一套针对其中某些信号的词汇表——Token 使用量、模型参数、工具调用结构——但这些规范到 2026 年仍在积极开发中。而特定于应用的信号(你的评判员评分、你的领域准则、你的反馈信号)则位于标准层之上,需要团队自行发明属性 schema。这项工作是不可避免的。标准层最终会使通用部分稳定下来,但对你的产品最重要的部分将不会包含在内。

这种做法所需的自律性是令人陌生的。采样策略在历史上一直由平台团队负责,每隔几个季度当有人注意到存储账单时才会更改一次。AI 感知的采样策略需要更贴近应用:编写评估器的团队最清楚哪些输出模式值得保留,而采样策略需要遵循这些准则。将采样配置视为由平台团队和 AI 团队共同拥有的工件——与评估套件同步版本,并在评估准则更改时进行审查——这是一个微小的组织转变,却能解决一大类“我们没有 trace 数据了”的问题。

复盘中的变化

工程负责人最关心的对话版本是在发生真实故障时能恢复出什么。如今,当用户抱怨你的 AI 功能产生的输出时,获取首个有效数据的时间(time-to-first-useful-data)取决于采样器是否保留了 trace。如果保留了,你可以立即开始调查。如果没有,故障发生的第一个小时你将不得不尝试重现错误,而这往往是徒劳的,因为重现概率性系统的故障并非易事。

在采样策略已进化为能够识别语义质量的版本中,trace 几乎总是存在的。或者是评判员在摄入时标记了它,或者是用户反馈升级了保留级别,亦或是智能体循环中异常的 Token 特征导致策略保留了它。对话从数据开始。虽然数据是不完美的——评判员评分本身存在噪声,反馈是稀疏的,派生信号可能具有误导性——但替代方案是一个空白的 UI,在那里,零样本量正教给你的团队:这种故障是无法调试的。

值得一提的文化层面的内容是,可观测性实践是从一个运行良好的世界引入到 AI 系统中的。尾部采样是分布式追踪产生的最优雅的设计之一。它被调整用于不同的故障面,这并非该学科的错。运行生产环境 AI 功能的团队面临的任务是,保留可观测性栈中仍然适用的部分——span 结构、上下文传播、属性规范——并更新那些默认假设“200 OK”包含的信息量与以往一样大的部分。采样器是需要更新的部分之一,更新推迟得越久,就越多的复盘会以那句在 2026 年本不该出现的句子开头:我们没有数据。

References:Let's stay in touch and Follow me for more thoughts and updates