从零开始手动实现 AI Agent(五)
目录
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 需要知道:
- 工具需要什么参数
- 每个参数的类型
- 哪些参数必填
// 示例: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 的核心要点
- LLM 不执行工具:LLM 只决定调用什么,Agent 负责执行
- Agent Loop:工具调用是一个循环,直到 LLM 返回文本
- JSON Schema:用 Schema 描述参数,让 LLM 正确传参
- 好的描述:工具描述决定 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! 🚀
更多推荐



所有评论(0)