跳到主要内容

330 篇博文 含有标签「observability」

查看所有标签

你的 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 数、延迟。但你对模型当时在针对什么作答,一无所知。这道缺口,就是调用链路与决策记录之间的差别。大多数团队没意识到自己只造了两者中的一个。

把自己调度进维护窗口的 Agent

· 阅读需 11 分钟
Tian Pan
Software Engineer

凌晨两点值班的资深工程师不会在 Sev-2 事故中跑 schema 迁移。他们不会在发布冻结开始前十分钟重新部署支付服务。他们不会在邮件服务商状态页飘红的时候发起一波营销邮件投放。这些都不写在 JD 里。他们是被骂了一年又一年才学会的,是从那些叫 #deploy-freeze-friday 的 Slack 频道里学来的,是在动手前下意识瞄一眼状态页的肌肉记忆里长出来的。这种上下文不在任何 runbook 里,因为没人觉得它值得被写下来。

现在把同样的活交给一个 Agent。Agent 有工具。Agent 有多步计划。Agent 拥有你愿意写进 system prompt 的每一条策略。Agent 没有的,是那种半下意识的觉察——这个世界现在正着火。所以它就执行计划了。干净利落,信心满满。一头扎进维护窗口。事后复盘里那句话会变成一个新的固定句式:"Agent 没办法知道。"

多模态追踪:当各种模态必须共享一个 ID

· 阅读需 12 分钟
Tian Pan
Software Engineer

一位用户拨通了你的客服 Agent。他们说话,Agent 倾听,用户在通话中途上传了一张错误截图,Agent 同时对图片和转写文本进行推理,最后通话以一封总结修复方案的邮件收尾。三天后用户投诉过来:修复没有生效,邮件也从未送达。你打开可观测性栈,发现三个独立 UI 里躺着三条互不相干的追踪。语音流水线给你一条 ASR 追踪。视觉流水线给你一段图片上传的 span。LLM 调用给你一条带 token 数和工具调用的聊天追踪。这些仪表盘里没有任何东西告诉你:它们其实是同一次对话。

这就是没人愿意写的那种复盘。不是因为数据缺失——每一个模态都老老实实记录了它该记录的东西——而是因为跨模态的"接合"从来就没建起来。每条流水线都从自家模型供应商默认配置里长出了自己的追踪约定,而把它们绑在一起的那一次对话轮次,只存在于设计这个 Agent 的那位工程师的脑子里。

一路重试穿过你限流器的 agent

· 阅读需 11 分钟
Tian Pan
Software Engineer

你的网关给每个 tenant 干净利落地强制执行每秒 100 次请求的限制。dashboard 显示每个 tenant 都舒舒服服地在那个上限之下。但模型 provider 寄来的账单告诉你,你的支出上限照样被打穿了。rollout 电话会议上没有人能给出一个干净的解释。

答案在于限流器和账单衡量的是不同的东西。当用户点击一个按钮时,限流器看到的是一次"用户请求"。而 provider 看到的是一次 planner 调用、三次工具结果反思、一次因更严格 JSON schema 触发的格式修正重试,以及一次最终综合——每一次都带着自己的内部重试预算,在瞬时 429 或 500 回来时就会触发。一次点击可以扇出成三十次模型调用。限流器只数到一次。桶以它被设计容量的三十倍漏水。

在 HTTP 边界上对 agentic 系统做限流,就像在高速公路入口立速限标志,而入口里面的车却在自我繁殖。除非限流器理解了这个循环,否则循环就会绕过它。

长出胳膊和腿的缓存提示词前缀

· 阅读需 11 分钟
Tian Pan
Software Engineer

六个月前,你的提示词前缀是 4,000 tokens。它稳定、缓存预热,几乎可以摊销到不计成本——系统指令的每次调用附加费,相比每次响应的成本,只是一个舍入误差。今天那个前缀变成了 11,000 tokens,你的缓存命中率从 92% 滑到了 31%,你的推理账单上升了 4 倍。团队里没有人能指出是哪个 PR 干的。没有一条 commit message 写着"将提示词增加 7,000 tokens"。每一次修改都很小,每一次修改都有理有据,每一次修改都干干净净地合入了。

提示词前缀长出胳膊和腿,就像地下室积攒纸箱一样。一个团队需要注入用户的订阅等级,这样 agent 才能解释套餐限制。另一个团队需要用户时区的今天日期,这样"明天提醒我"才能工作。第三个团队把当前 A/B 变体名硬塞进去,这样 eval traces 才能切片。市场团队加进了当前促销 banner,这样 agent 才能适时提及它。合规团队加进了功能标志清单,这样模型才能拒绝那些不在灰度名单里的用户访问 beta 功能。每一条都是一行的添加。每一条单独看都站得住脚。但加起来摧毁了你的缓存。

那个谁也不敢从注册表里删掉的死工具

· 阅读需 12 分钟
Tian Pan
Software Engineer

一个工具在你共享的 agent 目录里已经躺了十四个月。它是某位早已离职的工程师为两次组织重组前就被砍掉的工作流接上的,对接的后端服务的归属如今也没人说得清。这个工具定义占 380 个 token。它会出现在组织里每一个 agent、每一轮对话的系统提示中,因为没人能证明它没在被使用,而一旦证明错了,代价比永远扛着它还高。

这个工具就是那个没人敢删的数据库字段,就是那个日志早已轮转干净的定时任务,就是那条你 grep 不到任何引用、但因为 eval() 存在所以你不敢动的死代码。Agent 版本的这个问题更糟,因为持有成本不只是磁盘上的几个字节——它是用 token、用选择准确率、用安全攻击面付出的,而且是在你平台跑的每一次推理里付出。