工具调用(Function Calling / Tool Use)是 Agent 的"手脚"——它让模型的决策真正落地到可执行的动作。本文拆解工具调用的底层机制、Schema 设计最佳实践,以及跨平台兼容性处理。
1. 工具调用的本质
当用户说 “查一下北京明天天气”,模型需要:
- 识别意图:用户想获取天气信息
- 匹配工具:存在
get_weather工具可用 - 参数推导:城市 = “北京”,日期 = “明天”
- 调用执行:实际调用 API
- 结果消化:将 API 返回的 JSON 转化为自然语言回答
用户输入 → 模型推理 → 工具调用决策 → 参数提取 → API 执行 → 结果反馈 → 回答生成
2. 六大工具调用标准对比
| 标准 | 代表 | Schema 格式 | 并行调用 | 返回格式 |
|---|---|---|---|---|
| OpenAI Function Calling | GPT-4o | JSON Schema | ✅ | tool_calls id |
| Claude Tool Use | Claude 3.5 | JSON Schema | ✅ | tool_use id |
| Google Function Declaration | Gemini | OpenAPI Schema | ✅ | functionCall |
| Mistral Function Calling | Mistral | JSON Schema | ✅ | tool_calls |
| Llama 3 Tool Use | Llama 3.1 | JSON Schema (简化) | ⚠️ | tool_calls |
| Anthropic Computer Use | Claude 3.5 | 伪代码 | ✅ | tool_use |
2.1 OpenAI Function Calling 详解
from openai import OpenAI
client = OpenAI()
# 定义工具
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的天气信息。当用户询问天气时使用。",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名称"},
"date": {"type": "string", "description": "日期(YYYY-MM-DD)"},
},
"required": ["city", "date"],
},
},
},
{
"type": "function",
"function": {
"name": "send_email",
"description": "发送一封邮件。",
"parameters": {
"type": "object",
"properties": {
"to": {"type": "string"},
"subject": {"type": "string"},
"body": {"type": "string"},
},
"required": ["to", "subject", "body"],
},
},
},
]
# 第一轮对话:请求工具调用
messages = [{"role": "user", "content": "北京明天天气怎么样?"}]
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools,
tool_choice="auto", # auto | none | {"type": "function", "function": {"name": "xxx"}}
)
# model 决定调用 get_weather
print(response.choices[0].message) # role=assistant, content=None, tool_calls=[...]
# 解析工具调用
tool_call = response.choices[0].message.tool_calls[0]
print(f"调用的工具: {tool_call.function.name}")
print(f"参数: {tool_call.function.arguments}")
# 执行工具(模拟)
result = {"temp": 28, "weather": "晴", "humidity": 45}
# 第二轮:将结果归还模型
messages.append(response.choices[0].message) # 把模型请求加回去
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"name": tool_call.function.name,
"content": str(result),
})
final = client.chat.completions.create(model="gpt-4o", messages=messages, tools=tools)
print(final.choices[0].message.content) # "北京明天晴,28度..."
2.2 Claude Tool Use 详解
from anthropic import Anthropic
client = Anthropic()
# Claude 的工具声明与 OpenAI 几乎相同
tools = [
{
"name": "get_weather",
"description": "获取天气信息",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string"},
"date": {"type": "string"},
},
"required": ["city"],
},
},
]
response = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1024,
messages=[{"role": "user", "content": "北京明天天气"}],
tools=tools,
)
# 检测是否使用了工具
if response.stop_reason == "tool_use":
tool_use = response.content[-1] # 最后一条 content block 是 tool_use
print(f"Claude 想调用: {tool_use.name},参数: {tool_use.input}")
3. Schema 设计最佳实践
3.1 description 是关键
模型通过 description 决定选哪个工具。这 100 字决定了成败:
# ❌ 差的描述
"description": "搜索功能"
# ✅ 好的描述
"description": "在商品数据库中根据关键词搜索商品信息并返回符合条件的 SKU 列表。适用于用户想要查找特定商品的讲候。不支持搜索用户个人信息。"
Schema Tips:
- 在 description 中明确包含"何时用这个工具"(触发条件)
- 提供约束条件:“日期格式必须是 YYYY-MM-DD”
- 提供枚举值:如果 city 只能是几个明确值,用
enum - 给出典型例子:使用
examples字段辅助理解
3.2 参数命名
# ❌ 模糊命名
"from": str # 从哪里?时间?地点?
# ✅ 自解释命名
"start_date": str
"origin_city": str
3.3 幂等工具
标记哪些工具是幂等的(可安全重复调用):
{
"name": "get_user_profile",
"description": "获取用户信息(幂等操作)。",
"parameters": {...},
"strict": True, # OpenAI 参数校验强化
}
3.4 限制并发
from typing import Callable, Dict
import asyncio
class RateLimitedTool:
def __init__(self, fn: Callable, max_concurrent: int = 5):
self.fn = fn
self.semaphore = asyncio.Semaphore(max_concurrent)
async def __call__(self, **kwargs):
async with self.semaphore:
return await self.fn(**kwargs)
4. 跨平台兼容层
class ToolRegistry:
"""跨 LLM 平台的工具注册器"""
def __init__(self):
self._tools: Dict[str, Callable] = {}
def register(self, name: str, description: str, fn: Callable, schema: dict):
self._tools[name] = {
"fn": fn,
"description": description,
"schema": schema,
}
return self
def to_openai_format(self):
return [
{
"type": "function",
"function": {
"name": name,
"description": meta["description"],
"parameters": meta["schema"],
},
}
for name, meta in self._tools.items()
]
def to_claude_format(self):
return [
{
"name": name,
"description": meta["description"],
"input_schema": meta["schema"],
}
for name, meta in self._tools.items()
]
def execute(self, tool_name: str, arguments: dict):
if tool_name not in self._tools:
raise ValueError(f"未知工具: {tool_name}")
# Schema 校验
import jsonschema
jsonschema.validate(arguments, self._tools[tool_name]["schema"])
return self._tools[tool_name]["fn"](**arguments)
# 使用
registry = ToolRegistry()
registry.register(
name="get_weather",
description="获取城市天气(当用户询问天气时使用)",
fn=lambda city, date: {"temp": 28, "weather": "晴"},
schema={"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]},
)
# OpenAI 格式
openai_tools = registry.to_openai_format()
# Claude 格式
claude_tools = registry.to_claude_format()
5. 高级:自动工具发现
不需要手动注册所有工具——让 Agent 自己发现:
import inspect
class AutoToolDiscovery:
def __init__(self):
self.registry = {}
def discover(self, module):
"""扫描模块中所有 @tool 装饰的函数"""
for name, obj in inspect.getmembers(module, inspect.isfunction):
if hasattr(obj, "_tool_schema"):
self.registry[name] = {
"fn": obj,
"schema": obj._tool_schema,
"desc": obj._tool_desc,
}
# 装饰器用法
def tool(description, schema):
def decorator(fn):
fn._tool_schema = schema
fn._tool_desc = description
return fn
return decorator
# 应用
@tool(
description="获取天气信息",
schema={"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}
)
def get_weather(city: str):
return f"{city} 今天晴,25度"
6. 错误处理与降级
class ResilientToolExecutor:
def execute(self, tool_name, arguments, fallback=None):
try:
result = self.registry.execute(tool_name, arguments)
return {"status": "success", "data": result}
except jsonschema.ValidationError as e:
return {
"status": "validation_error",
"message": f"参数错误: {e.message}",
"suggestion": f"请确保按此格式调用: {json.dumps(self.registry.get_schema(tool_name))}",
}
except Exception as e:
if fallback:
return fallback(arguments)
return {"status": "error", "message": str(e)}
7. 总结与选型建议
| 场景 | 推荐标准 | 理由 |
|---|---|---|
| 快速开发 MVP | OpenAI Function Calling | 文档最全、生态最广 |
| 长上下文 + 多模态 | Claude Tool Use | 200K 上下文,多模态优秀 |
| 控制成本 + 多 provider | Mistral Tool Use | API 成本最低 |
| 私有化部署 | Llama 3.1 + JSON Schema | 开源,离线运行 |
📂 继续阅读:
- AI 智能体架构设计 — 感知、推理、记忆、工具、执行五大组件
- Agent 工作流编排设计 — DAG 与状态机实现
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。
「llm」更多文章
模型上下文协议(MCP)完整指南:从 Anthropic 标准到 AI 应用互操作性革命
系统拆解 Model Context Protocol(MCP)的设计哲学、协议分层和核心概念:Resources、Prompts、Tools、Sampling。 涵盖 MCP 与 Function Calling、插件系统、API 网关的区别与互补关系。 附架构全景图、协议消息格式详解,以及 MCP 在 Claude Desktop、Cline、Continue 等客户端中的实际运行机制。
多模态语音合成与识别:从 Whisper + TTS 到实时语音交互的 LLM 实践
系统拆解 LLM 语音技术栈:语音识别(ASR / Whisper)、语音合成(TTS / ElevenLabs / Coqui TTS)、语音活动检测(VAD)。 覆盖实时语音交互架构(流式识别 + 流式合成 + 打断机制)、多语言语音克隆、以及语音 Agent 的安全与隐私考量。 附完整的 Whisper API 集成、本地 TTS 部署、WebRTC 实时语音管道代码。
多模态视频分析:从关键帧抽取到时序理解的 LLM 视频理解实战
系统拆解 LLM 视频理解的技术路径:关键帧提取、时序编码(Temporal Modeling)、视觉-时序联合推理。 覆盖 Gemini 1.5 Pro(原生视频)、GPT-4o(帧序列)、Claude(关键帧截图)、Video-LLaMA 四种实现方式。 附视频摘要生成、动作识别、异常检测、教育内容分析四个场景的完整代码与成本优化策略。