Model Context Protocol

总结摘要
Model Context Protocol (MCP) 和 OpenAI 函数调用(Function Calling) 技术的用武之地。本文将带你从零开始,用 Go 语言实现一个基于 MCP 协议与 OpenAI SDK 深度整合的 Demo,构建一个能动态发现并调用远程工具的智能对话系统

引言:从“问答机器人”到“行动型AI助手”

传统的聊天机器人大多停留在“理解-回答”的单向交互模式。而随着大模型能力的演进,我们不再满足于“知道答案”,更希望 AI 能“采取行动”——比如查询天气、发送邮件、执行数据库操作等。

这正是 Model Context Protocol (MCP)OpenAI 函数调用(Function Calling) 技术的用武之地。本文将带你从零开始,用 Go 语言实现一个基于 MCP 协议与 OpenAI SDK 深度整合的 Demo,构建一个能动态发现并调用远程工具的智能对话系统。

我们将深入剖析核心架构、关键设计模式,并最终实现一个可扩展、高可用的 AI 助手原型。

核心目标:

  • 多轮递归调用:支持 LLM 决策后多次调用工具。
  • 上下文累积:确保每一轮工具调用的结果都能被 LLM 看到。
  • 高效并发处理:利用 Go 的并发特性提升响应速度。
  • 精准流程控制:正确解析 FinishReason,避免冗余请求。

一、技术选型与核心概念

1.1 OpenAI Function Calling(函数调用)

OpenAI 的 gpt-3.5-turbo 及以上模型支持“函数调用”功能。你可以向模型描述一组函数(工具)的能力,模型会根据用户输入决定是否调用、调用哪个函数,并生成符合函数签名的参数。

1.2 Model Context Protocol (MCP)

MCP 是一种新兴的标准化协议,用于描述和暴露 LLM 可调用的“工具”(Tools)。它允许 LLM 客户端动态发现远程服务提供的功能,并安全地执行调用。

  • 核心价值
    • 动态发现:无需硬编码工具列表,运行时自动获取。
    • 解耦架构:LLM 核心与工具提供方完全分离,便于微服务部署。
    • 标准化接口:统一的 ListToolsCallTool 接口,降低集成成本。
  • 参考实现modelcontextprotocol/go-sdk

1.3 为什么选择 Go?

  • 高性能:适合高并发的 API 网关场景。
  • 强类型与并发支持sync, errgroup 等包让并发控制更安全。
  • 丰富的生态go-openai, gin, grpc 等库成熟稳定。

二、系统架构设计

我们的系统由三部分组成:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
+----------------+     +---------------------+     +------------------+
|                |     |                     |     |                  |
|   用户请求      | --> |   OpenAI LLM        | --> |   MCP 工具服务     |
| (DingTalk等)    |     | (决策与生成)        |     | (天气、数据库等)   |
|                |     |                     |     |                  |
+----------------+     +----------+----------+     +------------------+
                                  |
                                  v
                          +------------------+
                          |   MCP Manager    |
                          | (工具发现与路由)  |
                          +------------------+
  1. LLM 核心:负责理解用户意图,决定是否调用工具。
  2. MCP Manager:管理多个 MCP 服务连接,实现工具发现与负载均衡。
  3. MCP 服务:实际提供工具能力的微服务(如天气查询、数据库操作)。

三、核心代码解析

3.1 MCPManager:工具服务的“指挥官”

MCPManager 是整个系统的核心调度器,负责管理所有 MCP 服务的生命周期和工具映射。

1
2
3
4
type MCPManager struct {
	clients  []*MCPClient // 所有 MCP 客户端
	toolMap  sync.Map     // 工具-服务器映射: toolName -> []*MCPClient
}

关键方法:

  • NewMCPManager()
    从配置文件加载所有 MCP 服务地址,初始化客户端列表。

  • ConnectAll(ctx)
    使用 errgroup 并发连接所有 MCP 服务,提升启动效率。

  • buildToolMap(ctx)
    核心逻辑!遍历所有已连接的服务,调用 ListTools() 获取其提供的工具,并构建全局映射表 toolMap
    优势:支持多服务提供同一工具(负载均衡),自动去重。

  • ExecuteTool(ctx, toolName, args)
    根据 toolMap 找到提供该工具的服务,尝试调用。支持失败重试,增强系统鲁棒性。


3.2 MCPClient:MCP 服务的“通信桥梁”

MCPClient 封装了与单个 MCP 服务的通信细节。

1
2
3
4
5
6
func (m *MCPClient) Connect(ctx context.Context) error {
	client := mcp.NewClient(&mcp.Implementation{...}, nil)
	session, err := client.Connect(ctx, &mcp.StreamableClientTransport{Endpoint: m.host}, nil)
	m.session = session
	return err
}

关键方法:

  • ListTools(ctx)
    调用 MCP 的 ListTools 接口,并将返回的工具描述转换为 OpenAI 兼容的 openai.Tool 格式。这是实现协议桥接的关键一步。

  • ExecuteTool(ctx, toolName, args)
    使用 CallTool 接口执行工具调用,并内置了3次重试机制,避免因网络抖动导致失败。


3.3 LLMClient:OpenAI 的“代言人”

封装了 sashabaranov/go-openai 客户端,提供更简洁的调用接口。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func (l *LLMClient) CreateCompletion(ctx context.Context, messages []openai.ChatCompletionMessage, tools []openai.Tool) (interface{}, error) {
	req := openai.ChatCompletionRequest{
		Model:       l.model,
		Messages:    messages,
		Tools:       tools,
		ToolChoice:  "auto",
		Stream:      l.stream,
	}
	// ...
}
  • 线程安全初始化:使用 sync.OnceFunc 确保 LLMClientO 全局单例。
  • 灵活配置:支持自定义 baseURL(用于私有化部署的 LLM)。

3.4 ChatSession:对话状态的“管理者”

ChatSession 维护了完整的对话上下文(messages),并驱动整个交互流程。

核心流程 HandleUserInput

  1. 添加用户消息 → 2. 调用 LLM → 3. 处理响应
  • 若 LLM 返回文本,则直接回复用户。
  • 若 LLM 返回工具调用,则执行 handleToolCalls

buildSystemPrompt():LLM 的“操作手册”

这是最精妙的设计之一!我们动态生成系统提示,将所有可用工具的描述注入给 LLM:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
你是一个有帮助的助手可以使用以下工具:

{
  "name": "getWeather",
  "description": "获取天气情况",
  "parameters": { ... }
}

重要提示: 当你需要使用工具时必须只返回以下格式的JSON对象:
{
    "tool": "tool-name",
    "arguments": { ... }
}

优势:LLM 始终知道“我能做什么”,无需重新训练。


四、深入理解 FinishReason:LLM 的“决策信号灯”

FinishReason 是 LLM 返回响应时附带的状态码,它告诉客户端“我为什么停止生成”。正确解析它,是实现智能交互的前提。

FinishReason含义我们的处理策略
stop模型自然结束,通常已生成最终答案直接回复用户
length因达到最大 token 限制而截断⚠️ 可选择扩展上下文或警告用户
tool_calls模型决定调用一个或多个工具🔧 执行工具调用,并将结果返回给模型继续思考
function_call旧版函数调用(与 tool_calls 类似)🔧 兼容处理,同 tool_calls
content_filter内容被安全策略拦截🛡️ 向用户返回安全提示
null生成未完成(流式响应中常见)🔄 继续接收流数据

重点tool_callsfunction_call 是我们进行工具调用的触发信号。


五、核心交互流程的增强设计

我们对 ChatSession.HandleUserInput 方法进行了全面升级,使其支持递归式多轮工具调用

增强版交互流程图

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
                          +------------------+
                          |  用户输入问题     |
                          +--------+---------+
                                   |
                                   v
                          +------------------+
                          | 调用 LLM 生成响应  |
                          +--------+---------+
                                   |
         +-------------------------+-------------------------+
         |                                                   |
         v (tool_calls)                                      v (stop / length)
+---------------------+                            +---------------------+
| 执行所有工具调用      |                            | 直接回复用户          |
| - 并发执行(可选)    |                            +---------------------+
| - 累积结果到 messages |
+----------+----------+
           |
           v
   +---------------------+
   | 将结果喂回 LLM       |
   | 再次调用 HandleUserInput |
   +----------+----------+
              |
              +--------> (循环,直到 LLM 返回 stop)

六、实战演示:多轮工具调用

假设用户提问:

“请查询北京和杭州的天气,并告诉我哪个更热?”

系统执行流程:

  1. 第一轮 LLM 调用
  • LLM 分析问题,决定需要调用 getWeather 工具两次。
  • 返回 FinishReason: tool_calls,并包含两个 ToolCall
  1. 执行工具调用
  • MCPManager 并发调用两次 getWeather
  • 假设返回:
    • 北京:30°C
    • 杭州:25°C
  1. 将结果注入上下文
  • 两条 Role: tool 的消息被加入 messages
  1. 第二轮 LLM 调用(递归调用 HandleUserInput("", ctx)):
  • LLM “看到”了两个城市的天气数据。
  • 经过推理,生成最终回复:

    “北京的气温是 30°C,杭州是 25°C,因此北京更热。”

  • 返回 FinishReason: stop
  1. 回复用户
  • 系统调用 replyToUser(),将 Content 中的内容直接发送给用户。

成功实现多轮推理与决策,且在 stop 时正确终止流程!


七、设计亮点与最佳实践

  1. 动态工具发现:无需重启即可接入新服务。
  2. 高可用设计errgroup 并发连接、工具调用重试、服务健康检查。
  3. 协议桥接:无缝整合 MCP 与 OpenAI 两种协议。
  4. 可扩展架构:轻松支持数据库查询、邮件发送、内部 API 调用等更多工具。
  5. 清晰的职责分离MCPManager, ChatSession, LLMClient 各司其职。

结语

通过将 MCP 的动态工具发现能力与 OpenAI 强大的语言理解能力相结合,我们构建了一个真正“能做事”的 AI 助手。这不仅是一个 Demo,更是一种可落地的架构范式。

在微服务与 AI 融合的浪潮中,掌握这种“LLM + 工具调用”的模式,将为你打开通往下一代智能应用的大门。


代码已开源GitHub 仓库链接
欢迎 Star 与贡献