让大模型真正“动手”:用 Function Calling 接入外部 API 的完整实战(附可运行代码)

大模型只会“说话”在真实业务里是不够的。用户问“北京今天适合跑步吗”,模型既不知道实时天气,也不懂你库存里还剩几台机器。2026 年落地的 AI 应用,几乎都有一个共同点:让模型在需要时主动去调你的代码、查你的数据库、打你的接口。这条能力的入口,就是 Function Calling(函数调用)。本文用 OpenAI 兼容接口,从定义工具到自动调度外部 API,给你一份能直接跑通的实战代码,不堆概念,只看怎么落地。

大模型通过 Function Calling 调度外部工具的闭环示意图

一、为什么模型自己答不了,非得“调工具”

模型的知识有截止日期,而且它进不了你的私有系统。天气、订单、物流、内部知识库——这些信息既实时又在墙内。Function Calling 的本质,是让模型根据对话自己判断:这件事我该不该调用某个外部函数,以及该传什么参数。模型不负责执行函数,它只负责“决定调谁、传什么”,真正的执行和结果回填在你这边的代码里。这样既守住了安全边界,又把模型从“纯文本生成器”变成了“业务调度入口”。

二、第一步:把工具定义成一份 Schema

模型看不懂你的 Python 函数,它只认 JSON Schema。你要告诉它:有哪些函数可用、每个函数干什么、需要哪些参数。下面用“查天气”举例,把一份工具描述写出来:

tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "查询指定城市当前天气,返回温度、天气状况和风力",
        "parameters": {
            "type": "object",
            "properties": {
                "city": {
                    "type": "string",
                    "description": "城市名,例如 北京、上海"
                }
            },
            "required": ["city"]
        }
    }
}]

关键点:description 写得越清楚,模型选对工具、填对参数的概率越高。模糊的描述只会换来瞎猜的参数。

三、第二步:让模型决定“调哪个、传什么”

把工具列表随消息一起发给模型,模型会在回复里给出 tool_calls——也就是它想调用的函数名和参数。这一步不需要你写任何判断逻辑:

from openai import OpenAI

# 接任意 OpenAI 兼容服务:vLLM / Ollama / 云端都行
client = OpenAI(base_url="http://localhost:8000/v1", api_key="EMPTY")

messages = [{"role": "user", "content": "北京今天适合出门跑步吗?"}]
resp = client.chat.completions.create(
    model="qwen2.5-7b",
    messages=messages,
    tools=tools,
    tool_choice="auto"        # auto = 让模型自己决定要不要调
)
msg = resp.choices[0].message

tool_choice="auto" 表示“能调就调、不能调就直接回答”。如果你强制必须调某个函数,可以改成 {"type": "function", "function": {"name": "get_weather"}}

四、第三步:本地执行并把结果“喂回去”

模型只给出了调用意图,真正执行在你的代码里。执行完,要把结果以 role: "tool" 的消息回填,模型才能基于真实数据作答:

import json

def get_weather(city: str) -> str:
    # 这里可换成真实天气 API;示例返回模拟数据,保证离线可跑
    return json.dumps({"city": city, "temp": 24, "cond": "晴", "wind": "3级"})

if msg.tool_calls:
    for call in msg.tool_calls:
        args = json.loads(call.function.arguments)
        result = get_weather(**args)
        messages.append(msg)   # 先回写模型的调用意图
        messages.append({       # 再回填执行结果
            "role": "tool",
            "tool_call_id": call.id,
            "content": result
        })

# 模型基于真实天气数据生成最终回答
final = client.chat.completions.create(model="qwen2.5-7b", messages=messages)
print(final.choices[0].message.content)

这就是一个最小可跑闭环:用户提问 → 模型决定调 get_weather("北京") → 你的代码执行 → 结果回填 → 模型结合数据回答“24 度、晴、3 级风,适合跑步”。把 get_weather 换成查订单、查库存、发短信,逻辑一模一样。

五、工程落地必须踩平的几个坑

真上生产,裸奔代码会出问题。结合我们线上踩过的雷,给你几条硬经验:

  • 参数先校验再执行:模型偶尔会漏填 required 字段或类型错,执行前用 pydantic 或手写校验兜底,别直接 **args 进业务函数。
  • 给外部调用加超时:天气、库存接口可能挂,务必 requeststimeout,否则一次慢响应拖垮整轮对话。
  • 结果要可序列化:回填给模型的 content 必须是字符串(JSON 字符串即可),别直接塞 Python 对象,否则第二轮请求直接报错。
  • 权限与副作用:凡是会“改数据”的工具(下单、删记录),要么加二次确认,要么只允许只读,别让模型一句话把库改了。

Function Calling 调度时序图(含参数校验、超时、错误处理)

总结

Function Calling 不是花活,它是 Agent 的基石:多工具并行调用、失败重试、把结果拼回上下文,全部建立在这套“模型决策 + 代码执行”的闭环上。今天的代码已经能跑通单工具调用,下一步你可以加并行调用、调用失败自动重试、以及把多个工具编排成一条流水线。先把这个最小闭环跑顺,再谈更复杂的智能体,才不会一上来就被各种边界情况淹没。

更多推荐