LLM 是如何使用工具的

我一直把 Large Language Models(LLM)看成高级文本处理器,这个理解从技术上讲也没错:你给它文本,它回你文本。这个心智模型在大多数时候都很好用,直到我遇到“tool calling”或者“Model Context Protocol(MCP)”这类词。突然之间,这些模型不只是会“说”,还会事:调 API、跑代码、读取实时信息。

这让我开始思考一个问题:如果 LLM 本质上只是处理和生成文本,为什么有些模型能用外部工具,有些却不行?到底是什么,让一个模型可以查天气,而另一个只能写一首关于天气的诗?

这不只是 Prompt 小技巧

我最初的假设是,“tool calling”不过是更花哨一点的 prompt engineering。 我脑补的是一个超长 system prompt,用自然语言把函数定义得巨细无遗,然后祈祷 LLM 能输出“看起来像函数调用”的文本。如果没生成对,就再赌一次。接着我们开发者再去解析输出,提取函数名和参数。 这个方法在某些场景能凑合,但本质上很脆弱也不可靠。我们太习惯确定性的代码执行了,而 LLM 是概率模型。它在生成文本,而这段文本理论上可以是任何东西。

为了让 function calling 变成原子操作(要么发生,要么不发生),我们会用 API 里专门的结构化字段。像 Anthropic 的 Claude、OpenAI 的 GPT-4 这类现代模型,都支持在 API 请求中传 tools 参数。这不只是 SDK 帮你封装的便利功能,而是模型接口的一等公民。

发起 API 调用时,你会传一组可用工具,每个工具都有清晰结构:

  • name:工具的唯一标识。
  • description:这个工具做什么的自然语言说明。
  • input_schema:机器可读的 schema(比如 JSON Schema),定义工具接受哪些参数。

下面是一个简化版示例,展示请求 LLM 时的样子:

{
  "model": "claude-3-5-sonnet-20240620",
  "messages": [
    {
      "role": "user",
      "content": "What's the weather like in London?"
    }
  ],
  "tools": [
    {
      "name": "get_weather",
      "description": "Get the current weather in a given location.",
      "input_schema": {
        "type": "object",
        "properties": {
          "location": {
            "type": "string",
            "description": "The city and state, e.g. San Francisco, CA"
          }
        },
        "required": ["location"]
      }
    }
  ]
}

通过把这类结构化数据放在主 prompt 之外,我们是在明确告诉模型:它现在有哪些能力可用。这个结构化格式对安全性和可靠性非常关键,因为它让模型清楚知道可调用工具的精确签名,大幅减少歧义。

并不是所有 LLM 都一样

这个发现又让我意识到下一件事:并不是每个模型都能处理 tools 参数。tool calling 是一种专门能力,不是 LLM 的通用默认能力。我查下来发现,很多模型,特别是较老或者某些开源版本,压根不支持这个。如果你给不支持的模型传 tools,通常只会收到报错。

这也正是我最初问题的答案核心:能不能使用工具,是模型和它的推理引擎共同提供的一项能力。

模型家族是否支持 Tool Calling
GPT-4 / GPT-4o
Claude 3 / 3.5
Google Gemini
Llama 3.1
许多更老或更小的开源模型

不过这也不只是商业模型的特权。开源社区跟进得非常快。像 Ollama 这样的推理引擎,现在也支持兼容模型的 tool calling(例如 Llama 3.1、Deepseek),而且很多时候还提供 OpenAI 兼容的 API 格式,这对开发者非常友好:同一套应用更容易接不同模型。

不止是训练这么简单

所以,模型到底为什么能使用工具?我一开始的想法很直接:“这肯定全靠训练。”虽然训练确实是大头,但故事并不止于此。真正的关键发生在专项训练专用推理引擎的交汇处。

1. 训练:学会“工具语言”

对开发者来说,把 tool calling 理解成“模型学会了一门新的、结构高度严格的语言”会更直观。这个能力通常通过 Supervised Fine-Tuning(SFT) 获得。

在这个阶段里,模型会用大量精心整理的数据训练。每条样本通常包含:

  • 用户请求(例如 “What’s the weather in London?”)。
  • 可用工具定义列表(就是前面提到的 tools schema)。
  • 期望输出:格式完美、语法正确的工具调用(例如 {"name": "get_weather", "arguments": {"location": "London, UK"}})。

当模型吃进几百万条这样的样本后,它学到的不只是“怎么描述调用函数”,而是“怎么精确生成可执行调用的语法”。它会把模糊的自然语言意图,映射到 input_schema 规定的刚性结构里。这就是“理解请求”到“输出机器可执行动作”之间的桥梁。

2. 推理引擎:运行时编排者

这是我一开始忽略、但对开发者又特别关键的一层。训练好的模型本质上只是一组权重;而推理引擎才是把它运行起来的那层软件。对于 tool calling 场景,推理引擎不只是一个文本生成器,它是主动参与者。

运行时大概这样工作:

  1. 当你发带 tools 参数的请求时,推理引擎会先把这些信息喂给模型。
  2. 模型照常开始生成响应。但此时推理引擎会持续监控输出,寻找一个特殊信号——通常是某个特定 token 或结构化格式——表示模型想调用工具。
  3. 一旦检测到这个信号,引擎就会暂停文本生成
  4. 然后解析已生成文本,提取结构化工具调用(get_weather(...))。
  5. 最后把这个结构化数据返回给你的应用,并暂停对话,等待工具执行结果再继续。

这也是为什么不是所有强模型都天然支持工具调用。这个能力需要两件事同时成立:模型本身要被训练成会产出工具调用语法;推理引擎也要被设计成能识别该语法、停止生成并把控制权交回开发者代码。它是模型知识和运行时逻辑之间的共生关系。

对开发者来说的现实问题

理解“原理”是一回事,落到真实应用怎么做是另一回事。我在研究 tool calling 的工程实践时,总结出几个关键点。

第一,就算模型做过工具调用训练,也并不完美。它仍然是概率模型,始终有小概率产出格式错误的调用,或者该调用时没调用。所以稳健的错误处理和重试逻辑,责任在开发者这边,通常放在应用的“orchestrator”里。你的代码要验证模型输出,并在不符合预期时决定下一步怎么处理。

第二,从开发者视角看,这个过程是“原子化”的。一次 API 调用只会有两种结果:要么模型返回普通文本;要么返回一个结构化的工具调用请求。你不会看到模型的“内心挣扎”,也不会拿到半成品调用。如果模型这一轮决定不用工具,就像工具在这轮根本不存在。这种设计大大简化了开发工作。

用 MCP 把这一切串起来

最后,我的探索走到了 Model Context Protocol(MCP)。我理解到,MCP 是一个标准化协议,目的是让 LLM 与工具之间的连接更健壮、更可互操作。你可以把它看成 AI 工具世界里的通用转接头。

一个 MCP server 可以托管一组工具,而任何兼容 MCP 的客户端或 LLM 都可以发现并使用它们。在这个生态里:

  • Tool Calling 是模型调用函数的能力
  • MCP 是把这些函数标准化暴露出来、供模型调用的框架

在实际应用里,你的程序只要对接某个 MCP server,LLM 就能自动拿到可用工具列表,以及对应的 schema 和 description。这样管理大量工具会干净很多,也让不同模型可以复用同一套工具集,而不需要为每个模型做一套定制集成。

从文本处理器到行动执行者

我最初把 LLM 视为“文本输入、文本输出”机器,这个认知其实并不完整。现在我更明确地理解到:使用工具是一种经过有意训练的能力,它让模型从被动的信息处理器,变成能与数字世界交互的主动 agent。

这次深入研究后,我的核心结论很明确:

  1. Tool calling 是特定模型的原生能力,通过 API 请求中的结构化 tools 参数启用。
  2. 这项能力来自专项训练和 fine-tuning,而不只是靠巧妙 prompt。
  3. 作为开发者,我们要负责编排工具执行并处理潜在错误。
  4. MCP 这样的标准正在打造更强互操作性的生态,让 LLM 更容易连接外部能力。

这段探索让我把 tool-calling LLM 背后的“魔法”看清楚了。它们依旧在处理文本,但由于训练方式不同,它们学会了理解并生成一种特殊文本——结构化 API 调用——从而真正具备了执行有意义动作的能力。