Function Calling 工具调用入门指南

让大模型查天气、下单、读数据库,靠的是 Function Calling 而不是更长的提示词。这篇讲清工具调用的完整循环:定义 tools、模型输出 tool_calls、本地执行、结果回传,附可直接运行的 Python 代码与常见坑。

很多「智能体」看起来很聪明,核心机制其实只有一环:Function Calling。模型本身不会执行任何操作,它只是学会了一件事——判断什么时候该调用你注册的函数,并按你定义的 JSON Schema 生成参数。真正的执行发生在你自己的代码里,执行结果再回传给模型继续推理。理解了这个循环,就能看清所有 Agent 框架的地基。

先说结论

  • Function Calling = 模型输出结构化参数 + 你的代码执行 + 结果回传,三步循环
  • 模型永远不会真的执行函数,安全边界始终在你手里
  • 参数由 JSON Schema 约束,但模型仍可能编造值,关键参数必须在执行前校验
  • 不是所有任务都需要工具调用:纯文本生成、结构化抽取用 JSON 模式更便宜
  • 换模型不需要改协议——OpenAI 兼容网关下 tools 参数的格式是统一的

和其他结构化输出的区别

能力适用场景执行方
Function Calling需要根据用户意图触发外部动作你的代码
JSON Mode / 结构化输出固定格式的信息抽取、表单填充不执行,直接拿 JSON
RAG 检索增强基于固定知识库回答检索流程自己写死

简单判断:如果「是否调用、调用哪个」需要模型临场判断,用 Function Calling;如果流程是固定的,直接写代码编排,模型只负责文本环节。Dify 等平台把工具编排做成了可视化配置,原理相同,见 Dify 接入 OpenAI 兼容模型配置。

完整循环:四段代码

以「查询天气」为例,用 Python 官方 SDK 演示。先定义函数的 Schema,注意 description 写得越清楚,模型选对函数和填对参数的概率越高:

from openai import OpenAI

client = OpenAI(
    api_key=os.environ["ZAIMOD_API_KEY"],
    base_url="https://api.zaimod.com/v1",
)

tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "查询指定城市当前天气,城市名必须用中文",
        "parameters": {
            "type": "object",
            "properties": {
                "city": {"type": "string", "description": "城市名,如:杭州"},
                "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
            },
            "required": ["city"],
        },
    },
}]

第一步,把 tools 注册进对话,模型会自己决定是否调用:

messages = [{"role": "user", "content": "杭州今天适合穿什么?"}]

resp = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=messages,
    tools=tools,
)

msg = resp.choices[0].message
if msg.tool_calls:
    print(msg.tool_calls[0].function.name)
    print(msg.tool_calls[0].function.arguments)  # {"city": "杭州"} 这样的字符串

第二步,在本地真正执行函数。参数是 JSON 字符串,先解析再校验:

import json

def get_weather(city: str, unit: str = "celsius") -> dict:
    # 这里换成真实的天气 API;示例返回固定数据
    return {"city": city, "temp": 18, "condition": "多云", "unit": unit}

tool_call = msg.tool_calls[0]
args = json.loads(tool_call.function.arguments)
result = get_weather(**args)

第三步,把执行结果按角色 tool 回传给模型,让它基于真实数据继续回答:

messages.append(msg)  # 必须把模型的 tool_calls 消息原样放回历史
messages.append({
    "role": "tool",
    "tool_call_id": tool_call.id,
    "content": json.dumps(result, ensure_ascii=False),
})

final = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=messages,
    tools=tools,
)
print(final.choices[0].message.content)
# 「今天杭州 18 度多云,建议穿薄外套……」

这四段就是全部。所谓 Agent,不过是把这个循环套上 while、加上多个工具、限制最大轮数。

实践中最容易踩的四个坑

工具描述偷懒。 description 是模型判断「该不该调用」的唯一依据。两个工具功能相近时,在描述里写清边界,例如「用于查实时天气,不适合查历史天气」,能显著降低误调率。

参数不做校验。 模型可能给出格式正确但内容错误的参数(不存在的城市 ID、负数金额)。执行前对关键参数做业务校验,校验失败可以把错误信息以 role: tool 回传,模型通常会自己修正参数重试。

tool_call_id 对不上。 回传工具结果时 tool_call_id 必须和模型发出的调用一一对应,漏传或串号会直接报 400。多工具并行调用(parallel_tool_calls)时尤其要逐个对应。

无限循环。 工具结果可能让模型再次调用工具。生产代码必须加最大轮数限制(比如 5 轮),超限后强制要求模型基于已有信息作答。实际应用(如微信机器人自动应答)里这是必须的兜底,见 微信 AI 支持机器人搭建教程。

需要选什么模型

主流模型都支持 tools 参数,但能力有差异:参数越复杂、工具越多,越依赖模型的指令遵循能力。简单场景(一两个工具、参数简单)用性价比模型即可;工具多、链路长的复杂 Agent 建议用旗舰模型,并实测工具调用成功率再定。模型单价以 价格速查 页为准。

常见问题

Q:模型该调用工具时却不调用?

通常是描述不够明确,或者系统提示词里没有引导。可以在 system message 里写明「涉及实时信息时必须调用工具」。另外部分模型对 tool_choice: "auto" 的理解不同,调试期可临时设为 required 强制触发,验证 Schema 没问题后再改回 auto。

Q:Function Calling 能用来做 RAG 吗?

可以但没必要。固定知识库的检索时机是确定的(每次都检索),直接代码编排更稳更省。工具调用适合检索时机不确定的场景,比如「用户问天气才检索、问代码才查仓库」。

Q:流式输出下怎么处理 tool_calls?

流式 chunk 里工具调用参数是分段拼接的,需要把 delta.tool_calls 按 index 聚合成完整对象再执行。协议层面 tools 字段与普通请求一致,背景可参考 OpenAI 兼容协议详解。

延伸阅读

#function calling 教程#大模型工具调用#openai tools 参数#llm agent 入门
免费创建 API Key

相关阅读

Claude API 和 OpenAI API 有什么区别

Claude 的 messages 和 OpenAI 的 chat/completions 看着像,实际有 6 处硬性差异:system 位置、max_tokens 必填、content 块数组、工具调用结构、多模态格式、流式事件名。这篇逐项对照并给出同一需求的两套写法,附迁移检查表。

大模型 API 429 报错:原因、重试与并发调优

429 不一定是「你请求太多了」。这篇拆开 RPM、TPM、并发数和免费档限流四种成因,给出带抖动的退避重试实现、并发池写法与降级链路,附监控指标清单。

流式输出 SSE 原理与调试:从 curl 到前端

大模型 API 的流式输出本质是一条 SSE 长连接。本文拆解 text/event-stream 报文格式、stream:true 的分片结构、curl 与前端 ReadableStream 的调试方法,以及代理缓冲、[DONE]、usage 丢失等高频坑位。