引言:从"聊天"到"做事"的关键一步
大模型真正进入生产系统,靠的不是它能说多少漂亮话,而是它能不能精准地调用工具完成任务。Function Calling(也称 Tool Use)是连接 LLM 推理能力与现实世界操作的核心桥梁。
然而,在生产实践中,Function Calling 远比演示示例复杂。函数定义写得不对,模型调用失败;参数提取不准,下游逻辑崩溃;多工具并行调用,顺序和依赖关系难以管理。
本文从工程角度深入解析 Function Calling 的完整体系,涵盖函数定义最佳实践、参数校验、错误恢复、并行调用、以及构建可靠工具链的系统方法。
一、Function Calling 工作机制深度解析
1.1 协议层理解
以 OpenAI 格式为例,Function Calling 的完整流程:
用户消息
↓
模型决策:是否需要调用函数?调用哪个?参数是什么?
↓
返回 tool_calls(不是最终文本,而是调用指令)
↓
应用层执行函数,获取结果
↓
将结果以 tool 角色消息传回模型
↓
模型基于函数结果生成最终回复
核心 API 结构:
import openai
import json
client = openai.OpenAI()
# 定义工具
tools = [
{
"type": "function",
"function": {
"name": "search_products",
"description": "在商品数据库中搜索商品,支持按名称、类别、价格区间筛选",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "搜索关键词,如商品名称或描述"
},
"category": {
"type": "string",
"enum": ["electronics", "clothing", "food", "home"],
"description": "商品类别,不确定时不传此参数"
},
"max_price": {
"type": "number",
"description": "最高价格(元),不限制时不传此参数"
}
},
"required": ["query"]
}
}
}
]
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "帮我找一下500元以内的蓝牙耳机"}],
tools=tools,
tool_choice="auto"
)
# 处理 tool_calls
if response.choices[0].message.tool_calls:
for tool_call in response.choices[0].message.tool_calls:
func_name = tool_call.function.name
func_args = json.loads(tool_call.function.arguments)
print(f"调用函数: {func_name}")
print(f"参数: {func_args}")
二、函数定义:工程质量的关键
2.1 描述要素:影响模型决策的核心
函数描述不是给人看的注释,而是给模型看的"说明书"。描述质量直接决定模型调用的准确率。
差的描述 vs 好的描述:
# ❌ 差的描述 - 信息不足,模型不知何时调用
{
"name": "get_user",
"description": "获取用户信息",
"parameters": {
"type": "object",
"properties": {
"id": {"type": "string"}
}
}
}
# ✅ 好的描述 - 明确功能边界和适用场景
{
"name": "get_user_profile",
"description": "根据用户 ID 获取用户完整档案,包括基本信息、偏好设置和历史行为摘要。当用户询问个人信息、需要个性化推荐、或分析用户行为时使用。注意:此函数需要有效的用户 ID,匿名用户不支持。",
"parameters": {
"type": "object",
"properties": {
"user_id": {
"type": "string",
"description": "用户唯一标识符,格式为 'usr_' 前缀加 24 位字母数字,例如 'usr_a1b2c3d4e5f6g7h8i9j0k1l2'"
},
"include_history": {
"type": "boolean",
"description": "是否包含历史行为数据,默认 false。数据量较大,仅在需要分析历史时设为 true"
}
},
"required": ["user_id"]
}
}
2.2 参数设计原则
原则一:只暴露模型需要决策的参数
# ❌ 暴露不必要的技术参数
{
"name": "query_database",
"parameters": {
"sql_query": ..., # 模型不应该写 SQL
"connection_pool": ..., # 技术实现细节
"timeout_ms": ... # 让模型决定超时?
}
}
# ✅ 只暴露业务逻辑参数
{
"name": "query_orders",
"parameters": {
"user_id": ...,
"status": ..., # pending/completed/cancelled
"date_from": ...,
"limit": ...
}
}
原则二:使用 enum 约束可枚举参数
"status": {
"type": "string",
"enum": ["pending", "processing", "shipped", "delivered", "cancelled"],
"description": "订单状态"
}
原则三:为复杂参数提供示例
"date_range": {
"type": "string",
"description": "日期范围,格式为 'YYYY-MM-DD,YYYY-MM-DD',例如 '2026-01-01,2026-01-31' 表示查询一月份数据"
}
三、完整的工具执行引擎
3.1 带参数校验的工具执行器
import json
import logging
from typing import Any, Callable, Dict
from pydantic import BaseModel, ValidationError
logger = logging.getLogger(__name__)
class ToolExecutor:
def __init__(self):
self.tools: Dict[str, dict] = {}
self.handlers: Dict[str, Callable] = {}
def register(self, schema: dict, handler: Callable):
"""注册工具及其处理函数"""
name = schema["function"]["name"]
self.tools[name] = schema
self.handlers[name] = handler
def execute(self, tool_call) -> dict:
"""执行工具调用,带错误处理"""
func_name = tool_call.function.name
tool_call_id = tool_call.id
# 检查工具是否存在
if func_name not in self.handlers:
return {
"tool_call_id": tool_call_id,
"role": "tool",
"content": json.dumps({
"error": f"工具 '{func_name}' 不存在",
"available_tools": list(self.handlers.keys())
}, ensure_ascii=False)
}
# 解析参数
try:
args = json.loads(tool_call.function.arguments)
except json.JSONDecodeError as e:
return {
"tool_call_id": tool_call_id,
"role": "tool",
"content": json.dumps({"error": f"参数解析失败: {str(e)}"})
}
# 执行函数
try:
result = self.handlers[func_name](**args)
return {
"tool_call_id": tool_call_id,
"role": "tool",
"content": json.dumps(result, ensure_ascii=False, default=str)
}
except Exception as e:
logger.error(f"工具 {func_name} 执行失败: {e}", exc_info=True)
return {
"tool_call_id": tool_call_id,
"role": "tool",
"content": json.dumps({
"error": f"执行失败: {str(e)}",
"suggestion": "请检查参数是否正确,或尝试换一种方式"
}, ensure_ascii=False)
}
def execute_all(self, tool_calls: list) -> list:
"""并行执行所有工具调用"""
import concurrent.futures
with concurrent.futures.ThreadPoolExecutor() as executor:
futures = {executor.submit(self.execute, tc): tc for tc in tool_calls}
results = []
for future in concurrent.futures.as_completed(futures):
results.append(future.result())
return results
3.2 完整的多轮对话循环
def run_agent(user_message: str, executor: ToolExecutor, max_rounds: int = 5) -> str:
"""运行带工具调用的 Agent 对话循环"""
messages = [{"role": "user", "content": user_message}]
tools = list(executor.tools.values())
for round_num in range(max_rounds):
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools,
tool_choice="auto"
)
message = response.choices[0].message
messages.append(message) # 追加 assistant 消息
# 没有工具调用,返回最终答案
if not message.tool_calls:
return message.content
# 执行所有工具调用(支持并行)
tool_results = executor.execute_all(message.tool_calls)
messages.extend(tool_results) # 追加工具结果
logger.info(f"第 {round_num+1} 轮:执行了 {len(message.tool_calls)} 个工具调用")
return "已达到最大轮次,请简化您的请求"
四、高级模式:并行调用与依赖管理
4.1 识别可并行的工具调用
现代模型(GPT-4o、Claude 3.5 等)支持在一次响应中返回多个工具调用,且这些调用可以并行执行:
用户:给我分析一下苹果公司的股价、最新新闻和财报数据
模型返回 tool_calls:
- get_stock_price(symbol="AAPL") ← 可并行
- search_news(query="Apple Inc") ← 可并行
- get_financial_report(symbol="AAPL") ← 可并行
4.2 依赖型调用的顺序管理
# 有些调用存在依赖关系,需要串行执行
# 例如:先创建订单,再获取订单 ID,再发送通知
def execute_with_dependency(messages, executor, client):
"""处理可能存在依赖的工具调用链"""
context = {} # 保存中间结果
while True:
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=list(executor.tools.values()),
)
message = response.choices[0].message
messages.append(message)
if not message.tool_calls:
break
# 注入上下文到工具调用(通过 context 传递依赖值)
for tc in message.tool_calls:
args = json.loads(tc.function.arguments)
# 自动替换占位符(如 {{order_id}} → 真实 ID)
for key, value in args.items():
if isinstance(value, str) and value.startswith("{{") and value.endswith("}}":
context_key = value[2:-2]
if context_key in context:
args[key] = context[context_key]
results = executor.execute_all(message.tool_calls)
for result in results:
# 将结果存入上下文供后续调用使用
content = json.loads(result["content"])
if isinstance(content, dict):
context.update(content)
messages.extend(results)
return messages[-1].content
五、生产级最佳实践
5.1 工具调用日志与可观测性
import time
from dataclasses import dataclass
@dataclass
class ToolCallLog:
tool_name: str
arguments: dict
result: Any
duration_ms: float
success: bool
error: str = None
class ObservableToolExecutor(ToolExecutor):
def __init__(self):
super().__init__()
self.call_logs: list[ToolCallLog] = []
def execute(self, tool_call) -> dict:
start = time.time()
result = super().execute(tool_call)
duration = (time.time() - start) * 1000
content = json.loads(result["content"])
log = ToolCallLog(
tool_name=tool_call.function.name,
arguments=json.loads(tool_call.function.arguments),
result=content,
duration_ms=duration,
success="error" not in content,
error=content.get("error")
)
self.call_logs.append(log)
return result
5.2 工具调用限速与安全
import time
from collections import defaultdict
class RateLimitedToolExecutor(ToolExecutor):
def __init__(self, calls_per_minute: int = 60):
super().__init__()
self.calls_per_minute = calls_per_minute
self.call_timestamps = defaultdict(list)
def _check_rate_limit(self, tool_name: str) -> bool:
"""检查是否超出调用频率限制"""
now = time.time()
window_start = now - 60
self.call_timestamps[tool_name] = [
ts for ts in self.call_timestamps[tool_name]
if ts > window_start
]
if len(self.call_timestamps[tool_name]) >= self.calls_per_minute:
return False
self.call_timestamps[tool_name].append(now)
return True
结语
Function Calling 的工程质量,直接决定 AI Agent 在生产环境中的可靠性。一个设计良好的工具体系,应该做到:函数描述精准,模型能自主判断何时调用;参数定义清晰,避免解析错误;错误处理完整,故障不会导致对话中断;可观测性完善,问题能快速定位。
从演示级代码到生产级系统,工具调用工程化是不可绕过的必经之路。