2026-07-15-Agentic RAG 系统实践:从知识检索到自主行动
一、综述
llm的能力局限
每当将大模型应用于实际业务场景时发现,通用的基础大模型基本无法满足实际业务需求,主要有以下几方面原因:
知识截止:大模型自身的知识完全源于训练数据,而现有的主流大模型(deepseek、gpt、通义千问…)的训练集基本都是构建于网络公开的数据,对于一些实时性的、非公开的或私域的数据是没有。此外模型的训练数据有明确的时间边界,一旦问到边界之后的事情,比如"2025 年诺贝尔奖得主是谁",它便无从知晓,而 RAG 可以通过实时检索外部知识库来补上这段空白。
幻觉问题:所有的深度学习模型的底层原理都是基于数学概率,模型输出实质上是一系列数值运算,大模型也不例外,所以它经常会一本正经地胡说八道,尤其是在大模型自身不具备某一方面的知识或不擅长的任务场景。面对不确定的内容,模型倾向于"一本正经地编造"看似合理的答案,虚构出并不存在的论文或数据,而 RAG 让回答建立在检索到的真实文档之上,并且可以溯源。
领域知识缺失:企业的私有数据、专业领域的知识往往不在训练集里,像"公司上季度的销售额是多少"这样的问题模型根本答不上来,而 RAG 恰好能把企业知识库、文档系统连接进来。对于企业来说,数据安全至关重要,没有企业愿意承担数据泄露的风险,尤其是大公司,没有人将私域数据上传第三方平台进行训练会推理。这也导致完全依赖通用大模型自身能力的应用方案不得不在数据安全和效果方面进行取舍。
上下文窗口有限:即便模型支持 128K 乃至 1M token,也塞不下一个百万级文档的知识库,RAG 则通过精准检索,只把最相关的片段注入上下文。
RAG:检索增强生成
RAG,全称Retrieval Augmented Generation(检索增强生成),其工作原理为:当用户提问时,系统先从海量知识库中检索出与问题相关的实时或私有信息,并将这些信息作为参考背景与问题一起提供给大模型,让模型像做开卷考试一样,基于提供的可靠证据生成更准确、实时且可溯源的回答。
https://arxiv.org/pdf/2005.11401 Facebook AI 2020 年提出 RAG 概念的开山之作,定义了「检索器 + 生成器」联合训练范式

RAG 的工作链路
RAG 的工程链路通常分两个阶段:离线索引和在线检索生成。索引阶段把原始文档处理成可检索的数据结构;在线阶段在用户提问时完成查询理解、检索召回、上下文构建和答案生成。详细的实现会在之后进行说明。
索引阶段
输入文档:文本文件、PDF、网页、数据库记录等。
清理文档:去掉 HTML 标签、特殊字符等噪声。
增强文档:补充元数据,比如时间戳、分类标签,为后续检索提供过滤维度。
文档拆分(Chunking):用文本分割器把文档切成较小片段。这一步要兼顾语义完整性、Embedding 模型输入长度、生成模型上下文窗口和召回粒度。Chunk 太大容易引入噪声,太小又可能丢上下文。拆分策略会直接影响召回质量,详细可以看 RAG 文档处理篇。
向量化表示(Embedding Generation):通过嵌入模型将文本片段映射为语义向量,也就是高维稠密向量。常见嵌入模型包括 OpenAI 的 text-embedding-3-small / text-embedding-3-large,以及 Hugging Face 上的开源模型。
存储到向量存储或索引系统:把嵌入向量、原始内容和对应元数据存入向量存储或向量索引系统,比如 Milvus、pgvector、Elasticsearch / OpenSearch 向量检索,或基于 Faiss 构建本地向量索引。向量数据库选型、索引算法和 pgvector 实践可以看 RAG 向量库篇。
索引过程通常离线完成。团队每周跑一次定时任务,把新增和变更的文档重新索引一遍。如果是用户上传文档这类动态场景,索引也可以在线完成,直接集成到主应用里。
检索阶段
检索是在线进行的。用户提问之后,系统通常会走下面这些步骤:
接收请求:拿到用户的自然语言查询。有些系统会先做查询改写或扩充,让后续检索更容易命中。
查询向量化:用嵌入模型把查询也转成向量,这样才能和文档向量在同一个空间里比较。
信息检索(R):在向量库里做相似性搜索,把和查询向量最相关的文档片段捞出来。
上下文增强(A):把检索片段、原始问题、系统指令和引用要求组织成 Prompt,交给 LLM。
输出生成(G):LLM 输出自然语言回复,同时附上参考资料链接。
结果反馈:用户不满意时可以反馈,系统再调整 Prompt 或检索策略。有些实现也支持多轮对话来逐步完善回答。
检索效果不稳定时,问题往往出在查询改写、召回策略、排序或上下文质量上。
RAG 文档处理与切分策略
RAG 的瓶颈在文档处理。
买了最贵的向量库、调了最好的 embedding 模型,上线后答案还是一塌糊涂。排查半天才发现文档在进入索引之前那段管线
文档处理
把各种格式变成干净文本原始文档,去掉页眉页脚、水印、重复的导航文字、乱码字符,统一空白和换行,纠正 OCR 错误等
PDF 的处理是最麻烦的。扫描版 PDF 本质是图片,需要 OCR 才能提取文字;电子版 PDF 虽然有文字层,但多栏排版、跨页表格、图文混排都会让朴素的文本提取工具读出乱序甚至错位的内容。工程上通常需要版面分析 (layout analysis) 先识别出标题、正文、表格、图片各自的区域,再按阅读顺序还原文本。
Word / HTML / Markdown 相对友好,本身带有结构化的标签 (标题层级、列表、表格), 处理时也要保留这些结构信息,其层级可以作为后续切分和检索时有价值的语义边界。
Excel / 表格数据 特殊在于它的语义在于 "行列关联": 一个单元格的值只有结合它所在的行标题和列标题才有意义。粗暴地把表格转成纯文本,往往会丢失这种关联。常见做法是把每一行转成一句自然语言描述,或者保留 Markdown 表格格式,让模型能理解结构。
切分策略
切分 (Chunking) 是把长文档拆成一个个可被检索的片段。 片段太大,会包含过多无关内容和引入噪声并稀释关键信息,还会增加 embedding 和推理成本;片段太小,可能切断语义,一个片段传达不了完整的信息。如何在 "语义完整" 和 "上下文长度" 之间取得平衡,是切分的主要难题。
固定长度切分 (Fixed-size Chunking) 是最简单的做法:按固定的 token 数 (比如 256、512) 硬切,通常配合一定比例的重叠 (overlap) 来缓解边界处的语义断裂。实现简单、速度快,但完全不考虑语义,经常在句子中间、段落中间一刀切下去。
递归切分 (Recursive Chunking) 是工程上最常用的折中方案:按一个优先级列表的分隔符 (先按段落 \n\n、再按句子 。、再按逗号、最后才按字符) 递归地尝试切分,尽量在自然的语义边界处断开,同时把每个片段控制在目标长度内。参考实现可以见 LangChain 的 RecursiveCharacterTextSplitter
基于文档结构的切分 (Structure-aware Chunking) 利用文档本身的层级,基于Markdown 标题、HTML 章节、Word 大纲切分,让每个片段天然对应一个逻辑单元。对于结构清晰的技术文档、政策文件,这种方式效果往往最好。
语义切分 (Semantic Chunking) 相比更进一步:计算相邻句子的嵌入相似度,当相似度骤降 (话题发生转移) 时才切开,让每个片段内部主题一致。效果好,但需要额外的 embedding 调用来计算句子相似度,文档量一大,成本和耗时都会上升。
Small-to-Big (小块检索、大块喂给模型): 用小片段 (如单句) 做检索以保证精度,命中后把它前后的上下文一起提供给模型以保证完整性,兼顾召回精度和上下文完整。
父子分块: 建立父块与子块的层级关系,检索命中子块后返回其父块。 滑动窗口: 让相邻片段之间保留重叠,避免关键信息恰好落在切分边界上被割裂。
策略选择取决于文档类型、问答场景和成本预算。工程上一般先用递归切分或结构切分打底,遇到表格、代码、法律条款等特殊内容单独处理,再通过评测数据来验证和调参。
元数据:检索之外的第二索引
切分的时候除了存文本还要给每个片段附上元数据 —— 来源文件名、页码、章节标题、作者、创建 / 更新时间、文档类别等 —— 会在检索阶段带来巨大价值。有了元数据,就可以做基于条件的过滤 (只检索某个部门、某个时间段、某类文档的片段), 缩小搜索范围、提升精度;还可以给时间戳赋予权重,实现 "时间感知" 的检索,优先返回新鲜内容。
此外,像 "反向 HyDE" 这类技巧,会用大模型为每个片段生成若干 "该片段能回答的假设性问题", 一并存为元数据,检索时用问题去匹配问题,缩小查询与文档之间的语义鸿沟。
RAG 向量索引算法和向量数据库
文档切分并嵌入成向量之后,面对动辄百万、千万级的向量,需要在毫秒级内找出和查询最相似的那几个,这就是向量索引和向量数据库要解决的主要问题。
嵌入:把语义变成向量
嵌入 (Embedding) 是把一段文本映射成一个高维稠密向量的过程,语义相近的文本,其向量在空间中的距离也相近。检索的本质就是计算查询向量和文档向量之间的相似度。
嵌入模型分两大类。稀疏检索以 BM25 为代表,本质是关键词匹配的加权统计,擅长精确的词面命中,对专有名词、代码、ID 类查询很有效,但理解不了同义改写。稠密检索基于 BERT 等预训练语言模型,把文本编码成稠密向量,擅长捕捉语义,能理解 "如何提升性能" 和 "怎样优化速度" 是相近的意思。
近年涌现了 BGE、GTE、E5、Voyage 等一批优秀的嵌入模型,可以参考 MTEB、C-MTEB 这类公开榜单来选型。需要强调的是,没有放之四海皆准的最佳嵌入模型, 不同模型在不同语言、不同领域上的表现差异很大;在医疗、法律等术语密集的专业领域,往往还需要在自有数据上微调嵌入模型, 才能取得理想效果。
实践中越来越多系统采用混合检索 (Hybrid Retrieval) 同时跑稀疏和稠密两路,再把结果融合。稀疏保证关键词精确命中,稠密保证语义召回,两者互补,鲁棒性明显优于单路。
向量索引算法:精确与近似
如果向量只有几千条,直接暴力遍历 (Flat /brute-force) 计算与每一条的距离,既精确又够快。但当向量规模上到百万千万级,暴力搜索的延迟就无法接受了,于是需要近似最近邻 (Approximate Nearest Neighbor,ANN) 索引:用少量精度损失,换取数量级的速度提升。
IVF (倒排文件索引) 的思路是先用聚类把向量空间划分成若干个 "桶"(cell), 查询时只在与查询最接近的几个桶里搜索,而不是全库扫描。它通过 nprobe(搜索的桶数) 在精度和速度之间调节。
HNSW (分层可导航小世界图) 是目前生产环境最主流的算法之一。它把向量组织成一个多层的图结构,上层稀疏、下层稠密,查询时从上层入口点开始,逐层向下贪心地向更近的邻居跳转,像在一张 "高速公路 + 乡间小路" 的地图上导航一样快速逼近目标。HNSW 查询速度快、召回率高,代价是内存占用较大、构建较慢。它有两个关键参数
PQ (乘积量化) 是一种压缩技术,把高维向量切成若干子段分别量化编码,大幅降低内存占用,常与 IVF 组合成 IVF-PQ, 用于超大规模、内存受限的场景,代价是精度有一定损失。
中小规模追求精度用 Flat 或 HNSW; 大规模、追求速度和召回平衡用 HNSW; 超大规模、内存吃紧用 IVF-PQ。
向量数据库
向量数据库是承载上述索引算法、并提供工程化能力的系统。除了向量的存储与 ANN 检索,一个成熟的向量库还需要提供元数据过滤(把标量条件和向量检索结合,即所谓 filtered search)、增删改的实时性(知识库会持续更新)、水平扩展与高可用、混合检索支持等。
常见选型包括:Milvus (功能全面、面向大规模)、Qdrant (Rust 实现、过滤能力强)、Weaviate (自带混合检索和模块化)、Chroma (轻量、适合原型)、pgvector (在 PostgreSQL 上直接加向量能力,适合已有 PG 技术栈的团队)、以及 Pinecone 这类托管服务。选型时除了性能,还要综合考虑运维成本、生态、是否需要与现有数据栈集成。
RAG 知识库文档更新策略
知识库不是一次性建好就完事的。企业文档天天在变 —— 新增、修订、下线,如果索引不能同步更新,RAG 就会拿着过时的资料一本正经地给出错误答案。更新策略,是 RAG 绕不开的一环。
更新面临的四个主要挑战
第一,如何感知变化: 成千上万份文档,怎么知道哪些变了、哪些是新增、哪些被删了? 第二,避免全量重建: 每次有一点改动就把整个知识库重新解析、切分、嵌入、入库,成本高、耗时长、还会中断服务。 第三,数据一致性: 更新过程中如何保证检索到的始终是完整、正确的版本,不会出现新旧混杂。 第四,删除与失效: 文档下线后,对应的向量必须及时从索引里清除,否则会检索到 "僵尸知识"。
增量更新
生产系统普遍采用增量更新而非全量重建。给每份文档 (乃至每个 chunk) 维护一个唯一标识和一个内容指纹 (如内容哈希或版本号 / 更新时间戳)。当文档发生变化时,通过对比指纹识别出真正变化的部分:
- 新增文档: 解析、切分、嵌入后直接插入索引。
- 修改文档: 先删除该文档对应的所有旧 chunk 向量,再把新版本重新处理入库 (即 "先删后增")。若只是局部修改,理想情况下只重算受影响的 chunk, 但工程上按文档粒度整体替换更简单可靠。
- 删除文档: 根据文档 ID 找到其全部 chunk, 从索引中物理删除或标记失效。
生产环境建议不要 硬删除"对象,可以引入版本管理和软删除。每次更新保留版本号,检索时只返回当前有效版本;下线的文档先标记为失效 (逻辑删除), 过滤掉即可,确认无误后再定期物理清理。这样既保证了检索结果的一致性,又为回滚、审计留了余地。此外,时间感知检索在这里也能发挥作用:给较新的版本更高的检索权重,让系统在有多个相关片段时优先采信最新内容。
更新监听的常见做法有监听文件系统 / 对象存储的变更事件、订阅业务系统的 webhook 回调、或定时扫描对比内容哈希。对于飞书、Confluence 这类协作文档,通常可以通过其开放平台的变更订阅能力拿到实时通知,做到近实时同步。
按业务对实时性的要求,更新可以有不同的触发节奏。实时更新通过事件驱动 (webhook / 消息队列), 文档一变就触发处理,适合对时效敏感的场景,但工程复杂度高。定时批量更新用定时任务周期性扫描变更并批量处理,实现简单、资源可控,适合大多数对分钟级 / 小时级延迟可接受的场景。手动触发则用于重大内容调整后的一次性重建。实践中往往是几种方式的组合:核心文档实时同步,长尾文档定时批处理。
检索 (Retrieval)、生成 (Generation) 与增强 (Augmentation)
RAG 在线运行时的检索、生成、增强是 RAG 框架的三块基石。
检索 (Retrieval): 找对证据
检索的目标,是从知识库里为当前问题找出最相关的证据。除了前面讲过的嵌入和索引,在线检索环节还有大量优化空间,核心在于查询优化和结果处理。 查询优化解决的是 "用户的原始问题往往不适合直接拿去检索" 这个问题。真实场景里,用户的提问经常口语化、有歧义、信息不全,甚至一个问题里藏着好几个子问题。常见手段有:
查询重写 (Query Rewrite): 用大模型或专门的小模型把口语化、模糊的问题改写成更利于检索的形式。 查询扩展 / 多查询 (Multi-Query): 把一个问题从多个角度改写成若干个查询,并行检索后合并结果,提升召回覆盖面。 子查询分解 (Sub-Query): 把一个复杂问题拆成若干个简单子问题,分别检索,再综合作答,适合多跳、多条件的复杂问题。 HyDE (假设性文档嵌入): 先让大模型对问题生成一个 "假想的答案", 再用这个假想答案去检索 —— 因为答案和答案之间的语义相似度,往往比问题和答案之间更高。 回退提示 (Step-back Prompting): 把具体问题抽象成一个更高层的概念性问题,把原问题和回退问题的检索结果一起用于生成。 查询路由 (Query Routing) 则用于多数据源场景:先判断这个问题该去哪个知识库、走哪条流程 (需要摘要?查特定数据库?还是走 SQL?), 再分发到对应的检索路径。
增强 (Augmentation): 把证据用好
检索到证据之后,直接把一大堆片段拼进提示词会让过长的上下文导致模型 "迷失在中间"(Lost in the Middle), 只关注开头和结尾、忽略中段;冗余和噪声还会干扰生成。所以在把证据交给模型之前,需要做上下文整理 (Context Curation)。
重排 (Rerank) 是关键步骤。初步检索 (为了速度) 召回的前 K 个片段,相关性排序未必准确。用一个更精细的重排模型 (如 Cohere Rerank、bge-reranker, 或直接用大模型打分) 对这 K 个结果重新排序,把最相关的顶到最前面,再截取 top-N 喂给模型。重排在信息检索里同时扮演 "增强器" 和 "过滤器" 两个角色,是提升最终答案质量性价比很高的一步。
上下文压缩/选择 用于进一步剔除噪声。应该给模型的不是最多的上下文,而是最精准的上下文。可以用 LLMLingua 这类方法压缩提示词、去掉不重要的 token; 也可以用小模型先过滤、大模型再重排的 "过滤器 — 重排器" 范式;还可以让大模型在正式作答前先对检索内容做一轮自我批判 (critique), 把相关性差的片段筛掉。
除了这种 "单次检索" 的增强,更高级的增强过程还包括:
迭代检索 (Iterative Retrieval): 检索和生成交替进行,用已生成的内容作为新的检索线索,反复补充证据,适合需要多步推理的复杂问题 (如 ITER-RETGEN)。
递归检索 (Recursive Retrieval): 根据上一轮检索结果不断优化查询,逐步收敛到最相关的信息;常与多跳检索结合,层层深入 (如 IRCoT 用思维链引导检索)。 自适应检索 (Adaptive Retrieval): 让模型自己判断 "要不要检索、什么时候检索、检索什么"。比如 FLARE 监测生成过程的置信度,置信度低于阈值时才触发检索;Self-RAG 引入 "反思 token", 让模型自主决定检索时机并对自身输出做批判。这类方法让 RAG 从固定流程走向按需触发,更接近智能体 (Agent) 的工作方式。
生成 (Generation): 基于证据作答
生成阶段会把整理好的证据和原始问题组装成最终提示词交给大模型作答。这里有几个工程要点。
提示词工程:要明确指示模型 "仅依据提供的资料作答"" 如果资料里没有答案就明确说不知道,不要编造 ""在答案中标注引用出处", 这些约束能显著降低幻觉、提升可信度和可追溯性。
忠实度 (Faithfulness) :模型的回答必须忠于检索到的上下文,不能自相矛盾,也不能脱离证据自由发挥。与之配套的还有答案相关性(是否切中问题) 和上下文相关性(检索到的资料是否真的相关)。
对于本地部署的模型,还可以对生成器做微调, 让它更擅长基于检索内容作答、更贴合特定的输出格式和风格;甚至可以让检索器和生成器协同微调(如 RA-DIT 用 KL 散度对齐两者的偏好), 让整条链路配合得更好。
三者的协同
检索、生成、增强是相互结合的整体。检索决定了证据的质量上限,增强决定了证据能被多好地利用,生成决定了最终呈现。任何一环拉胯,整体效果都会被拖累 —— 检索召回一堆噪声,再强的模型也救不回来;工程检索很准但不做重排和压缩,长上下文又会让模型迷失;前两步都做好了,提示词和生成约束不到位,照样会幻觉。好的 RAG 系统是这三者在具体业务场景下反复调优、彼此适配的结果。
评估与落地
RAG 系统好不好要靠科学的评估。评估通常分两个维度:检索质量(用命中率 Hit Rate、MRR、NDCG 等衡量召回的准不准) 和生成质量(用忠实度、答案相关性、上下文相关性等衡量答得好不好)。业界也已经有 RAGAS、ARES、TruLens 等自动化评估工具,以及 RGB、CRUD 等基准,可以用来系统化地度量噪声鲁棒性、负样本拒绝、信息整合、反事实鲁棒性等能力。
工程落地的环节要注意几点: 先把上游做干净。文档解析和切分是地基,地基歪了上层怎么调都白搭,优先投入在这里回报最高。从简单开始,按需加复杂度。先用递归切分 + HNSW + 混合检索 + 重排跑通一条基线,再针对评测暴露的问题逐点优化,不要一上来就堆迭代检索、自适应检索这些重武器。
建立评测集。比如精心标注的问答对,每次改动都用它回归,才能知道是效果真变好还是错觉。
重视更新与运维。知识库会持续变化,更新策略和监控要在设计之初就纳入考虑,减少上线后打补丁。
此外RAG 与微调可以结合。RAG 负责动态、可溯源的知识注入,微调负责固化领域语感和输出风格,两者互补,很多成熟系统是二者的组合拳。 RAG 的价值,在于用一种低成本、可更新、可追溯的方式,把大模型的通用能力和企业的私域知识、实时信息接了起来。它不是一个一劳永逸的银弹,而是一整套需要在文档处理、索引、检索、生成、增强、更新、评估各个环节持续打磨的工程体系。把每一环都做扎实,才能真正把它从惊艳的 Demo, 变成可靠的生产力。
MCP:LLM 与后端数据和工具对话的协议层
RAG解决的是"如何把外部知识喂给大模型"的问题,但是如果把视角从"知识"扩展到"能力"——让大模型不仅能读到数据,还能调用真实的业务功能时,就需要一个标准化的连接层,于是我们需要 MCP
MCP (Model Context Protocol,模型上下文协议)起源于 2024 年 11 月 25 日 Anthropic 发布的文章:Introducing the Model Context Protocol。其定义了应用程序和 AI 模型之间交换上下文信息的方式。这使得开发者能够以一致的方式将各种数据源、工具和功能连接到 AI 模型(一个中间协议层),就像 USB-C 让不同设备能够通过相同的接口连接一样。MCP 的目标是创建一个通用标准,使 AI 应用程序的开发和集成变得更加简单和统一。

用最简单的场景举例,设想我们的系统里有这样几个工具:一个用来获取库存订单数据,一个用来获取天气数据。这些工具原本只是后端内部的普通函数或接口,而 MCP 层的作用,就是把它们统一封装、暴露成大模型能够理解和请求的标准工具。于是后端就多了一个"工具层",里面登记着所有可供 LLM 调用的能力。
无论后端用的是 Java Spring 还是 Python Flask,技术栈差异并不重要,我们希望在后端沉淀出一批可被大模型调用的工具(Tools)。
MCP 的调用流程

以一个用户查询为例,整个链路是这样流转的:
- 前端发起请求。前端识别出"这是一个需要调用大模型的查询",于是把用户的查询发送出去,准备交给 LLM 处理。
- 后端把查询连同工具清单一起交给 LLM。这一步是关键:发给模型的不只是查询本身,还附带了 MCP 服务器当前可用的工具列表。这样 LLM 才能"知道"自己手上有哪些牌可打——比如它看到有一个"获取天气数据"的工具。
- LLM 判断并向 MCP 请求工具。如果模型判断回答这个问题需要天气数据,它就会向 MCP 层发出请求:"请给我调用天气数据工具的权限。"
- MCP 层执行工具并回传数据。MCP 层根据登记的功能真正去执行这个工具,取到数据后再把结果交回给 LLM。
- LLM 处理数据并生成回答。模型基于拿到的真实数据组织出最终回答。
另外,LLM 与 MCP 之间是一种双向通信:模型可以反复地"要工具、拿结果、再要工具",直到它认为信息足够为止。所有操作完成后,响应会沿原路返回——从 LLM 回到后端,再传到前端展示给用户。整个闭环可以概括为:
前端(发起查询) │ 查询 ▼后端 ──── 查询 + 工具清单 ────► LLM ▲ │ ①请求某个工具 │ ▼ │ MCP 层(工具:库存订单/天气数据…) │ │ ②执行工具,回传数据 │ ▼ │ ◄──── 最终响应 ──── LLM(基于真实数据生成) ▼前端(展示结果)MCP 服务器是如何运作的
后端集成 MCP 背后有不少细节要处理。在一个最小可用 (MVP) 的实现里,主要围绕四个步骤展开:
注册工具
这一步是让模型就知道自己能做什么,所以初始阶段必须先把工具登记清楚。我们可以向 MCP 服务器注册大量工具,它们的来源五花八门 —— 可能是数据库里的数据,可能是某种数组或数据的加工处理,也可能是来自第三方 API 的实时数据。具体做法就是定义一个个网关函数,比如 "获取价格" 或 "查询数据库", 然后由 MCP 服务器把这些能力暴露给 AI 模型。
设置传输通道
这一步是在 LLM 与服务器之间建立起可靠的通信链路。先创建端点(通常基于 HTTP ),让大模型能通过一套标准化的协议与服务器通信。传输通道可以支持会话 (有状态), 也可以是无会话 (无状态) 的,这里可以看具体需求而定。
处理请求
服务器需要接收模型传来的工具调用请求, 执行对应的业务逻辑,最后把一个结构化的响应返回给模型。以 "获取天气" 的函数为例,无论它底层调用的是什么功能还是实时数据 API, 都要完成 "执行 — 取数 — 整理" 这套动作。这里有个关键点:LLM 对返回格式是有要求的,所以取到的数据不能原样丢回去,必须按模型能接受的特定格式组织好再传递。这套请求处理机制使得 LLM 与服务器之间才能进行双向交互,模型要什么工具,服务器就处理什么,来回协商,而不是一次性把所有事情做完。
把结果呈现给用户
当模型基于工具返回的数据生成了最终响应,还要把它和前端整合起来。无论前端是 Angular 还是 React, 或是别的技术栈,目的都是把结果流畅、无缝地展示给用户。至此,一条完整的 MCP 工作流就走完了:注册工具 → 设置传输 → 处理请求 → 展示结果。
以天气 MCP 服务器为例,假设我们有一个天气 MCP 服务器,它对外提供一个网关工具。当用户问出 "帮我看看天气" 这类问题时,大模型会判断需要外部数据,于是向 MCP 请求调用这个网关工具;网关在服务器侧完成处理,取到数据后整理成结构化响应,再回传给大模型;大模型拿到结果后,用自然语言把它组织出来 —— 比如 "伦敦现在气温多少度"—— 最终呈现在用户面前。整个过程里,大模型负责理解和表达,而实时数据的获取则完全依赖 MCP 工具, 两者各司其职,配合完成了一次看似简单、实则跨越了多层的问答。
RAG 与 MCP 的结合
"静态、稳定、不常变" 的知识都是 RAG 的主场 —— 公司文档、产品目录、常见问题 (FAQ) 与售后文章、互联网知识库、历史记录等等。但一套架构再好也有边界,一旦需求需要从 API、数据库或文件里动态检索,RAG 就触及了它的局限:处理的是已被索引的静态内容,拿不到实时数据。这时候 MCP 就该登场与 RAG 配合使用,由它去调用外部 API、访问动态数据源,调度多个工具、编排 GPT、Gemini 等多个 LLM,承担所有工具与集成的中枢。

假设用户提出"把我的销售报告和今天的实时汇率对比一下。" 这个问题同时踩中了两条数据线:一条是存放在文档里的历史报告,另一条是随时变动的实时数据。设计的系统会这样拆解并处理:
首先,RAG 负责取回文档数据。应用通过检索流程,从文档存储 (比如 Firebase 里的文档) 中找到并召回相关的销售报告,这一步 RAG 的检索与重排 (re-ranking) 能力就发挥了作用,确保拿到的是最相关的内容。接着 MCP 工具负责取回实时数据。系统调用一个 MCP 工具,通过外部 API 获取当前的实时汇率。到这里两路数据都已就位。
然后进入上下文整合 (Context Integration)环节。系统把来自两个数据源的信息合并成一份完整的上下文,再交给大模型 (Deepseek,GPT) 去处理。模型同时看到了 "历史销售报告" 和 "实时汇率" ,才能给出一个有依据、贴合当下的响应, 准确性也随之大幅提升。
这套架构对任何想构建 AI 系统、既要用好自有数据、又要接入实时数据的公司来说都极具价值。落地到产品,用户只需和 AI 助手对话,就能拿到分散在文档、数据库、实时接口里的全部数据,提高结果的可靠性。
从"回答"到"行动":Agentic RAG 系统的诞生
Agentic RAG系统(智能体系统),即 RAG 与 MCP 协同后诞生的、会自主决策与行动的应用。
当 RAG 和 MCP 协同运转时,应用就从被动应答进化到自主做决策、规划合理的步骤,并自动选用正确的工具的闭环。模型会自己判断"何时该从文档中检索信息",会综合所有已知事实进行推理,把多个工作流串联起来,完成一连串多步推理(multi-step reasoning)。它会通过 MCP 挑选正确的工具去执行动作;它还会维护对话历史,在需要时回溯过往的信息。类似O.G.A.S ,会主动采取行动去找到正确答案。这正是生成式智能体系统的内核。
很多产品早已把它落地。OpenAI 推出了名为 Atlas 的浏览器,基于它可以做网页搜索、获取实时数据,还能执行代码、进行多模型推理——检索、工具、推理在一个产品里被打通。GitHub Copilot 能在代码编辑器、终端和仓库里为你提供带上下文的编码帮助,大多数时候都能给出相当准确的结果。Gemini 通过底层的工具层( MCP 式的能力层)去获取实时信息——本质上是借助谷歌搜索拿到最新内容——并执行较复杂的操作,其内部集成了大量功能。可以预见,随着后续版本迭代,这类能力只会越来越强。智能体应用是未来,理解一个会思考、会行动的系统是怎样炼成的,并把这份认知应用到自己的系统里在现代软件工程中相当重要。
这些产品把检索、工具调用与自主推理结合在了一起,并且已经跑在真实的生产环境里。这一套完全可以在自己的应用中复刻:让 AI 查询我们自有的数据,只用一次提问,就从中提取、汇总出更多信息。
过去这几年,行业发生了巨大的变化,而这个方向正是需要主动构建能力、去适应市场的关键所在。如今相当一部分代码已经是借助 AI 完成的,如何与 AI 协作已经成为一项必备技能,不过当然要注意一下AI的安全风险和隐私风险。
从理论到实践
目前有一套把已有 RAG 检索能力封装成 MCP(Model Context Protocol)服务的完整系统设计。
让大模型 / Agent 通过标准的 MCP 协议把"知识检索"当作一个可发现、可调用的工具来使用。技术选型上采用 Go 与 Node 双语言分工——Go 承担高并发、低延迟的检索内核,Node 承担 MCP 协议接入层与工具编排。
一、为什么要把 RAG 做成 MCP 服务
在最初的 RAG 系统里,检索能力往往是以一个私有 HTTP 接口的形式存在的,谁要用就去对接它的 URL、参数、鉴权。这种做法在单一应用里没问题,但一旦接入方变多——不同的 Agent、不同的大模型客户端、IDE 插件、内部工作流——问题就暴露出来了:每个接入方都要重复实现一遍对接逻辑,参数格式各不相同,升级一次接口所有下游都要跟着改,模型也无法"自己发现"有哪些检索能力可用。
MCP 把这层耦合标准化,让 RAG 从接口变成一个能被模型自主发现和调用的标准工具。它是一套让大模型与外部工具、数据源通信的开放协议:工具方(Server)把自己的能力按统一格式注册出来,模型方(Client / Host)通过协议自动发现这些工具、按标准调用、拿回结构化结果。把 RAG 封装成 MCP 服务之后,任何支持 MCP 的宿主——无论是 Claude、还是自研 Agent 框架、还是 IDE——都能"即插即用"地获得知识检索能力,不需要为每个接入方单独适配。
二、整体架构
整个系统自上而下分为四层
┌─────────────────────────────────────────────────────────┐│ MCP Host / Client (Claude、自研 Agent、IDE 插件 … ) │└───────────────────────────┬─────────────────────────────┘ │ MCP 协议 (stdio / SSE / Streamable HTTP)┌───────────────────────────▼─────────────────────────────┐│ ① MCP 接入层 (Node + TypeScript) ││ · 实现 MCP Server,注册 tools / resources / prompts ││ · 协议解析、鉴权、参数校验、结果格式化 ││ · 会话管理、流式返回 │└───────────────────────────┬─────────────────────────────┘ │ gRPC (内部高性能 RPC)┌───────────────────────────▼─────────────────────────────┐│ ② 编排与工具层 (Node,也可下沉到 Go) ││ · 查询改写 / 扩展 / 路由 ││ · 多工具编排:检索 → 重排 → 上下文压缩 ││ · 调用大模型做 query rewrite / rerank(可选) │└───────────────────────────┬─────────────────────────────┘ │ gRPC┌───────────────────────────▼─────────────────────────────┐│ ③ 检索内核层 (Go) ││ · 向量检索(ANN)、BM25 稀疏检索、混合检索融合 ││ · Embedding 调用与缓存、Rerank 调度 ││ · 高并发、连接池、批处理、限流熔断 │└───────────────────────────┬─────────────────────────────┘ │┌───────────────────────────▼─────────────────────────────┐│ ④ 存储与数据层 ││ · 向量数据库(Milvus / Qdrant …) ││ · 元数据库(PostgreSQL)、缓存(Redis) ││ · 对象存储(原始文档)、消息队列(离线更新) │└─────────────────────────────────────────────────────────┘MCP 协议本身是 IO 密集型的,涉及大量 JSON-RPC 消息收发、流式传输、与模型的交互,Node 的异步事件模型和 TypeScript 生态(官方 MCP SDK 就是 TS 优先)非常契合;而向量检索、BM25 打分、结果融合这些是计算密集、并发密集的活儿,Go 的 goroutine、静态编译、低 GC 开销让它在高 QPS 下依然稳定低延迟。两层之间用 gRPC 通信,既有强类型契约,又有高性能。
三、MCP 接入层设计(Node + TypeScript)
接入层是整个系统对外的门面,它把内部的检索能力翻译成 MCP 协议规定的三类原语
工具定义
把 RAG 能力拆成几个语义清晰的工具暴露出去,这样模型能更精准地按意图选择工具:
rag_search:输入自然语言查询,返回带出处的相关片段。参数包括query(必填)、top_k、filters(元数据过滤条件,如部门 / 时间范围 / 文档类型)、retrieval_mode(vector / bm25 / hybrid)。rag_answer:在rag_search基础上进一步让服务侧完成"检索 + 生成"闭环,直接返回一段基于证据的答案 + 引用列表。适合宿主不想自己拼提示词的场景。list_collections:让模型发现当前有哪些知识库 / 集合可查,便于路由。get_document:按文档 ID 或 chunk ID 取回完整原文,用于让模型看到某个片段的上下文全貌。 每个工具都要提供清晰的description和 JSON Schema 化的入参定义——这一点至关重要,因为模型是靠 description 来判断该不该调用、怎么调用这个工具的。描述写得含糊,模型就用不好。
传输通道
MCP 支持多种传输方式,接入层应同时支持:
- stdio:适合本地场景(IDE 插件、桌面 Agent),宿主直接以子进程方式拉起 MCP Server,通过标准输入输出通信,零网络开销。
- Streamable HTTP / SSE:适合远程、多租户、云端部署场景。服务常驻,多个客户端通过 HTTP 连接,支持服务端流式推送(检索结果可以边算边返回)。 生产环境以 Streamable HTTP 为主,本地开发和轻量集成保留 stdio。
鉴权、校验与会话
接入层还要承担几件"守门"的工作。
鉴权@modelcontextprotocol/sdk,它把协议细节都封装好了,我们只需专注于工具的注册和实现。
四、编排与工具层设计
这一层是"检索前 + 检索后"优化的落点,决定了给到模型的证据质量。它可以放在 Node(靠近协议、方便调模型),也可以下沉到 Go(追求性能),推荐初期放 Node,链路成熟后把重计算部分下沉。
一个 rag_search 请求进来后,编排层典型地会走这样一条链路。首先是查询理解与改写:用户的原始问题往往口语化、有歧义,这里可以做查询重写、多查询扩展(把一个问题从多角度改写成几条并行检索)、或子查询分解(把复杂问题拆成几个简单问题)。如果场景需要,还可以做 HyDE——先让模型生成一个假想答案,再拿它去检索,通常召回更准。
接着是路由:如果有多个知识库,先判断这个问题该查哪个集合,避免全库乱撞。然后调用检索内核拿到初步候选。拿到候选后进入检索后处理:用 rerank 模型对候选重排,把最相关的顶到前面;再做上下文压缩,剔除冗余、控制长度,避免模型"迷失在中间"。最后把整理好的证据(或证据 + 生成的答案)返回给接入层。
这条链路里,查询改写和 rerank 可能需要调用大模型或专门的小模型,属于 IO 密集操作,这也是为什么编排层适合放在 Node。
五、检索内核层设计(Go)
检索内核是系统的性能担当,它不关心 MCP 协议,只专注一件事:给定一个(或一组)查询,又快又准地返回候选片段。用 Go 实现的理由是它在高并发下的稳定性和低延迟表现。
内核对外(对编排层)暴露 gRPC 接口,内部负责:向量检索(调用向量库做 ANN 搜索)、稀疏检索(BM25 关键词打分)、混合检索融合(把稠密和稀疏两路结果用 RRF 等算法融合排序)、Embedding 调用(把 query 编码成向量,带缓存避免重复计算)、以及可选的 rerank 调度。
高并发场景下,每个检索请求可能要并行发起"向量检索 + BM25 检索 + embedding 计算"多个子任务,Go 的 goroutine 让这种并行编排写起来简单、开销极低。同时,静态编译产物部署方便、内存占用可控、GC 停顿短,在 QPS 冲高时延迟曲线比解释型语言平滑得多。此外,向量库(如 Milvus、Qdrant)大多提供 Go SDK,连接池、批量查询、超时控制都好实现。
内核层要把稳定性做扎实:对下游(向量库、embedding 服务、rerank 服务)都要有连接池、超时、限流、熔断;对 embedding 结果做缓存(相同 query 或相同文本不重复算);支持批处理(把并发到达的多个 embedding 请求攒成一批调用,提升吞吐);并暴露完善的指标(检索延迟、召回数、缓存命中率)供监控。
六、离线更新链路
在线检索之外,还有一条离线的知识库更新链路,它决定了知识的新鲜度。这条链路可以独立部署,用 Node 或 Go 都行(文档解析生态 Python/Node 更丰富,重计算部分可用 Go)。 流程是:文档源发生变化(新增 / 修改 / 删除)时,通过 webhook 或定时扫描感知变更,把变更事件投递到消息队列;消费者做文档解析、清洗、切分(chunking)、调用 embedding 编码成向量,再写入向量库和元数据库。更新采用增量方式而非全量重建:给每个文档维护唯一 ID 和内容指纹,修改时"先删旧 chunk、再写新 chunk",删除时按文档 ID 清除对应向量,并配合软删除和版本管理保证一致性。这条链路和在线检索通过共享的存储层解耦,互不阻塞。
七、技术选型汇总
| 层 / 组件 | 选型 | 理由 |
|---|---|---|
| MCP 接入层 | Node + TypeScript + @modelcontextprotocol/sdk | 官方 SDK 为 TS 优先,协议/IO 密集,异步模型契合 |
| 参数校验 | zod | 与 TS 无缝、Schema 即校验 |
| 编排层 | Node(重计算部分可下沉 Go) | 需频繁调模型做改写/rerank,IO 密集 |
| 检索内核 | Go | 高并发、低延迟、GC 友好、向量库 Go SDK 完善 |
| 内部通信 | gRPC + Protobuf | 强类型契约、高性能、跨语言(Go↔Node) |
| 向量数据库 | Milvus / Qdrant | 大规模 ANN、元数据过滤、水平扩展 |
| 元数据 / 权限 | PostgreSQL(可加 pgvector) | 事务、复杂过滤、成熟运维 |
| 缓存 | Redis | embedding / 检索结果缓存 |
| 消息队列 | Kafka / RocketMQ | 离线更新解耦、削峰 |
| 对象存储 | S3 兼容存储 | 原始文档存放 |
| 可观测 | OpenTelemetry + Prometheus + Grafana | 全链路追踪与指标监控 |
Roadmap
MVPrag_search 一个工具的 MCP Server,内核直连向量库做纯向量检索,用 stdio 在本地跑通,验证协议链路和检索效果。
补齐质量:加入混合检索、rerank、查询改写、元数据过滤和权限,把检索质量做上去,切到 Streamable HTTP 支持远程多客户端。
做生产化:补齐离线增量更新链路、缓存、限流熔断、全链路可观测,压测调优,并把编排层的重计算部分按需下沉到 Go 服务
相关技术细节可以看后面的博客
分享到社交平台
将本文分享给你的朋友们
Zhongye