[控场AI]
· 15 分钟阅读· 7,844 字

AI Agent烧钱三大教训:Schema校验、熔断机制与重试策略实战

AI Agent烧钱三大教训:Schema校验、熔断机制与重试策略实战

一位独立开发者构建了一个 Chrome 扩展:用户只需输入一句话,比如「帮我做一个 10 道题的入职调查问卷」,背后的 AI Agent 就会通过 Google Forms API 自动写出完整的表单。Chrome 扩展是基于 Web 技术构建的浏览器插件,能够访问 Chrome 提供的特殊 API 来增强浏览体验;而 Google Forms API 是 Google Workspace 开发者平台的一部分,允许以编程方式创建和管理表单。值得注意的是,当前 Chrome 扩展已全面迁移至 Manifest V3 架构,该架构用 Service Worker 替代了旧版的持久后台页面(Background Page)。Service Worker 具有短暂的生命周期——它会在空闲时被浏览器终止,在需要时重新唤醒。这对 AI Agent 的设计提出了额外挑战:Agent 的多轮推理过程必须能够容忍 Service Worker 的随时中断与恢复,状态管理需要依赖 chrome.storage 等持久化 API 而非内存变量。同时,调用 Google Forms API 需要通过 OAuth 2.0 授权流程获取用户令牌,Chrome 扩展通过 chrome.identity API 简化了这一过程,但令牌的刷新和过期处理仍然是工程中容易被忽视的故障点。将 AI Agent 嵌入 Chrome 扩展意味着整个推理-执行链条都在浏览器环境中运行,Agent 需要将自然语言指令转化为符合 API 规范的结构化请求——而这个过程中,每一次模型调用都会消耗 API token 并产生费用。

听起来很酷,但在把它推向生产的过程中,作者踩了三个坑——而每一个坑,都在他察觉之前实实在在地烧掉了 API 预算。

这三个教训看似琐碎,却揭示了当下 AI Agent 工程化中最容易被忽视的现实:当模型陷入某种失败循环时,它并不知道自己错在哪里,只会不断重试并持续消耗 token。 下面我们逐一拆解。

reddit 原帖:用一句话构建 Google Forms 的 Agent

教训一:严格Schema校验反而让AI Agent烧光预算

在 AI Agent 架构中,Tool Schema(工具模式定义)是连接大语言模型与外部 API 的核心桥梁。OpenAI、Anthropic 等主流模型提供商都支持 Function Calling(函数调用)机制:开发者用 JSON Schema 格式定义工具的名称、参数类型和结构约束,模型在推理时会生成符合该 Schema 的结构化输出来调用工具。Function Calling 机制由 OpenAI 在 2023 年 6 月首次引入,随后 Anthropic(Tool Use)、Google(Function Declarations)和其他模型提供商纷纷跟进,如今已成为 Agent 开发的事实标准。值得区分的是,OpenAI 后来推出的 Structured Outputs 模式通过受限解码(Constrained Decoding)技术,在 token 采样层面强制输出符合 JSON Schema——理论上可以完全消除格式错误。但普通的 Function Calling 模式并不具备这种硬约束,模型仍然是「尽力而为」地生成符合格式的输出。这就是为什么即便提供了精确的 Schema 定义,模型在处理复杂结构时仍然会出现类型退化。

但大语言模型本质上是 token 序列生成器,它对 JSON 结构的理解是概率性的而非确定性的,在处理复杂或冗长的结构时,容易出现类型退化。所谓类型退化,是指模型在生成较长的结构化输出时,注意力机制(Attention)对早期上下文的权重逐渐衰减,导致它「忘记」了最初的类型约束。一个常见的表现是:当数组元素较多时,模型可能会将整个数组序列化为字符串,或者在嵌套层级较深的地方将数字写成字符串形式。这不是模型的「bug」,而是自回归生成机制的固有局限。

作者的工具 Schema 定义中,requests 字段应当是一个数组。但在处理较长的批量任务时,模型会「犯懒」——它把整个数组序列化成一个 JSON 字符串发过来,而不是真正的数组结构。

作者的做法是直接拒绝这个不合法的请求。从纯粹的校验逻辑看,这完全正确。但正是这个「正确」,成了最大的错误。

模型看不到自己错在哪

问题的核心在于:模型无法理解它为什么被拒绝。 它只知道请求失败了,于是会本能地重写 payload——把请求拆得越来越小,试图「绕过」这个它根本不理解的障碍。

结果是灾难性的:一次「添加 20 道题」的操作,模型连续折腾了 9 轮,烧掉约 15 万 token,最终放弃并告诉用户「API 坏了」。大语言模型的 API 计费通常基于 token 数量,分为输入 token 和输出 token 两部分。以 GPT-4o 为例,输入 token 价格约为每百万 token 2.5 美元,输出 token 约为每百万 token 10 美元;而 Claude 3.5 Sonnet 的定价分别为 3 美元和 15 美元。在 Agent 循环场景中,每一轮对话都需要将完整的上下文——包括系统提示、历史消息、工具定义和之前所有轮次的交互记录——作为输入发送给模型。随着轮次增加,每轮的输入 token 数量会线性甚至超线性增长。具体来说,如果第一轮的上下文为 C 个 token,每轮模型输出 O 个 token、系统反馈 F 个 token,那么第 N 轮的输入 token 数约为 C + (N-1) × (O + F)。9 轮下来,后面几轮的单轮输入可能已经膨胀到数万 token,加上每轮的输出 token,总计 15 万 token 的消耗完全在预期之内。按照主流模型的定价,这一次失败操作可能就花费了 0.5 到 1.5 美元——对一个本该几秒完成的表单生成操作来说,这是不可接受的。用户体验和成本控制双双崩盘。

两个关键修复方案

作者给出了两个成本极低但效果显著的修复方案:

  • 如果收到的是字符串,就直接解析它。 既然模型发来的字符串内容是无歧义的合法 JSON,那么解析它不会带来任何风险,也几乎不产生成本。与其教条地拒绝,不如宽容地接受。在工程实践中,这种模式被称为 Postel 法则(Robustness Principle):「对自己发送的东西要严格,对接收的东西要宽容。」这条原则最初由 TCP 协议的设计者 Jon Postel 提出,在 AI Agent 开发中获得了新的生命力。
  • 拒绝时,要指出模型真正犯的错误,而不是抛出解析器的原始报错。 「Expected double-quoted property name at position 827」这种信息对模型毫无价值——它无法据此采取任何行动。而「检查 location 是否位于 createItem 内部、紧挨着 item」则把问题说得清清楚楚,模型立刻就能修正。

这个细节点出了 AI Agent 工程的一个通用原则:错误信息是写给模型看的,必须是「可操作的」,而非「技术正确的」。 这与 Prompt Engineering 的核心思想一脉相承——对模型的指令应当具体、可执行且不产生歧义。Google DeepMind 和 Anthropic 的研究都表明,当错误反馈包含明确的修复方向时,模型的自我纠正成功率可以提高数倍。Anthropic 在其 Agent 设计最佳实践文档中明确建议:工具的错误响应应包含足够的上下文,使模型能够在不重新提问用户的情况下自主修复问题。

教训二:AI Agent的轮次上限不等于熔断机制

作者原本设置了一个「最大轮次」上限来防止无限循环,但这个上限设得太宽,根本无法及时捕捉到一个已经卡住的模型。

为什么固定轮次上限不够用

这里有一个反直觉的洞察:第四次相同的失败,并不比第三次提供更多信息。 当模型陷入重复失败时,继续给它机会不会带来任何转机,只会持续烧钱。一个宽松的轮次上限,本质上是在为无意义的重试买单。这在信息论中有一个对应的概念:当系统进入确定性的失败状态后,额外的尝试提供的信息熵为零——你不会从第 N 次相同的失败中获得任何你在第 N-1 次中不知道的信息。

基于「连续失败」的熔断策略

熔断器(Circuit Breaker)是微服务架构中的经典容错模式,最早由 Michael Nygard 在《Release It!》一书中系统阐述,后来 Netflix 的 Hystrix 库使其广为人知。其核心思想来自电路中的断路器:当下游服务连续失败达到阈值时,熔断器跳闸,后续请求直接快速失败而不再发送。传统熔断器通常遵循三态模型:Closed(关闭/正常通行)状态下所有请求正常发送,但失败会被计数;当失败次数达到阈值时,熔断器切换到 Open(打开/拒绝通行)状态,所有请求被直接拒绝而不会真正发送到下游;经过一个预设的冷却时间后,熔断器进入 Half-Open(半开)状态,允许少量请求通过以探测下游是否恢复——如果成功则回到 Closed,失败则回到 Open。传统熔断器通常基于时间窗口内的失败率来触发(例如「10 秒内失败率超过 50% 即熔断」),但在 AI Agent 场景中,由于每轮交互的时间跨度较长(单次模型推理可能需要数秒)且成本高昂,基于时间窗口的统计不够灵敏。作者提出了一种更精准的变体——基于连续失败计数的即时触发。

作者的新策略更聪明:

  • 连续 3 次工具调用失败即停止。 关键在于「连续」——任何一次成功都会重置计数器。这样一个正确执行了很多步的长任务,不会因为累计触发上限而被误伤。这种设计特别适合 Agent 的工作特征:一个复杂任务可能包含几十个步骤,偶尔失败一两次是正常的,但连续三次相同类型的失败则高度表明模型已陷入无法自行恢复的死循环。从概率角度看,如果单次调用的成功率为 90%,连续三次失败的概率仅为 0.1%——这个信号足够强烈,足以判定系统处于异常状态而非正常波动。
  • 在最后一轮明确告知模型这是最后一轮。 这样模型会向用户解释发生了什么,而不是「悄无声息地死掉」,留给用户一个莫名其妙的中断。这个设计暗含了一个重要的 UX 原则:Agent 的失败应当是「优雅降级」(Graceful Degradation)而非「硬性崩溃」。当模型被告知「这是你最后一次机会」时,它通常会切换到总结和解释模式,向用户报告已完成的步骤和未完成的部分,让用户有能力手动接管或重新发起请求。

从「固定轮次」到「连续失败计数」,本质上是从「限制总量」升级到「识别状态」——真正的熔断器关心的是系统是否卡死,而不是跑了多少圈。

教训三:API重试策略——重试失败请求几乎总是错的

第三个教训涉及网络请求的重试策略,作者的结论相当明确且值得所有 AI Agent 开发者记住。

永远不要重试HTTP错误响应

HTTP 协议定义了明确的状态码体系:4xx 表示客户端错误(如 400 Bad Request、429 Rate Limited),5xx 表示服务器错误(如 500 Internal Server Error、503 Service Unavailable)。在传统 Web 开发中,对 5xx 错误进行带指数退避(Exponential Backoff)的重试是标准做法,因为服务器错误通常是暂时性的。指数退避的核心思想是每次重试的等待时间呈指数增长(如 1 秒、2 秒、4 秒、8 秒……),通常还会加入随机抖动(Jitter)以避免「惊群效应」——大量客户端在同一时刻重试导致服务器再次过载。AWS、Google Cloud 等云平台的 SDK 几乎都内置了这种重试机制。但 AI 模型 API 的计费逻辑彻底改变了这个等式。

当你收到一个 HTTP 错误响应(比如 4xx、5xx)时,这意味着模型已经运行过、你已经付过费了。 即使返回 500 错误,模型推理可能已经完成并产生了计费。这一点在流式响应(Streaming)场景下尤为明显:当使用 Server-Sent Events(SSE)进行流式传输时,模型可能已经生成了部分 token 并通过 HTTP 连接发送给客户端,然后在中途因为某种原因(如输出过长触发安全过滤、服务端负载突增)返回错误。此时,已经生成的 token 已经计费,而你的重试会触发一次全新的完整推理过程。对于 OpenAI 的 API,其文档明确指出:只要请求被成功处理(即模型开始生成输出),即使最终返回了错误,已消耗的 token 仍会计费。如果此时盲目重试,你只会被双重计费。请求已经到达服务器并被处理,重试没有任何安全保障。

一个值得特别注意的状态码是 429 Too Many Requests(速率限制)。许多开发者的本能反应是对 429 进行退避重试——这在传统 API 场景中是正确的,因为传统 API 通常在执行业务逻辑之前就进行速率检查。但对于某些 AI API,429 可能在模型已经开始推理之后才返回(例如因为并发请求数在推理过程中超出了配额),这种情况下重试同样会导致双重计费。开发者必须仔细阅读所使用 API 的具体计费策略。

唯一值得重试的例外:连接未建立

但有一个例外至关重要:当 fetch 抛出 TypeError 拒绝时,意味着没有任何响应头抵达——也就是说请求根本没有到达服务器,什么都没有被计费。在 JavaScript 的 fetch API 中,当网络连接本身失败(DNS 解析失败、TCP 连接超时、TLS 握手失败等),fetch 会抛出 TypeError 而不是返回 Response 对象。这个区别至关重要:TypeError 意味着 HTTP 请求根本没有离开客户端或没有到达服务器的应用层,因此不可能产生任何服务端处理或计费。从网络协议栈的角度理解这个区别会更清晰:一个完整的 HTTPS 请求需要经历 DNS 解析 → TCP 三次握手 → TLS 握手 → HTTP 请求发送 → 服务端处理 → HTTP 响应返回这几个阶段。fetch 的 TypeError 发生在前三个阶段,此时请求的 HTTP 层数据还没有被服务器接收和处理;而 4xx/5xx 响应则发生在最后两个阶段,服务器已经接收并处理了请求(对于 AI API,这意味着模型已经运行)。

这正是「连接中断」在用户端的表现形式。对用户来说,这才是最常见、最需要被优雅处理的场景——特别是在 Chrome 扩展中,用户可能在移动网络、公共 Wi-Fi 等不稳定的网络环境下使用。因此,唯独这一种情况可以安全重试,也正是这一种情况最值得重试。这也是幂等性(Idempotency)概念在 AI API 场景下的实际应用——只有确认请求未被处理时,重试才是安全的。部分 API 提供商(如 Stripe)通过幂等键(Idempotency Key)机制允许客户端安全地重试已处理的请求,但目前主流 AI 模型 API(OpenAI、Anthropic、Google)尚未提供类似机制,这使得「不要重试已收到响应的请求」成为 AI Agent 开发中的硬性原则。

判断标准非常清晰:请求是否真正到达了服务器? 到达了就别重试,没到达才重试。这条简单的分界线,避免了双重计费的同时,又保证了对真实网络抖动的容错。

对AI Agent开发者的核心启示

把这三个教训串起来看,会发现它们指向同一个更深层的主题:AI Agent 与传统软件的错误处理逻辑截然不同。

在传统程序里,严格校验、固定重试上限、失败即报错,都是无可指摘的最佳实践。错误处理建立在「调用者能理解错误」的假设之上:开发者看到堆栈跟踪能定位问题,程序收到错误码能执行对应的恢复逻辑。但 AI Agent 引入了一个根本性的变量:调用链中的决策者是一个概率模型,它通过自然语言理解错误信息,并基于统计模式生成修复策略。在 Agent 场景下:

  1. 模型是一个「看不见上下文」的黑盒。 你的错误处理策略必须假设模型无法理解技术细节,因此错误信息要「翻译」成模型能行动的语言。这正是越来越多的 Agent 框架(如 LangChain、CrewAI)开始内置结构化错误反馈机制的原因——将原始异常信息转译为模型友好的自然语言描述。LangChain 的 ToolException 机制允许开发者定义当工具执行失败时返回给模型的自定义错误消息,而不是让原始的 Python 堆栈跟踪直接暴露给模型。CrewAI 更进一步,为每个 Agent 定义了错误处理的「人格」——当遇到工具失败时,Agent 会基于其角色设定决定是重试、换一种方法、还是向用户求助。此外,Agent 可观测性(Observability)正在成为一个快速成长的细分领域:LangSmith、Helicone、Braintrust 等平台提供了 Agent 执行链路的全程追踪,包括每一轮的 token 消耗、工具调用结果、模型决策依据等,使开发者能够在事后分析和优化 Agent 的失败模式,而非等到账单到来时才发现问题。
  2. 每一次循环都在花钱。 失控的重试不是性能问题,而是直接的财务损失。熔断必须基于「是否卡死」的状态判断,而非粗糙的次数上限。在生产环境中,建议同时设置 token 预算上限作为最后一道防线:为每个用户请求分配一个最大 token 预算(例如 5 万 token),当累计消耗接近预算时,无论是否触发连续失败的熔断,都应强制终止。这种「双保险」策略可以防止那些虽然没有连续失败、但每轮都生成冗长无效输出的隐性成本泄漏。
  3. 宽容比严格更划算。 能无歧义解析的输入就接受它,不要为了校验的纯粹性去付出昂贵的重试代价。这个原则可以进一步延伸为「防御性解析」(Defensive Parsing)策略:在工具接收模型输出时,建立一个多层解析管道——首先尝试严格解析,失败后尝试宽容解析(如 JSON.parse 字符串化的数组),再失败后尝试启发式修复(如补全缺失的引号或括号),只有在所有层次都失败时才返回错误——且错误信息必须是可操作的。

对于任何正在构建生产级 AI Agent 的团队来说,这三个来自真实生产环境、且「先烧了真金白银才发现」的教训,都值得写进工程 checklist 里。Agent 的可靠性,往往不取决于模型有多聪明,而取决于你如何设计它失败时的行为。

#AI Agent工程化#AI Agent成本优化#LLM token消耗#AI Agent熔断机制#API重试策略#AI Agent错误处理#Agent开发最佳实践#LLM生产部署
分享:

相关推荐