Agent:跟着一次循环走完全程
这份文档不按概念分类,按一次 agent 循环的执行顺序排列。 Function Calling、RAG、MCP、上下文工程——它们不是四个并列的话题,是同一个循环里的四个位置。
每个条目固定三行:是什么 / 它往上下文里塞了多少 token / 没有它会坏在哪。
第二行统一换成 token 预算,因为应用层的一切最后都是上下文预算问题。RAG 是往上下文塞检索结果,Function Calling 是塞 JSON schema,MCP 是塞工具发现的协议开销。谁塞得多、谁塞得值,就是这一层的全部工程。
🔗 前置:LLM 基础:跟着一个 token 走完全程 · 思想原型:ReAct 论文笔记
0. 骨架
glossary §0 那 12 行讲的是一次前向传播。Agent 是在它外面再套一层循环:
ctx = [system_prompt, tool_schemas] # 还没开工就已经花掉的钱 §1
ctx += [user_message]
while True:
resp = llm(ctx) # ← 整个 glossary 就是这一行 §0
ctx += [resp] # 模型的输出立刻变成下一轮的输入
if resp.tool_calls: # 模型决定要调工具 §1
for call in resp.tool_calls:
result = execute(call) # 唯一真正接触外部世界的一行 §2
ctx += [result] # ★ 结果原样塞回上下文 §3
continue # 回到循环开头,带着更长的 ctx
return resp # 没有工具调用 = 结束三件事看完这段就该记住:
ctx只增不减。每一轮循环都在往同一个数组末尾追加,而这个数组是有长度上限的。Agent 的全部工程难点,是管理这个只增不减的数组。llm(ctx)里面是无状态的。模型不记得上一轮——它每次都是从头读完整个ctx。所谓「记忆」完全是这个数组在承载。execute(call)是唯一的外部接口。RAG 是它的一种,搜索、读文件、跑代码都是它的一种。Agent 能力的上限 = 这一行能干什么。
和 glossary 的对应关系
| Agent 层 | 底层机制 | 在哪 |
|---|---|---|
ctx 只增不减 | 上下文窗口是训练决定的硬边界 | glossary §6 |
每轮重读整个 ctx | Prefill 阶段,算力受限 | glossary §6 |
| 长 ctx 变慢变贵 | KV Cache 随长度线性膨胀 | glossary §6 |
| 一步错,后面全歪 | 自回归误差自我放大 | glossary §6 |
| 模型「学会」了先想再做 | RLVR 把 CoT 训进了权重 | glossary §7 |
速查表
| 名词 | 在哪 | 名词 | 在哪 |
|---|---|---|---|
| Function Calling / 工具定义 | §1 | 上下文工程 | §3 |
| MCP | §1 | 有效上下文 / context rot | §3 |
| 工具搜索 / 延迟加载 | §1 | 提示缓存 | §4 |
| RAG / 分块 / 嵌入 | §2 | 成功快、失败慢 | §4 |
| 结构化输出 / 约束解码 | §2 | ReAct | §5 |
1. 开工前:工具定义的固定成本
这是最反直觉的一节。 大多数人以为 agent 的成本在「跑了多少轮」,实际上很大一块在「还没开始跑」。
Function Calling(工具调用)
- 是什么:把可用工具的名字、描述、参数 JSON Schema 拼进上下文,模型输出一个结构化的调用意图,由你的代码去执行。模型本身不能执行任何东西——它只是生成了一段表示「我想调用 X(a=1)」的文本。
- 它往上下文里塞了多少 token:分两笔。
- 系统提示开销(只要
tools非空就自动加上,还不含工具定义本身):Claude Opus 5 是 286 token(tool_choice为auto)/ 406 token(强制调用);Opus 4.7 是 675 / 804;Sonnet 5 是 354 / 474。 - 工具定义本身:这才是大头,见下。
- 系统提示开销(只要
- 没有它会坏在哪:模型只能吐文本,碰不到任何外部状态。它连当前时间都不知道。
工具定义的真实开销
Anthropic 自己公布的实测数据(第一方,最可信的一组):
| 服务 | 工具数 | token |
|---|---|---|
| GitHub | 35 | ≈26,000 |
| Slack | 11 | ≈21,000 |
| Sentry | 5 | ≈3,000 |
| Grafana | 5 | ≈3,000 |
| Splunk | 2 | ≈2,000 |
| 合计 | 58 | ≈55,000 |
58 个工具,55,000 token,在用户敲下第一个字之前就已经花掉了。 200k 上下文的 27%。 换算一下平均值:Slack 约 1,909 token/工具,GitHub 约 743。Jira 一家单独就 ≈17,000 token。
Anthropic 还给了一个更狠的自曝:「我们内部见过工具定义在优化前消耗 134,000 token。」
⚠️ 而且不止是贵——是会变笨
同一份文档里的另一句话,比成本数字重要得多:
一旦可用工具超过 30~50 个,Claude 挑对工具的能力就开始退化。
成本和能力是一起崩的,而且崩在同一个地方。 这不是「加工具会变贵,但值得」的权衡——过了那条线,你是花更多的钱买更差的效果。
MCP(Model Context Protocol)
- 是什么:Anthropic 在 2024 年 11 月 25 日发布的工具接入协议,让工具提供方和模型使用方解耦。发布时自带 6 个官方 server(Google Drive、Slack、GitHub、Git、Postgres、Puppeteer)。现在社区建了数千个。
- 它往上下文里塞了多少 token:MCP 本身不产生额外的协议开销——它的开销就是上面那张表。它的作用是让你更容易接入更多工具,而每一个接进来的工具都在往上下文里塞 schema。MCP 的便利性直接转化成了上下文压力,这是它最需要被理解的性质。
- 没有它会坏在哪:每个工具都要为每个模型单独写一遍适配。MCP 解决的是 M×N 问题。
- ⚠️ 它同时制造了一个新问题:接入变简单之后,「装十个 server」的成本感知消失了,但 55K token 的账单还在。
工具搜索:把固定成本变成按需成本
- 是什么:不把全部工具定义塞进上下文,只塞一个「搜索工具的工具」,模型需要时再检索出 3~5 个具体工具。
- 数字(全部第一方):
- 传统方式 50+ 个 MCP 工具 ≈ 72,000 token;工具搜索方式前置加载 ≈ 500 token。
- 定义负载降低 超过 85%:77,000 → 8,700 token,保住 95% 的上下文窗口。
- 准确率同时上升:Opus 4 从 49% → 74%;Opus 4.5 从 79.5% → 88.1%。
- 官方给的切换阈值:≥10 个工具、或定义 >10k token、或在做 MCP 聚合(200+ 工具)时该换;<10 个工具且定义总量 <100 token 时用标准方式就行。
- 硬限制:单请求最多 10,000 个延迟加载工具,搜索默认返回 5 个。
- 没有它会坏在哪:见上面那个 30~50 的悬崖。工具搜索本质上是在给模型做检索——只不过检索的对象是工具而不是文档。
程序化工具调用:让工具结果不进上下文
- 是什么:模型不再逐个调用工具、逐个接收结果,而是写一段代码,在沙箱里把多个工具串起来跑完,只把最终结果返回上下文。
- 数字:复杂研究任务平均用量 43,588 → 27,297 token(降 37%);准确率同步上升(内部知识检索 25.6% → 28.5%,GAIA 46.5% → 51.2%,复杂参数处理 72% → 90%)。 Anthropic 那篇《Code execution with MCP》给的场景化例子更夸张:150,000 → 2,000 token,省 98.7%。典型手法是「一万行的表格,agent 只看到五行」。
- 没有它会坏在哪:中间结果全都要过一遍上下文。取一万行数据做个求和,那一万行就永久占据你的
ctx——而且后面每一轮都要重读一次。 - ⚠️ 注意那个 98.7% 的性质:它是博客里的场景示例,不是 benchmark。同一篇里更硬的数字是 77K→8.7K 和那两组准确率提升,引用时优先用那几个。
2. 循环体:工具调用与 RAG
RAG(检索增强生成)
是什么:把外部文档切块、向量化、建索引;提问时检索最相关的 k 块,塞进上下文。它就是
execute()的一个特例——只不过被单独起了个名字,因为它出现得最早、用得最多。它往上下文里塞了多少 token:Databricks 那份 13 个模型的长上下文 RAG 研究用的是块大小 512 token、步长 256,检索块数 k 测了 1 / 5 / 13 / 29 / 61 / 125 / ... / 381。k=13 就是约 6,600 token,k=61 是约 31,000。
嵌入模型的规格(决定索引大小和检索质量):
模型 维度 最大序列 text-embedding-3-small 1536(可降到 256) 8,191 text-embedding-3-large 3072 8,191 bge-large-en-v1.5 1024 512 BGE-M3 1024 8,192 Qwen3-Embedding-8B 4096(可降到 32) 32k 没有它会坏在哪:模型只能靠预训练里的知识回答,而那些知识有截止日期、且无法引用来源。
⚠️ RAG 最重要的一个反直觉数字
Databricks 那份研究测出:检索召回率随上下文增长持续上升,但生成质量不跟着涨。
召回率:2k → 0.468,16k → 0.839,64k → 0.927,128k → 0.95。 而各模型开始退化的位置:Mixtral-8x7b 在 4k 之后,DBRX 在 8k,GPT-4-turbo 和 Claude-3-sonnet 在 16k,Llama-3.1-405b 在 32k。
SWE-bench 论文里有一个更干净的版本,同一篇论文的两张表:
| 上下文预算 | BM25 召回率 | Claude 2 解决率 |
|---|---|---|
| 13k | 29.58% | 1.96% |
| 27k | 44.41% | 1.87% |
| 50k | 51.06% | 1.22% |
召回率涨了 73%,解决率掉了 38%。 论文自己的结论是「模型在最短的上下文窗口下表现最好」。
「检索到了」和「用对了」是两件事,而且它们的曲线是反向的。 这是整篇文章最该记住的一个事实——它直接否定了「把更多相关内容塞进去总没坏处」这个默认假设。
结构化输出 / 约束解码
- 是什么:在采样那一步动手脚——按语法把不合法的 token 的 logits 直接置
-inf,保证输出一定是合法 JSON。它作用的位置正是 glossary §5 那个采样旋钮。 - 数字(XGrammar 论文,H100 + Llama-3.1-8B):每 token 掩码生成 <40 微秒;TPOT 开销几乎为零(JSON Schema,batch 1:6.2 → 6.3 ms;batch 16:9.0 → 9.2 ms)。相比现有方案,CFG 场景下单 token 延迟最多降 100×,端到端服务最多快 80×。 关键机制数字:上下文相关的 token 只占极小比例——Llama-3.1 用 JSON 语法时是 128k 里的 1,134 个,不到 1%。所以 99% 以上的掩码可以预计算。
- 没有它会坏在哪:模型有一定概率吐出不合法 JSON,你的
execute()直接抛异常。在一个几十轮的循环里,单轮 99% 的成功率意味着整体成功率0.99^30 ≈ 74%。约束解码把这个概率变成 100%,不是提高了一点。
3. 上下文工程:管理那个只增不减的数组
§0 那段代码里,
ctx += [...]出现了三次,没有一次ctx -= [...]。这一节讲的就是怎么补上那个减号。
有效上下文:标称长度是营销数字
glossary §6 已经提过「标称长度、可服务长度、有效长度是三个不同的数」。这里给出具体的数。
NoLiMa(2025) 的做法是让「针」和「问题」几乎没有字面重叠,逼模型做语义关联而不是字符串匹配。有效长度定义为「保持短上下文成绩 85% 以上的最长长度」:
| 模型 | 标称 | 有效 | 短上下文基线 | 32K 时 |
|---|---|---|---|---|
| GPT-4.1 | 1M | 16K | 97.0 | 79.8 |
| GPT-4o | 128K | 8K | 99.3 | 69.7 |
| Claude 3.5 Sonnet | 200K | 4K | 87.6 | 29.8 |
| Llama 3.3 70B | 128K | 2K | 97.3 | 42.7 |
| Gemini 1.5 Pro | 2M | 2K | 92.6 | 48.2 |
| GPT-4o mini | 128K | <1K | 84.9 | 13.7 |
测的 13 个模型全部声称支持 ≥128K,其中 11 个在 32K 时已经跌破短上下文成绩的一半。 GPT-4o 是 8K 对 128K,差 16 倍;Gemini 1.5 Pro 是 2K 对 2M,差 1000 倍。
⚠️ 但这个数字取决于你怎么测
RULER benchmark 对同一个 Gemini 1.5 Pro 的判决是有效长度 >128K,128K 时还有 94.4%。和 NoLiMa 的 2K 差了 64 倍。
这不是谁测错了。区别在于 RULER 保留了字面重叠,NoLiMa 剥掉了。
所以正确的表述是:「有效上下文长度」不是模型的属性,是「模型 × 任务字面重叠度」的属性。 你的任务越接近关键词匹配,可用长度越长;越依赖语义关联,可用长度掉得越快。RAG 恰好是后者——你检索回来的文档,和用户的问题往往用的不是同一套词。
Context rot(上下文腐烂)
Chroma 2025 年那份报告测了 18 个模型、194,480 次调用,结论:
随着「针」和问题的语义相似度下降,模型性能随输入长度增长的退化更严重。测的 18 个模型,在每一个长度增量上全部退化。
还有一个模型家族之间的行为差异值得记:Claude 系幻觉率最低但倾向于弃权(不答);GPT 系在有干扰项时幻觉率最高。(GPT-3.5 Turbo 因为 60.29% 的拒答率被排除出统计。)
具体的减号怎么打
- 裁剪历史:SWE-agent 的做法很直接——最近 5 条之外的观察结果,每条压成一行;除第一条外的所有报错信息直接删掉。论文给的理由有两条:省出上下文换更多交互轮次,以及避免过时的文件内容误导模型。第二条更重要。
- 子 agent:Anthropic 的做法是让子 agent 探索「数万 token 或更多」,只把 1,000~2,000 token 的压缩摘要返回主循环。
- 只保留最近的:Claude Code 保留最近访问的 5 个文件,其余压缩。
- 把中间结果挡在上下文外:就是 §1 那个程序化工具调用。
这四条是同一个动作的四个变体:让进入 ctx 的东西,少于工具产出的东西。
4. 账单:一次 agent 任务多少钱
SWE-agent 的实测数据(论文,最可信的一组)
SWE-bench 是 12 个 Python 仓库的 2,294 个真实 issue(Lite 300 个 / Verified 500 个)。任务规模:issue 文本平均 195 词,仓库非测试文件平均 3,010 个 / 43.8 万行,而标准答案平均只改 32.8 行、1.7 个文件。
大海捞针的比例:从 43.8 万行里找出要改的 32.8 行。
| 方案 | 解决率 | 每个已解决实例平均成本 |
|---|---|---|
| RAG + GPT-4 Turbo | 1.31% | $0.13 |
| RAG + Claude 3 Opus | 3.79% | $0.25 |
| SWE-agent + GPT-4 Turbo | 12.47% | $1.59 |
| SWE-agent + Claude 3 Opus | 10.46% | $2.59 |
论文自己的总结:agent 方案比 RAG 贵 8~13 倍,但解决率高 6.7 倍。
轮次:GPT-4 Turbo 跑完整 SWE-bench 平均 14.71 轮,中位数 12,75% 的轨迹在 18 轮内完成。单实例预算上限 $4.00,大约对应 30~40 轮。
⚠️ 最值得记住的一个模式:成功快,失败慢
成功的实例中位数是 $1.21 / 12 步;失败的平均是 $2.52 / 21 步。
Agent 不是「多跑几轮就能做出来」。跑得越久,越可能是在打转。 93.0% 的已解决实例在预算耗尽前就提交了,而全体只有 69.0%。
这个现象的机制在 glossary §6:自回归的误差会自我放大。第 10 轮走错方向,后面 11 轮都在这个错误的条件下生成,而且模型会努力让这个错误显得自洽。它不会自己意识到该回头。
工程含义很直接:轮次上限不是省钱手段,是质量手段。 该早停就早停。
提示缓存:agent 循环的必需品,不是优化项
§0 那个循环里,每一轮都要重读整个 ctx。第 20 轮时你在为前 19 轮的内容重复付第 20 次钱。
| 厂商 | 缓存读取折扣 | 写入成本 | TTL | 最小可缓存长度 |
|---|---|---|---|---|
| Anthropic | 0.1×(省 90%) | 1.25×(5分钟)/ 2×(1小时) | 5 分钟 / 1 小时 | 512~4,096 token(按模型) |
| OpenAI | 0.1×(省 90%) | 无额外费用 | 5~10 分钟,最长 1 小时;GPT-5.6+ 为 30 分钟 | ≥1,024 token,按 128 递增命中 |
| 隐式缓存默认开启 | — | — | 2,048~4,096 token(按模型) |
以 Claude Opus 5 为例:输入 $5/MTok,缓存命中 $0.50/MTok。
⚠️ 而这直接约束了你的上下文布局:缓存是前缀匹配的。系统提示和工具定义必须放在最前面且永不改动,否则整个缓存作废。Anthropic 的工具搜索之所以设计成「延迟加载的工具不进系统提示前缀、发现的工具以 tool_reference 块追加在后面」,就是为了这个——官方原话是「前缀没被动过,所以提示缓存得以保留」。相应地,defer_loading: true 的工具上不能加 cache_control(会返回 400)。
上下文里的东西该放哪个位置,是一个成本问题,不只是一个逻辑问题。
1M 上下文能用多少
Gemini 3.1 Pro 的第一方数据最能说明问题:同一个 MRCR v2(8 针)benchmark,128k 处 84.9%,1M 处 26.3%。同一个模型、同一个测试,长度拉到 1M 掉了 3.2 倍。
5. 这一切的思想原型
上面全部内容——工具定义、检索、上下文管理、轮次控制——都是在给同一个循环做工程。而那个循环 2022 年就被写出来了:
Thought: 我需要知道 X,应该先查 Y
Action: search[Y]
Observation: (真实结果返回)
Thought: 结果没说 X,再查 Z
...
Action: finish[答案]这是 ReAct。它用 1~2 个 in-context 示例、不做任何训练,就在 ALFWorld 和 WebShop 上比需要大量训练的强化学习基线绝对成功率高 34% / 10%(摘要口径;ALFWorld 那个 34% 是 best-of-6 对 best-of-8,六次均值只有 57 而非 71,详见笔记)。
四年过去,§0 那段代码和这个循环是同构的。变的是三件事:
- Thought 不再需要提示。glossary §7④ 讲的 RLVR 把「一步步想」训进了权重——CoT 从提示技巧变成了模型的默认行为。
- Action 的空间从 3 个变成了数千个。ReAct 的 HotpotQA 只有
search/lookup/finish;MCP 生态现在有数千个 server。而这正是 §1 那个 55K token 账单的来源。 - Observation 成了主要的成本项和主要的失败源。ReAct 时代观察结果就是一小段维基摘要;现在它可能是一万行表格、一整个代码库的检索结果。
ReAct 论文当年诚实地列出了两种失败模式:CoT 的病是幻觉,ReAct 的病是卡死、被检索绑架。
四年后 SWE-agent 的数据给了这个判断一个量化版本:失败的任务平均跑 21 步、花 $2.52,而成功的中位数是 12 步、$1.21。
「被检索绑架」现在有了数字。
6. 一句话收尾
Agent 不是一个新架构。它是 glossary §0 那 12 行外面套的一个 while 循环,加上一个只增不减的数组。
所有的工程都发生在那个数组上:什么进得去,什么该被挡在外面,什么该被删掉,以及在第几轮该承认这次不会成功了。
58 个工具 55,000 token,标称 128K 的有效长度是 8K,失败的任务比成功的多跑 9 步。这三个数字就是这一层的全部工程约束。
附:本文的证据分级
- 可直接引用(第一方文档或同行评议论文):§1 的 Anthropic token 表与工具搜索数据、§2 的 Databricks 与 SWE-bench 检索数据、§2 的 XGrammar、§3 的 NoLiMa / RULER / Chroma、§4 的 SWE-agent 与三家缓存定价、§4 的 Gemini MRCR。
- 场景示例而非 benchmark:MCP 那个 150k→2k / 98.7%。同一来源里更硬的数字是 77K→8.7K 和两组准确率提升,优先用后者。
- 未采用:OpenAI 侧没有公布对等的工具调用 token 开销表,本文未给 OpenAI 的具体开销数字;网上流传的第三方 MCP token 测量(67,300 token / 7 个 server 等)未经方法审计,本文未采用;「约束解码会让模型变笨」这个说法没有找到可验证的测量,未采用。