1. Efficient Agents 到底优化了什么:先看懂这个项目要打的痛点
Efficient Agents 这个词,最近在工程圈里被频繁提起。它不是一个具体框架,也不是某个新模型的名字,而是一类“用更少的 token、更低的延迟、更稳的推理,完成同样任务”的智能体工程方案合集。我接手这个方向,是在一个内部代号叫“模拟项目X”的跨终端客服系统里。系统本身跑得通:用户问一句,Agent 能调工具、能查资料、能给出答案。但打开监控面板那一刻,人就麻了——单次会话平均消耗的 token 高得吓人,峰值时一个简单问答要等十几秒才出结果,遇到稍微复杂点的任务,Agent 还会在工具调用之间反复横跳,把同一个查询查了三遍。
这个项目想解决的就是这类问题。它适合正在做 Agent 落地的人参考:不管是做客服机器人、工单自动分类、定时巡检,还是内部知识库问答,只要你的 Agent 已经“能跑”,但觉得响应慢、成本高、逻辑乱,那这篇文章里的思路就能直接搬走。
先说结论:Efficient Agents 的核心,不是在单一模型上做文章,而是从路由策略、上下文管理、工具调用方式、缓存复用这四个层面,把每个环节的浪费挤出来。我实测下来,同样一轮问答,优化前后的 token 消耗能差三到五倍,延迟能压掉三分之二,这是单纯换一个更大的模型完全做不到的。
1.1 “能跑”不等于“有效率”:智能体工程的成本真相
很多团队在第一个 Agent Demo 跑通之后,会陷入一种“甜蜜的错觉”:模型能理解意图,工具能返回结果,整个链路是通的,于是急着往上加功能。但只要用户量一大,问题就炸出来了。
第一个炸的是 token 账单。大模型按 token 计费,而 Agent 的结构性浪费在于:你好不容易生成了一段不错的回答,结果因为它要“确认一下工具结果”,又把之前的对话历史完整发给模型一次。对话越长,每轮调用携带的历史越多,成本呈线性甚至超线性上涨。群里有人贴过这样一张现象截图:一次完整的产品咨询,用户只说了十几句话,Agent 却累计发出了四十多次模型请求,其中一半是在反复读取同一个库存接口。
第二个炸的是延迟。模型推理本身要时间,工具调用要网络往返,再加上重试和循环,用户感受就是“对面在慢吞吞地打字”。这比幻觉还致命,因为幻觉偶尔发生,慢是每一次都慢。
第三个炸的是稳定性。Agent 是“自走型程序”,它自己决定下一步调什么工具。但如果不加约束,它会陷入死循环、调用错误参数、或者在两个工具之间来回横跳。我在模拟项目X里见过最离谱的一次:它为了确认用户地址,把地图服务、配送服务、订单服务各调了四遍,最后给出一句“无法确认”。
所以 Efficient Agents 这件事,本质上是做减法而不是加法。它不是为了追求极限性能,而是为了让你现有的 Agent 系统,在保持效果不退化的前提下,把开销和延迟压下来。
1.2 四类典型场景里的效率痛点
结合我接触过的实际项目,效率痛点主要集中在四类场景里,各有各的恶心。
第一类是客服问答。特点是高频、短对话、长尾问题多。痛点在于:短期会话稍长,Agent 就开始把无关历史也塞进上下文;而且客服场景经常要查订单、查优惠券、查物流,每查一次就是一次工具调用,来回反而拖慢回答。
第二类是工单与内容处理。比如自动分拣工单、总结摘要、提取结构化信息。这类任务输入往往上千 tokens,但输出只有几十 token。问题是,Agent 为了判断该用什么工具,会把整篇文档反复发给模型,等于每个文档被读了三四遍。
第三类是定时巡检与数据处理任务。凌晨跑批,要求稳定、便宜、错误可重试。这里的问题不是延迟,而是成本。明明可以用小模型处理的逻辑判断,因为链路里没有路由,全部发给了旗舰级大模型,一个月下来账单感人。
第四类是嵌入式或者桌面端集成。用户把 Agent 直接嵌到 App 里,对首字响应特别敏感。这类场景里,工具调用链路稍微绕一点,用户就流失。
这四类场景有一个共同特征:不是模型不够聪明,而是工程架构把模型的聪明用在了不必要的地方。Efficient Agents 的理念就是,把模型的能力用在刀刃上,把那些机械的判断、压缩、缓存工作交给工程代码来做。
2. 设计思路拆解:想做高效 Agent,先做这三件事
解决效率问题不能靠“调 prompt 试试”。Prompt 能微调行为,但解决不了结构性浪费。我在拆模拟项目X时,把所有耗时和 token 消耗来源做了个分布统计,发现三个大头:模型选型单一、上下文无节制膨胀、工具调用串行且重复。对应的设计调整,就是路由分层、上下文压缩、工具并行复用。这三件事,是整个 Efficient Agents 方案的骨架。
2.1 路由层:先判断该动用多大的模型
我见过很多 Agent 系统,所有请求统一发给同一个“能力最强”的闭源大模型。开发者的想法是:反正差不了多少钱,用最好的最省心。但实际不是这样。Agent 的请求分很多种:比如“用户输入了一句话,判断该用哪个工具”,这是单纯的意图分类任务,用中小模型完全能胜任;比如“根据工单内容提取客户名称、商品编号、问题分类”,这是结构化抽取,中小模型也能干;真正需要旗舰模型出场的,是最后一步生成回答,或者遇到模糊需求时进行澄清。
所以路由层,就是做一个前置判断,把请求按复杂度和类型分发到不同路径。它的收益不止是省钱,还有速度:小模型推理快,简单问题几秒内就能回,旗舰模型用来解决硬骨头,整体体验绝对不是“一刀切”能比的。
具体实现上,路由的判断逻辑可以很简单:第一条策略,任务类型白名单,比如工单分类、关键词提取、情感判断这些固定类型,直接指定用小模型;第二条策略,输入长度阈值,超短问题走快速通道,长文档走抽取摘要通道;第三条策略,兜底的复杂会话,才交给旗舰模型,并且设置人工兜底标记。
模拟项目X里面,我甚至把路由层做成了“三级漏斗”:先过一个正规表达式加关键词规则,能命中就直接返回;命中不了再走一个小模型做意图分类;两个都判断不了,才升级到大模型链路。这个漏斗看起来传统,但实测能截掉将近四成根本不需要模型介入的请求。
2.2 记忆与上下文管理:把 token 花在刀刃上
上下文膨胀是 Agent 成本失控的第二大原因。这里要分清一个问题:用户要的上下文记忆,和模型推理所需的上下文,不是同一个东西。用户希望 Agent 记得三天前的诉求,那是产品语义层面的记忆,做成数据库查询就够了;而模型每次推理时都需要发送的“历史对话全量”,是计算层面的负担,必须做瘦身。
我在项目里落地了一套“三级记忆”结构。第一级是原始对话窗口,只保留最近的几轮,用于即时理解;第二级是摘要记忆,每一段历史对话结束后,用模型生成一个短摘要,存进向量数据库,等需要的时候再按相关度调出来;第三级是结构化记忆,比如用户身份、订单号、偏好标签,存到 JSON 或关系表里,需要时精确读取。
这套结构的核心原则是:模型上下文中只放当前任务真正需要的信息。系统提示词里不要塞一堆“你是一个乐于助人的助手,请遵循以下规则……”之类的废话;工具定义不要全量塞入,按用户意图动态选择三到五个相关工具的描述。光是这两项,就能把一次请求的输入 token 砍掉很大一块。
还有一点很关键,就是缓存。Agent 的系统提示词、工具 schema、常用的知识库片段,很多内容每一轮请求都在重复发送。把这些内容做清晰的静态与动态分层,静态部分配合接口的 prompt 缓存能力,能有效降低重复计费。实现上,我会把系统提示词拆成固定部分和变量部分,固定部分尽可能保持一致,命中缓存后这部分 token 就不再计入每次调用的成本。这是很多团队忽略的红利。
2.3 工具调用的并行化:减少一次任务,多次往返
Agent 慢的另一个根源,是工具调用串行。它接到一个复杂问题“帮我查一下商品的库存、价格和评价”,最优做法是三个查询同时发出去,但很多 Agent 的实现是:先调库存,等返回;再调价格,等返回;最后调评价,等返回。三次串行网络请求,加上每次都要发一次模型推理,延迟能不爆吗。
Efficient Agents 这里要解决的是两个问题:一是让 Agent 学会在同一轮推理里声明多个工具调用,按依赖关系批量执行;二是在应用层做并发控制,把无依赖的调用放入线程池或异步任务并发执行。
但这也有个陷阱,就是并行调用之后,如果多个结果相互矛盾,Agent 会失去判断力。我后面会单独说这个问题。先记住原则:能并行的不要串行,但并行的结果必须遵循“统一汇总、集中消歧”的逻辑,让 Agent 在一个清晰的上下文里看到全部结果再作答。
3. 实操落地:从零实现一个低 token 高并发智能体
讲完设计理念,接下来是这套方案落地时会碰到的具体环节。我按一条完整链路的顺序来写,从最小闭环讲到成本控制,再讲记忆压缩,最后给一段可以直接改用的核心代码骨架。
3.1 搭建最小闭环:先有一个能跑的全链路
第一步别管优化,先把全链路跑通。一个基本的智能体链路包含五块:入口接收用户输入、路由判断、模型推理、工具调用、回答生成。我用一个最简单的结构说明工作流:
- 用户输入到达后,先把消息推入一个统一的消息对象,记录会话 ID、用户 ID、时间戳。
- 路由层做快速判定。这一步先用关键词和正则判断;如果命中“查订单”、“查物流”这类固定表达式,直接分配对应的处理节点;否则进入意图模型分类。
- 根据路由结果,从上一步的结果中携带“候选工具列表”,传给后续的模型调用层。
- 模型层根据系统提示词、精简后的记忆、候选工具列表,生成回答或工具调用序列。
- 工具执行层按依赖关系执行,结果回填到消息对象。
- 生成最终回答,同时把本轮记录写入日志和记忆存储。
第一次实现,不用做得很花哨。确认整个链路能稳定跑完,再把优化一项项加进去。很多团队一上来就搞复杂编排,结果排查问题的时候根本定位不了是哪一层在拖慢。
3.2 第二步:加一道“成本闸门”
这个是我强烈建议加的一步。成本闸门是一个独立模块,它在模型请求发出之前和之后各检查一次。
发出前,它负责检查请求对象的预估 token 数、模型路由是否符合策略、上下文是否超出预算阈值。如果发现某条请求的系统提示词超过约定大小,或者某个请求强行指定了旗舰模型但实际任务只是分类,就直接降级或拒绝发出。发出后,它负责记录真实 token 消耗,并回写台账,方便统计每个会话、每种任务类型的单价。
模拟项目X里,我设了一个双阈值机制。单次模型请求的输入 token 上限,默认设为 4 万。超过这个数的请求,先走一次上下文裁剪;裁剪后仍超限的,直接打断当前分支,让 Agent 明确回复“信息量过大,请分步查询”。另一个阈值是单会话累计 token 上限,比如 8 万,超过后强制触发会话摘要,把早期历史压缩成要点再继续。
这套闸门看着简单,但在真实项目里特别管用。它给了系统一个显式的“安全边界”,避免 Agent 在失控时账单爆炸。我把一条告警接入到监控里,token 消耗超过当日均值三倍就报警,踩过一次坑之后,再也没有出现过月底突然多出一大笔费用的情况。
3.3 第三步:上下文压缩与持久记忆的落地
上下文压缩有两处要落地。第一处是“会话内压缩”:当对话轮数超过阈值,比如 8 轮,就把最前面的若干轮历史提取出来,生成一段摘要,然后把原始内容从上下文中移除,只保留摘要和最近几轮原文。这里有讲究,摘要不是随便让模型写一段话,而是要按“用户目的、已处理事项、待处理事项、关键事实”这个框架来生成,这样后续检索时信息密度才高。
第二处是“跨会话记忆”。用户隔天回来,Agent 需要知道背景。做法是把每次会话结束时的摘要、结构化抽取的信息,写入向量库。下次新会话开始时,先根据用户 ID 做一次记忆检索,把最相关的三到五条记录拼到提示词里。这个技术的难点是搞清楚“相关”的定义。我实测下来,只按时间倒序取最近记录,效果比不上按问题相似度检索,因为用户可能隔了一个月才回头问一个跟上次操作有关的问题。
记忆落地常见的坑,是把所有内容都存成向量,导致检索时你的记忆库里塞满了无意义信息。我的策略是分级:原始对话文本收进冷存储,只保留 30 天下载路径;摘要和结构化事实放进向量库,常驻内存索引。这样又便宜又高效。
3.4 示例代码:一个简化版的高效 Agent 核心
我整理了一个伪代码级别的参照实现,核心逻辑可以直接迁移到自己的 Agent 框架里。这段代码做了三件事:路由分发、并行工具调用、上下文裁剪。
# 简化版 Efficient Agent 核心流程 class EfficientAgent: def __init__(self, routers, executor, memory_store): self.routers = routers # 路由列表: [规则路由, 小模型路由, 旗舰模型路由] self.executor = executor # 工具执行器,支持并发和依赖管理 self.memory_store = memory_store def run(self, user_input, session_id): # 1. 取出精简后的记忆 memory = self.memory_store.retrieve(session_id, top_k=3) # 2. 路由判定:决定调用哪一种模型 model_level, tools = self._route(user_input) # 3. 构建轻量级提示词:只带必要记忆和工具定义 prompt = { "system": compression_rules("标准系统提示词"), "history": memory, "tools": dynamic_tools(tools), } # 4. 模型推理,支持一次返回多个工具调用 response = self._infer(model_level, prompt) if response.is_tool_call(): # 5. 并行执行无依赖的工具 results = self.executor.run_with_dependencies(response.tool_calls) return self._final_answer(results) return response.text def _route(self, text): # 规则路由优先 if re.match(r"查订单|查物流", text): return "small", ["order_service", "trace_service"] # 小模型分类路由 category = self.routers["classifier"].predict(text) if category in self._simple_types: return "small", self._tool_map[category] # 兜底用旗舰模型 return "large", self._tool_map["default"]这段代码里的dynamic_tools函数值得多说一句。它根据路由结果,只把当前任务可能用到的工具定义拼进提示词,而不是把所有工具的 schema 全部塞进去。实测中,工具定义数量从 20 个减到 5 个,输入 token 明显下降,而且模型反而更听话了,因为选择少了,误调罕见工具的几率也少了。
executor.run_with_dependencies是另外一个独立的模块。它把并行工具调用封装起来,每个工具返回后会自动更新一个共享上下文对象,后续依赖冲突的时候,Agent 能在最终答案里显式提示差异,而不是把矛盾吞掉。
4. 我在真实项目里踩过的坑和排查笔记
优化 Agent 效率的过程,与其说是在调参数,不如说是在和各种隐蔽的工程问题打持久战。下面这些问题都是我实操中真实撞上的,直接讲结论和排查方法。
4.1 跑圈和重复调用问题
模拟项目X上线第一天,我的 Agent 在压力测试里暴露了一个尴尬的行为:用户问一句“什么时候发货”,它依次调用了订单服务、物流服务、又调回订单服务,最后竟然重复调了一次物流服务,前后五次,耗时 27 秒。排查下来,根因是工具描述写得有歧义。订单服务和物流服务都在工具描述里提到“可查询发货时间”,模型不清楚到底该选哪个,于是“保险起见”都调了。
这个问题的解决办法分两层。第一层是工具描述必须职责分离,明确边界,比如订单服务只负责订单状态,物流服务只负责物流轨迹,两个权限不要重叠。第二层是在 Agent 的执行回路里加“工具调用去重”机制:同一会话内,对同一组参数的同一工具,短时间内只允许执行一次,第二次直接读取第一次的结果缓存。这个缓存时间不用长,几十秒就够,足以打断绝大多数重复调用。
还有一个看似不起眼但很管用的排查技巧:把工具的输入参数写成枚举或正则约束,比如发货时间查询的query_type参数,只允许latest或estimated。模型乱传字符串的心智负担越小,调错参数的概率越低。
4.2 并行工具调用的“一起崩”问题
并行化之后,新的坑又来了。某次我问“这个商品有没有优惠券、库存够不够、评价如何”,Agent 同时发起了三个请求,结果库存接口超时,优惠券接口返回正常,评价接口返回了一个空列表。模型在汇总时,居然把超时当成“库存为 0”来回答了。
这个问题的本质是:工具调用失败的结果不能被静默吞掉。我在执行器里加了一层结果标注,每个工具返回都带一个status字段:成功、失败、超时、空结果。模型拿到结果的时候能明确看到哪些数据不可信,并且在最终答案里如实反馈“库存信息暂时无法获取,请稍后重试”。别小看这个细节,它直接关系到 Agent 的输出可信度。
排查并行问题的时候,日志是关键。我给每个工具调用都打了独立的 trace_id,保证同一轮并行的多个调用能按 trace_id 串成一组。一旦出现乱回答,拉日志一看,谁超时了、谁返回了错误体、谁响应了 200 但内容为空,一目了然。
4.3 上下文压缩阈值设太高或太低,效果都会崩
上下文压缩的阈值是个双刃剑。最初我把压缩阈值设得很激进,对话超过 4 轮就开压,结果模型频繁丢失前文细节。让用户报个订单号,前面聊了三轮,压缩之后模型把订单号弄丢了,用户气到给我们客服打电话。
后来我把阈值放到了 10 轮,效果是稳定了,但 token 又开始浪费。最后折中的方案是“动态阈值”:以 token 数而不是轮数作为触发条件,并且压缩时把关键实体独立提取出来,单独放到一个“事实清单”里,不塞进摘要正文。比如订单号、电话号码、日期,这些是绝对不能丢的信息。摘要可以写得模糊,但事实清单必须精确。
建议压缩触发条件设为累计输入 token 超过 3 万,或者对话轮数超过 8 轮,两者谁先到就执行。这里的数字可以从你的实际 token 分布观察表得出,不同业务差异很大。
4.4 问题速查与推荐配置
我把自己遇到的高频问题做成了一张速查表,每次新项目接手,都会先对照这张表排查一遍。
| 症状 | 根因 | 解决方向 |
|---|---|---|
| 单次问答 token 消耗过高 | 工具定义全量塞入提示词 | 按路由动态裁剪工具列表 |
| 简单问题响应超 8 秒 | 所有请求走旗舰模型 | 接入分层路由,中小模型优先 |
| Agent 重复调用同一工具 | 工具职责交叉或参数歧义 | 工具描述去重、加请求缓存 |
| 长对话后答非所问 | 上下文膨胀导致关键信息被淹没 | 引入摘要+事实清单双轨记忆 |
| 并行工具结果相互矛盾 | 失败结果被静默吞掉 | 返回状态标记显式进入上下文 |
| 凌晨巡检任务费用过高 | 复杂任务和简单任务混用同一模型 | 按任务类型拆分配置矩阵 |
| 模型总是调错参数 | 工具参数描述过于开放 | 用枚举和正则约束参数取值 |
5. 实测数据与成本账:省下来的才是利润
方案做没做对,最终要看数据。我把模拟项目X优化前后的几组核心指标列出来,方便你对优化结果有个直观预期。
5.1 延迟优化记录
优化前,一次普通客服问答的平均端到端延迟在 11.8 秒左右;其中模型推理占 5 秒,工具调用占 4 秒,其他开销占 2 秒多一点。优化后,简单问题的平均延迟降到 2.6 秒,复杂问题在 6 秒左右。
延迟拆开看,路由层帮了大忙。简单问题直接用中小模型,推理时间从 5 秒降到 1.5 秒。工具并行使一次三个查询的请求从 12 秒降到 4 秒。上下文裁剪之后,长对话的排队时间也明显减少,因为每个请求的 token 数变小,模型的 batch 效率高了。
5.2 token 消耗账本
token 账目有些意外,但也合理。单次会话平均消耗从优化前的 2.3 万降到 7 千,降幅接近七成。产生这个结果的主要原因是动态工具列表加路由分层。原本每次请求都要携带 20 个工具定义,加上一长串历史记录;现在只带 5 个左右必需工具定义,加上精简后的记忆片段,这部分的输入 token 下降幅度最大。
另一个被忽略的收益是缓存。系统提示词做成固定模板之后,稳定住了重复内容,这部分钱省下来非常可观。另外,中小模型承接了大约六成请求,单价只有旗舰模型的几十分之一,成本结构彻底变了。
5.3 效率与效果的平衡经验
最后说几句平衡经验。追求效率不能伤害最终回答质量。我从测试中总结了一个原则:能用小模型解决的问题,绝不升级到旗舰模型,但判断答案是否可信,必须有独立的校验渠道,不能完全依赖模型自评。
我给每个任务类型设定了一个“效果红线”。工单分类准确率低于 90% 就要回退到更大的模型;客服满意度低于某个目标值就要放宽上下文裁剪力度;工具调用成功率要保持在 95% 以上,否则回查工具描述质量。数据驱动,而不是拍脑袋。
根据我个人经验,最值得优先投入的永远不是换更好的模型,而是把上面的路由、记忆、工具并行这三件事先做扎实。它们投入小、见效快,而且不挑模型,哪怕以后换了模型供应商,这套架构依然成立。
最后分享一个小技巧:给每个 Agent 请求打上“成本标签”,把 token 消耗、模型类型、路由判定结果、耗时全部写进日志。跑了三个星期之后,你会得到一张非常精准的成本分布表,所有关于效率和预算的争论,都能用这张表直接一锤定音。