跳到主要内容

348 篇博文 含有标签「observability」

查看所有标签

重试预算如何从你的仪表板中隐藏了供应商的真实错误率

· 阅读需 12 分钟
Tian Pan
Software Engineer

周会汇报的幻灯片上写着 99.9%。发票则显示账单翻了三倍。这两个数字在相邻的仪表盘上共存了数月,却没人发现它们衡量的是不同的世界。可靠性数值是重试后的结果——每一个最终返回 200 的调用都被计为成功——而成本数值则是客户端进行的每一次尝试,按 Token 计费。在两者之间,是一个慷慨的五次重试循环,以及一个尾部延迟正在悄悄恶化的供应商。第一次有人同时观察这两个数字是在一次故障期间,当时成本异常告警在可用性告警之前就触发了。

这就是整个模式。一个看起来像是可靠性机制的重试预算,本质上也是一个成本-质量调节旋钮,而那些只关注其中一面的团队,正在为一个发票最终会修正的可用性数字买单。

云厂商负载均衡器悄然忽略的会话亲和性

· 阅读需 12 分钟
Tian Pan
Software Engineer

你的仪表盘显示缓存命中率为 71%。你的财务伙伴很满意。你的 p50 延迟也表现正常。然而,一个来自长时间运行的智能体(agent)会话的客户支持工单传了过来:第 14 轮对话花了 11 秒才产生首个 token,第 15 轮花了 8 秒,第 16 轮花了 9 秒。你调出链路数据(trace)。每一轮对话报告的 cache_read_input_tokens 值都是 0。系统提示词有 1.6 万个 token。用户认为智能体坏了,你认为你的供应商坏了。你们两个都不对。总体的命中率是一个幸存者统计数据 —— 它平均了那些容易命中缓存的短对话,并悄悄吸收了那些在会话中期崩溃为“首轮冷启动”的长对话。

这是任何供应商的复盘报告都不会向你描述的故障模式,因为从他们的遥测数据来看,系统正在按设计运行。负载均衡器正在做出它被要求做出的路由决策。缓存正按照它被要求遵循的时间表进行填充和置换。你传递的提示 —— prompt_cache_key、会话 ID、用户 ID,或者你序列化到该字段中的任何字符串 —— 始终都只是建议性的,而“建议性”意味着“在方便时会被忽略”。在负载压力下、发生扩缩容事件时、上游节点(pod)正在排空时,或者亲和性感知层饱和时,你的提示会悄无声息地降级为均匀的路由决策。请求落在一个冷启动的节点上。原本可以以亚毫秒级成本提供服务的前缀 KV 张量就在 16 英尺外的兄弟机架上,却无法访问。你的对话再次支付了全额前缀成本,而你仪表盘上的标题数字纹丝不动,因为另外 2000 个只有一轮的对话都正常命中了缓存。

被反向代理剥离的 SSE Keep-Alive,以及你支付了两次费用的 Prompt

· 阅读需 11 分钟
Tian Pan
Software Engineer

你的 Agent 调用了一个耗时 35 秒的工具。在这 35 秒内,没有任何 Token 从模型流回浏览器。Provider 的 SSE 流仍然开启。你的工具仍在运行。用户的加载动画也在旋转。而在路径中间的某个你无法控制的反向代理认为连接静默时间过长,关闭了它,随后你的客户端重连逻辑尽职地从头重新启动了整个请求。

第一次响应产生了 4,200 个 Prompt Token 和 600 个 Completion Token。第二次响应也是 4,200 个 Prompt Token 和 600 个 Completion Token。用户得到了一个答案。而你的账单却收到了两份。

你发出的流式中止信号,供应商照样收了费:账单中隐藏的 14% 差额

· 阅读需 11 分钟
Tian Pan
Software Engineer

你的财务团队发起了一项申诉并失败了。该账单项是“输出 token”,它比你交付 token 总数的统计指标高出了 14%。供应商的支持工程师以“流式传输取消下的预期行为”为由关闭了工单,并附上了一份文档链接,上面写着“取消操作将在最后一个交付的 token 处停止计费”。这两句话都是事实,而它们之间的差距,正是你尚未编写的那行代码。

你阅读的合同是一回事,推理调度器的实际操作是另一回事。这种不匹配既不是 bug,也不是计费错误,更不是恶意欺诈——它是一个分层系统,取消信号必须穿越三个边界(浏览器、边缘节点、GPU),而计费表位于第三个边界,但你的“停止生成”按钮却位于第一个边界。缩小这一差距是一个由财务负责人发起的工程项目。

那些将模型未完成的残缺回答存入数据库的流式 UI

· 阅读需 12 分钟
Tian Pan
Software Engineer

这份事后分析读起来像是一份幻觉报告。一名用户根据一份语气笃定的建议采取了行动,但结果证明该建议是错误的——这种错误在模型正常完成输出的情况下是不会出现的。然而,追踪记录显示模型并未完成输出。在预期的 800 个 Token 中,供应商连接在第 412 个 Token 时断开了。客户端的错误处理程序记录了这次失败。但随着 Token 的到达,持久化的部分消息已被写入对话历史,在用户的 UI 中看起来与其他完整的回答毫无二致。于是用户采信了它。支持团队将该工单归类为内容质量问题,花了整整两周时间才将其转交给平台团队。

这条链路中没有任何环节属于模型故障。模型对生成的 412 个 Token 表现得非常正确。失败的原因在于流式 UI 和持久化对话历史在“什么才算是一条消息”的问题上产生了隐秘的分歧。而正是这种流式传输本应缓解的故障模式,导致这一分歧成为了权威记录。

这是乐观渲染(Optimistic Rendering)与持久化存储之间的契约。大多数聊天产品只是从教程或框架中继承了这种模式,而从未将其视为一项契约,这种鸿沟最终表现为一系列看似模型 Bug 实则不然的尾部故障。

客户端估算的 Token 数量与供应商结算账单的差异

· 阅读需 13 分钟
Tian Pan
Software Engineer

你的应用程序使用与你认为的提供商所使用的相匹配的分词器(tokenizer)库在本地计算 token 数量。SDK 在每次调用前报告“预计 4,200 个 token”。你的预算逻辑通过了该请求。然而,提供商的账单显示相同的负载为 6,800 个 token。将这 60% 的差距乘以每月数百万次的调用,财务团队无法根据你的日志核对的这一项开支,看起来就不再是四舍五入的误差,而是一个架构错误。

错误并不在于本地分词器是错的。错误在于将本地分词器视为一种契约,而不是一种猜测。Token 化(Tokenization)是提供商在其服务栈内部完成的事情——你的库只是该过程的一个模型,而非过程本身,而且两者会产生偏差:这种偏差在每次调用中虽然微小,但在你实际进行的总体调用量中却具有结构性。

使所有 Prompt 缓存前缀失效的分词器升级

· 阅读需 10 分钟
Tian Pan
Software Engineer

发布说明只有两行。“改进了多语言分词(Tokenization)。模型输出无破坏性变更。”一共不到二十个字。你的评估(Evals)确认了这一点:相同的提示词,相同的生成内容,相同的评分。你的平台团队在周五下午批准了升级。到了周二早上,你的缓存命中率从 80% 下降到 4%,每日推理费用翻了两番,而凌晨 6 点把你叫醒的轮值工程师在你的代码里找不到任何一行改动。

你的代码确实没有任何改动。但服务商发布了一个新的分词器,它对某个 Unicode 字符的一个字节划分与旧版本不同。你系统中每个缓存的前缀现在都是基于一个已不再存在的 Token 序列生成的指纹。模型的表现完全一致 —— 这确实是事实。但发布说明中未曾提及的缓存层,却为此付出了全额代价。

导致你的智能体重试机制失效两周的工具 Schema 迁移

· 阅读需 12 分钟
Tian Pan
Software Engineer

弃用通知是在周二发出的。下游团队更改了其搜索工具的响应结构 —— results[].snippet 变成了 results[].excerpt,这是一个干净的重命名,有六周的窗口期,文档中有横幅提醒,还给工程邮件列表发了三封提醒邮件。每一个人类用户都迁移了。但 Agent 没有,因为 Agent 不读邮件。在 14 天的时间里,重试循环静默地解析新的有效负载,发现它正在寻找的字段缺失了,抛出了一个 KeyError,并将其计为可重试的故障。重试命中了相同的端点,得到了相同的新结构,抛出了相同的错误,在尝试三次后放弃,并向用户返回了一条致歉消息。重试预算仪表盘在那段时间里一直显示为绿色 —— 重试次数从未 耗尽,它们只是在 预算内永久失败。在该路径上,从工具层测得的成功率为零。但没有人察觉,因为没有触发报警。

这是 2026 年最让工程师头疼的失败形式:不是那种戏剧性的停机,而是隐蔽的契约漂移(Contract Drift)。在这种情况下,面向人类的迁移已经顺利完成,而面向 Agent 的迁移甚至从未开始,因为没有人意识到还需要进行迁移。弃用流程完全按照设计运行,服务于它所设计的使用方。而 Agent 却是一个不在名单上的使用方。

你的网关在 LLM 调用与工具执行之间丢失的 traceparent 请求头

· 阅读需 13 分钟
Tian Pan
Software Engineer

一名用户反馈 Agent 回答正确,但数据库从未更新。你打开可观测性工具,搜索用户端对话中标记的 trace ID,发现了一个清晰的树状结构——五次 LLM 调用,四次工具决策,一个最终回答。没有任何错误。接着你搜索负责数据库写入的工具服务,发现了另一个 trace,虽然墙钟时间窗口相同,但 trace ID 不同,根 span 不同,且没有关联回溯。你搜索网关日志。又发现了三个孤立追踪(orphan traces)。在聊天 UI 中看起来像是单次连贯交互的 Agent 运行,在你的追踪后端却分裂成了一片森林。

本应将这一切串联起来的请求头是 traceparent。它是一个 55 字节的 W3C 标准字符串,分布式系统中的每个 span 都用它来识别其父节点。然而,在大多数生产环境的 LLM Agent 技术栈中,它在用户请求与用户真正想要的副作用(side effect)之间,至少会被丢弃一次。

供应商重新校准后,你的智能体所信任的转录置信度得分

· 阅读需 11 分钟
Tian Pan
Software Engineer

语音智能体有一个门控机制。转录置信度高于 0.85 的任何内容都会直接进入规划步骤;低于该值的内容则会被路由给人工。该阈值是六个月前针对标记的真实客户通话语料库进行调优的,随后被固定在配置文件中并被遗忘。在六个月的时间里,它确实履行了职责。然后,转录服务提供商发布了模型升级——同样的 API、同样的响应形式、同样的延迟范围、同样记录在案的准确率——但在接下来的两周里,该智能体开始向错误的人授权电汇。

“给妈妈转账 50 美元”变成了“给 Tom 转账 5,000 美元”。新的转录结果返回的置信度为 0.91,远高于门控阈值。下游规划器看到了一个置信度很高的转录结果并据此执行。客户的申诉最终暴露了这个 Bug,但到那时,支持队列已经将一周内类似的事件作为欺诈纠纷过滤掉了。复盘分析将差距追溯到团队从未明确做出的一个决定:旧模型的 0.85 和新模型的 0.85 是同一个数字。

那个批准了“单次调用成本”却从未衡量“单次解决任务成本”的智能体预算

· 阅读需 11 分钟
Tian Pan
Software Engineer

在部署后的一个季度,AI 团队报告单次 API 调用平均成本降低了 25%。支持团队报告 AI 分流工单的平均处理时间从 4 轮增加到了 7 轮。这两个数字都是正确的。两个团队都在测量他们被要求优化的系统。夹在中间的财务团队无法核对仪表盘,因为这两个指标都不是以客户实际支付的东西来衡量的:一个已解决的工单。单次调用成本下降了,而单次任务解决成本上升了 40%。由于没有团队负责这个指标,所以没人注意到它的变动。

这是我在智能体(agentic)部署中见到的最常见的单位经济效益(unit-economics)失败,而且这不是一个测量上的 Bug,而是一个定义上的 Bug。供应商的价格页面展示了单次调用成本,因为这是他们计费的单位。由于电子表格的单元格刚好放得下,这个单位就被继承到了表格中。工程团队针对给定的单位进行优化。等到 API 经济与业务经济之间的鸿沟变得清晰可见时,这种影响已经累积了一个季度,而智能体整个时间都在基于错误的损失函数(loss function)被悄悄训练。

那个基于已被你的上下文剪枝器丢弃的事实进行分支的智能体计划

· 阅读需 12 分钟
Tian Pan
Software Engineer

一个运行时间较长的 Agent 在第 3 步生成了一个计划。计划的内容大致是:“如果第 1 步中 get_order 返回的订单状态为 shipped,则向客户发送一封物流追踪邮件;否则开启退款工单。”Agent 自信地选择了邮件分支。但客户从未收到追踪号码,因为订单实际上处于 pending 状态。你查看 Trace,期望能发现幻觉。但你发现的情况更糟:第 1 步的工具结果已经不在上下文中了。Pruner 在第 2 步和第 3 步之间将其剔除了——因为它在最近性排名中较低,而且为了给 12KB 的对话记录腾出空间。计划仍在运行。分支仍被选中。现在的决策指向了一个根本不存在的证据。

这在通常意义上并不是模型失败。模型生成了语法正确的计划,按顺序执行,并做出了分支决策。分支是基于一个曾经在上下文中但现在已不在其中的事实做出的。思维链编码了条件(if status == "shipped");而实际的状态在传递到需要它的步骤时被丢弃了。计划看起来是确定性的,但它已经被悄悄地从证据中剥离了。