上一章《RAG 检索增强》给模型配了本参考书,但它还是只会说出来。
它再聪明也改变不了世界——你说"帮我查一下天气",它只能编一个听起来合理的数字。
Agent 的全部魔法就是给它一双手:让它能真的调用工具。
| 场景 / 问题 | 普通模型 | Agent |
|---|---|---|
| 能做什么 | 生成文字 | 生成文字 + 触发真实世界的操作 |
| "现在几点?" | 它不知道,但可能编一个时间 | 调用 get_time(),拿到真实答案 |
| "算一下 17 × 23" | 容易算错(数字被切碎了,回看分词那章) | 调用计算器,100% 正确 |
| "帮我订明天的机票" | 只能写一段"订票指南" | 真的调用订票 API |
| 本质 | 文本 → 文本 | 文本 → 动作 → 观察 → 文本(观察 = 程序把执行结果告诉它;循环也由程序转) |
Agent = 模型 + 工具 + 一个循环。
少了任何一个都不成立:没有工具它就只是聊天;没有循环它就只能做一步。
而真正难的部分不是"调用工具"——是知道什么时候该调、调哪个、参数怎么填、结果不对怎么办。
图解 · Agent 其实只有三个零件,缺一个都不成立
2022 年那篇 ReAct(Reason + Act,边推理边行动)论文给出了至今最通用的形式: 让模型交替输出「思考」和「行动」。
关键:这个循环由外部代码驱动,不是模型自己在跑。 模型只是每次输出一句话,然后"停下来等"。是外面的程序决定要不要把这句话当成工具调用来执行。
像一个只能打电话、看不见外面世界的顾问。
你问他"北京今天多少度",他没法直接知道。但他可以说:"请帮我打给气象台,问北京的温度。"
你打完电话,把结果告诉他。他接着说:"再帮我算一下,23 摄氏度是多少华氏度。"
顾问全程没有离开房间——他只负责说"该做什么",动手的永远是外面那个人。
这个类比管到「谁在想、谁在做」为止:真实 Agent 的顾问能看到你贴进上下文的全部资料,也能在一轮里同时要求打好几个电话。
图解 · ReAct 循环:四步转一圈,直到能给出答案
下面的循环是实际执行的:你选动作,工具被调用、返回结果,上下文随之变长。 任务是 「查一下北京今天的气温,并换算成华氏度」。
互动 · ReAct 循环模拟器
执行轨迹
下一步做什么?
模型一个字节也没执行,执行的是你的代码。上面这个循环里,模型每轮只负责说一句"下一步做什么", 真正解析、执行、把结果拼回去的是外面那段程序。这一条同时解释了 它为什么灵活(换工具不用改模型),也解释了它为什么危险——第 4 节的安全后果、第 6 节那四个失败模式,都是从这里长出来的。
· 选「查上海天气」:结果对了,但城市错了——Agent 没有任何机制知道"北京≠上海",
它会拿着上海的温度继续往下算。
· 选「换算成开尔文」:单位错了,但它会一本正经地给你 296.15K。
· 选「再查一次北京天气」:结果一样,但你白白多花了一轮的钱和时间。
这就是"循环卡死"的雏形。
· 选 「计算器 23×9/5+32」:这条其实也正确——
换算是可以绕开专用工具、直接用计算器算的。同一个任务往往有多条正确路径。
这是最容易被误解的一点。「模型调用了一个函数」听起来像是它在执行代码—— 完全不是。
因为模型只是在"生成一段看起来像工具调用的文字",
所以如果你把用户输入直接拼进上下文,用户就可以伪造出一段工具调用,
让模型以为"系统刚才已经批准了删除数据库的操作"。
所以:工具调用的结果必须由你的代码注入并标记来源(比如在消息里写明 role: tool、加上固定前缀,用户输入永远不带这个标记),绝不能允许用户输入伪装成工具输出。
真正执行前的权限校验,必须放在你的代码里,而不是指望模型"不会乱来"。
图解 · 「模型调用工具」的四步:真正执行的永远是第 ③ 步
图解 · prompt injection(提示注入)是从哪进来的
每一轮工具调用,都要把之前所有内容重新发给模型一次。 模型是无状态的——它不记得上一轮,是你把整段历史重发给它。 这一节算完整账:除任务和工具本身,每轮还要重发一段约 600 token 的系统提示(第 3 节的读数不含它)。
互动 · 上下文增长的账本
Agent 每一轮都要把历史重发一遍,所以越往后越贵:第 N 轮要发前 N−1 轮的全部内容,
总成本是 1+2+3+…+N = N(N+1)/2,平方增长。
10 轮下来累计约是单轮的 20 倍,30 轮就是 130 倍。
这些输入每次还要重新做一遍前向计算(KV cache 能省掉一部分,但长上下文本身还是贵)。
「Agent 比聊天贵」不是定价策略问题,是结构决定的。
图解 · 每一轮都要把历史整段重发一次
| 失败模式 | 长什么样 · 为什么会发生 | 怎么缓解 |
|---|---|---|
| ① 循环卡死 | 反复调用同一个工具、参数几乎一样,跑了 30 轮还在原地。 模型没意识到"我已经试过了":失败和成功的记录混在一起,没有"这个已经试过了"的标记 | 设最大轮数硬上限;记录已调用过的「工具+参数」组合并在提示里显式列出 |
| ② 工具误用 | 参数格式错(日期写成"明天"这种自由文本,而不是 ISO 格式「2026-09-29」); 选错工具(该用计算器却用了搜索);编造不存在的工具名。 模型对工具的理解只来自文字说明,说明写得含糊,它就只能猜 | 工具描述要写清参数类型和示例;参数做严格校验并把错误信息返回给它 |
| ③ 错误累积 | 第一步查错了城市,后面每一步都基于这个错误,最后给出一个完全自信但完全错误的答案。 Agent 没有"我可能错了"的内在信号——错误的结果在上下文里读起来和正确结果一模一样 | 关键结果做交叉验证;引入独立的校验步骤;把"不确定"显式写进工具返回里 |
| ④ 过早停止 | 任务只做了一半就说"已完成",比如只查了天气、没做换算。 模型倾向于"给出一个完整回答",而且看不到自己的进度条——没有机制告诉它"还剩几步" | 把任务拆成显式的检查清单放进上下文;用一个独立的评判步骤检查是否真的完成 |
它们都不是模型"不够聪明",而是信息不对称:
模型不知道自己已经试过了、不知道自己错了、不知道自己还没做完。
所以解决 Agent 可靠性问题的主要手段,不是在模型上下功夫,
而是把你的程序和状态管理做得更好——把模型看不见的东西显式写进上下文。
图解 · 模型看不见自己已经试过什么
互动 · 工具调用能力是怎么长出来的
| 方案 | 核心做法 | 解决了什么 |
|---|---|---|
| ReAct 2022 | 把「思考」和「行动」交替输出 | 给出了通用范式。今天几乎所有 Agent 都是它的变体 |
| Toolformer 2023 | 训练模型自己学会"什么时候该调工具" | 把工具调用从"提示工程"变成了模型的内在能力 |
| Function Calling 2023 | API 层面原生支持结构化输出 | 不用再靠字符串解析,可靠性大幅提升。成为今天的标准做法 |
| Code Interpreter 2023 | 给模型一个真的 Python 沙箱 | 一个"万能工具"——需要什么现算,不用预先定义每个工具 |
| MCP 2024 | 把"工具怎么接"标准化成一个协议 | 工具从"每个应用自己写"变成"写一次到处能用"。 这是把 Agent 生态从作坊推进到工业化的那一步 |
| Computer Use 2024-25 | 直接控制鼠标键盘、看屏幕截图 | 不需要 API 了——任何人能用的软件它都能用。 代价是慢、贵、而且容易点错 |
| SWE-Agent 等垂直 Agent 2024-25 | 把循环和工具针对一个领域深度定制 | 在软件工程等任务上,专用 Agent 远超通用 Agent—— 因为工具集和反馈信号都是精心设计的 |
这一章有两句听上去像口号的话:「Agent 的可靠性是相乘的」「上下文是平方增长的」。 它们其实都能算。这一节就真的算一遍——公式里每一个符号,下面图上都有一块。
动画 · 每一轮到底重发了多少内容:它其实是一个三角形
互动 · 每个符号管图上的哪一块
图解 · 「可靠性是相乘的」:每一步都乘一次,不是加一次
互动 · 重试为什么是最划算的杠杆
互动 · 单次问答 vs Agent 走完:token 账对比
把「Agent 轮数」从 3 拖到 30:三角形公式那一栏从 6 涨到 465,而实际重发段数一模一样。
再把「摘要窗口」从 30 调到 2——三角被削成一根细柱,省下的比例直接跳到 85% 以上。
这就是第 5 节那条粉色曲线为什么向上弯,也是「该忘什么」为什么是一道成本算术题。
互动 · 单步 95% 正确,10 步之后还剩多少
| 代价 | 说明 | 什么时候真的会痛 → 换什么 |
|---|---|---|
| 可靠性随步数衰减 | 每一步单独看都很准(95%+),但组合起来会衰减。 10 步之后可能只剩 60%。而人类用户对"助手"的期望是 99% | 任务要 10 步以上、又要求接近 99% → 把长任务拆短,每步加重试与校验 |
| 成本是聊天的几十倍 | 如上所述,上下文平方增长。一个跑 20 轮的 Agent, 花费可能是同样问题的单次问答的 约 60 倍 | 轮数多、上下文长,账单按 token 计 → 上摘要压缩/检索,把确定性步骤移出模型 |
| 延迟不可控 | 每一步都要等一次模型推理。10 轮就是 10 次串行等待, 用户可能要等好几分钟 | 用户在等一个交互式结果 → 并行工具调用、减少轮数、换更小的模型 |
| 几乎没法测试 | 同一个输入每次走的路径可能都不同(模型有随机性)。 传统单元测试那套"给定输入期望输出"在这里基本失效(单元测试 = 每次只测一个小函数), 只能做端到端的成功率评估(端到端 = 整条任务跑一遍看结果) | 要上线、要回归测试 → 固定温度/随机种子,断言整条轨迹,把确定性步骤移出模型 |
| 安全面极大 | 一个能执行代码的 Agent,就能删掉你的数据库、发邮件给你的客户、 花掉你的钱。而且 prompt injection 可以通过它读到的任何一个网页、 任何一封邮件进入。权限必须收得比你能接受的更紧 | Agent 能做写操作 → 白名单 + 人工确认,永远别给全权 |
不要追求"全自主 Agent"。今天的可靠做法是
把 Agent 嵌在一个确定性的程序里:程序负责流程和校验,
模型只负责那些"真的需要判断"的小步骤。
让模型做决定,让你的代码做执行和把关。
这也正是再下一章"多智能体与工作流"要讲的核心——大多数时候你要的是一个工作流,不是一个 Agent 群。
互动 · 延迟不可控:串行等待是墙,并行工具调用是唯一便宜的出口
图解 · 工作流还是 Agent:这是本章真正要你带走的选择
Agent 不属于第一性原理那张图上的任何一条线,这不是偷懒—— 这一章讲的是怎么把前面所有章的模型当成一个零件来用,它的第一性原理就是「组装」。
回到第 8 节。把「单步成功率」从 0.95 拖到 0.99—— 看 10 步后那条曲线抬起来多少。再把「每步失败后重试次数」从 0 拖到 2,看整条曲线往上移。
那个瞬间你看到的就是「相乘」这件事:单步那点提升,被十步一乘就放大成了天壤之别; 而这就解释了为什么 Agent 的可靠性不能靠「换个更强的模型」解决。
再去第 5 节,把「Agent 轮数」从 3 拖到 30——看粉色曲线怎么向上弯(那是 N²,不是 N)。 两张图合起来,就是「为什么今天不能真的全自动」的全部理由。
| 看起来像 | 实际上是什么 | 后果 |
|---|---|---|
| 「模型在自主思考、自己决定下一步」 | 模型没有执行任何东西。它只是把下一步写成文字, 执行的是你的代码(第 4 节) | 权限校验、参数校验、循环上限——全得写在你的代码里 |
| 「它有记忆,记得上一轮干了什么」 | 模型是无状态的。是你把整段历史重发了一遍(第 5 节) | “该忘什么”变成一道成本算术题,而且它直接影响可靠性 |
| 「单步 95% 已经很准了」 | 成功率是相乘的,不是相加的; 而且 95% 是平均值,长尾任务(少数特别难、特别长的)远低于它 | 要么提高单步、要么重试、要么把长任务拆短。没有第四条路 |
| 暗线 | 这一章的回答 |
|---|---|
| A 信息流动 | 每一轮的输入 = 之前所有轮次的拼接 + 本轮新内容,输出只是一小段文字(可能是工具调用);
|
| C 参数账本 | 模型参数量一点没变——Agent 不训练模型,这是它和前面所有章最大的区别;
变的是 token 账本:跑 20 轮的累计输入约为单次问答的 60 倍。另一笔是 |
Agent = 模型 + 工具 + 循环;模型只吐出"看起来像函数调用的文字", 真正执行和把关的是你的代码——这也是它灵活、又容易被 prompt injection 的原因。 所以别追求全自主:把它嵌进确定性的程序里,程序管流程和权限,模型只负责需要判断的那几步。
下一章《上下文工程与记忆》接住。
上面讲的都是「够用」的版本。想往下挖,这里有三个入口—— 它们不是必修内容,是给想再往前走一步的读者准备的。