4. 工具调用(Function Calling):让大模型学会使用工具

好,咱们进入第四章节。说实话,Function Calling 是我个人觉得大模型应用里最实用的能力之一。你想想看,如果大模型只能聊天,那它就是个高级聊天机器人。但一旦它能调用工具,那它就能帮你订机票、查天气、操作数据库——这才是真正的智能体。

4.1 OpenAI Function Calling 原理

先说说原理。Function Calling 的核心思想很简单:让大模型理解有哪些工具可用,然后让它决定什么时候用、用什么、怎么用

具体流程是这样的:

  1. 你定义好工具的描述(我们叫它 Schema)
  2. 把 Schema 和用户问题一起发给大模型
  3. 大模型判断是否需要调用工具
  4. 如果需要,它返回一个结构化的调用请求
  5. 你执行这个请求,把结果返回给大模型
  6. 大模型基于结果生成最终回答

嗯,这里要注意:大模型并不真正执行代码。它只是告诉你「我想调用这个函数,参数是这些」。真正执行的是你——你的应用程序。

关键理解: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 和记忆系统是怎么配合的。