
1. 为什么知识获取管道是 AI Agent 的分水岭做 AI Agent 的人迟早会撞上一堵墙模型本身很聪明但你问它公司内部的报销标准、上周刚更新的产品参数、某个客户的特殊约定它要么一本正经地胡说要么直接摊手说不知道。这不是模型不行而是它的知识被冻结在训练截止的那一天而且压根没见过你的私有数据。知识获取管道要解决的就是把这个断层补上让 Agent 在回答问题之前先去查资料。RAGRetrieval-Augmented Generation检索增强生成就是目前最主流、最工程化的一条知识获取管道。它的核心思路用一句话说清楚把外部知识切块、向量化、存进索引用户提问时先检索出最相关的几块再连同问题一起塞给大模型让它看着材料答题。听起来简单但真正落地时从文档切分、嵌入模型选型、检索策略到重排每一步都有坑。这篇是走进 AI Agent系列的第四篇专门啃知识获取管道这块硬骨头。我会把 RAG 的基础链路拆开讲透包括稠密嵌入和稀疏嵌入到底差在哪、为什么很多团队最后都上了混合检索、切块大小怎么定、命中率上不去该怎么排查。适合正在从 0 到 1 搭建 AI Agent 的开发者也适合已经跑通了 demo 但发现效果拉胯、想搞清楚瓶颈在哪的人。读完你应该能自己搭一条能用的 RAG 管道并且知道每个参数背后的取舍逻辑。2. RAG 基础链路的整体设计与选型思路2.1 一条完整的知识获取管道长什么样很多人对 RAG 的理解停留在向量数据库 大模型两个框实际工程里它是一条至少五段的流水线。我把它拆成下面这几步每一步都是一个可以独立优化、也独立出问题的地方。文档加载Loading把 PDF、Word、Markdown、网页、数据库记录等各种来源的原始内容读进来统一成纯文本或结构化文本。切块Chunking把长文档切成一段段适合检索和塞进上下文的小块这是最容易被低估、却最影响效果的一步。嵌入Embedding把每个文本块转成一个高维向量语义相近的文本在向量空间里距离更近。索引与存储Indexing Storage把向量和原文、元数据一起存进向量数据库建立可快速检索的索引。检索与重排Retrieval Rerank用户提问时把问题也向量化找出最相似的若干块必要时再用重排模型精排一遍。生成Generation把检索到的上下文和用户问题拼成提示词交给大模型生成最终答案。这条链路里加载和切块决定了原料质量嵌入和索引决定了能不能找得到检索和重排决定了找得准不准生成决定了答得好不好。任何一环拉胯最终体验都会崩。我见过太多团队一上来就纠结用哪个向量库结果真正的问题出在切块把一句话从中间劈开了。2.2 为什么是 RAG而不是微调或者长上下文在动手之前值得先想清楚一个选型问题让 Agent 掌握私有知识到底该用 RAG、微调Fine-tuning还是干脆靠超长上下文硬塞微调的本质是改变模型的权重让它记住知识。它适合的是风格、格式、特定任务的模式学习比如让模型学会用你公司的口吻写邮件。但用它来记事实性知识有几个硬伤更新成本高知识一变就得重训、容易产生幻觉模型会自信地记错、无法追溯来源你没法告诉用户这个答案出自哪份文档。而 RAG 的知识存在外部索引里改一条文档就更新一条来源可追溯成本也低得多。长上下文看起来更省事——把整本手册塞进去不就行了问题是成本和精度。上下文越长推理越贵越慢而且模型对长上下文中间部分的注意力会衰减也就是常说的lost in the middle。你把 200 页文档塞进去模型可能恰恰漏掉了最关键的那一段。RAG 的价值就在于先做一轮筛选只把最相关的几块喂给模型既省钱又提精度。所以结论很明确事实性、易变、需要溯源的私有知识优先用 RAG。微调和 RAG 也不是互斥的很多成熟方案是 RAG 负责知识、微调负责风格两者叠加。2.3 稠密嵌入与稀疏嵌入两条腿走路才稳这是本篇最核心的一个技术点也是热词里反复出现的稠密嵌入、稀疏嵌入。搞懂这两个你就理解了检索的半壁江山。稠密嵌入Dense Embedding把一段文本压成一个固定长度的稠密向量比如 768 维或 1024 维每一维都是浮点数。它的强项是语义匹配你搜怎么退钱它能召回写着退款流程的文档哪怕一个字都不重合。这是传统关键词搜索做不到的。代表模型有各种开源的 sentence-transformers 系列以及各家提供的 embedding API。稀疏嵌入Sparse Embedding则是高维但绝大多数维度为零的向量本质上还是词袋模型那一套每个维度对应词表里的一个词值是该词的权重。经典代表是 BM25以及后来的 SPLADE 这类学习式稀疏模型。它的强项是精确匹配搜产品型号XR-2000、人名、错误码、专有名词它能精准命中而稠密嵌入反而容易把这些低频但关键的词糊掉。两者的短板正好互补。稠密嵌入对专有名词、数字、罕见词不敏感稀疏嵌入对同义改写、语义泛化无能为力。所以工业界的主流做法是混合检索Hybrid Retrieval两路并行召回再用一个融合算法常见的是 RRFReciprocal Rank Fusion倒数排名融合把两路结果合并。RRF 的公式很朴素对每个文档在每路结果里的排名取倒数再求和score(d) Σ 1 / (k rank_i(d))其中 k 是个平滑常数通常取 60。这个公式的好处是不依赖两路分数的绝对量级稠密是余弦相似度稀疏是 BM25 分数量纲根本不一样只看排名天然可融合。实测下来混合检索相比单用稠密在包含大量专有名词的场景里命中率能提升一截这也是为什么rag hit rate成了大家天天念叨的指标。3. 核心细节解析与实操要点3.1 切块策略决定 RAG 上限的隐形之手切块Chunking是整条链路里最不性感、却最要命的一步。切得好检索精准切得烂神仙难救。核心矛盾在于块太大噪声多、塞进上下文浪费 token、语义被稀释块太小上下文不完整、一句话被劈成两半、检索到的碎片拼不出完整意思。我的经验是块大小没有万能值但有一个合理的起步区间中文 300 到 500 字英文 200 到 400 词配合 10% 到 20% 的重叠overlap。重叠的作用是防止关键信息正好落在切割边界上被劈开代价是存储和检索时会有一些冗余。比固定长度切分更好的做法是按语义结构切。Markdown 按标题层级切代码按函数切合同按条款切网页按段落切。这样每个块天然是一个语义完整的单元。我一般会优先用递归字符切分Recursive Character Splitting它按段落 → 句子 → 词的优先级依次尝试尽量在自然边界断开。还有一个容易被忽略的点给每个块加上下文元数据。光切出一段退款需在 7 日内申请检索时它可能匹配到任何跟退款相关的提问但你不知道它属于哪个产品线。所以要在块里带上来源文档标题、章节路径、产品名等元信息检索时可以用元数据做过滤生成时也能给用户标注来源。这一步做扎实后面排查问题会轻松很多。提示切块前一定要先做清洗。PDF 提取出来的文本常带页眉页脚、乱码、断行这些噪声会污染嵌入向量直接拉低检索质量。宁可多花时间清洗也别指望模型能忽略噪声。3.2 嵌入模型选型别只看排行榜嵌入模型决定了语义能不能被正确表达选型时我关注这几个维度。第一是语言适配。如果你的知识库以中文为主务必选中文或多语言表现好的模型。很多英文榜单上的明星模型中文语义空间是塌的直接拿来用效果惨不忍睹。选型时一定要用你自己的真实数据做小规模评测别信通用榜单。第二是维度与成本。维度越高表达能力通常越强但存储和检索成本也越高。1024 维和 768 维在实际效果上往往差不了多少但存储能差三分之一。如果知识库规模很大百万级以上块维度就是真金白银。第三是是否支持稀疏或混合输出。现在有些模型能同时输出稠密和稀疏向量一次调用搞定两路召回工程上省事不少。如果你的框架支持优先考虑这类。第四是归一化与相似度度量。绝大多数嵌入模型输出后要做 L2 归一化然后用余弦相似度或内积检索。归一化后两者等价。这一步如果漏了检索结果会莫名其妙地差而且很难排查。3.3 检索参数top-k、阈值与重排检索阶段有几个关键参数调不好就是明明库里有就是搜不出来。top-k是召回多少个候选块。太小比如 3容易漏太大比如 50会把噪声一起带进来还可能超出上下文预算。我的起步值是稠密和稀疏各召回 20 个融合后取前 10 个进入重排重排后取前 3 到 5 个喂给模型。这个数字要根据你的块大小和模型上下文窗口动态调。相似度阈值用来过滤掉明显不相关的块。如果所有召回块的相似度都低于某个阈值说明知识库里可能根本没有相关内容这时候与其硬答不如让 Agent 老实说我没找到相关资料。这个拒答机制对降低幻觉极其重要很多团队忽略了它。重排Rerank是提升精度的利器。检索阶段用的是双塔模型问题和文档分别编码快但精度有限重排用的是交叉编码器cross-encoder把问题和文档拼在一起过一遍模型精度高但慢。所以典型架构是检索粗筛 重排精排。重排模型通常只对前 20 到 50 个候选做成本可控但命中率提升明显。这也是rag hit rate优化的关键一招。3.4 生成阶段的提示词工程检索做得好生成阶段也不能掉链子。核心原则是约束模型只依据检索到的上下文回答并明确告诉它如果上下文里没有答案就说不知道。一个我常用的提示词骨架是这样的你是一个严谨的问答助手。请仅根据下面提供的【参考资料】回答用户问题。 规则 1. 如果参考资料中没有相关信息直接回答根据现有资料无法回答不要编造。 2. 回答时尽量引用资料中的原文依据。 3. 不要使用参考资料之外的知识。 【参考资料】 {context} 【用户问题】 {question}这个骨架看着简单但仅根据资料无法回答就说不知道这两句能挡掉大量幻觉。另外把检索到的块按相关性排序后拼接最相关的放最前面也能提升模型对关键信息的注意力。4. 从零搭一条 RAG 管道的实操过程4.1 环境与依赖准备下面用 Python 生态走一遍最小可用链路思路是通用的换成 Java 的 Spring AI、LangChain4j 或者别的框架逻辑完全一样。先装依赖pip install langchain langchain-community langchain-text-splitters pip install sentence-transformers pip install chromadb pip install rank-bm25 pip install pypdf这里选 Chroma 做向量库是因为它轻量、能本地跑、适合验证选 sentence-transformers 是因为它开源、可控、方便换模型。生产环境可以换成 Milvus、Qdrant、pgvector 等接口思路一致。4.2 文档加载与清洗from langchain_community.document_loaders import PyPDFLoader loader PyPDFLoader(handbook.pdf) raw_docs loader.load() # 清洗去掉多余空白、页眉页脚等噪声 def clean(text: str) - str: lines [ln.strip() for ln in text.splitlines()] lines [ln for ln in lines if len(ln) 2] # 丢掉过短的行 return \n.join(lines) for d in raw_docs: d.page_content clean(d.page_content)清洗这一步别偷懒。我踩过的坑是PDF 提取出来的文本里混着页码和页眉这些高频重复的噪声会被嵌入模型当成重要特征导致检索时一堆无关块被召回。清洗后效果立竿见影。4.3 切块与元数据注入from langchain_text_splitters import RecursiveCharacterTextSplitter splitter RecursiveCharacterTextSplitter( chunk_size400, chunk_overlap60, separators[\n\n, \n, 。, , , , ], ) chunks splitter.split_documents(raw_docs) # 注入元数据 for i, c in enumerate(chunks): c.metadata[chunk_id] i c.metadata[source] c.metadata.get(source, unknown)注意 separators 里我把中文标点也放进去了这样切分时会优先在句号、问号处断开避免把一句话劈成两半。chunk_size 用 400 字是中文场景的稳妥起步值overlap 取 60 约 15%。4.4 稠密嵌入与向量入库from sentence_transformers import SentenceTransformer import chromadb model SentenceTransformer(BAAI/bge-base-zh-v1.5) # 中文表现好的模型 client chromadb.PersistentClient(path./rag_db) collection client.get_or_create_collection( namehandbook, metadata{hnsw:space: cosine}, ) texts [c.page_content for c in chunks] embeddings model.encode(texts, normalize_embeddingsTrue).tolist() collection.add( ids[str(c.metadata[chunk_id]) for c in chunks], embeddingsembeddings, documentstexts, metadatas[c.metadata for c in chunks], )这里normalize_embeddingsTrue是关键它做了 L2 归一化配合 cosine 距离度量检索才准。忘了这一步相似度计算会失真。4.5 稀疏检索与混合融合稠密检索有了再补一路 BM25 稀疏检索然后融合。from rank_bm25 import BM25Okapi import jieba # 中文需要先分词 tokenized [list(jieba.cut(t)) for t in texts] bm25 BM25Okapi(tokenized) def sparse_search(query, top_k20): q_tokens list(jieba.cut(query)) scores bm25.get_scores(q_tokens) ranked sorted(range(len(scores)), keylambda i: scores[i], reverseTrue) return ranked[:top_k] def dense_search(query, top_k20): q_emb model.encode([query], normalize_embeddingsTrue).tolist() res collection.query(query_embeddingsq_emb, n_resultstop_k) return [int(i) for i in res[ids][0]] def rrf_fuse(dense_ids, sparse_ids, k60): scores {} for rank, doc_id in enumerate(dense_ids): scores[doc_id] scores.get(doc_id, 0) 1 / (k rank) for rank, doc_id in enumerate(sparse_ids): scores[doc_id] scores.get(doc_id, 0) 1 / (k rank) return sorted(scores, keyscores.get, reverseTrue)RRF 融合的好处前面讲过只看排名不看分数天然规避了稠密和稀疏分数量纲不一致的问题。实测在专有名词多的场景混合检索比单路稠密召回率明显更高。4.6 重排与生成from sentence_transformers import CrossEncoder reranker CrossEncoder(BAAI/bge-reranker-base) def retrieve(query, top_k5): dense_ids dense_search(query) sparse_ids sparse_search(query) fused rrf_fuse(dense_ids, sparse_ids)[:20] pairs [(query, texts[i]) for i in fused] scores reranker.predict(pairs) ranked sorted(zip(fused, scores), keylambda x: x[1], reverseTrue) return [texts[i] for i, _ in ranked[:top_k]] def answer(query): ctx \n\n.join(retrieve(query)) prompt f你是一个严谨的问答助手。请仅根据下面提供的【参考资料】回答用户问题。 如果参考资料中没有相关信息直接回答根据现有资料无法回答不要编造。 【参考资料】 {ctx} 【用户问题】 {query} # 这里调用你的大模型接口 return call_llm(prompt)到这里一条完整的 RAG 管道就跑通了加载 → 清洗 → 切块 → 稠密嵌入 → 稀疏索引 → 混合召回 → RRF 融合 → 重排 → 生成。每一步都可以单独替换和优化这也是 RAG 相比微调更灵活的地方。5. 常见问题与排查技巧实录5.1 命中率上不去的排查顺序rag hit rate低是最常见的问题我一般按下面的顺序排查从便宜到贵排查项典型症状处理方式文本清洗召回一堆无关块去页眉页脚、乱码、断行切块大小答案被劈开或噪声多调 chunk_size 和 overlap嵌入归一化相似度普遍偏低确认做了 L2 归一化嵌入模型语言中文语义匹配差换中文/多语言模型检索路数专有名词搜不到加稀疏检索做混合重排缺失相关块排不进前几加 cross-encoder 重排阈值缺失无答案时硬编加相似度阈值和拒答按这个顺序走八成问题在前三步就能定位。我见过最离谱的一次是团队把 PDF 的页眉内部资料 请勿外传切进了每个块结果所有查询都优先召回这些噪声块排查了半天才发现是清洗没做。5.2 几个高频踩坑点坑一块太小导致语义碎片化。有人为了精准把块切到 100 字结果检索到的都是半句话模型拼不出完整答案。块大小要保证一个块能独立表达一个完整意思。坑二忽略元数据过滤。知识库里有多个产品线的文档用户问 A 产品却召回了 B 产品的相似条款。加元数据过滤按产品名、版本、时间能大幅提升精度。坑三重排模型和嵌入模型不匹配。重排模型最好和嵌入模型来自同一系列或同一语言适配否则精排效果打折。坑四把 RAG 当万能药。有些问题本质是推理问题不是检索问题。比如对比 A 和 B 两个方案的优劣需要的是跨文档综合单靠检索几块拼不出答案。这类场景要考虑 GraphRAG 或者 Agentic RAG让 Agent 多轮检索、自己规划查询。注意RAG 的效果上限由知识库质量决定。如果原始文档本身就过时、矛盾、残缺再好的管道也救不回来。上线前一定要做知识库治理。5.3 从基础 RAG 到 Agentic RAG 的演进方向基础 RAG 是一问一检索一答的单轮模式遇到复杂问题就力不从心。往上的演进方向有几个一是查询改写Query Rewriting让模型先把用户的口语化问题改写成更适合检索的形式甚至拆成多个子查询分别检索。二是多轮检索Iterative RetrievalAgent 先检索一轮看结果不够就换个角度再检索直到信息足够再回答。这就是 Agentic RAG 的核心思想——把检索变成 Agent 的一个可调用工具由它自己决定什么时候检索、检索什么。三是图增强GraphRAG把知识建成实体关系图检索时沿着图遍历适合需要跨文档推理的场景。热词里出现的ontology raggraphrag说的就是这一类。四是和 Skill 结合把 RAG 检索封装成一个标准化的 Skill让 Agent 在需要时调用这样知识获取就成了 Agent 能力体系里的一个模块而不是硬编码在流程里。这些方向都是在基础 RAG 之上做加法但前提是基础链路得先跑稳。基础不牢上再花哨的架构也是空中楼阁。6. 我在实际项目里的一些体会搭 RAG 这件事最反直觉的一点是决定效果的不是你用了多牛的模型而是数据处理的脏活累活。我做过好几个项目换更贵的嵌入模型带来的提升往往不如老老实实把文档清洗干净、把切块策略调对来得明显。模型是放大器原料不行放大出来的还是垃圾。另一个体会是评测必须尽早做。别等到上线才发现效果差那时候改起来牵一发动全身。我的做法是准备一批真实问题加标准答案每次改动链路都跑一遍看命中率和答案准确率的变化。没有评测调参就是盲人摸象。最后分享一个小技巧检索到的块在拼进提示词之前可以按相关性分数做个简单的去重和截断把明显重复或过长的块处理掉。这一步能省不少 token还能让模型注意力更集中。RAG 这条管道没有银弹靠的就是一环一环抠细节抠到最后你会发现稳比炫重要得多。