ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

AI Agent 知识获取管道实战:RAG 稠密与稀疏嵌入混合检索落地指南

AI Agent 知识获取管道实战:RAG 稠密与稀疏嵌入混合检索落地指南 1. 为什么知识获取管道是 AI Agent 落地的第一道坎做 AI Agent 开发的人都有一个共识模型能力再强如果喂给它的上下文是错的、旧的、不完整的那输出基本没法看。我在过去一年里帮三个团队搭过 Agent 系统踩得最多的坑不是模型选型也不是工具调用编排而是知识获取管道这一段。说白了Agent 要能干活首先得能拿到对的信息而 RAGRetrieval-Augmented Generation检索增强生成就是目前解决这个问题最成熟、最通用的方案。这一篇是 AI Agent 系列的第四篇前面聊了 Agent 的基本架构、工具调用和记忆机制这次专门把 RAG 这条知识获取管道拆开讲。核心关键词包括AI Agent、RAG、知识获取管道、稠密嵌入、稀疏嵌入。如果你正在从 0 到 1 搭建 AI Agent或者想给自己的 Agent 接一个本地知识库这篇内容基本能覆盖你从设计到落地的全部关键环节。RAG 解决的核心问题其实就一句话让模型在生成回答之前先去外部知识库里捞一把相关的资料把捞到的内容塞进上下文再让模型基于这些资料回答。听起来简单但真正做起来从文档切分、嵌入模型选型、向量库搭建、检索策略设计到重排序每一步都有讲究。我见过太多项目在 Demo 阶段效果惊艳一上生产就拉胯问题几乎都出在管道设计上。这篇文章适合三类人一是刚接触 AI Agent 开发、想搞明白 RAG 到底怎么跑通的初学者二是已经用过 LangChain 或 Spring AI 的 RAG 模块、但效果不稳定的开发者三是需要给企业级 Java AI Agent 应用平台设计知识管道的架构师。我会尽量用从业者的视角把每个环节的“为什么”讲清楚而不是只丢一堆 API 调用。2. RAG 知识获取管道的整体设计与思路拆解2.1 一条完整的 RAG 管道到底包含哪些环节很多人对 RAG 的理解停留在“文档丢进向量库问题来了就检索”这个层面但真正生产级的管道要复杂得多。我习惯把它拆成六个阶段文档采集、文档解析与清洗、切分Chunking、嵌入Embedding、存储与索引、检索与重排序。这六个阶段环环相扣任何一个环节出问题最终效果都会打折。文档采集阶段要考虑数据源的多样性可能是 PDF、Word、Markdown、数据库记录、网页内容甚至是 ERP 系统里的结构化数据。解析阶段要把这些异构内容统一成纯文本同时保留必要的结构信息比如标题层级、表格、列表。切分阶段决定了每个知识块的大小和边界直接影响检索粒度。嵌入阶段把文本转成向量这里就涉及稠密嵌入和稀疏嵌入的选择。存储阶段要选合适的向量库并设计索引。检索阶段则是把用户问题转成向量去库里找最相似的 Top-K 内容再经过重排序精筛。我个人的经验是管道设计的第一原则是“可观测”。每个阶段都要能单独测试、单独评估否则出了问题你根本不知道是切分切坏了还是嵌入模型不行。很多团队一上来就把整条链路串起来跑结果效果不好时完全无从下手。2.2 为什么选择 RAG 而不是微调这是我被问得最多的问题。微调Fine-tuning和 RAG 解决的是两类不同的问题。微调改变的是模型的“行为模式”和“表达风格”适合让模型学会某种特定的输出格式或领域语言习惯。而 RAG 解决的是“知识注入”问题适合让模型访问它训练时没见过或已经过时的信息。从工程角度看RAG 有几个微调比不了的优势。第一是知识更新成本低你只需要更新知识库里的文档不用重新训练模型。第二是可溯源检索到的内容可以作为引用展示给用户出了问题能定位。第三是成本可控微调一次大模型的费用和时间成本都不低而 RAG 的增量更新几乎可以做到实时。当然 RAG 也有短板它对检索质量高度依赖如果知识库本身质量差或者检索策略设计得烂效果会很差。而且 RAG 会增加推理时的上下文长度带来额外的 token 消耗和延迟。所以我的建议是知识频繁变化、需要溯源、预算有限的场景优先用 RAG需要改变模型输出风格、对延迟极度敏感的场景考虑微调。两者也可以结合先用微调让模型适应领域语言再用 RAG 注入实时知识。2.3 稠密嵌入与稀疏嵌入两条腿走路才稳这是本篇的核心技术点之一。稠密嵌入Dense Embedding把文本映射成一个固定长度的稠密向量比如 768 维或 1024 维每个维度都是浮点数。它的优势是能捕捉语义相似性即使用户用的词和文档里的词完全不一样只要意思接近就能检索到。比如用户问“怎么退款”文档里写的是“申请退货流程”稠密嵌入能把它们匹配上。稀疏嵌入Sparse Embedding则是高维稀疏向量大部分维度是零只有少数维度有值典型代表就是 BM25 和 SPLADE。它的优势是精确匹配关键词对于专有名词、产品型号、代码标识符这类内容特别有效。比如用户搜“ERR-4032”稀疏嵌入能精准命中而稠密嵌入可能会把它和“ERR-4033”混在一起。我在实际项目里的做法是混合检索Hybrid Search同时跑稠密和稀疏两路检索然后用加权融合或倒数排名融合RRF把结果合并。实测下来混合检索在大多数场景下比单用任何一种都能提升 10% 到 20% 的召回率。这个提升在知识库内容异构、既有自然语言描述又有结构化字段的场景下尤其明显。3. 核心细节解析与实操要点3.1 文档切分切得好比嵌入模型选得好更重要切分是 RAG 管道里最容易被低估的环节。我见过一个项目嵌入模型用的是当时最好的但检索效果一直上不去最后发现问题出在切分上——他们按固定 512 个字符硬切把完整的表格和代码块切得七零八落检索出来的内容根本没法用。切分的核心目标是保证每个知识块的语义完整性。我的实操策略是分层切分先按文档的自然结构切比如 Markdown 按标题层级、PDF 按段落、代码按函数如果某个块还是太大再按语义边界二次切分。块大小一般控制在 256 到 512 个 token 之间同时设置 10% 到 20% 的重叠Overlap避免关键信息正好落在切分边界上被割裂。注意重叠不是越大越好。重叠太大不仅浪费存储和检索成本还会导致检索结果里出现大量重复内容反而稀释了有效信息。我一般从 15% 开始调根据实际召回效果微调。还有一个细节是元数据保留。每个块除了文本内容还要带上来源文档、章节标题、页码、更新时间等元数据。这些元数据在检索时可以用来做过滤比如只检索某个产品线的文档或者只检索最近半年更新的内容。很多团队切分时只保留纯文本后面想做过滤时才发现元数据全丢了只能重来。3.2 嵌入模型选型别只看排行榜嵌入模型的选择直接决定检索质量。市面上主流的嵌入模型有 OpenAI 的 text-embedding 系列、BGE 系列、GTE 系列、以及各种多语言模型。选型时不能只看 MTEB 排行榜要结合自己的实际场景。我的选型框架是这样的先看语言支持如果知识库有中文内容必须选中文能力强的模型BGE 和 GTE 的中文版本表现都不错。再看维度维度越高表达能力越强但存储和检索成本也越高768 维和 1024 维是常见的平衡点。然后看最大输入长度如果你的块比较大要确保模型能处理。最后看部署方式如果数据不能出内网就得选能本地部署的开源模型。这里有个实操心得嵌入模型一定要在自己的数据上做评估。我一般会准备 50 到 100 个真实问题配上人工标注的相关文档然后跑一遍检索看 Top-5 的命中率。不同模型在通用榜单上可能只差一两个点但在你的具体数据上可能差十几个点。这个评估工作花半天时间能省掉后面几周的调优。3.3 向量库选型从 Demo 到生产的跨越向量库的选择要分阶段看。Demo 阶段用 FAISS 或者 Chroma 就够了轻量、上手快。但到了生产环境要考虑的东西就多了并发能力、持久化、分布式、过滤能力、混合检索支持。我整理了一个常见向量库的对比供选型参考向量库适合场景混合检索部署复杂度备注FAISS本地实验、小规模需自行实现低无持久化需自己管理Chroma快速原型有限支持低上手极快生产需谨慎Milvus大规模生产原生支持中高功能全运维成本高Qdrant中小规模生产原生支持中过滤能力强Rust 实现pgvector已有 PostgreSQL需配合低复用现有数据库省事Elasticsearch已有 ES 集群原生支持中稀疏检索天然强我的建议是如果团队已经有 PostgreSQLpgvector 是最省心的选择不用引入新的运维负担。如果数据量上千万级别或者需要复杂的过滤和混合检索Milvus 或 Qdrant 更合适。如果已经有 Elasticsearch 集群直接用它做混合检索也很香因为 ES 的 BM25 是现成的。3.4 检索策略Top-K 只是起点检索阶段最基础的是向量相似度搜索取 Top-K 个最相似的块。但 K 取多少、用什么相似度度量、要不要加阈值过滤这些都有讲究。K 太小可能漏掉关键信息K 太大又会引入噪声、拉长上下文。我一般从 K5 开始配合一个相似度阈值比如余弦相似度 0.7低于阈值的直接丢掉。更进一步的是重排序Reranking。向量检索是粗筛召回的内容里难免有相关性不高的。重排序用一个更精细的模型通常是 Cross-Encoder对召回的 Top-K 重新打分排序把最相关的排到前面。实测下来加一层重排序能把最终答案的准确率提升 15% 以上。常用的重排序模型有 BGE-Reranker 系列和 Cohere Rerank。还有一个进阶技巧是查询改写Query Rewriting。用户的问题往往口语化、有歧义直接拿去检索效果不好。可以先用一个小模型把用户问题改写成更适合检索的形式或者生成多个查询变体分别检索再合并。这个技巧在多轮对话场景下特别有用因为用户的问题经常依赖上下文需要先补全再检索。4. 实操过程与核心环节实现4.1 从零搭建一条最小可用的 RAG 管道我以 Python 技术栈为例走一遍从文档到检索的完整流程。这套代码可以直接抄作业跑通之后再逐步替换成生产级组件。第一步是文档加载和切分。用 LangChain 的文档加载器处理不同格式然后用递归字符切分器做分层切分from langchain_community.document_loaders import PyPDFLoader, TextLoader from langchain.text_splitter import RecursiveCharacterTextSplitter # 加载文档 loader PyPDFLoader(knowledge_base.pdf) documents loader.load() # 分层切分先按段落再按句子 text_splitter RecursiveCharacterTextSplitter( chunk_size512, chunk_overlap80, separators[\n\n, \n, 。, , , , ], length_functionlen, ) chunks text_splitter.split_documents(documents) # 给每个块补充元数据 for i, chunk in enumerate(chunks): chunk.metadata[chunk_id] i chunk.metadata[source] knowledge_base.pdf这里的separators参数很关键它定义了切分的优先级。先尝试按双换行段落切如果还太大就按单换行再不行按中文句号、感叹号、问号切。这样能最大程度保证语义完整。chunk_overlap设成 80大约是 chunk_size 的 15%是我实测下来比较稳的值。第二步是嵌入和存储。这里用 BGE 的中文模型做嵌入用 Chroma 做向量存储from langchain_community.embeddings import HuggingFaceBgeEmbeddings from langchain_community.vectorstores import Chroma # 加载嵌入模型 embedding_model HuggingFaceBgeEmbeddings( model_nameBAAI/bge-large-zh-v1.5, model_kwargs{device: cuda}, encode_kwargs{normalize_embeddings: True}, ) # 构建向量库 vectorstore Chroma.from_documents( documentschunks, embeddingembedding_model, persist_directory./chroma_db, collection_nameknowledge_base, ) vectorstore.persist()normalize_embeddingsTrue这个参数别漏了它把向量归一化到单位长度这样余弦相似度就可以用内积快速计算检索速度会快不少。第三步是检索。先做基础的相似度检索再加上重排序from langchain.retrievers import ContextualCompressionRetriever from langchain.retrievers.document_compressors import CrossEncoderReranker from langchain_community.cross_encoders import HuggingFaceCrossEncoder # 基础检索器 base_retriever vectorstore.as_retriever( search_typesimilarity, search_kwargs{k: 10}, ) # 重排序 cross_encoder HuggingFaceCrossEncoder(model_nameBAAI/bge-reranker-large) reranker CrossEncoderReranker(modelcross_encoder, top_n5) # 组合成带重排序的检索器 retriever ContextualCompressionRetriever( base_compressorreranker, base_retrieverbase_retriever, ) results retriever.invoke(用户的问题)这套流程先召回 10 个候选再用重排序模型精筛出 5 个最相关的。实测下来比直接取 Top-5 的效果好很多尤其是在知识库内容多、噪声大的场景下。4.2 混合检索的实现细节单用稠密检索在遇到专有名词时容易翻车所以我把稀疏检索也加进来。这里用 BM25 做稀疏检索然后和稠密检索的结果融合from langchain_community.retrievers import BM25Retriever from langchain.retrievers import EnsembleRetriever # 稀疏检索器 bm25_retriever BM25Retriever.from_documents(chunks) bm25_retriever.k 10 # 稠密检索器 dense_retriever vectorstore.as_retriever(search_kwargs{k: 10}) # 融合权重各占一半 ensemble_retriever EnsembleRetriever( retrievers[bm25_retriever, dense_retriever], weights[0.5, 0.5], ) results ensemble_retriever.invoke(用户的问题)权重的设置要看场景。如果知识库里专有名词多、用户查询偏精确匹配稀疏检索的权重可以调高到 0.6 甚至 0.7。如果用户查询偏自然语言描述稠密检索权重高一些。我一般从 0.5 比 0.5 开始根据评估结果调整。提示BM25 检索器需要把全部文档加载到内存里构建索引如果知识库很大比如几十万块内存会吃不消。这种情况下建议用 Elasticsearch 或 Milvus 内置的稀疏检索能力而不是在应用层用 BM25Retriever。4.3 检索效果评估没有度量就没有优化搭完管道只是开始真正的工作是持续优化。优化的前提是能度量我一般用三个指标命中率Hit Rate、MRRMean Reciprocal Rank、NDCGNormalized Discounted Cumulative Gain。命中率最简单就是看正确答案有没有出现在 Top-K 里。MRR 看正确答案排在第几位越靠前越好。NDCG 则考虑了多个相关文档的排序质量。实操时我会准备一个评估集包含问题和对应的相关文档 ID然后跑一遍检索算这三个指标。def evaluate_retriever(retriever, eval_set, k5): hits 0 mrr_sum 0.0 for query, relevant_ids in eval_set: results retriever.invoke(query)[:k] retrieved_ids [doc.metadata[chunk_id] for doc in results] # 命中率 if any(rid in relevant_ids for rid in retrieved_ids): hits 1 # MRR for rank, rid in enumerate(retrieved_ids, 1): if rid in relevant_ids: mrr_sum 1.0 / rank break hit_rate hits / len(eval_set) mrr mrr_sum / len(eval_set) return {hit_rate: hit_rate, mrr: mrr}这个评估脚本我每次调整管道参数后都会跑一遍确保改动是正向的。没有这个调优就是盲人摸象。5. 常见问题与排查技巧实录5.1 检索不到相关内容怎么办这是最常见的问题排查思路要按管道顺序来。先看切分把检索到的块打印出来看看内容是不是被切得支离破碎。如果块里全是半句话那就是切分参数有问题调大 chunk_size 或者调整 separators。再看嵌入把用户问题和文档块的向量相似度算出来如果相似度普遍很低可能是嵌入模型不适合你的语言或领域换一个模型试试。然后看检索策略如果稠密检索不行加上稀疏检索做混合。最后看查询用户问题是不是太短或有歧义试试查询改写。我整理了一个排查速查表现象可能原因排查方法解决方向完全检索不到切分过碎/嵌入模型不匹配打印检索块内容调整切分参数/换模型检索到但不相关检索策略单一对比稠密和稀疏结果上混合检索重排序相关但排序靠后缺少重排序看正确答案的排名加 Cross-Encoder 重排序专有名词检索失败稠密嵌入语义漂移测试精确匹配提高稀疏检索权重多轮对话效果差查询依赖上下文看原始查询加查询改写补全上下文5.2 上下文太长导致模型回答变慢变差检索回来的内容太多塞进上下文后模型处理慢而且容易被无关信息干扰。解决办法有两个一是控制召回数量Top-K 不要设太大配合重排序精筛。二是做上下文压缩把检索到的块进一步提炼只保留和问题最相关的句子。LangChain 里有 ContextualCompressionRetriever 可以做这个事或者用一个小模型对每个块做摘要。我个人的经验是最终塞进模型的上下文控制在 2000 到 4000 token 之间比较合适。太少信息不够太多模型注意力被稀释。这个值要根据模型的实际表现调不同模型对长上下文的处理能力差异很大。5.3 知识库更新后检索结果没变化这是增量更新没做好的典型症状。向量库更新文档时要确保旧文档被正确删除或覆盖否则会出现新旧内容同时被检索到的情况。我的做法是给每个文档一个唯一 ID更新时先按 ID 删除旧块再插入新块。如果用 Chroma可以用collection.delete(ids[...])如果用 Milvus可以用delete表达式。还有一个坑是嵌入模型版本不一致。如果更新文档时换了嵌入模型新旧向量就不在同一个语义空间里检索会乱套。所以嵌入模型一旦确定要么不换要么全量重建索引。这个成本要在项目初期就考虑进去。5.4 实操心得几个让我少走弯路的习惯第一个习惯是先小后大。不要一上来就把所有文档灌进去先用 100 个文档跑通全流程验证效果后再逐步扩大。这样出问题时排查范围小迭代快。第二个习惯是保留原始文档。切分后的块和原始文档要能对应上出问题时能回溯。我一般会在块的元数据里存原始文档路径和页码。第三个习惯是定期评估。知识库会随着时间变化用户的问题分布也会变上个月效果好的参数这个月可能就不行了。我一般每两周跑一次评估集监控指标变化。第四个习惯是记录每次改动。调 RAG 参数是个反复试错的过程很容易改着改着就忘了之前试过什么。我会用一个简单的表格记录每次改动的参数和对应的评估指标避免重复劳动。6. 从基础 RAG 到 Agentic RAG 的演进方向基础 RAG 跑通之后下一步就是让它更“聪明”。传统的 RAG 是单轮检索用户问一次就检索一次检索完就生成。但真实场景下用户的问题往往需要多步检索、多源融合甚至需要 Agent 自己判断该不该检索、检索什么。这就是Agentic RAG的思路把检索能力封装成 Agent 的一个工具让 Agent 根据任务需要自主决定何时检索、检索几次、用什么查询检索。比如用户问“对比 A 产品和 B 产品的退款政策”Agent 可以先检索 A 产品的政策再检索 B 产品的政策最后对比生成。这种多步检索的能力是基础 RAG 做不到的。再往深了走还有GraphRAG和Ontology RAG的方向。GraphRAG 把知识库构建成知识图谱检索时沿着图的关系走适合处理实体关系复杂的场景。Ontology RAG 则引入本体论对知识做更结构化的建模。这些方向目前还在演进中落地成本比基础 RAG 高不少适合对知识结构要求高的企业场景。我的建议是先把基础 RAG 做扎实把检索质量、评估体系、更新机制都跑顺再考虑往 Agentic RAG 演进。基础不牢上层再花哨也是空中楼阁。我见过太多团队一上来就搞多智能体协作结果连单轮检索的命中率都不到 60%最后项目不了了之。最后分享一个我在实际项目里验证过的小技巧给检索结果加上来源标注让模型在回答时引用来源。这不仅提升了回答的可信度还让用户能自己验证信息。实现方式很简单在构造 prompt 时把每个块的来源信息带上要求模型在回答里标注引用编号。这个改动成本极低但对用户体验的提升非常明显尤其是在企业知识库场景下用户对信息的可信度要求很高。
返回列表