Function Calling 工具调用实战指南

大模型刚会「说话」时,最大的痛点是只能聊、不能干。Function Calling(函数调用 / 工具调用)正是把模型从「聊天框」拉进「业务系统」的关键一步:你告诉模型「你有哪些函数可用」,模型在合适的时候返回结构化调用请求,你的代码执行后把结果喂回去,模型再总结成自然语言。Function Calling 让 AI 真正具备操作数据库、调用内部 API、查实时数据的能力。本文用 Python 走通一条可落地的工具调用闭环。

一、Function Calling 到底解决了什么

没有工具调用时,模型给出的「北京 25 度」是它训练记忆里的近似值,不是实时数据,也不能触发任何动作。引入 Function Calling 后,模型负责决策(要不要调、调哪个、传什么参数),你的程序负责执行(真正去查、去写、去算)。这种「模型管脑子、代码管手脚」的分工,是今天几乎所有 AI 应用和智能体的底座。无论是查订单、发邮件还是跑 SQL,本质都是同一套 tool_calls 机制。

举个真实场景:客服机器人被问「我上周下的那笔订单到哪了」。没有工具调用,它只能泛泛安慰;接入 Function Calling 后,它可以先调 query_order 拿到物流单号,再调 track_express 查实时轨迹,最后把「已到杭州转运中心,预计明天送达」这样的确定信息回给用户。区别不在于模型变聪明,而在于它第一次真正连上了你的业务系统。这也是为什么工具调用常被称为「AI 落地的最后一公里」。

二、工作机制:一次调用发生了什么

整个流程分三步:① 你在请求里附带 tools 数组,描述每个函数的名字、用途和参数 JSON Schema;② 模型判断用户意图需要工具时,不再直接吐文本,而是返回 tool_calls,里面是函数名和已校验过的参数;③ 你本地执行函数,把结果以 role: "tool" 的消息回传,模型据此生成最终答复。关键点在于:参数由模型按 Schema 生成,但执行权永远在你手里

2.1 请求:把函数声明交给模型

import openai  # 任意兼容 OpenAI 协议的 SDK 均可

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "查询指定城市的实时天气",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {"type": "string", "description": "城市名,如 北京"},
                    "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
                },
                "required": ["city"]
            }
        }
    }
]

resp = client.chat.completions.create(
    model="your-model",
    messages=[{"role": "user", "content": "北京今天天气怎么样?"}],
    tools=tools,
    tool_choice="auto"
)
print(resp.choices[0].message.tool_calls)

注意 parameters 必须是标准 JSON Schema,required 越严格,模型漏填参数的概率越低。tool_choice="auto" 表示让模型自己决定是否调用;如果你的场景「一定得调某个函数」,可改成 {"type":"function","function":{"name":"get_weather"}} 强制指定。

2.2 响应:模型返回 tool_calls

当模型决定调用时,message.tool_calls 不再是 None,而是一个列表,每个元素含 idfunction.namefunction.arguments(JSON 字符串)。这一步模型只「开单」,不执行——真正查天气的代码在你这边。

2.3 并行工具调用:一次返回多个 tool_calls

现代模型支持在一次响应里返回多个 tool_calls。例如用户问「北京和上海天气怎么样」,模型可能同时开出 get_weather(北京)get_weather(上海) 两个调用。你的代码应当并发执行它们(线程池或 asyncio),再按顺序把每条结果以对应的 tool_call_id 回传。注意:并行调用能显著降低延迟,但也要求你给每个工具都做好隔离,避免一个慢调用拖垮整批。

三、完整闭环:把结果喂回模型

import json

msg = resp.choices[0].message
if msg.tool_calls:
    for call in msg.tool_calls:
        args = json.loads(call.function.arguments)
        result = get_weather(args["city"], args.get("unit", "celsius"))
        messages = [
            {"role": "user", "content": "北京今天天气怎么样?"},
            msg,  # 模型原始消息(含 tool_calls)
            {"role": "tool", "name": call.function.name,
             "tool_call_id": call.id, "content": json.dumps(result, ensure_ascii=False)}
        ]
        follow = client.chat.completions.create(
            model="your-model", messages=messages, tools=tools
        )
        print(follow.choices[0].message.content)

这是 Function Calling 最容易写错的地方:回传时必须带上原始 msg(含 tool_calls)和对应的 tool_call_id,否则模型无法把「结果」对上「那次调用」。多工具时按 tool_calls 列表逐个 dispatch 即可。想要流式体验,可参考 LLM 流式输出实战:SSE 推流与前端渲染 把 tool_calls 也做成增量推送。

生产环境里,工具执行必须包一层容错:给每个调用加超时,捕获异常后把错误信息(而不是异常栈)作为 tool 消息回传,让模型自己换参数重试或改用别的工具。例如查询超时,回传 {"error":"timeout"},模型可能会把城市名写全或改用缓存接口。这正是「模型管决策、代码管执行」的优势——出错时由模型参与纠错,而不是链路直接崩。

四、三个工程化要点

  • 白名单校验:模型可能「幻觉」出一个你没注册的函数名,执行前必须先校验,再反射调用。
  • 参数二次校验:即便有 JSON Schema,也要对类型、范围、枚举做防御性检查,别直接信任 arguments
  • 超时与降级:工具可能慢或挂,给每个调用加超时,失败时回传错误信息让模型自我纠正,而不是整个链路崩掉。
ALLOWED = {"get_weather", "search_doc"}

def dispatch(name, args):
    if name not in ALLOWED:          # 永远先校验白名单
        raise PermissionError(f"未注册的工具: {name}")
    return TOOL_REGISTRY[name](**args)

五、三种「让模型用工具」的方式怎么选

方式适用场景上手成本本文关联
原生 Function Calling单模型调用自有 API本文主角
MCP 协议跨应用 / 多工具标准化接入见《MCP 模型上下文协议实战
Agent 框架多步编排 / 多智能体协作见《多智能体协作实战

实践中三者不互斥:用原生 Function Calling 写最小闭环,需要标准化接别人工具时上 MCP,要编排多个思考体再上 Agent 框架。想快速把模型能力跑成可视化应用,可看 Dify + Ollama 私有化部署实战

六、避坑清单

  • 忘带 tool_call_id:回传消息缺 ID,模型报 「tool_call_id not found」。
  • 把工具结果当普通 user 消息发:必须用 role:"tool",否则模型不知道这是上一次调用的返回值。
  • 循环不收敛:模型反复调同一个工具,用最大轮次上限(如 5 轮)强制退出。
  • 把密钥塞进函数描述:description 会原样进 prompt,敏感信息放代码里。

七、小结

Function Calling 是 AI 应用从「能说」到「能干」的分水岭:你定义函数、模型生成调用、你的代码负责执行、结果再回流给模型。掌握这条 tool_calls 闭环,你就拥有了把任意业务系统接进大模型的通用钥匙。下一步可以顺着 MCP 把工具标准化,或顺着 Agent 框架做多步编排。

回头看,整套模式只有四个不变的部件:tools 声明、模型返回的 tool_calls、你侧的执行、以及回传的 role:tool 消息。无论业务多复杂,都是在它们之上加轮次、加并发、加校验。当你能把一个天气函数跑通,也就具备了把订单、库存、CRM 全接进大模型的能力——区别只是函数体里那几行业务逻辑。

上一篇 技术雷达实战:团队如何建立技术趋势判断机制
下一篇 eza + zoxide + bat:用现代命令行工具替换 ls/cd/cat