Function Calling:让大模型稳定调用外部工具

FreeGuideOnline 最新 2026-06-14

Function Calling:让大模型稳定调用外部工具

在构建基于大语言模型的应用时,你很快就可能碰到一个核心问题:如何让模型使用最新的数据、执行精确计算或操作外部 API? 提示词里塞入整个数据库显然不现实,让模型直接生成 SQL 语句又容易出错且不安全。这就是 Function Calling(函数调用) 要解决的问题。它提供了一种标准化、可靠的方式,让大模型能够“意识”到外部工具的存在,并按需返回结构化指令来调用它们。

什么是 Function Calling

简单来说,Function Calling 不是让模型真的去执行函数,而是让模型生成一个结构化 JSON 对象,精确描述应该调用哪个函数以及传入什么参数。你的应用程序拿到这个 JSON 后,自行执行对应函数,再将结果返回给模型,由模型生成最终的自然语言回答。这套机制将大模型的推理能力与外部系统的执行能力无缝衔接,既避免了幻觉,又保持了语境连贯性。

核心能力

  • 工具感知:模型可以“看到”你定义好的函数签名(名称、描述、参数)。
  • 智能路由:根据用户意图,自动决定是否需要调用函数,以及调用哪一个。
  • 参数提取:从用户的自然语言输入中提炼出结构化的参数值。
  • 结果整合:接收函数执行结果,生成融合了外部信息的人性化回复。

为什么需要 Function Calling

没有 Function Calling 时,你可能尝试用提示词工程让模型输出 JSON,但格式经常不稳定,字段名偶尔拼错,或者模型擅自添加解释性文字。Function Calling 提供的是**模式约束(schema-constrained)**的输出,大模型服务端会强制遵循你给定的 JSON Schema,从而极大提升生产环境下的可靠性。

典型应用场景

  • 知识增强:调用搜索引擎或向量数据库,获取最新信息。
  • 数据查询:将自然语言转为 SQL 或 API 调用,查询用户订单、库存状态。
  • 动作执行:发送邮件、创建工单、控制智能家居设备。
  • 复杂计算:调用数学引擎完成精确的单位换算、金融精算等。

工作原理与流程

整个流程通常由你(开发者)编排,模型充当决策中枢:

  1. 定义工具:在请求中附带一个 tools 列表,每个工具声明一个函数,包括名称、描述和严格的 JSON Schema 参数定义。
  2. 用户输入:用户发送一条自然语言消息。
  3. 模型决策:模型判断是否需要调用工具。若需要,它会返回一个 tool_calls 对象,内含函数名和参数。
  4. 应用执行:你的代码解析这个对象,调用真实函数,获得结果。
  5. 结果回传:将函数执行结果以 tool 角色消息的形式追加到对话历史中。
  6. 最终回复:模型读取函数结果,生成融合了该结果的最终回答。
sequenceDiagram
    participant User
    participant App
    participant LLM
    participant Function

    User->>App: “北京今天天气如何?”
    App->>LLM: 发送消息 + 函数定义(get_weather)
    LLM-->>App: tool_calls: {name:"get_weather", args:{city:"北京"}}
    App->>Function: 调用 get_weather("北京")
    Function-->>App: {temperature: "26°C", condition: "晴"}
    App->>LLM: 追加工具结果消息
    LLM-->>App: “北京今天晴,气温26°C。”
    App->>User: 回复用户

一个完整的调用示例(基于 OpenAI API)

以下示例展示了一个天气查询函数调用的完整实现,使用了 OpenAI Python SDK 的最新接口。其他主流模型(如 Claude、Gemini)的调用方式类似。

第一步:安装依赖

pip install openai

第二步:定义函数工具

我们使用 tools 参数,其中包含一个函数定义。注意参数的 typedescription 对模型提取信息至关重要。

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "获取指定城市的实时天气情况",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "城市名称,例如:北京、上海"
                    }
                },
                "required": ["city"]
            }
        }
    }
]

第三步:发起对话并处理工具调用

import json
from openai import OpenAI

client = OpenAI()

def get_weather(city: str) -> str:
    # 模拟外部 API 调用
    return json.dumps({"city": city, "temperature": "26°C", "condition": "晴"})

messages = [{"role": "user", "content": "北京今天天气如何?"}]

response = client.chat.completions.create(
    model="gpt-4o",
    messages=messages,
    tools=tools,
    tool_choice="auto"  # 让模型自行决定是否调用
)

# 检查模型是否要求调用工具
message = response.choices[0].message
if message.tool_calls:
    # 遍历所有工具调用(可能并行调用多个)
    for tool_call in message.tool_calls:
        function_name = tool_call.function.name
        arguments = json.loads(tool_call.function.arguments)

        if function_name == "get_weather":
            result = get_weather(arguments["city"])

        # 将函数结果作为 tool 消息追加
        messages.append({
            "role": "tool",
            "tool_call_id": tool_call.id,
            "content": result
        })

    # 二次请求模型,生成最终自然语言回答
    final_response = client.chat.completions.create(
        model="gpt-4o",
        messages=messages
    )
    print(final_response.choices[0].message.content)

函数定义最佳实践

工具定义的质量直接决定模型调用成功率。请遵循以下原则:

名称与描述

  • 名称:使用下划线分隔的小写动词_名词结构,如 search_knowledge_basecreate_order。语义清晰,避免缩写。
  • 描述:简洁说明函数的功能、适用场景以及可能的影响。有副作用(如发送邮件)的函数务必注明。

参数 Schema

  • 详细描述每个参数:例如,对 date 字段说明“格式为 YYYY-MM-DD 的日期”,对 amount 说明“以分为单位的整数金额”。
  • 善用枚举:如果参数值固定,使用 enum 约束可以大幅提高准确率。
"parameters": {
    "properties": {
        "unit": {
            "type": "string",
            "enum": ["celsius", "fahrenheit"],
            "description": "温度单位"
        }
    }
}
  • 合理标记必填:仅将必须参数设为 required,可选参数给默认值或允许 null

工具选择策略

  • tool_choice: "auto":默认,模型自行判断是否调用工具。
  • tool_choice: "required":强制模型必须调用一个工具,适用于明确需要外部数据的场景。
  • 指定特定函数:可直接指定函数名,例如 tool_choice: {"type": "function", "function": {"name": "get_weather"}},引导模型使用某个函数。

多工具协同与并行调用

当提供多个工具时,模型会根据意图自动选择合适的工具。更强大的是,模型支持并行工具调用:如果用户一句话中需要多个独立信息,模型会一次性返回多个 tool_calls,你的代码可以并发执行这些函数,显著降低延迟。

例如,用户问:“比较一下北京和上海的天气”,模型可能同时调用 get_weather(“北京”)get_weather(“上海”)

# 并行执行示例
import asyncio
# ... 收到多个 tool_calls ...
tasks = []
for tool_call in message.tool_calls:
    if tool_call.function.name == "get_weather":
        args = json.loads(tool_call.function.arguments)
        tasks.append(async_get_weather(args["city"]))  # 异步调用
results = await asyncio.gather(*tasks)

实现稳定函数调用的高级技巧

定义清晰的合约

函数不只是给模型看的,也是给你的后端系统看的。保持函数名称、参数命名与后端 API 一致,减少转换层。在函数描述中明确提示模型,当信息不足时应该询问用户,而不是猜测参数。

处理错误与重试

函数执行可能失败(如外部 API 超时)。你应该捕获异常,将结构化的错误信息返给模型:

{"error": "查询超时,请稍后重试。"}

模型通常能理解这类错误,并生成恰当的道歉或重试提示。

使用检查点与验证

在应用层捕获模型返回的参数,做一次基本的类型和取值范围校验。如果发现明显错误,可以在追加 tool 消息前修正,或向模型返回一个友好错误,提示重新生成。

确保安全边界

绝对不要让模型直接执行 SQL 或写入文件系统。Function Calling 的角色只是生成调用意图,真正的执行必须由你的后端沙箱完成。对危险操作,额外添加用户确认步骤。

常见问题与排查

现象 可能原因 解决方法
模型不调用函数 函数描述与用户意图不匹配 优化函数描述,使其更通用;或临时设置 tool_choice: "auto" 同时提供更清晰的系统提示。
参数提取错误 参数描述模糊或格式不明确 增加示例,在 description 中给出典型的输入样例。
额外返回文本 模型在 tool_calls 之外提供了文本 注意检查 message.content,若同时有文本和工具调用,可忽略文本或要求模型只返回调用。
并行调用失败 多个工具依赖顺序关系 在提示词中说明步骤顺序,或使用多轮交互而非一次并行调用。

总结

Function Calling 让大语言模型从一个“对话生成器”升级为“智能调度中心”。它通过严格的结构化输出,使模型能够稳定、安全地接入现实世界的数据与功能。掌握 Function Calling 的核心是:设计清晰精准的函数签名,用健壮的代码处理执行,再将结果优雅地交还给模型进行自然语言合成。现在,你可以开始为你的应用构建专属的工具集,让模型真正“做点实事”了。