AI知识

Function Calling 全解析:大模型如何调用外部工具

9 次阅读更新于 2026/9/10

大语言模型(LLM)的核心能力是生成文本。无论是 GPT-4、Claude 还是开源的 Llama,它们本质上都是在做同一件事:根据输入预测下一个 token。但现实世界的任务远不止文本生成——查询天气、操作数据库、发送邮件、调用内部 API,这些都需要与外部系统交互。Function Calling(函数调用)正是为解决这一问题而生的机制:它让模型能够以结构化的方式声明"我需要调用某个工具",由应用层执行后将结果返回模型,从而完成闭环。

这不是一个可选的高级特性,而是构建生产级 AI 应用的基础能力。没有 Function Calling,模型只能"说";有了它,模型才能"做"。

核心工作流程

Function Calling 的运行机制可以拆解为四个步骤:定义、决策、执行、响应。理解这四步是掌握整个机制的关键。

图表加载中...

第一步:定义(Define)

开发者通过 JSON Schema 描述可用的函数,包括函数名称、用途说明、参数类型与约束。这些定义随用户消息一起发送给模型。模型不会"看到"函数的实现代码,它只知道函数的接口契约。

第二步:决策(Decide)

模型分析用户输入,判断是否需要调用函数、调用哪个函数、传入什么参数。这一步完全由模型自主完成。如果用户的问题不需要工具(例如"什么是机器学习?"),模型会直接回答而不触发任何函数调用。

第三步:执行(Execute)

模型本身不执行任何函数。 它只输出一段结构化的 JSON,表明意图。实际的函数调用由应用层(你的后端代码)负责执行。这是一个关键的安全设计——模型永远不会直接操作外部系统。

第四步:响应(Respond)

执行结果被送回模型,模型据此生成面向用户的自然语言回复。例如,天气 API 返回 {"temp": 28, "condition": "sunny"},模型会回复"今天北京气温 28 度,晴天,适合户外活动"。

函数定义格式详解

函数定义的质量直接决定模型调用的准确性。以下是两个典型示例:

示例一:天气查询

{
  "type": "function",
  "function": {
    "name": "get_weather",
    "description": "查询指定城市的当前天气信息,包括温度、天气状况和湿度",
    "parameters": {
      "type": "object",
      "properties": {
        "city": {
          "type": "string",
          "description": "城市名称,如北京、上海、广州"
        },
        "unit": {
          "type": "string",
          "enum": ["celsius", "fahrenheit"],
          "description": "温度单位,默认摄氏度"
        }
      },
      "required": ["city"]
    }
  }
}

示例二:数据库查询

{
  "type": "function",
  "function": {
    "name": "query_database",
    "description": "根据条件查询用户数据库中的记录。支持按字段筛选和排序。",
    "parameters": {
      "type": "object",
      "properties": {
        "table": {
          "type": "string",
          "enum": ["users", "orders", "products"],
          "description": "要查询的数据表名"
        },
        "filters": {
          "type": "object",
          "description": "筛选条件,键为字段名,值为匹配值",
          "additionalProperties": true
        },
        "limit": {
          "type": "integer",
          "description": "返回记录数上限,默认 10",
          "default": 10
        }
      },
      "required": ["table"]
    }
  }
}

函数描述(description)是模型理解工具用途的唯一依据。描述越清晰、越具体,模型的调用决策就越准确。避免写"查询数据"这种模糊描述,应写明查询什么数据、支持哪些条件、返回什么格式。

模型的决策机制

一个常见误解是:模型会"运行"函数。事实上,模型只负责输出结构化的调用意图,本质上就是一段 JSON。

当用户问"北京今天天气怎么样?",模型的输出并不是天气信息,而是:

{
  "function_call": {
    "name": "get_weather",
    "arguments": "{\"city\": \"北京\", \"unit\": \"celsius\"}"
  }
}

整个过程可以理解为:

图表加载中...

模型之所以能做出正确的调用决策,是因为它在训练阶段学习了大量的函数调用模式。函数定义中的 descriptionparameters 信息帮助模型建立"用户意图 -> 函数选择 -> 参数填充"的映射关系。

并行函数调用

在复杂场景中,用户的一个请求可能需要同时调用多个函数。例如用户问"北京和上海今天的天气分别怎么样?",模型可以一次性输出两个函数调用:

{
  "tool_calls": [
    {
      "id": "call_1",
      "function": { "name": "get_weather", "arguments": "{\"city\": \"北京\"}" }
    },
    {
      "id": "call_2",
      "function": { "name": "get_weather", "arguments": "{\"city\": \"上海\"}" }
    }
  ]
}

应用层可以并发执行这两个调用,将所有结果一次性返回给模型。这显著减少了交互轮次,提升了响应速度。并行调用的前提是函数之间没有依赖关系——如果第二个函数的参数依赖第一个函数的返回值,则必须串行执行。

主流平台对比

不同 LLM 服务商在 Function Calling 的 API 设计上存在差异,但核心概念一致:

特性OpenAI(GPT)Anthropic(Claude)开源模型(如 Qwen)
API 字段名tools / tool_choicetools / tool_use因框架而异
函数定义格式JSON SchemaJSON Schema(相同)JSON Schema(相同)
并行调用支持支持部分支持
调用结果角色role: "tool"tool_result content block因框架而异
强制调用指定函数tool_choice: {"type": "function", "function": {"name": "xxx"}}tool_choice: {"type": "tool", "name": "xxx"}因框架而异
禁用函数调用tool_choice: "none"tool_choice: {"type": "none"}因框架而异

尽管 API 字段名不同,JSON Schema 格式的函数定义在各平台间是通用的。如果你的应用需要支持多个模型,建议将函数定义与平台适配层分离。

实战代码示例

以下是一个完整的 Python 示例,展示如何使用 OpenAI SDK 实现 Function Calling:

import json
import openai

# 1. 定义可用工具
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "查询指定城市的当前天气",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {"type": "string", "description": "城市名称"},
                    "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
                },
                "required": ["city"]
            }
        }
    }
]

# 2. 实际的函数实现
def get_weather(city: str, unit: str = "celsius") -> dict:
    """模拟天气 API 调用"""
    mock_data = {
        "北京": {"temp": 28, "condition": "晴", "humidity": 45},
        "上海": {"temp": 31, "condition": "多云", "humidity": 72},
    }
    return mock_data.get(city, {"temp": 0, "condition": "未知", "humidity": 0})

# 3. 对话主循环
def chat_with_tools(user_message: str):
    messages = [{"role": "user", "content": user_message}]

    # 第一次调用:让模型决定是否需要工具
    response = openai.chat.completions.create(
        model="gpt-4o",
        messages=messages,
        tools=tools
    )

    msg = response.choices[0].message

    # 如果模型决定调用函数
    if msg.tool_calls:
        messages.append(msg)

        for tool_call in msg.tool_calls:
            func_name = tool_call.function.name
            func_args = json.loads(tool_call.function.arguments)

            # 执行对应的函数
            if func_name == "get_weather":
                result = get_weather(**func_args)
            else:
                result = {"error": f"未知函数: {func_name}"}

            # 将执行结果添加到消息历史
            messages.append({
                "role": "tool",
                "tool_call_id": tool_call.id,
                "content": json.dumps(result, ensure_ascii=False)
            })

        # 第二次调用:模型根据结果生成最终回复
        final_response = openai.chat.completions.create(
            model="gpt-4o",
            messages=messages,
            tools=tools
        )
        return final_response.choices[0].message.content

    # 模型直接回复,无需工具
    return msg.content

# 运行
print(chat_with_tools("北京今天天气怎么样?"))

代码中的核心模式是 两次 API 调用:第一次让模型决策,第二次让模型基于工具返回的结果生成最终回复。在生产环境中,你需要加入重试机制、参数校验和超时控制。

最佳实践

编写高质量的函数描述

函数描述是模型理解工具的唯一途径。好的描述应当包含:工具的用途、适用场景、参数含义、返回值说明。

{
  "description": "在公司内部知识库中搜索文档。适用于用户询问公司政策、产品文档、技术规范等内部信息时调用。返回最相关的 Top-K 文档片段。"
}

参数约束要严格

利用 JSON Schema 的 enumminimummaximumpattern 等约束条件,减少模型产生无效参数的概率。

做好错误处理

函数执行可能失败。应用层必须捕获异常并返回结构化的错误信息,让模型能据此给出合理的回复,而不是崩溃或返回空值。

try:
    result = call_external_api(params)
except TimeoutError:
    result = {"error": "服务超时,请稍后重试"}
except Exception as e:
    result = {"error": f"调用失败: {str(e)}"}

校验模型输出

模型生成的参数不一定总是合法的。在执行函数前,务必校验参数类型、范围和格式。

永远不要盲目信任模型输出的参数。模型可能产生不存在的枚举值、超出范围的数字、格式错误的日期。在执行前做校验是生产环境的刚性要求。

Function Calling vs 基于 Prompt 的工具调用

在 Function Calling 机制出现之前,开发者通常通过 Prompt 工程让模型输出结构化文本来模拟工具调用。两种方式的对比如下:

维度Function CallingPrompt 工程方式
输出格式原生结构化 JSON需要从文本中解析
可靠性高,有 Schema 约束低,格式可能不稳定
参数校验模型层面自动对齐完全依赖 Prompt 指令
多函数选择原生支持需要复杂的 Prompt 设计
并行调用原生支持很难实现
模型要求需要模型原生支持任何模型均可尝试
开发成本低,标准化接口高,需要大量调试

Function Calling 在可靠性和开发效率上有决定性优势。但对于不支持 Function Calling 的模型(如部分小参数开源模型),Prompt 方式仍然是可行的降级方案。

与 Agent 系统的关系

Function Calling 是构建 AI Agent 的基石。

Agent 的核心能力是"感知 -> 思考 -> 行动"的循环。其中"行动"环节就依赖 Function Calling 来实现。一个典型的 Agent 架构如下:

图表加载中...

在 LangChain、LlamaIndex 等主流 Agent 框架中,工具定义的底层格式就是 Function Calling 的 JSON Schema。Agent 框架在此基础上增加了记忆管理、多步规划、错误重试等高级能力,但工具调用的核心协议始终是 Function Calling。

理解了 Function Calling,你就掌握了 AI Agent 工具调用的底层原理。无论框架如何演进,这一层协议的设计思想都是稳定的:用结构化的契约描述工具能力,让模型在理解用户意图后自主选择和组合工具,由应用层安全执行。

评论

登录 后参与评论

加载中...