跳到主要内容

Function Calling 与结构化输出

🔧 Function Calling 让 LLM 从文本生成器变成行动执行者;结构化输出确保返回结果机器可解析。

先建立正确心智模型

Function Calling 是一份模型与应用之间的协议:模型提出“调用哪个工具、参数是什么”,应用负责校验、鉴权、执行和回传。模型不能绕过应用直接获得权限。

Structured Output 是另一份协议:它约束模型最终返回的数据形状。两者可以组合:工具参数用 Schema 约束,最终答复再使用另一个 Schema。

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

一个可靠的工具循环

MAX_STEPS = 8
items = initial_items

for step in range(MAX_STEPS):
response = model.generate(items=items, tools=tools)
calls = [x for x in response.output if x.type == "function_call"]
if not calls:
return validate_final(response)

outputs = []
for call in calls:
args = validate_schema(call.arguments)
authorize(user, call.name, args)
result = execute_with_timeout_and_idempotency(call.name, args)
outputs.append({
"type": "function_call_output",
"call_id": call.call_id,
"output": safe_serialize(result),
})
items += response.output + outputs

raise MaxStepsExceeded()

关键点:

  • 同时限制步骤数、时间、token 和费用。
  • 写操作使用幂等键,网络重试不会重复产生副作用。
  • 参数通过 Schema 只是第一步,还要做用户、租户和资源级鉴权。
  • 工具错误应结构化返回:可重试、参数错误、权限错误或不可恢复。
  • 工具结果也是不可信数据,外部网页或邮件中的指令不能改变策略。

并行工具调用

一次对话可以同时调用多个工具:

# 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 个时,模型选择准确率下降,考虑分类或动态注入