YAOTU INSIGHTS

Agent开发必看:降低工具调用错误率,从源头节省Token成本

Agent开发必看:降低工具调用错误率,从源头节省Token成本
做 Agent 开发的人应该都有过这种体会明明 Prompt 写了好几版各种约束条件都堆上去了结果 Agent 还是会在某个毫不起眼的环节翻车——要么工具调用了半天发现参数传错了要么一个请求重试了三五次还在同一个地方打转。每次看到后台日志里那一长串重复调用记录说实话心里挺不是滋味的。那些被浪费掉的 Token 和 API 调用额度本质上都是钱而且烧得毫无意义。我越来越觉得与其花大量时间反复压缩 Prompt、调整措辞不如认真想想怎么让 Agent 本身少犯错。少踩一个坑省下的不仅是 Token 费用更是排查问题的精力和用户的耐心。这期内容我想从成本链路、错误根因和实际改造经验三个角度聊聊怎么让 Agent 从频繁试探变成一次走对。如果你也在做 Agent 相关开发或者正在被调用消耗高、成功率低的问题困扰这篇应该能给你一些直接可用的思路。1. 先算笔账一次错误到底烧掉了多少钱很多人对 Agent 的消耗不太敏感觉得一次调用也就几分钱能贵到哪里去。但真实情况完全不是这样。我建议所有做 Agent 的朋友都先从成本链路的角度重新审视一次踩坑到底意味着什么。1.1 踩坑的隐性成本链路一次典型的 Agent 错误通常不是一次性损失而是一条完整的成本链。我拿一个实际场景举例你让 Agent 去查询某订单的物流信息。第一步Agent 需要识别意图、提取参数。如果用户表述得不够清晰比如只说了查一下我的快递但没有提供订单号Agent 通常不会直接反问而是会尝试先调用查询接口——这时候大概率会因为缺少必填参数而失败。第二步接口返回错误后Agent 需要反思并重新生成调用。这个过程会把错误信息重新发给模型让模型再次推理、再次构造参数。一次失败后的重试大模型要重新读一遍上下文里的所有内容比如系统提示、用户历史、之前的工具定义、失败报错全部重新处理一遍。这一轮的消耗比首次调用可能还要高因为上下文里新增了错误信息。第三步如果 Agent 设计得不够好它会进入重试循环。我见过最夸张的一次Agent 在同一个参数缺失问题上连续重试了 7 次。每一次重试都在膨胀上下文长度Token 单价虽然没有变但总消耗是指数级上升的。所以一次表面上的小错误实际成本大概是首次调用成本 错误解析成本 重试成本 上下文膨胀带来的增量成本。后三者加起来往往是最初调用成本的 3 到 5 倍。1.2 压缩 Prompt 的边际收益其实很有限很多人面对成本问题第一反应是压缩 Prompt。把系统提示从 3000 Token 压到 1500 Token确实立竿见影但这条路走到后面会有瓶颈而且可能产生新的问题。我试过把一份 Prompt 反复精简到极致结果 Agent 的行为表现开始变得神经质。为了省那几百 Token把一些边界情况的说明删掉了结果模型遇到边界问题就开始自由发挥。比如你删掉了当用户未提供订单号时应主动询问用户这条指令模型可能就会编造一个假的订单号去查询然后得到空结果后再编一个查询无记录的结论——比原来直接询问用户的方案反而多消耗了更多 Token。更重要的是压缩 Prompt 解决不了工具调用本身的质量问题。无论怎么压缩Agent 该传错的参数还是会传错该重试还是会重试。Prompt 优化是必要的但它解决的是指示清晰度问题解决不了执行稳定性问题。后者的根源在架构设计上。提示如果你的 Agent 成本高企先别急着动 Prompt。先去翻日志统计一下有多少次调用是失败的有多少次是重试的有多少次返回结果根本无法解析。这些数字会告诉你真正的钱烧在了哪里。2. 为什么 Agent 总在踩坑错误根因的三类画像要解决问题得先搞清楚问题长什么样。我梳理了大量 Agent 运行日志后发现高频踩坑基本可以归为三类。每一个类别的成因和对策都不太一样需要分开处理。2.1 工具调用失败参数要么缺要么错要么类型不对工具调用失败是出现频率最高的一类问题占到了所有错误的一半以上。它的表现形式很典型大模型成功识别了用户的意图也选对了工具但在生成参数时却出了问题。最常见的是必填参数缺失。用户说帮我订明天去上海的票但没说明出发地。模型在生成工具参数时如果没有被明确告知必须向用户确认出发地它可能就会默认一个城市或者干脆留空。接着如果代码层没有做好参数校验这个错误参数就会直接被发到 API直到 API 抛异常才发现问题。另一种是参数格式错误。比如接口预期的是 ISO 日期格式模型却生成了明天这样的自然语言接口预期的是数字类型模型给传成了字符串。这些格式性问题靠 Prompt 约束能改善但没法根治。因为模型生成文本本质上是一个概率过程你没法保证它在每一次采样中都严格遵守格式规范。还有一种情况比较隐蔽模型的上下文已经很长工具定义的描述在上下文中被稀释了。上下文越长模型对工具的细节关注度越差生成错误参数的概率越高。这其实不是模型变笨了而是长上下文中的注意力分散是客观存在的现象。2.2 多步任务中的上下文漂移多步任务比如先查询用户信息再查询订单最后汇总成报告这种场景下 Agent 特别容易在中途失忆。原因很简单每一步的中间结果都在改变上下文的内容每一步新的输出都在挤压旧信息的位置。我做过一个实验让 Agent 执行一个五步的调研任务。第一步它提取了三个关键数据等到执行第五步的时候让它复述第一步的内容结果只记得大约一半。这不是模型能力不行而是没有外部记忆的情况下用对话式上下文承担任务状态存储本来就不太可靠。上下文漂移带来的直接后果是后续步骤可能在错误的信息基础上继续执行。比如第一步得出用户等级为金牌第五步总结时却写成了银牌用户——因为中间步骤里出现过银牌的对照案例模型就混淆了。这类错误非常隐蔽你光看最终结果很难发现因为整个链路是自洽的只有和真实数据对照才能看到差异。2.3 无效重试与死循环当执行失败时Agent 有两种应对策略一种是直接报错退出另一种是尝试重新执行。问题在于很多 Agent 的重试是无差别的。只要失败就重试完全不考虑失败的原因是什么。这种无差别重试会带来两组很严重的问题。第一消耗爆炸。一次失败引发 3 次重试每次重试都基于包含错误信息的上下文重新推理成本直接翻倍。第二死循环风险。如果错误出在模型能力边界上比如模型无论如何都理解不了某个模糊指令那不管重试多少次都会失败就是一个死循环。红锁围巾就是典型的死循环场景上千次的调用全部无效全在烧钱。真正合理的重试策略应该是区分错误类型的。参数错误、格式错误这类问题可以通过代码层面的校验提前拦截根本不需要让模型重新生成只有模型确实漏掉了信息或理解偏差这类问题才值得重试。重试也应该设置最大次数并且每次重试前强制改变策略比如改变指令措辞、换一个提示模板而不是原封不动再来一次。3. 少踩坑的四个实操方向从架构层降低错误率分析完原因下面进入正题。这四个方向是我在实际项目中验证过、确定能显著降低错误率的做法核心思路都是用工程手段去约束模型的行为边界让模型在该发挥的地方发挥在不该出错的地方杜绝出错。3.1 用结构化输出约束而不是只靠 Prompt 祈祷Prompt 里写请严格输出 JSON这句话效果其实非常有限。模型确实会输出 JSON但字段名、嵌套结构、类型都无法保证稳定。我见过太多看起来像 JSON 但解析就报错的情况多了个逗号、中文引号混入、外层加了 Markdown 代码块包裹各种变体都有。正确做法是两层约束第一层在模型层强制使用结构化输出能力让模型的输出直接绑死一个 JSON Schema模型输出前就按这个 Schema 生成从根本上杜绝格式问题。这个能力目前的调用接口里基本都内置了只是很多人没有留意到。第二层在代码层做校验兜底。即使模型返回了结构化 JSON也要做一次轻量级校验检查必填字段是否存在、类型是否匹配、枚举值是否合法。校验不通过就立即拦截而不是让错误数据流入下游。实际操作中我会给每个关键工具接口定义一个 Pydantic 模型或者 TypeScript Interface校验逻辑写得很薄但很精准。校验失败时代码直接抛出一个参数校验失败的异常通知上层回调处理打断 Agent 的重试循环而不是让 Agent 自己尝试修复参数。3.2 给工具调用加熔断和校验只靠约束还不够工具调用本身必须是一件有防线的事情。我给 Agent 接的工具都套了一层保护壳功能是参数前置校验、超时熔断、错误归一化。超时熔断很容易理解就是单个工具调用设置一个合理的最大等待时间。比如调用第三方 API正常情况下 3 秒出结果超过 10 秒就主动中断不要让它无限等下去。这在 Agent 里特别重要因为 Agent 经常会等待外部响应等太久整个会话就卡死了。错误归一化是指无论工具内部抛出什么类型的异常都要统一转换成 Agent 能理解的错误描述。比如接口返回了 500你需要在错误信息里写服务内部错误请稍后重试而不是直接扔一个 HTTP 500 的原始报错。Agent 的推理能力在理解自然语言错误时表现更好在面对原始堆栈或状态码时反而容易产生误判。熔断逻辑更需要留意。如果一个工具在短时间内连续失败应当让 Agent 暂停使用这个工具而不是继续反复调用。比如每 30 秒内最多调用 3 次超过就直接跳过这个工具。这套逻辑其实很简单但能切断大部分重试循环的路径。3.3 设计计划-执行-验证回路我发现一个特别常见的踩坑模式Agent 直接进入执行阶段不做计划执行完也不回头验证结果。这种设计在简单任务中没问题但一旦任务稍微复杂一点成功率就会断崖式下降。我推荐的模式是给 Agent 的推理过程里加一道验证关卡。具体来说每一步执行完之后强制要求 Agent 把执行结果与最初目标做一次对照这一步是否完成了预期目标输出是否在合理范围内如果执行结果明显不合理比如查到的数据量异常大、数值异常高就触发重新思考的机制。验证环节不一定需要模型参与。更靠谱的方式是用代码做规则判断。比如拿到结果后检查返回字段是否为空、列表长度是否为 0、数值是否超出合理范围。规则比模型判断更可预测速度也更快。只有规则判断通过了才允许 Agent 进入下一步。执行路径上也可以做约束先规划再执行把规划结果作为一个结构化输出然后每一步严格按照计划的编号逐步执行。这样既方便追踪也方便在某一步出错时快速定位到具体的步骤而不是让整个任务推导重来。提示计划执行和自由执行在复杂场景下的成功率差距非常明显。五步以上的任务自由执行第一次成功率可能只有 40%但加了计划约束后可以提升到 70%再配合验证环节能超过 85%。关键是成本不仅没增加反而因为重试少了总消耗降下来了。3.4 缓存与记忆机制别让模型反复做同一道题Agent 的运行模式里有一个很大的浪费点同一个查询、同一个中间结果在任务的不同阶段会被反复计算。比如用户问比较一下 A 产品和 B 产品的价格Agent 先去查 A后来为了对比又去查了一次 A。如果中间结果不缓存同一个 API 会查询两次Token 和接口费用都翻倍。我一般在 Agent 会话层做两件事。第一短期记忆缓存把每一步的工具调用结果存储在当前会话内存里键值对设计成工具名 参数摘要如果后续步骤需要相同的数据直接读缓存不再调用工具。第二长期缓存对于更新频率低的数据比如产品基础信息、地区列表、货币汇率用一个简单的 Redis 或内存缓存设置合理的过期时间。这个策略在实际中的收益非常明显特别是在密集型查询任务里能省下 30% 左右的 API 调用。记忆机制这块还要补充一个点不要把全部历史都无脑塞进上下文。如果一次对话已经进行了 20 轮每轮都有较长的工具结果那么每一轮新请求的上下文都会包含全部历史——这其实是在为记住买单。更好的做法是定期对历史对话做摘要把低价值的信息压缩成几句话高价值的保留原文。这和写笔记是一个道理留下重点删掉废话。4. 实战复盘一个查询任务的完整改造有抽象的方法论还不够我拿一个自己做过的改造成果来细说。这个项目本身不大但很典型能看出改造前后的成本差异有多明显。4.1 原始实现的问题点这个项目是一个简单的订单查询助手用户输入订单号Agent 调订单接口返回物流状态。按理说这个任务足够简单但实际运行中的成功率却一直不理想。我翻日志的时候发现问题集中在一个点用户经常不按标准格式输入订单号有时候会带空格有时候会混入单号这样的前缀。Agent 在提取参数时经常把订单号提取成乱七八糟的内容然后传给查询接口接口查不到返回订单不存在。Agent 收到订单不存在后也不会去怀疑是否参数提取错了而是直接把这个结果当成最终答案告诉用户。这就导致两个问题一是用户明明输入的订单号是对的只是格式带了噪音Agent 却告诉用户查不到体验很差二是对于同一个任务Agent 会多消耗好几轮无效推理的 Token。纠正格式是代码层几行字符串处理就能做完的事放到 Agent 的推理里却要反复消耗昂贵的模型 Token。4.2 改造方案与成本对比我的改造方案围绕三个方向第一在模型调用之前加一个轻量级预处理模块。这个模块不经过大模型用规则和简单的正则表达式直接清洗用户输入。比如去除空白、去掉常见前缀、只保留合法的订单号结构。清洗后的参数再交给 Agent。这个改动让参数错误率从百分之十几降到了接近零。第二给订单查询接口的调用加了一层校验。当 Agent 生成工具参数后代码校验不通过就直接返回提示并且附带一条提示信息参数不符合格式请参考示例格式重新生成。这条提示信息其实是在引导模型自我纠正但因为校验发生在模型输出之后、API 调用之前所以不会触发任何 API 费用。第三给查询结果为空这事件加了判断。如果查询接口返回空数据代码层不直接视为最终结果而是返回一个特殊状态查询结果为空可能是订单号不存在也可能是参数有误。这样 Agent 在回答用户前会多一步自我检查的逻辑。改造上线后我对同一个测试集跑了一轮对比。结果的差异很直观对比维度改造前改造后首次调用成功率约 68%约 93%单任务平均 Token 消耗约 4800约 2700重试次数平均 2.4 次平均 0.3 次错误报告频率每 10 次任务约 3 次每 10 次任务约 0.5 次Token 消耗下降接近 44%主要省下的是重试和上下文膨胀的部分。这个改造并没有压缩 Prompt而是通过代码层做掉了模型不该做的脏活让模型只做它最擅长的那部分——推理和决策。5. 常见问题与排查技巧实录实践过程中总会遇到一些零碎但很磨人的问题。我把它们整理成了一个速查清单方便大家在遇到类似情况时快速对照排查。5.1 高频问题速查表现象可能原因处理方案Agent 反复重试同一个失败的工具没有区分错误类型无差别重试在代码层拦截格式错误只对模型本身错误触发重试输出 JSON 偶尔解析失败模型输出未绑定 Schema或外层有包装使用结构化输出约束并配置容错解析器长任务执行到后半段信息错乱上下文漂移缺少中间状态记忆引入外部状态存储定期摘要压缩历史工具调用成功但没有返回数据参数可能被静默修正或接口返回模型未理解显式区分无数据与错误前置校验单任务消耗波动大不同输入导致推理路径差异明显设置最大重试次数和中途退出机制Agent 偶尔编造数据模型在信息缺失时试图补全给工具返回结果加置信度标记缺失时强制说明未知每一行都来自我实际踩过的坑。比如最后一条编造数据这个在刚开始做 Agent 的时候真的有些措手不及——模型明明没有查到数据却生成了一段看起来非常合理的物流信息。后来我强制规定任何工具返回空结果模型只能回答未找到相关信息不允许自行补充。5.2 排查链路的三板斧如果你现在有一个 Agent 在频繁踩坑我建议按照这个顺序排查先看日志里的工具调用记录统计各类失败的比例再对比例最高的失败类型做针对性处理最后验证处理是否有效。这个方法不花哨但非常管用。日志记录这一点看起来基础但在真实项目里很多 Agent 根本没有做好日志结构化。建议每条日志至少包含时间戳、用户原始输入、模型生成的完整调用含参数、工具返回结果、后续 Agent 行为。有了这条完整链路绝大多数问题排查时间能从几小时缩短到十几分钟。还有一个小技巧给 Agent 跑回归测试集。固定一批有代表性的输入在每次改动后都跑一遍比较成功率、消耗和输出格式的一致性。这一步能有效防止那种修好一个 bug 引出另一个 bug的连锁问题。写在最后我做了很长一段时间 Agent 相关开发有一个特别深的体会模型的能力只是一方面真正决定项目成败的往往是边缘路径的处理方式。同样是调用一个工具有人让模型自由生成参数在错误中反复摩擦有人在模型前后各加一道工程防线把模型可能犯的错误直接挡在外面。两者的 Token 消耗差距是数量级的。所以很多找我咨询的朋友问怎么压缩 Prompt 省钱我一般会反问一句你的 Agent 目前的首次成功率是多少如果低于 85%对不起省钱的关键根本不在压缩 Prompt而在提高首次成功率。先把踩坑率降下来成本自然就蒸发了。如果你也在做 Agent我建议这个周末干一件事拉出最近一周的运行日志统计一下累积失败次数和重试次数算算这些失败一共浪费了多少 Token。等你看到那个数字你大概就明白了让 Agent 少踩坑才是真正省钱的路子。