从一次"以为自己懂了"说起
我之前写过一篇《从 0 到 1 打造一个 Agent 系统》,从主循环讲到多 Agent 协作,十三层架构拆得清清楚楚。写完之后觉得自己对 agent 的理解已经很透彻了。
直到有一天我停下来认真想了一个问题:大模型本质上就是一个文本生成器,给它一段话,它续写下一段话。那它到底是怎么做到"调用工具"这件事的?function call 不也是输出文字吗?从一段文字变成一个真实的搜索动作、一次真实的文件读取,这中间到底发生了什么?
我发现我说不清楚。
十三层架构里每一层我都能讲,但最底下那一层,那个"文字怎么就变成了行动"的跨越,我其实没有真正想透。
这篇文章就是把这个问题彻底拆解之后的记录。如果你也对 agent 有一种说不出的"神秘感",这篇可能会帮你彻底消除它。
· · ·
第一个事实:大模型没有"做事"的能力
先建立一个共识。
大模型的全部能力就是:接收一段文本,生成下一段文本。它不能访问网络,不能读文件,不能执行代码,不能调用任何 API。输入是 token,输出也是 token。它就是一个被锁在纯文本世界里的大脑,极其聪明,但没有手脚。
那当我们说"大模型调用了搜索工具"的时候,到底是谁在调用?
· · ·
Function Call 的真相:它就是一段带格式的文字
在没有 function call 机制的时代,人们怎么让大模型"使用工具"?靠 prompt 工程。你在系统提示里告诉模型:
你有以下工具可以使用。当你需要搜索时,请用以下格式输出:{"name": "web_search", "arguments": {"query": "搜索内容"}}
模型就照做了。用户问"北京今天天气怎么样",模型输出:
{"name": "web_search", "arguments": {"query": "北京今天天气"}}它并不知道自己在"调用工具"。它只是根据上下文,预测最合理的下一段输出,恰好这段输出是一个结构化的 JSON。
现在各家 API 提供的 function calling 只是把这个过程标准化了:模型被训练成输出特定的 JSON 结构,API 返回时自动帮你标记为 tool_use 类型,方便你的程序解析。但底层逻辑没有任何变化。
所谓的 function call,就是一段带有约定格式的文字。仅此而已。
到这里,一个关键的问题浮出水面:一段 JSON 文字,怎么就变成了真正的搜索动作?
· · ·
真正的关键:编排层
这是整篇文章的核心。
模型输出了一段 JSON,比如 {"name": "web_search", "arguments": {"query": "北京天气"}}。这段文字被你的程序接收到。
注意,到这一步为止,什么都没有发生。没有任何搜索被执行。这就是一串字符,跟模型回复"你好"没有本质区别。
接下来发生的事情,全部是你自己写的普通代码在做的,没有任何 AI 参与。
第一步:字符串解析。 你的代码检测模型的输出里是否包含工具调用格式,提取出工具名和参数。这是普通的 JSON 解析。
第二步:函数分发。 你的代码里预先注册了一组真正的工具函数。比如 web_search 对应一个真正能发起 HTTP 请求的搜索函数,get_weather 对应一个真正能查天气 API 的函数。根据解析出的工具名,找到对应的函数。
第三步:执行。 调用那个真正的函数,发起真正的网络请求,拿到真正的搜索结果。这一步,才是"行动"真正发生的地方。
第四步:回注。 把执行结果塞回对话历史,再次调用模型。模型看到搜索结果后,基于结果生成最终的回答。
用代码来表达,整个过程就是这样:
messages = [{"role": "user", "content": "北京今天天气怎么样?"}]# 预先注册好的真实工具函数tools = {"web_search": real_search_function,"get_weather": real_weather_function,}while True:response = call_llm(messages)if response.type == "text":# 模型直接回复了文字,任务完成print(response.text)breakif response.type == "tool_use":# 模型输出了工具调用的 JSON# 但这只是一段文字,什么都还没发生# 现在,由我们的代码来真正执行tool_fn = tools[response.tool_name]result = tool_fn(**response.arguments)# 把结果塞回对话历史,让模型继续思考messages.append({"role": "tool", "content": result})
从文字到行动的桥梁,不是 AI,是你写的这几十行普通代码。
这段代码就叫编排层(Orchestration Layer)。它是整个 agent 系统真正的心脏。大模型是大脑,负责思考和决策,但它只能输出文字。编排层是躯干,负责把文字意图翻译成真实的行动。工具是四肢,是具体的执行能力。三者通过文字来交换信息,构成一个完整的循环。
· · ·
模型为什么"知道"要调用工具?
理解了编排层之后,接下来最容易产生误解的就是这个问题。很多人觉得模型"决定"调用工具,好像它有某种意图和动机。
它没有。
模型做的事情从头到尾只有一件:预测下一个最合理的 token。
它怎么知道有哪些工具?
因为你在 prompt 里写了。工具定义会被拼接进系统提示,模型通过"阅读"这段文字来了解可用的工具。它并不真的"拥有"这些工具,它只是读到了一份说明书。
比如你定义了一个天气工具,模型实际看到的 prompt 大概长这样:
你可以使用以下工具:工具名称:get_weather描述:查询指定城市的实时天气参数:- city(字符串):城市名称,如"北京"当你需要使用工具时,请输出以下 JSON 格式...
模型通过阅读这段文字来决定什么时候调用什么工具,跟你看一份产品使用手册没有区别。
它怎么知道用什么格式?
同样因为 prompt 里写了。你告诉它"用 JSON 格式",它就输出 JSON。你告诉它"用 XML 标签包裹",它就输出 XML。
经过 function calling 专门训练的模型,是在训练阶段就见过了大量的工具调用样本,所以它已经内化了这种格式习惯。这跟模型能写 Python 代码是一个道理:见得足够多,就学会了。
它为什么"想"调用工具?
它不"想"。当它看到"用户问了实时天气 + 系统提示里有搜索工具可用"这个上下文时,基于训练数据中的海量模式,它"学到"了这样一个规律:此时输出工具调用格式,是统计意义上最合理的续写。
这跟你问一个经验丰富的助手"今天天气怎么样",助手会说"我帮你查一下"是一回事。助手不是因为有"调用工具的动机"才这么做的,而是因为经验告诉他,这种问题不能瞎编,应该去查。模型也一样,只不过它的"经验"来自训练数据中几百万个类似的模式。
所以回答这个问题:模型为什么知道要调用工具?短期靠 prompt 引导(告诉它有什么工具、怎么用),长期靠训练(让它学会在合适的时机、用正确的格式去调用)。两者缺一不可。
· · ·
编排层的深层价值:不只是一个转发器
理解了编排层的本质之后,一个更有意思的东西浮出水面。
既然模型的每一个行动意图都必须经过编排层才能变成真实行动,那编排层其实握着真正的权力。模型说"我想执行这个操作",编排层可以选择执行,也可以拒绝。模型对此完全无能为力,因为它只能输出文字,执行权不在它手里。
这个"拒绝权"往深了想,可以延伸出一整套治理体系。
第一层:规则拦截。 最简单的 if/else。模型想删文件?检查路径是否在允许的目录内。模型想执行 shell 命令?检查命令有没有危险操作。不需要任何智能,纯粹的硬编码规则,但非常可靠。
第二层:人类审批(Human-in-the-Loop)。 模型想发一封邮件,编排层不自动执行,而是暂停循环,把邮件内容展示给用户:"模型想发这封邮件,你确认吗?"用户点确认才继续。这意味着你可以把操作分成两类:低风险的自动执行(搜索、读文件),高风险的需要人类批准(发邮件、花钱的 API 调用、删除数据)。Agent 保持了自主性,关键决策权还在人手里。
第三层:AI 审核。 编排层拦截到工具调用后,把请求发给另一个"审核模型"来判断是否安全。这就是"用 AI 监督 AI"。本质上还是利用了编排层这个拦截点,只不过检查逻辑从简单规则变成了另一个模型的判断。
第四层:动态权限。 不同场景给 agent 不同的权限等级。用户说"帮我看看这个文件夹",agent 拿到只读权限;用户说"帮我重构这个项目",agent 拿到读写权限。甚至可以做"权限升级":一开始只有低权限,需要高权限操作时自动触发人类审批。
第五层:成本熔断。 编排层是所有工具调用的必经之路,天然是做监控的最佳位置。当这次对话已经消耗了超过预算的 token 或 API 费用,编排层直接停止所有后续调用,告诉模型"预算用完了,用现有信息给最终答案"。
一个拦截点,撑起了一整套治理体系。
极端聪明的模型能跳过编排层吗?
不能直接跳过,但可以"绕路"。这两者有本质区别。
不能直接跳过,是因为这不是规则约束的问题,而是物理架构的问题。模型运行在一个进程里,编排层运行在另一个进程里。模型的输出必须通过编排层的代码才能变成行动。模型再聪明,它改不了你的 Python 代码。
但聪明的模型可以利用工具本身的能力走迂回路线。比如编排层禁止了 rm -rf /,但模型有一个"执行代码"的工具,它可以写一段 Python 脚本,脚本里间接调用删除操作。编排层如果只检查了工具名,没有深入分析代码内容,这个操作就溜过去了。
这不是跳过了编排层,而是编排层的检查深度不够。编排层的安全性取决于你在这个拦截点上投入多少设计:参数校验、路径沙箱、行为审计、链路分析、甚至用另一个模型来审核。你在这里投入多少,就能获得多少安全保障。
· · ·
一个工程问题:100 个工具怎么给?
前面都是原理,这里接一个实际问题。
3 个工具全塞进 prompt 没问题。但当你有 100 个工具的时候,问题就来了。每个工具的定义都要占 token,100 个工具可能吃掉上万个 token,挤压真正用来对话的空间。而且工具太多的时候,模型选错工具的概率会显著上升。
业界目前的主流思路是"按需提供",而不是一次性全给。
分层路由:先用一个轻量的分类器,根据用户问题判断它属于哪个类别(天气、邮件、代码),然后只把对应类别的工具塞进 prompt。用户问天气,只给天气相关的 3 个工具,不给其他 97 个。
语义检索:把所有工具的描述做成向量索引。用户问题进来后,用语义搜索找到最相关的 5 到 10 个工具,只把这些塞进 prompt。相当于给工具库建了一个搜索引擎。
多 Agent 分工:不让一个 agent 背所有工具。设一个主 agent 负责理解用户意图,然后分发给专门的子 agent。"邮件 agent"只知道邮件相关的 5 个工具,"代码 agent"只知道代码相关的 8 个工具。主 agent 的工具列表里只有"调用邮件 agent""调用代码 agent"这几个高层入口。
核心原则是让模型永远在一个精简的菜单里做选择,而不是面对一本工具百科全书。
· · ·
回到最初的问题
从文字到工具调用,没有黑魔法。
整个 agent 系统由三个角色构成:大模型是大脑,负责思考和规划,但它只能输出文字;编排层是躯干,负责把文字意图翻译成真实行动,同时握着拦截、审核、熔断的权力;工具是四肢,是具体的执行能力。它们之间通过一套约定好的文本格式来交换信息,构成一个不断循环的系统。
这个循环就是 agent 的全部。主循环本身永远是那几行代码:收集信息,让模型思考,执行工具,把结果喂回去。所有的复杂性都被推到了循环的外围。
如果你读过我之前写的《从 0 到 1 打造一个 Agent 系统》,那篇是鸟瞰整个架构的全景图,从主循环一路讲到多 Agent 协作、安全边界、可观测性。这篇是把最底下那一层放到显微镜下,看清了从文字到行动的每一步到底发生了什么。
两个视角合在一起,agent 对你来说就不再有任何神秘感了。
它就是一个循环,加上围绕这个循环的一圈基础设施。理解了这一点,你就理解了所有 agent 框架的底层逻辑。剩下的,只是工程实现的细节。
我建了一个AI学习研究群,目前几十来人,都是在真正动手搞AI的人。如果你也在自己跑模型、写代码、做项目,欢迎点赞关注转发一键三连,然后加我微信,备注写清"你在做的AI方向",聊得来的话我拉你进群。纯围观的和小白就不加了,有一定门槛。
