跳到主要内容

330 篇博文 含有标签「observability」

查看所有标签

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

· 阅读需 12 分钟
Tian Pan
Software Engineer

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

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

系统提示词提供的人格,模型每次都会以同样的方式选择

· 阅读需 11 分钟
Tian Pan
Software Engineer

我最近接触的一个产品团队针对回复人格设定(简洁、详尽、对话式)进行了一项为期三周的 A/B 测试,覆盖了所有用户群组。系统提示词描述了这三种设定,并要求模型选择最匹配用户的那一种。当他们打开数据集编写分析报告时,一个数字让他们愣住了:“详尽”组占据了 91% 的流量。另外两组的比例小到几乎可以忽略不计。

他们的实验平台没有标记任何异常。没有触发任何警报。流水线完全按照他们的指令运行。三周所谓的“多人格测试”产生了一个只能告诉他们关于“详尽”信息的数据集。另外两组样本太少,根本无法进行任何统计推断。

房间里的第一直觉是提示词需要改进——更好的指令、更清晰的人格区分、为对话式场景提供更刻意的示例。如果是在十年前基于规则的路由器中,这个诊断是正确的。但对于模型来说,它是错误的。提示词不是变量,路由器才是。

用户还来不及核实多模态模型的结论,预签名 URL 就已过期

· 阅读需 11 分钟
Tian Pan
Software Engineer

用户打开了昨天的对话。在支持专员的回复旁边,原本上传收据的地方显示为一个破碎的图片占位符。回复中自信地引用了“3 月 14 日在 Coffee Tribunal 商店消费的 47.32 美元”。用户无法检查该引用是否准确,因为模型赖以工作的证据现在来自你对象存储的 403 错误。他们提交了一个“幻觉”工单。你的评估套件没有发现这个问题,因为在调用时,模型的回答确实是完全正确的。

这是一个关于“保留策略不匹配”的故事,而不是模型质量的问题。你的对话记录比它的事实依据(grounding)活得更久。事实依据是一个只有 15 分钟有效期的预签名 URL,而关于该依据的断言则是会存放在你数据库中多年的文本。当资源时钟(asset clock)和断言时钟(claim clock)以不同的速度运行时,任何有据可查的多模态答案在重新访问时,最终都会看起来像是凭空编造。

那些悄悄将你的高级流量路由到 Haiku 的供应商自动路由器

· 阅读需 11 分钟
Tian Pan
Software Engineer

你的平台团队出于成本考虑采用了供应商的 "auto" 模型标识符。上线后的第一个仪表盘数据非常有说服力:在每周 eval(评估)中没有出现可衡量的质量下降,支出却减少了 34%。三个月后,在你最短、流量最高的功能点上,客户满意度已经连续两个季度下滑。一项由产品主导的调查最终将这种回归追踪到了一个工程团队无人触及的模型标识符上。代码里写的是 "auto"。而供应商一直在重新定义 "auto" 这一路整过程中的含义。

教训并不是自动路由不好。教训是,"auto" 是一个移动的目标,其分布随供应商的经济效益而漂移,而你的 eval 的代表性是供应商优化与你的产品质量之间唯一的屏障。如果 eval 与流量不匹配,那么你所庆祝的折扣,其实是从一个无人审查的质量斜坡中支付的。

那些与实际限流不符的提供商频率限制响应头

· 阅读需 11 分钟
Tian Pan
Software Engineer

响应头显示你还有每分钟 480,000 个 token 的剩余额度。但在你仅消耗了 240,000 个之后,429 错误就降临了。你的调度器一直在根据一个运行环境根本不会遵守的数字进行自动扩缩,墙上的燃尽图显示的是文档里的理论值,而限流器执行的却是另一套完全不同的规则。

这种故障往往需要很长时间才能被察觉,因为路径上的每个组件都在执行其宣称的功能。供应商返回了一个响应头。你的客户端解析了它。你的调度器读取了它。你的仪表盘绘制了它。这些层级都没有损坏。真正出问题的是那个假设:即响应头是一份契约。

当供应商重新定义 Bucket 时,那份让你溢出流量成本暴增的预留容量合同

· 阅读需 12 分钟
Tian Pan
Software Engineer

一个平台团队签署了一份为期数个季度的预留吞吐量合约。在承诺容量内按固定的 token 费率计费,超过上限的部分则按更高的超额费率计费。财务部门根据六个月的历史流量对消耗进行了建模,而这些流量很少触及上限。合约中规定“溢出”是指超过承诺上限的每分钟字节数,基于这个定义,这笔交易看起来很稳健。

六周后,在流量形态、路由配置和产品界面均未改变的情况下,账单飙升了 2.4 倍。供应商在季度中期悄悄修改了计量定义。现在,“溢出”还包括自动路由器发送到高于预留层级的模型请求——因此,即使总吞吐量完全在承诺范围内,一次在复杂提示词上选择 Sonnet 的操作也会被计入超额桶中。原本按预留费率结算的 30% 流量,现在改按超额费率计费。财务部门通过仪表板追踪了三周的突发增长,最后才有人读到季度中期的定价补充协议,并在脚注中发现了这一重新定义。

合约并未被违反。但计价所使用的单位被重新定义了。

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

· 阅读需 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)是提供商在其服务栈内部完成的事情——你的库只是该过程的一个模型,而非过程本身,而且两者会产生偏差:这种偏差在每次调用中虽然微小,但在你实际进行的总体调用量中却具有结构性。