一、什么是 Function Calling
Function Calling(函数调用)是 LLM 的一项关键能力——模型在生成回复时,可以输出一个结构化的函数调用请求,而不是直接生成文本。这使得 Agent 能够与外部系统交互。
用户:"帮我查一下北京的天气"
LLM 输出:search_weather(city="北京")
工具执行:{"temperature": 22, "condition": "晴"}
LLM 最终输出:"北京今天 22°C,天气晴朗"1.1 为什么需要工具调用
| 场景 | LLM 做不到的 | 工具解决的 |
|---|---|---|
| 实时数据 | 训练数据有截止日期 | 联网搜索 |
| 精确计算 | 大数运算不准确 | 计算器工具 |
| 操作外部系统 | 无法直接操作 | API 调用 |
| 私有数据 | 未在训练集中 | 数据库查询 |
二、Function Calling 的工作原理
2.1 完整流程
① 定义工具 Schema(JSON Schema 格式)
② 将 Schema 随 System Prompt 一起发给 LLM
③ LLM 推理 → 决定调用哪个工具 → 返回结构化参数
④ 解析参数 → 执行工具函数 → 获取结果
⑤ 将结果返回给 LLM → LLM 继续推理
⑥ 重复 ③~⑤ 直到任务完成2.2 OpenAI 格式的工具定义
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.3 执行工具调用
import json
from openai import OpenAI
client = OpenAI()
def execute_function_call(tool_call, available_functions):
"""解析并执行工具调用"""
function_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
function = available_functions.get(function_name)
if not function:
return json.dumps({"error": f"未知函数: {function_name}"})
try:
result = function(**arguments)
return json.dumps(result, ensure_ascii=False)
except Exception as e:
return json.dumps({"error": str(e)})
# 注册可用函数
def get_weather(city: str, unit: str = "celsius"):
# 实际实现中调用天气 API
return {"city": city, "temperature": 22, "unit": unit}
available_functions = {
"get_weather": get_weather
}
# 多轮工具调用循环
messages = [
{"role": "system", "content": "你是一个天气助手"},
{"role": "user", "content": "北京和上海今天哪个更暖和?"}
]
for _ in range(5): # 最多 5 轮工具调用
response = client.chat.completions.create(
model="gpt-4",
messages=messages,
tools=tools,
tool_choice="auto"
)
message = response.choices[0].message
if not message.tool_calls:
# 没有工具调用,直接输出
print(message.content)
break
messages.append(message)
for tool_call in message.tool_calls:
result = execute_function_call(tool_call, available_functions)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result
})三、常见工具类型及实现
3.1 网络搜索
import requests
from duckduckgo_search import DDGS
def search_web(query: str, max_results: int = 5) -> List[Dict]:
"""搜索互联网获取最新信息"""
with DDGS() as ddgs:
results = []
for r in ddgs.text(query, max_results=max_results):
results.append({
"title": r["title"],
"url": r["href"],
"snippet": r["body"]
})
return results3.2 代码执行
import subprocess
import tempfile
def execute_python(code: str) -> str:
"""在沙箱中执行 Python 代码"""
with tempfile.NamedTemporaryFile(
mode="w", suffix=".py", delete=False
) as f:
f.write(code)
f.flush()
result = subprocess.run(
["python3", f.name],
capture_output=True,
text=True,
timeout=30
)
if result.returncode == 0:
return result.stdout
else:
return f"错误: {result.stderr}"3.3 文件操作
import os
def read_file(path: str) -> str:
"""读取文件内容"""
if not os.path.exists(path):
return f"文件不存在: {path}"
if os.path.getsize(path) > 1024 * 1024:
return "文件超过 1MB 限制"
with open(path, "r", encoding="utf-8") as f:
return f.read()
def write_file(path: str, content: str) -> str:
"""写入文件"""
os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
with open(path, "w", encoding="utf-8") as f:
f.write(content)
return f"已写入 {len(content)} 字符到 {path}"四、工具设计最佳实践
4.1 工具定义原则
- 单一职责:每个工具只做一件事
- 清晰的命名和描述:LLM 靠描述理解工具用途
- 参数校验:对输入参数做类型和范围校验
- 友好的错误返回:错误信息要能被 LLM 理解
4.2 工具注册模式
from functools import wraps
class ToolRegistry:
"""工具注册与管理"""
def __init__(self):
self._tools = {}
self._schemas = []
def register(self, name: str = None):
"""装饰器:注册工具"""
def decorator(func):
tool_name = name or func.__name__
self._tools[tool_name] = func
self._schemas.append(self._build_schema(func))
return func
return decorator
def _build_schema(self, func) -> Dict:
"""从类型注解自动生成 Schema"""
import inspect
sig = inspect.signature(func)
properties = {}
required = []
for param_name, param in sig.parameters.items():
if param_name == "self":
continue
properties[param_name] = {
"type": self._type_to_str(param.annotation),
"description": f"参数 {param_name}"
}
if param.default is inspect.Parameter.empty:
required.append(param_name)
return {
"type": "function",
"function": {
"name": func.__name__,
"description": func.__doc__ or "",
"parameters": {
"type": "object",
"properties": properties,
"required": required
}
}
}
def _type_to_str(self, t) -> str:
mapping = {str: "string", int: "integer", float: "number", bool: "boolean"}
return mapping.get(t, "string")
# 使用示例
registry = ToolRegistry()
@registry.register()
def search(query: str, max_results: int = 5) -> List[Dict]:
"""搜索互联网"""
...
@registry.register()
def calculate(expression: str) -> float:
"""执行数学计算"""
return eval(expression) # 注意:生产环境需要使用安全计算引擎五、错误处理与重试策略
5.1 常见错误类型
| 错误类型 | 原因 | 处理方式 |
|---|---|---|
| 参数错误 | LLM 生成的参数不合法 | 返回清晰错误信息让 LLM 修正 |
| 超时 | 工具执行时间过长 | 设置超时并返回友好提示 |
| 限流 | API 调用频率超限 | 指数退避重试 |
| 依赖失败 | 下游服务故障 | 返回降级结果 |
5.2 带重试的工具执行
import time
from functools import wraps
def with_retry(max_retries: int = 3, delay: float = 1.0):
"""工具执行重试装饰器"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
last_error = None
for attempt in range(max_retries):
try:
return func(*args, **kwargs)
except Exception as e:
last_error = str(e)
if attempt < max_retries - 1:
time.sleep(delay * (2 ** attempt))
return {"error": f"重试 {max_retries} 次后失败: {last_error}"}
return wrapper
return decorator
@with_retry(max_retries=3)
def call_external_api(url: str) -> Dict:
response = requests.get(url, timeout=10)
response.raise_for_status()
return response.json()六、总结
Function Calling 是 Agent 与外部世界交互的核心能力。掌握工具定义、调用流程和错误处理,是构建可靠 Agent 的基础。
关键要点:
- 工具 Schema 要清晰准确,描述要详细
- 完善的错误处理让 Agent 能自我修正
- 单一职责原则让 LLM 更容易选择正确工具
- 限流和重试保障系统稳定性
下一篇将深入讲解 Agent 的规划与推理机制。