1. 为什么知识获取管道是 AI Agent 的第一道生死关
做 AI Agent 开发的人,十有八九都经历过这样一个阶段:模型接上了,工具调通了,流程跑起来了,但一到真实业务场景就露馅。用户问“我们公司去年Q3的差旅报销标准是多少”,Agent 要么胡编一个数字,要么支支吾吾说“我无法获取内部信息”。这不是模型不够聪明,而是它压根没拿到正确的上下文。
知识获取管道要解决的就是这个问题。你可以把它理解成 Agent 的“进食系统”——模型是大脑,管道负责把外部世界的知识嚼碎了、分类好、在需要的时候精准投喂。没有这条管道,Agent 就是个只会背课本的书呆子;管道设计得不好,Agent 就是个消化不良的病人,喂进去再多也吸收不了。
RAG,也就是检索增强生成,是目前搭建这条管道最主流、最成熟的技术路线。它的核心逻辑不复杂:用户提问时,系统先从知识库里检索出最相关的片段,把这些片段和问题一起塞给大模型,让模型基于真实材料来回答。听起来简单,但真正落地的时候,从文档切分、向量化、索引构建到检索排序,每一步都有大量细节决定成败。
这篇文章适合三类人看:正在从零搭建 AI Agent 的开发者、已经用了 RAG 但效果不理想的工程师、以及想搞清楚 RAG 底层原理再决定技术选型的技术负责人。我会从整体设计思路讲到具体实操细节,把稠密嵌入和稀疏嵌入的取舍、检索命中率的优化、常见故障的排查都掰开揉碎讲清楚。读完你至少能搭出一个能用的知识获取管道,并且知道哪里容易踩坑。
2. 知识获取管道的整体设计与技术选型
2.1 从“知识割裂”说起:为什么传统方案不够用
很多团队一开始的做法很朴素:把公司文档全部拼成一个大文本,直接塞进模型的上下文窗口。短文档还行,一旦知识库上到几百兆,这条路就走不通了。上下文窗口再大也有上限,而且塞太多无关内容进去,模型的注意力会被稀释,回答质量反而下降。
另一种常见做法是关键词搜索。用户问“报销标准”,系统就去文档里找包含“报销”和“标准”的段落。问题是,用户可能问的是“出差费用怎么算”,字面上跟“报销标准”没有重叠,关键词匹配就失效了。这就是所谓的知识割裂——知识明明在库里,但检索环节没把它找出来。
RAG 的思路是在两者之间找平衡:用语义检索代替字面匹配,用片段召回代替全文塞入。它把知识库拆成一个个语义完整的片段,每个片段转成向量存起来。用户提问时,把问题也转成向量,在向量空间里找距离最近的片段。这样即使用户用的词跟文档里的词不一样,只要意思相近,就能被检索到。
2.2 稠密嵌入与稀疏嵌入:两条腿走路才稳
说到向量化,就绕不开稠密嵌入和稀疏嵌入这两个概念。它们不是二选一的关系,而是互补的。
稠密嵌入是把一段文本映射成一个固定长度的浮点数向量,比如 768 维或 1024 维。这个向量里的每个维度都参与表达语义,没有哪个维度是“空”的,所以叫稠密。它的优势是能捕捉深层语义关系,“出差费用”和“差旅报销”在稠密向量空间里距离很近。缺点是对于专有名词、产品型号、精确数字这类信息,稠密嵌入有时候会“糊”掉,把“A100”和“A800”当成差不多的东西。
稀疏嵌入则相反,它的向量维度跟词表大小一致,绝大多数维度是零,只有出现过的词对应的维度才有值。传统的关键词权重算法就是典型的稀疏表示。它的优势是精确匹配能力强,用户搜“ISO-9001”就一定能找到包含这个字符串的文档。缺点是无法处理同义替换和语义泛化。
实际生产环境中,我见过效果最好的方案基本都是混合检索:同时跑稠密和稀疏两路召回,然后用融合算法把两边的结果合并排序。这样既能抓住语义相似的内容,又不会漏掉精确匹配的关键信息。具体怎么融合,后面实操部分会详细讲。
2.3 管道架构的四个核心模块
一个完整的知识获取管道,不管用什么框架实现,本质上都包含四个模块。
文档处理模块负责把各种格式的原始材料(PDF、Word、HTML、数据库记录)统一转成纯文本,然后按照语义边界切分成片段。切分策略直接决定了后续检索的质量,切得太碎会丢失上下文,切得太大会引入噪声。
向量化模块把每个文本片段转成向量表示。这里要决定用哪个嵌入模型、要不要做归一化、批量处理的并发度怎么控制。嵌入模型的选择要考虑语言支持、维度大小、推理速度和成本。
存储与索引模块负责把向量和原始文本存起来,并建立高效的相似度检索索引。向量数据库的选择很多,从轻量级的本地库到分布式的云服务都有,关键看数据规模和查询延迟要求。
检索与重排模块是用户提问时真正跑起来的环节。它把用户问题转成向量,在索引里召回候选片段,然后可能经过一个重排模型做精排,最后把最相关的几个片段返回给生成模块。
这四个模块串起来就是一条完整的管道。每个模块都有优化空间,但根据我的经验,文档切分和检索重排是投入产出比最高的两个环节,值得多花时间打磨。
3. 核心细节解析与实操要点
3.1 文档切分:别让一刀切毁掉语义完整性
文档切分是很多人最容易忽视的环节。我见过不少项目直接按固定字数切,每 500 字一刀,结果一个完整的操作步骤被切成两半,检索出来的片段缺头少尾,模型看了也拼不出完整答案。
合理的切分策略应该优先尊重文档的自然结构。Markdown 文档按标题层级切,HTML 按 DOM 节点切,PDF 如果解析质量好可以按段落切。在自然结构的基础上,再控制片段长度在一个合理区间,通常 200 到 500 个 token 是比较舒服的范围。
注意:切分长度没有万能值。技术文档可以短一些,因为概念密集;叙事性内容可以长一些,因为需要上下文连贯。建议先用一批真实用户问题做测试,看检索出来的片段是否包含完整答案。
还有一个实用技巧是重叠切分。相邻两个片段之间保留 10% 到 20% 的重叠内容,这样即使答案刚好落在切分边界上,也不会完全丢失。代价是存储和检索时会多一些冗余,但相比漏检的风险,这点冗余完全值得。
对于表格和代码块,要特殊处理。表格最好整表保留,不要按行切散;代码块要保留完整的函数或类定义,不要从中间截断。这些结构化内容一旦被破坏,检索出来也没法用。
3.2 嵌入模型选型:维度不是越高越好
选嵌入模型的时候,很多人第一反应是看排行榜,哪个分数高用哪个。但实际落地要考虑的因素远不止分数。
语言支持是第一条。如果你的知识库主要是中文,就要选中文语料训练充分的模型。有些英文模型在中文上也能跑,但语义捕捉能力会打折扣。现在有不少多语言模型表现不错,可以优先考虑。
维度大小直接影响存储成本和检索速度。768 维和 1536 维的模型,存储开销差一倍,检索时的计算量也差不少。高维度通常意味着更强的表达能力,但边际收益递减很明显。我的经验是,对于大多数企业知识库场景,768 到 1024 维足够用了,没必要盲目追高。
推理速度在批量处理大量文档时很关键。有些模型效果好但推理慢,处理十万个片段可能要跑好几个小时。如果知识库更新频繁,这个时间成本就不可接受了。建议在选型阶段就用真实数据量做一次基准测试,算清楚全量处理和增量更新的耗时。
成本也是硬约束。商用嵌入 API 按 token 计费,知识库大了之后费用不小。开源的本地嵌入模型虽然省了 API 费用,但需要 GPU 资源,这笔账也要算进去。
3.3 向量索引:HNSW 与 IVF 的取舍
向量存进数据库之后,怎么快速找到最相似的 top-k 个结果,这就是索引要解决的问题。暴力遍历当然最准,但数据量上到百万级就慢得没法用了。近似最近邻算法是必须的。
HNSW(分层可导航小世界图)是目前最常用的索引结构。它构建一个多层图,查询时从顶层开始逐层向下搜索,每层都快速逼近目标区域。优点是查询速度快、召回率高,缺点是内存占用大,因为图结构本身要占不少空间。对于千万级以下的数据量,HNSW 基本是首选。
IVF(倒排文件索引)先把向量聚类成若干个簇,查询时只搜索距离最近的几个簇。优点是内存占用小,适合超大规模数据。缺点是如果目标向量刚好落在簇边界上,可能会漏掉。通常需要配合 PQ(乘积量化)来压缩向量,进一步降低内存,但会损失一些精度。
实际选型的时候,如果数据量在百万级以内,直接上 HNSW,省心效果好。如果数据量上亿,再考虑 IVF 加 PQ 的组合。参数调优方面,HNSW 的M参数控制每个节点的连接数,越大召回率越高但内存也越大,通常设 16 到 64 之间;efConstruction控制构建时的搜索范围,设大一些索引质量更好,但构建更慢。
3.4 混合检索的融合策略
稠密和稀疏两路召回的结果怎么合并,常见的有两种做法。
一种是加权求和。把两路的相似度分数归一化到同一量级,然后按权重相加。稠密检索的权重通常设高一些,比如 0.7,稀疏检索设 0.3。这个权重可以根据实际效果调,如果发现精确匹配的需求多,就调高稀疏的权重。
另一种是倒数排名融合。不看具体分数,只看排名。每个结果在两路召回中分别有一个排名,把排名的倒数加权求和作为最终分数。这种方法的优势是不需要处理分数归一化的问题,对两路分数的量纲差异不敏感。
实操心得:我一般先用倒数排名融合跑一版基线,因为不需要调归一化参数,上手快。等基线效果稳定了,再尝试加权求和,看能不能通过调权重再提升几个百分点。两种方法都试试,用真实查询集评估,别凭感觉选。
融合之后通常还要过一个重排模型。重排模型比嵌入模型更重,但精度更高,它会把候选片段和用户问题一起编码,输出一个精细的相关性分数。因为只对 top-50 或 top-100 的候选做重排,计算量可控,但效果提升往往很明显。
4. 实操过程与核心环节实现
4.1 环境准备与依赖安装
假设我们用 Python 生态来搭建这条管道。核心依赖包括文档解析库、嵌入模型库、向量数据库客户端和重排模型库。
pip install unstructured pdfplumber markdown pip install sentence-transformers pip install chromadb pip install rank-bm25 pip install FlagEmbeddingunstructured负责把各种格式的文档转成纯文本,pdfplumber专门处理 PDF 表格提取,sentence-transformers提供稠密嵌入模型,chromadb是轻量级向量数据库,rank-bm25实现稀疏检索,FlagEmbedding提供重排模型。
如果你打算用 GPU 加速嵌入推理,还需要装对应版本的 PyTorch 和 CUDA 工具包。CPU 也能跑,但处理大量文档时速度差距很明显。
4.2 文档加载与智能切分
先写文档加载逻辑。不同格式的文件走不同的解析器,统一输出成带元数据的文本块。
from unstructured.partition.auto import partition from langchain.text_splitter import RecursiveCharacterTextSplitter def load_and_split(file_path): elements = partition(filename=file_path) text = "\n".join([str(el) for el in elements]) splitter = RecursiveCharacterTextSplitter( chunk_size=400, chunk_overlap=80, separators=["\n## ", "\n### ", "\n\n", "\n", "。", " "] ) chunks = splitter.split_text(text) return chunks这里chunk_size设 400 个字符,chunk_overlap设 80 个字符,重叠比例 20%。分隔符列表按优先级排列,优先在二级标题、三级标题处切分,其次才是空行和句号。这样能最大程度保留语义完整性。
对于表格,单独走一条路径。用pdfplumber提取表格后,把整个表格转成 Markdown 格式作为一个独立片段,不参与常规切分。
4.3 稠密向量生成与入库
嵌入模型选BAAI/bge-large-zh-v1.5,这是目前中文场景下综合表现很稳的一个模型,1024 维,对中文语义捕捉到位。
from sentence_transformers import SentenceTransformer import chromadb model = SentenceTransformer('BAAI/bge-large-zh-v1.5') client = chromadb.PersistentClient(path="./vector_store") collection = client.get_or_create_collection( name="knowledge_base", metadata={"hnsw:space": "cosine"} ) def embed_and_store(chunks, source): embeddings = model.encode(chunks, normalize_embeddings=True) ids = [f"{source}_{i}" for i in range(len(chunks))] metadatas = [{"source": source, "chunk_index": i} for i in range(len(chunks))] collection.add( embeddings=embeddings.tolist(), documents=chunks, ids=ids, metadatas=metadatas )normalize_embeddings=True把向量归一化到单位长度,这样余弦相似度计算就等价于内积,检索时更快。hnsw:space设成 cosine 表示用余弦距离。
批量处理的时候,不要一条一条 encode,把 chunks 攒成 batch 一起跑,GPU 利用率高很多。batch size 根据显存大小调,一般 32 到 128 之间。
4.4 稀疏检索索引构建
稀疏检索用 BM25 算法,它根据词频和逆文档频率给每个词打分,是信息检索领域的经典方法。
from rank_bm25 import BM25Okapi import jieba def build_sparse_index(chunks): tokenized = [list(jieba.cut(chunk)) for chunk in chunks] bm25 = BM25Okapi(tokenized) return bm25, tokenized def sparse_search(bm25, tokenized, query, top_k=20): query_tokens = list(jieba.cut(query)) scores = bm25.get_scores(query_tokens) top_indices = sorted(range(len(scores)), key=lambda i: scores[i], reverse=True)[:top_k] return top_indices, [scores[i] for i in top_indices]中文需要先分词,jieba是最常用的分词库。BM25 的k1和b参数控制词频饱和度和文档长度归一化,默认值 1.5 和 0.75 在大多数场景下够用。如果发现长文档被系统性压低,可以把b调小一些。
4.5 混合检索与重排的完整流程
把稠密和稀疏两路串起来,加上重排,就是完整的检索流程。
from FlagEmbedding import FlagReranker reranker = FlagReranker('BAAI/bge-reranker-large', use_fp16=True) def hybrid_search(query, top_k=5): # 稠密检索 query_embedding = model.encode([query], normalize_embeddings=True) dense_results = collection.query( query_embeddings=query_embedding.tolist(), n_results=20 ) # 稀疏检索 sparse_indices, sparse_scores = sparse_search(bm25, tokenized_chunks, query, top_k=20) # 倒数排名融合 rrf_scores = {} for rank, doc_id in enumerate(dense_results['ids'][0]): rrf_scores[doc_id] = rrf_scores.get(doc_id, 0) + 1 / (60 + rank + 1) for rank, idx in enumerate(sparse_indices): doc_id = f"doc_{idx}" rrf_scores[doc_id] = rrf_scores.get(doc_id, 0) + 1 / (60 + rank + 1) # 取融合后的 top-20 做重排 candidates = sorted(rrf_scores.items(), key=lambda x: x[1], reverse=True)[:20] candidate_texts = [get_doc_text(doc_id) for doc_id, _ in candidates] # 重排 pairs = [[query, text] for text in candidate_texts] rerank_scores = reranker.compute_score(pairs) reranked = sorted(zip(candidate_texts, rerank_scores), key=lambda x: x[1], reverse=True) return reranked[:top_k]倒数排名融合里的常数 60 是经验值,来自原始论文,作用是平滑排名差异,让靠前的结果优势不那么极端。重排模型用bge-reranker-large,它比嵌入模型大不少,但只对 20 个候选做推理,延迟可以接受。
4.6 参数调优的实测记录
我在一个约 5 万片段的知识库上做过一轮参数对比测试,用 200 条真实用户问题做评估集,看 top-5 命中率。
| 配置 | 稠密 top-20 | 稀疏 top-20 | 融合方式 | 重排 | top-5 命中率 |
|---|---|---|---|---|---|
| A | 是 | 否 | 无 | 否 | 72% |
| B | 是 | 是 | RRF | 否 | 81% |
| C | 是 | 是 | RRF | 是 | 89% |
| D | 是 | 是 | 加权 0.7/0.3 | 是 | 90% |
从 A 到 B 的提升说明混合检索确实有效,稀疏路补充了稠密路漏掉的精确匹配。从 B 到 C 的提升说明重排模型价值很大,它能把真正相关的片段从候选池里挑出来。从 C 到 D 的提升很小,说明 RRF 已经够用,加权求和调参的边际收益有限。
实操心得:如果你的资源有限,优先保证稠密检索加混合融合,重排模型可以后面再加。但如果对精度要求高,重排是必须的,它带来的提升比换更大的嵌入模型更明显。
5. 常见问题与排查技巧实录
5.1 检索命中率低的排查思路
检索效果不好的时候,不要急着换模型,先按顺序排查这几个环节。
第一步,看切分质量。把检索出来的片段打印出来,人工判断它是否包含完整答案。如果片段明显缺头少尾,问题出在切分策略上,调整 chunk_size 和分隔符优先级。
第二步,看嵌入模型是否匹配语言。如果知识库是中文但用了英文为主的嵌入模型,语义捕捉会打折扣。换一个中文或多语言模型试试。
第三步,看查询改写。用户的问题往往口语化、省略多,直接拿去检索效果不好。可以在检索前加一步查询改写,用大模型把用户问题改写成更适合检索的形式,或者生成多个查询变体分别检索再合并。
第四步,看是否需要领域微调。如果知识库涉及大量专业术语,通用嵌入模型可能区分不开。可以用领域数据对嵌入模型做微调,让它在你的专业语境下更敏感。
5.2 常见故障速查表
| 现象 | 可能原因 | 排查方法 | 解决方向 |
|---|---|---|---|
| 检索结果完全不相关 | 嵌入模型加载错误或维度不匹配 | 检查模型输出维度与索引维度是否一致 | 重新生成向量并重建索引 |
| 精确匹配查不到 | 稀疏检索未启用或分词错误 | 单独测试 BM25 检索结果 | 检查分词器配置,补充自定义词典 |
| 响应延迟高 | 重排模型推理慢或候选集太大 | 分别计时各环节耗时 | 减小候选集,或用更小的重排模型 |
| 新文档检索不到 | 增量更新未触发索引重建 | 检查文档入库流程 | 确保新增文档走完整的嵌入和索引流程 |
| 相似文档互相干扰 | 切分过碎导致重复内容多 | 查看 top-k 结果是否高度重复 | 增大 chunk_size,或做去重后处理 |
| 长尾问题效果差 | 训练数据覆盖不足 | 分析失败案例的查询类型 | 补充领域数据微调嵌入模型 |
5.3 增量更新的坑与解法
知识库不是一次建好就完事了,新文档要加进来,旧文档要更新或删除。增量更新有几个容易踩的坑。
坑一:ID 冲突。如果新文档的 ID 生成规则跟旧文档撞了,会覆盖掉原有内容。建议用“来源文件名 + 内容哈希”作为 ID,内容变了哈希就变,不会误覆盖。
坑二:稀疏索引不支持增量删除。BM25 索引通常是全量构建的,删文档需要重建整个索引。如果知识库更新频繁,可以考虑用支持增删的稀疏索引实现,或者定期全量重建。
坑三:嵌入模型换了之后旧向量作废。如果升级了嵌入模型,新旧向量不在同一空间,不能混用。必须全量重新嵌入,重建索引。所以嵌入模型选型要慎重,别频繁换。
5.4 评估与监控:别等上线了才发现问题
RAG 系统需要持续评估。我建议至少维护两套评估集:一套是人工标注的标准问答对,用来算命中率和准确率;另一套是线上真实查询的采样,用来发现分布变化。
关键指标包括:检索命中率(top-k 里包含正确答案的比例)、检索精确率(top-k 里相关片段的比例)、端到端回答准确率(最终回答正确的比例)、平均响应延迟。
监控方面,把每次查询的检索结果和最终回答都记下来,定期抽样人工检查。如果发现某类问题的失败率突然升高,可能是知识库内容过期了,或者用户查询模式变了,需要针对性调整。
实操心得:我习惯在检索结果里保留相似度分数,设一个阈值,低于阈值的直接返回“未找到相关信息”,而不是硬塞给模型让它编。这个阈值需要根据实际数据调,太高会漏答,太低会引入噪声。一般从 0.6 开始试,根据误答率调整。
6. 从基础 RAG 到 Agentic RAG 的演进方向
基础 RAG 跑通之后,你会很快遇到它的天花板。用户的问题越来越复杂,单次检索搞不定,需要多步推理、多源检索、甚至主动追问澄清。这就是 Agentic RAG 要解决的问题。
Agentic RAG 的核心思路是把检索从一次性的动作变成 Agent 的一个工具。Agent 可以决定什么时候检索、检索什么、检索几次。比如用户问“对比我们公司和竞争对手的产品定价策略”,Agent 可以先检索自家定价文档,再检索竞品分析报告,然后综合两边信息生成对比。如果发现某个信息缺失,它还能主动发起补充检索。
实现上,这需要在 Agent 的决策循环里集成检索工具,并且让 Agent 能评估检索结果的质量,决定是否需要再次检索。这比基础 RAG 复杂不少,但能处理的问题类型也丰富得多。
另一个方向是图增强检索。传统 RAG 把知识当成孤立的片段,但很多知识之间存在关联。比如“产品A的定价”和“产品A的成本结构”是相关的,但作为独立片段检索时可能只召回其中一个。图增强检索把知识片段和它们之间的关系一起建模,检索时能沿着关系边扩展,召回更完整的上下文。
这两个方向都还在快速演进中,没有形成绝对标准。我的建议是先把基础 RAG 的每个环节做扎实,把检索命中率和回答准确率提上去,再考虑往 Agentic 方向扩展。基础不牢,上层建筑再花哨也撑不住。
最后分享一个我在多个项目里验证过的经验:知识获取管道的质量,八成取决于文档切分和检索策略,两成取决于模型选型。很多人把大量时间花在对比嵌入模型上,却忽略了切分策略的优化。实际上,把切分做好、把混合检索和重排加上,效果提升比换一个更大的模型明显得多。先把这两件事做到位,再考虑其他优化。