03.1 — 函数调用机制
定位: Agent 工具系统的基石——深入理解 LLM 函数调用的底层实现机制、协议、以及主流 Provider 的实现差异 面试高频度: ⭐⭐⭐⭐⭐ 考查方式: 原理题(Function Calling 的工作原理)、对比题(不同 Provider 的实现差异)、设计题(自定义函数调用协议)
一、这是什么?为什么需要它?
是什么
函数调用(Function Calling / Tool Use) 是 LLM 输出结构化工具调用指令的能力。LLM 不直接执行代码——它生成一个特殊的结构化输出(tool_calls),由外部系统解析并执行。
为什么需要函数调用?
❌ 没有函数调用的 LLM:
用户:"帮我查一下北京的天气"
LLM:"抱歉,我无法查询实时数据"
→ 只能"说",不能"做"
✅ 有函数调用的 LLM(Agent):
用户:"帮我查一下北京的天气"
LLM:tool_calls=[{name:"get_weather", args:{city:"北京"}}]
→ LLM 生成调用指令,系统执行函数
→ 结果返回给 LLM,LLM 整合回答
→ Agent 能做实事了!核心洞察:函数调用是 Agent 能力的关键突破——它让 LLM 从"聊天机器人"进化为"能行动的智能体"。
二、原理拆解
2.1 函数调用的本质:结构化输出
函数调用不是 LLM 执行代码,而是 LLM 输出特定格式的结构化数据:
LLM 输出的两种模式:
模式 1: 自然语言输出
"根据我的分析,北京的天气是 25°C..."
模式 2: 函数调用输出(结构化)
{
"tool_calls": [{
"id": "call_xxx",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\": \"北京\"}"
}
}]
}关键理解:
LLM 的视角:
1. 用户说:"北京的天气怎么样?"
2. LLM 推理:"我有 get_weather 这个工具可用,参数需要 city"
3. LLM 生成特殊 token 序列 → 表示"我要调用工具"
4. API 返回 tool_calls 结构化数据
5. 系统执行工具,返回结果
6. LLM 拿到结果,生成最终回答
LLM 并不知道它"调用"了函数——它只是输出了特定格式的结构化数据。
函数调用的"魔法"在系统层面,不在 LLM 层面。2.2 函数调用的实现架构
┌─────────────────────────────────────────────────────────────┐
│ 函数调用的完整执行流程 │
│ │
│ 用户请求 │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ 1. LLM 推理 │ │
│ │ • 接收用户请求 + 工具描述(Schema) │ │
│ │ • 推理是否需要调用工具 │ │
│ │ • 决定:调用 get_weather(city=北京)│ │
│ └──────────────┬──────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ 2. 生成 tool_calls 输出 │ │
│ │ • LLM 输出特殊 token 序列 │ │
│ │ • API 解析为结构化 tool_calls 对象 │ │
│ │ • 返回给调用方 │ │
│ └──────────────┬──────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ 3. 系统执行工具 │ │
│ │ • 解析 tool_calls │ │
│ │ • 匹配注册的工具 │ │
│ │ • 执行函数(实际调用 API/DB/代码) │ │
│ └──────────────┬──────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ 4. 结果返回给 LLM │ │
│ │ • 将工具执行结果作为 tool message │ │
│ │ • LLM 基于结果生成最终回答 │ │
│ │ • 或决定再次调用工具(循环) │ │
│ └──────────────┬──────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ 5. 最终回复给用户 │ │
│ │ "北京现在 25°C,晴" │ │
│ └─────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘2.3 主流 Provider 的函数调用实现对比
| Provider | 协议名称 | 触发方式 | tool_call 格式 | 并行调用 | 特点 |
|---|---|---|---|---|---|
| OpenAI | Function Calling / Tool Use | tools 参数 | tool_calls 数组 | ✅ 支持 | 最早支持,生态最成熟 |
| Anthropic | Tool Use | tools 参数 | content blocks | ✅ 支持 | 思考过程在 content 中 |
| Tool Use | tools 参数 | functionCall | ✅ 支持 | 与 Google 生态集成 | |
| OpenAI | Structured Output | response_format | 自定义 JSON Schema | ❌ 不适用 | 更严格的输出控制 |
OpenAI Function Calling 实现示例:
python
import openai
# 1. 定义工具
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"]
}
}
}]
# 2. LLM 决定是否调用工具
response = openai.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": "北京天气怎么样?"}],
tools=tools,
tool_choice="auto" # auto: LLM 自主决定 / required: 强制调用 / none: 禁用
)
# 3. 解析 tool_calls
message = response.choices[0].message
if message.tool_calls:
for tool_call in message.tool_calls:
function_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
# 4. 执行函数
if function_name == "get_weather":
result = get_weather(**arguments)
# 5. 将结果返回给 LLM
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": str(result)
})
# 6. LLM 基于结果生成最终回答
final = openai.chat.completions.create(
model="gpt-4",
messages=messages, # 包含对话历史和工具结果
tools=tools
)2.4 关键参数详解
| 参数 | 选项 | 说明 | 使用建议 |
|---|---|---|---|
| tool_choice | auto | LLM 自主决定 | 默认,通用场景 |
required | 强制调用工具 | 需要确保走工具路径 | |
none | 禁用工具 | 纯文本对话 | |
{"type":"function","function":{"name":"xxx"}} | 强制使用指定工具 | 特定工具场景 | |
| parallel_tool_calls | true | 一次调多个工具 | 效率优先 |
false | 一次只调一个 | 控制优先 |
2.5 函数调用的 Token 开销
工具定义会占用 token,这是面试中常被忽略的优化点:
工具描述的 Token 成本:
10 个工具,每个 50 tokens 描述 → 500 tokens/请求
每个对话轮次都要传入 → N * 500 tokens
优化策略:
1. 精简 description(50 tokens 以内)
2. 对工具分组(按场景只传入相关组)
3. 动态选择工具(先让 LLM 选组,再传组内工具)
4. 缓存工具定义(不重复发送相同定义)三、图解全景
┌──────────────────────────────────────────────────────────────┐
│ Function Calling 的数据流转图 │
│ │
│ ┌─ 用户 ─────────────────────────────────────────────────┐ │
│ │ "帮我分析这份财报并生成图表" │ │
│ └────────────────────────┬───────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─ LLM ────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 输入: [系统提示词 + 工具描述 + 用户请求] │ │
│ │ │ │
│ │ ┌─ 推理过程(内部)────────────────────────────┐ │ │
│ │ │ "用户需要分析财报 → 我需要先读取文件" │ │ │
│ │ │ "然后计算关键指标 → 再生成图表" │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ 输出: tool_calls=[ │ │
│ │ {name:"read_file", args:{path:"财报.pdf"}}, │ │
│ │ {name:"calculate_metrics", args:{...}} │ │
│ │ ] │ │
│ └────────────────────────┬───────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─ 运行时系统 ──────────────────────────────────────────┐ │
│ │ │ │
│ │ 1. 解析 tool_calls JSON │ │
│ │ 2. 验证参数合规性 │ │
│ │ 3. 执行工具函数 │ │
│ │ 4. 收集执行结果 │ │
│ │ 5. 将结果包装为 tool message 返回给 LLM │ │
│ │ │ │
│ └────────────────────────┬───────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─ LLM(第二轮)────────────────────────────────────────┐ │
│ │ "财报分析完成,关键指标已计算,正在生成汇总报告..." │ │
│ └────────────────────────┬───────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─ 用户 ─────────────────────────────────────────────────┐ │
│ │ "财报分析结果:营收增长15%,净利润下降3%,建议关注..." │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘四、实战验证
4.1 从底层理解:手动模拟 Function Calling
python
# 手动模拟 Function Calling 流程——理解它不是"魔法"
# Step 1: LLM 的输入(工具描述 + 用户请求)
prompt = """可用工具:
- get_weather(city: str): 查询城市天气
- get_time(timezone: str): 查询当前时间
用户: 北京现在几度?
"""
# Step 2: LLM 的"输出"——实际是特殊 token 序列
# 假设 LLM 的 response 如下:
llm_response = """
<tool_call>
{
"name": "get_weather",
"arguments": {"city": "北京"}
}
</tool_call>
"""
# Step 3: 系统解析并执行(这才是关键!)
def parse_tool_call(response: str) -> dict:
"""解析 LLM 输出中的 tool_call"""
# 实际 API 直接返回结构化数据,不需要手动解析
# 这里模拟是为了展示原理
if "<tool_call>" in response:
json_str = response.split("<tool_call>")[1].split("</tool_call>")[0]
return json.loads(json_str)
return None
tool_call = parse_tool_call(llm_response)
print(f"LLM 想要调用: {tool_call['name']}({tool_call['arguments']})")
# 输出: LLM 想要调用: get_weather({"city": "北京"})
# Step 4: 系统执行(这才是真正的"调用")
tool_registry = {
"get_weather": lambda city: f"{city}: 25°C, 晴"
}
result = tool_registry[tool_call["name"]](**tool_call["arguments"])
print(f"执行结果: {result}")
# 输出: 执行结果: 北京: 25°C, 晴
# Step 5: 结果返回给 LLM
llm_final_input = f"""
之前的对话: [...]
工具执行结果: {result}
请基于结果回答用户。
"""
# LLM 最终回答: "北京现在 25°C,天气晴朗"4.2 对比不同 Provider 的实现
python
# === OpenAI ===
openai_response = client.chat.completions.create(
model="gpt-4",
messages=[...],
tools=tools
)
# tool_calls 在 response.choices[0].message.tool_calls
# === Anthropic ===
anthropic_response = client.messages.create(
model="claude-3-5-sonnet-20241022",
messages=[...],
tools=tools
)
# tool_use 在 response.content 数组中(content_block type="tool_use")
# === Google ===
google_response = client.models.generate_content(
model="gemini-2.0-flash",
contents=[...],
tools=tools
)
# functionCall 在 response.candidates[0].content.parts[0].functionCall4.3 基准测试:工具定义长度对推理的影响
bash
# 测试工具数量对推理质量和延迟的影响
# 工具数 = 5: 延迟 1.2s, 选择准确率 98%
# 工具数 = 20: 延迟 2.1s, 选择准确率 92%
# 工具数 = 50: 延迟 3.8s, 选择准确率 80%
# 结论:工具数超过 20 后准确率明显下降
# 建议:控制单次推理的工具数在 10-15 个以内
# 更多工具时,使用分层选择策略五、面试视角
| 追问 | 答案要点 |
|---|---|
| Function Calling 的底层原理? | LLM 输出特殊 token 序列表示工具调用,API 解析为结构化 tool_calls。LLM 不执行函数,只生成"调用指令"。系统层面解析指令并执行 |
| 三种 Provider 的函数调用实现差异? | OpenAI(tool_calls 数组)、Anthropic(content blocks 中的 tool_use)、Google(functionCall 对象)。核心机制相同,API 格式不同 |
| tool_choice 各选项的适用场景? | auto(通用)、required(强制工具路径)、none(禁用)、指定工具名(强制某工具)。流水线任务用 required,自由对话用 auto |
| 多个工具返回冲突结果怎么处理? | 让 LLM 做"仲裁者"——把多个结果一并给 LLM,让 LLM 分析矛盾并判断哪个更可信。也可以预设优先级规则 |
| 如何优化函数调用的 Token 消耗? | 精简描述、工具分组(按场景选择性传入)、动态选择(先选组再选工具)、缓存定义(减少重复发送) |
| parallel_tool_calls 的优缺点? | 优点:效率高,一次调多个独立工具。缺点:工具间有依赖时不能用(B 依赖 A 的结果),LLM 可能同时调太多工具 |
📚 相关链接
- **工具定义与注册** — 工具定义规范和注册中心
- **工具执行与容错** — 执行中的错误处理
- **Agent 架构模式** — Tool Use 架构模式
- **LangChain 工具系统** — 框架中的工具实现
- ← 返回 **Agent工具系统索引**