ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AI Agent知识获取管道:RAG基础架构与混合检索实战

AI Agent知识获取管道:RAG基础架构与混合检索实战

1. 为什么知识获取管道是 AI Agent 落地的第一道坎

做 AI Agent 的人迟早会撞上同一堵墙:模型本身很聪明,但你问它公司上个月的报销政策、某个客户的合同条款、你们内部那套跑了八年的 ERP 字段含义,它要么一本正经地胡说,要么干脆告诉你"我没有这方面的信息"。这不是模型不行,而是它的知识边界停在了训练数据截止的那一天。知识获取管道要解决的,就是把这个边界往后推、往深挖,让 Agent 在推理时能拿到外部真实、最新、私有的知识。

在整条 Agent 链路里,知识获取管道的位置非常靠前。你可以把 Agent 想象成一个刚入职的高材生:脑子好使,但对你公司的业务一无所知。RAG(Retrieval-Augmented Generation,检索增强生成)就是给他配的那套"资料室 + 检索员"系统——他不需要把所有资料背进脑子,只需要在回答问题时,能快速从资料室里翻出最相关的几页,然后基于这几页组织答案。这个类比里,"资料室"是知识库,"检索员"是检索器,"翻出最相关的几页"就是稠密嵌入和稀疏嵌入在背后干的活。

我见过太多团队在 Agent 项目上翻车,不是因为模型选错了,而是因为知识管道没搭好。检索召回率上不去,后面接再强的模型也是垃圾进垃圾出。所以这一篇我不打算讲虚的,直接把 RAG 基础里最核心的几个环节拆开:文档怎么切、向量怎么算、稠密和稀疏怎么配合、检索结果怎么重排、以及那些文档里不会写但实际会踩的坑。适合正在从 0 到 1 搭建 AI Agent 的开发者,也适合已经用了 RAG 但效果不理想的同学对照排查。

2. RAG 基础架构的整体设计与选型思路

2.1 一条完整的知识获取管道长什么样

很多人对 RAG 的理解停留在"把文档塞进向量库,然后相似度检索"这一步。这只是管道的一小段。一条能上生产的知识获取管道,至少包含五个环节:文档解析与清洗、分块(Chunking)、嵌入(Embedding)、索引与存储、检索与重排。每个环节都会影响最终的召回质量,而且它们之间是乘法关系——任何一环拉胯,整体效果都会断崖式下跌。

我习惯把这五个环节画成一条流水线来理解。原始文档进来,先做解析,把 PDF、Word、HTML、数据库记录统一成纯文本;然后按语义或固定长度切块,每块单独算向量;向量连同原文、元数据一起写进向量库;用户提问时,问题也走一遍嵌入,去库里做相似度匹配,召回 Top-K 候选;最后用重排模型对候选精排,把最相关的几块喂给大模型生成答案。这条链路里,分块策略和检索策略是两个最容易被低估、也最影响效果的环节。

为什么强调"管道"这个词?因为它意味着数据是流动的、可迭代的。你今天切的分块策略,明天发现召回不行,得能快速重切、重建索引,而不是推倒重来。所以架构设计之初就要考虑:分块参数是否可配置、嵌入模型是否可替换、索引是否支持增量更新。这些决定了你后续调优的成本。

2.2 稠密嵌入与稀疏嵌入:为什么两个都要

这是 RAG 基础里最值得讲清楚的一对概念。稠密嵌入(Dense Embedding)把一段文本映射成一个几百到几千维的稠密向量,每一维都是浮点数,语义相近的文本在向量空间里距离近。它的强项是语义匹配——你问"怎么报销差旅费",它能召回写着"出差费用申请流程"的文档,哪怕字面一个词都不重合。稀疏嵌入(Sparse Embedding)则是高维稀疏向量,绝大多数维度是零,只有出现过的词对应的维度有值,典型代表就是 BM25 这类基于词频的算法。它的强项是精确匹配——你搜一个产品型号"XR-2000A",它能精准命中,而稠密嵌入很可能把这个型号和"XR-2000B"混为一谈。

只用一个行不行?实测下来,单用稠密嵌入,遇到专有名词、型号、代码、人名这类需要精确匹配的场景,召回经常掉链子;单用稀疏嵌入,遇到同义改写、口语化提问,又完全抓瞎。所以现在主流的做法是混合检索(Hybrid Retrieval):两路并行召回,再用融合算法(比如 RRF,Reciprocal Rank Fusion)把结果合并。RRF 的逻辑很简单,不看绝对分数,只看排名,把两路结果里排名靠前的文档加权求和,避免了两路分数尺度不一致的问题。

提示:混合检索不是银弹。如果你的知识库全是自然语言叙述类文档,稠密嵌入单独用可能就够了;如果全是结构化字段、型号、编号,稀疏嵌入的权重应该调高。别盲目上混合,先搞清楚你的数据长什么样。

2.3 分块策略:切得好比嵌入模型选得好更重要

我个人的经验是,分块策略对召回效果的影响,往往比换一个更贵的嵌入模型还大。原因很简单:嵌入模型再强,它也只能对你给它的那块文本负责。如果一块文本里混了三个不相关的主题,算出来的向量就是个"四不像",检索时哪个主题都匹配不准。

常见的分块方式有三种。固定长度分块最简单,按字符数或 token 数切,比如每 512 个 token 一块,块之间留 50 token 重叠。优点是实现快、块大小均匀;缺点是经常在句子中间切断,语义不完整。语义分块按段落、标题、句子边界切,尽量保证每块是一个完整语义单元。递归分块是前两者的折中,先按大分隔符(段落)切,太大再按小分隔符(句子)切,直到满足大小限制。LangChain 的RecursiveCharacterTextSplitter就是典型实现。

块大小怎么定?没有万能值,但有个经验区间:中文场景 300 到 800 字,英文场景 256 到 1024 token。块太小,上下文不足,模型拿到碎片拼不出完整答案;块太大,噪声多,检索精度下降。重叠部分建议是块大小的 10% 到 20%,防止关键信息正好卡在切割边界上被劈成两半。

3. 核心环节的实操要点与参数细节

3.1 文档解析:脏数据是召回率的第一杀手

真实项目里,文档解析花的时间经常比调模型还多。PDF 里的表格、扫描件里的文字、Word 里的批注、HTML 里的导航栏,全是噪声。我踩过最深的坑是一个客户的知识库,PDF 用双栏排版,解析出来文字顺序全乱,两栏内容交错在一起,检索出来的东西驴唇不对马嘴。

处理原则是:能拿到结构化源就别用解析。如果知识来自数据库,直接查字段;如果来自 Confluence、Notion 这类有 API 的平台,走 API 拿干净的 Markdown;只有实在没有源文件时,才上 PDF 解析。PDF 解析工具里,PyMuPDF对文本型 PDF 效果好,扫描件得先走 OCR。表格建议单独抽出来转成 Markdown 或 JSON,别混在正文里。

清洗环节要做的事包括:去掉页眉页脚、合并被硬换行拆断的句子、统一全半角、去掉多余空白。这些看着琐碎,但每一条都直接影响后续分块和嵌入的质量。我一般会写一个清洗函数,把常见噪声用正则批量处理,处理完抽样人工看几十条,确认没有明显问题再往下走。

3.2 嵌入模型选型:别只看排行榜

嵌入模型的选择要看三个维度:语言支持、维度、以及是否支持本地部署。中文场景优先选对中文优化过的模型,很多英文榜单第一的模型在中文上表现平平。维度方面,768 维和 1024 维是常见选择,维度越高表达能力越强,但存储和计算成本也越高。一个百万级文档的知识库,1024 维向量用 float32 存储,光向量就占 4GB 左右,这个账要提前算。

本地部署还是调 API?涉及私有数据、合规要求高的场景,本地部署是刚需。开源嵌入模型里,BGE 系列、M3E 系列在中文检索任务上表现稳定,社区验证充分。调 API 的好处是省事、模型更新快,但数据要出域,成本和延迟也要考虑。我的建议是:先用 API 快速验证流程跑通,效果达标后再评估是否迁移到本地,别一上来就纠结部署方式,把流程跑通才是第一优先级。

注意:嵌入模型一旦选定,整个知识库的向量都得用它来算。中途换模型意味着全量重建索引。所以选型时多花点时间做对比测试,用你自己的真实数据测召回,别信通用榜单。

3.3 向量库与索引:HNSW 参数怎么调

向量库选型上,小规模(十万级以下)用 FAISS 或 Chroma 就够,部署简单;中大规模(百万级以上)考虑 Milvus、Qdrant、Weaviate 这类专业向量库,支持分布式和更丰富的索引类型。选型的核心考量是:数据规模、是否需要增量更新、是否需要元数据过滤、运维成本。

索引类型里,**HNSW(Hierarchical Navigable Small World)**是目前最常用的近似最近邻索引。它有两个关键参数:M和efConstruction。M是每个节点的连接数,越大图越密,召回越高但内存占用越大,常用值 16 到 64;efConstruction是建索引时的搜索宽度,越大索引质量越高但建得越慢,常用值 100 到 200。查询时还有个efSearch参数,越大召回越高但查询越慢,需要在召回和延迟之间权衡。

我一般这样调:先用默认值(M=16,efConstruction=200)建索引,测召回率和延迟;如果召回不够,先加efSearch,因为它只影响查询不影响索引;还不够再考虑重建索引调大M。这个顺序能让你用最小成本找到合适的参数组合。

4. 完整实操流程:从零搭一条可用的 RAG 管道

4.1 环境准备与依赖安装

先把基础环境搭起来。Python 3.10 以上,核心依赖包括文档解析、分块、嵌入、向量库四类。下面是一套我常用的组合,你可以按需替换。

pip install pymupdf langchain langchain-community pip install sentence-transformers faiss-cpu pip install rank-bm25 jieba

sentence-transformers用来加载本地嵌入模型,faiss-cpu是向量库,rank-bm25加jieba用来做中文稀疏检索。如果你的数据量大或者要上生产,把faiss-cpu换成faiss-gpu或者专业向量库的客户端。

4.2 文档解析与分块实现

假设我们有一批 PDF 文档,先解析再分块。这里用递归分块,中文按标点和段落切。

import fitz from langchain.text_splitter import RecursiveCharacterTextSplitter def parse_pdf(path): doc = fitz.open(path) text = "" for page in doc: text += page.get_text() return text def clean_text(text): import re text = re.sub(r'\n{3,}', '\n\n', text) text = re.sub(r'[ \t]{2,}', ' ', text) return text.strip() splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=80, separators=["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""] ) raw = parse_pdf("handbook.pdf") cleaned = clean_text(raw) chunks = splitter.split_text(cleaned) print(f"共切出 {len(chunks)} 块")

chunk_size=500是字符数,中文场景这个值比较稳。chunk_overlap=80是 16% 的重叠,防止边界信息丢失。separators列表的顺序很重要,它会优先按段落切,段落太大再按句子切,最后才按字符切,这样能最大程度保证语义完整。

4.3 稠密与稀疏双路索引构建

接下来算嵌入、建索引。稠密用本地模型,稀疏用 BM25。

from sentence_transformers import SentenceTransformer import faiss import numpy as np from rank_bm25 import BM25Okapi import jieba model = SentenceTransformer("BAAI/bge-base-zh-v1.5") # 稠密索引 embeddings = model.encode(chunks, normalize_embeddings=True) dim = embeddings.shape[1] index = faiss.IndexFlatIP(dim) index.add(np.array(embeddings).astype("float32")) # 稀疏索引 tokenized = [list(jieba.cut(c)) for c in chunks] bm25 = BM25Okapi(tokenized)

这里用IndexFlatIP做精确内积检索,数据量小的时候够用。normalize_embeddings=True把向量归一化后,内积等价于余弦相似度,这是标准做法。BM25 那边用 jieba 分词,中文必须分词,否则按字切效果很差。

4.4 混合检索与重排

检索时两路并行,用 RRF 融合。

def hybrid_search(query, top_k=5, k=60): # 稠密召回 q_emb = model.encode([query], normalize_embeddings=True) _, dense_ids = index.search(np.array(q_emb).astype("float32"), top_k * 2) dense_ids = dense_ids[0].tolist() # 稀疏召回 q_tokens = list(jieba.cut(query)) scores = bm25.get_scores(q_tokens) sparse_ids = np.argsort(scores)[::-1][:top_k * 2].tolist() # RRF 融合 rrf = {} for rank, doc_id in enumerate(dense_ids): rrf[doc_id] = rrf.get(doc_id, 0) + 1 / (k + rank + 1) for rank, doc_id in enumerate(sparse_ids): rrf[doc_id] = rrf.get(doc_id, 0) + 1 / (k + rank + 1) ranked = sorted(rrf.items(), key=lambda x: x[1], reverse=True) return [chunks[i] for i, _ in ranked[:top_k]]

k=60是 RRF 的标准平滑参数,来自原论文,一般不用改。两路各召回top_k * 2个候选,融合后取前top_k,给重排留了余量。如果对精度要求高,可以在融合后再接一个交叉编码器重排模型,比如 BGE 的 reranker,把候选和 query 拼一起过一遍模型打分,精度能再提一截,代价是延迟增加。

4.5 接入大模型生成答案

最后把召回的内容拼进 prompt,交给大模型。

def build_prompt(query, contexts): ctx = "\n\n---\n\n".join(contexts) return f"""基于以下资料回答问题,资料中没有的信息不要编造。 资料: {ctx} 问题:{query} 回答:"""

这个 prompt 里最关键的一句是"资料中没有的信息不要编造"。RAG 最常见的失败模式就是模型在召回内容不足时开始自由发挥,加这句能显著降低幻觉率。更严格的做法是要求模型在答案里标注引用来源,方便溯源。

5. 常见问题与排查技巧实录

5.1 召回不准的排查顺序

召回不准是最常见的问题,排查要有顺序,别乱试。我一般按这个顺序走:先看分块,再看嵌入,再看检索策略,最后看 query 本身。

分块问题表现为:召回的内容总是缺头少尾,或者一块里混了多个主题。解决办法是调整chunk_size和separators,或者改用语义分块。嵌入问题表现为:语义相近的内容召不回,换个说法就找不到。这时候要检查嵌入模型是否适合你的语言和领域,必要时换模型或做微调。检索策略问题表现为:精确匹配的词召不回,或者召回一堆不相关的。这时候调混合检索的权重,或者加元数据过滤。query 问题最容易被忽略:用户提问太短、太口语、有错别字,都会影响召回。可以考虑加一层 query 改写,把用户问题扩写成更适合检索的形式。

5.2 常见问题速查表

现象可能原因排查方向
召回内容与问题无关分块过大,噪声多减小 chunk_size,优化分隔符
专有名词召不回稠密嵌入对精确匹配弱提高稀疏检索权重,加关键词过滤
同义改写召不回稀疏检索对语义弱提高稠密检索权重,换嵌入模型
答案编造不存在的信息召回不足,模型自由发挥加 prompt 约束,提高召回数量
检索延迟高索引参数过大或候选过多调小 efSearch,减少 top_k
增量更新后检索异常索引未同步更新检查增量写入逻辑,重建索引

5.3 几个文档里不会写的避坑经验

第一个坑:别用同一套参数处理所有类型的文档。产品手册、合同、FAQ、代码文档,它们的理想分块大小和检索策略都不一样。我现在的做法是按文档类型分组,每组一套分块参数,检索时按类型路由。这样虽然麻烦,但效果比一刀切好太多。

第二个坑:元数据过滤能救命。给每块加上来源、时间、部门、文档类型这些元数据,检索时先按元数据过滤再算相似度,能大幅减少不相关召回。比如用户问"今年的政策",你直接按时间过滤掉去年的文档,召回质量立刻上一个台阶。

第三个坑:定期评估,别凭感觉。RAG 效果会随着知识库增长而漂移。我建议建一个几十到上百条的评测集,每条包含问题和标准答案,每次改动后跑一遍,看召回率和答案准确率的变化。没有评测集,你根本不知道改动是变好还是变坏。

提示:评测集不用一开始就很大,20 条真实问题就能发现大部分明显问题。关键是持续维护,把线上发现的 bad case 补进去,让它越来越贴近真实使用场景。

6. 从基础 RAG 到 Agentic RAG 的演进方向

基础 RAG 跑通之后,你会发现它有几个天花板:只能单轮检索、不会判断该不该检索、不会多步推理。这就是Agentic RAG要解决的问题——让 Agent 自己决定什么时候检索、检索什么、检索几轮、结果够不够。比如用户问一个需要跨多个文档综合的问题,基础 RAG 一次召回可能只拿到部分信息,而 Agentic RAG 可以先检索、评估结果、发现不足再补检索,直到信息足够才生成答案。

再往深走,还有GraphRAG和Ontology RAG这类把知识图谱和 RAG 结合的方向。它们解决的是"知识割裂"问题:基础 RAG 把文档切成独立的块,块与块之间的关系丢了,而图谱能把实体和关系显式建模出来,让检索能沿着关系走。这些方向门槛更高,但对付复杂领域知识效果更好。

我的建议是:先把基础 RAG 的每个环节调扎实,再考虑上 Agentic 或 Graph。基础没打好就上高级架构,只会把问题藏得更深。知识获取管道这条链路,每一环都值得你花时间打磨,因为它决定了你的 Agent 到底是在"基于事实回答"还是在"一本正经地猜"。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进