
1. 为什么知识获取管道是 AI Agent 落地的第一道坎做 AI Agent 开发的人绕不开一个尴尬的现实模型本身很聪明但它对你私有的业务知识一无所知。你问它公司内部的报销流程它只能编你问它某个产品的技术参数它要么胡诌要么拒答。这不是模型能力不行而是它的知识边界停在了训练数据截止的那一天。知识获取管道要解决的就是这个问题。它的核心任务只有一句话在 Agent 需要知识的时候把正确的那一小块知识准确、及时地送到模型面前。听起来简单做起来全是细节。我见过太多团队模型选得挺好Agent 框架搭得也漂亮结果卡在知识管道上——检索出来的内容驴唇不对马嘴或者明明知识库里有答案却死活搜不到最后整个项目不了了之。RAGRetrieval-Augmented Generation检索增强生成就是目前最主流的知识获取管道方案。它的思路很朴素不指望模型记住所有东西而是在生成回答之前先去知识库里把相关资料捞出来塞进上下文让模型看着材料答题。这个思路从 2020 年提出到现在已经演化出了朴素 RAG、高级 RAG、模块化 RAG、Agentic RAG 等多个阶段但底层逻辑没变——检索的质量决定了生成的质量。这篇是走进 AI Agent系列的第四篇专门讲知识获取管道的基础部分。我会把 RAG 的核心链路拆开重点讲清楚稠密嵌入和稀疏嵌入这两条技术路线的区别、各自的适用场景以及在实际搭建中怎么选、怎么调、怎么避坑。不管你是刚接触 RAG 的新手还是已经搭过几套知识库但效果不理想的老手这篇内容应该都能给你一些可以直接抄作业的东西。提示这篇偏基础重点在理解原理和建立正确的技术直觉。如果你已经能熟练搭建 RAG 管道可以重点关注第 3 节和第 5 节的调优经验部分。2. RAG 管道的完整链路拆解从文档到答案经历了什么很多人对 RAG 的理解停留在把文档切一切、向量化、搜一搜这个层面但真正跑起来会发现每一个环节都有大量决策点。我先把完整链路摊开让你看清楚数据从原始文档到最终答案中间到底经过了哪些加工站。2.1 文档加载与预处理脏数据是检索失败的头号元凶知识获取管道的第一站是文档加载。你的知识可能散落在 PDF、Word、Markdown、HTML、数据库、甚至飞书/钉钉的聊天记录里。加载器的任务就是把这些异构数据统一成纯文本。这一步看起来最没技术含量但恰恰是坑最多的地方。我踩过的典型坑包括PDF 解析丢格式很多 PDF 是扫描件或者排版复杂的报表直接解析出来文字顺序全乱表格变成一堆散落的数字。这种脏数据进了知识库检索出来的内容根本没法用。页眉页脚污染每页都重复的页眉页脚、页码、水印文字如果不清理会被切成独立的 chunk白白占用检索名额。编码问题中文文档经常遇到 GBK 和 UTF-8 混用解析出来一堆乱码。我的经验是文档预处理阶段至少要花整个项目 30% 的精力。具体做法PDF 优先用能保留版面结构的解析器比如基于版面分析的方案解析后做一轮正则清洗把页眉页脚、连续空行、特殊符号干掉。对于表格密集的文档考虑单独走表格提取通道别指望通用解析器能处理好。2.2 文本分块chunk 切得好检索成功一半文本分块Chunking是 RAG 里最容易被低估的环节。chunk 太大检索出来的内容包含太多无关信息稀释了关键信号chunk 太小语义不完整模型拿到手也拼不出完整答案。常见的分块策略有这么几种策略做法适用场景缺点固定长度按 token 数硬切如每 512 token 一块快速原型容易切断句子和语义递归分块按段落→句子→词的优先级递归切通用文档需要调参数语义分块用嵌入相似度判断语义边界高质量要求计算成本高结构化分块按标题层级、代码块等结构切技术文档、法律合同依赖文档结构我实测下来递归分块 适度重叠是性价比最高的方案。具体参数上中文文档我一般用 chunk size 300-500 字overlap 50-100 字。overlap 的作用是防止关键信息正好卡在切割边界上被切断但也不能太大否则检索结果里全是重复内容。注意chunk size 没有万能值。技术文档可以小一点300 字左右因为概念密集叙述性文档可以大一点500-800 字因为需要上下文才能理解。一定要根据你的文档类型做实验。2.3 嵌入与索引把文字变成可以计算距离的向量嵌入Embedding是把文本映射成高维向量的过程。这个向量有个关键性质语义相近的文本向量距离也相近。这样一来检索就变成了在向量空间里找最近的邻居可以用数学方法高效完成。嵌入模型的选择直接决定了检索的上限。目前主流的中文嵌入模型有 BGE 系列、M3E、GTE 等英文有 OpenAI 的 text-embedding 系列、Cohere 等。选型时重点看三个指标检索准确率Recall/NDCG、向量维度、推理速度。索引阶段则是把生成的向量存进向量数据库建立高效的相似度搜索结构。常见选择有 FAISS本地、轻量、Milvus分布式、功能全、QdrantRust 写的、性能好、Chroma上手快、适合原型。小规模知识库几万条以内用 FAISS 或 Chroma 完全够用上百万条再考虑 Milvus 这类分布式方案。2.4 检索与重排先粗筛再精排的两段式打法检索阶段系统拿用户 query 的向量去向量库里找 top-K 个最相似的 chunk。但纯向量检索有个问题它擅长捕捉语义相似但对精确匹配比如产品型号、专有名词不敏感。所以工业级 RAG 普遍采用两段式检索第一阶段用向量检索或向量关键词混合检索快速召回一批候选比如 top-50第二阶段用重排模型Reranker对候选做精细打分选出最相关的 top-3 到 top-5 送给模型。重排模型通常是交叉编码器Cross-Encoder它把 query 和每个候选 chunk 拼在一起过一遍模型打分更准但速度慢所以只用在候选集上。这个粗筛精排的组合是我实测下来提升检索命中率最明显的手段之一。2.5 生成与引用让模型看着材料答题最后一步是把检索到的 chunk 拼进 prompt让模型基于这些材料生成答案。这里的关键是prompt 设计要明确告诉模型只根据提供的材料回答材料里没有就说不知道并且要求它标注引用来源。引用来源这个功能看似小实际价值极大。它让用户能验证答案的可靠性也方便你排查是检索错了还是生成错了。我在项目里会强制要求模型输出[1][2]这样的引用标记对应到具体的 chunk 来源。3. 稠密嵌入与稀疏嵌入两条检索路线的本质区别理解了完整链路现在聚焦到最核心的技术分歧点稠密嵌入Dense Embedding和稀疏嵌入Sparse Embedding。这两个词你可能听过很多次但它们的本质区别、各自的强项和短板值得掰开揉碎讲清楚。3.1 稠密嵌入用语义空间捕捉意思相近稠密嵌入的输出是一个固定长度、每个维度都是非零实数的向量比如 768 维或 1024 维。它的核心能力是语义匹配。举个例子用户搜怎么退钱知识库里写的是退款流程说明。字面上几乎没有重叠词但稠密嵌入能把它们映射到相近的向量位置因为模型在训练时学到了退钱和退款是同一个意思。这就是稠密嵌入的杀手锏——它理解语义不受字面表达限制。稠密嵌入的典型代表是 BGE、M3E、OpenAI text-embedding 这些模型。它们的训练目标通常是让语义相近的文本对比如问题和答案、同义句在向量空间里靠近让无关文本远离。但稠密嵌入也有明显短板对精确匹配不敏感用户搜X2000 型号的电池规格如果知识库里有X2000这个型号稠密嵌入可能因为整体语义偏向电池规格而漏掉精确的型号匹配。对罕见词、专有名词、数字不友好这些词在训练数据里出现少模型没学好它们的表示。可解释性差你很难说清楚为什么这两个向量相似因为每个维度都没有明确含义。3.2 稀疏嵌入用词频统计守住字面精确稀疏嵌入的输出是一个维度等于词表大小、绝大多数维度为零的向量。每个非零维度对应一个词值代表这个词的权重。最经典的稀疏表示就是BM25它基于词频TF和逆文档频率IDF计算权重。稀疏嵌入的核心能力是精确匹配。用户搜X2000 电池它会把 query 拆成词然后找包含这些词的文档并根据词的稀有程度加权——X2000这种罕见词权重高电池这种常见词权重低。所以它能精准命中包含特定型号、专有名词的文档。稀疏嵌入的短板同样明显不理解语义用户搜退钱文档写退款如果这两个词没有共同词根BM25 完全匹配不上。词表固定遇到词表外的新词OOV就无能为力。无法捕捉上下文同一个词在不同语境下含义不同稀疏表示区分不了。3.3 一张表看清两者的取舍维度稠密嵌入稀疏嵌入向量形态固定维度稠密实数词表维度大量为零核心能力语义匹配字面精确匹配代表方案BGE、M3E、text-embeddingBM25、SPLADE强项同义改写、模糊查询专有名词、型号、数字弱项精确匹配、罕见词语义泛化、OOV可解释性差好能看出哪些词命中计算成本需要模型推理统计计算轻量看到这里你应该明白了稠密和稀疏不是替代关系而是互补关系。稠密管意思对不对稀疏管词对不对。真实场景里用户的 query 往往两者都需要——既希望理解语义又希望精确命中关键词。3.4 混合检索把两条路线的优势叠起来既然互补那就混合检索Hybrid Retrieval。做法是同时跑稠密检索和稀疏检索各自召回一批候选然后用融合算法把两路结果合并排序。最常用的融合算法是RRFReciprocal Rank Fusion倒数排名融合。它的逻辑很简单对每个文档把它在两路结果里的排名取倒数再相加得到最终分数。排名越靠前贡献越大。RRF 的好处是不需要归一化两路分数稠密是余弦相似度稀疏是 BM25 分数量纲完全不同直接基于排名融合鲁棒性好。我实测下来混合检索相比纯稠密检索在包含大量专有名词的技术文档场景下召回率能提升 15%-30%。这个提升幅度足以决定一个 RAG 项目是能用还是不能用。提示混合检索的权重需要调。有些框架允许你给稠密和稀疏分配不同权重一般从 0.5:0.5 起步然后根据你的评测集调整。技术文档可以适当提高稀疏权重对话类场景可以提高稠密权重。4. 从零搭一条最小可用的 RAG 管道理论讲完了现在动手。这一节我带你把一条最小可用的 RAG 管道搭起来用 Python 生态里最成熟的组件。目标不是搭一个生产级系统而是让你亲手跑通全链路建立对每个环节的体感。4.1 环境准备与依赖选型我选的技术栈是LangChain 做流程编排 BGE 做稠密嵌入 BM25 做稀疏检索 FAISS 做向量索引 一个开源重排模型。这套组合全部可以本地跑不依赖外部 API适合学习和中小规模部署。pip install langchain langchain-community faiss-cpu rank_bm25 pip install sentence-transformers FlagEmbedding选型理由LangChain 的抽象层次适中既能快速搭原型又不会把你锁死BGE 系列在中文检索任务上长期霸榜bge-large-zh-v1.5是我常用的默认选择FAISS 是 Facebook 开源的向量检索库CPU 版本就够用零运维成本。4.2 文档加载与递归分块实操假设你有一批 Markdown 格式的技术文档先加载再分块from langchain_community.document_loaders import DirectoryLoader, TextLoader from langchain.text_splitter import RecursiveCharacterTextSplitter # 加载文档 loader DirectoryLoader(./docs, glob**/*.md, loader_clsTextLoader, loader_kwargs{encoding: utf-8}) docs loader.load() # 递归分块 splitter RecursiveCharacterTextSplitter( chunk_size400, chunk_overlap80, separators[\n\n, \n, 。, , , , , , ] ) chunks splitter.split_documents(docs) print(f共生成 {len(chunks)} 个 chunk)这里separators的顺序很关键。RecursiveCharacterTextSplitter 会优先按靠前的分隔符切切出来的块还太大就换下一个分隔符继续切。我把中文标点加进去就是为了尽量在句子边界切避免把一句话拦腰截断。4.3 稠密向量生成与 FAISS 索引构建from langchain_community.embeddings import HuggingFaceBgeEmbeddings from langchain_community.vectorstores import FAISS # 加载 BGE 嵌入模型 embed_model HuggingFaceBgeEmbeddings( model_nameBAAI/bge-large-zh-v1.5, model_kwargs{device: cpu}, encode_kwargs{normalize_embeddings: True} ) # 构建 FAISS 索引 vectorstore FAISS.from_documents(chunks, embed_model) vectorstore.save_local(./faiss_index)normalize_embeddingsTrue这个参数别漏。它把向量归一化到单位长度这样余弦相似度就等价于内积计算更快而且 BGE 模型本身推荐这样用。4.4 BM25 稀疏检索器的搭建from rank_bm25 import BM25Okapi import jieba # 中文分词 tokenized_corpus [list(jieba.cut(chunk.page_content)) for chunk in chunks] bm25 BM25Okapi(tokenized_corpus) def bm25_search(query, top_k20): tokenized_query list(jieba.cut(query)) scores bm25.get_scores(tokenized_query) top_indices scores.argsort()[::-1][:top_k] return [(chunks[i], scores[i]) for i in top_indices]中文用 BM25 必须分词jieba是最常用的选择。如果你的领域有大量专有名词记得往 jieba 的自定义词典里加否则这些词会被切碎BM25 就匹配不上了。4.5 用 RRF 融合两路检索结果def rrf_fusion(dense_results, sparse_results, k60): scores {} for rank, (doc, _) in enumerate(dense_results): scores[doc.page_content] scores.get(doc.page_content, 0) 1 / (k rank 1) for rank, (doc, _) in enumerate(sparse_results): scores[doc.page_content] scores.get(doc.page_content, 0) 1 / (k rank 1) sorted_docs sorted(scores.items(), keylambda x: x[1], reverseTrue) return sorted_docsk60是 RRF 论文里的推荐值作用是平滑排名靠前文档的权重避免某一路的 top-1 过度主导。这个值一般不用改。4.6 接上重排模型做精排from FlagEmbedding import FlagReranker reranker FlagReranker(BAAI/bge-reranker-large, use_fp16True) def rerank(query, candidates, top_k5): pairs [[query, doc] for doc, _ in candidates] scores reranker.compute_score(pairs) ranked sorted(zip(candidates, scores), keylambda x: x[1], reverseTrue) return ranked[:top_k]重排模型是整条管道里性价比最高的投入。它把候选集从几十个精筛到几个送给模型的上下文质量直接上一个台阶。我做过对比加了重排之后最终答案的准确率能提升 20% 以上。5. 检索效果调优那些文档里不会写的实战经验管道搭起来只是开始真正决定成败的是调优。这一节我分享几个踩过坑才总结出来的经验都是常规文档里不会写的。5.1 命中率上不去的三个隐藏原因第一个原因query 和文档的语言风格不匹配。用户提问是口语化的短句知识库文档是书面化的长句。这种风格差异会让嵌入模型算出的相似度偏低。解决办法是做query 改写——用一个小模型把用户的口语 query 改写成更接近文档风格的表述再拿去检索。第二个原因嵌入模型和你的领域不匹配。通用嵌入模型在通用语料上训练对你的垂直领域比如医疗、法律、工业可能水土不服。如果预算允许用领域数据对嵌入模型做微调效果提升非常明显。预算有限的话至少试试在你的领域数据上评测几个不同的开源模型选最适合的那个。第三个原因chunk 里混入了太多噪声。前面提过页眉页脚、无关段落会稀释信号。我遇到过一个案例知识库里每个 chunk 都带着一段公司简介的模板文字导致检索时这段模板文字贡献了大量相似度真正的答案反而被淹没。清理掉之后命中率立刻回升。5.2 重排模型不是万能药什么时候它会帮倒忙重排模型很强但不是所有场景都适合。我遇到过两种情况加了重排反而变差候选集本身就没有正确答案如果第一阶段召回率太低top-50 里根本没有相关文档重排再准也没用。这时候要解决的是召回问题不是排序问题。query 本身模糊用户 query 太短太泛比如就一个词重排模型缺乏足够信号判断相关性可能把原本靠前的正确结果排下去。所以我的建议是先保证召回率再优化排序。评测时分开看召回率和最终准确率定位问题到底出在哪一环。5.3 评测集没有它你就是在盲调这是我最想强调的一点。没有评测集的调优都是玄学。你需要准备一批query-正确答案的配对每次改动管道后跑一遍看指标是涨是跌。评测集不用很大50-100 条就能反映趋势。构造方法从真实用户提问里采样人工标注每个 query 对应的正确 chunk。指标上检索阶段看RecallK和MRR生成阶段看答案准确率和引用正确率。我见过太多团队凭感觉调参今天改改 chunk size明天换换模型最后自己也说不清哪个改动有效。有了评测集每次改动都有数据支撑调优效率能提升好几倍。5.4 一个容易被忽略的细节元数据过滤真实场景里用户的问题往往隐含了过滤条件。比如2024 年的销售政策是什么这里2024 年就是一个过滤条件。如果只靠向量检索很可能把 2023 年的政策也召回来。解决办法是给每个 chunk 打上元数据标签年份、部门、文档类型等检索时先按元数据过滤再做向量搜索。这个技巧在多版本、多部门的文档库里特别有用能大幅减少干扰项。6. 从基础 RAG 到 Agentic RAG 的演进方向基础 RAG 跑通之后你会发现它有几个天花板只能检索一次、不会判断检索结果够不够、不会根据问题类型切换检索策略。这些正是Agentic RAG要解决的问题。Agentic RAG 的核心思路是把检索变成 Agent 的一个工具让 Agent 自己决定什么时候检索、检索几次、用什么策略检索。比如面对一个复杂问题Agent 可以先拆解成子问题逐个检索发现信息不足就换个 query 再搜最后综合所有材料生成答案。这个演进方向涉及的技术点包括查询规划Query Planning、自适应检索Adaptive Retrieval、多跳检索Multi-hop Retrieval、自我反思Self-Reflection等。这些内容足够单独写一篇这里先给你一个方向感——基础 RAG 是一次检索一次生成Agentic RAG 是边想边搜边答。另外值得一提的是GraphRAG和本体 RAGOntology RAG这两个方向。它们不满足于把文档切成孤立的 chunk而是试图构建知识之间的关联图谱让检索能沿着关系链走。对于需要多跳推理的场景比如和 A 产品共用同一个供应商的产品有哪些这类方案比纯向量检索有天然优势。不过它们的构建成本也高得多适合知识结构稳定、关系复杂的领域。我在实际项目里的体会是别一上来就追求 Agentic RAG 或 GraphRAG。先把基础 RAG 的每个环节调扎实把评测集建起来把召回率和准确率做到一个可用的水平。基础打牢之后再根据实际瓶颈决定往哪个方向演进。很多团队基础还没跑通就急着上高级方案结果是在沙地上盖楼越盖越不稳。最后分享一个小技巧如果你的知识库更新频繁一定要把索引更新流程自动化。文档一变就重新嵌入、重建索引否则知识库很快就会和实际文档脱节。我一般用文件哈希做增量更新只重新处理变化的文档能省下大量计算。