AI Agent 进阶:Tool Use 工具调用 —— 让 Agent 拥有"手脚"

🎯 从"只会说"到"能行动"——Function Calling 让 Agent 真正能做事

前情回顾:上篇文章的局限

在上一篇文章《框架重构与 RAG 优化》中,我们实现了:

  • ✅ langchaingo 框架集成
  • ✅ Rerank 重排提升检索质量
  • ✅ 模块化的代码架构

但是,我们的 Agent 仍然只是一个"嘴炮":

用户: 帮我把机型 CPH2223 添加到数据库
Agent: 好的,您需要联系管理员或者通过后台系统添加机型...

用户: 你能帮我添加吗?
Agent: 抱歉,我只能提供信息,无法直接操作数据库。

问题:Agent 有知识,但没有行动能力。它只能"说",不能"做"。


什么是 Tool Use / Function Calling?

核心概念

Tool Use(工具调用) 也叫 Function Calling(函数调用),是让 LLM 能够调用外部函数/API 的能力。

┌─────────────────────────────────────────────────────────────┐
│                    Tool Use 概念图                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  传统 LLM:                                                  │
│  用户 ──▶ LLM ──▶ 文本回复                                   │
│              │                                              │
│              └──▶ "我无法执行这个操作..."                     │
│                                                             │
│  ─────────────────────────────────────────────────────────  │
│                                                             │
│  Tool Use LLM:                                              │
│  用户 ──▶ LLM ──▶ 判断需要工具 ──▶ 调用工具 ──▶ 获取结果      │
│              │                         │                    │
│              │                         ▼                    │
│              │                    [数据库]                   │
│              │                    [API]                     │
│              │                    [文件系统]                  │
│              │                         │                    │
│              └────────────────◀────────┘                    │
│                         │                                   │
│                         ▼                                   │
│                    生成最终回复                               │
│                                                             │
└─────────────────────────────────────────────────────────────┘

类比理解

把 Agent 想象成一个人:

能力 没有 Tool Use 有 Tool Use
大脑 ✅ 有知识、能思考 ✅ 有知识、能思考
嘴巴 ✅ 能说话 ✅ 能说话
手脚 ❌ 只能说不能做 能行动

Tool Use = 给 Agent 装上手脚


为什么需要 Tool Use?

场景 1:数据操作

用户: 把机型 CPH2223 添加到数据库

没有 Tool Use:
Agent: 您需要联系管理员...

有 Tool Use:
Agent: [调用 add_device_model 工具]
       已成功将机型 CPH2223 添加到数据库!

场景 2:信息查询

用户: 今天北京天气怎么样?

没有 Tool Use:
Agent: 我无法获取实时天气信息...

有 Tool Use:
Agent: [调用 get_weather 工具]
       北京今天晴,温度 22°C,适合户外活动!

场景 3:多步骤任务

用户: 帮我订一张明天去上海的机票

有 Tool Use:
Agent: [思考] 需要:1. 查询航班 2. 选择航班 3. 预订
       [调用 search_flights 工具] 找到 5 个航班
       [调用 book_flight 工具] 预订成功
       已为您预订明天 8:00 的航班,订单号 ABC123!

Tool Use 的工作原理

核心流程

┌─────────────────────────────────────────────────────────────┐
│                   Tool Use 工作流程                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1️⃣ 用户发送消息                                             │
│     "把机型 CPH2223 添加到数据库"                             │
│              │                                              │
│              ▼                                              │
│  2️⃣ LLM 分析消息 + 工具列表                                   │
│     "用户想添加机型,我有 add_device_model 工具可以用"         │
│              │                                              │
│              ▼                                              │
│  3️⃣ LLM 返回工具调用请求(不是文本!)                         │
│     {                                                       │
│       "tool_calls": [{                                      │
│         "name": "add_device_model",                         │
│         "arguments": {"model": "CPH2223"}                   │
│       }]                                                    │
│     }                                                       │
│              │                                              │
│              ▼                                              │
│  4️⃣ Agent 执行工具                                           │
│     add_device_model(model="CPH2223")                       │
│     → 返回 {"success": true, "message": "添加成功"}          │
│              │                                              │
│              ▼                                              │
│  5️⃣ 将工具结果发回 LLM                                        │
│     "工具执行结果: 添加成功"                                   │
│              │                                              │
│              ▼                                              │
│  6️⃣ LLM 生成最终回复                                          │
│     "已成功将机型 CPH2223 添加到数据库!"                      │
│                                                             │
└─────────────────────────────────────────────────────────────┘

关键点:LLM 不执行工具!

这是最重要的概念:

❌ 错误理解:LLM 直接调用 API
✅ 正确理解:LLM 只是"说"要调用什么,Agent 负责真正执行

LLM 的角色:决策者——决定调用哪个工具,传什么参数

Agent 的角色:执行者——真正执行工具调用

API 协议:OpenAI Function Calling 格式

// 请求:告诉 LLM 有哪些工具可用
{
  "model": "gpt-4",
  "messages": [...],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "add_device_model",
        "description": "添加设备机型到数据库",
        "parameters": {
          "type": "object",
          "properties": {
            "model": {
              "type": "string",
              "description": "机型代码,例如 CPH2223"
            }
          },
          "required": ["model"]
        }
      }
    }
  ],
  "tool_choice": "auto"  // 让 LLM 自动决定是否调用
}

// 响应:LLM 决定调用工具
{
  "choices": [{
    "message": {
      "content": null,  // 没有文本内容!
      "tool_calls": [{
        "id": "call_abc123",
        "type": "function",
        "function": {
          "name": "add_device_model",
          "arguments": "{\"model\": \"CPH2223\"}"
        }
      }]
    }
  }]
}

代码实现详解

1. 工具接口定义

首先定义工具的标准接口:

// agent/tools.go

// FunctionTool 用于 function calling
type FunctionTool interface {
    // Name 返回工具名称(唯一标识)
    Name() string
    
    // Description 返回工具描述(告诉 LLM 这个工具做什么)
    Description() string
    
    // Parameters 返回工具参数的 JSON Schema
    // 这是告诉 LLM 需要传什么参数
    Parameters() map[string]interface{}
    
    // Call 执行工具调用
    // input 是 JSON 格式的参数字符串
    Call(ctx context.Context, input string) (string, error)
}

为什么需要 JSON Schema?

LLM 需要知道:

  1. 工具需要什么参数
  2. 每个参数的类型
  3. 哪些参数必填
// 示例:add_device_model 的参数 Schema
{
    "type": "object",
    "properties": {
        "model": {
            "type": "string",
            "description": "机型代码,例如 CPH2223"
        },
        "marketName": {
            "type": "string", 
            "description": "中文营销名"
        }
    },
    "required": ["model"]  // model 必填,marketName 可选
}

2. 工具注册表

管理所有可用工具:

// agent/tools.go

// ToolRegistry 工具注册表
type ToolRegistry struct {
    tools map[string]FunctionTool
}

// NewToolRegistry 创建新的工具注册表
func NewToolRegistry() *ToolRegistry {
    return &ToolRegistry{
        tools: make(map[string]FunctionTool),
    }
}

// Register 注册工具
func (tr *ToolRegistry) Register(tool FunctionTool) {
    tr.tools[tool.Name()] = tool
}

// GetLangChainTools 获取 langchaingo 格式的工具定义
// 这是发送给 LLM 的工具列表
func (tr *ToolRegistry) GetLangChainTools() []llms.Tool {
    tools := make([]llms.Tool, 0, len(tr.tools))
    for _, tool := range tr.tools {
        tools = append(tools, llms.Tool{
            Type: "function",
            Function: &llms.FunctionDefinition{
                Name:        tool.Name(),
                Description: tool.Description(),
                Parameters:  tool.Parameters(),
            },
        })
    }
    return tools
}

// ExecuteToolCall 执行工具调用
func (tr *ToolRegistry) ExecuteToolCall(ctx context.Context, toolCall llms.ToolCall) (string, error) {
    // 找到对应的工具
    tool, ok := tr.Get(toolCall.FunctionCall.Name)
    if !ok {
        return "", fmt.Errorf("tool not found: %s", toolCall.FunctionCall.Name)
    }

    // 执行工具,传入 JSON 参数
    result, err := tool.Call(ctx, toolCall.FunctionCall.Arguments)
    if err != nil {
        return "", fmt.Errorf("tool execution failed: %w", err)
    }

    return result, nil
}

3. 实现具体工具

// tools/model_tool.go

// AddModelTool 添加机型工具
type AddModelTool struct{}

// Name 返回工具名称
func (t *AddModelTool) Name() string {
    return "add_device_model"
}

// Description 返回工具描述
// 🔑 关键:描述要清晰,让 LLM 知道什么时候该用这个工具
func (t *AddModelTool) Description() string {
    return "添加设备机型到数据库。当用户查询未解禁的机型时,可以使用此工具将机型添加到数据库中。机型代码应该去除后缀(如 IN、CN 等),例如 CPH2223IN 应该使用 CPH2223。"
}

// Parameters 返回参数 Schema
func (t *AddModelTool) Parameters() map[string]interface{} {
    return map[string]interface{}{
        "type": "object",
        "properties": map[string]interface{}{
            "model": map[string]interface{}{
                "type":        "string",
                "description": "机型代码,例如 CPH2223(去除后缀如 IN、CN 等)",
            },
            "marketName": map[string]interface{}{
                "type":        "string",
                "description": "中文营销名,例如 OPPO Find N6",
            },
            "marketNameEn": map[string]interface{}{
                "type":        "string",
                "description": "英文营销名,例如 OPPO Find N6",
            },
        },
        "required": []string{"model"},
    }
}

// Call 执行工具调用
func (t *AddModelTool) Call(ctx context.Context, input string) (string, error) {
    // 1. 解析 JSON 参数
    var args map[string]interface{}
    if err := json.Unmarshal([]byte(input), &args); err != nil {
        return "", fmt.Errorf("failed to parse arguments: %w", err)
    }

    // 2. 执行业务逻辑
    result, err := t.execute(ctx, args)
    if err != nil {
        return "", err
    }

    // 3. 返回 JSON 结果
    resultJSON, _ := json.Marshal(result)
    return string(resultJSON), nil
}

// execute 执行核心逻辑
func (t *AddModelTool) execute(ctx context.Context, args map[string]interface{}) (interface{}, error) {
    // 构建请求
    url := os.Getenv("ADD_MODEL_URL")
    bodyMap := map[string]interface{}{
        "model":        args["model"],
        "marketName":   args["marketName"],
        "marketNameEn": args["marketNameEn"],
    }
    
    // 设置默认值
    if bodyMap["marketName"] == nil {
        bodyMap["marketName"] = "营销名未解密"
    }
    
    // 发送 HTTP 请求
    jsonData, _ := json.Marshal(bodyMap)
    req, _ := http.NewRequest("POST", url, strings.NewReader(string(jsonData)))
    req.Header.Set("Content-Type", "application/json")
    
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    
    body, _ := io.ReadAll(resp.Body)
    return string(body), nil
}

4. 工具调用循环(核心)

这是 Tool Use 最核心的部分——Agent Loop(代理循环)

// agent/agent.go

// sendMessageWithTools 支持工具调用的消息发送
func (ca *ChatAgent) sendMessageWithTools(ctx context.Context) (string, error) {
    maxIterations := 10  // 防止无限循环
    iterations := 0

    for iterations < maxIterations {
        iterations++

        // 1️⃣ 获取工具定义
        langChainTools := ca.toolRegistry.GetLangChainTools()

        // 2️⃣ 获取优化后的消息
        messages := ca.memoryManager.GetOptimizedMessages()

        // 3️⃣ 调用 LLM(带工具列表)
        response, err := ca.llmProvider.GetLLM().GenerateContent(
            ctx, 
            messages, 
            llms.WithTools(langChainTools),  // 🔑 关键:传入工具列表
        )
        if err != nil {
            return "", err
        }

        choice := response.Choices[0]

        // 4️⃣ 检查是否有工具调用
        if len(choice.ToolCalls) > 0 {
            // ✅ LLM 决定调用工具
            
            // 记录工具调用
            toolCallsDesc := fmt.Sprintf("调用工具: %d 个", len(choice.ToolCalls))
            for _, tc := range choice.ToolCalls {
                toolCallsDesc += fmt.Sprintf("\n- %s(%s)", 
                    tc.FunctionCall.Name, 
                    tc.FunctionCall.Arguments)
            }
            ca.memoryManager.AddMessage("assistant", toolCallsDesc)

            // 5️⃣ 执行工具调用
            toolResults := make([]string, 0)
            for _, toolCall := range choice.ToolCalls {
                result, err := ca.toolRegistry.ExecuteToolCall(ctx, toolCall)
                if err != nil {
                    toolResults = append(toolResults, fmt.Sprintf("工具调用失败: %v", err))
                    continue
                }
                toolResults = append(toolResults, 
                    fmt.Sprintf("工具 %s 执行结果: %s", 
                        toolCall.FunctionCall.Name, result))
            }

            // 6️⃣ 将工具结果添加到消息历史
            toolResultMessage := "工具执行结果:\n" + strings.Join(toolResults, "\n")
            ca.memoryManager.AddMessage("user", toolResultMessage)

            // 优化消息
            ca.optimizeMessages()

            // 🔄 继续循环,让 LLM 基于工具结果生成回复
            
        } else if choice.Content != "" {
            // ✅ LLM 返回文本回复(不需要工具)
            ca.memoryManager.AddMessage("assistant", choice.Content)
            ca.optimizeMessages()
            return choice.Content, nil  // 🏁 结束循环
        } else {
            return "", fmt.Errorf("LLM response has no content and no tool calls")
        }
    }

    return "", fmt.Errorf("达到最大工具调用迭代次数 (%d)", maxIterations)
}

Agent Loop 图解

┌─────────────────────────────────────────────────────────────┐
│                     Agent Loop(代理循环)                    │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  开始 ──▶ 调用 LLM(带工具列表)                               │
│              │                                              │
│              ▼                                              │
│         ┌────────────┐                                      │
│         │ LLM 返回   │                                      │
│         └─────┬──────┘                                      │
│               │                                             │
│       ┌───────┴───────┐                                     │
│       │               │                                     │
│       ▼               ▼                                     │
│  有 tool_calls?    有 content?                              │
│       │               │                                     │
│       ▼               ▼                                     │
│  执行工具         返回文本                                    │
│  添加结果         ──▶ 结束 🏁                                 │
│  到消息                                                      │
│       │                                                     │
│       └──────▶ 继续循环 🔄                                   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

5. LLM Wrapper 处理工具调用响应

// agent/llm_wrapper.go

// parseResponse 解析API响应
func (w *CustomLLMWrapper) parseResponse(body []byte) (*llms.ContentResponse, error) {
    var response apiResponse
    json.Unmarshal(body, &response)

    choice := response.Choices[0]
    contentChoice := &llms.ContentChoice{
        Content: choice.Message.Content,
    }

    // 🔑 关键:解析工具调用
    if len(choice.Message.ToolCalls) > 0 {
        toolCalls := make([]llms.ToolCall, 0)
        for _, tc := range choice.Message.ToolCalls {
            toolCall := llms.ToolCall{
                ID:   tc.ID,
                Type: tc.Type,
                FunctionCall: &llms.FunctionCall{
                    Name:      tc.Function.Name,
                    Arguments: tc.Function.Arguments,
                },
            }
            toolCalls = append(toolCalls, toolCall)
        }
        contentChoice.ToolCalls = toolCalls
    }

    return &llms.ContentResponse{
        Choices: []*llms.ContentChoice{contentChoice},
    }, nil
}

6. 注册工具到 Agent

// agent/agent.go

// registerTool 注册工具到代理中
func registerTool(agent *ChatAgent) error {
    agent.toolRegistry = NewToolRegistry()

    // 注册添加机型工具
    addModelTool, err := tools.NewAddModelTool()
    if err != nil {
        return err
    }
    agent.toolRegistry.Register(addModelTool)

    // 🔑 关键:更新系统消息,告知 LLM 可以使用工具
    toolSystemMessage := `You are a helpful AI assistant. Be concise and friendly.

You have access to tools that can help users. When a user asks about a device model that is not unlocked (解禁), you can use the add_device_model tool to add it to the database. Always use tools when appropriate to help users solve their problems.`

    agent.memoryManager.Clear()
    agent.memoryManager.AddMessage("system", toolSystemMessage)

    return nil
}

实际运行效果

🤖 Chat Agent (Tool Use Enabled)
============================================================

You: 帮我把机型 CPH2223 添加到数据库

Agent: [内部流程]
       → 分析用户意图:添加机型到数据库
       → 找到匹配工具:add_device_model
       → 调用工具: add_device_model({"model": "CPH2223"})
       → 工具执行结果: {"success": true, "message": "添加成功"}
       
       已成功将机型 CPH2223 添加到数据库!如果需要添加其他机型,请告诉我。

Tool Use 的设计原则

1. 工具描述要清晰

// ❌ 不好的描述
func (t *AddModelTool) Description() string {
    return "添加机型"
}

// ✅ 好的描述
func (t *AddModelTool) Description() string {
    return "添加设备机型到数据库。当用户查询未解禁的机型时,可以使用此工具将机型添加到数据库中。机型代码应该去除后缀(如 IN、CN 等),例如 CPH2223IN 应该使用 CPH2223。"
}

好描述的要素

  • 说明工具做什么
  • 说明什么时候该用
  • 说明参数的格式要求

2. 参数 Schema 要完整

// ❌ 不好的 Schema
{
    "model": {"type": "string"}
}

// ✅ 好的 Schema
{
    "type": "object",
    "properties": {
        "model": {
            "type": "string",
            "description": "机型代码,例如 CPH2223(去除后缀如 IN、CN 等)"
        },
        "marketName": {
            "type": "string",
            "description": "中文营销名,例如 OPPO Find N6"
        }
    },
    "required": ["model"]
}

3. 系统提示词要引导

// 告诉 LLM 有工具可用
systemMessage := `You are a helpful AI assistant.

You have access to tools that can help users:
- add_device_model: 添加设备机型

When appropriate, use these tools to help users solve their problems.
Always explain what you're doing when using tools.`

4. 错误处理要友好

func (t *AddModelTool) Call(ctx context.Context, input string) (string, error) {
    // 解析失败,返回友好错误
    var args map[string]interface{}
    if err := json.Unmarshal([]byte(input), &args); err != nil {
        return "", fmt.Errorf("参数格式错误: %w", err)
    }
    
    // 必填参数检查
    if args["model"] == nil {
        return "", fmt.Errorf("缺少必填参数: model")
    }
    
    // ... 执行逻辑
}

扩展:常见的工具类型

1. 信息查询类

// 天气查询
type WeatherTool struct{}
func (t *WeatherTool) Name() string { return "get_weather" }
func (t *WeatherTool) Description() string { 
    return "查询指定城市的实时天气" 
}

// 数据库查询
type QueryDBTool struct{}
func (t *QueryDBTool) Name() string { return "query_database" }

2. 数据操作类

// 添加数据
type AddRecordTool struct{}
func (t *AddRecordTool) Name() string { return "add_record" }

// 更新数据
type UpdateRecordTool struct{}
func (t *UpdateRecordTool) Name() string { return "update_record" }

3. 系统操作类

// 发送邮件
type SendEmailTool struct{}
func (t *SendEmailTool) Name() string { return "send_email" }

// 执行脚本
type RunScriptTool struct{}
func (t *RunScriptTool) Name() string { return "run_script" }

4. 计算类

// 计算器
type CalculatorTool struct{}
func (t *CalculatorTool) Name() string { return "calculator" }
func (t *CalculatorTool) Description() string {
    return "执行数学计算,支持加减乘除、幂运算等"
}

安全考虑

1. 权限控制

// 检查用户权限
func (t *AddModelTool) Call(ctx context.Context, input string) (string, error) {
    // 从 context 获取用户信息
    user := ctx.Value("user")
    if !user.HasPermission("add_model") {
        return "", fmt.Errorf("权限不足")
    }
    // ...
}

2. 参数校验

func (t *AddModelTool) execute(args map[string]interface{}) error {
    model := args["model"].(string)
    
    // 格式校验
    if !regexp.MustCompile(`^[A-Z0-9]+$`).MatchString(model) {
        return fmt.Errorf("机型代码格式不正确")
    }
    
    // 长度校验
    if len(model) > 20 {
        return fmt.Errorf("机型代码过长")
    }
    
    // ...
}

3. 调用限制

// 限制最大迭代次数,防止无限循环
maxIterations := 10

// 限制单次调用的工具数量
if len(toolCalls) > 5 {
    return "", fmt.Errorf("单次调用工具过多")
}

完整代码结构

┌────────────────────────────────────────────────────────────┐
│                    Tool Use 代码结构                        │
├────────────────────────────────────────────────────────────┤
│                                                            │
│  agent/                                                    │
│  ├── tools.go          # 🆕 工具接口和注册表               │
│  │   ├── FunctionTool      接口定义                        │
│  │   ├── ToolRegistry      工具注册表                      │
│  │   └── ExecuteToolCall   执行工具                        │
│  │                                                         │
│  ├── types.go          # 🆕 API 响应类型                   │
│  │   ├── apiResponse       响应结构                        │
│  │   └── apiToolCall       工具调用结构                    │
│  │                                                         │
│  ├── agent.go          # ⚡ 增强:工具调用循环             │
│  │   ├── registerTool      注册工具                        │
│  │   └── sendMessageWithTools  工具调用循环                │
│  │                                                         │
│  └── llm_wrapper.go    # ⚡ 增强:解析工具调用响应         │
│      └── parseResponse     解析 tool_calls                 │
│                                                            │
│  tools/                                                    │
│  └── model_tool.go     # 🆕 具体工具实现                   │
│      └── AddModelTool      添加机型工具                    │
│                                                            │
└────────────────────────────────────────────────────────────┘

总结

本次更新解决了什么?

问题 解决方案
Agent 只能说不能做 Tool Use 让 Agent 能执行操作
无法与外部系统交互 通过工具调用 API
复杂任务需要人工干预 Agent 自动完成多步骤任务

Tool Use 的核心要点

  1. LLM 不执行工具:LLM 只决定调用什么,Agent 负责执行
  2. Agent Loop:工具调用是一个循环,直到 LLM 返回文本
  3. JSON Schema:用 Schema 描述参数,让 LLM 正确传参
  4. 好的描述:工具描述决定 LLM 是否正确使用工具

Agent 进化路线

Level 1: 基础对话 ✅
    ↓
Level 2: 智能记忆 ✅
    ↓
Level 3: RAG 知识库 ✅
    ↓
Level 4: 框架重构 + Rerank ✅
    ↓
Level 5: Tool Use 工具调用 ✅ ← 当前
    ↓
Level 6: 多 Agent 协作

🔥 扩展:Tool Use vs MCP 对比分析

什么是 MCP?

MCP(Model Context Protocol) 是 Anthropic 于 2024 年底推出的开放协议,旨在标准化 LLM 与外部数据源/工具的连接方式。

┌─────────────────────────────────────────────────────────────┐
│                    MCP 架构示意图                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ┌─────────┐     MCP 协议      ┌─────────────────────┐     │
│   │   LLM   │◀────────────────▶│    MCP Server       │     │
│   │ (Claude)│   标准化通信       │  ┌─────────────┐   │     │
│   └─────────┘                   │  │ 文件系统     │   │     │
│                                 │  │ 数据库      │   │     │
│   ┌─────────┐                   │  │ API        │   │     │
│   │  其他   │◀─────────────────▶│  │ ...        │   │     │
│   │  LLM   │                    │  └─────────────┘   │     │
│   └─────────┘                   └─────────────────────┘     │
│                                                             │
│  🔑 关键:一个 MCP Server 可以被多个 LLM 复用!              │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Tool Use vs MCP:核心区别

维度 Tool Use (Function Calling) MCP
本质 LLM 的一种能力 一种通信协议/标准
提出者 OpenAI (2023) Anthropic (2024)
层级 应用层(代码实现) 协议层(通信标准)
工具定义 在代码中硬编码 由 MCP Server 动态提供
工具发现 启动时固定 运行时动态发现
复用性 每个项目自己实现 一个 Server 多处复用
生态系统 各自为战 统一生态,社区共享

类比理解

Tool Use 就像:

每个人自己造轮子 🛞
- 你需要天气功能?自己写一个 get_weather 函数
- 你需要文件操作?自己写一个 read_file 函数
- 每个项目都要重复实现相同的工具

MCP 就像:

USB 标准 🔌
- 你需要天气功能?装一个天气 MCP Server
- 你需要文件操作?装一个文件系统 MCP Server
- 一次实现,到处使用
- 社区已经有大量现成的 Server 可用

工作流程对比

Tool Use 流程:

┌────────────────────────────────────────────────────────────┐
│                    Tool Use 流程                            │
├────────────────────────────────────────────────────────────┤
│                                                            │
│  1. 开发者在代码中定义工具(JSON Schema)                    │
│  2. 启动 Agent 时注册工具                                   │
│  3. 用户发送消息                                            │
│  4. Agent 把工具列表发给 LLM                                │
│  5. LLM 返回 tool_calls                                    │
│  6. Agent 执行工具(调用本地代码)                           │
│  7. 结果返回 LLM                                            │
│                                                            │
│  🔑 工具是"嵌入"在 Agent 代码中的                           │
│                                                            │
└────────────────────────────────────────────────────────────┘

MCP 流程:

┌────────────────────────────────────────────────────────────┐
│                    MCP 流程                                 │
├────────────────────────────────────────────────────────────┤
│                                                            │
│  1. MCP Server 独立运行(可以是任何语言实现)                │
│  2. Agent (MCP Client) 连接到 MCP Server                   │
│  3. Agent 向 Server 请求可用工具列表                        │
│  4. Server 返回工具定义(动态!)                            │
│  5. 用户发送消息                                            │
│  6. Agent 把工具列表发给 LLM                                │
│  7. LLM 返回 tool_calls                                    │
│  8. Agent 通过 MCP 协议调用 Server 的工具                   │
│  9. Server 执行并返回结果                                   │
│                                                            │
│  🔑 工具是"外部"的,通过协议通信                            │
│                                                            │
└────────────────────────────────────────────────────────────┘

MCP 的三大核心能力

MCP 不只是"远程 Function Calling",它提供三种能力:

┌─────────────────────────────────────────────────────────────┐
│                   MCP 三大核心能力                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1️⃣ Resources(资源)                                        │
│     暴露数据给 LLM 读取                                      │
│     例:文件内容、数据库记录、API 响应                        │
│                                                             │
│  2️⃣ Tools(工具)                                            │
│     让 LLM 执行操作                                          │
│     例:写文件、发邮件、调用 API                              │
│                                                             │
│  3️⃣ Prompts(提示模板)                                      │
│     预定义的提示词模板                                       │
│     例:代码审查模板、翻译模板                                │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Tool Use 只有 Tools! MCP 额外提供了 Resources 和 Prompts。

代码对比

Tool Use 实现:

// 在你的 Agent 代码中定义工具
type WeatherTool struct{}

func (t *WeatherTool) Name() string { return "get_weather" }
func (t *WeatherTool) Description() string { return "获取天气" }
func (t *WeatherTool) Call(ctx context.Context, input string) (string, error) {
    // 直接在这里实现天气查询逻辑
    return callWeatherAPI(input)
}

// 注册工具
agent.toolRegistry.Register(&WeatherTool{})

MCP 实现:

# weather_server.py - 独立的 MCP Server
from mcp.server import Server
from mcp.types import Tool

server = Server("weather-server")

@server.tool()
async def get_weather(city: str) -> str:
    """获取指定城市的天气"""
    return await call_weather_api(city)

# 启动 Server
server.run()
// Agent 端 - 只需要连接 MCP Server
client := mcp.Connect("weather-server")
tools := client.ListTools()  // 动态获取工具
result := client.CallTool("get_weather", args)

何时用 Tool Use,何时用 MCP?

场景 推荐方案 理由
简单项目,几个工具 Tool Use 实现简单,无需额外依赖
工具需要跨项目复用 MCP 一次实现,多处使用
团队协作开发 MCP 工具独立维护,解耦
使用社区工具 MCP 直接用现成的 MCP Server
需要动态添加工具 MCP 工具可热插拔
对延迟敏感 Tool Use 少一层网络通信
多语言团队 MCP Server 可以用任何语言

MCP 生态系统

MCP 已经有丰富的社区 Server:

官方 Server:
├── filesystem     # 文件系统操作
├── github         # GitHub 集成
├── postgres       # PostgreSQL 数据库
├── slack          # Slack 消息
└── memory         # 知识图谱记忆

社区 Server:
├── weather        # 天气查询
├── arxiv          # 论文搜索
├── youtube        # YouTube 字幕
├── notion         # Notion 集成
└── ... 100+ 更多

Tool Use + MCP 可以共存

它们不是互斥的!最佳实践是结合使用:

┌─────────────────────────────────────────────────────────────┐
│                   混合架构                                   │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│                    ┌─────────────┐                          │
│                    │    Agent    │                          │
│                    └──────┬──────┘                          │
│                           │                                 │
│            ┌──────────────┼──────────────┐                  │
│            │              │              │                  │
│            ▼              ▼              ▼                  │
│    ┌───────────┐  ┌───────────┐  ┌───────────────┐         │
│    │ Tool Use  │  │ Tool Use  │  │  MCP Client   │         │
│    │ 内置工具   │  │ 业务工具   │  │  连接外部     │         │
│    └───────────┘  └───────────┘  └───────┬───────┘         │
│    │ 计算器     │  │ 添加机型   │          │                │
│    │ 时间       │  │ 查询数据   │          ▼                │
│    └───────────┘  └───────────┘  ┌───────────────┐         │
│                                  │  MCP Servers  │         │
│                                  │  ┌─────────┐  │         │
│                                  │  │ GitHub  │  │         │
│                                  │  │ Notion  │  │         │
│                                  │  │ Weather │  │         │
│                                  │  └─────────┘  │         │
│                                  └───────────────┘         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

总结对比

Tool Use (Function Calling)
├── ✅ 简单直接,易于理解
├── ✅ 无需额外依赖
├── ✅ 延迟低
├── ❌ 工具复用困难
├── ❌ 工具与代码耦合
└── ❌ 每个项目重复实现

MCP (Model Context Protocol)
├── ✅ 标准化协议
├── ✅ 工具可复用
├── ✅ 丰富的社区生态
├── ✅ 工具可热插拔
├── ❌ 需要额外学习
├── ❌ 多一层网络通信
└── ❌ 生态还在发展中

一句话总结

Tool Use 是 LLM 的能力,MCP 是连接工具的协议。Tool Use 让 LLM "能"调用工具,MCP 让工具 “更容易” 被调用。


作者注:Tool Use 是 Agent 从"聊天机器人"进化为"智能助手"的关键一步。有了工具调用能力,Agent 才能真正帮用户做事,而不只是说话。记住核心公式:

真正的 Agent = LLM(大脑)+ Memory(记忆)+ RAG(知识)+ Tools(能力)

Happy Coding! 🚀

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐