Skip to content

Agent:跟着一次循环走完全程

这份文档不按概念分类,按一次 agent 循环的执行顺序排列。 Function Calling、RAG、MCP、上下文工程——它们不是四个并列的话题,是同一个循环里的四个位置。

每个条目固定三行:是什么 / 它往上下文里塞了多少 token / 没有它会坏在哪

第二行统一换成 token 预算,因为应用层的一切最后都是上下文预算问题。RAG 是往上下文塞检索结果,Function Calling 是塞 JSON schema,MCP 是塞工具发现的协议开销。谁塞得多、谁塞得值,就是这一层的全部工程。

🔗 前置:LLM 基础:跟着一个 token 走完全程 · 思想原型:ReAct 论文笔记


0. 骨架

glossary §0 那 12 行讲的是一次前向传播。Agent 是在它外面再套一层循环:

python
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                            # 没有工具调用 = 结束

三件事看完这段就该记住:

  1. ctx 只增不减。每一轮循环都在往同一个数组末尾追加,而这个数组是有长度上限的。Agent 的全部工程难点,是管理这个只增不减的数组。
  2. llm(ctx) 里面是无状态的。模型不记得上一轮——它每次都是从头读完整个 ctx。所谓「记忆」完全是这个数组在承载。
  3. execute(call) 是唯一的外部接口。RAG 是它的一种,搜索、读文件、跑代码都是它的一种。Agent 能力的上限 = 这一行能干什么。

和 glossary 的对应关系

Agent 层底层机制在哪
ctx 只增不减上下文窗口是训练决定的硬边界glossary §6
每轮重读整个 ctxPrefill 阶段,算力受限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
结构化输出 / 约束解码§2ReAct§5

1. 开工前:工具定义的固定成本

这是最反直觉的一节。 大多数人以为 agent 的成本在「跑了多少轮」,实际上很大一块在「还没开始跑」。

Function Calling(工具调用)

  • 是什么:把可用工具的名字、描述、参数 JSON Schema 拼进上下文,模型输出一个结构化的调用意图,由你的代码去执行。模型本身不能执行任何东西——它只是生成了一段表示「我想调用 X(a=1)」的文本。
  • 它往上下文里塞了多少 token:分两笔。
    • 系统提示开销(只要 tools 非空就自动加上,还不含工具定义本身):Claude Opus 5 是 286 tokentool_choiceauto)/ 406 token(强制调用);Opus 4.7 是 675 / 804;Sonnet 5 是 354 / 474。
    • 工具定义本身:这才是大头,见下。
  • 没有它会坏在哪:模型只能吐文本,碰不到任何外部状态。它连当前时间都不知道。

工具定义的真实开销

Anthropic 自己公布的实测数据(第一方,最可信的一组):

服务工具数token
GitHub35≈26,000
Slack11≈21,000
Sentry5≈3,000
Grafana5≈3,000
Splunk2≈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)。现在社区建了数千个
  • 它往上下文里塞了多少 tokenMCP 本身不产生额外的协议开销——它的开销就是上面那张表。它的作用是让你更容易接入更多工具,而每一个接进来的工具都在往上下文里塞 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-small1536(可降到 256)8,191
    text-embedding-3-large30728,191
    bge-large-en-v1.51024512
    BGE-M310248,192
    Qwen3-Embedding-8B4096(可降到 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 解决率
13k29.58%1.96%
27k44.41%1.87%
50k51.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.11M16K97.079.8
GPT-4o128K8K99.369.7
Claude 3.5 Sonnet200K4K87.629.8
Llama 3.3 70B128K2K97.342.7
Gemini 1.5 Pro2M2K92.648.2
GPT-4o mini128K<1K84.913.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 Turbo1.31%$0.13
RAG + Claude 3 Opus3.79%$0.25
SWE-agent + GPT-4 Turbo12.47%$1.59
SWE-agent + Claude 3 Opus10.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最小可缓存长度
Anthropic0.1×(省 90%)1.25×(5分钟)/ 2×(1小时)5 分钟 / 1 小时512~4,096 token(按模型)
OpenAI0.1×(省 90%)无额外费用5~10 分钟,最长 1 小时;GPT-5.6+ 为 30 分钟≥1,024 token,按 128 递增命中
Google隐式缓存默认开启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 那段代码和这个循环是同构的。变的是三件事:

  1. Thought 不再需要提示glossary §7④ 讲的 RLVR 把「一步步想」训进了权重——CoT 从提示技巧变成了模型的默认行为。
  2. Action 的空间从 3 个变成了数千个。ReAct 的 HotpotQA 只有 search / lookup / finish;MCP 生态现在有数千个 server。而这正是 §1 那个 55K token 账单的来源。
  3. 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 等)未经方法审计,本文未采用;「约束解码会让模型变笨」这个说法没有找到可验证的测量,未采用。

Released under the MIT License.