跳到主要内容

299 篇博文 含有标签「observability」

查看所有标签

Agent 循环容量计算:为什么你的预置吞吐量只有你想象的一半

· 阅读需 13 分钟
Tian Pan
Software Engineer

我曾合作过的一个团队发布了一个他们称之为 “小规模” 的功能:一个供几百名分析师使用的内部研究助手。他们的容量模型认为一次用户请求等于一次模型调用,因此他们根据峰值用户 QPS,并预留了标准的 30% 突发余量来设定预置吞吐量(provisioned throughput)的大小。发布当天,他们在不到一小时内就遇到了 429 错误,原本应该只消耗 40% 预留容量的流量却让容量达到了 100% 的饱和状态。事后复盘发现了一个之前没有人算进去的数字:平均每个请求触发了 11 次模型调用,而不是 1 次。

这是我在 Agent 推广过程中看到的最常见的容量估算失误。其中的数学逻辑并不复杂,失败模式也并不罕见。团队问错了单位问题——他们以用户请求为单位进行规划,而计费表是按模型调用次数计次的——他们支付真金白银买下的预留容量,在一种如果换成聊天产品本应被视为轻负载的压力下化为乌有。

智能体状态差异对比 (Agent State Diff):为什么肉眼对比两条追踪路径无法规模化

· 阅读需 11 分钟
Tian Pan
Software Engineer

一个回归错误流入了生产环境。团队选取了导致失败的输入,针对上周的提示词进行回放,却得到了不同的输出。现在他们必须查明原因——而答案埋藏在 3 MB 大小的文本差异、分歧的工具调用序列以及被打乱的检索块中,人类根本无法有效地进行比对(diff)。于是,他们将两份记录粘贴到左右分栏的查看器中,滚动查看了二十分钟,得出结论“模型今天感觉不太一样”,然后发布了一个并没有解决根本原因的热修复,因为他们从未找到真正的原因。

这就是 Agent 状态差异问题,也是通用工程工具在处理 Agent 系统时失效的首要环节。传统的回归二分查找(bisect)针对的是确定性代码:相同的输入产生相同的输出,git bisect 遍历历史记录,直到你找到破坏代码的提交。但 Agent 的运行不是确定性的,输入也不仅仅是一个字符串,其“历史”是一个多轴的包(envelope)——模型快照、采样配置、检索到的上下文、工具目录、框架标志——其中任何一个变量都可以独立地改变行为。

审计追踪的不匹配:当用户、智能体和工具各有各的日志时

· 阅读需 12 分钟
Tian Pan
Software Engineer

一名监管人员给你发了一封邮件,只问了一个问题:该用户是否授权了这笔交易?六小时后,三名工程师正在聊天频道里,试图将聊天界面的对话日志、规划代理(planner agent)的推理追踪以及工具的 API 记录关联起来。聊天日志记录了轮次 ID(turn ID)和用户可见的消息,但没有工具调用的细节。规划器追踪记录了工具调用的记录,其时间戳与聊天日志相差几百毫秒。工具日志记录了 API 调用及其自身的关联 ID(correlation ID),而该 ID 在代理的记录中无处寻觅。下游服务的日志则有另一个 ID,且没有回溯链接。团队最终通过关联用户 ID 和大致的时间戳重建了答案,祈祷没有什么关键信息因错过一个轮次而产生偏差,最后向法务部门提交了一份 PDF 文件。

这就是审计追踪不匹配(audit trail mismatch)。每一层的负责人(owner)都认为自己的日志没问题——而且从单体来看,它们确实没问题。缺失的产物是那个本应存在的“关联视图(joined view)”,并且没人为它的缺失负责。团队只有在发生事故、客户升级投诉或监管机构强制要求关联数据时,才会发现它并不存在。

上下文膨胀:你无法用 Grep 搜寻的 AI 内存泄漏

· 阅读需 14 分钟
Tian Pan
Software Engineer

一个长时间运行的智能体(agent)会话最初以 2K 上下文开启,现在却在为 40K token 的“死状态”买单。第三轮的检索结果、智能体早已跳过的目录列表、工具调用返回的 JSON 转储(即便其答案只是一个整数)—— 所有这些都在随后的每一次推理调用中如影随形,全额计费,并拖累注意力。这种模式在结构上与内存泄漏完全一致:无引用的数据无限制增长。但没有剖析器(profiler)能发现它,因为泄漏并不存在于进程内存中。它存在于对话历史里,而大多数智能体框架在发布时都没有配备回收机制。

成本同时体现在两个地方。token 账单呈二次方增长 —— 一个 20 步的循环,每一步贡献 1,000 个 token,累计产生约 210,000 个输入 token,而不是 20,000 个,因为之前的每一轮对话都会在后续的每一次调用中重新计费。而且模型本身也开始退化:当积累了 50K token 的噪声时,即使是拥有 1M token 窗口的模型,在实际任务上的准确度也会出现两位数的下降。你在花更多的钱,让模型更差地去思考它在三轮前就已经解决的问题。

昼夜延迟:为什么你的 AI 功能在东部时间上午 9 点最慢

· 阅读需 10 分钟
Tian Pan
Software Engineer

在上个季度的某个时候,你团队的一名工程师在 Slack 上发了一个帖子,开头是“模型变慢了”。他们展示了一张图表:你的助手功能的 p95 延迟从早上 7 点开始稳步攀升,在东部时间上午 10 点左右达到顶峰,午餐期间处于平台期,并在下午 5 点后悄然恢复。这种形态在第二天、第三天不断重复。团队追溯了他们的部署记录,指责了分词器(tokenizer)的更改,接着是上下文长度的退化,最后发现没什么是特别确定的。修复方案从未落地,因为 Bug 根本不在你的代码里。

顶尖模型提供商运行着共享的推理集群。当你的用户醒来时,北美其他地区也醒了,再加上欧洲的下午,以及每一家购买了相同 API 的公司的每一个内部工具。提供商端的队列深度翻倍,GPU 竞争加剧,你的 p95 延迟也随之翻倍——而你的代码库没发生一行代码变更。这是你技术栈中最可预测的生产事故,但几乎没有团队会为此建立仪表板。

隐藏的 SDK 重试机制:为什么你付了两倍的钱却浑然不知

· 阅读需 12 分钟
Tian Pan
Software Engineer

打开 OpenAI Python SDK 的源代码,你会发现一行安静的代码:DEFAULT_MAX_RETRIES = 2。Anthropic SDK 也采用了同样的默认设置。大多数 TypeScript SDK 也是如此。两次重试,指数级退避(exponential backoff),在连接错误、408、409、429 以及任何 5xx 错误时自动触发——这些都在你的代码看到失败之前就执行了。你没有配置它。你没有选择加入。你通常甚至不知道它的发生,因为你的应用记录的指标是 request_count(请求数),而不是 attempt_count(尝试数),并且你的追踪器(tracer)唯一能看到的 span 是 SDK 在最后一次尝试后关闭的最外层 span。

这在大多数情况下都没问题,直到出问题为止。如果在该 SDK 调用之上再添加一个应用级的重试装饰器——那种每个团队在遇到第一个 429 错误后都会写的代码——你就构建了一个 3x3 的风暴:SDK 尝试三次,你的包装层围绕 SDK 又尝试三次,在服务商降级期间,一个单一的用户请求会扇出为九次推理调用。服务商的账单会计算每一次尝试。而你的仪表盘只记录了一次。当最终有人进行账实对账时,那将是一场谁都不会喜欢的季度末谈话。

服务商侧安全漂移:当你的产品在未发布的情况下发生回退

· 阅读需 10 分钟
Tian Pan
Software Engineer

周二还能用的提示词(prompt),到周四就返回了“我无法提供帮助”。CI 评估依然是绿色的。你配置中的模型名称没变。提示词在字节层面完全一致,在源码控制中也经过了哈希处理和固定。然而,一个围绕新出现的拒绝回答(refusal)的客户支持线程正在形成——AI 团队在两周内都不会察觉到这一点,因为它必须经过一级支持、分类,最后才落到能读取追踪信息(trace)的人手中。

这就是服务商侧的安全漂移(provider-side safety drift),它是当今生产环境 AI 中构建最不完善的监控缺口。前沿服务商会以不在你发布日程上的频率,在服务端调整安全过滤器、拒绝阈值和内容分类器。你的团队没有订阅这些变更,通常也没有发布说明。而且这种退化是具有非对称性的,以一种确实难以察觉的方式呈现:正当意图的拒绝率悄悄爬升,而你认为服务商会过滤的有害查询却开始悄悄溜过。边界在两端独立移动,且毫无预警。

拒绝审计:为什么单一拒绝率掩盖了一半的失败分布

· 阅读需 11 分钟
Tian Pan
Software Engineer

打开任何生产环境 LLM 功能的安全仪表盘,你都会看到拒绝率(refusal rate)被绘制成一条单线,并带有颜色标记:下降是坏事,上升是好事。这背后的隐含逻辑是:拒绝是系统对不该做的事情说“不”,因此拒绝率越高,产品就越安全。这种说法只反映了事实的一半,而缺失的另一半,正是已部署助手中大多数无形质量损伤的根源。

拒绝率是一个双面分布。右尾是安全团队痴迷的部分:模型同意编写恶意软件、伪造药物剂量或生成政策明确禁止的内容。左尾则是相反的失败——错误拒绝(false refusals),即模型因为某些表面特征与禁止类别模式匹配,从而拒绝了良性请求。客户询问如何对费用提出异议,却收到“我无法提供财务建议”的样板回复。护士询问药物相互作用,却被引导至“请咨询医疗保健专业人员”。开发者询问如何解析邮件头,却因为提示词中包含 “exploit” 一词而被拒绝。

会话边界问题:计费、评估和记忆的对话终点在哪里

· 阅读需 12 分钟
Tian Pan
Software Engineer

三个团队正在查看同一个事件流,每个团队都有一个名为 session_id 的列,但每个团队对什么是“会话”都有不同的定义。计费(Billing)继承了来自认证库的 30 分钟空闲窗口。评估(Eval)从聊天机器人框架中继承了“直到用户说‘再见’或停止打字 10 分钟为止”的定义。记忆(Memory)则使用 UI 在用户点击“开启新聊天”时生成的线程 ID —— 而大多数用户从不点击这个按钮。三列数据,三种语义,一个汇总仪表盘,以及三个共用一个根因但互不相关的 Bug。

这就是会话边界问题(session boundary problem)。它看起来像是一个埋点琐事,但实际上是一个披着基础设施外衣的产品问题:一段对话在哪里结束?坦诚的回答是没有单一的标准答案 —— 计费会话、评估会话和记忆会话并不是同一种对象 —— 如果一个团队选择了一个默认定义并让另外两个团队继承它,那么他们交付的就是具有相同根因的计费纠纷、评估偏见和内存泄漏。

小模型,大账单:为什么单 Token 成本更低反而更贵

· 阅读需 10 分钟
Tian Pan
Software Engineer

由财务主导的“切换到更小模型”的指令,是让你的 LLM 账单季度环比增长最可靠的方式之一。采购团队盯着的仪表盘——单次调用成本、每次请求的平均 token 数——一直在下降。与此同时,发票金额却在不断攀升。当有人终于把这两者对上账时,团队已经花了六个月的时间进行提示词(prompt)迭代,以补偿那个在任务处理上表现更差的模型,而且团队已经陷得太深,如果不承认最初的切换是个错误,就无法走回头路。

错误不在于定价,而在于计量单位。当推理深度、重试次数和提示词大小都随模型而异时,单 token 价格是一个具有误导性的维度。正确的指标是“单次成功完成所需的 token 数”,在这个维度上,更便宜的模型往往会输。

快照追踪测试:将生产环境追踪作为你的回归测试套件

· 阅读需 12 分钟
Tian Pan
Software Engineer

大多数团队作为回归测试套件运行的评估集,是由一名工程师在项目第三周手工挑选的。到了第六周,因为没人想在发布前动它,它就被冻结了;而到了第九个月,它正被用来拦截部署。产品已经调整了两次。用户群翻了三倍。LLM 在生产环境中实际遇到的案例与那个冻结的测试集重合度可能只有 40%。当测试集通过时,没人相信它;当它失败时,没人知道是真实的失败,还是案例已经过时。团队写了一份提议“v2 评估集”的文档,却从未真正动手。

与此同时,系统在生产环境中处理的每一个请求都已被记录在追踪后端中。每一个提示词、每一次工具调用、每一项中间输出、每一次拒绝、每一次重试——所有这些都存储在对象存储中,按时间索引并带有 span 标签,随时准备回放。团队所能拥有的最高保真度的测试语料库已经在磁盘上了。他们却从零开始构建了一个评估集,而不是从中读取。

停止序列的“自毁”陷阱:当用户输入与分隔符发生冲突

· 阅读需 12 分钟
Tian Pan
Software Engineer

一位用户将一段 Markdown 粘贴到你的支持代理中。他们粘贴内容中的第一个标题是 ### Steps I tried。你的提示词模板(prompt template)使用 ### 作为停止序列(stop sequence)。模型尽职地读取了用户的输入,开始回答,并生成了 ### 作为其结构化响应的一部分——结果 API 返回了两句自信的回复,随后便是沉默。工单以“模型质量退化”的名义进入你的队列。其实不然。修复方法只是网关中的一行代码。

停止序列是生产级 LLM 技术栈中极其关键却又常被忽视的调节开关。它们通常是在最初编写提示词的那一周选定的,那时输入还是整洁的工程示例,还没有人粘贴过 JIRA 工单的堆栈信息。十二个月后,用户内容的分布已经远远超出了提示词作者的想象,曾经整洁的分隔符现在变成了潜伏在每三百个用户粘贴中就有一个的隐患。没有任何告警。评估套件(eval suite)依然能够通过。受影响部分的 CSAT 指标下降了 0.5 分并维持在那里。

这不是模型的问题。这是一个伪装成模型问题的输入契约(input-contract)问题,它的形态类似于典型的分布式系统 Bug:为一方的内容分布选择的分隔符被强制应用于另一方的内容分布,且在边界处没有任何监控。