1. 为什么知识获取管道是 AI Agent 的分水岭
做 AI Agent 开发的人,绕不开一个尴尬的现实:模型本身很聪明,但它对你私有的业务知识一无所知。你问它公司内部的报销流程,它只能给你编一段听起来很像那么回事的废话。这不是模型不行,而是它的知识边界停在了训练数据截止的那一天,而且从来没读过你电脑里的那些文档。
知识获取管道要解决的就是这个问题。它的核心任务只有一句话:在 Agent 需要知识的时候,把正确的那一小块知识,准确地送到模型面前。听起来简单,做起来全是坑。我见过太多项目,模型选的是顶配,Prompt 写得也漂亮,结果卡在知识检索这一步,答非所问、张冠李戴,最后整个 Agent 沦为演示玩具。
RAG(Retrieval-Augmented Generation,检索增强生成)就是目前最主流的知识获取管道实现方案。它的思路非常朴素:先把知识切块存起来,用户提问时去库里捞最相关的几块,连同问题一起塞给大模型,让模型基于这些"参考资料"来回答。这个思路从 2020 年提出到现在,已经演化出了稠密嵌入、稀疏嵌入、混合检索、重排序、Agentic RAG 等一大堆变体,但底层逻辑没变。
这篇文章适合谁看?如果你正在从零搭建 AI Agent,或者已经搭了一个但发现它"不够懂业务",那知识获取管道就是你下一个必须啃下来的硬骨头。我会从整体设计思路讲到具体实操,把稠密嵌入和稀疏嵌入的区别、向量库怎么选、切块策略怎么定、检索效果怎么调,全部拆开揉碎讲清楚。不堆概念,只讲能落地的东西。
2. 知识获取管道的整体设计与方案选型
2.1 一条完整的知识管道长什么样
很多人对 RAG 的理解停留在"向量数据库 + 大模型"这个层面,实际上一条能用的知识获取管道至少包含五个环节,每个环节都会影响最终效果。
第一个环节是文档加载与解析。你的知识可能散落在 PDF、Word、Markdown、网页、数据库里,格式五花八门。解析质量直接决定了后面所有环节的上限——如果 PDF 里的表格被解析成一堆乱码,后面检索再精准也是白搭。
第二个环节是切块(Chunking)。大模型有上下文长度限制,你不可能把整本手册塞进去,所以要把长文档切成小块。切块策略是 RAG 里最容易被低估的环节,切得太碎会丢失上下文,切得太大会引入噪声。
第三个环节是嵌入(Embedding)。把每个文本块转换成一串数字向量,让语义相近的文本在向量空间里距离更近。这里就涉及到稠密嵌入和稀疏嵌入两条技术路线,后面会详细展开。
第四个环节是存储与索引。向量存到哪里?用专门的向量数据库还是传统数据库加扩展?索引怎么建才能保证检索速度?
第五个环节是检索与重排。用户提问时,把问题也转成向量,去库里找最相似的块。但单纯的向量相似度往往不够准,需要配合关键词检索、重排序模型等手段来提升精度。
这五个环节串起来,才是一条完整的知识获取管道。任何一环掉链子,整个 Agent 的问答质量都会崩。
2.2 稠密嵌入与稀疏嵌入:两条路线的取舍
这是 RAG 里最核心的技术选型问题,值得单独拎出来讲。
稠密嵌入(Dense Embedding)是把文本映射成一个固定长度的稠密向量,比如 768 维或 1024 维,每一维都是浮点数。它的优势是能捕捉语义相似性——"如何申请年假"和"休假流程是什么"这两句话字面完全不同,但稠密嵌入能把它们映射到相近的位置。代表模型有 BGE、M3E、text-embedding-3 系列等。
稀疏嵌入(Sparse Embedding)则是高维稀疏向量,绝大多数维度是零,只有少数维度有值。最典型的就是 BM25 这类基于词频的算法,它本质上是在做关键词匹配。它的优势是精确——用户搜"RTX 4090 显卡",它就能精准命中包含这几个词的文档,不会因为语义相近就召回一堆无关内容。
两条路线各有软肋。稠密嵌入对专有名词、型号、代码标识符这类精确匹配场景表现很差,你搜"ERR_5021"它可能给你返回一堆讲错误处理的通用文档。稀疏嵌入则完全不懂语义,用户换个说法它就抓瞎。
所以现在主流的做法是混合检索(Hybrid Search):同时跑稠密和稀疏两路召回,然后用 RRF(Reciprocal Rank Fusion)之类的算法把两路结果融合排序。实测下来,混合检索在大多数场景下比单用一路的召回率高 15% 到 30%。这个提升幅度,值得你多花那点计算资源。
2.3 为什么我不建议一上来就上 GraphRAG
最近GraphRAG、Ontology RAG这些概念很火,很多人在搭第一个 RAG 项目时就想着上知识图谱。我的建议是:先别。
GraphRAG 的核心思路是把知识抽成实体和关系,构建图谱,检索时沿着图谱游走。它在处理"跨文档的多跳推理"这类问题上确实有优势,比如"张三负责的项目里,哪些用到了李四团队开发的组件"这种需要串联多个实体的问题。
但它的代价也很明显。构建图谱需要额外的实体抽取和关系抽取步骤,这一步要么烧钱调大模型,要么本地跑一个抽取模型,成本高、耗时长。而且抽取质量不稳定,实体识别错了整个图谱就歪了。对于绝大多数"文档问答"场景,一套调好的混合检索 RAG 已经够用,上 GraphRAG 属于杀鸡用牛刀。
我的经验是:先用基础 RAG 跑通,把检索命中率(RAG hit rate)调到 80% 以上,再考虑要不要上图谱。如果基础 RAG 都调不好,图谱只会让问题更复杂。
3. 核心细节解析与实操要点
3.1 切块策略:决定检索质量的第一道关
切块这件事,看起来就是"把长文本切成小段",但里面的门道很多。
最粗暴的做法是固定长度切块,比如每 500 个字符切一刀。这种做法的问题是经常把一句话、一个段落从中间切断,导致语义不完整。你检索到的块可能前半句在讲 A,后半句在讲 B,模型看了也懵。
稍微好一点的是按语义边界切块,优先在段落、句子、标题这些自然边界处切分。这样每个块内部语义相对完整。实现上可以按换行符、句号、分号这些标点做递归切分,LangChain 的 RecursiveCharacterTextSplitter 就是这个思路。
再进阶一点是带重叠的切块(Overlapping Chunking)。相邻两个块之间保留一定比例的重叠内容,比如 10% 到 20%。这样做的好处是,即使一个关键信息正好落在切分点上,它也会同时出现在前后两个块里,不至于丢失。代价是存储和检索的计算量会增加。
关于块大小,没有一个放之四海皆准的数字,但有一些经验值可以参考:
| 场景类型 | 建议块大小 | 重叠比例 | 理由 |
|---|---|---|---|
| 技术文档、API 手册 | 300-500 字符 | 15% | 内容密度高,小块更精准 |
| 长篇文章、报告 | 500-800 字符 | 10% | 需要保留段落完整性 |
| 对话记录、FAQ | 200-400 字符 | 20% | 一问一答本身就很短 |
| 法律、合同文本 | 800-1200 字符 | 10% | 条款之间关联性强 |
注意:块大小不是越小越好。块太小会导致单个块信息量不足,模型拿到手也不知道在说什么;块太大则会引入无关噪声,稀释关键信息。我一般会先用 500 字符起步,然后根据实际检索效果微调。
还有一个容易被忽略的点:给每个块加上下文元数据。比如这个块来自哪个文档、哪一章、哪一节。检索时可以把这个元数据一起返回,让模型知道这段内容的出处。更高级的做法是给每个块生成一个简短的摘要,检索时用摘要匹配,返回时用原文,这样能兼顾检索精度和上下文完整性。
3.2 嵌入模型选型:别只看排行榜
嵌入模型的选择直接决定了稠密检索的效果。市面上的模型很多,选型时不能只看 MTEB 排行榜,还要考虑实际约束。
中文场景下,BGE 系列(如 bge-large-zh-v1.5)是目前比较稳妥的选择,在中文语义相似度任务上表现稳定,社区支持也好。M3E 系列也是常见选项,体积相对小一些,适合资源受限的场景。如果追求更好的效果且不介意调用外部服务,一些商业嵌入 API 的表现确实更强,但要注意数据隐私和调用成本。
多语言场景下,需要选支持多语言的模型,比如 BGE-M3,它同时支持稠密、稀疏和多向量检索,一个模型搞定多种需求,省去了维护多套模型的麻烦。
选型时要重点看几个指标。向量维度影响存储和检索速度,768 维和 1024 维在效果上可能只差几个百分点,但存储成本差不少。最大输入长度决定了单个块能有多长,大部分模型支持 512 个 token,超过就会被截断,所以块大小要配合模型的最大长度来定。推理速度在批量处理大量文档时很关键,本地部署的话要考虑 GPU 显存够不够。
实操心得:嵌入模型和检索时的查询向量必须用同一个模型生成。我见过有人建库时用模型 A,查询时用模型 B,结果检索效果惨不忍睹。这个坑一定要避开。
3.3 向量数据库:从轻量到企业级的选型
向量存哪里,取决于你的数据规模和部署环境。
小规模场景(几万条以内),用 FAISS 就够了。它是 Facebook 开源的向量检索库,纯内存运行,速度快,部署简单,一个 Python 包搞定。缺点是数据存在内存里,重启就没了,需要自己做持久化。
中等规模场景(几十万到几百万条),可以考虑 Chroma、Qdrant、Milvus 这些专门的向量数据库。Chroma 上手最简单,适合快速原型;Qdrant 性能好,支持过滤和混合检索;Milvus 功能最全,但部署和维护成本也最高。
企业级场景,如果团队已经在用 PostgreSQL,可以直接上 pgvector 扩展,省去维护独立向量库的麻烦。如果用的是 Java 技术栈,Spring AI已经提供了对多种向量库的统一抽象,切换底层实现时改动很小。
选型时除了看规模,还要考虑是否支持元数据过滤。比如你只想在"2024 年之后的文档"里检索,向量库得支持按元数据筛选后再做向量搜索。这个功能在实际项目里非常常用,选型时一定要确认。
4. 实操过程与核心环节实现
4.1 从零搭建一条最小可用的知识管道
下面我用 Python 走一遍完整流程,把一条最小可用的 RAG 管道搭起来。这套代码可以直接抄作业,改改路径就能跑。
第一步,加载和切块。假设你的知识是一堆 Markdown 文件:
from langchain_community.document_loaders import DirectoryLoader from langchain_text_splitters import RecursiveCharacterTextSplitter # 加载目录下所有 markdown 文件 loader = DirectoryLoader('./knowledge', glob='**/*.md') docs = loader.load() # 递归切块,优先在段落、句子边界切分 splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=75, # 15% 重叠 separators=['\n\n', '\n', '。', '!', '?', ';', ',', ' ', ''] ) chunks = splitter.split_documents(docs) print(f'共切出 {len(chunks)} 个块')这里 separators 的顺序很重要,它会按顺序尝试用这些分隔符切分,优先用靠前的。把段落分隔符放在最前面,能最大程度保证语义完整。
第二步,生成嵌入并存入向量库。这里用 Chroma 做演示:
from langchain_community.embeddings import HuggingFaceBgeEmbeddings from langchain_community.vectorstores import Chroma # 加载中文嵌入模型 embedding = HuggingFaceBgeEmbeddings( model_name='BAAI/bge-large-zh-v1.5', model_kwargs={'device': 'cuda'}, encode_kwargs={'normalize_embeddings': True} ) # 建库并持久化 vectorstore = Chroma.from_documents( documents=chunks, embedding=embedding, persist_directory='./chroma_db' )normalize_embeddings=True这个参数别漏了,它把向量归一化到单位长度,这样计算余弦相似度时可以直接用点积,速度快很多,效果也稳定。
第三步,检索。基础检索就是拿问题去向量库里找最相似的几个块:
query = '报销流程需要哪些材料' results = vectorstore.similarity_search_with_score(query, k=5) for doc, score in results: print(f'相似度: {score:.4f}') print(doc.page_content[:200]) print('---')k=5表示返回最相似的 5 个块。这个数字也不是随便定的,太小可能漏掉关键信息,太大则会引入噪声。一般 3 到 8 之间比较合适,具体看你的块大小和文档密度。
4.2 混合检索的实现:稠密加稀疏
单走向量检索在遇到精确匹配需求时会翻车,所以要把 BM25 加进来。LangChain 提供了 EnsembleRetriever 来做这件事:
from langchain_community.retrievers import BM25Retriever from langchain.retrievers import EnsembleRetriever # 稀疏检索:BM25 bm25_retriever = BM25Retriever.from_documents(chunks) bm25_retriever.k = 5 # 稠密检索:向量库 dense_retriever = vectorstore.as_retriever(search_kwargs={'k': 5}) # 融合两路结果,权重各占一半 ensemble_retriever = EnsembleRetriever( retrievers=[bm25_retriever, dense_retriever], weights=[0.5, 0.5] ) results = ensemble_retriever.invoke('ERR_5021 错误怎么处理')权重怎么定?如果业务里精确匹配需求多(比如查型号、查错误码),可以把 BM25 权重调高到 0.6 甚至 0.7;如果更多是自然语言问答,向量检索权重可以高一些。这个需要根据实际数据调,没有标准答案。
4.3 重排序:把最相关的顶上来
混合检索解决了"召回"问题,但召回的块排序未必最优。这时候可以加一个**重排序(Rerank)**环节,用一个专门的交叉编码器模型对召回的块重新打分排序。
交叉编码器和嵌入模型的区别在于:嵌入模型是分别把问题和文档转成向量再算相似度,速度快但精度有限;交叉编码器是把问题和文档拼在一起送进模型,直接输出相关性分数,精度高但速度慢。所以典型做法是:先用向量检索快速召回一批候选(比如 20 个),再用重排序模型精排,取前 5 个送给大模型。
from langchain.retrievers import ContextualCompressionRetriever from langchain_community.document_compressors import CrossEncoderReranker from langchain_community.cross_encoders import HuggingFaceCrossEncoder # 加载重排序模型 cross_encoder = HuggingFaceCrossEncoder(model_name='BAAI/bge-reranker-large') reranker = CrossEncoderReranker(model=cross_encoder, top_n=5) # 在混合检索基础上加重排序 compression_retriever = ContextualCompressionRetriever( base_compressor=reranker, base_retriever=ensemble_retriever ) final_results = compression_retriever.invoke('报销流程需要哪些材料')加了重排序之后,检索精度通常能再提升 10% 到 20%。代价是每次查询多了一次模型推理,延迟会增加几百毫秒。如果对响应速度要求极高,可以权衡是否加这一层。
4.4 把检索结果喂给大模型
最后一步是把检索到的块和用户问题拼成 Prompt,送给大模型生成回答:
def build_prompt(query, contexts): context_text = '\n\n'.join([c.page_content for c in contexts]) return f'''基于以下参考资料回答问题。如果资料中没有相关信息,请明确说明"资料中未提及",不要编造。 参考资料: {context_text} 问题:{query} 回答:'''这个 Prompt 模板有两个关键点。一是明确要求"基于参考资料回答",防止模型自由发挥;二是要求"没有就说没有",这是抑制幻觉最有效的手段之一。我试过很多种 Prompt 写法,这两句话是性价比最高的。
5. 常见问题与排查技巧实录
5.1 检索命中率低的排查思路
RAG hit rate是衡量知识管道质量的核心指标,指的是"应该被检索到的块,实际被检索到的比例"。如果命中率低,按下面的顺序排查。
先看切块是否合理。把检索失败的案例拿出来,看看正确答案所在的块长什么样。如果块被切得支离破碎,语义不完整,那问题出在切块环节,调整 chunk_size 和 separators。
再看嵌入模型是否匹配。中文文档用了英文模型,或者专业领域用了通用模型,都会导致语义捕捉不准。可以拿几个典型问题手动算一下和正确答案块的相似度,如果相似度普遍偏低,考虑换模型。
然后看检索方式是否单一。纯向量检索在精确匹配场景下天然弱势,加上 BM25 做混合检索往往能立竿见影。
最后看是否需要重排序。如果召回的块里其实有正确答案,只是排在了后面没被取到,那加重排序就能解决。
5.2 常见问题速查表
| 问题现象 | 可能原因 | 排查方向 | 解决手段 |
|---|---|---|---|
| 答非所问 | 检索到的块不相关 | 检查切块和嵌入模型 | 调整块大小,换嵌入模型 |
| 精确查询失败 | 纯向量检索不擅长精确匹配 | 确认是否用了混合检索 | 加入 BM25,调高稀疏权重 |
| 答案不完整 | 召回块数量不足 | 检查 top_k 设置 | 增大 k 值,加重排序 |
| 模型编造答案 | Prompt 未约束或检索为空 | 检查检索结果是否为空 | 优化 Prompt,加"无则说明"约束 |
| 响应太慢 | 重排序或嵌入模型太大 | 分析各环节耗时 | 换小模型,或异步处理 |
| 新文档检索不到 | 索引未更新 | 检查文档是否入库 | 建立增量更新机制 |
5.3 几个踩过的坑
坑一:块重叠设置过大导致重复召回。有一次我把重叠设成了 50%,结果检索出来的 5 个块里有 3 个内容高度重复,浪费了上下文窗口。重叠比例控制在 10% 到 20% 就够了,别贪多。
坑二:忽略元数据过滤导致跨领域污染。一个项目里同时有产品和财务两类文档,用户问产品问题却召回了财务文档。后来给每个块打上 category 标签,检索时先按标签过滤,问题就解决了。
坑三:嵌入模型没归一化导致相似度计算错误。这个坑很隐蔽,因为不归一化也能算出结果,只是排序会乱。用余弦相似度时一定要确认向量是否归一化,或者直接用支持余弦距离的向量库。
坑四:文档更新后忘记重建索引。知识库是活的,文档会增删改。如果没有增量更新机制,Agent 就会一直用旧知识回答。建议把索引更新做成定时任务,或者文档变更时触发重建。
独家技巧:建库时给每个块生成一个"假设性问题"。具体做法是让大模型针对每个块生成 2 到 3 个它可能回答的问题,把这些问题和块一起存进向量库。检索时用户的问题更容易匹配到这些假设性问题,命中率能提升不少。这个技巧来自一些前沿的 RAG 实践,实测有效。
6. 知识管道的进阶方向
基础 RAG 跑通之后,如果还想继续提升,有几个方向可以探索。
Agentic RAG是最近比较热的方向。传统 RAG 是"一次检索,一次生成",Agentic RAG 则让 Agent 自己决定要不要检索、检索几次、用什么查询词检索。比如用户问一个复杂问题,Agent 可以先拆解成几个子问题,分别检索,再综合回答。这种模式对复杂查询的处理能力明显更强,但实现复杂度也更高,需要 Agent 框架的支持。
多路查询改写是另一个性价比很高的优化。用户的问题往往表述不理想,可以先用大模型把问题改写成几个不同角度的查询,分别检索后合并结果。比如"怎么报销"可以改写成"报销流程"、"报销所需材料"、"报销审批步骤"等,覆盖面更广。
知识图谱融合适合有大量实体关系的场景。把 RAG 和轻量级的知识图谱结合,用图谱来处理实体间的关联查询,用向量检索来处理语义查询,两者互补。
不过还是那句话,别一上来就追求最复杂的方案。把基础管道的每个环节调扎实,命中率做到 85% 以上,比堆一堆花哨的技术但每个都半吊子要强得多。我在实际项目里的体会是,RAG 的效果提升,80% 来自切块策略和检索方式的优化,剩下 20% 才来自那些高级技巧。先把地基打牢,再考虑盖楼。