降低智能体令牌成本:缓存技术、路由策略与上下文优化。
通过提示词缓存与语义缓存、延迟加载工具、模型路由与级联机制、子代理以及常规的上下文管理,降低大型语言模型代理的运行成本。
初期的智能体通常配备500个标记的系统提示词和两个工具,这两个数值往往会迅速上升。据称泄露的Claude系统提示词长度约为24,000个标记。OpenClaw的用户报告称,Gemini 3.1 Pro在第一次响应时就接收了超过150,000个输入标记,而最终回复仅包含29个标记。如果不进行优化,每天处理100条消息、每条消息需要166,000个输入标记的智能体,在Gemini 3.1 Pro上的月费约为996美元,在Claude Opus 4.6上的月费则约为2,490美元。通过一些设计上的调整,这一费用可以降至50到100美元左右。
这里介绍的理念包括提示词缓存、语义缓存、懒加载工具与MCP服务器、模型间的路由与级联、任务委托给子智能体,以及保持工作上下文整洁。每种方法都以不同的方式节省成本,但都会在复杂性或质量方面带来一定代价,没有一种是完全免费的。
相关内容可分为四类。第一类是通过提示词缓存和语义缓存尽可能重复使用令牌。第二类是削减那些始终存在的令牌,比如内存和工具定义。第三类是将任务分配给较小的模型或升级到更大的模型,并在节省成本与质量风险之间权衡。最后一类则是为提升性能和降低成本而保持上下文整洁,同时简要介绍压缩技术。
避免为相同的令牌重复付费
模型成本不仅源于频繁调用模型,还来自于反复处理相同文本而产生的费用。有两种截然不同的机制可以解决这一问题:K/V缓存是提示词缓存的底层技术,而语义缓存则是另一种独立的方案。对于较长的系统提示词而言,提示词缓存能带来立竿见影的效果;而语义缓存则需要更多投入,并伴随更高风险。
K/V缓存的工作原理
在模型生成任何内容之前,它必须先处理提示词,这一过程需要消耗计算资源,从而导致延迟并产生费用。因此,没有必要反复处理相同的内容。
当大型语言模型接收到提示词后,文本会被分词,这些词元会转化为向量,而在每个注意力层中,这些向量又会进一步被映射为K/V张量。
缓存的意思是保存这些张量,这样就不必再次计算它们。推理引擎在生成过程中本来就会保留键/值张量,否则数学运算无法以合理的速度进行。更进一步的做法是在响应结束后仍保留缓存而不将其丢弃。
在下一个请求中,系统会检查提示语的开头是否与它已有的张量匹配。如果匹配,就会直接加载这些张量并跳过后续处理。
用整数就能很容易看出其经济效益。假设处理2,000个标记需要1秒,而系统提示语有10,000个标记。跳过重新计算的话,每次调用就能节省5秒时间,不过实际的预填充处理速度会因配置不同而有很大差异。
与存储的缓存进行匹配时必须完全一致。如果任何标记发生变化,提示语中对应部分的张量就不再适用,必须重新处理。人们经常因此出错:多一个空格、工具定义的顺序改变,或是时间戳位置错误,都会导致匹配失败。
因此,存储的缓存能够加快请求速度,进而降低成本。不过存储缓存并非免费。缓存的键值对会占用服务端的内存,这就是为什么许多服务提供商会在大约5到10分钟后使缓存条目失效。
无需从头构建这一机制,因为框架和API提供商会处理相关事宜,且各有自己的规则。部署开源模型的团队通常会使用内置缓存层的服务框架,如vLLM。而API用户则需遵循各自提供商的政策。
vLLM中的前缀缓存
vLLM中的缓存层会将提示语分割成多个块,对每个块及其前序标记生成哈希值,并将这些哈希值对应的K/V张量存储起来。与大多数方案类似,需要缓存的静态部分应位于提示语的开头。
有三个重要参数:
--enable-prefix-caching用于启用该功能。--block-size用于设置每个块包含的标记数量。当块大小为16时,引擎会在处理完16个标记后开始新的块。--kv-cache-memory-bytes用于明确指定每个GPU的K/V缓存大小。更大的内存可以让缓存的块保存更久,但同时处理多个长请求会更快耗尽缓存空间,导致旧块被提前清除。
还有其他选择,包括使用 RadixAttention 实现前缀缓存的 SGLang,以及专为集成到服务引擎而设计的 LMCache。其工作原理与上述内容一致。
通过 API 提供商进行缓存
大多数团队都会使用 API 提供商,而这些提供商都有相应的规则以确保提示词能够被缓存。提示词的结构必须符合这些要求。OpenAI 就是一个典型的例子:要缓存提示词的一部分,就需要前缀完全匹配,也就是说开头部分必须完全相同。因此,固定的指令、示例和工具会先被放入缓存,而变化的内容则放在后面。
发送 prompt-cache-key 可以将相似的请求归类到一起,从而提高命中率。对于长度为 1,024 个标记或更长的提示词,缓存功能会自动启用,不过前 256 个标记会被用来将请求引导回同一个缓存,因此静态部分需要超过 256 个标记。
Anthropic的运作方式不同:必须通过cache-control参数来启用缓存功能。在两家服务提供商处,通常在5到10分钟无操作后就会触发缓存清除,不过这一时间可以延长。Anthropic允许用户以双倍的价格将缓存保留时间延长至一小时。
自行托管时,节省的时间也就意味着节省了成本。使用API服务提供商则能以更低的缓存输入令牌价格获得好处。OpenAI对缓存输入的折扣最高可达正常输入价格的90%。Anthropic也对缓存输入提供相同比例的折扣,但还会收取缓存维护费用,因此不当使用反而可能比不使用缓存更昂贵。
如图表所示,当提示语中有90%的内容是静态的,并且正确使用缓存时,成本会大幅下降。
为便于比较,人们开发了交互式计算器,这样就可以调整数值。总体而言,对于那些系统提示语长期不变的用户来说,快速缓存无疑是个巨大优势。
语义缓存
语义缓存则是完全不同的概念。它是通过含义进行匹配的:如果新请求与之前的请求足够相似,就会返回已存储的答案。这听起来很简单,但实际上存在明显的陷阱。
通过含义匹配文本需要使用嵌入模型,这些模型会生成向量并通过余弦相似度来进行比较。较高的相似度通常意味着含义相近,不过这取决于所使用的嵌入模型。
该方案旨在将相似的请求与已存在的答案匹配起来。像“法国的首都是什么?”这样的问题以及匆忙提出的“快,告诉我法国的首都是啥”都应该指向同一个已存储的答案,无需再次调用大型语言模型来生成答案。当很多人提出几乎相同的通用问题且相关数据会逐渐过时时,这种做法效果很好。
那为什么不能在所有地方都采用这种方式呢?因为很多问题可能会出现。该设计需要解决以下几个问题:
- 何种相似度阈值才算匹配。
- 答案的有效期有多长。
- 如何处理多轮对话。
- 究竟要存储什么内容。
- 是否需要在前端设置路由器。
- 如何区分不同用户。
- 如果缓存了错误答案会怎样。
生存时间这一参数需要特别关注,因为不同问题的信息过时速度各不相同。其实现原理很简单,但在实际应用中需要元数据过滤器与标签,比如用户、工作空间、语料库版本、角色设定、会话范围、智能生存时间设置,以及判断返回结果是否合格的规则。这实际上会演变成一个小项目。
一种可行的设计是利用语义索引查找更早提出的问题,让不同问题指向同一个存储的答案,从而控制存储空间的增长。生存时间可以根据使用频率来设定:频繁被使用的条目可保留更长时间,而其他条目则予以删除。此外,在日志显示出现重复内容之后再构建此类系统更为明智,而非在一开始就设计,因为有些使用场景根本不适合这种方式。
许多数据库都能完成这项工作,各类库也负责相关实现,其中包括 semanticcache、prompt-cache、GPTCache、vCache、Upstash semantic-cache 以及带有 LangCache 的 Redis。
实际节省的效果相当显著。Redis 宣称可减少多达 68.8% 的 API 调用次数,并将延迟降低 40% 至 50%,不过这一数据部分属于营销宣传,因为它基于理想的问答场景得出。实际效果完全取决于系统配置——那些存在大量重复调用的问答机器人能节省更多资源,而所有调用都唯一的编程助手则节省的资源较少。
当问题变化体现在庞大的静态提示语中时,提示语缓存能发挥重要作用;而当人们不断用不同方式表述同一个问题时,语义缓存则更为有效。15% 的语义匹配率只能带来微小的节省效果,但一旦达到 45% 至 50% 以上,其价值就开始显现。
两者在可能出现的问题上也有所不同。提示词缓存未命中仅会产生正常费用,因此这种故障代价较低且容易被发现;语义缓存未命中同样无害,但虚假命中则不然:用户会满心信任地收到针对另一个问题的答案,而日志中却没有任何提示。正是这种不对称性使得语义缓存需要设定较为保守的相似度阈值、进行用户范围限制,并建立命中结果审核机制,而提示词缓存则主要需要规范化的提示词结构。
普通缓存也值得提及。诸如SQL查询结果、工具输出及检索结果这类成本高昂的确定性任务应当被缓存,以避免不必要的重复执行。一个例子就是那种用于收集摘要所需关键词数据的工具,它会将这些数据缓存起来直到过期,只有再次触发该流程时才会重新执行。
语义缓存是一个有趣的概念,能在某些使用场景中节省令牌,但要实现得好需要一定的工程技巧。
不要预加载闲置的令牌
由于庞大的工具定义或不断增长的内存,系统提示词可能会变得臃肿。小型智能体不会感受到这一问题,但那些持续膨胀的智能体规格可以通过按需获取信息来简化,或者至少尝试这样做。
保持始终加载的层规模较小
当智能体的提示词超过一定长度时,始终加载的层应保持小巧且稳定,而日益增多的细节则应存储在单独的位置。加载几百个工具,或发送不断变化的完整MCP服务器描述,都会让系统变得混乱不堪。
问题不仅在于成本,还在于性能。每当任何一层不断变化时,提示词缓存就会变得极难实现。最上层应当简洁稳定,足以告诉模型当前所在位置以及下一步该去往何处,而不需要预先加载所有信息。
据称 Claude Code 在内存管理上采用了这种模式:始终会加载一个索引文件,其行数不应超过200行,而详细的主题文件则存储在其他地方。实际运行中的表现与系统设计初衷是否一致则是另一个问题。类似的思路也体现在 Claude 的高级工具设置中、Claude Skills 的分层设计中,以及通过延迟加载 MCP 工具而非在提示词中提前写入所有服务器定义的做法中。
它有效吗?
其原理在于:当上下文规模变大时,模型会更难以选择正确的操作。由于这一领域尚处于发展初期,目前已有具体工具展示了其运作方式。
几个月前,Anthropic推出了先进的工具搜索功能,旨在即便模型可访问数百种工具时也能缩小上下文规模。该公司称,在优化之前,工具定义的文本量在5.5万到13.4万个标记之间,而当上下文达到如此大的规模时,选择错误的工具便成了常见的错误。
该搜索工具通过让大语言模型查找工具而非预先定义所有工具来优化上下文。其配置如下:
tools=[
{
"type": "tool_search_tool_bm25_20251119",
"name": "tool_search"
},
{
"name": "search_contacts",
"description": "Find a contact by name or email.",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string"}
},
"required": ["query"]
}
},
{
"name": "send_email",
"description": "Send an email to one or more recipients.",
"input_schema": {
"type": "object",
"properties": {
"to": {"type": "string"},
"subject": {"type": "string"},
"body": {"type": "string"}
},
"required": ["to", "subject", "body"]
},
"defer_loading": True
}
]
定义了一个名为 tool_search 的工具,可使用现成的选项 BM25 或正则表达式,也可自定义。作为示例,有一个工具被标记为延迟执行。只有当工具数量达到10个及以上时这样做才有意义。搜索工作由 Anthropic 执行,因此它将工具结构插入系统提示词的方式以及搜索过程本身都是隐藏的。一旦有工具匹配成功,其定义会以 tool_reference 块的形式直接附加在对话中。
这个设计相当巧妙,虽然需要多一步搜索,但能减少初始上下文的内容。在4,000个工具上的外部测试结果表现一般,因此还有进一步测试的必要。此外,工具还需要有足够的描述才能被找到,而当中间步骤不可见时,调试也会变得更加困难。
这种模式在“优秀的 AI 工程实践”这一普通标签下也会出现。不要让智能体面对庞大而混乱的上下文,应为其提供筛选信息的方法,使其仅在需要时才检查或加载工具。
由此带来的节省程度取决于最初发送的标记数量。另一个计算器则用于比较工具搜索与提示词缓存的效果。
上述示例展示了结合提示词缓存与延迟加载工具后的预估节省效果。两者都能带来节省,但同时使用并不会在单独使用任一方法的基础上产生显著变化。工具搜索不仅关乎成本,还能保持上下文整洁以提升性能。若仅从节省角度考虑,采用这两种方法中的至少一种便是最佳选择。
用低成本模型处理简单任务
本节介绍将提示词路由到不同模型,以及为特定任务使用成本更低的子代理的方法。这两种方式都能降低令牌费用,但也可能影响质量。许多人认为,60%甚至更多的查询都很简单,无需使用最强大的模型,更不必是具备推理能力的模型。
现有的商业产品已经实现了部分功能。据报道,ChatGPT会根据对话类型、复杂度、工具需求以及“深入思考”之类的明确指令来路由请求,而Claude则依靠基于描述的任务分配机制以及内置的子代理(如Explore)。这个概念很容易理解,难点在于如何在不损害质量的前提下实现它。接下来的内容将介绍预测性路由和输出校验方法,如级联处理与子代理机制,第三部分则会计算由此带来的成本节省。
按难度路由
在生成任何输出之前,请求级路由系统就会判断任务的难度与意图。虽然其优势显著,但一个错误的决策就可能破坏整个会话,因此质量问题至关重要。必须有一种路由模型来决定每个请求的去向。
OpenAI的路由机制并未公开,用户常常会在不合适的时刻被转接到性能较弱的模型上,这令人十分困扰。开源领域则提供了更多可供研究的选项。由Chatbot Arena背后的伯克利团队开发的RouteLLM,能够利用在Arena中收集的真实用户偏好数据来学习。它将标准嵌入模型与一个小型路由模块相结合,这样既能降低托管成本,其开发者也声称在保持GPT-4大部分性能的同时实现了大幅的成本削减。不过这一说法尚未经过独立验证。
《LLMRouterBench》论文指出一个令人警醒的事实:许多基于机器学习的路由器几乎无法超越关键词或启发式路由、嵌入最近邻以及kNN风格路由等简单基准方法。
这些复杂的路由器可能并不会比简单方法带来太多优势。人们并未放弃路由技术,仍在不断尝试,但当答案质量无法与之匹配时,就无法保证一定能节省成本。也有托管式的解决方案,比如OpenRouter Auto和Switchpoint,不过目前还没有公开资料说明它们的路由机制或准确率。该计算工具对比了LLMRouter、启发式方法、自托管分类器、以LLM作为路由器的方案、RouteLLM以及OpenRouter Auto。要评估实际应用中的质量,就需要通过明确的数值进行恰当测试,这一领域本身就值得如此的评估。
从低成本方案开始,在置信度较低时再升级
这种廉价模型不会试图根据提示语来预测请求的难度,而是先给出答案,再通过检查决定是否保留该答案或升级处理。谷歌的“Speculative Cascades”论文阐述了其中的权衡:为追求成本和速度,优先使用小型模型,只有在必要时才使用大型模型。
廉价模型首先生成答案,随后由一个轻量级的检查器分析各种信号,如对数概率、熵值或基于边际值的不确定性指标,以及语义一致性。
这种方法很有吸引力,因为提示语的难度很难预测,而且大多数路由器也并非完美无缺;而一旦有了答案,其质量则相对容易判断。只有当大多数问题都能由较简单的模型解决时,这种做法才有意义,因为需要升级处理的请求会涉及两次调用。使用该技术的团队表示,两次调用之间的验证延迟可保持在20毫秒以内。
一种名为CascadeFlow的开源实现声称,与GPT-5相比,它能在保持96%质量的同时实现69%的成本节省。不过,用于测试的提示语都有可验证的正确答案,比如数学题和选择题。主要担忧在于小型模型常常会错误地给出自信的答案,因此从较低的阈值开始并更频繁地调整阈值可能是明智之举,但这又会增加成本。如果在计算器中加入低成本优先的级联处理方式,只要某些请求确实需要更大型的模型,就能实现近50%的成本节省。
委托给子代理
子代理会将任务委托给独立的代理,这些代理有时会使用较小的模型,因此这也是一种路由方式。这种方式的成本节省幅度较小。其价值还在于能够保持上下文整洁,让每个代理都能全神贯注地处理自己的任务。
Claude Code 自带内置子代理。Explore 子代理专为探索和搜索代码库而设计,属于 Haiku 工作器类型,体现了用成本更低的模型处理简单任务的思路。主会话也会通过匹配描述以隐式方式分配任务,用户仅能看到更低的总费用。由于调度器通常仍需参与规划、整合和重试等工作,因此其节省的成本不及路由机制带来的效果。
根据计算结果,子代理可使无需路由的方案成本降低约 11%,因此它们并非单独控制成本的主要手段。
保持上下文整洁
良好的上下文工程主要关乎性能,但也会影响成本。智能体随着时间推移会积累大量冗余数据:过时的尝试记录、旧计划、重复的状态信息、日志、反复的观察结果以及工具的原始输出。初次构建智能体的人尤其容易出现这种情况,因为他们总是将各种结果直接写入主智能体的工作状态中。
这种情况随处可见。OpenClaw的用户会抱怨上下文积累问题,Claude Code的用户也是如此,因为添加内容比清理它要容易得多。
难点在于状态处理流程
这是个双层问题。不仅仅是压缩聊天内容,新产生的数据在进入工作状态时也必须保持整洁,而这需要繁琐的工程处理。不良的处理方式表现为智能体不断将原始输出直接放入上下文中:
bad state:
agent does work
→ dumps tool output into context
→ reads files
→ dumps files into context
→ runs tests
→ dumps logs into context
→ retries
→ keeps everything
真正的任务是在删除冗余内容的同时保持正确状态。原始输出可以存入档案,只有当前上下文所需的部分才会被保留。通常的问题在于工具生成的输出过于冗余,因此让工具默认生成更简洁的输出也是工作内容之一。更健康的当前上下文应如下所示:
Good active context
[system rules]
[project rules]
[user task]
[current working state]
Keep:
+ auth flow lives in auth.ts + session.ts
+ bug only happens on refresh path
+ failing test: session_refresh_keeps_user
+ likely overwrite during refresh
+ files in scope: auth.ts, session.ts, auth.test.ts
Drop:
- raw grep results
- full test logs
- duplicate file dumps
- dead-end retries
某些上下文内容也可能具有生命周期或有效期。一旦需要压缩,就能更清楚地了解大语言模型实际的需求。
Anthropic关于长期任务的说明指出,压缩应保留架构设计决策、未解决的漏洞以及实现细节。LangChain的自动压缩功能允许智能体自行决定何时进行压缩,而不会等到上下文已经过于臃肿时才处理,这似乎也是Anthropic所采用的方法。各团队开始将压缩视为一个系统级问题,会制定相关基准和针对特定智能体的策略,而不再仅仅将其视为一种通用的总结技巧。
Jia等人最近发表的一篇论文指出,通过6倍压缩率,他们在SWE-bench Verified测试中实现了51.8%至71.3%的令牌预算减少,同时问题解决效率提升了5.0%至9.2%。因此,压缩并不仅仅关乎成本问题,还能整体提升性能。
去除冗余内容通常能清理掉上下文中的30%到70%,这相当于节省了相应的成本。对于1万token的上下文窗口,若在10万次调用中清理30%到50%的冗余内容,最多可节省1,500美元;而当上下文窗口为4万token时,节省金额则上升至6,000美元。那些已经使用非常小型且廉价模型的压缩工具,其成本可能反而高于节省的费用。不过,干净的上下文不会像语义缓存或路由机制那样牺牲质量,因此如果操作得当,显然能带来好处。唯一的缺点就是需要投入相应的精力。
如何选择
降低token成本有四种方法,具体采用哪种组合取决于应用场景:
- 对于在循环调用大型语言模型时保持不变的系统提示语,可使用提示词缓存。
- 对于需要保持低成本运行的通用问答机器人,可采用语义缓存。
- 当智能体需同时处理简单和复杂问题时,可尝试使用路由机制。