跳到主要内容

278 篇博文 含有标签「reliability」

查看所有标签

Agent 的演练日:排演那些无法复现的故障

· 阅读需 11 分钟
Tian Pan
Software Engineer

传统的混沌工程建立在一个默认的假设之上:如果你两次注入同样的故障,你会得到两次同样的失败。停掉 pod,观察故障转移,修复漏洞,再次停掉 pod 以进行确认。整个学科——假设、爆炸半径、稳态指标——都假定系统具有足够的确定性,使得实验是可重复的。

智能体系统从根本上打破了这一假设。在智能体运行过程中注入工具超时,模型会重新规划路径——有时它会重试,有时它会换一个工具,还有时它会自信地编造一个从未获取到的结果。针对相同的提示词运行相同的故障,你最终会得到不同的轨迹,因为故障路径是通过一个随机规划器(stochastic planner)运行的。上周二你在生产环境中看到的故障永远不会以完全相同的形式再次发生。而这恰恰就是你必须对其进行演练的原因。

学习型系统的任意时间点恢复:那个没人做的备份

· 阅读需 12 分钟
Tian Pan
Software Engineer

询问任何基础设施团队,要求将生产数据库恢复到昨天下午 3 点的状态,他们会向你提供一份操作手册 (Runbook)、一个 RPO 以及一份预估时间。但如果询问同一个团队,要求将 Agent 恢复到昨天下午 3 点的状态——在它吸收一批中毒的记忆之前,在有人发布错误的提示词版本之前,或者在那个悄无声息地破坏了检索的重新索引之前——你得到的将是沉默。这并不是因为各个组件缺乏备份,而是因为没有人能说出“下午 3 点的 Agent”究竟意味着什么。

这是每个运行学习型 Agent 的团队都会面临的尴尬发现:你的数据库有快照,你的代码有 git,而你的 Agent——那个用户真正与之交互的东西——两者都没有。它的运行状态散落在向量索引、一堆记忆文件、提示词注册表和一套工具配置中,这些组件要么各自拥有独立的版本,要么根本没有版本。仅恢复其中任何一个都无法找回昨天的 Agent。你得到的只是一个逻辑破碎的大脑。

当硬件说谎时:静默数据损坏遇上随机性软件

· 阅读需 13 分钟
Tian Pan
Software Engineer

在你的推理集群中的某个地方,可能有一块芯片正在计算错误答案。不是崩溃 —— 而是错误答案。它通过了制造测试,通过了健康检查,但在指令序列、数据值、电压和温度的特定组合下,它会返回一个完全错误的数字。超大规模云服务商已经在大规模范围内记录了这一现象:大约每千台设备中就有一台会发生静默数据损坏(Silent Data Corruption),这一比率比我们过去担心的宇宙射线导致的比特翻转(bit flip)要高出几个数量级。

五十年来,软件拥有一种针对此情况的免疫系统:确定性(Determinism)。相同的输入,相同的输出 —— 因此你可以进行校验和(checksum)、回放,并与基准结果(golden results)进行对比,撒谎的硬件最终会被抓获。LLM 推理是第一个失去这种免疫系统的主要工作负载。当模型给出一个略差的答案时,是因为采样器的随机性,还是因为退化的 GPU 在你的 KV 缓存中翻转了比特?没人能通过检查来判断。一个不稳定的加速器可以在所有仪表盘显示正常的情况下,静默地拉低你的质量指标数周之久。

崩溃是承重性的:大语言模型 (LLM) 的容错性如何掩盖了破碎的数据契约

· 阅读需 12 分钟
Tian Pan
Software Engineer

五十年来,数据管道通过“宕机”来强制执行它们的契约。上游团队重命名了一个列,下游解析器抛出异常,任务崩溃,有人在凌晨 2 点收到告警,到了早上,契约要么被修复,要么被正式重新协商。没有人将其设计为一种治理机制。它只是源于这样一个事实:僵化的代码无法处理它预期之外的输入。崩溃就是执行。告警器就是审计追踪。

![](https://opengraph-image.blockeden.xyz/api/og-tianpan-co?title=%E5%B4%A9%E6%BA%83%E6%98%AF%E6%89%BF%E9%87%8D%E6%80%A7%E7%9A%84%EF%BC%9A%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%20(LLM%29%20%E7%9A%84%E5%AE%B9%E9%94%99%E6%80%A7%E5%A6%82%E4%BD%95%E6%8E%A9%E7%9B%96%E4%BA%86%E7%A0%B4%E7%A2%8E%E7%9A%84%E6%95%B0%E6%8D%AE%E5%A5%91%E7%BA%A6)

接着,我们让 LLM 坐上了消费者的位置,于是破损不再导致崩溃。

模型在读取格式错误的数据记录时不会抛出解析错误。它会“自适应”。缺失的字段变成了看似合理的猜测。重命名的字段变成了略微错误的解读。单位的变化——从分到元,从 UTC 到本地时间——变成了一个自信的答案,而其偏差倍数模型从未提及。管道端到端显示为绿色,仪表盘保持静默,而破碎的契约在三周后才浮出水面,表现为一种谁也无法进行二分定位(bisect)的弥散性质量投诉。我们没有消除故障。我们消除的是“信号”。

模型 API 现已成为 Tier 0 级别。在状态页变红之前,请先设计好降级模式

· 阅读需 13 分钟
Tian Pan
Software Engineer

询问基础设施团队,如果主数据库宕机会发生什么,你会得到一个演练过的答案:副本、故障转移运行手册、某人签字确认的 RTO 和 RPO 指标。询问同一个团队,如果模型 API 宕机会发生什么,你通常只会得到一个耸肩和指向供应商状态页面的链接。这种不对称性在 2023 年是有道理的,那时 LLM 只是驱动一个实验性的侧边栏。但在你的支持流程、搜索排名、代码审查机器人和入职助手都开始通过单一供应商的推理端点路由的那天起,这种不对称就不再合理了。

模型 API 现在是许多产品的 Tier-0 依赖项——对营收至关重要,且与数据库一样位于请求路径中——但大多数灾难恢复计划仍将其视为可有可无的集成。其结果是一种熟悉的故障形态:供应商服务降级,产品中的每个 AI 功能都显示相同的加载图标,值班人员盯着一个他们无法影响的状态页面,没人能回答唯一重要的问题:这个产品现在应该做什么?

无法回滚的回滚:提示词、工具和记忆必须同步版本化,否则满盘皆输

· 阅读需 13 分钟
Tian Pan
Software Engineer

故障频道里传来消息,说新的提示词正在幻觉退款金额,所以你做了一个显而易见的决定:将生产环境标签重新指向到上周的提示词版本。三十秒,无需重新部署,教科书级的回滚。结果 Agent 表现得反而更糟。上周的提示词引用了一个名为 lookup_order 的工具,但平台团队在周二将其重命名为 orders.search。记忆库中充满了由新提示词格式生成的偏好摘要,而旧提示词会将这些内容误读为用户指令。你并没有回滚 Agent,而是制造了一个“奇美拉”——三分之一是上周的,三分之二是今天的——并在从未测试过该组合的情况下将其推向了生产环境。

这是任何人的运维手册中都没有涵盖的失败模式:Agent 的部署不是一个单一的构建产物,而是一个三元组——提示词版本、工具契约、以及累积的记忆状态。回滚其中一个维度,而让另外两个维度保持现状,并不能恢复之前的状态。它创造了一个从未存在过、从未通过评估、且不属于任何团队值班范畴的新状态。

429 错误背后的惊群效应:速率限制是一个分布式系统问题

· 阅读需 13 分钟
Tian Pan
Software Engineer

调出你上次遇到持续 429 错误时的请求日志。你可能会发现一些奇怪的现象:错误并不是以稳定的流形式出现的。它们成波浪式出现——一波 429 爆发,接着是一段安静的间隔,然后是更大的爆发,再接着是另一个间隔。服务商的配额在波动期间并没有改变。你的流量也没有激增。你所看到的其实是你自己的重试逻辑在自我同步。每个在第 0 秒失败的客户端都计算了相同的退避(backoff)延迟,睡眠了相同的时长,并在同一瞬间醒来,然后再次共同失败。

这就是“惊群效应”(thundering herd),而最讽刺的是,标准的修复方案——指数退避(exponential backoff)——并不能阻止它。确定性的指数退避“组织”了惊群。它将一群几乎在同一时刻失败的客户端聚集起来,并让他们步调一致地前进:所有人都在 1 秒后重试,然后所有人都在 2 秒后,再然后是 4 秒。负载峰值的间隔变得更远了,但每个峰值的高度依然和第一个一样。如果正是这个峰值触发了你的速率限制,那么你实际上构建了一个永远重新触发它的节拍器。

你的智能体需要的是监督者,而不是重试循环

· 阅读需 12 分钟
Tian Pan
Software Engineer

你的 Agent 在一个包含十二个步骤的任务的第七步挂掉了。框架捕获了异常,经过指数退避(exponential backoff)后进行了重试。它重试了该步骤 —— 但使用的是同样的上下文窗口(context window),其中已经堆积了三次失败的工具调用、一段解析了一半的错误消息,以及一个模型已经放弃的计划。当然,重试也失败了,因为重试是一场赌注,赌的是世界发生了变化,而那个 Agent 的世界没有任何改变。需要改变的是 Agent 的状态 —— 而任何 Agent 框架中的重试策略都不会做出这种决定。

Erlang 的 OTP 库在三十年前就为必须运行数十年的电话交换机制定了这项决策。主管树(supervisor trees)背后的洞察力从来不是“在崩溃时重启”。而是“如何恢复”是一个与“执行工作”完全解耦的关注点,它由一个独立的进程负责,并排列在一个层级结构中,每一层都对恢复的含义有更深一点的理解。现如今的大多数 Agent 框架将重试强加到单个调用上,这就像在电话交换机的每一行代码周围都套上 try/catch 一样。它们真正需要的是这种层级结构。

护栏也是模型:那个没人放进仪表盘的隐藏依赖

· 阅读需 13 分钟
Tian Pan
Software Engineer

这是一个正在演变成一种典型的故障复盘(postmortem)模式。主模型整晚运行良好。延迟平稳,Token 吞吐量正常,服务商状态页显示正常。然而,整整 40 分钟内,每一个用户请求都失败了——因为位于模型前端的安全分类器(safety classifier)超时了,中间件将该超时封装为一个通用异常,而异常处理程序返回了拒绝响应。你的模型并没有宕机。是你的“门禁”宕机了,而这扇门被一名从未将其视为决策选项的工程师配置成了“故障即关闭”(fail closed)。

令人不安的事实是,大多数团队在生产环境中运行着第二个机器学习系统,却不愿承认。内容审核分类器、越狱检测器、PII 清洗器、主题过滤器——每一个都是模型,拥有各自的延迟分布、错误率、在漂移中悄然腐烂的训练数据假设,以及各自的故障模式。但因为它被称为“护栏”,它就被当作配置文件来对待:设置一次,从不监控,在仪表盘上缺席,也不在值班手册中。你绝不会在没有 SLO 的情况下发布主模型。但大多数团队发布护栏时,甚至连健康检查都没有。

你的 Prompt 拥有外键

· 阅读需 11 分钟
Tian Pan
Software Engineer

重命名一个数据库列,看看会发生什么。编译器会捕获每一个查询构建器。ORM 迁移会捕获模型类。类型检查器会捕获 API 序列化器。集成测试会捕获通过网络读取该字段的两个服务。该列的每一个消费者都会被标记出来——只有一个除外。你的系统提示词(System Prompt)包含了一份为了“让模型理解数据”而精心格式化的表描述,它仍在自信地描述一个早已不存在的列。没有编译器错误。没有失败的测试。没有弃用警告。只有一个开始针对三个迭代(Sprint)前的架构生成查询的模型,以及一个慢慢被格式错误的 SQL 填满的仪表盘。

提示词中包含外键。它们引用数据库架构、工具签名、枚举值、API 结构,以及从生产数据中复制的 few-shot 示例——而这些引用都不参与重构。你粘贴到提示词中的每一个事实,都是在对一个你的工具链根本不知道其存在的表进行连接(Join)。当被引用的制品(Artifact)发生变化时,没有任何级联反应。提示词只是在那原地腐烂。

你的工具 Schema 验证的是类型,而非单位

· 阅读需 13 分钟
Tian Pan
Software Engineer

一个退款智能体正在处理一笔 42 美元的客户请求。它发出的工具调用是 refund(amount: 4200) —— 等等,这正确吗?如果后端以“分”为单位存储金额,那它完全正确。如果后端以“美元”为单位存储,那么客户刚刚得到了 4,200 美元的退款。两次调用在语法上都是完美的。两者都在微秒级内通过了 JSON Schema 验证。Schema 规定 amount 是一个数字,而 4200 毫无疑问是一个数字。

这是一类单元测试、Schema 验证器和大多数智能体评估(Agent evals)都会忽略的错误:工具调用边界处的单位混淆。模型会根据其训练数据进行数值模式匹配 —— 以美元计价的货币、以秒计价的时间、由周围文本暗示的重量单位 —— 而你的工具则期望的是分、毫秒或公斤。类型系统不会提出任何异议。发票金额只是偏差了 100 倍。

在你的智能体能够自我重试之前,精确一次性处理(Exactly-Once)曾是一件难事

· 阅读需 10 分钟
Tian Pan
Software Engineer

我们花了二十年的时间来教导服务如何安全地进行重试。这个方案已经非常成熟了:客户端生成一个唯一的幂等键 (idempotency key),将其附加到请求中,服务器在执行工作的同一个事务中记录该键及其结果。掉线、超时、500 错误 —— 客户端使用相同的键进行重试,服务器识别出该键,并返回记录的结果,而不是再次扣款。Stripe 多年前就推出了这种模式,它已成为任何涉及资金业务的 API 的基本要求。

整个设计都基于一个无人提及的假设:调用者会逐字节地重复其请求。 重试携带相同的键,是因为重试是同一段代码路径使用相同的变量重新执行。一旦打破这个假设,整个方案就会悄无声息地失效。