Function Calling 详解:让大模型从"只会说"到"能干活"
前言:大模型的"手"在哪里?
使用 GPT、文心一言等大模型时,常会遇到一类典型局限:
问它"今天北京天气怎么样",它回答得头头是道——但那是编的,它根本没查过天气。问它"帮我查一下订单 12345 的状态",它礼貌地告诉你"我无法访问你的订单系统"。
这不是模型不够聪明。大语言模型(LLM)本质上是一个文本生成器,它的知识来自训练数据的快照,没有权限访问互联网、数据库、或者任何外部系统。它能写出一篇完美的库存分析报告,却没法帮你扣减一条库存记录。
Function Calling(函数调用)就是来解决这个问题的——它给大模型装上了"手"。
2023 年 6 月,OpenAI 率先在 GPT-4 和 GPT-3.5-turbo 上推出了 Function Calling 功能。随后百度、阿里、Anthropic 等厂商纷纷跟进。到 2026 年的今天,Function Calling 已经成为构建 AI Agent(智能体)的基石技术,也是连接"大模型的语义理解能力"和"业务系统的执行能力"之间最核心的桥梁。
这篇文章会从原理到实战,把 Function Calling 讲透。代码示例覆盖 OpenAI 和百度千帆两个主流平台,直接能跑。
术语约定:下文统一采用 OpenAI 兼容协议的命名——用 tools 定义工具、用 tool_calls 承载模型返回的调用请求;“Function Calling” 与"工具调用"等价使用。
一、先纠正一个常见误区
很多人一听 Function Calling,第一反应是"大模型帮我调函数"——这个理解只对了一半。
LLM 本身并不执行任何代码。 它运行在云端的 GPU 集群上,没有你本地环境的权限,不能直接调你的数据库、不能发 HTTP 请求、不能操作文件系统。
Function Calling 的实际工作方式是这样的:
用一句话概括:模型负责决策,你的程序负责执行。 这种"意图在模型、执行在代码"的边界划分,是 AI 系统安全性的核心保障。
用户:"帮我查一下北京今天的天气"
↓
LLM 分析语义,发现自己没有实时天气数据
↓
LLM 返回:建议调用 get_weather,参数 {"city": "北京"}
↓
★ 你的代码实际执行 get_weather() 函数
↓
将执行结果回传给 LLM
↓
LLM 基于真实数据生成最终回答
二、完整工作流程
下面先把整个流程拆开看,再提炼几个绕不开的约束。
2.1 时序拆解
用「查天气」为例,一次标准函数调用的来回是这样的(ASCII 图,任意平台都能正常显示):
用户 应用程序 大模型(LLM) 外部函数
│ "北京天气?" │ │ │
│─────────────>│ 消息 + tools │ │
│ │───────────────>│ │
│ │ │ 分析意图 │
│ │ │ 决定调工具 │
│ │<── tool_calls ─│ (函数名+参数) │
│ │ 执行函数 │ │
│ │────────────────────────────────>│
│ │<───── "晴天 25°C" ──────────────│
│ │ 结果作为 tool 消息回传 │
│ │───────────────>│ │
│ │ │ 生成最终回复 │
│<─ "北京晴天25°C"─│<──────────│ │
2.2 核心关键约束
无论用哪个平台,下面六步都成立:
- 第一步:每次调用都要把 tools 定义带上传给模型,告诉它有哪些工具可用
- 第二步:模型自己判断要不要调、调哪个、传什么参数——你没法强制它
- 第三步:模型返回的不是最终答案,而是一个"函数调用请求"
- 第四步:你的代码解析这个请求,执行真正的函数
- 第五步:把执行结果以 tool 角色的消息追加到对话历史里,再次调用模型
- 第六步:模型拿到真实数据后,生成用户能看懂的最终回复
整个过程是一个多轮对话循环,函数调用的结果会作为上下文喂回给模型。
三、实战:OpenAI 平台
3.1 环境准备
pip install openai
3.2 完整示例:天气查询助手
下面以「天气查询」为例完整跑一遍:先定义工具 tools,再实现真实函数,最后发起对话、处理模型返回的调用请求。
from openai import OpenAI
import json
client = OpenAI(api_key="your-api-key")
# ———- 第一步:定义工具 ———-
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的当前天气信息",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,例如:北京、上海"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "温度单位,默认摄氏度"
}
},
"required": ["city"]
}
}
}
]
# ———- 第二步:实现真实函数 ———-
def get_weather(city: str, unit: str = "celsius") –> str:
"""实际项目中这里应该调用真实的天气 API"""
weather_data = {
"北京": {"celsius": "25°C 晴", "fahrenheit": "77°F 晴"},
"上海": {"celsius": "22°C 多云", "fahrenheit": "72°F 多云"},
"深圳": {"celsius": "30°C 雨", "fahrenheit": "86°F 雨"},
}
return weather_data.get(city, {}).get(unit, "暂无该城市天气数据")
# ———- 第三步:发起对话 ———-
messages = [
{"role": "user", "content": "北京和深圳今天天气怎么样?"}
]
# 第一次调用:模型决定是否需要调用函数
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools,
tool_choice="auto" # auto 表示模型自己决定是否调用
)
message = response.choices[0].message
messages.append(message)
# ———- 第四步:处理函数调用 ———-
if message.tool_calls:
for tool_call in message.tool_calls:
func_name = tool_call.function.name
func_args = json.loads(tool_call.function.arguments)
print(f"模型请求调用: {func_name}({func_args})")
# 执行真实函数
if func_name == "get_weather":
result = get_weather(**func_args)
else:
result = "未知函数"
# 把结果作为 tool 消息回传
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result
})
# ———- 第五步:第二次调用,模型生成最终回复 ———-
final_response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools
)
print(final_response.choices[0].message.content)
else:
# 模型直接回答了,不需要调用函数
print(message.content)
运行结果:
模型请求调用: get_weather({'city': '北京', 'unit': 'celsius'})
模型请求调用: get_weather({'city': '深圳', 'unit': 'celsius'})
北京今天晴天,气温25°C;深圳今天有雨,气温30°C。出门记得带伞!
注意看:用户一次问了两个城市的天气,模型并行返回了两个 tool_call。你的代码需要遍历处理每一个调用,然后把所有结果一起回传给模型。
四、实战:百度千帆平台
百度千帆在 2026 年已提供 OpenAI 兼容的 /v2 端点,函数调用的入参、返回格式与 OpenAI 完全一致——你甚至不需要装 qianfan SDK,只要用 openai 库、把 base_url 指向千帆即可。目前该端点支持 ERNIE 系列、DeepSeek 系列、Qwen 系列等多个模型。
注:千帆旧版 qianfan.ChatCompletion + function_call 方案属于历史遗留接口,新业务请统一使用下面的 OpenAI 兼容 /v2 接口,避免被老教程误导。
4.1 环境准备
pip install openai
千帆的 API Key 在百度智能云千帆控制台获取,配置到环境变量 QIANFAN_API_KEY 即可(不再需要旧的 AK/SK 配对)。
4.2 完整示例:数据库文件查询
与第三节结构完全一致——只是把 client 换成千帆的 OpenAI 兼容端点,模型换成千帆支持的其中之一。
import os
import json
from openai import OpenAI
# 千帆提供 OpenAI 兼容的 /v2 端点,只需换 base_url 和 api_key
client = OpenAI(
api_key=os.environ["QIANFAN_API_KEY"],
base_url="https://qianfan.baidubce.com/v2"
)
# ———- 定义真实函数 ———-
def get_file_num(language: str) –> str:
"""获取数据库中指定语言的代码文件数量"""
language_map = {
"python": 35,
"java": 10,
"javascript": 25,
"c/c++": 35,
"go": 32,
}
return str(language_map.get(language.lower(), 0))
# ———- 定义工具描述(与 OpenAI 格式完全一致)———-
tools = [
{
"type": "function",
"function": {
"name": "get_file_num",
"description": "获取内部数据库中以某一编程语言编写的文件数量",
"parameters": {
"type": "object",
"properties": {
"language": {
"type": "string",
"description": "代码所运用的编程语言,例如:python、c/c++、go、java"
}
},
"required": ["language"]
}
}
}
]
# ———- 发起对话 ———-
messages = [
{"role": "user", "content": "请帮我查询一下数据库中用 Python 撰写的代码文件数量"}
]
# 第一次调用:模型决定是否调用工具(与 OpenAI 完全一致)
response = client.chat.completions.create(
model="deepseek-v3", # 千帆支持的模型之一,可按需替换为 ERNIE / Qwen 等
messages=messages,
tools=tools,
tool_choice="auto"
)
message = response.choices[0].message
messages.append(message)
# ———- 处理函数调用 ———-
if message.tool_calls:
for tool_call in message.tool_calls:
func_name = tool_call.function.name
func_args = json.loads(tool_call.function.arguments)
print(f"模型请求调用: {func_name}({func_args})")
if func_name == "get_file_num":
result = get_file_num(**func_args)
else:
result = "未知函数"
# 把结果作为 tool 消息回传(与 OpenAI 完全一致)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": str(result)
})
# 第二次调用,模型生成最终回复
final_response = client.chat.completions.create(
model="deepseek-v3",
messages=messages,
tools=tools
)
print(final_response.choices[0].message.content)
else:
print(message.content)
运行结果:
模型请求调用: get_file_num({'language': 'Python'})
数据库中使用 Python 编写的代码文件共有 35 个。
可以看到,换成千帆之后代码几乎和第三节的 OpenAI 示例一模一样——因为千帆的 /v2 端点直接兼容 OpenAI 的 tools / tool_calls 协议。千帆早期 SDK 曾用 function_call 字段(旧版格式),现在官方已统一到 tool_calls 标准。 核心逻辑始终不变:模型只产出调用请求,真实执行在你的代码里。
五、tool_choice 参数详解
tool_choice 控制模型调用工具的行为,四个选项:
| "auto" | 模型自己决定调不调 | 默认值,大多数场景用这个 |
| "none" | 禁止调用任何工具 | 想让模型纯对话回答时用 |
| "required" | 必须调用至少一个工具 | 强制走工具流程时用 |
| {"type": "function", "function": {"name": "xxx"}} | 强制调用指定函数 | 已知必须调某个函数时用 |
# 强制模型必须调用 get_weather 函数
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools,
tool_choice={"type": "function", "function": {"name": "get_weather"}}
)
实际开发中,"auto" 基本够用。只有在特定业务流程里需要强制走工具时,才用 "required" 或指定函数。
六、进阶:多工具并行调用
实际项目中,用户一句话可能需要调多个函数。比如"帮我查一下北京天气,再查一下我的订单状态"——模型应该并行返回两个 tool_call。下面给出完整可运行示例:
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取城市天气",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名"}
},
"required": ["city"]
}
}
},
{
"type": "function",
"function": {
"name": "get_order_status",
"description": "查询订单状态",
"parameters": {
"type": "object",
"properties": {
"order_id": {"type": "string", "description": "订单编号"}
},
"required": ["order_id"]
}
}
}
]
messages = [
{"role": "user", "content": "帮我查一下北京天气,还有订单 12345 的状态"}
]
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools,
tool_choice="auto"
)
# 模型会返回两个 tool_call,遍历处理
message = response.choices[0].message
for tool_call in message.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)
elif func_name == "get_order_status":
result = get_order_status(**func_args)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": str(result)
})
# 把所有结果一起回传,模型生成最终回复
final_response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools
)
处理多工具调用的关键是:遍历所有 tool_call,分别执行,然后把所有结果一起回传给模型。不要只处理第一个就停。
七、踩坑与最佳实践
落地多个 Function Calling 业务后,整理出高频踩坑点与标准化实践:
1. description 写清楚,模型才能选对
工具的 description 字段不是写给人看的注释,是写给模型看的"使用说明书"。描述越清晰,模型选对工具的概率越高。
# 差:太模糊
"description": "查询天气"
# 好:说清楚什么场景该用
"description": "获取指定城市的当前天气信息。当用户询问某地天气、温度、是否下雨时使用此工具。"
2. 参数描述也要具体
# 差
"city": {"type": "string", "description": "城市"}
# 好
"city": {"type": "string", "description": "城市名称,如'北京'、'上海',不要用缩写"}
模型是根据这些描述来提取参数的,描述含糊会导致参数提取出错。
3. 函数执行结果要转成字符串
tool 角色消息的 content 必须是字符串。如果你的函数返回的是字典或数字,记得 json.dumps() 或 str() 转一下:
result = get_weather(city="北京") # 返回 dict
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result, ensure_ascii=False) # 转 JSON 字符串
})
4. 处理模型不调函数的情况
模型有可能判断不需要调函数,直接回复了。你的代码要处理这种情况:
if message.tool_calls:
# 有函数调用
...
else:
# 没有函数调用,直接用模型的回复
print(message.content)
5. Token 消耗会增大
把 tools 定义传给模型会增加 prompt token 消耗。百度千帆的文档提到,内置 prompt 模板拼接会导致约 300 token 的膨胀。工具定义越多,消耗越大。别一股脑塞几十个工具进去,按场景分组、按需加载。
6. 安全边界:别把危险函数暴露给模型
Function Calling 的安全性靠的是"模型只做决策不执行"这个边界。但如果你把删库、转账等高危函数也定义成工具,模型一旦判断失误,你的代码就会真的执行。面向生产环境,建议至少落实以下四条:
- 入参校验与白名单:函数执行前对模型传入的参数做类型、范围、白名单过滤,防止 SQL 注入、路径遍历(../../etc/passwd)等恶意参数直接打到后端。
- 高危操作禁止自动执行:写/改/删数据、资金转账、权限变更这类接口,不要封装成"模型可自动调用"的工具,必须保留人工二次确认(或独立的审批流)才能落地。
- 全链路调用日志落库:记录用户 prompt、模型产生的 tool_calls 参数、函数返回结果,便于出问题时的审计与溯源。
- 限制单轮最大调用次数:给 Agent 循环设上限(如单轮最多 10 次工具调用),防止模型陷入无限循环把接口流量和 token 费用打爆。
7. 生产健壮性:异常处理、循环上限、并发控制
示例里的代码为了聚焦主流程,省略了几处生产环境绕不开的健壮性处理,上线前务必补上:
- 捕获函数调用异常:网络抖动、接口超时、参数缺失都会导致执行失败,要用 try/except 包住工具执行,失败时把错误信息回传给模型,而不是直接让程序崩溃。
- 给 Agent 循环设最大轮次:多步推理场景如果不限制循环次数,模型可能反复调用工具,产生高额 token 费用甚至死循环。务必用计数器或超时掐断。
- 并行调用做好限流:模型一次可能返回多个 tool_call,真实业务可以异步并发执行来加速,但要加并发上限和熔断,避免瞬间打爆下游接口。
八、从 Function Calling 到 Agent
理解了 Function Calling,你就理解了 AI Agent 的核心。
Agent 不是什么神秘的东西,它本质上就是一个循环(ASCII 图,任意平台都能正常显示):
┌──────────────────────────────────────┐
│ ↓
用户输入 → LLM 思考 → 需要调工具? ─否─→ 输出最终回复 → 返回用户
↑ │是
│ ↓
│ 返回 tool_calls
│ ↓
│ 执行函数
│ ↓
└────── 结果回传 LLM ←──┘
- LLM 是大脑,负责理解意图、规划步骤、选择工具
- Function Calling 是手脚,负责执行具体操作
- 循环(Agentic Loop)让模型可以多步推理、连续调用工具,直到完成任务
LangChain、AutoGPT、各种 Agent 框架,底层都是在这个循环上做封装。理解了 Function Calling 的原始 API,再用这些框架时就知道它们帮你省了什么、在哪里可能出问题。
总结
| 核心本质 | 模型输出结构化 JSON,程序执行真实函数 |
| 关键边界 | 模型只决策不执行,安全性靠代码层保障 |
| 标准流程 | 定义工具 → 用户输入 → 模型判断 → 返回调用请求 → 执行函数 → 结果回传 → 生成回复 |
| 主流平台 | OpenAI / 百度千帆(均遵循 OpenAI tool_calls 标准)、Anthropic(tool_use) |
| 进阶能力 | 多工具并行、tool_choice 控制、多轮循环 |
| 最佳实践 | description 写清楚、参数描述具体、结果转字符串、处理无调用情况、控制 token、守安全边界、补生产健壮性 |
Function Calling 不复杂,但它重要。它是大模型从"聊天工具"走向"生产力工具"的关键一步。掌握了它,你就能让 AI 真正替你干活——查数据、调接口、发通知、做决策。
写这篇的时候我尽量把原理和代码都讲到位了,有不清楚的地方欢迎评论区交流。
参考文档
- OpenAI Function Calling 文档:https://developers.openai.com/docs/function-calling
- 百度千帆 v2 函数调用文档:https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Alm6k3z0t
本文代码基于 2026 年 8 月的 API 版本,实际使用时请以官方最新文档为准。 传 → 生成回复 | | 主流平台 | OpenAI / 百度千帆(均遵循 OpenAI tool_calls 标准)、Anthropic(tool_use) | | 进阶能力 | 多工具并行、tool_choice 控制、多轮循环 | | 最佳实践 | description 写清楚、参数描述具体、结果转字符串、处理无调用情况、控制 token、守安全边界、补生产健壮性 |
Function Calling 不复杂,但它重要。它是大模型从"聊天工具"走向"生产力工具"的关键一步。掌握了它,你就能让 AI 真正替你干活——查数据、调接口、发通知、做决策。
写这篇的时候我尽量把原理和代码都讲到位了,有不清楚的地方欢迎评论区交流。
参考文档
- OpenAI Function Calling 文档:https://developers.openai.com/docs/function-calling
- 百度千帆 v2 函数调用文档:https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Alm6k3z0t
本文代码基于 2026 年 8 月的 API 版本,实际使用时请以官方最新文档为准。

![[特殊字符]DeepSeek‑Harness(DSH)小白保姆教程-171主机测评](https://www.171host.com/wp-content/uploads/2026/08/20260816085112-6a817a009aabf-220x150.png)