AI Agent知识获取管道:RAG原理、稠密与稀疏嵌入及混合检索实战 1. 为什么知识获取管道是 AI Agent 落地的第一道分水岭做 AI Agent 的人绕不开一个尴尬的现实模型本身很聪明但它不知道你公司上个月改了报销制度不知道你手里那套 ERP 系统的字段命名规则更不知道你三年前写的那份技术方案里埋了哪些坑。Agent 要真正干活第一步不是让它会推理而是让它先知道。这就是知识获取管道存在的意义而 RAGRetrieval-Augmented Generation检索增强生成是目前这条管道上最成熟、最通用的实现范式。我在过去一年里帮几个团队搭过 Agent 的知识底座从最初级的把 PDF 塞进向量库到后来带图谱的混合检索踩过的坑比想象中多得多。很多人以为 RAG 就是切块、嵌入、检索、拼 prompt四步走真上手才发现切块策略决定了召回上限嵌入模型选错了整个管道白搭检索环节的排序逻辑直接决定 Agent 会不会一本正经地胡说八道。这篇是走进 AI Agent系列的第四篇专门把知识获取管道这一层拆开讲透从 RAG 的基础原理到稠密嵌入、稀疏嵌入的取舍再到实际搭建时的参数细节和避坑经验。不管你是刚接触 AI Agent 开发的新手还是已经用 LangChain、Spring AI、LangChain4j 搭过 demo 但效果不理想的开发者这篇内容都能帮你把 RAG 这条管道从能跑推到能用。我会尽量少讲空泛概念多讲为什么这么设计、参数怎么算、实测什么效果。RAG 不是一个组件而是一条完整的流水线任何一环掉链子最终 Agent 的回答质量都会崩。先说清楚一个定位问题RAG 解决的是知识割裂——模型参数里固化的通用知识和你的私有知识之间存在断层。微调Fine-tuning能补一部分但成本高、更新慢、容易灾难性遗忘。RAG 的思路是把知识外置成可检索的库Agent 每次回答前先去库里捞相关片段再交给模型组织语言。这个思路的代价是引入了检索质量这个新变量好处是知识可以随时更新、可溯源、成本可控。理解了这层取舍后面的技术选型才有判断依据。2. RAG 管道的五个环节与各自的失效点2.1 从原始文档到可检索片段的完整链路一条标准的 RAG 管道拆开来看是五个环节文档加载Loading、文本切分Chunking、向量化Embedding、存储与索引Indexing、检索与重排Retrieval Rerank。每个环节都有它自己的失效模式而且往往是上游的问题在下游才暴露出来排查起来特别费劲。文档加载阶段最容易被低估的是格式多样性。PDF 里的表格、扫描件里的图片、Word 里的批注、Markdown 里的代码块这些内容如果加载器处理不好要么丢信息要么把结构打乱。我见过一个案例某团队把产品手册 PDF 直接喂给默认加载器结果表格被拆成了逐行的乱码文本检索出来的片段完全没法用。加载阶段的核心原则是尽量保留原始结构信息宁可多花时间做预处理也不要把脏数据带进管道。文本切分是整条管道里最考验经验的一环。切得太碎单个片段缺乏上下文模型拿到也拼不出完整语义切得太粗一个片段里混了好几个主题检索命中后噪声太大。常见的策略有固定长度切分、按语义切分、按文档结构切分比如按标题层级。固定长度最简单但最粗暴一般配合重叠窗口overlap使用重叠比例通常取 chunk 大小的 10% 到 20%。按语义切分效果更好但需要额外的模型调用成本和延迟都会上升。向量化环节决定了能不能检索到。这里就是稠密嵌入和稀疏嵌入的分野所在后面会专门展开。存储与索引环节向量数据库的选型、索引类型HNSW、IVF 等、距离度量余弦、内积、欧氏都会影响检索速度和召回率。检索与重排环节则是把召回和精排分开先用向量检索捞出一批候选比如 top 50再用重排模型Rerank精挑细选出最相关的几条比如 top 5喂给 LLM。2.2 每个环节最典型的翻车场景我把实际项目中遇到的高频失效场景整理成了一张表方便对照排查环节典型失效表现根因应对方向加载表格变乱码、图片信息丢失加载器不支持复杂结构换用结构化解析器表格单独处理切分检索片段语义不完整chunk 过小或边界切错调大 chunk改用语义/结构切分向量化明明有答案却检索不到嵌入模型与领域不匹配换领域适配模型或做微调索引检索慢、内存爆索引类型选错、维度太高调 HNSW 参数或降维检索召回一堆不相关内容只做向量检索、无重排引入混合检索 Rerank这张表里的每一行背后都是真金白银的调试时间。特别提醒一点很多人一上来就怀疑嵌入模型不行其实八成问题出在切分和加载。数据没处理好再好的模型也救不回来。我的一般排查顺序是先看加载出来的原始文本干不干净再看切分后的片段是否语义自洽最后才怀疑向量化和检索参数。2.3 为什么能跑通 demo和能上生产差着十万八千里Demo 阶段你随便找几篇文档切一切、嵌一嵌问几个问题能答上来就觉得很成功。但生产环境的考验完全不同文档量从几十篇涨到几万篇查询从标准问法变成各种口语化、错别字、多轮追问还要考虑权限隔离不同用户能检索到的知识范围不同、增量更新文档改了要能及时反映、成本控制嵌入和重排都是要花钱的。我印象最深的一次是帮一个团队做内部知识库 Agent。Demo 阶段召回率看着有 80%上线后用户投诉不断。排查发现用户真实提问里大量是那个报销的流程是啥来着这种模糊指代而文档里写的是费用报销管理办法。字面和语义都对不上纯向量检索直接歇菜。后来加了稀疏检索关键词匹配做混合又补了一层查询改写把口语化问题改写成规范问法召回率才拉回来。这件事让我彻底明白RAG 的难点不在搭起来而在搭得准。3. 稠密嵌入与稀疏嵌入两条腿走路才稳3.1 稠密嵌入到底在做什么稠密嵌入Dense Embedding是把一段文本映射成一个固定长度的高维向量比如 768 维、1024 维、1536 维。这个向量里的每个维度都是一个浮点数整体编码了文本的语义信息。语义相近的文本向量在空间里的距离就近。检索时把查询也嵌入成向量然后找库里距离最近的若干条这就是稠密检索。稠密嵌入的强项是语义泛化。你问怎么请年假文档里写的是员工休假申请流程字面几乎不重叠但稠密向量能把它们拉到一起。这是稀疏检索做不到的。它的弱项也很明显对精确匹配不敏感。比如产品型号XR-2000A和XR-2000B在稠密空间里可能非常接近但业务上完全是两个东西。还有专有名词、代码标识符、错误码这类稠密嵌入经常抓瞎。选稠密模型时我一般看几个维度向量维度影响存储和检索速度、最大输入长度决定 chunk 能切多大、多语言支持中文场景必须验证中文效果、以及是否针对检索任务做过训练。很多通用嵌入模型在语义相似度任务上表现不错但直接拿来做检索效果会打折扣因为检索任务需要的是查询-文档匹配能力和句子-句子相似度不是一回事。3.2 稀疏嵌入不是过时技术而是精确匹配的保险稀疏嵌入Sparse Embedding的代表是 BM25 这类基于词频的算法以及后来的 SPLADE 这类学习式稀疏表示。传统 BM25 的思路很朴素一个词在文档里出现得越多、在整个语料里越罕见它的区分度就越高。检索时按查询词逐个匹配打分。它不需要训练可解释性强对精确匹配极其敏感。很多人觉得有了稠密嵌入稀疏检索就可以扔了。这是个误区。稀疏检索在几个场景下不可替代一是专有名词和标识符匹配二是长尾罕见词三是需要可解释性的场景你能明确告诉用户因为命中了这几个关键词。SPLADE 这类方法则是在保留稀疏结构的同时引入了语义扩展能力算是两者的融合尝试但工程复杂度也上去了。我的实践结论是混合检索Hybrid Retrieval是当前性价比最高的方案。稠密负责语义召回稀疏负责精确召回两路结果用加权融合比如 RRFReciprocal Rank Fusion合并再交给重排模型精排。这套组合拳下来召回率通常比单用稠密高出 10 到 20 个百分点尤其在垂直领域文档上效果显著。3.3 融合策略与重排把两路结果拧成一股绳混合检索的关键在融合。最简单的做法是分数加权final_score α * dense_score (1-α) * sparse_score。但稠密和稀疏的分数尺度往往不一致直接加权容易失衡。更稳的做法是 RRF它不看绝对分数只看排名RRF_score Σ 1/(k rank_i)k 一般取 60。这样两路检索的排名信息被公平地融合在一起对分数尺度不敏感实测很稳。融合之后是重排Rerank。重排模型通常是 Cross-Encoder 结构把查询和候选文档拼在一起输入模型直接输出相关性分数。它比向量检索的双塔结构慢得多但准得多。所以标准做法是向量检索召回 top 50 到 100重排模型精排出 top 3 到 5再喂给 LLM。这个粗召回 精排序的两段式设计是 RAG 检索质量的核心保障。重排模型的选择上中文场景我一般优先考虑针对中文优化的模型或者多语言效果经过验证的。重排的延迟是实打实的top 50 的重排在一张普通 GPU 上大概几百毫秒如果对延迟敏感可以适当减少候选数量或者用更轻量的重排模型。这里没有银弹得根据你的延迟预算和准确率要求做权衡。4. 从零搭一条能用的 RAG 管道参数与代码细节4.1 切分参数的确定过程切分参数没有万能值但有一套推导逻辑。先看你的嵌入模型最大输入长度比如 512 token。那 chunk 就不能超过这个长度否则会被截断。然后看你的文档特点如果是技术文档、法律条文这种语义密集的chunk 可以小一点256 到 384 token保证每个片段聚焦如果是叙述性内容chunk 可以大一点512 到 768 token保留上下文。重叠窗口overlap的作用是防止关键信息正好被切在边界上。一般取 chunk 大小的 10% 到 20%。比如 chunk 是 512 tokenoverlap 取 64 到 100 token。overlap 太大会导致重复内容多、存储浪费、检索时同一信息反复出现太小则起不到保护作用。我一般会做一个小实验拿一批真实查询用不同切分参数跑一遍看召回率变化。这个实验花不了多少时间但能帮你把参数定在合理区间比拍脑袋强得多。下面是一段用 LangChain 做递归切分的示例注意chunk_size和chunk_overlap的单位是字符数中文场景要按实际 token 换算from langchain.text_splitter import RecursiveCharacterTextSplitter splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap80, separators[\n\n, \n, 。, , , , , , ], length_functionlen, ) chunks splitter.split_text(raw_text)separators的顺序很关键它决定了切分时优先在哪里断开。中文场景一定要把中文标点加进去否则默认按空格切中文文本会被切得乱七八糟。这是很多人第一次做中文 RAG 时踩的坑。4.2 嵌入与索引的落地配置嵌入环节我建议先把候选模型在小批量数据上跑一遍用真实查询测召回别只看模型榜单。榜单上的高分模型未必适合你的领域。索引环节向量数据库选型要看数据规模和查询模式数据量小几万条以内用 FAISS 这类本地库就够数据量大、需要分布式和权限控制就上 Milvus、Qdrant 这类专业向量库。以 Qdrant 为例创建一个带 HNSW 索引的集合from qdrant_client import QdrantClient from qdrant_client.models import Distance, VectorParams client QdrantClient(hostlocalhost, port6333) client.create_collection( collection_nameknowledge_base, vectors_configVectorParams(size1024, distanceDistance.COSINE), )size必须和你的嵌入模型输出维度一致这个错了会直接报错。distance选余弦还是内积取决于模型训练时用的度量方式一般检索模型用余弦的多。HNSW 的关键参数是m每个节点的连接数和ef_construct构建时的候选集大小m越大召回越高但内存越大ef_construct越大构建越慢但索引质量越好。默认值通常够用数据量大且对召回敏感时再调。4.3 混合检索与重排的代码骨架下面这段是混合检索的简化骨架稠密用向量库稀疏用 BM25最后 RRF 融合from rank_bm25 import BM25Okapi # 稀疏检索 tokenized_corpus [doc.split() for doc in corpus] bm25 BM25Okapi(tokenized_corpus) sparse_scores bm25.get_scores(query.split()) sparse_rank sorted(range(len(sparse_scores)), keylambda i: -sparse_scores[i])[:50] # 稠密检索 dense_hits client.search(collection_nameknowledge_base, query_vectorquery_vec, limit50) dense_rank [hit.id for hit in dense_hits] # RRF 融合 def rrf(ranks, k60): scores {} for rank_list in ranks: for pos, doc_id in enumerate(rank_list): scores[doc_id] scores.get(doc_id, 0) 1 / (k pos 1) return sorted(scores.items(), keylambda x: -x[1]) fused rrf([dense_rank, sparse_rank])[:20]融合出 top 20 后再送重排模型精排。这套流程跑通后你会发现召回质量比单路检索稳定得多。注意 BM25 的中文分词split()对中文无效得用 jieba 之类的分词器否则稀疏检索等于没做。5. 实测中那些文档不会写的坑5.1 召回率上不去的排查顺序召回率低是最常见的问题但原因可能分布在管道任何一环。我的排查顺序是这样的第一步把检索到的片段打印出来人工看它们和查询到底相不相关。如果片段本身就是垃圾问题在加载或切分如果片段质量还行但没命中答案问题在嵌入或检索策略。第二步把答案所在的原始文档找出来看它被切成了哪些片段这些片段的向量和查询向量的距离是多少。如果距离很远说明嵌入模型对这个领域不敏感。第三步检查是不是查询本身的问题试试把查询改写成更规范的问法看召回是否改善。这个顺序能帮你快速定位问题层级避免盲目换模型。我见过太多人一上来就换嵌入模型结果换了三四个还是不行最后发现是 PDF 加载时表格全乱了。5.2 增量更新与权限隔离的现实处理生产环境的 RAG 必须处理增量更新。文档改了对应的向量要更新或删除重建。这里有个坑如果只更新向量不更新稀疏索引混合检索的两路结果会不一致。我的做法是给每个片段打上文档 ID 和版本号更新时按文档 ID 批量删除旧片段、插入新片段稀疏索引同步重建或增量更新。权限隔离是另一个容易被忽略的点。不同用户能看到的文档范围不同检索时必须在向量库里带上权限过滤条件。Qdrant、Milvus 都支持 payload 过滤把权限标签存进 payload检索时加 filter 即可。千万别在检索后再过滤那样既浪费算力又可能泄露信息因为候选里已经包含了无权内容。5.3 成本与延迟的平衡技巧嵌入和重排都是要花钱和时间的。控制成本有几个实用技巧一是嵌入可以离线批量做用批处理接口比逐条快得多也便宜二是重排的候选数量别贪多top 50 通常够top 100 收益递减但延迟翻倍三是缓存高频查询的检索结果很多内部知识库的查询重复率很高缓存能省下大量算力。延迟方面向量检索本身很快毫秒级大头在重排和 LLM 生成。如果对首字延迟敏感可以把重排做成异步或者用更轻的模型先返回粗排结果精排结果出来后再更新。这个体验设计上的取舍得根据你的产品形态来定。6. 关于 RAG 进阶方向的一点个人判断RAG 这条管道搭到能用之后往深了走有几个方向。一个是 GraphRAG把知识图谱和向量检索结合解决多跳推理和全局性问题代价是构建成本高、更新复杂。一个是 Agentic RAG让 Agent 自己决定检索什么、检索几轮、要不要改写查询灵活性上去了但可控性下降。还有一个是本体Ontology驱动的 RAG用领域本体约束检索和生成适合垂直领域但前期建模投入大。我的建议是别一上来就追这些进阶方案。把基础 RAG 的加载、切分、混合检索、重排这四件事做扎实召回率和准确率能到 85% 以上再考虑加图谱或 Agent 化。基础不牢上层越复杂越难调。我自己踩过的最大教训就是过早引入复杂架构结果出了问题根本定位不到是哪一层。最后分享一个我一直在用的小技巧给 RAG 管道建一套离线评测集用真实查询和标准答案每次改参数或换模型都跑一遍看召回率、准确率、延迟的变化。没有评测集所有的感觉变好了都是幻觉。这套评测集不用很大一两百条高质量查询就能帮你避开大部分回归问题。搭 RAG 是个细活慢就是快。