AI Agent 的 Tool Use 设计模式:从 Function Calling 到 MCP 协议的架构演进

5 阅读4分钟

引言

在 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 开启了工具调用的大门,但它存在一些明显的局限:

  1. 厂商锁定:不同厂商的 API 格式不统一(OpenAI、Anthropic、Google 各有差异)
  2. 上下文管理困难:工具执行结果需要手动注入回对话上下文
  3. 状态管理复杂:多轮工具调用时,状态维护容易出错
  4. 工具发现机制缺失: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 CallingMCP 协议
标准化程度厂商依赖开放标准
工具发现不支持支持动态发现
上下文管理手动维护自动处理
安全性基础内置沙箱
可组合性
生态支持单一厂商多厂商支持

四、实战:构建一个支持 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 工具设计的原则

  1. 单一职责:每个工具只做一件事,做好一件事
  2. 幂等性:相同的输入应该产生相同的输出
  3. 容错性:工具应该优雅地处理错误情况
  4. 可观测性:工具执行应该有详细的日志记录

5.2 错误处理策略