最近圈子里聊 AI Agent 的密度,几乎到了逢人必谈的程度。从 Copilot 到各种五花八门的自主智能体,概念和 Demo 满天飞。但有一个问题被有意无意地忽略了:当 Agent 从技术演示走进真实业务,你的基础设施到底能不能扛住?我最近帮几个团队做了 Agent 项目的上线前压测和架构走查,发现大家普遍盯着模型效果、提示词工程,却严重低估了背后的工程代价。今天不聊 Agent 能做什么,只聊这三个最容易被忽视、且会在上线后直接引爆的成本点。
如果你正在把 Agent 从实验阶段推向生产环境,或者准备用 Agent 打磨一个正经产品,这篇文章值得你花十分钟看完。我会结合最近实测的几个项目,把 token 成本、并发压力、状态混乱三个核心代价拆开揉碎,最后再补上我在实践里踩过的一些坑。
1. 代价一:Token 账单与不可控的调用成本
很多人规划 Agent 成本时,用的还是传统 API 调用的思维:一次请求,一次计费。但 Agent 的调用模式是循环迭代的——它要思考、调用工具、观察结果、再思考,一个任务可能要打十几甚至几十次模型接口。这种模式带来的账单增长,常常会超出预估一个数量级。
1.1 “流式输出”与“上下文膨胀”两个账单陷阱
第一个陷阱是流式输出。我知道不少团队为了让用户体验更好,默认开启流式。流式的好处是首字延迟低,用户感觉“响应快”,但别忘了,流式在服务端实际上是把整段生成内容完整算完、再分段吐出来,Token 消耗并不会减少。很多人在设计计费预估时,误以为流式等于“生成多少、算多少”,其实模型内部还是要跑完整个推理流程。
第二个陷阱更隐蔽,叫上下文膨胀。Agent 的核心机制是“把历史信息不断塞给模型”。对话轮次一多,工具返回的内容一长,上下文就会像滚雪球一样越滚越大。我做过的某个模拟项目 X,初期每分钟 Token 消耗约 1 万;到第 30 轮对话后,单次请求的输入 Token 直接涨到 8 万以上。这是什么概念?输入成本与 Token 数直接挂钩,上下文膨胀 8 倍,单次调用的输入成本就涨 8 倍。你以为是模型变贵了,其实是自己的上下文管理失控了。
第三个与“上下文膨胀”强绑定的坑,是工具返回的原始数据。Agent 调数据库查询,经常会把整表数据一股脑返回给模型。比如查一个订单列表,返回 500 行记录,模型其实只需要里面的金额和状态两个字段。这 500 行数据全部进入上下文,在下一次调用里又被完整地当作输入重新计费。我见过最夸张的案例,光工具返回就占了单次输入的 70%。具体怎么排查这类问题,后面的常见问题章节里我会一并展开。
1.2 三个实测有效的降本策略
先说结论:降本的核心不是砍掉模型能力,而是减少无效 Token 进入上下文。
策略一是工具返回裁剪。调用工具前先做字段白名单,只返回模型决策真正需要的字段。比如查天气,只返回温度、降水概率、风力,而不是把整份气象报文塞进去。这能直接降低 30%-50% 的输入 Token。
策略二是上下文压缩与摘要。对话超过 N 轮,就把早期的对话内容压缩成结构化摘要。我通常的做法是:保留最近 10 轮完整消息,更早的内容用user_summary消息替代。这个策略能从根源上抑制上下文无限膨胀的势头。
策略三是预算上限与熔断保护。在应用层设置每个会话的 Token 预算和费用阈值,超过阈值先告警、再限流,最后才是熔断。我用过一个很直接的做法:把“预估费用”放进每个 Agent 步骤的日志里,单步费用超过预设值立即终止任务。实测下来,预算超标项目减少了 90% 以上。
2. 代价二:并发来临时,系统比你想象的更脆弱
如果说 token 成本是慢性失血,那并发压力就是急性心梗。Agent 服务与普通请求服务有一个本质区别:它在单次任务内需要反复调用下游依赖。这意味着一个用户的 Agent 任务,在服务端会被展开成多次模型调用和多次工具调用,占用连接、线程、内存的时间被拉长好几倍。
2.1 Agent 服务并不是标准 Web 服务
传统 Web 服务一个请求通常几十毫秒到几百毫秒就返回了,连接释放得很快。但一个 Agent 任务可能持续几十秒甚至几分钟。我用一个生活类比来解释:普通 Web 服务像点餐窗口,一个人过来、下单、拿走、走人,队伍流动很快。Agent 服务像自助餐厅,每个人进来要反复去多个取餐台挑菜,占着座位吃很久,整个餐厅很快就被占满了。
这种差别直接决定了你不能用传统的“预估 QPS 乘以平均响应时间”来设计 Agent 服务的容量。你需要基于“并发任务数 × 每个任务的平均步骤数 × 每步耗时”来推算系统水位。
2.2 从压测数据看真实水位
我给某模拟项目 X 做过一轮压测。这个 Agent 的任务链是:接收用户指令、调用搜索工具、解析结果、调用另一个分析工具、汇总答案,合计大约 7 个步骤。单任务总耗时平均在 30 秒左右,其中模型调用占 70%,工具调用占 20%,其余是解析和组装。
压测结果(20 并发用户持续 5 分钟):
| 指标 | 数值 | 说明 |
|---|---|---|
| 成功任务数 | 96 | 20 并发下接近极限 |
| 平均任务耗时 | 31.4s | 与单任务基线接近 |
| 服务端 CPU 峰值 | 78% | 主要消耗在 JSON 解析与编排逻辑 |
| 下游接口错误率 | 23% | 工具服务先于应用崩了 |
| 超时任务数 | 7 | 单步重试策略不完善导致 |
看到 20 并发就已经让下游错误率飙升到 23%,你大概能理解为什么我说 Agent 并发不是线性扩展的。每个任务内部有多步串行依赖,任何一步失败都可能触发重试,而重试又进一步放大了对下游的压力。
再说一个大家常忽略的问题:很多团队用“并发数”而不是“QPS”来评估 Agent 服务,这方向是对的,但他们没有意识到,下游的工具接口仍然以 QPS 为计费或限流单位。你的 Agent 每处理一个任务,下游接口可能要承受 7 次请求。如果还混入了重试,这个倍数会更高。所以扩容不能只扩应用层,工具链、数据库、模型网关的容量必须同步评估。
2.3 收益最高的三个防护手段
结合几个项目的实战经验,我确认这三个手段的投入产出比最高:
第一是全局并发控制器。在 Agent 编排层加一个信号量,限制同时运行的任务数。很多团队不做这个限制,任由用户并发触发,结果就是雪崩。我一般设置为max_concurrent_tasks = 单机可承受任务数 × 节点数 × 0.7,留 30% 余量给突发工具重试。
第二是下游限流与快速失败。工具调用层必须单独设置超时和错误阈值。下游一慢、一错,不要默默重试三次,那样只会叠加压力。我踩过一个坑:某工具接口偶发超时,Agent 里默认重试了 5 次,结果下游服务雪崩,最后排查日志看到同一个任务对同一接口的 5 连击,脸都绿了。现在我的做法是:首次超时后快速失败,把错误写进上下文,让模型决策是否换另一种工具方案。
第三是队列化长任务并暴露进度。对于耗时超过 30 秒的任务,不要用同步 HTTP 长连接傻等。提交任务到队列,立即返回task_id,前端轮询任务状态。这既缓解了连接占用,又提升了用户体验。关于队列与补偿机制的配合,第 3 节里会有更详细的状态管理展开。
3. 代价三:有状态的编排与上下文管理
第三个代价是最容易被低估的,也是团队内争吵最多的:Agent 的本质是状态机。一旦涉及多步工具调用,服务端就必须管理任务状态。可很多人的基础设施是“无状态设计”,这就产生了尖锐的矛盾。
3.1 “无状态”神话与现实的碰撞
不少微服务架构都追求无状态,因为无状态方便横向扩容,挂了重启就行。但 Agent 任务天然有状态:当前在执行哪一步、历史消息有哪些、工具返回了什么、下一步该调用什么,这些信息必须在任务生命周期内持续存在。
推进过程中最常见的做法是“先跑在内存里再说”。单机 Demo 没问题,一旦上生产、做多实例部署,问题就出现了:用户在 A 实例发起任务,下一次轮询却被负载均衡转发到 B 实例,B 实例根本没有这份上下文,只能报错。
我强烈建议团队尽早引入外部状态存储。刚开始不需要很复杂的方案,把每个会话的执行状态、消息历史、步骤变量持久化到 Redis 或数据库即可。状态用 JSON 存,字段至少包含session_id、current_step、message_history、tool_results、status。
3.2 断点续跑与会话恢复的工程实现
状态存储不只是为了多实例,还有一个更实际的作用:断点续跑。Agent 任务跑了几分钟,网络抖动导致客户端断连,或者服务发布重启,如果没有持久化状态,用户只能重来一遍。
我在某跨平台系统的 Agent 模块里实现过一套简单的恢复机制,核心思路如下:
- 每个步骤开始时,在状态存储中记录
step_id与step_input。 - 每完成一个步骤,更新
last_completed_step。 - 恢复时,从
last_completed_step的下一个步骤继续执行,而不是从零开始。
这套机制给我带来的最直观收益是:发布重启不再需要用户重新提交任务,系统自动恢复。实现的代码逻辑不复杂,但需要很强的纪律性:每个状态变更都必须落盘,否则恢复无从谈起。
3.3 状态膨胀与治理节奏
有状态还有一个副作用:状态本身会膨胀。消息历史、工具返回原始数据、临时变量全堆在一起,不仅占存储,还会在恢复时重新塞进上下文,造成二次成本浪费。
我的治理方案遵循三步走:
- 状态内只保留“可重放”的数据。比如工具返回结果,保留精简字段;原始大对象存对象存储,状态里只存引用地址。
- 定期快照与清理。超过一定时间未活跃的会话,归档状态并释放内存与存储空间。
- 步骤之间做隔离。临时变量随步骤结束即清理,不要让上一个任务的中间变量污染下一个任务的上下文。
关于第 1 点,这里其实有个和可观测性相关的细节:如果你要做问题回溯,就需要在状态里同时保留上游输入的摘要。这里的平衡,我放在下一节的可观测性部分一起说。
4. 代价之外的隐藏成本:可观测性与可调试性
最后聊一个不在标题里、但实际会伴随前三个代价一起爆发的隐藏成本:可观测性。Agent 系统比传统系统难调试得多。一个任务的失败,可能是模型决策错了,可能是工具返回不对,也可能是上下文被污染了。如果基础设施缺少完整的链路追踪,排查起来就像大海捞针。
4.1 拿不到链路,就没有办法定位问题
传统开发排查问题,看日志、看调用链就能八九不离十。Agent 系统里,一次任务就是一场多轮决策的“连续剧”,单看某一帧根本不知道剧情。你必须把整个任务的完整链路串起来。
我现在维护的项目里,要求每一次模型调用、工具调用都必须携带统一的trace_id。日志里至少包含:
step_id:当前是任务里的第几步。model_input_tokens/model_output_tokens:这一步的 token 消耗。tool_name与tool_response_summary:调了哪个工具,返回了什么。latency_ms:这一步耗时。decision_score:如果有多个候选决策,记录模型最终选择了哪一个。
有了这些字段,排查问题基本就变成了“看回放”。哪一步 token 异常飙升、哪一步超时、哪一步开始进入错误循环,一目了然。
4.2 缓存设计:一个容易被忽视的好杠杆
可观测性顺手解决之后,我建议你投入精力做一层语义缓存。Agent 的多次任务之间,其实有不少重复的子问题。比如多个用户都在问“今天有什么热门话题”,走完一遍 Agent 流程后,结果往往是相似的。如果完全不做缓存,每来一个用户,你都要付一遍完整的推理成本。
我的做法是:在工具调用层设置两级缓存。
第一级是精确缓存:完全相同的查询参数,直接命中,返回上次结果。适合高频且参数固定的场景。
第二级是语义缓存:对用户输入做 embedding 向量化,匹配历史相似问题。相似度超过阈值,直接把历史答案作为参考。这个做法尤其适合知识库问答类 Agent。
需要注意的是,语义缓存不直接返回历史答案,而是把历史答案作为提示词的一部分交给模型参考。这样既省了大部分推理成本,又保留了模型的应变能力。我在实践中用这一招,把重复类任务的成本压低了约 40%。
4.3 两个低成本改造建议
如果你所在团队还没有专职的基础设施工程师,这节内容会特别实用。两个我认为性价比极高的改造:
第一,给项目加上“上下文使用率”指标。在每次模型调用前,计算当前上下文字符数与最大上下文字符数的比值,超过某个阈值就打印告警。这个指标能直接暴露计划外的上下文膨胀,是发现成本失控前最有效的哨兵。
第二,建立“任务成本排行榜”。按天统计哪个任务平均 token 消耗最高,哪个用户触发了最多的工具调用。很多成本问题光凭技术优化很难根治,但把数据摆到台面上,推动业务方调整流程,效果往往立竿见影。
我在实际项目里就遇到过这种情况:一个高成本的 Agent 任务,排查后发现是流程设计上让模型反复调用同一个低效工具。后来推动产品方改了指令设计,成本直接下降了一个级别。这类问题用调参很难发现,有了榜单数据,说服力就完全不一样了。
5. 常见问题与排查技巧实录
把近期几个项目里踩过比较典型的坑集中列一下,供大家参考。
问题一:token消耗突然翻了 3 倍,怎么排查?
优先查语义缓存是否失效,再查工具返回数据是否异常膨胀,最后确认是不是有用户在对话里贴了大段文本。我的排查顺序是“工具返回数据 → 历史消息摘要触发情况 → 用户输入长度”,前三名原因基本覆盖了 80% 的突发上涨。
问题二:单任务偶发卡顿,但系统资源利用率并不高?
这种卡顿大概率不在自己应用,而在下游工具或模型网关。我建议直接给每个步骤打点,看一眼耗时分布。某次我排查一个持续一周的“玄学卡顿”,最后发现是某个工具接口在大对象返回时产生了非常慢的序列化,和我们的节点利用率毫无关系。
问题三:Agent 任务总是在某一步失败,但重试无效?
不要盲目重试。先看失败原因分类:如果是提示词导致的解析错误,重试一万遍也没用;如果是下游限流,重试只会加重问题。我自己处理过最典型的情况是,模型偶尔输出不符合要求的 JSON 格式,加一个“解析失败后反馈错误信息给模型,让它修正”的循环即可解决,而不是简单粗暴重发同一份请求。
问题四:多个任务并发时,上下文互相串了?
这说明状态存储的作用域没做隔离。检查你存上下文时用的 key 是否包含 session_id。有些团队喜欢把上下文存在全局变量里,一并发就串。这是我在代码评审中反复提醒的点:所有 Agent 的临时状态必须绑定 session_id。
再补充一个独家避坑技巧:最开始的阶段尽量选择生产环境可回放的模拟流量。把线上真实流量录制下来,离线重放,在测试环境里压测、调优,看问题是否复现。这比你自己臆想构造数据要靠谱得多,能帮你排除掉大量“只在生产环境出现”的玄学问题。
我在实际使用中最深切的体会是,Agent 的上线难度不在模型,而在工程细节。很多人拿着一个惊艳的 Demo 就冲进生产环境,结果被账单、崩溃和状态问题打得措手不及。如果你正在规划 Agent 项目,我建议你先回答这三个问题:每个任务的平均 token 消耗是多少?20 个并发用户时下游接口扛得住吗?服务重启后任务能恢复吗?这三个问题想清楚了,基础设施的底气就有了。