跳到主要内容

299 篇博文 含有标签「observability」

查看所有标签

你的代码从未检查过的 Finish Reason

· 阅读需 11 分钟
Tian Pan
Software Engineer

你的处理器(handler)做对了一切。HTTP 状态码是 200。Body 解析成功。文本字段里有内容。你增加了 responses_succeeded 的计数,将消息追加到对话中,把 JSON 返回给客户端,然后继续下一步。用户得到的是一个在句中戛然而止的句子,一个伪装成正常答案的经过编辑的回复,或者是一个被措辞为补全的礼貌拒绝。你的仪表盘对这一切一无所知。供应商已经告诉了你,但你没有读取那个字段。

每个主流的推理 API 都会在文本之外返回一个停止信号:OpenAI 称之为 finish_reason,Anthropic 称之为 stop_reason,Gemini 称之为 finishReason。这个字段很小,每个响应对应一个枚举值。它也是模型用来告诉你刚才发送的响应是一个完整答案还是一个碎片的唯一带外(out-of-band)通道。将其视为无关紧要的装饰,与忽略 HTTP 状态码属于同一种类型的 Bug —— 不同之处在于,你的监控系统在十年前就能捕捉到 HTTP 错误,但对这个错误却无动于衷。

Agent 循环从搜索框偷走的延迟预算

· 阅读需 13 分钟
Tian Pan
Software Engineer

发布指标看起来很干净。回答质量提升了,引用率上升了,评估套件全绿。那个用基于 Agent 的检索器替换旧关键词搜索的团队发布了产品,赢得了胜利,然后转向了下一个项目。六周后,有人注意到该界面的周活跃用户数下降了 12%,但没人能找到性能回归。其实并没有回归。Agent 运行正常。用户离开是因为以前在 200 毫秒内就能给出答案的搜索框,现在需要 4 秒钟,而发布回顾中没有任何内容涉及到这方面的预算。

这就是延迟预算转移问题,而且几乎没有人会画出能捕捉到这一问题的组织架构图。搜索框不仅仅是一个函数调用。它是与用户神经系统签订的一份为期三十年的契约:输入、查看结果、扫视、点击。200 毫秒的响应并不是某个仪表盘上的性能指标——它是当结果送达时,用户的注意力仍然留在屏幕上的原因。当搜索框背后的团队用 Agent 循环替换关键词索引时,函数调用表面看起来是一样的,但新调用的 SLA 处于一个完全不同的范畴。延迟预算从拥有索引的团队转移到了拥有 Agent 的团队,又从拥有 Agent 的团队转移到了用户身上,而唯一参加会议的只有用户。

那个因为模型拒绝处理难题而提升的成功指标

· 阅读需 11 分钟
Tian Pan
Software Engineer

你在周二升级了模型。到了周五,“任务完成率”仪表盘从 71% 爬升到了 78%。领导层注意到了。有人在全员大会上截图展示了它。两周后,客服部门悄悄反馈说,特定一批复杂工单的流失率翻了一番。没人把这两件事联系起来,因为从纸面上看,智能体(agent)变得更好了。而现实情况是,新模型只是变得更擅长拒绝了。

这就是指标脱钩问题,也是以 LLM 为动力的产品欺骗其开发者的最昂贵方式之一。你的成功率并没有衡量你认为它在衡量的东西。它衡量的是“模型尝试的内容”与“模型尝试时做对的内容”的交集。当模型升级、提示词更改或安全调优(safety-tuning)改变了“尝试”的边界时,你的分子和分母会同步移动——即使在用户感知的质量一落千丈时,该比率也可能上升。

那些你的真实用户永远不会表现出的合成评估

· 阅读需 11 分钟
Tian Pan
Software Engineer

有一类评估失败是任何仪表盘都捕捉不到的,因为它表现为成功。分数逐周攀升。评审模型认可答案。回归测试保持绿色。与此同时,支持团队记录到用户反馈的质量在缓慢下滑,销售团队听到“它不太明白我的意思”,而工程团队中没有人能复现这些投诉,因为在评估集上尝试的每个例子都能通过。评估集和用户生活在不同的分布中,而评估集是两者中更“完美”的那一个。

其中的机制很简单,而且就隐藏在显眼处:编写评估提示词的模型与受测模型是同胞关系,而同胞共享先验知识。它们磨平同样的棱角,偏好同样的措辞,遗漏同样类型的格式错误输入。评估集验证的是在一个生成器所构想的用户世界里的表现。你真实的用户住在别处。

那个直到触发时你才察觉的 Token 预算

· 阅读需 11 分钟
Tian Pan
Software Engineer

你的团队与推理提供商协商了月度 Token 配额。合同规定了上限。提供商门户的仪表板显示昨天的使用情况,存在一天的延迟。API 本身返回每分钟速率限制头——anthropic-ratelimit-tokens-remainingx-ratelimit-remaining-requests——而对于你实际需要规划的月度配额桶却只字未提。你的智能体集群没有机制在预算耗尽时减速,因为实时到达的唯一信号是 429 错误——而这个信号在预算已经用完后才出现,且伪装成重试逻辑通常会忽略的瞬时错误。

这是一个与速率限制(rate limiting)性质不同的问题。速率限制是一个快速波动的节流阀,消费者必须在几秒钟内做出反应;响应头告诉你桶里还剩一千个 Token,并在 40 秒内补满,一个编写良好的客户端会退避并重试。月度配额则是一个缓慢变化的预算,消费者必须以周为单位进行规划。这两者之所以容易混淆,是因为它们共享错误代码,有时甚至共享同一个仪表板,但它们需要不同的控制手段——而提供商公开的信息与消费者需求之间的差距,正是本月最严重事故的导火索。

你的 Agent 悄然适应了的那次工具版本升级

· 阅读需 11 分钟
Tian Pan
Software Engineer

一个下游搜索服务在周二下午发布了 v2.3.2 版本。发布说明提到重命名了一个状态字段,新增了一个可为空的 confidence 值,并重新排列了结果包中的数组。CHANGELOG 中没有任何内容被标记为破坏性变更。提供商自家的客户端库通过一个小版本更新消化了这些变化。你团队的 HTTP 集成通常会在一小时内记录下反序列化错误。但你的智能体——那个通过该搜索工具路由客户问题的智能体——并没有报错。它继续回答。问题依然得到解决。仪表盘依然是一片绿色。

六周后,有人注意到“缺货”回复在查询中的比例从 2% 攀升到了 11%。根本原因是 v2.3.2 的升级。重命名后的状态字符串从 in_stock 变为 available,而智能体——作为一个对文本进行灵活推理而非严格遵守模式(schema)的客户端——将旧令牌(token)的缺失解读为“无货”,然后将这一发现组织成乐于助人、语气自信但内容错误的客户消息。契约回归在消费者端被吸收了,而那里没有任何测试套件在监控。

这是传统的 API 规范(hygiene)从未被设计用来捕捉的故障模式。严格的客户端会大声报错。智能体则静默失效。你越是将智能体当作普通的 HTTP 消费者对待,这类 Bug 隐藏在看似正常的指标中的时间就越长。

不可信的 Trace Replay:为什么你的新模型评估在撒谎

· 阅读需 14 分钟
Tian Pan
Software Engineer

LLM 升级的标准流程往往具有单元测试那种令人安心的形态。捕获上周现有模型(incumbent model)的生产追踪数据(traces)。在候选模型(candidate model)上回放这些数据。对比输出差异(Diff)。如果不一致率低于某个阈值——比如 3% ——就发布。差异很小,仪表盘显示绿色,迁移看起来很安全。一周后,值班频道里充满了各种报告,称新模型在跨轮次对话中丢失上下文、调用工具时使用了无法解析的参数,并且自信地引用了早已从语料库中删除的文档。

回放并没有真正撒谎。它测量的是真实的东西。它只是测量了生产模型从未真正见过的上下文中的行为,而那个绿色的数字,只是一个除了在回放测试环境(replay harness)之外,在任何地方都不存在的分布上的置信区间。

止于供应商边界的链路追踪

· 阅读需 12 分钟
Tian Pan
Software Engineer

你做了追踪(tracing)工作。检索有 span。工具调用有 span。编排循环有 span。Trace ID 贯穿每一个内部跳跃,记录在 W3C 的 traceparent 请求头中,正如 SRE 手册所说。然后请求到达 messages.create,SDK 记录了一个名为 llm.call 的单一 span,接着你流水线中接下来的 2.8 秒在火焰图上变成了一个没有任何内部结构的黑色矩形。首个 token 出现前的 800 毫秒:不透明。之后的 2 秒解码过程:不透明。你的追踪无法得知网络、队列等待、Prefill 或单 token 解码在总耗时中所占的比例。

当客户报告“今天助手感觉很慢”时,你的仪表板可以证实这种缓慢,但无法定位它。你流水线中最昂贵的一分钟——以美元、p95 以及用户感知的延迟来衡量——发生在供应商的数据中心内部,而你签约时接受的合同几乎没有给你任何可见性。你正在为一个黑盒值班(on call)。

你的定时 Agent 有四个时钟,而你信任的是错误的那一个

· 阅读需 14 分钟
Tian Pan
Software Engineer

一个每日站会总结被安排在 UTC 时间 09:00。定时任务(Cron)准时触发。两秒钟后,一个工作节点容器组(Worker pod)启动。LLM 调用又耗费了四十秒的往返时间。模型在撰写总结时认为现在是去年的 2 月,因为那是其训练数据最后确信的时间点。工具层在 UTC 时间 09:00:42 根据挂钟时间(Wall clock)发送了 Slack 消息,模型从未提及具体日期,因为没人要求它这样做。消息进入了正确的频道,昨天的站会笔记被总结成了“今天的”,而且整整三周都没有人察觉。

这并不是任何单一组件的 bug。这是一种在四个不同的时钟之间、谁也没有写下来的契约,而这四个时钟都认为自己知道“现在”是什么时候。

那个用一小时反复重试同一个 400 错误的 Agent

· 阅读需 12 分钟
Tian Pan
Software Engineer

一个 agent 调用了某个工具。工具返回 400 Bad Request,错误体结构清晰得无可挑剔:{"error": "missing required field", "field": "email"}。Agent 的推理链一字不差地复述了错误,说"我需要在请求里加上 email 字段",然后发出了下一个工具调用——负载和上一次一模一样。框架的重试策略是多年前为不稳定 HTTPS 连接写的,把这个 400 当成瞬时错误,又发了一次。三次。八次。十五次。一小时后,这个 agent 烧掉了上下文、烧掉了预算、烧掉了限流额度,却从未发出过一个工具能接受的请求。

这种失败看起来像是模型的问题。其实不是。模型把错误读对了。是包在它外面的框架,没有给"修正"留任何落脚点。

把沉默当作同意的 ChatOps 机器人

· 阅读需 11 分钟
Tian Pan
Software Engineer

你的部署机器人已经上线九个月了。仪表盘显示消息量持续上扬,负面反馈率稳定在 2% 以下。负责它的团队把这解读为"已被采用"。然后,一位资深工程师顺口提了一句:他那个小组的人早在二月份就把那个频道静音了——他们对机器人发出的每小时摘要的信任程度,跟对厂商邮件 newsletter 差不多;他们受不了那种持续的嗡嗡声。机器人在对一个空房间说话,而指标却称之为"势头"。

这就是大多数 ChatOps 团队都会撞上、却几乎没人去度量的失败模式。当 Slack 或 Teams 里的机器人不再收到回复时,最轻松的解读是"智能体进入稳态了——用户不再需要跟它争论了"。诚实的解读通常恰好相反:用户在绕开它、把它静音,或者认定忽略提示比读它更省事。参与度图表无法分辨这两者。仪表化必须围绕一个前提重新设计:沉默是默认状态,而正确解读这种沉默才是真正的工作。

你的 Agent 没有留下的那本证据档案

· 阅读需 10 分钟
Tian Pan
Software Engineer

你的调用链路会记录每一个 token。它记录每一次工具调用、每一次重试、每一次检索延迟、每一个模型 id。看起来无所不包。然后,某个监管机构、某个客户、或者你自己内部的 incident 频道,抛出一个本该轻松回答的问题:模型在决策那一刻究竟看到了什么? 你这才发现,调用链路记下了"问题",却没记下模型当时正在看着回答的"答案"。

那次检索命中的 chunks 早就因为上周二的重建索引轮换出了向量存储。那次工具响应是一段流式 payload,你只保留了最终态摘要,因为完整流会让账单翻三倍。那段 system prompt 是运行时按一个 feature flag 拼装出来的,而那个 flag 此后又翻转了两次,你的 flag 服务并不按时间戳保留历史值。你对发生了什么有完整的可观测性——调用图、token 数、延迟。但你对模型当时在针对什么作答,一无所知。这道缺口,就是调用链路与决策记录之间的差别。大多数团队没意识到自己只造了两者中的一个。