引言
在 AI Agent 的构建过程中,工具调用(Tool Use) 是连接大模型与外部世界的桥梁。从早期的 Function Calling 到如今的 MCP(Model Context Protocol)协议,工具调用的设计模式正在经历一场深刻的变革。
本文将深入探讨这一演进过程,分析不同设计模式的优劣,并提供实战代码示例,帮助你构建更强大的 AI Agent。
一、Function Calling:工具调用的起点
1.1 什么是 Function Calling?
Function Calling 是 OpenAI 在 2023 年推出的功能,允许大模型根据用户输入自动判断是否需要调用外部函数,并生成相应的函数调用参数。
import openai
def get_weather(location: str, unit: str = "celsius"):
"""获取指定位置的天气信息"""
# 实际的天气 API 调用
return {"temperature": 25, "condition": "sunny"}
# 定义工具 schema
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定位置的天气信息",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市名称,如北京、上海"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "温度单位"
}
},
"required": ["location"]
}
}
}
]
# 调用模型
response = openai.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": "北京今天天气怎么样?"}],
tools=tools,
tool_choice="auto"
)
1.2 Function Calling 的局限性
虽然 Function Calling 开启了工具调用的大门,但它存在一些明显的局限:
- 厂商锁定:不同厂商的 API 格式不统一(OpenAI、Anthropic、Google 各有差异)
- 上下文管理困难:工具执行结果需要手动注入回对话上下文
- 状态管理复杂:多轮工具调用时,状态维护容易出错
- 工具发现机制缺失:Agent 无法动态发现和加载新工具
二、ReAct 模式:推理与行动的融合
2.1 ReAct 的核心思想
ReAct(Reasoning + Acting)模式由 Google 在 2022 年提出,它将推理(Reasoning)和行动(Acting)紧密结合,让模型通过"思考-行动-观察"的循环来解决问题。
class ReActAgent:
def __init__(self, llm, tools):
self.llm = llm
self.tools = {tool.name: tool for tool in tools}
self.memory = []
def run(self, query: str, max_iterations: int = 10):
self.memory.append(f"User: {query}")
for i in range(max_iterations):
# 构建提示词
prompt = self._build_prompt()
# 调用 LLM
response = self.llm.generate(prompt)
# 解析 Thought 和 Action
thought = self._extract_thought(response)
action = self._extract_action(response)
if action:
tool_name, tool_input = self._parse_action(action)
observation = self._execute_tool(tool_name, tool_input)
self.memory.append(f"Thought: {thought}")
self.memory.append(f"Action: {action}")
self.memory.append(f"Observation: {observation}")
else:
# 没有 Action,直接返回结果
return response
return "达到最大迭代次数限制"
2.2 ReAct 的优势与挑战
优势:
- 可解释性强,每一步都有明确的推理过程
- 适合复杂的多步骤任务
- 错误恢复能力较好
挑战:
- 需要精心设计的提示词模板
- 对模型能力要求较高
- 延迟较大(需要多轮交互)
三、MCP 协议:工具调用的标准化
3.1 什么是 MCP?
MCP(Model Context Protocol)是 Anthropic 于 2024 年底推出的开放协议,旨在标准化 AI 模型与外部工具、数据源之间的交互方式。
MCP 的核心设计理念:
- 标准化:统一的工具定义和调用格式
- 可组合性:工具可以像积木一样组合使用
- 安全性:内置权限控制和沙箱机制
- 可发现性:支持工具的动态发现和注册
3.2 MCP 的架构设计
// MCP Server 定义
interface MCPServer {
name: string;
version: string;
tools: MCPTool[];
resources: MCPResource[];
}
// MCP Tool 定义
interface MCPTool {
name: string;
description: string;
inputSchema: JSONSchema;
handler: (args: any) => Promise<any>;
}
// MCP 客户端调用示例
class MCPClient {
private servers: Map<string, MCPServer> = new Map();
async connect(serverUrl: string): Promise<void> {
const server = await this.discoverServer(serverUrl);
this.servers.set(server.name, server);
}
async callTool(serverName: string, toolName: string, args: any) {
const server = this.servers.get(serverName);
if (!server) throw new Error(`Server ${serverName} not found`);
const tool = server.tools.find(t => t.name === toolName);
if (!tool) throw new Error(`Tool ${toolName} not found`);
// 参数验证
this.validateArgs(tool.inputSchema, args);
// 执行工具
return await tool.handler(args);
}
}
3.3 MCP 与 Function Calling 的对比
| 特性 | Function Calling | MCP 协议 |
|---|---|---|
| 标准化程度 | 厂商依赖 | 开放标准 |
| 工具发现 | 不支持 | 支持动态发现 |
| 上下文管理 | 手动维护 | 自动处理 |
| 安全性 | 基础 | 内置沙箱 |
| 可组合性 | 弱 | 强 |
| 生态支持 | 单一厂商 | 多厂商支持 |
四、实战:构建一个支持 MCP 的 AI Agent
4.1 项目结构
mcp-agent/
├── src/
│ ├── core/
│ │ ├── agent.ts # Agent 核心逻辑
│ │ ├── mcp-client.ts # MCP 客户端
│ │ └── tool-registry.ts # 工具注册中心
│ ├── tools/
│ │ ├── file-system.ts # 文件系统工具
│ │ ├── web-search.ts # 网络搜索工具
│ │ └── code-executor.ts # 代码执行工具
│ └── index.ts
├── mcp-servers/
│ └── filesystem-server.json
└── package.json
4.2 核心实现
// src/core/agent.ts
import { MCPClient } from './mcp-client';
import { LLMProvider } from './llm-provider';
export class AIAgent {
private mcpClient: MCPClient;
private llm: LLMProvider;
private memory: Message[] = [];
constructor(config: AgentConfig) {
this.mcpClient = new MCPClient();
this.llm = new LLMProvider(config.llm);
}
async initialize(): Promise<void> {
// 连接所有配置的 MCP Server
for (const serverUrl of this.config.mcpServers) {
await this.mcpClient.connect(serverUrl);
}
}
async run(userInput: string): Promise<string> {
this.memory.push({ role: 'user', content: userInput });
// 获取所有可用工具
const availableTools = this.mcpClient.getAllTools();
// 调用 LLM
const response = await this.llm.chat({
messages: this.memory,
tools: availableTools,
});
// 处理工具调用
if (response.toolCalls) {
for (const toolCall of response.toolCalls) {
const result = await this.mcpClient.callTool(
toolCall.serverName,
toolCall.toolName,
toolCall.arguments
);
this.memory.push({
role: 'tool',
content: JSON.stringify(result),
toolCallId: toolCall.id
});
}
// 再次调用 LLM 获取最终回复
const finalResponse = await this.llm.chat({
messages: this.memory,
tools: availableTools,
});
this.memory.push({
role: 'assistant',
content: finalResponse.content
});
return finalResponse.content;
}
this.memory.push({
role: 'assistant',
content: response.content
});
return response.content;
}
}
4.3 工具注册与发现
// src/core/tool-registry.ts
export class ToolRegistry {
private tools: Map<string, RegisteredTool> = new Map();
register(tool: RegisteredTool): void {
const key = `${tool.serverName}:${tool.name}`;
this.tools.set(key, tool);
}
discover(query?: string): RegisteredTool[] {
const allTools = Array.from(this.tools.values());
if (!query) return allTools;
// 基于描述的语义搜索
return allTools.filter(tool =>
tool.description.toLowerCase().includes(query.toLowerCase()) ||
tool.name.toLowerCase().includes(query.toLowerCase())
);
}
getTool(serverName: string, toolName: string): RegisteredTool | undefined {
return this.tools.get(`${serverName}:${toolName}`);
}
}
五、设计模式的最佳实践
5.1 工具设计的原则
- 单一职责:每个工具只做一件事,做好一件事
- 幂等性:相同的输入应该产生相同的输出
- 容错性:工具应该优雅地处理错误情况
- 可观测性:工具执行应该有详细的日志记录
5.2 错误处理策略