做 Agent 项目做久了,多半会撞上这样一句经典对白:“Agent 跑一次太贵了,把 Prompt 压一压吧。”这句话听起来毫无毛病,token 是按长度计费的,Prompt 短一点,单价确实低一点。但如果你真的把成本压力全都押在“压缩 Prompt”上,大概率会踩进另一个坑:省下的是开头那点输入费用,赔进去的是 Agent 一次次失败重试、状态错乱、人工介入带来的成倍消耗。我见过一个项目,Prompt 压缩了 30%,一个月后账单反而涨了——因为压缩之后的指令边界模糊了,Agent 动不动就走错路,重试次数翻了一倍。这篇文章想聊的,不是怎么把 Prompt 写得又短又全,而是怎么让 Agent 在运行过程中少踩坑。每一脚坑踩下去,付出的代价都远比“那几行多余描述”贵得多。
1. 不要在“提示词的字数”上省钱,要在“无效重试”上省钱
1.1 压缩 Prompt 的收益其实存在天花板
先说结论:Prompt 可以压,但别把它当成成本优化的主力。
能压缩的内容通常有这么几类:重复出现的示例、过度修饰的背景说明、冗余的边界话术、以及各种“以防万一”的补充条款。这些内容确实在吃掉输入长度,压掉它们可以立竿见影地让单次调用的费用下降。但问题是,Prompt 压缩到一定程度之后,指令的区分度、边界定义、异常处理说明就都开始变得模糊。模型还是能看懂,但理解出现偏移的概率开始上升。
这里面的逻辑很像节食减肥:少吃几口当然能掉秤,但如果你本身代谢差,吃再少也是虚胖。Agent 的“代谢”就是它对工具使用、状态维护、错误处理的整体能力。代谢不好,Prompt 压到极限也只是让它在一条错误的路上跑得更快而已。
更关键的是,单次调用省下的那部分 token 通常很有限。比如一个 2000 token 的输入,你压掉 30%,单次省 600 token。听着不错,但一次失败重试要消耗的 token 往往数倍于此——因为它会带上完整上下文、工具返回结果、失败日志,重试两三次就把你辛苦省下来的份额全部吃掉。
1.2 Agent 失败的真实成本:一次空转翻三倍账单
很多团队算成本的时候只算“成功路径”的 token,这是最大的账目盲区。真实的 Agent 成本分布里,失败和重试通常才是大头。
举个我实际遇到过的例子。在某零售客服场景中设计了一个订单查询 Agent,模型需要用工具调用接口查订单状态。正常情况下,一次会话十轮交互,token 消耗在可控范围内。某天压测时发现,一个稍微模糊的查询操作让 Agent 连续调了三次工具接口:第一次返回“订单号不存在”,Agent 没读懂错误信息,换成另一个字段继续查;第二次返回权限不足,Agent 以为是参数问题,又换了一种方式重试;第三次重试把整段对话历史、前面两次失败的工具返回、再加上新的执行计划全部拼进了上下文。最终这次任务消耗的 token 量,是正常成功路径的三倍以上。
如果这只是偶发一两次,问题不大。但在生产环境里,失败率通常会稳定在 15%-25% 之间,遇到流量高峰还会更高。你会发现账单的涨幅和阿 Q 那句“和尚摸得,我摸不得”一样不讲道理:你明明没有新增用户,成本却在涨;你明明只省了 600 token 的输入,失败路径却在以几倍的速度把钱花出去。
所以别再盯着“这一次调用能不能再短一点”了。该盯的是“能不能让 Agent 一次做对、不空转、不重复犯错”。
2. Agent 最容易踩的六个坑,每一个都在烧 token
我习惯把 Agent 的常见失败模式总结成六个坑。这六个坑不是理论推演,是我在多个项目里都撞过的实际事故来源。识别它们是省钱的第一步,因为只有知道钱是从哪个环节漏掉的,才谈得上堵漏。
2.1 工具返回信息过载:把整本手册塞进上下文
最容易犯的错误,是设计工具接口时从“业务完整”出发,而不是从“决策需要”出发。
比如一个订单详情接口,业务同学希望接口返回全部字段:订单编号、商品列表、优惠明细、发票信息、物流轨迹、售后记录……这些数据全部拼进工具返回结果,然后原封不动地交给模型。单次工具返回可能就有 1500 token,Agent 在里面找“当前订单到底有没有付款”这种关键信息,跟翻索引查页码差不多。
问题在于模型没有工作记忆,每次工具返回都会写进上下文,后续每一轮对话都要背着这些历史继续跑。一次工具调用返回 1500 token,十次就是 15000 token,这笔账轻轻松松翻倍。
提示:工具返回不是给业务系统看的,是给模型做决策用的。接口可以完整,但喂给模型的返回必须做裁剪和摘要。
2.2 时间感知缺失:Agent 不知道自己在等什么
这个坑特别隐蔽。当 Agent 发起一个异步任务时,比如“生成报表”“批量发送通知”“等待用户确认”,它往往没有办法感知时间流逝。你问它“报表好了吗”,它会说“我看看”;工具说“还在处理中”,它就傻等;过了五分钟还没好,它不会主动降级、不会告知用户、不会重新规划,而是维持上一次的失败状态继续重试。
这也是一种典型的 token 浪费:每一轮轮询都是一次完整的上下文调用。如果 Agent 在异步任务上轮询了八次,那就等于八个完整任务在同时烧钱。
2.3 参数校验太宽松:错误从源头传入下游
Agent 从自然语言里抽取工具参数,本质上是把“人话”翻译成“结构体”。翻译过程难免出错,比如把“明天下午三点”翻成“15:00:00”时少了个日期,把“北京总部”翻成一个不存在的地点 ID。
很多团队在工具侧没有做参数校验,或者只做了非空校验。结果就是,模型传了一个格式正确的空值,工具层反而报错了。这个错误的根源在模型侧,但报错信息反馈给模型之后,它往往根本意识不到是自己参数的问题,反而拼命尝试别的路径,甚至编造参数再试一次。
比较理想的处理方式是:参数错误是不可重试的错误类型,必须在源头拦截,不给模型第二次犯错的机会。
2.4 全量状态重述:每一次重试都在重复整个历史
这是最高频的浪费模式。当 Agent 需要维护多步任务状态时,很多实现方案会把全部状态序列化成文本,塞进每轮 Prompt。比如“当前任务进行到第 5 步,前 4 步动作分别是……当前已有库存数量是……用户对第 1 步表达过不满……”
这个写法在轮次少的时候看不出问题,但多步任务跑到十几轮时,状态重述内容可能会超过真正的问题描述。最恶心的是,如果其中一步失败并触发重试,重试时会把“全套历史 + 上次失败记录 + 新计划”一起发出去,token 消耗直接爆表。
2.5 失败重试没有上限:踩坑后的无底洞
这是最容易被忽视的预算黑洞。Agent 框架里默认会给工具调用加重试逻辑,但重试上限往往配得很随意——有的团队干脆没设上限。
没有上限意味着什么?意味着模型可以因为一个“网络超时”连续重试七次,或者因为“参数被拒绝”换个说法再试四次。每一次重试都不是简单的逐步执行,它会重新生成整段思考链和工具输入,token 消耗是几何级上升的。如果重试期间还把失败信息写进了上下文,下次重试的 prompt 就更长,恶性循环。
2.6 目标优先级含糊:Agent 在路上忘了主线
最后这个坑属于“方向问题”。当用户让 Agent 同时完成多个目标时,比如“先查一下库存,再对比价格,顺便帮我看看物流到哪了”,如果系统提示词里没有明确的优先级划分,模型就会凭感觉排序,甚至在一个次要目标上反复尝试,把主线任务晾在一边。
我见过一个案例:用户让 Agent 查两个商品的价格,Agent 却花了两轮去确认“要不要查”,又花三轮去解释“为什么两个价格不一样”,真正查询动作反而只做了一次。这种跑偏消耗的 token 不产生任何业务价值,而且因为结果看起来“很努力”,还很难被自动化监控识别出来。
3. 我实际用过的“防踩坑”三板斧:工具契约、决策护栏、记忆减负
识别坑之后,真正要做的是从架构层面把它堵上。我自己的经验可以浓缩成三条:工具契约让模型拿到高信噪比的决策输入;决策护栏让模型在错误路径上走不远;记忆减负让上下文永远只装当前最需要的东西。
3.1 工具契约:统一返回三字段,让模型一眼看懂
先说返回格式。我给所有工具接口约定的返回结构是三个字段:ok、data、error。成功返回长这样:
{ "ok": true, "data": { "order_id": "A1001", "status": "paid", "summary": "订单 A1001 已支付,金额 236.00 元,预计明天送达" }, "error": null }失败返回长这样:
{ "ok": false, "data": null, "error": { "code": "ORDER_NOT_FOUND", "message": "订单 A1001 不存在", "retryable": false } }这个设计有几个关键考虑。第一,data 里塞的是 summary,不是完整业务详情,因为模型只需要摘要就能做决策,完整详情是业务系统才需要的。第二,error 里必须带 retryable 字段,明确告诉模型“这个错误不值得重试”,把重试决策从模型自由发挥变成有依据的结构化判断。第三,统一结构让模型识别成本下降,它不需要在每一轮重新学习不同工具的返回格式长什么样。
建议:把工具返回的摘要字段看成“给模型看的简报”,只留下它做判断必需的信息。全部详情可以放在日志系统里,模型不需要背走。
3.2 决策护栏:预算、步数、停损条件不能交给模型自觉
再好的 Prompt 也只是概率性的约束。真正的护栏必须落在执行层,用代码保证模型走不远。
我常用的护栏包括三类。第一类是总步数预算:一个任务最多允许多少次模型动作,超过就强制中断并返回“任务超限,需要人工介入”。第二类是重试预算:单个工具失败重试次数上限,以及重试之间的退避间隔。第三类是止损条件:当模型连续两次给出相同的失败动作时,直接触发旁路,而不是让它换一种方式继续烧钱。
伪代码大概是这样的逻辑:
def guarded_step(state, budget): if state.step_count >= budget.max_steps: return AgentResult.fail("max_steps_exceeded") if state.duplicate_retries >= 2: return AgentResult.fail("repeated_failure_detected") result = agent.run() state.step_count += 1 if result.failed and not result.retryable: return AgentResult.fail("non_retryable_error") return result这套护栏的价值在于,它把“不要反复横跳”这种感性指令变成了硬约束。模型再笨、再执着,最多也只能在预定的预算内空转,而不是无休止地把账单滚大。
3.3 记忆减负:只发工作记忆,不背全量历史
最后一个关键改动是话术与状态分层。
传统做法是上下文里放“全部聊天历史 + 全部工具返回 + 全部中间状态”,这在一两轮的时候够用,但在长任务里是灾难。我现在采用的方式是维护一份“工作记忆”:只包含任务目标、当前进度、最近一轮的关键结果、以及待决策事项。每次运行时只发送这份工作记忆,不发送完整历史。
比如一个多步骤任务,每步完成后都把关键结论压成一个状态摘要,塞回工作记忆。下一步决策时,模型只看到“当前状态:订单已支付;下一步:确认物流信息”,而不是从头到尾的每一句话。这就像上班交接班时只交接关键进度,而不是把昨天每一个开会讨论都重新讲一遍。
在代码上就是一个很轻量的状态容器:
def update_working_memory(memory, step_summary, new_decision): memory["goal"] = step_summary.get("goal", memory.get("goal")) memory["latest_summary"] = step_summary memory["pending_decision"] = new_decision # 不追加历史,保留固定大小 return memory固定大小的工作记忆,意味着无论任务跑了多少轮,单轮输入都不会因为历史积累而膨胀。它把成本曲线从“随轮数线性增长”变成“随轮数基本持平”,这种优化远比压缩几个字来得实在。
4. 这笔账怎么算:压缩 Prompt 与防踩坑改造的真实收益对比
4.1 先看看压缩 Prompt 能省多少
假设单次成功任务的基础输入是 2000 token,共 8 轮,那么单任务上下文消耗是 16000 token。压掉 30%,变成每轮 1400 token,单任务降到 11200 token,省下 4800 token。听起来不错。
但这里有一个隐藏前提:压缩之后,任务成功率和之前持平。现实中这个前提经常不成立。Prompt 压缩后指令边界变模糊,失败率往往不降反升。如果没有监控指标,团队很容易把上涨的失败成本归咎于“模型发挥不稳定”,而忽略真正的变量是 Prompt 被改短了。
4.2 防踩坑改造的省法不一样
防踩坑改造的省法不是压单价,而是降低“额外消耗的倍数”。
假设没有任何优化时,失败率是 20%。失败任务的代价是正常任务的三倍(因为包含重试、额外上下文、状态错乱)。1000 个任务中 200 个失败,每个失败多花的代价相当于 2 个正常任务:200 个失败任务 × 2 个额外任务量 = 400 个任务量。加上成功任务的 800 个任务量,总共相当于 1200 个正常任务量。
通过防踩坑改造,把失败率从 20% 降到 5%,那么失败任务只剩 50 个,多花 100 个任务量。总消耗变成 950 个任务量。省下的 250 个任务量,换算成基础成本,相当于省了 21%。
你会发现:压 30% Prompt 省下的那 4800 token 是单任务层面的小账;而把失败率从 20% 压到 5%,省下的是 21% 的任务总量级大账。两者完全不在一个数量级。
4.3 算给老板看的表
如果老板只看 token 费用,可以直接拿这张表说事:
| 优化方向 | 投入方式 | 1000 个任务的 token 消耗 | 风险/副作用 |
|---|---|---|---|
| 压缩 Prompt | 人工精简指令与提示词 | 基础部分降低约 30%(约省 4.8M token) | 指令模糊、失败率上升,可能抵消收益 |
| 防踩坑改造 | 工具契约、决策护栏、工作记忆 | 基础部分不变,失败相关消耗降低约 60% | 需要一次性工程投入,开发成本 |
还有一个没法直接写在表里的收益:失败率降低之后,人工介入次数也随之下降。人工介入小时费用远高于 token 费用,真正让老板肉疼的往往是这个隐形项。你把失败率压下去,不只是省 token,还省了好几档人工成本上限,这才是“比压缩 Prompt 更省钱”那句论断成立的底层原因。
5. 落地时容易翻车的三个反模式(以及怎么判断该止损换路)
防踩坑改造也不是越多越好。工程上一旦用力过猛,就会从“优化”变成“新的坑”。这块我踩过不少,单独提醒几句。
5.1 护栏过重:Agent 变成不会走路的木偶
护栏是必要的,但护栏过重会让 Agent 丧失自主性。某些团队把所有工具参数都做硬校验,每个动作都要经过规则引擎审批,Agent 本质上变成了脚本调度器。这时候它已经不能叫 Agent 了,因为它没有任何决策空间,遇到规则之外的场景就是直接失败。
我的判断标准是:护栏应该拦“走出底线”的动作,而不是指导“每一步怎么走”。留给模型的自由空间还是要足够它去规划路径、尝试多步组合、处理局部异常。如果一次任务里模型 80% 的动作都被规则硬控,那这个项目应该直接改写成规则引擎,而不是花钱调用大模型。
5.2 契约过度设计:错误码比业务还复杂
工具契约的另一个极端,是把错误码拆得过于精细。比如订单场景设计 200 个错误码,每个码有各自的 message 和 retryable 标志,模型根本背不住,遇到不认识的码反而会开始猜测和编造。
错误码设计要以模型能“本能使用”为标准,而不是以业务完整为标准。通常 5 到 15 个高频错误码就够用了,剩下的大部分错误统一归为 UNKNOWN_ERROR,并明确标注“不可重试,放弃当前目标并告知用户”。模型看到一个大类错误的反应,比看到一百个细分错误码要可靠得多。
5.3 什么时候该止损换路,而不是继续加规则
最后说一个比较残酷的现实:有些坑不是工程能填平的,是模型能力本身的短板。
如果你已经做了工具契约、决策护栏、记忆减负,失败率还是降不下来,那大概率不是少踩坑的问题,而是当前模型根本承担不了这个任务的复杂度。此时继续加规则、加验证、加重试,只会让系统越来越复杂、越来越难维护,成本也水涨船高。这种时候该做的是换一个更强或者更适合的模型,或者干脆拆任务:把复杂任务拆成多个简单 Agent 串行处理,用流水线代替一个大而全的 Agent。
我以前在一个多工具协作场景里硬撑了两个版本,试图用 Prompt 和护栏把所有边界情况都兜住,结果每次改动都引入新问题。后来退了一步,把任务拆成三个小 Agent 串联,整体失败率从 16% 降到了 4%,改造量却少了很多。这个故事给我的启发是:防踩坑省钱的下一步,是学会合理地“认怂”和改架构路径,而不是在一个不可救药的方案上继续贴膏药。
最后分享一个实测阶段的小技巧:上线前给你的 Agent 做一轮“故障注入测试”,主动让工具返回各种错误、超时、参数异常,观察模型在错误路径上的表现。如果它在错误发生后的两轮之内能正确止损,说明护栏和契约设计到位;如果它在这个错误上反复尝试三遍以上,那你上线后大概率会看到账单和投诉同步上升。这个测试成本极低,却能在真正烧钱之前把所有雷点提前暴露出来。