Ida Silfverskiöld
2026-04-29
原文
---
title: "Agentic AI:怎么省 token"
author: Ida Silfverskiöld
source_url: https://towardsdatascience.com/agentic-ai-how-to-save-on-tokens/
published_at: 2026-04-29
fetched_at: 2026-05-27
language: zh
translator: Hermes
---
# Agentic AI:怎么省 token
你的第一个 agent 上线时可能只带一份 500 token 的 system prompt 和两个工具,但这两个数字往往很快就暴涨。
举个例子,泄露的那份 Claude system prompt 大约 24,000 token;OpenClaw 用户[报告](https://github.com/openclaw/openclaw/issues/21999)过第一轮对 Gemini 3.1 Pro 发了 15 万以上 input token,最后只换来 29 个 output token。
这种没优化过的 agent,按一天 100 条消息、每次 16.6 万 input token 算,跑 Gemini 3.1 Pro 大约要 996 美元/月,跑 Claude Opus 4.6 大约要 2,490 美元/月。
但有一些招数能把这笔账压下来——压到 50 美元和 100 美元的量级。
我想把搭 agent 时该考虑的几个设计原则过一遍。
我们会讲 prompt caching 怎么跑、为什么是见效最快的一招;讲 semantic caching(语义缓存);讲工具和 MCP 的懒加载;讲路由分发和级联;讲怎么委派给 subagent;最后讲讲怎么保持 context 干净。
文章里穿插了几张交互图——能让你按自己实际用的 token 量直观看到每个原则能省多少。
_对,我全篇都老实讲,每一条节省都附带 trade-off。_
## 四个值得记住的设计原则
这篇里我们会过四个不同的部分,配四个不同的交互式计算器。

第一部分讲怎么尽量复用 token,包括 [prompt caching](https://claude.ai/public/artifacts/af356b1d-175b-467a-9c09-2568b7f0bcd0) 和 [semantic caching](https://claude.ai/public/artifacts/9231bfa1-6291-4c05-8082-10f1b6f282b8)。然后讲怎么压缩那些每次都得带上的稳定 token——记忆和[工具定义](https://claude.ai/public/artifacts/bedb2567-6e8f-4d71-bcc6-4c9c18cde2cb)。
接下来讲怎么把请求[路由到更小的模型](https://claude.ai/public/artifacts/f231d43b-0063-40d4-9907-36fe18cdafce),或者升档到更大的模型,质量风险和能省的钱都看一下。
最后一节讲怎么[让 context 保持干净](https://claude.ai/public/artifacts/5b50845f-6fc9-4912-a577-100183ef253a)——为了性能也为了成本,顺带提一句 compaction(压缩)。
### 能复用的 token 就复用
LLM 的成本不只来自调模型太频繁,也来自一遍又一遍地为同一份 token 付钱。
所以这一节我们讲 K/V caching——prompt caching 底层那套机制,再讲 semantic caching,两件完全不同的事。讲清楚它们是什么、做什么、能给你省多少。
Prompt caching 对长 system prompt 来说见效最快;semantic caching 工程量更大、风险也更多。
#### K/V caching 与 prefix caching
模型生成任何东西之前,得先处理 prompt。这一步要算力——也就是延迟和钱。所以为了高效,不该反复处理同一份内容。
你用 LLM 时,prompt 先切成 token,token 转成向量;每层 attention 把这些向量投影成 K/V tensor。

_把这些张量留住,下次就不用重新算 | 图:作者_
推理引擎在生成过程中本来就缓存 K/V tensor,否则按我理解整个计算根本撑不起合理的速度。
但生成结束之后,你不必把 cache 扔掉——可以存下来。
下次请求进来时,我们检查一下 prompt 那一段是不是已经存过 tensor。是 → 直接加载,不再处理。

经济上为什么重要——找点感觉:**假设处理 2,000 token 要 1 秒**,你的 system prompt 是 10,000 token。
**那就是每次 LLM 调用都省 5 秒**——只因为没把 prompt 开头那一截再走一遍模型(不过 prefill 吞吐在不同环境差很多)。
要注意的是,输入必须跟存好的 K/V cache **完全匹配**才算命中。
token 一变,那段 prompt 对应的 K/V tensor 就没现成的了,得重新处理。这就是大家反复栽跟头的地方:多打了一个空格、工具定义重排了一下、时间戳放错了位置。
所以,把 cache 存下来,对加快请求和降低费用都有真实的价值。
_注意,存张量不是免费的。缓存的 K/V 占服务端的内存——这也是为什么很多服务商把 TTL 窗口设成 5–10 分钟。_
我们不必自己造这套,上面只是把机制走一遍。有一些框架能帮你做,API 服务商也有自己的 prompt caching 规则——下面两边都讲。
#### Prefix caching:自托管推理场景
如果你在自己托管开源模型,理想做法是用一个 LLM serving 框架,比如 vLLM。其他框架也能做缓存层,但 vLLM 有一个可以直接打开的附加功能。
vLLM 的缓存层是这么干的:把 prompt 切成块(block),按"这一块的 token + 它前面所有 token"算 hash,再用这些 hash 把 K/V tensor 存起来。
跟大多数方案一样,要被缓存的那段静态内容应该放在 prompt 最前面。

vLLM 里打开缓存:`--enable-prefix-caching`
调块大小:`--block-size`
block size 是每块多少 token。block size 设成 16,每块就装 16 个 token,到了再切下一块。
还有 `--kv-cache-memory-bytes` 可以显式设每张 GPU 的 KV cache 大小。
给的内存越多,能留住的缓存块越久。但同时进来很多长且各不相同的请求时,内存填得快,老块也淘汰得快。
市面上还有别的方案,但你已经懂思路——跟前一节是同一套机制。
prefix caching 也可以看 SGLang 和 RadixAttention,还有 LMCache(可以接到 serving 引擎里)。
不过大多数人用的是 API 服务商,每家有自己的 prompt caching 用法——咱们一家家走。
#### 通过 API 服务商用 prompt caching
用 API 服务商时,prompt 得按命中 cache 的方式组织。要做对得守几条规则。
我先用 OpenAI 举例。
OpenAI 写得很明白:要缓存 prompt 的某部分,必须 **prefix 完全匹配**——也就是 prompt 开头的静态输入要一致。
意思是,稳定的指令、示例、工具列表永远放最前面,可变内容放后面。

你也可以传一个 `prompt-cache-key`(缓存键),把相似请求路由到一起,提高 cache 命中率。
还有更细的:只要 prompt 长度 ≥ 1,024 token,缓存自动开启,但 OpenAI 用前 256 token 把请求路由回同一个 cache。所以 prompt 那段静态内容必须超过 256 token。
Anthropic 那边得显式用 `cache-control`(缓存控制)参数打开。
值得一提:一般来说,5–10 分钟没动静就会被淘汰(TTL),但可以延长。Anthropic 也是这套,但你能把它推到 1 小时(代价是 2 倍计费)。
前面讲过你省的是时间——自己托管的话,省时间就直接等于省钱。用 API 服务商时,省下来的体现为 cache 命中的 input token 单价更低。
OpenAI 的 cached input 比 base input 便宜最多 90%。
Anthropic 给同样的 cache 折扣,但同时收 cache 存储费。所以用错的话,Anthropic 反而更贵。
总的来说,如果你 90% 的 prompt 是静态的,正确用 cache 之后大概是这个价位:

我用 Claude 做了一张交互图放在[这里](https://claude.ai/public/artifacts/af356b1d-175b-467a-9c09-2568b7f0bcd0),可以自己玩玩。
所以,对每个人来说——只要你的 system prompt 较长且变动不大——prompt caching 都是相当不错的一招,省 token 时值得考虑。
接下来讲 semantic caching,那是另一回事。
#### Semantic caching
Semantic caching 是按"含义"匹配——如果一个请求跟以前足够像,就把缓存的结果返回。听起来简单,但坑很明确。
要在语义层面匹配文本,得用 embedding。如果你不熟这个词可以先去查一下,我几年前[写过](https://towardsdatascience.com/working-with-embeddings-closed-versus-open-source-39491f0b95c2/)一篇。
简单说,embedding 是向量,两两之间可以算余弦相似度。相似度高,含义就该接近——不过这取决于模型。

semantic caching 想做的事:把相似的请求匹到已经存好的答案上。"What's the capital of France?" 和 "Quick, give me the capital of France" 应该路由到同一个答案。
不必每次都让 LLM 重新答一遍同一件事。
如果很多人问近乎相同的通用问题,且数据不会过期太快,这招挺好。
那为什么不每个场景都用?这里坑一大堆。
随便想想就能列出:相似度阈值定多少、答案有效期多长、多轮问题怎么办、到底存什么、要不要加一层路由、怎么按用户隔离、答错被缓存了又怎么处理。
还得考虑 TTL(Time to Live),也就是哪些信息什么时候过期、对哪些问题适用。
机制虽然简单,但你还要 metadata(元数据)过滤和打标——按用户、工作区、语料版本、persona(用户画像)、按会话/用户分组、智能 TTL,还要有一条规则判定"返回的够不够"。
整套下来就是一个项目级的工程了。
所以如果你想做,可以用语义索引找之前的问题,让不同问题指向同一份存好的答案——存储就不会爆炸。TTL 按使用频率智能处理,常用的留久一点,不常用的清掉。
我还会建议你别一上来就做,先看 log 里有没有出现重复模式再做。可能这场景根本就不适合 semantic cache。
至于怎么做,很多数据库都自带这个能力。也有一堆库帮你打通底层:semanticcache、prompt-cache、GPTCache、vCache、Upstash semantic-cache、Redis + LangCache 等。
省钱潜力当然是有的。[Redis](https://redis.io/blog/how-to-cache-semantic-search/) 声称能减少 68.8% 的 API 调用、把延迟改善 40–50%——但要警觉这有点营销味,他们用的是非常典型的 Q&A 场景。
完全看你自己的环境。Q/A agent 重复调用多就省得多;coding bot 每次都不一样就省得少。
prompt caching 适合"问题在变、外面套着大段静态 prompt"的场景。semantic caching 适合"很多人换不同说法问同一件事"的场景。
可以玩这个[交互工具](https://claude.ai/public/artifacts/9231bfa1-6291-4c05-8082-10f1b6f282b8)看两种方式各自能省多少。
走完这一节之前再提一句——**普通缓存**也能省不少。
记得把那些贵的、确定性的东西也缓存起来:SQL 查询结果、工具输出、检索结果。能少跑一次绝不多跑。
我自己有一个工具就这么干。它会拉关键词数据做摘要,把结果缓存到数据过期为止。过期了再次访问该路由时再重跑。
总结:semantic caching 是个有意思的想法,特定场景能帮你省 token,但要做好得下功夫。
### 别预加载休眠的 token
这一节讲的是:当你的 system prompt 因为臃肿的工具定义或不停长大的记忆开始膨胀时怎么办。
小 agent 这不是问题。但如果你做的 agent spec 越长越大,有办法瘦身——按需取信息(或者说至少试着按需取)。
#### Context 保持轻量,细节按需取
agent 的 prompt 长到一定程度后,把那个"始终加载"的层尽量保持小且稳定,把会变的细节剥出去——这是好做法。
为什么——一旦这些层开始膨胀,比如你加载几百个工具,或者把整份不停在变的 MCP server 描述塞进去,就开始乱了。

问题不只是钱,还有性能。而且如果其中一层一直在变,prompt caching 就更难命中了。
所以思路是:**最上面那层尽量紧凑、稳定**。最上层应该让模型知道自己在哪、下一步该去哪,不必把整个世界都搬过来。

如果你看过 Claude Code 源码,会发现他们的记忆系统大概就是这套:
一个永远加载的索引文件,规定不超过 200 行,详细的话题文件放在别处。_至于实操中 agent 的实际行为跟系统期望的差距,那是另一篇的话题。_
同样的思路在别的地方也能看到,比如 Claude 的 advanced tool 配置、Claude Skills 的分层结构、还有把 MCP 工具改成懒加载(而不是一上来就把所有 server 定义全塞进 prompt)。
#### 这招在哪儿被用过、能不能跑通
思路是站得住的。context 越大,LLM 越难选对动作。但这块还很早期,咱们用一个工具走一遍看看怎么落地。
几个月前,Anthropic 发布了一个叫 advanced Tool Search 的东西——目标就是 context 保持精瘦的同时让模型还能用上几百个工具。
Anthropic [说](https://www.anthropic.com/engineering/advanced-tool-use)优化前他们见过 5.5 万到 13.4 万 token 的工具定义,且 context 一大就常出现"工具选错"这种典型 failure。

所以会用一个 search tool 来优化 context——让 LLM 自己去搜工具,而不是把工具全部预先定义出来。
```python
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 或 Regex,也可以自己写一个。然后这里把一个工具标成 deferred(延迟加载)当例子。
不过你只在 10+ 个工具的场景下才该这么做。
Anthropic 帮你处理搜索那块,所以你看不到这个工具 schema 是怎么注入到 system prompt 的,也看不到底层怎么搜的。
他们说一旦匹到工具,工具的定义会作为 `tool_reference`(工具引用)block 内联追加到对话里给 LLM。

思路漂亮:初始 context 更小——但你多了一步搜索。也有人[测过](https://www.arcade.dev/blog/anthropic-tool-search-4000-tools-test/)这个工具,结果有点拉胯,不过那是 4,000 个工具的场景,余地还很大。
把工具定义写到搜索能命中——这归你。但中间这步看不见,调试就更难了。
这思路在别处也出现过,但大家一般直接叫它"好的 AI 工程实践"。**别让 agent 直面又大又乱的 context**。给它一种把范围缩小的方式,等需要时再让它检查或加载工具。
这部分能省的 token 是真不少——但要看你本来发的 token 有多少。
我们做了[这个](https://claude.ai/public/artifacts/bedb2567-6e8f-4d71-bcc6-4c9c18cde2cb)额外的计算器,对比 tool search 和 prompt caching。

_凭感觉算的:prompt caching + 懒加载工具的省钱效果_
可以看到 prompt caching 和懒加载 context 各自都有省,但加起来变化也没那么夸张。tool search 这种东西不只为了省钱,也为了 context 干净以提升性能。
但如果你只盯省钱看,最实在的省钱方式是——至少先用其中一种。
### 便宜活就用便宜模型
这一节讲怎么把 prompt 路由到不同模型,再讲对某些任务用更便宜模型的 subagent——能降 token 成本,但也可能损质量。
这块挺有意思的——大多数人都说,进来的请求里 60% 以上是简单任务,根本不需要最强的模型,更不需要带 thinking 的。
ChatGPT 用对话类型、复杂度、是否要工具、明确意图("think hard")这类信号做这件事。Claude 用的是 description-based delegation(按描述匹配委派),还有内置的 subagent 比如 Explore。
思路简单,难的是怎么做对——又不让质量掉太多。
下面咱们走两套:predictive routing(预测式路由)和级联 + subagent 这种带输出校验的方法,让你对该测什么有点感觉。
这部分的省钱潜力非常实在。我又做了一张交互图,[在这里](https://claude.ai/public/artifacts/f231d43b-0063-40d4-9907-36fe18cdafce)。
#### 按任务难度路由到不同模型
请求级路由(request-level routing)就是看到 output 之前先估算难度和意图。上限高,但选错了能毒掉整个会话——所以要记住质量这边的代价。
要做这个,得有某种 router model 来决定路由到哪。

我们不知道 OpenAI 用什么信号路由到不同模型——但说实话我经常感觉自己被分发到一个明显更弱的模型上,挺让人上火的。
不过我们还能从开源社区扒到一些情报。可以看 LMSYS(Chatbot Arena 背后的 Berkeley 团队)出的 [RouteLLM](https://github.com/lm-sys/RouteLLM)。这套方案是从 Chatbot Arena 的真实偏好数据里学出来的。
RouteLLM 用标准 embedding 加一个很小的 router head(路由头),自己托管不会太贵。
我没亲测过 RouteLLM,但他们报告说能在保住大部分 GPT-4 性能的同时大幅降本。
不过我翻了一下 [LLMRouterBench](https://arxiv.org/abs/2601.07206) 这篇 paper,结论大致是:很多训练出来的 router 几乎没比简单 baseline 强多少——简单 baseline 包括关键词/启发式路由、embedding 最近邻、kNN 风格路由。

问题是,这些 router 在猜难度上可能并不太行——所以跟用一个简单方法相比,未必有多大提升。
大家没因此放弃 routing,还在折腾,但如果答案质量跟不上,省钱也算不上稳赢。
这块也有现成方案,比如 OpenRouter Auto 和 Switchpoint。但他们的路由内部实现和准确率数字都没我想要的那种公开资料。
但这一节也可以看一下我们做的[计算器](https://claude.ai/public/artifacts/f231d43b-0063-40d4-9907-36fe18cdafce):LLMRouter、启发式、自托管分类器、LLM 当 router、RouteLLM、OpenRouter Auto。
至于质量和它在真实项目里的表现,我没亲自测够之前不敢说,这块以后值得单写一篇。
进入下一节前简单说一下级联和 subagent。
#### 先用便宜的,置信度低再级联
不必从 prompt 上猜请求"简单"还是"难"——可以让便宜模型先试,再决定接不接受这个答案,还是升档。
Google 那篇 "Speculative Cascades" 把这个权衡讲得很清楚:先用更小的模型省钱省时间,需要的时候再交给更大的模型。
做法:让便宜模型先生成,再用一个轻量 checker 看几个信号——logprobs/token 概率、entropy 或边界式不确定性、或语义对齐。

这个想法挺有吸引力——上面也说过 prompt 难度往往不好预判,多数 router 表现也一般。
而且,质量在你拿到答案**之后**判断要容易得多。
只是它得在"大多数问题用更简单模型就能回答"的前提下才划算——升档的那一部分要付两次调用的钱。
不过从在做这件事的人那里,我听说挺香——两次调用之间的校验延迟能控制在 20ms 以内。
我看了几个开源实现,比如 [CascadeFlow](https://github.com/lemony-ai/cascadeflow),号称相对 GPT-5 省 69%、保持 96% 的质量。但要注意他们测的 prompt 都自带 ground truth(标准答案),比如数学答案和选择题。
要警觉的一个主要问题是:小模型常常"自信地答错"——所以最好从保守阈值开始,多升档一些。这必然推高成本。
我也把 Cascade(先用便宜的)加到[那张交互图](https://claude.ai/public/artifacts/f231d43b-0063-40d4-9907-36fe18cdafce)里了,可以跟其他选择对比省钱效果。
如果属实,且你确实有些请求需要更大模型,这招可能砍掉 50% 的成本。但代价又是——质量风险。
#### 把活委派给 subagent
Subagent 就是把活委派给一个隔离的 agent。有时这些 subagent 用的是更小的模型,所以也算一种 routing。省钱效果没前面那些猛,但值得一提。
委派给 subagent 不只是为了省钱,也是为了 context 干净——让每个 agent 能完全聚焦在自己该做的活上。
Anthropic 给 Claude Code 直接配了内置 subagent,很多人都见过。Explore 这个 subagent 明确就是个 Haiku worker,专做代码库搜索和探索。所以这条设计原则就摆在那儿——**用更小的模型干便宜的活**。
主 Claude session 也通过描述匹配做委派,但我们看不到,只看到聚合费用变便宜。
但 orchestrator 还得继续在 loop 里做规划、综合、重试,所以省下来的并不像 routing 那么多。

可以看上面那张[图](https://claude.ai/public/artifacts/f231d43b-0063-40d4-9907-36fe18cdafce)——按我们的估算,subagent 大概比"不路由"省 11%——所以如果你只想砍成本,这个不是首选。
我下一篇会深挖 subagent,但更多是把它当成"委派活、隔离任务"的工具,主要配合 deepagents 用。
过完最后一节再收尾。
### 让 context 保持干净
好的 context 工程通常是为了性能,但也能省钱。下面讲 context compaction(压缩),讲讲保持 context 干净怎么省 token。
问题是:agent 一直在堆积垃圾——工具输出、log、重复的观察、旧计划、失败的尝试、重复的状态。
第一次搭 agent 的人尤其容易这样——把所有结果都堆进主 agent 的 working state。
```text
坏的 active context
[system rules]
[project rules]
[user task]
grep output: 2,000 行
file read: 900 行
test logs: 1,300 行
重试 logs
重复读取
走错路的旧推理
更多 logs
更多 logs
更多 logs
```
我自己也这么干过,尤其是 agent 还在草稿阶段的时候——为了先看看"它怎么表现"。
但我也见过有人吐槽 OpenClaw context 在涨,所以哪都有这事。Claude Code 里也被吐槽——一般而言,往里加东西比清理它要轻松得多。
简单聊聊这块,性能那一面就不展开了——不过性能本身也是你该做这件事的理由。
#### 难点是建一条 state pipeline
这是一个两层问题。你不只是"压缩聊天记录",还得在每往 working state 加东西时就把它清干净——这是繁琐的工程活。
第一步,要保持 context 干净,就别让下面这种东西开始挤占 context:
```text
坏 state:
agent 干活
→ 把工具输出全堆进 context
→ 读文件
→ 把文件全塞进 context
→ 跑测试
→ 把 log 全堆进 context
→ 重试
→ 全留下
```
所以真正的活儿是:**边干边把垃圾删掉,把该留的状态留下**。
像这样的 raw 输出可以存进归档,只把需要的塞进 active context。一般来说,最大的麻烦是工具输出膨胀——所以工作就是默认让工具少出杂音。
```text
好的 active context
[system rules]
[project rules]
[user task]
[当前 working state]
留:
+ auth 流程在 auth.ts + session.ts
+ bug 只在 refresh 路径上出现
+ 失败的 test:session_refresh_keeps_user
+ refresh 期间可能被覆盖
+ 涉及文件:auth.ts、session.ts、auth.test.ts
丢:
- 原始 grep 结果
- 完整 test log
- 重复的文件 dump
- 走不通的尝试
```
我也在想——某些 context 片段可以有生命周期或固定过期时间。
然后到了真要压缩的那一步,至少更清楚什么对 LLM 有用。
翻翻 Anthropic 那边的资料——他们提到,一旦得压缩,你得想个办法保住架构决策、未解决的 bug、实现细节。
LangChain 那个 autonomous compression 让 agent **自己决定什么时候压**,而不是非要等 context 已经膨胀了——按我理解 Anthropic 是后一种。
有意思的是,业界开始把"压缩"当系统问题来评估了——配 benchmark、配 agent 专属策略,而不只是当通用摘要小招。
也可以看一篇近期的 paper 找点感觉。[Jia 等人这篇](https://arxiv.org/html/2603.28119v1)说在 6 倍压缩下,token 预算降 51.8–71.3%,同时在 SWE-bench Verified 上 issue 解决率提升 5.0–9.2%。
所以这事不只是省钱,整体性能也能上。
至于成本——搭好的 context 本身就是大工程,但把垃圾清掉大概能腾出 30–70% 的 context,钱也省同样比例。
举例:10k 的 context window,在 10 万次跑里清掉 30%–50%,可能省到 1,500 美元。40k context window 时这数字到 6,000 美元。
我们也做了[这个](https://claude.ai/public/artifacts/5b50845f-6fc9-4912-a577-100183ef253a)算法可视化,可以自己看。
值得提一句:如果 agent 本来就用很小很便宜的模型,再做压缩反而可能更贵。
不过把 context 清干净的好处是——你不用拿质量换钱,不像 semantic caching 或 routing 那样。做对了就是纯赚。
唯一的问题就是工程量。
## 收尾
这是一篇很长的文章,讲了四种不同的方式——搭 agent 时怎么砍 token 成本。
完全看你的场景——大段 system prompt 不变、循环调 LLM,用 prompt caching;做通用 Q/A bot 又要保持便宜,用 semantic caching。
要能同时回答简单和难题,测一下 routing;想避免发多余 token,就尽量保持 context 干净。

以后或许值得再写一篇更短、更聚焦经济性的文章,专门讲某些特定配置。
希望这篇对你有用。可以在 [LinkedIn](https://www.linkedin.com/in/ida-silfverskiold/)、[Medium](https://medium.com/@ilsilfverskiold) 或我的[网站](https://www.ilsilfverskiold.com/)上找我——不管你是想合作做 agent,还是单纯想看更多类似内容。