4. 工具调用(Function Calling):让大模型学会使用工具
好,咱们进入第四章节。说实话,Function Calling 是我个人觉得大模型应用里最实用的能力之一。你想想看,如果大模型只能聊天,那它就是个高级聊天机器人。但一旦它能调用工具,那它就能帮你订机票、查天气、操作数据库——这才是真正的智能体。
4.1 OpenAI Function Calling 原理
先说说原理。Function Calling 的核心思想很简单:让大模型理解有哪些工具可用,然后让它决定什么时候用、用什么、怎么用。
具体流程是这样的:
- 你定义好工具的描述(我们叫它 Schema)
- 把 Schema 和用户问题一起发给大模型
- 大模型判断是否需要调用工具
- 如果需要,它返回一个结构化的调用请求
- 你执行这个请求,把结果返回给大模型
- 大模型基于结果生成最终回答
嗯,这里要注意:大模型并不真正执行代码。它只是告诉你「我想调用这个函数,参数是这些」。真正执行的是你——你的应用程序。
关键理解:Function Calling 本质上是把「决策」和「执行」分开了。大模型负责决策,你负责执行。这种分工非常合理。
4.2 定义工具 Schema
工具 Schema 就是给大模型看的「使用说明书」。你得告诉它:这个工具叫什么、干什么用、需要什么参数。
我个人习惯用 JSON Schema 格式来定义。举个例子:
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的天气信息",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,比如北京、上海"
},
"date": {
"type": "string",
"description": "日期,格式为YYYY-MM-DD"
}
},
"required": ["city"]
}
}
}
这里有几个坑,我踩过:
- description 要写清楚:大模型靠这个理解工具用途。写得太模糊,它可能不会调用。
- 参数名要有意义:别用 a、b、c 这种。大模型看不懂。
- required 字段别漏了:不写的话,大模型可能不传这个参数。
我的经验:description 里可以加一些示例值。比如「city:城市名称,例如'北京'、'上海'」。这样大模型更容易理解。
4.3 解析工具响应
大模型返回的响应长什么样?我直接给你看个例子:
{
"choices": [{
"message": {
"role": "assistant",
"content": null,
"tool_calls": [{
"id": "call_123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\": \"北京\", \"date\": \"2024-01-15\"}"
}
}]
}
}]
}
注意几个要点:
- content 是 null:因为大模型决定调用工具,所以没有直接回复文本
- tool_calls 是个数组:一次可以调用多个工具
- arguments 是字符串:需要你自己 JSON.parse 一下
我曾经遇到过一个坑:arguments 里的 JSON 格式可能不规范。比如字符串用了单引号,或者多了个逗号。所以解析的时候最好加个 try-catch。
try {
const args = JSON.parse(toolCall.function.arguments);
// 正常处理
} catch (e) {
console.error('解析参数失败:', e);
// 可以尝试修复或重新请求
}
4.4 实现动态工具选择
动态工具选择,说白了就是让大模型自己决定用哪个工具。你不需要写 if-else 来判断用户意图。
举个例子,你定义了三个工具:
| 工具名 | 用途 |
|---|---|
| get_weather | 查天气 |
| search_flight | 查航班 |
| book_hotel | 订酒店 |
用户说「明天北京天气怎么样」,大模型会自动选择 get_weather。用户说「帮我查下后天去上海的航班」,它会选 search_flight。完全不需要你写规则。
实现代码大概是这样:
// 定义工具列表
const tools = [
{ type: "function", function: getWeatherSchema },
{ type: "function", function: searchFlightSchema },
{ type: "function", function: bookHotelSchema }
];
// 发送请求
const response = await openai.chat.completions.create({
model: "gpt-4",
messages: messages,
tools: tools,
tool_choice: "auto" // 让模型自动选择
});
// 处理响应
const message = response.choices[0].message;
if (message.tool_calls) {
for (const toolCall of message.tool_calls) {
const result = await executeTool(toolCall);
// 把结果加回对话
messages.push({
role: "tool",
tool_call_id: toolCall.id,
content: JSON.stringify(result)
});
}
}
注意:tool_choice 参数可以设置成 "auto"(自动选择)、"none"(禁止调用)或者指定某个工具名(强制使用)。我建议初期用 "auto",等熟悉了再玩高级的。
4.5 实战中的避坑指南
最后分享几个我踩过的坑:
- 工具数量别太多:一次传 10 个以上工具,大模型容易懵。我一般控制在 5-8 个。
- 参数要尽量简单:嵌套太深的参数结构,大模型容易搞错。扁平化设计更好。
- 记得处理错误:工具执行可能失败。比如查天气的 API 挂了。这时候要把错误信息返回给大模型,让它重新处理。
- 上下文别太长:每次调用工具都会增加对话长度。注意控制 token 消耗。
嗯,Function Calling 的内容就这些。说白了就是三步:定义工具、解析调用、执行返回。但真正用好它,需要你在实践中不断调整工具的描述和参数设计。
下一章我们会讲 Agent 的记忆管理,到时候你会看到 Function Calling 和记忆系统是怎么配合的。