跳到主要内容

348 篇博文 含有标签「observability」

查看所有标签

JSON Schema 校验通过了,但下游消费者因语义漂移拒绝了你的输出

· 阅读需 11 分钟
Tian Pan
Software Engineer

JSON Schema 验证的是输出的形状(shape)。它并不验证该形状内数值的含义。在长达 9 个月的时间里,你的 AI 流水线产生的每一条输出都顺利通过了校验,监控显示 Schema 合规率为 100%,你的团队也理所当然地认为符合 Schema 的响应在契约层面就是正确的。接着,一次模型升级发布了,每一条输出依然能通过校验,但你的 Slack 告警频道却在一夜之间从每天 50 条消息飙升到了 800 条。

Schema 没有出问题,出问题的是其内部数值的分布。这就是大多数 AI 团队在生产环境中发现的鸿沟:JSON 契约是一个类型系统(type system),而非行为系统(behavior system),而下游消费者一直依赖于某种契约从未被要求强制执行的数值分布。

KV 缓存预热 Cron 任务只在蓝环境运行而从未进入绿环境,原因竟是主机绑定从未迁移

· 阅读需 12 分钟
Tian Pan
Software Engineer

事故复盘将十二天前的一次部署确定为支出增加 3.6 倍的原因,而当时在场的参与者中,没有一个人在变更发布时参与其中。部署过程非常常规:蓝绿切换,流量按计划转移到绿色环境,蓝色环境停用,流水线变绿,发布工程师关闭了工单。生产环境的 SLO 都没有触发。应用层的告警也没有响起。系统运行得完全符合设计。

原本的设计是一个每五分钟运行一次的 Cron 任务,它每五分钟针对稳定的系统提示词前缀 (system-prompt prefix) 预热提供商的 Prompt 缓存。这种预热为团队在冷启动时带来了 91% 的缓存命中率,并在每个会话的第一次请求中获得了大约 4 倍的成本优势。该 Cron 任务是一年前首次引入蓝绿模式时编写的,其主机选择器 (host selector) 被固定在蓝色池 (blue pool),以避免在重叠窗口期间运行两次预热。当绿色环境变成活跃环境而蓝色环境消失时,该 Cron 任务失去了它的主机,并从“每五分钟运行一次”悄无声息地转变为“永不运行”。随着提供商缓存的 TTL 使预热的前缀过期,缓存命中率在接下来的 36 小时内逐渐下降。成本仪表盘计算的是每日窗口内的平均单次请求成本,平滑了趋势,直到下一个计费周期让问题变得显而易见。

供应商移除的 Logprobs 字段如何静默地破坏了你的置信度路由

· 阅读需 13 分钟
Tian Pan
Software Engineer

在事故复盘报告中,最昂贵的代码行反而是没人写出的那一行:一个字段缺失的 200 OK。原本应该将难题上报(Escalate)给更强模型的路由,在整整六周的时间里上报的流量为零。成本看板在欢庆,质量看板却在下滑——但仅限于那组被现有评估集低估的难题切片。直到客户投诉系统以前能正确处理的特定问题时,一切看起来都还像是场胜利。

起因是协议栈上层的一个响应结构变化。供应商的中级方案取消了逐 Token 的 logprobs,这在发行说明中被称作“特定层级的特性对等调整”。客户端收到的仍是有效的 JSON,HTTP 状态码依然是 200,响应中的模型标识符与请求中的模型标识符也完全匹配。唯一的变化是,路由用来做上报决策的字段消失了,而一年前在一次事故中添加的防御性默认设置,悄然变成了每个请求的生产环境默认行为。

供应商将你的模型标识符重定向到特定租户的微调模型,而其他人使用的却是基础模型

· 阅读需 12 分钟
Tian Pan
Software Engineer

客户支持团队升级了一个问题:“你的助手以前能正确处理退款资格问题。但上周开始出错了。”值班工程师调取了对话记录,在开发账号中使用相同的模型标识符回放了完全相同的提示词(prompt),得到了正确的回答,于是以“无法复现”为由关闭了工单。两周后,另一名客户提出了同样的投诉。工程师再次在同一个开发账号中进行回放,结果依然正确。团队开始归咎于没人做过的提示词更改。

请求中的模型标识符从未改变。响应字段中的字符串与请求字段中的字符串匹配。评估套件在六周内一直保持绿色。生产流量使用的模型权重与评估套件使用的模型权重是两套不同的集合,而且在该账号的整个生命周期中一直如此——直到过去这六周,它们变成了同一套权重,而团队注意到这一点仅仅是因为客户先发现了。

那个按会话分桶并导致 A/B 测试分群漂移的模型发布标志

· 阅读需 12 分钟
Tian Pan
Software Engineer

事后分析会以一句房间里所有人都希望是真的话开头:新模型在满意度上赢得了 4 %,p 小于 0.01,发布吧。一个月后,一项更冷静的分析发现,这种提升其实是一个混杂因素,模型表现实际上持平或略差,而团队在中间几周一直在争论哪个 prompt 更改“导致”了这一胜利。模型本身并没有导致任何结果。实验衡量错了对象,因为标志服务(flag service)和分析流水线在静默状态下对“分群(cohort)”的定义产生了分歧。

这是 A/B 测试中最昂贵的故障模式之一,因为系统中没有任何东西是损坏的。标志服务工作正常。实验追踪器工作正常。仪表板能正常渲染。统计数据是根据接收到的数据正确计算的。故障存在于三个组件之间的缝隙中,每个组件对身份都有不同的假设,而且除非你主动寻找,否则这个缝隙是不可见的。

配额窗口机制重写后,这批夜间脚本是如何拖垮你的交互流量的

· 阅读需 13 分钟
Tian Pan
Software Engineer

一个稳定运行了十个月的 cron 任务是你系统中最危险的任务,因为其中的代码没变,你的代码也没变,唯一改变的是别人发布的、你们团队没人读的发行说明(release notes)中的一句话。那个每晚在 00:05 UTC 启动、在十分钟内清空工作队列并重新进入休眠状态的每晚 embedding 刷新任务,曾是教科书般的范例。它通过在用户醒来前占用几分钟刚重置的每分钟配额,并在当天的剩余时间内保持在每日配额之内,从而与白天的交互式流量和谐共存。接着,服务商重写了每日窗口的核算方式,保持了每分钟窗口不变,并保留了你客户端测试的所有签名。批处理任务依然稳定运行。但每晚 00:13 UTC,交互界面开始返回 429 错误。团队一直在追查一个根本不存在的、本应在一周后才开始的上游维护窗口。

Bug 从不在你的代码里。Bug 在于“每日限制”不再是前一天的意思,而你的调度程序固定在了一个与旧定义对齐的时钟边界上。这篇文章讨论的是:速率限制核算(rate-limit accounting)作为一种服务商可以在不破坏任何签名的情况下修改的契约;两个独立正确的调度是如何组合成拒绝服务模式的;以及如何通过架构手段让 cron 任务不再是一个连接到别人时钟上的定时炸弹。

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% 流量,现在改按超额费率计费。财务部门通过仪表板追踪了三周的突发增长,最后才有人读到季度中期的定价补充协议,并在脚注中发现了这一重新定义。

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