Function Calling 与结构化输出
🔧 Function Calling 让 LLM 从文本生成器变成行动执行者;结构化输出确保返回结果机器可解析。
Function Calling 基础
工作流程
1. 定义工具(名称、描述、参数 Schema)
2. 用户提问 → LLM 决策是否调用工具
3. LLM 返回 tool_call(工具名 + 参数 JSON)
4. 应用层执行工具,获取结果
5. 将结果放回对话,LLM 生成最终回答
工具定义示例
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的当前天气",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,如北京、上海"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"]
}
},
"required": ["city"]
}
}
}
结构化输出
方式一:JSON Mode
强制模型输出合法 JSON,但不保证字段结构:
response = client.chat.completions.create(
model="gpt-4o",
response_format={"type": "json_object"},
messages=[{"role": "user", "content": "..."}]
)
方式二:Structured Outputs(推荐)
提供 JSON Schema,模型严格遵守结构:
from pydantic import BaseModel
class NewsItem(BaseModel):
title: str
summary: str
tags: list[str]
sentiment: Literal["positive", "negative", "neutral"]
response = client.beta.chat.completions.parse(
model="gpt-4o",
response_format=NewsItem,
messages=[...]
)
news = response.choices[0].message.parsed
并行工具调用
一次对话可以同时调用多个工具:
# LLM 可能返回多个 tool_calls
for tool_call in response.choices[0].message.tool_calls:
result = execute_tool(tool_call.function.name,
json.loads(tool_call.function.arguments))
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result)
})
工具设计原则
| 原则 | 说明 |
|---|---|
| 单一职责 | 每个工具只做一件事 |
| 幂等性 | 相同输入多次调用结果一致 |
| 描述精确 | description 比 name 更影响调用决策 |
| 参数精简 | 参数越少,模型越准确 |
| 返回简洁 | 只返回模型需要的信息 |
常见问题排查
| 问题 | 原因 | 解决 |
|---|---|---|
| 模型不调用工具 | description 不够清晰 | 改进工具描述,加示例 |
| 参数填错 | 参数名歧义 | 用 description 详细说明每个参数 |
| 重复调用 | 工具结果没有放回对话 | 确保 tool result 正确追加 |
| JSON 解析失败 | 模型输出不合规 | 用 Structured Outputs 或加校验重试 |
常见误区
- Function Calling 不是自动执行,需要应用层手动调用并将结果传回
- tool_choice: "auto" 时模型可能选择不调用工具,需要在对话设计上引导
- 工具数量超过 15-20 个时,模型选择准确率下降,考虑分类或动态注入