第4章:Function Calling——让大模型从「聊天者」变成「执行者」
系列导读:从本章开始,我们进入 LLM 的「行动能力」阶段。如果你还没看第1章,建议先了解 LLM 的本质,再去理解为什么它能「调用工具」是真正的事半功倍。
一、Function Calling 到底是什么?
LLM 本身是一个文本生成器,它生成了天气查询请求,但它不能自己去调用 API。
Function Calling(工具调用)就是解决这个问题的机制:让 LLM 输出结构化的工具调用指令(通常是 JSON 格式),你的代码负责解析并执行真实的调用。
核心原则:模型只判断「该做什么」,代码负责「真正把它做了」。
二、Function Calling 的两轮对话流程
用户提问 → LLM 判断需要调工具 → 输出 tool_call JSON →
代码解析 JSON → 真正调用 API → 获得结果 →
把结果塞回对话 → LLM 生成最终答案
这是最基础的流程,实际可能有多次工具调用循环。
具体例子
Round 1 - 用户输入:
“北京今天的天气怎么样?”
Round 1 - LLM 响应(不是答案,是工具调用指令):
{
"tool_calls": [{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\": \"北京\", \"date\": \"2026-07-21\"}"
}
}]
}
代码层 - 解析并执行:
tool_call = response.tool_calls[0]
if tool_call.function.name == "get_weather":
args = json.loads(tool_call.function.arguments)
result = get_weather(args["city"], args["date"])
Round 2 - 把结果塞回对话:
User: 北京今天的天气怎么样?
Assistant: [tool_call: get_weather(city="北京", date="2026-07-21")]
Function: {"temperature": 32, "condition": "晴", "humidity": "65%"}
Assistant: 北京今天晴天,气温 32℃,湿度 65%,非常适合户外活动!
三、Schema 定义:关键工程细节
Function Calling 的核心是 schema 的定义。schema 告诉模型每个工具:
- name:工具名称,越具体越好
- description:工具的详细用途,模型靠这个决定要不要调它
- parameters:参数结构,包含每个参数名、类型、描述、是否必填
- required:必填参数列表
Schema 示例:
{
"name": "get_weather",
"description": "获取指定城市和日期的天气信息,包括温度、天气状况、湿度。支持中国境内城市。",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "要查询天气的城市名称,例如:北京、上海、广州"
},
"date": {
"type": "string",
"description": "查询日期,格式为 YYYY-MM-DD"
}
},
"required": ["city", "date"]
}
}
description 是最关键的字段。模型完全靠 description 来判断该不该调这个工具。写 description 的技巧:
- 明确边界:不是什么都调,只在什么场景下调
- 给出例子:比如 “当用户问 xx 时调用”
- 避免触发词陷阱:description里别用模型的名字(如 “ChatGPT”),否则模型可能误触发
四、Function Calling 和「土办法」的区别
在没有 Function Calling 之前,开发者只能靠解析自然语言文本来判断模型要不要调工具:
# 土办法
response = model.chat("帮我查北京天气")
if "天气" in response:
# 手动解析,极其脆弱
city = extract_city(response) # 容易出错
Function Calling 以后:
# Function Calling 方式
response = model.chat("帮我查北京天气", tools=[weather_tool_schema])
if response.tool_calls:
# 模型输出的是结构化 JSON,解析安全得多
tool_call = response.tool_calls[0]
关键区别:结构化输出 vs 自然语言解析。结构化输出准确率接近100%,自然语言解析经常抽风。
五、高级特性
1. 并行工具调用
模型可能在一次响应中输出多个 tool_calls,比如用户问"北京和上海的天气":
{
"tool_calls": [
{"function": {"name": "get_weather", "arguments": "{\"city\": \"北京\"}"}},
{"function": {"name": "get_weather", "arguments": "{\"city\": \"上海\"}"}}
]
}
你的代码可以并行执行两个调用,然后再把结果一起塞回去。
2. 强制使用工具
有些场景要求模型必须调工具,不提供直接回答:
response = model.chat(query, tools=tools, tool_choice="required")
# 设置 tool_choice 为 "required" 强制模型输出 tool_calls
3. 多轮工具调用链
复杂任务可能需要多次调用:查天气 → 根据温度推荐衣服 → 查找推荐店铺的地址 → 查询交通路线。
每一轮把结果塞回 history,LLM 自动判断下一步该做什么。这就是 Agent 的基础。
六、常见坑
| 坑 | 解决方案 |
|---|---|
| 模型选错工具 | description 写清楚边界 + 用 tool_choice 限制 |
| 参数类型错误 | schema 中严格定义 enum/number/string |
| 函数结果太长塞爆 context | 摘要压缩后返回 |
| 工具调用死循环 | max_iterations 限制 + 超时熔断 |
| 结果格式不一致 | 在 function result 中也标准化输出格式 |
七、本章小结
- Function Calling = 模型决策 + 代码执行,两者严格分离
- Schema description 是核心,决定了模型是否选对工具、传对参数
- 并行调用、强制调用、多轮链式调用是高级用法
- 它是通往 Agent 的必经之路
思考题
- 为什么 Function Calling 要求模型输出的是 JSON schema 而不是自由文本?这背后的设计哲学是什么?
- 如果模型的 tool_calls 返回的参数含危险操作(如删除文件),你的拦截策略应该放在哪一层?