一个容易被忽略的事实
大模型本身只会做一件事:根据输入的文字,生成后面的文字。它不会真的联网,不会真的发邮件,不会真的读你的硬盘。
那 Agent 是怎么"动手"的?答案有点反直觉:
模型不亲自动手,它只是"说"出想调用哪个工具、传什么参数;真正动手的是外面那段程序。
模型负责"点菜",厨房(你的程序)负责"做菜"。这个"点菜"的机制,就叫 Function Calling(函数调用),也叫工具调用(Tool Calling)。
三步搞懂 Function Calling
第一步:告诉模型"你有哪些工具可用"
在每次请求时,除了用户的问题,你还会附上一份工具清单,每个工具用结构化的方式描述清楚。比如查天气这个工具:
{
"name": "get_weather",
"description": "查询某个城市今天的天气",
"parameters": {
"city": { "type": "string", "description": "城市名,比如 北京" }
}
}
这段东西相当于给模型看的"工具说明书":工具叫什么、干什么用、需要哪些参数。description 写得好不好,直接决定模型能不能在对的时机选对工具——这是实战里很吃经验的一环。
第二步:模型输出一个"调用请求"(而不是执行它)
当模型判断需要天气信息时,它不会假装自己查到了,而是输出一段结构化的调用请求:
{
"tool_call": {
"name": "get_weather",
"arguments": { "city": "北京" }
}
}
注意:这一步模型只是表达意图——"我想调用 get_weather,参数是北京"。它并没有、也不可能真的查到天气。球现在踢给了你的程序。
第三步:你的程序执行工具,把结果喂回去
你的程序(也就是第 2 篇说的调度器)收到这个请求后:
- 解析出"哦,要调 get_weather,city=北京";
- 真正地去调用天气 API,拿到
{"temp": 32, "desc": "多云"}; - 把这个结果作为一条新消息,塞回对话历史,再交给模型。
模型这才"看到"了真实天气,继续它的推理。是不是很眼熟?这正是上一篇 ReAct里的 Action(模型发出调用)→ Observation(程序返回结果) 那两步的底层实现。
完整走一遍
把三步串起来,一次带工具的对话是这样流动的:
你 → 模型:北京今天多少度?(附:工具清单[get_weather])
模型 → 你:我要调用 get_weather(city="北京") ← 只是意图,没真查
你的程序:真的调用天气 API → 得到 32℃
你 → 模型:get_weather 的结果是 {temp:32, desc:多云}
模型 → 你:北京今天 32℃,多云。 ← 拿到真实数据才作答
关键就一句:模型和真实世界之间,永远隔着你的程序这层"中介"。 模型说想做什么,程序决定要不要做、怎么做、结果给不给它看。这层中介也正是安全和可控的关键——第 8 篇讲护栏时会回到这里。
为什么这套设计很重要
- 能力可扩展:想让 Agent 多一项本领,不用重新训练模型,只要多写一个工具、加进清单就行。
- 安全可控:模型只能"请求",执行权在你手里。它请求删库,你的程序可以拦下来。危险操作可以要求人工确认。
- 结果可信:数据来自真实 API,而不是模型"编"出来的,大大减少一本正经胡说八道。
现实中的几个坑
Function Calling 听起来干净利落,实战里有几个常见麻烦:
- 参数给错:模型可能把城市名拼错,或漏填必填参数。程序要做校验,错了就把报错信息喂回去让它改。
- 选错工具:工具一多,模型容易选错,或者该用工具时偏要自己硬答。清晰的
description和适当的示例能缓解。 - 无限调用:万一模型一直反复调同一个工具停不下来?调度器要设最大步数上限兜底。
- 工具太多装不下:几十上百个工具的说明书会把上下文撑爆。这引出了下一个问题——工具怎么标准化、按需接入。
最后这个坑,正是催生 MCP 协议的背景。过去每接一个新工具都要手写一套对接代码,又碎又难维护。下一篇就讲这个正在成为行业标准的解决方案。
小结
- 大模型只会输出文字,它靠 Function Calling"说"出想调哪个工具,真正执行的是你的程序。
- 三步:给清单 → 模型输出调用请求 → 程序执行并把结果喂回。
- 这层"程序中介"带来了可扩展、可控、可信三大好处。
- 它就是 ReAct 里 Action/Observation 的底层实现。
下一篇,我们看看 MCP——一个让 Agent 接工具不再靠"手搓"的通用协议。