Function Calling 工具调用:原理、定义与并行优化
摘要: 系统阐述了大语言模型 Function Calling 技术的完整工作流程,从 LLM 作为决策者输出结构化调用指令、代码作为执行者的核心原理出发,深入分析了工具定义 JSON Schema 的设计要点与描述质量对调用准确率的影响。对比了单次调用、多轮调用与并行调用的适用场景,并给出并行调用的判断逻辑与性能优势。最后,通过对比 Function Calling 与 Structured Output 的适用边界,为构建具备工具使用能力的 Agent 系统提供技术基础。
1. 原理:模型输出结构化调用指令
核心思想
让 LLM 不再是"只会说话",而是能调用外部函数和 API,真正地"做事"。
1.1 问题起源:LLM 的"手脚束缚"
大语言模型本质上是"纯文本引擎"——你输入文字,它输出文字。它没有能力:
- 查询实时数据库
- 发送邮件或 Slack 消息
- 调用计算器做精确数学运算
- 获取今天的天气或股价
- 操作文件系统
Function Calling 就是给 LLM 装上"手和脚"。当用户的问题需要外部操作时,LLM 不直接回答,而是输出一个结构化的"调用指令",由外部程序执行后再把结果还给 LLM。
1.2 工作流程:一个完整的调用周期
Function Calling 完整流程示意如下:
① 用户输入 + 工具定义 ② LLM 决策:调用哪个函数?
┌──────────────────┐ ┌──────────────────────┐
│ User: "北京今天 │ │ Model 内部推理: │
│ 天气怎么样?" │──────────>│ "用户想知道北京天气" │
│ │ │ "我有 get_weather │
│ Tools: [ │ │ 这个工具..." │
│ get_weather, │ │ │
│ get_stock, │ │ → 输出 JSON: │
│ send_email │ │ { │
│ ] │ │ "name": "get_weather│
└──────────────────┘ │ "arguments": { │
│ "city": "北京" │
┌───────────────────────│ } │
│ └──────────┬───────────┘
│ ⑤ LLM 综合生成最终回答 │
│ ┌──────────────────────┐ │ ③ 你的代码执行函数
│ │ Model: "北京今天晴, │ │
│ │ 温度 25°C,适合 │ ▼
│ │ 户外活动。" │ ┌──────────────────────┐
│ └──────────────────────┘ │ 调用真实天气 API: │
│ ▲ │ │
│ │ │ get_weather("北京") │
│ │ │ → "晴, 25°C, 湿度60%" │
│ │ └──────────┬───────────┘
│ │ │
│ └───────────────────────────┘
│ ④ 函数结果返回给 LLM
│关键要点:LLM 并不真正执行函数,它只输出"我想调用这个函数,参数是这些"。你的代码负责实际执行,然后把结果传回给 LLM。
1.3 类比理解:LLM 是"决策者",代码是"执行者"
类比:餐厅点餐系统
LLM = 服务员 用户代码 = 厨房
────────────── ─────────────────
听到顾客说"我要牛排" 收到"牛排 x1"
写在小票上 实际煎牛排
不可能真的去做牛排 端出牛排给服务员
但知道牛排要几分熟 服务员再告诉顾客"您的牛排好了"
LLM 知道要调用什么函数、传什么参数,但它不(也不能)执行函数。2. Tool Definition(工具定义 Schema)
2.1 函数定义的结构
告诉 LLM 你有哪些工具可用,你需要以 JSON Schema 的方式描述每个函数:
工具定义的三个要素
1. 函数名 (name) — LLM 用来识别调用哪个函数
2. 函数描述 (description) — LLM 用来判断何时调用这个函数
3. 参数定义 (parameters) — LLM 用来填充正确的参数值
┌─────────────────────────────────────────────────────────┐
│ { │
│ "name": "get_weather", │
│ "description": "获取指定城市的实时天气信息, │
│ 包括温度、湿度、天气状况", │
│ "parameters": { │
│ "type": "object", │
│ "properties": { │
│ "city": { │
│ "type": "string", │
│ "description": "城市名称,使用中文,如'北京'、 │
│ '上海'" │
│ }, │
│ "unit": { │
│ "type": "string", │
│ "enum": ["celsius", "fahrenheit"], │
│ "description": "温度单位" │
│ } │
│ }, │
│ "required": ["city"] │
│ } │
│ } │
└─────────────────────────────────────────────────────────┘2.2 描述质量直接影响调用准确率
工具的 description 是 LLM 判断"什么时候用这个工具"的唯一依据。写得不好,LLM 就会用错工具或者不用。
差的描述 vs 好的描述
❌ 差:
{
"name": "search_docs",
"description": "搜索文档",
"parameters": {
"query": {"type": "string", "description": "搜索词"}
}
}
→ LLM 不知所措:什么时候用?搜索什么文档?
✅ 好:
{
"name": "search_internal_docs",
"description": "在公司内部知识库中搜索技术文档和操作手册。
当你需要查找产品规格、API文档、运维流程时使用此工具。
注意:此工具仅搜索技术文档,不包含HR政策或财务信息。",
"parameters": {
"query": {
"type": "string",
"description": "搜索关键词或自然语言查询。中文或英文。建议使用完整的句子而不是单词。"
},
"category": {
"type": "string",
"enum": ["技术文档", "运维手册", "API参考", "产品规格"],
"description": "文档分类过滤,默认搜索所有分类"
}
}
}
→ LLM 清楚知道什么场景用、参数怎么填2.3 工具定义最佳实践
| 最佳实践 | 说明 | 示例 |
|---|---|---|
| 描述要具体 | 说明工具的用途、适用范围、限制 | "搜索技术文档(不含HR和财务)" |
| 参数要有约束 | 使用 enum、type 约束参数 | "enum": ["asc", "desc"] |
| 给出参数示例 | 在 description 中附示例值 | "如'2024-01-15'" |
| 标记必选/可选 | 使用 required 字段 | "required": ["query"] |
| 控制工具数量 | 提供太多工具会让 LLM 混淆 | 一次提供 5~20 个工具为佳 |
| 命名清晰 | 函数名应自解释 | get_weather 而不是 f1 |
| 区分相似工具 | 在描述中明确区分相似工具的职责 | "用 search_code 搜索代码,用 search_docs 搜索文档" |
2.4 完整示例:一个智能助手的工具集
// 为一个智能客服定义的完整工具集
[
{
"name": "search_knowledge_base",
"description": "在知识库中搜索信息。用于回答产品使用、技术支持和常见问题。不包含用户个人数据。",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "搜索查询,尽量使用完整句子"},
"top_k": {"type": "integer", "description": "返回结果数量,默认5", "default": 5}
},
"required": ["query"]
}
},
{
"name": "get_order_status",
"description": "查询用户的订单状态。需要提供订单号。只能查询当前登录用户自己的订单。",
"parameters": {
"type": "object",
"properties": {
"order_id": {"type": "string", "description": "订单号,格式为 ORD-开始,如 ORD-20240001"}
},
"required": ["order_id"]
}
},
{
"name": "create_support_ticket",
"description": "当用户问题无法自动解决时,创建人工客服工单。需要确认用户同意后再调用。",
"parameters": {
"type": "object",
"properties": {
"title": {"type": "string", "description": "工单标题,简要概括问题"},
"description": {"type": "string", "description": "问题详细描述"},
"priority": {
"type": "string",
"enum": ["low", "medium", "high", "urgent"],
"description": "紧急程度。系统故障使用urgent,一般问题使用medium"
}
},
"required": ["title", "description", "priority"]
}
},
{
"name": "calculate",
"description": "执行数学计算。当需要精确的算术运算时使用此工具,而不是让模型心算。支持加减乘除和基本函数。",
"parameters": {
"type": "object",
"properties": {
"expression": {"type": "string", "description": "数学表达式,如 '2+3*4' 或 'sqrt(16)'"}
},
"required": ["expression"]
}
}
]3. 单次调用 vs 多轮调用
3.1 单次调用(Single Tool Call)
LLM 判断只需要调用一个工具就能回答问题。单次调用流程如下:
[用户] "帮我查一下 ORD-20240001 这个订单的状态"
│
[LLM] 分析:这是查订单 → 用 get_order_status
输出:ToolCall(name="get_order_status", args={order_id: "ORD-20240001"})
│
[代码] 执行 get_order_status("ORD-20240001")
返回:{"status": "已发货", "tracking": "SF123456789", "eta": "2024-03-15"}
│
[LLM] 综合结果生成回答:
"您的订单 ORD-20240001 已发货,快递单号 SF123456789,预计 3月15日送达。"3.2 多轮调用(Multi-turn Tool Calling)
复杂问题需要多次调用——可能需要调用不同的工具,也可能同一个工具调用多次。 多轮调用流程如下: [用户] "北京和上海今天哪个城市更暖和?"
轮次 1:
[LLM] 需要两个城市的天气数据
输出:ToolCall(name="get_weather", args={city: "北京"})
│
[代码] 返回:{"temp": 25, "condition": "晴"}
│
▼
轮次 2:
[LLM] ToolCall(name="get_weather", args={city: "上海"})
│
[代码] 返回:{"temp": 22, "condition": "小雨"}
│
▼
轮次 3:
[LLM] 北京25°C 晴,上海22°C 小雨
→ "北京今天更暖和,25°C且天气晴朗,比上海高3°C。"注意:多轮调用的每一轮都需要一次完整的 LLM API 请求,这意味着更多的 token 消耗和更高的延迟。这也是后面要讲 "并行调用" 的原因。
3.3 何时用单次、何时用多轮?
决策树:单次 vs 多轮 vs 并行
用户的问题
│
▼
┌─────────────────────┐
│ 是否需要调用工具? │
└──────┬──────────────┘
│
┌────┴────┐
否 是
│ │
▼ ▼
直接回答 ┌─────────────────────┐
│ 需要几个工具调用? │
└──────┬──────────────┘
│
┌───────┼───────────┐
1个 多个独立 多个有依赖
│ │ │
▼ ▼ ▼
单次调用 并行调用 多轮调用4. 并行 Tool Calling
4.1 什么是并行调用?
当 LLM 判断多个工具调用之间互不依赖时,可以在一次响应中同时发起多个调用。
并行调用 vs 串行调用(以查天气为例)
串行调用(慢): 并行调用(快):
───────────── ──────────────
Tool: get_weather("北京") Tool: get_weather("北京") ─┐
│ 200ms │ │
Tool: get_weather("上海") Tool: get_weather("上海") ─┤ 同时发起
│ 200ms │ │
Tool: get_weather("广州") Tool: get_weather("广州") ─┘
│ 200ms │
▼ ▼
总耗时: ~600ms 总耗时: ~200ms (最慢的那个)
节省: 67% 时间4.2 并行调用的工作方式
并行调用完整流程示意如下:
[用户] "对比北京、上海、广州今天的空气质量"
[LLM] 分析:三个城市互相独立,可以并行查询
输出:
[
ToolCall(name="get_air_quality", args={city: "北京"}),
ToolCall(name="get_air_quality", args={city: "上海"}),
ToolCall(name="get_air_quality", args={city: "广州"})
]
│
▼
[代码] 并发执行三个函数调用
┌─────────────────┐
│ Promise.all([ │
│ getAqi("北京"),│──> {"aqi": 85, "level": "良"}
│ getAqi("上海"),│──> {"aqi": 120, "level": "轻度污染"}
│ getAqi("广州") │──> {"aqi": 55, "level": "优"}
│ ]) │
└─────────────────┘
│
▼
[LLM] 综合三个结果:
"今天空气质量:
广州最优(AQI 55),北京良好(AQI 85),
上海轻度污染(AQI 120),建议敏感人群减少户外活动。"4.3 如何让 LLM 进行并行调用?
在 API 调用层面,这是一个参数设置:
API 调用参数
# OpenAI / 大多数 API 兼容的方式
response = client.chat.completions.create(
model="gpt-4",
messages=[...],
tools=[...],
parallel_tool_calls=True # ← 关键参数:允许并行
)
# 如果 parallel_tool_calls=False,
# LLM 一次只返回一个 Tool Call,需要多轮何时不适合并行?
- 后续调用依赖于前一个调用的结果时(如先搜项目负责人的名字,再用名字搜上级)
- 工具之间有副作用顺序时(如先创建用户再发送欢迎邮件——虽然也可以反过来处理)
4.4 并行调用的最佳实践
并行调用的判断逻辑(LLM 内部推理)
Q: "帮我查 ORD-001 的订单状态,顺便看看北京天气"
│
├─ get_order_status("ORD-001") ← 可以并行
│ └─ 输入来自用户,不依赖其他调用
│
└─ get_weather("北京") ← 可以并行
└─ 输入来自用户,不依赖其他调用
→ 两个调用完全独立,一次发出!
Q: "查一下苹果公司最新股价,然后用人民币换算"
│
├─ get_stock_price("AAPL") ← 第一步
│ └─ 返回: $198.50
│
└─ convert_currency(198.50, "USD", "CNY") ← 依赖第一步的结果!
└─ 输入 $198.50 来自第一个调用
→ 不能并行!必须串行。5. Structured Output / JSON Mode
5.1 为什么需要结构化输出?
在 Function Calling 中,LLM 输出的是函数调用指令(JSON 格式)。但即使不使用工具,我们也经常需要 LLM 输出结构化的 JSON 数据:
- 提取文档中的结构化信息(人名、日期、金额)
- 分类任务(将文本归入预设类别)
- 数据转换(自然语言 → 结构化数据)
传统方式:在 Prompt 中要求"输出 JSON 格式",但 LLM 可能不遵守,输出带有额外的文字或格式错误。
Structured Output / JSON Mode 是让 LLM"保证"输出合法 JSON 的机制。
5.2 两种实现方式
方式一:JSON Mode(宽松模式)
Prompt: "提取以下文本中的人名和日期,以JSON格式输出"
LLM 输出(可能有问题):
好的,以下是提取结果:
\`\`\`json
{
"persons": ["张三", "李四"],
"date": "2024-03-15"
}
\`\`\`
如果您需要更多信息...
问题:输出中包含了"好的,以下是提取结果"和"如果您需要更多信息..."
这不是纯 JSON!代码解析会失败。方式二:Structured Output(严格模式)
使用 response_format 参数指定 JSON Schema:
response = client.chat.completions.create(
model="gpt-4o",
messages=[...],
response_format={
"type": "json_schema",
"json_schema": {
"name": "person_extraction",
"schema": {
"type": "object",
"properties": {
"persons": {
"type": "array",
"items": {"type": "string"}
},
"date": {"type": "string"}
},
"required": ["persons", "date"],
"additionalProperties": False
}
}
}
)
LLM 保证输出:
{"persons": ["张三", "李四"], "date": "2024-03-15"}
干净、合法的 JSON,不会有任何额外内容。5.3 Structured Output 的关键特性
┌────────────────────────────────────────────────────────────┐
│ 1. 格式保证 │
│ 输出一定是合法的 JSON,不会有 Markdown 包装或多余文本 │
│ │
│ 2. Schema 遵循 │
│ 输出一定符合你定义的 JSON Schema(类型、必填字段等) │
│ │
│ 3. 拒绝额外字段 │
│ 如果设了 additionalProperties: false,不会出现未定义 │
│ 的字段 │
└────────────────────────────────────────────────────────────┘5.4 Function Calling vs Structured Output
两者都涉及结构化 JSON,但用途不同:
| 维度 | Function Calling | Structured Output |
|---|---|---|
| 目的 | 让 LLM 调用外部工具 | 让 LLM 返回结构化数据 |
| 后续动作 | 执行函数,结果返回 LLM | 程序直接使用输出 |
| LLM 行为 | 输出 ToolCall,等待结果 | 直接输出 JSON,结束 |
| 典型场景 | 查天气、搜文档、发邮件 | 信息提取、分类、数据转换 |
| JSON 格式 | 固定的 ToolCall 结构 | 自定义 Schema |
5.5 实际应用示例
信息提取应用:
输入文本:
"我叫张三,是字节跳动的工程师,负责AI平台研发。
我的手机号是13812345678,邮箱是zhangsan@bytedance.com。
我于2020年3月入职。"
Structured Output Schema:
{
"name": "employee_info",
"schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"company": {"type": "string"},
"role": {"type": "string"},
"phone": {"type": "string"},
"email": {"type": "string"},
"start_date": {"type": "string", "description": "入职日期 YYYY-MM-DD"}
},
"required": ["name", "company"]
}
}
LLM 输出:
{
"name": "张三",
"company": "字节跳动",
"role": "AI平台研发工程师",
"phone": "13812345678",
"email": "zhangsan@bytedance.com",
"start_date": "2020-03-01"
}
→ 直接存入数据库或传递给下一个系统,无需手动解析!6. 总结
Function Calling 是让 LLM 从"会说话的模型"变成"能做事的 Agent"的关键技术。
| 概念 | 核心要点 |
|---|---|
| 基本原理 | LLM 决策调用什么、传什么参数;代码负责执行 |
| 工具定义 | JSON Schema 描述函数名、用途、参数类型和约束 |
| 描述质量 | 好的描述是准确调用的基础,比参数定义更重要 |
| 单次调用 | LLM 调用一个工具即完成 |
| 多轮调用 | 链式调用,每次基于前次结果决定下一步 |
| 并行调用 | 互不依赖的调用同时发出,大幅降低延迟 |
| Structured Output | 保证输出合法 JSON,用于信息提取等非工具场景 |
关键要点:
- Function Calling 的本质是 LLM 作为"决策者",输出执行指令,代码作为"执行者"。
- 工具定义的质量直接决定调用准确性——description 比参数 schema 更重要。
- 对于互不依赖的工具调用,始终使用并行模式以降低延迟。
- 非工具场景的结构化输出用 Structured Output(JSON Schema)保障格式正确。
- Function Calling 是后面要讲的 Agent 的基础能力,是构建智能应用的基础。