Model Context Protocol
引言:从“问答机器人”到“行动型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 及以上模型支持“函数调用”功能。你可以向模型描述一组函数(工具)的能力,模型会根据用户输入决定是否调用、调用哪个函数,并生成符合函数签名的参数。
- 核心优势:将自然语言指令转化为结构化 API 调用。
- 参考实现: sashabaranov/go-openai
1.2 Model Context Protocol (MCP)
MCP 是一种新兴的标准化协议,用于描述和暴露 LLM 可调用的“工具”(Tools)。它允许 LLM 客户端动态发现远程服务提供的功能,并安全地执行调用。
- 核心价值:
- 动态发现:无需硬编码工具列表,运行时自动获取。
- 解耦架构:LLM 核心与工具提供方完全分离,便于微服务部署。
- 标准化接口:统一的
ListTools、CallTool接口,降低集成成本。
- 参考实现: modelcontextprotocol/go-sdk
1.3 为什么选择 Go?
- 高性能:适合高并发的 API 网关场景。
- 强类型与并发支持:
sync,errgroup等包让并发控制更安全。 - 丰富的生态:
go-openai,gin,grpc等库成熟稳定。
二、系统架构设计
我们的系统由三部分组成:
| |
- LLM 核心:负责理解用户意图,决定是否调用工具。
- MCP Manager:管理多个 MCP 服务连接,实现工具发现与负载均衡。
- MCP 服务:实际提供工具能力的微服务(如天气查询、数据库操作)。
三、核心代码解析
3.1 MCPManager:工具服务的“指挥官”
MCPManager 是整个系统的核心调度器,负责管理所有 MCP 服务的生命周期和工具映射。
关键方法:
NewMCPManager()
从配置文件加载所有 MCP 服务地址,初始化客户端列表。ConnectAll(ctx)
使用errgroup并发连接所有 MCP 服务,提升启动效率。buildToolMap(ctx)
核心逻辑!遍历所有已连接的服务,调用ListTools()获取其提供的工具,并构建全局映射表toolMap。
✅ 优势:支持多服务提供同一工具(负载均衡),自动去重。ExecuteTool(ctx, toolName, args)
根据toolMap找到提供该工具的服务,尝试调用。支持失败重试,增强系统鲁棒性。
3.2 MCPClient:MCP 服务的“通信桥梁”
MCPClient 封装了与单个 MCP 服务的通信细节。
关键方法:
ListTools(ctx)
调用 MCP 的ListTools接口,并将返回的工具描述转换为 OpenAI 兼容的openai.Tool格式。这是实现协议桥接的关键一步。ExecuteTool(ctx, toolName, args)
使用CallTool接口执行工具调用,并内置了3次重试机制,避免因网络抖动导致失败。
3.3 LLMClient:OpenAI 的“代言人”
封装了 sashabaranov/go-openai 客户端,提供更简洁的调用接口。
- 线程安全初始化:使用
sync.OnceFunc确保LLMClientO全局单例。 - 灵活配置:支持自定义
baseURL(用于私有化部署的 LLM)。
3.4 ChatSession:对话状态的“管理者”
ChatSession 维护了完整的对话上下文(messages),并驱动整个交互流程。
核心流程 HandleUserInput:
- 添加用户消息 → 2. 调用 LLM → 3. 处理响应
- 若 LLM 返回文本,则直接回复用户。
- 若 LLM 返回工具调用,则执行
handleToolCalls。
buildSystemPrompt():LLM 的“操作手册”
这是最精妙的设计之一!我们动态生成系统提示,将所有可用工具的描述注入给 LLM:
✅ 优势:LLM 始终知道“我能做什么”,无需重新训练。
四、深入理解 FinishReason:LLM 的“决策信号灯”
FinishReason 是 LLM 返回响应时附带的状态码,它告诉客户端“我为什么停止生成”。正确解析它,是实现智能交互的前提。
FinishReason | 含义 | 我们的处理策略 |
|---|---|---|
stop | 模型自然结束,通常已生成最终答案 | ✅ 直接回复用户 |
length | 因达到最大 token 限制而截断 | ⚠️ 可选择扩展上下文或警告用户 |
tool_calls | 模型决定调用一个或多个工具 | 🔧 执行工具调用,并将结果返回给模型继续思考 |
function_call | 旧版函数调用(与 tool_calls 类似) | 🔧 兼容处理,同 tool_calls |
content_filter | 内容被安全策略拦截 | 🛡️ 向用户返回安全提示 |
null | 生成未完成(流式响应中常见) | 🔄 继续接收流数据 |
✅ 重点:
tool_calls和function_call是我们进行工具调用的触发信号。
五、核心交互流程的增强设计
我们对 ChatSession.HandleUserInput 方法进行了全面升级,使其支持递归式多轮工具调用。
增强版交互流程图
| |
六、实战演示:多轮工具调用
假设用户提问:
“请查询北京和杭州的天气,并告诉我哪个更热?”
系统执行流程:
- 第一轮 LLM 调用:
- LLM 分析问题,决定需要调用
getWeather工具两次。 - 返回
FinishReason: tool_calls,并包含两个ToolCall。
- 执行工具调用:
MCPManager并发调用两次getWeather。- 假设返回:
- 北京:30°C
- 杭州:25°C
- 将结果注入上下文:
- 两条
Role: tool的消息被加入messages。
- 第二轮 LLM 调用(递归调用
HandleUserInput("", ctx)):
- LLM “看到”了两个城市的天气数据。
- 经过推理,生成最终回复:
“北京的气温是 30°C,杭州是 25°C,因此北京更热。”
- 返回
FinishReason: stop。
- 回复用户:
- 系统调用
replyToUser(),将Content中的内容直接发送给用户。
✅ 成功实现多轮推理与决策,且在 stop 时正确终止流程!
七、设计亮点与最佳实践
- 动态工具发现:无需重启即可接入新服务。
- 高可用设计:
errgroup并发连接、工具调用重试、服务健康检查。 - 协议桥接:无缝整合 MCP 与 OpenAI 两种协议。
- 可扩展架构:轻松支持数据库查询、邮件发送、内部 API 调用等更多工具。
- 清晰的职责分离:
MCPManager,ChatSession,LLMClient各司其职。
结语
通过将 MCP 的动态工具发现能力与 OpenAI 强大的语言理解能力相结合,我们构建了一个真正“能做事”的 AI 助手。这不仅是一个 Demo,更是一种可落地的架构范式。
在微服务与 AI 融合的浪潮中,掌握这种“LLM + 工具调用”的模式,将为你打开通往下一代智能应用的大门。
代码已开源:
GitHub 仓库链接
欢迎 Star 与贡献!