1. 项目概述
在当今AI技术快速发展的背景下,构建高效、可扩展的智能问答系统成为许多开发者的需求。MCP(Model-Client-Protocol)架构作为一种新兴的设计范式,为构建复杂的AI系统提供了清晰的模块化思路。本文将详细介绍如何从零开始,基于MCP架构打造一个完整的Agentic RAG(Retrieval-Augmented Generation)系统。
这个系统结合了RAG的知识检索能力和Agent的决策规划能力,通过MCP架构实现了服务端与客户端的清晰分离。服务端负责文档处理和知识检索,客户端则专注于任务规划和用户交互。这种设计不仅提高了系统的可维护性,还使得不同组件可以独立开发和优化。
2. 核心架构设计
2.1 MCP与Agentic RAG的融合原理
MCP架构的核心思想是将系统划分为三个明确的部分:模型(Model)、客户端(Client)和协议(Protocol)。在Agentic RAG系统中,这种划分体现为:
- 模型层:负责知识处理和检索,包括文档解析、向量化存储和相似度查询
- 客户端层:处理用户交互和任务规划,决定何时以及如何使用服务端提供的功能
- 协议层:定义服务端与客户端之间的通信规范和数据格式
RAG系统通过引入外部知识来增强大语言模型的能力,这与MCP架构"提供外部工具"的理念高度契合。在传统RAG系统中,知识检索和生成往往耦合在一起,而MCP架构则将其解耦,使得每个部分可以独立优化。
2.2 系统架构详解
整个系统采用客户端-服务端模式,服务端基于LlamaIndex实现RAG功能,客户端则使用LangGraph构建智能Agent。这种分工充分利用了各自框架的优势:
- 服务端专注于:
- 文档解析和向量化
- 索引构建和管理
- 相似性搜索和知识检索
- 客户端专注于:
- 理解用户意图
- 规划查询策略
- 协调多个工具的使用
- 生成最终响应
两者通过明确定义的接口进行通信,服务端提供一系列工具(如创建索引、查询文档等),客户端则根据需要调用这些工具。
3. 服务端实现
3.1 核心工具设计
服务端提供了四个主要工具,每个工具都有明确的输入输出和功能边界:
-
create_vector_index:
- 功能:创建或加载文档向量索引
- 输入:文件路径、索引名称、分块参数等
- 输出:操作结果描述
- 特点:支持缓存机制,避免重复处理相同文档
-
query_document:
- 功能:查询事实性信息
- 输入:索引名称、查询文本、返回结果数量
- 输出:查询结果文本
- 特点:基于向量相似度检索最相关的文档片段
-
get_document_summary:
- 功能:获取文档摘要
- 输入:文件路径、查询问题
- 输出:摘要文本
- 特点:使用不同的索引类型处理总结性问题
-
list_indices:
- 功能:列出所有可用索引
- 输入:无
- 输出:索引列表
- 特点:辅助工具,帮助客户端了解可用资源
3.2 缓存机制实现
为了提高性能并减少不必要的计算,服务端实现了两级缓存:
-
文档节点缓存:
- 存储文档解析后的分块结果
- 缓存键:文档内容哈希值 + 解析参数(如chunk_size)
- 作用:避免重复解析相同内容和参数的文档
-
索引信息缓存:
- 存储已创建的向量索引
- 缓存键:索引名称
- 作用:避免重复嵌入和向量库访问
缓存策略确保了系统在以下场景中的高效性:
- 相同文档多次处理时直接使用缓存
- 修改文档内容或参数时自动重建索引
- 仅更改索引名称时复用已有解析结果
3.3 关键代码解析
以create_vector_index工具为例,其核心逻辑如下:
python复制@app.tool()
async def create_vector_index(
ctx: Context,
file_path: str,
index_name: str,
chunk_size: int = 500,
chunk_overlap: int = 50,
force_recreate: bool = False
) -> str:
# 获取存储路径和缓存路径
storage_path = f"{storage_dir}/{index_name}"
cache_path = get_cache_path(file_path, chunk_size, chunk_overlap)
# 判断是否需要重建索引
need_recreate = (
force_recreate or
not os.path.exists(storage_path) or
not os.path.exists(cache_path)
)
# 如果不需要重建且索引存在,直接返回
if os.path.exists(storage_path) and not need_recreate:
return f"索引 {index_name} 已存在且参数未变化,无需创建"
# 删除现有集合(如果存在)
try:
chroma.delete_collection(name=index_name)
except Exception as e:
logger.warning(f"删除集合时出错: {e}")
# 创建新集合和向量存储
collection = chroma.get_or_create_collection(name=index_name)
vector_store = ChromaVectorStore(chroma_collection=collection)
# 加载并分割文档
nodes = await load_and_split_document(ctx, file_path, chunk_size, chunk_overlap)
# 创建向量索引
storage_context = StorageContext.from_defaults(vector_store=vector_store)
vector_index = VectorStoreIndex(nodes, storage_context=storage_context, embed_model=embedded_model)
# 持久化索引
vector_index.storage_context.persist(persist_dir=storage_path)
return f"成功创建索引: {index_name}, 包含 {len(nodes)} 个节点"
这段代码展示了服务端如何处理索引创建请求,包括缓存检查、文档处理和索引构建等关键步骤。
4. 客户端实现
4.1 配置管理
客户端使用两个配置文件来管理系统行为:
-
mcp_config.json:
- 定义连接的MCP服务器信息
- 指定允许使用的工具列表
- 示例配置:
json复制{ "servers": { "rag_server": { "transport": "sse", "url": "http://localhost:5050/sse", "allowed_tools": ["create_vector_index", "query_document"] } } }
-
doc_config.json:
- 定义待处理的文档信息
- 包括文档描述、索引名称和处理参数
- 示例配置:
json复制{ "data/c-rag.pdf": { "description": "技术论文", "index_name": "c-rag", "chunk_size": 500 } }
4.2 Agent构建过程
客户端使用LangGraph框架构建智能Agent,主要步骤包括:
- 初始化MCP客户端连接
- 处理文档文件(创建必要索引)
- 构建Agent实例
- 进入交互循环
关键代码片段:
python复制client = MultiServerMCPClient.from_config('mcp_config.json')
async with client as mcp_client:
# 创建智能体
rag = AgenticRAGLangGraph(client=mcp_client, doc_config=doc_config)
# 处理文件(创建索引)
await rag.process_files()
# 构建Agent
await rag.build_agent()
# 交互式对话
await rag.chat_repl()
build_agent方法的核心是使用LangGraph的create_react_agent函数,将服务端工具封装为Agent可用的动作:
python复制async def build_agent(self):
# 获取服务端工具
mcp_tools = await self.client.get_tools_for_langgraph()
# 生成文档信息提示
doc_info = generate_doc_info(self.doc_config)
# 创建ReAct Agent
self.agent = create_react_agent(
model=llm,
tools=mcp_tools,
prompt=SYSTEM_PROMPT.format(doc_info_str=doc_info)
)
4.3 交互流程设计
客户端与用户的交互遵循以下流程:
- 解析用户输入,确定意图
- 根据意图选择适当的工具(可能是多个)
- 调用工具获取信息
- 综合工具结果生成最终响应
- 返回响应给用户
在这个过程中,Agent需要决定:
- 是否需要查询特定文档
- 应该使用哪个索引
- 是否需要结合多个来源的信息
- 何时使用搜索引擎补充信息
5. 系统演示与评估
5.1 端到端测试流程
-
启动服务端:
- 加载预配置的工具集
- 初始化向量数据库连接
- 开始监听客户端请求
-
启动客户端:
- 读取配置文件
- 连接服务端
- 检查并创建必要索引
- 构建Agent实例
-
交互测试:
- 简单事实查询
- 跨文档综合查询
- 摘要生成
- 索引管理
5.2 典型查询示例
-
事实性查询:
- 用户:"北京的人口是多少?"
- Agent行为:
- 识别需要查询"北京"相关文档
- 调用query_document工具
- 从结果中提取人口信息
- 生成响应
-
综合性查询:
- 用户:"比较北京和上海的气候特点"
- Agent行为:
- 识别需要查询两个城市的信息
- 分别调用query_document获取两地气候数据
- 综合结果生成比较响应
- 可能需要补充搜索引擎数据
-
摘要请求:
- 用户:"总结这篇论文的主要观点"
- Agent行为:
- 确定目标文档
- 调用get_document_summary工具
- 返回生成的摘要
5.3 性能评估
通过测试,系统展现出以下特点:
-
首次运行:
- 需要完整处理所有文档
- 索引创建时间取决于文档大小和数量
- 查询响应时间较长
-
后续运行:
- 直接使用缓存索引
- 查询响应快速
- 内存占用稳定
-
典型性能指标:
- 小型文档(<1MB)处理时间:2-5秒
- 中型文档(1-10MB)处理时间:10-30秒
- 查询响应时间:0.5-2秒
- 并发处理能力:取决于硬件配置
6. 优化与扩展
6.1 性能优化方向
-
并行处理:
- 文档解析并行化
- 批量索引创建
- 异步查询处理
-
缓存优化:
- 分级缓存策略
- 缓存自动清理
- 分布式缓存支持
-
索引优化:
- 增量更新支持
- 混合索引策略
- 压缩存储格式
6.2 功能扩展可能
-
多模态支持:
- 图像和表格处理
- 音频转录和分析
- 视频内容提取
-
高级查询能力:
- 复杂逻辑查询
- 时序数据分析
- 跨文档推理
-
安全增强:
- 访问控制
- 查询审计
- 内容过滤
6.3 部署方案改进
-
云原生支持:
- 容器化部署
- 自动扩缩容
- 服务网格集成
-
边缘计算:
- 轻量级客户端
- 离线操作支持
- 增量同步
-
混合架构:
- 公有云+私有部署
- 多区域复制
- 灾备方案
7. 实践经验分享
在实际开发和部署过程中,我们积累了一些有价值的经验:
-
文档处理方面:
- 不同格式的文档(PDF、Word、HTML)需要特定的解析器
- 分块大小对查询效果影响显著,需要根据内容类型调整
- 元数据(如标题、作者)应该保留并与内容块关联
-
索引设计方面:
- 为不同类型的查询创建专用索引(如事实查询vs摘要生成)
- 定期维护索引(重建、优化)可以保持查询性能
- 索引命名应有明确规范,便于管理和使用
-
Agent训练方面:
- 提示工程对工具使用准确性至关重要
- 需要提供足够的示例演示工具的正确用法
- 工具描述应该准确且包含足够的使用细节
-
系统监控方面:
- 记录所有工具调用的耗时和结果
- 监控缓存命中率和索引使用情况
- 设置性能警报阈值
8. 常见问题解决
在实际使用中,可能会遇到以下典型问题:
-
索引创建失败:
- 检查文档路径是否正确
- 验证文档格式是否受支持
- 确认有足够的存储空间
-
查询结果不准确:
- 调整分块大小和重叠参数
- 检查嵌入模型是否适合当前领域
- 增加返回结果数量(top-k)
-
性能下降:
- 检查缓存是否正常工作
- 监控系统资源使用情况
- 考虑索引优化或重建
-
Agent工具选择错误:
- 优化工具描述
- 提供更多使用示例
- 调整提示模板
9. 技术选型建议
基于我们的实践经验,对相关技术选型提供以下建议:
-
向量数据库:
- Chroma:轻量级,易于集成
- Pinecone:全托管,适合生产环境
- Weaviate:功能丰富,支持高级查询
-
LLM框架:
- LangChain:生态系统成熟,组件丰富
- LlamaIndex:专注RAG场景,优化良好
- Haystack:管道设计灵活,企业级功能
-
Agent框架:
- LangGraph:基于ReAct模式,适合复杂逻辑
- AutoGen:多Agent协作,场景丰富
- Semantic Kernel:微软生态集成良好
-
部署平台:
- 本地开发:Docker+GPU支持
- 小规模部署:云虚拟机+容器
- 大规模生产:Kubernetes集群
10. 总结与展望
MCP架构为构建复杂的AI系统提供了清晰的模块化思路,特别适合Agentic RAG这类需要结合多种技术的应用。通过将系统划分为服务端和客户端,我们实现了:
- 关注点分离:数据处理与任务规划解耦
- 技术灵活性:不同组件可以使用最适合的技术栈
- 独立扩展性:各组件可以根据需求单独扩展
- 维护便利性:模块边界清晰,便于调试和更新
未来,这种架构还可以进一步扩展到更复杂的场景,如多模态处理、分布式计算和边缘智能等。随着AI技术的不断发展,模块化、标准化的系统设计将变得越来越重要。
