跳到主要内容

252 篇博文 含有标签「reliability」

查看所有标签

当两个 Agent 共享一个工具:多 Agent 系统中的并发 Bug

· 阅读需 11 分钟
Tian Pan
Software Engineer

当你输入“启动另一个智能体来并行处理”的那一刻,你就已经成为了一名分布式系统工程师。你可能没有注意到。框架让它变成了一行代码的改动,演示运行良好,延迟也降低了。但在底层,你刚刚引入了两个在没有协调的情况下读写共享状态的进程 —— 而困扰了数据库领域五十年的每一个竞态条件 (race condition)、更新丢失 (lost update) 和脏读 (dirty read),现在都潜伏在你的智能体堆栈中,伺机而动。

这种情况之所以棘手,是因为故障看起来不像并发 Bug,而像是某个智能体出错了。输出在语法上是有效的,流水线显示绿色,没有抛出异常 —— 然而,客户被收取了两次费用,或者文件丢失了一半预期的内容,又或者一个智能体自信地根据另一个智能体已经覆盖的数字采取了行动。你去调试那个“愚蠢的智能体”,发现它的提示词 (prompt) 没有任何问题,因为提示词根本就不是问题的症结所在。

停止并非一种状态:为什么智能体需要类型化的终端原因协议

· 阅读需 11 分钟
Tian Pan
Software Engineer

打开一个 Agent 集群(fleet)的仪表板,你会看到一个干净的数字:完成率,94%。在它下方是一系列运行记录,每条都标记着两种状态之一 —— 正在运行(running)或未在运行(not running)。那 6% “未在运行”的记录看起来完全一样。其中一些完美地完成了任务。一些在离完成还差两步时达到了步骤限制。一些捕获到了工具错误并放弃了。一些正确地判定任务是不可能的。还有一些则干脆断了思路,停止输出 token。

你的监控无法区分这些情况。它只知道流程不再运行了。它不知道 为什么,而“为什么”正是你在决定是否要呼叫(page)值班人员时唯一关心的事情。

你的智能体假设存在的“撤销”按钮

· 阅读需 10 分钟
Tian Pan
Software Engineer

观察一个 Agent 思考多步任务的过程,你会注意到一些熟悉的东西:它的规划方式就像你调试代码一样。尝试一种方法,观察结果,如果错了,就撤回并尝试另一种。Agent 将其计划描述为一棵选项树,它可以探索、剪枝和重新访问。这种心智模型在代码沙箱中是正确的,因为在那里的每个操作都有隐式的撤销功能。但在 Agent 接触到现实世界的那一刻,这种模型就错得离谱且危险。

发出的邮件无法撤回。扣款的银行卡在没有退款流程、手续费以及已经看到通知的客户的情况下,是无法撤销扣款的。除非有人设置了软删除,否则被删除的数据行就彻底消失了。一条发布的 Slack 消息可能已经被阅读了。Agent 的规划模型没有原生的“单向门”概念——即一旦采取行动,就再也无法假装它从未发生过。

这不是一个模型智能问题。即使是更聪明的模型仍然不知道你的哪些工具是可逆的,因为可逆性不是操作本身的属性。它是操作所落地系统的属性。你必须明确告诉它。

向量索引存在一个没人定义的陈旧度 SLO

· 阅读需 11 分钟
Tian Pan
Software Engineer

一个用户询问你的智能体(agent)企业版的当前价格档位。智能体检索了一个分块(chunk),读取并回答:“每月 2,000 美元。” 信心满满,来源明确,格式优美。问题在于价格在四天前已经变了。智能体引用的数字在上周是真实的。它检索到的分块是在变更之前嵌入的,而索引还没有赶上进度。

没人决定让这种情况发生。没有设计评审说“智能体可以根据长达四天前的数据进行回答”。只是有一个每晚或每周运行的重新索引任务,以及一个随心所欲编辑内容的团队,而这两个时钟之间存在一个没人衡量的缺口。那个缺口就是一个服务等级目标(SLO)。无论你是否写下来,它都存在。唯一的问题是你是有意设定它的,还是由于意外继承下来的。

当升级请求无人响应时:人机回环是一个人员配置问题

· 阅读需 11 分钟
Tian Pan
Software Engineer

每个智能体 (Agent) 架构图中都有一个标记为“上报给人类 (escalate to human)”的方框。它用一条整洁的箭头画出,既能让评审人员满意,又能让系统显得安全。然而,架构图从未展示过箭头另一端的人——他们是否存在、是否醒着,以及是否能在智能体耗尽耐心之前给出答复。

人机回环 (Human-in-the-loop) 被当作一种设计模式来推销。但在生产环境中,它的表现更像是一个人员配置问题。这种模式假设有人在随时待命;而人员配置的现实是,上报请求并不会在人类刚好有空时出现——它们有自己的“时间表”。比如凌晨 2 点,当夜间批处理任务触发护栏 (guardrail) 时出现的爆发式请求。或者是午餐时间,当一半评审员都不在座位上时产生的长尾延迟。又或者是细水长流般的请求量,在不知不觉中超出了那支在演示阶段看起来绰绰有余的双人团队——毕竟在演示阶段,智能体每天只处理 10 个请求,而不是 1 万个。

“我们有上报路径”与“上报得到响应”之间的鸿沟,正是智能体系统发生故障且评估 (eval) 无法捕捉的地方。评估衡量的是智能体是否正确地发起了上报,而从未衡量过是否真的有人在那里。

谁该为模型的错误买单:在智能体产品中设计责任机制

· 阅读需 10 分钟
Tian Pan
Software Engineer

一个智能体订错了机票。它给错误的客户发送了道歉邮件。它编写了一个数据库迁移脚本,删掉了一个仍有三个服务在读取的列。在每种情况下,模型都生成了一个看起来合理的动作,执行了它,然后继续运行。而在每种情况下,都有人承担了真实的代价——改签费、受损的关系,或是凌晨 2 点的故障排查。

令人不安的地方在于:大多数 AI 产品对于“谁来承担代价”这个问题没有答案。在设计评审中,这个问题从未被提及。它在以后才会显现,以支持工单的形式,一个接一个地出现在支持队列中——因为客户听起来很生气,而客服代表没有可以遵循的政策,只能即兴提供 40 美元的额度。将此乘以每月几千张工单,单位经济效益就会悄然腐烂——不是因为一次戏剧性的失败,而是因为一个无人关注的缓慢漏洞。

“模型犯了错”不只是一个支持升级。它是一个计费事件。而在智能体时代生存下来的产品,将是那些在收到第一张愤怒的工单之前就为这类事件做好了设计的产品,而不是那些靠感觉即兴退款,直到毛利变为负值的产品。

Agent 记忆是一个没有失效策略的缓存

· 阅读需 10 分钟
Tian Pan
Software Engineer

现在每个智能体 (agent) 框架都将“长期记忆”作为核心功能发布,每个团队都将其视为百利而无一害的好事。智能体记住了用户的偏好、之前的决策、项目上下文以及上周收到的修正,因此每次会话的起始状态都比上一次更“热”。演示效果令人难以抗拒:用户说一句“按照我的喜好设置项目”,智能体就照办了。没有人问那个显而易见的问题,因为这一功能的叙事框架在刻意回避它。

问题是:这一切何时会变得不再准确?

记忆存储本质上是一个缓存。它保存着关于一个并非静止不变的世界的事实。智能体在 8 个月前记录了“用户偏好 Postgres”,但团队此后已迁移到了另一个数据库。智能体记得“用户在增长团队”,而用户在 3 月份已经调岗了。智能体存储了一个简洁的对话总结,但该对话的前提在两条消息后就被修正了。记忆层在提取这些信息时,其自信程度和新鲜感与今早刚写下的事实完全一致。我们花了 50 年的时间才意识到,没有失效策略的缓存就是一个正确性漏洞 (correctness bug)。然后我们构建了智能体记忆,并在没有这种策略的情况下将其发布了。

置信度分数税:为什么询问模型它有多确定比直接出错成本更高

· 阅读需 12 分钟
Tian Pan
Software Engineer

在每个 AI 功能的演进过程中,审阅者总会提出一个听起来很合理的问题:“我们能不能让模型告诉我们它的置信度(confidence),这样我们就可以把低置信度的回答路由给人工或备选方案?”这听起来像是一份免费保险。你在输出 schema 中添加一个 confidence 字段,模型尽职尽责地填好它,现在你就有了一个可以调节的旋钮。发布吧。

那个旋钮并不是免费的,更糟糕的是,它通常没有连接到任何实际逻辑上。置信度数字只是模型乐于生成的一个 token 序列,模型并没有义务让它具有实际意义。团队支付真实的 token 和延迟来获取它,却从不检查它是否与正确性相关,然后根据它路由生产环境的流量,就好像 “0.9” 真的代表 90% 的可靠性评估一样。它就像一个用螺栓固定在仪表盘上的压力表,但玻璃后面其实什么也没连。

这篇文章讨论了两个没人定价的成本:生成置信度字段本身的单次请求税,以及信任一个未校准的数字来做路由决策所带来的更巨大的成本。

改变答案的重试:针对非确定性 LLM 调用的幂等键

· 阅读需 10 分钟
Tian Pan
Software Engineer

你构建过的每个分布式系统都依赖于一个隐形的假设:超时后的重试是安全的。操作是幂等的,因此如果客户端放弃等待并重新发送,最坏的情况也只是重复工作,并最终收敛到相同的状态。两个 PUT 请求落地同一行。两个 DELETE 请求留下同样的空缺。重试只是伪装成第二次尝试的“无操作”(no-op)。

LLM 调用打破了这一假设,而且是悄无声息地打破。重试并不会重新获取相同的答案 —— 它会采样一个新的答案。当客户端因为响应在传输中丢失而在网络层超时,但提供商实际上已经完成了生成时,重试会产生第二个、不同的答案。现在,对于一个逻辑请求,存在两个不同的输出,而你的技术栈中没有任何部分知道哪一个是权威的。

这并非罕见的极端情况。在模型背后运行超时机制的从业者报告称,即使底层调用最终成功,仍有 5–10% 的请求会触发完整的超时加重试循环。其中的每一次重试都是一次抛硬币,而你的系统从未被设计成去裁定这种结果。

先返回 200 然后失败的流式响应:中途错误如何破坏你的 SLO

· 阅读需 11 分钟
Tian Pan
Software Engineer

你的可用性仪表盘显示 99.95%。你的用户却说回答在句子中间停住了。两者都是正确的,而这正是问题所在。

HTTP 时代的可靠性技术栈建立在一个假设之上:状态码在请求结束时到达,并总结其命运。200 意味着成功。5xx 意味着重试。负载均衡器计算比例,SLO 仪表盘进行聚合,告警则根据消耗率(burn rate)触发。技术栈的每一层都会读取并信任这个 Header。

流式传输(Streaming)反转了这一假设。当你的服务器刷新第一个 token 的那一刻,它就已经承诺了一个 200 状态码。在那之后发生的任何错误 —— 在第 400 个 token 时的供应商超时、段落中间触发的内容审查过滤、TCP 连接断开、格式错误的工具调用(tool-call)片段 —— 都是在结果已经判定且无法撤回之后发生的。请求失败了,但状态码却说它成功了。而你的可靠性工具中没有任何一部分是为了察觉这种差异而构建的。

AI 网关:那个没人点名的单点故障 (SPOF)

· 阅读需 12 分钟
Tian Pan
Software Engineer

这种说辞听起来很负责任。“我们别在各处硬编码 OpenAI —— 我们在前面加一层薄薄的抽象,这样以后如果需要,我们可以随时更换供应商。”两年后,那个“薄薄的抽象”变成了一项拥有自己部署流水线、SRE 值班表、拦截糟糕 Prompt 的评估门控、每年节省七位数资金的语义缓存、带有针对特定供应商退避机制的重试策略、所有仪表盘都依赖的可观测性架构,以及一个存放着六家模型厂商凭证的密钥库的服务。公司里的每一个 AI 功能最终都汇聚于此。

它也几乎是在无意间,成为了整个技术栈中爆炸半径最大的单点故障(SPOF)。当主要 LLM 供应商宕机时 —— 2025 年,OpenAI 自 1 月以来被记录了 294 次停机事件,而 Anthropic 仅在 12 月就记录了 184.5 小时的总客户影响 —— 网关会自动绕过它,大多数用户甚至察觉不到。而当网关本身挂掉时,每个产品中的每个 AI 功能都会同时停止工作,原本应该触发的故障转移根本没有机会执行,复盘报告的开头往往是:“我们为了隔离供应商宕机影响而构建的抽象层,本身成了那场宕机。”

随时间波动的质量偏移:为什么你的 AI 功能在东部时间上午 10 点表现不同

· 阅读需 11 分钟
Tian Pan
Software Engineer

太平洋时间凌晨 2 点,你的评估套件(eval suite)在平静的提供商环境下全绿通过。上线前一晚 11 点,QA 进行了冒烟测试。功能正式发布。到了周二上午 10 点(东部时间),你的 p95 延迟比你签字认可的仪表盘高出了 40%,你的智能体(agent)在一个包含六步的计划中丢掉了最后一个工具调用,而你的支持信箱塞满了听起来如出一辙的工单:“今天早上 AI 表现很奇怪。” 谁都没错。模型也没错。错的是评估集 —— 它从未见过饱和的提供商环境,因此对于当队列深度翻倍、截止时间预算(deadline budget)崩溃时功能会如何表现,它无法给出任何参考建议。

提供商负载不仅仅是一个伴随质量副作用的延迟问题。它是你的模型和智能体循环接收到的输入的一种分布偏移(distribution shift),而你所信任的每一个质量信号,都是建立在这个分布中错误的那一截之上的。解决办法不是换一个更快的区域或更好的模型。解决办法是停止假装你的评估框架是在与你用户相同的世界中进行采样。