资讯动态

AI Agent知识获取管道:RAG检索增强生成从原理到实战

发布时间:2026/9/29 18:59:44 来源:尧图企业网站定制
1. 为什么知识获取管道是 AI Agent 落地的第一道坎做 AI Agent 开发的人绕不开一个尴尬的现实模型本身很聪明但它对你业务里的私有知识一无所知。你问它公司报销流程它给你编一个看起来很像那么回事的答案你问它某个产品的技术参数它张口就来一串根本不存在的数字。这不是模型不行而是它的知识边界就停在那里——训练数据截止的那一天以及公开互联网上能爬到的那些内容。知识获取管道要解决的就是把这个边界往后推。它的核心任务只有一句话在模型生成回答之前把和用户问题真正相关的那几条知识准确、及时地塞进模型的上下文里。这件事听起来简单做起来全是坑。检索回来的内容不相关模型就被带偏相关内容没检索到模型就开始幻觉检索到了但塞进去的顺序不对模型又抓不住重点。RAGRetrieval-Augmented Generation检索增强生成就是目前解决这个问题最主流、也最工程化的一条路径。它的思路很朴素把知识切块、向量化、存进索引用户提问时先检索、再生成。但朴素不等于简单从文档解析到切块策略从嵌入模型选型到混合检索从重排序到上下文组装每一个环节都有大量需要拍板的细节。这篇是走进 AI Agent系列的第四篇专门聊知识获取管道的基础设施——RAG。我会把整条链路拆开讲清楚每个环节在干什么、为什么这么干、实际做的时候容易在哪里翻车。不管你是用 LangChain、Spring AI 还是自己手写底层的逻辑是相通的。读完你应该能自己搭出一条能用的 RAG 管道并且知道它什么时候会不靠谱。提示RAG 不是银弹。它擅长的是事实性知识的召回与引用不擅长复杂推理和多跳关系查询。搞清楚它的能力边界比盲目堆技术栈重要得多。2. 把 RAG 拆成四段管道从原始文档到可检索知识很多人一上来就写vectorstore.similarity_search()结果跑出来的东西驴唇不对马嘴然后开始怀疑嵌入模型不行。问题往往不在检索那一步而在前面的数据处理。RAG 是一条管道上游没处理好下游再怎么调参都是白费。我习惯把整条链路拆成四段摄取Ingest→ 切块Chunk→ 嵌入与索引Embed Index→ 检索与组装Retrieve Assemble。每一段都有独立的输入输出和质量标准出了问题能快速定位是哪一段的锅。2.1 摄取阶段文档解析决定了知识的上限摄取阶段做的事是把各种格式的原始文档变成干净的纯文本。这一步最容易被低估。PDF 里的表格、扫描件里的文字、Word 里的多级标题、网页里的导航栏噪音——这些如果没处理好后面所有环节都是在垃圾上做检索。我踩过最典型的一个坑一份产品手册是扫描版 PDF直接丢给默认的 PDF 解析器出来的文本是乱序的段落之间还夹杂着页眉页脚。嵌入之后检索出来的内容全是碎片模型拿到手里根本拼不出完整答案。后来换成带 OCR 的解析方案并且加了页眉页脚的过滤规则召回质量立刻上了一个台阶。不同格式的解析策略差别很大我整理了一张对照表文档类型常见问题推荐处理方式原生 PDF分栏错乱、表格丢失用支持版面分析的解析器保留阅读顺序扫描 PDF无文本层、识别错误OCR 后处理纠错注意中英文混排Word/HTML样式噪音、层级混乱按标题层级切分剥离导航和广告Markdown相对干净直接按标题结构切块保留代码块完整性表格数据行列关系丢失转成结构化文本或单独建索引这里有个经验解析阶段宁可慢一点也要保证文本质量。因为解析是一次性的检索是高频的。花时间把文档洗干净比后面反复调检索参数划算得多。2.2 切块策略块的大小和边界比你想的更重要切块是 RAG 里最需要手感的一步。块太大检索回来的内容里混着一堆无关信息稀释了相关性块太小一个完整的知识点被切碎模型拿到半句话没法用。常见的切块方式有这么几种固定长度切块按字符数或 token 数硬切实现简单但经常在句子中间断开。递归字符切块按段落、句子、词的优先级递归分割尽量在自然边界断开是大多数场景的默认选择。语义切块用嵌入相似度判断相邻句子是否属于同一主题在语义转折处切分效果好但成本高。结构化切块按文档本身的标题层级、章节结构切分适合 Markdown 和技术文档。我一般会先看文档结构。如果是结构清晰的技术文档直接用标题层级切块大小控制在 300 到 800 token 之间并且让相邻块之间保留 10% 到 20% 的重叠。重叠的作用是防止关键信息正好卡在切分点上被两个块各拿一半。注意重叠不是越多越好。重叠太多会导致检索结果高度重复浪费上下文窗口。我实测下来10% 到 15% 的重叠在大多数场景下够用。还有一个容易被忽略的点给每个块加上元数据。来源文件名、章节标题、页码、更新时间这些信息在检索时可以参与过滤在生成时可以附在引用里。用户看到答案能追溯到原文信任度完全不一样。2.3 嵌入与索引稠密和稀疏不是二选一嵌入这一步是把文本块转成向量存进向量数据库。这里的关键决策是选什么嵌入模型以及用稠密嵌入还是稀疏嵌入。稠密嵌入Dense Embedding把文本映射成一个固定维度的稠密向量比如 768 维或 1024 维。它的优势是能捕捉语义相似性——如何申请年假和休假流程怎么走在向量空间里距离很近即使字面完全不同。缺点是对于精确的关键词匹配不够敏感比如产品型号、专有名词、错误码这类东西稠密向量经常抓不准。稀疏嵌入Sparse Embedding则是高维稀疏向量本质上还是词袋模型那一套比如 BM25。它的优势是精确匹配强用户搜ERR-5021就能精准命中包含这个错误码的文档。缺点是无法理解语义换个说法就找不到了。我的做法是两者都用做混合检索。稠密负责语义召回稀疏负责精确召回两路结果合并后再重排序。这套组合拳在实际项目里的召回率明显比单用稠密要高。尤其是企业知识库这种既有自然语言问答、又有精确术语查询的场景混合检索几乎是标配。索引层面向量数据库的选择也值得说两句。小规模场景用 FAISS 或 Chroma 就够了本地跑、零依赖。上了规模、要支持过滤和更新就得上 Milvus、Qdrant 或 pgvector 这类。选型时重点看三件事是否支持元数据过滤、是否支持混合检索、更新和删除是否方便。很多团队一开始图省事用了纯向量库后来发现要按部门、按时间过滤只能推倒重来。2.4 检索与组装把对的上下文放到对的位置检索阶段拿到候选文档后还有几件事要做。第一是重排序Rerank。向量检索是粗筛返回的 top-k 里难免有噪音。用一个交叉编码器Cross-Encoder对候选做精排把真正相关的排到前面效果提升非常明显。代价是多一次模型推理延迟会增加但换来的是准确率的大幅提升通常值得。第二是上下文组装。检索回来的块怎么拼进 prompt顺序很讲究。我的经验是把最相关的放最前面和最后面中间放次相关的——模型对开头和结尾的内容注意力更集中这是中间遗忘现象带来的实用技巧。第三是引用标注。每个块带上来源信息生成时让模型标注引用编号。这样用户能核对也方便排查幻觉。3. 稠密与稀疏嵌入的选型逻辑别被语义两个字忽悠聊到嵌入很多人第一反应是要语义理解肯定选稠密。这个判断对了一半。稠密嵌入确实强在语义但它在某些场景下反而不如稀疏来得准。搞清楚两者的适用边界比盲目追新模型重要。3.1 稠密嵌入擅长什么、不擅长什么稠密嵌入的核心能力是语义泛化。用户问怎么退货文档里写的是商品退回流程字面重合度很低但稠密向量能把它们拉到一起。这种能力在面向 C 端用户的问答场景里价值巨大因为用户不会用你的文档术语提问。但稠密嵌入有几个明显的软肋。一是专有名词和编号比如SKU-8823这种稠密模型没见过向量表示基本是随机的检索全靠运气。二是否定和精确条件支持和不支持在向量空间里可能很近但语义完全相反。三是长尾低频词训练数据里出现少的词嵌入质量普遍不高。我做过一个对比测试同一个企业知识库用纯稠密检索问XX 型号的保修期是多久召回率只有六成左右加上 BM25 稀疏检索做混合召回率直接上到九成。差距主要就出在型号这种精确匹配上。3.2 稀疏嵌入的现代形态不只是 BM25传统稀疏检索就是 BM25基于词频和逆文档频率打分。它简单、快、可解释但有个硬伤词表外的词完全没法处理同义词也匹配不上。现在有一类学习型稀疏嵌入比如 SPLADE用模型来预测每个词的重要性权重既保留了稀疏检索的精确性又引入了一定的语义扩展能力。它会把手机这个查询在电话移动设备这些相关词上也给出非零权重弥补了传统 BM25 的短板。不过学习型稀疏嵌入的部署成本比 BM25 高需要额外的模型推理。我的建议是如果团队资源有限先用 BM25 加稠密做混合效果已经能覆盖大部分场景等有精力了再上学习型稀疏。不要一上来就追求最复杂的方案。3.3 混合检索的融合策略RRF 是个好起点两路检索各自返回一个排序列表怎么合并成一个最简单的是加权求和但两路分数的量纲不一样权重很难调。我更推荐RRFReciprocal Rank Fusion倒数排名融合。RRF 的思路是只看排名不看分数每个文档的得分等于它在各路结果中排名的倒数之和。公式大概是score Σ 1/(k rank)k 一般取 60。这样做的好处是完全绕开了分数归一化的问题鲁棒性很好。def rrf_fusion(dense_results, sparse_results, k60): scores {} for rank, doc_id in enumerate(dense_results): scores[doc_id] scores.get(doc_id, 0) 1 / (k rank 1) for rank, doc_id in enumerate(sparse_results): scores[doc_id] scores.get(doc_id, 0) 1 / (k rank 1) return sorted(scores.items(), keylambda x: x[1], reverseTrue)这段代码可以直接用。实测下来RRF 融合后的结果比单独调权重的方案稳定得多尤其是在两路检索质量参差不齐的时候。4. 从零跑通一条最小可用 RAG 管道理论讲完动手跑一遍。我用 Python 生态里最常见的组合来演示文档加载、切块、嵌入、检索、生成。这套流程换成 Java 的 Spring AI 或者 LangChain4j逻辑是一样的只是 API 名字不同。4.1 环境准备与依赖选择先装依赖。核心是文档加载器、文本分割器、嵌入模型和向量库。pip install langchain langchain-community langchain-text-splitters pip install chromadb sentence-transformers嵌入模型我选sentence-transformers里的多语言模型本地跑、免费、支持中文。如果追求更好的效果可以换成 API 形式的嵌入服务但要考虑成本和数据出境的合规问题——企业场景下这点很关键很多团队最后都选择了本地部署的嵌入模型。向量库用 Chroma轻量、零配置、支持元数据过滤适合起步阶段。等数据量上来了再迁移到 Milvus 或 pgvector。4.2 文档加载与切块的完整代码from langchain_community.document_loaders import DirectoryLoader, TextLoader from langchain_text_splitters import RecursiveCharacterTextSplitter # 加载文档 loader DirectoryLoader(./docs, glob**/*.md, loader_clsTextLoader) documents loader.load() # 切块按段落优先块大小 500 字符重叠 80 字符 splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap80, separators[\n\n, \n, 。, , , , ], ) chunks splitter.split_documents(documents) # 给每个块补充元数据 for i, chunk in enumerate(chunks): chunk.metadata[chunk_id] i chunk.metadata[source] chunk.metadata.get(source, unknown) print(f共切出 {len(chunks)} 个块)这里separators的顺序很关键。它按优先级尝试先按空行段落切切出来还太大就按换行切再不行按中文句号、感叹号、问号切。中文场景下一定要把中文标点加进去否则会在句子中间硬断。4.3 嵌入、入库与检索from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_community.vectorstores import Chroma # 嵌入模型 embeddings HuggingFaceEmbeddings( model_nameBAAI/bge-small-zh-v1.5, model_kwargs{device: cpu}, ) # 建库 vectorstore Chroma.from_documents( documentschunks, embeddingembeddings, persist_directory./chroma_db, ) # 检索 query 报销流程需要哪些材料 results vectorstore.similarity_search_with_score(query, k5) for doc, score in results: print(f[{score:.4f}] {doc.page_content[:100]}...)跑通之后你会发现检索结果的质量高度依赖切块质量。如果切出来的块是断句的检索出来的内容读起来就很别扭。这时候回去调chunk_size和separators比调检索参数有效得多。4.4 把检索结果组装进 Promptdef build_prompt(query, retrieved_docs): context \n\n.join( f[{i1}] {doc.page_content} for i, doc in enumerate(retrieved_docs) ) return f基于以下资料回答问题如果资料中没有相关信息请明确说明资料中未提及。 资料 {context} 问题{query} 回答这个 prompt 模板有两个关键点。一是明确要求资料中没有就说没有这能大幅降低幻觉率。二是给每个资料编号方便模型在回答里标注引用来源。别小看这两句话它们对输出质量的提升立竿见影。5. 检索质量调优那些文档里不会写的实战经验跑通管道只是开始真正花时间的是调优。这一节我分享几个踩过坑才总结出来的经验都是常规教程里不太会提的。5.1 命中率上不去先别急着换模型很多人一发现检索不准第一反应是换个更强的嵌入模型。但根据我的经验八成的问题出在数据处理而不是模型。排查顺序应该是这样的先看切块质量。把检索出来的块打印出来读一遍如果块本身是断句的、缺上下文的换什么模型都没用。再看查询和文档的表述差异。用户用口语提问文档用书面术语这种鸿沟要靠查询改写或者混合检索来补。然后看 top-k 设置。k 太小会漏k 太大会引入噪音。一般先设 10 到 20重排序后再取前 3 到 5 给模型。最后才考虑换嵌入模型。我见过太多团队跳过前三步直接换模型结果问题依旧白白浪费了时间和算力。5.2 查询改写让用户的问法和文档对齐用户提问和文档表述之间的鸿沟是检索失败的头号原因。解决办法之一是查询改写在检索之前先用一个小模型把用户的口语化问题改写成更接近文档表述的形式或者生成多个查询变体分别检索再合并。比如用户问东西坏了能换吗改写成商品质量问题退换货政策检索命中率立刻不一样。多查询变体Multi-Query的思路是让模型生成三到五个不同角度的查询每个都去检索最后合并去重。代价是多几次检索但召回率提升明显。5.3 重排序的性价比什么时候值得上重排序Rerank是提升精度的利器但它有成本。交叉编码器要对每个查询-文档对做一次推理候选有 20 个就要推理 20 次延迟会明显增加。我的判断标准是如果 top-k 里噪音明显、模型经常被无关内容带偏那就值得上重排序。反之如果检索结果本来就挺准重排序带来的边际收益有限不如把资源花在别处。实际部署时重排序模型可以选小一点的比如 bge-reranker-base速度和效果的平衡比较好。别一上来就用最大的模型延迟扛不住。5.4 上下文窗口不是越大越好有个常见的误区既然模型上下文窗口大那就多塞点检索结果进去。实际上塞得越多模型越容易迷失在中间关键信息反而被淹没。而且无关内容会稀释注意力增加幻觉风险。我的做法是精挑细选宁少勿滥。重排序后取前 3 到 5 个最相关的块每个块控制在合理长度总上下文占用控制在窗口的 30% 到 50%。留出空间给系统提示和对话历史效果反而更稳。6. RAG 的能力边界与常见失效场景把 RAG 用起来不难难的是知道它什么时候会失效。这一节聊聊几个典型的翻车场景以及应对思路。6.1 多跳问题RAG 的天然短板用户问我们公司去年营收最高的产品线它的负责人是谁这个问题需要两步先查营收最高的产品线再查这个产品线的负责人。单次检索很难同时命中这两条信息因为它们在文档里可能相隔很远。这类多跳问题基础 RAG 基本无能为力。应对方案有几个方向一是查询分解把复杂问题拆成多个子问题分别检索二是迭代检索根据第一轮结果决定第二轮查什么三是上GraphRAG把知识建成图结构通过关系遍历来回答。GraphRAG 效果确实好但构建和维护成本高不是所有场景都值得。6.2 知识冲突新旧文档打架怎么办企业知识库经常有这种情况同一件事旧文档和新文档说法不一致。检索时两个都召回了模型不知道该信哪个输出就可能自相矛盾。解决办法是在元数据里加时间戳和版本号检索时优先返回最新的或者在 prompt 里明确告诉模型以时间较新的资料为准。更彻底的做法是建立文档的生命周期管理过期文档及时下架别让它们留在索引里捣乱。6.3 表格和结构化数据纯文本检索的盲区财务报表、参数对照表这类结构化数据转成纯文本后行列关系就丢了检索出来模型也读不懂。这类数据最好单独处理要么转成自然语言描述再嵌入要么用专门的表格检索方案要么干脆走 Text-to-SQL 的路子让模型生成查询语句直接查数据库。我一般会判断数据的性质叙述性知识走 RAG结构化数据走 SQL 或专门的检索通道。硬把表格塞进向量库效果通常很差。6.4 评估没有度量就没有优化最后说一个容易被忽略但极其重要的事建立评估体系。没有评估你根本不知道调优有没有效果全靠感觉。评估分两块。一是检索质量看命中率Hit Rate、召回率Recall、MRR 这些指标需要标注一批问题-正确文档的测试集。二是生成质量看答案的忠实度是否基于检索内容、相关性、完整性可以用模型来打分也可以人工抽检。测试集不用很大几十到上百条就够起步。关键是持续维护每次改动都跑一遍用数据说话。我见过太多团队凭感觉调参改来改去反而越调越差就是因为没有基准。提示评估集要覆盖典型问题和边界情况包括那些你知道会失败的场景。只测简单问题评估结果会虚高上线后照样翻车。7. 我在实际项目里的一些取舍做了几个 RAG 项目之后我最大的体会是别追求一步到位先跑通最小闭环再逐步加料。一开始就上混合检索、重排序、查询改写、GraphRAG听起来很美好但调试成本极高出了问题根本不知道是哪一环的锅。我的做法是先搭一条最简管道——固定切块、纯稠密检索、直接拼 prompt——跑通之后用评估集测出基线然后一次只改一个变量看指标变化。这样每一步的收益都清清楚楚。另一个体会是数据质量决定天花板。再花哨的检索技术也救不了一堆垃圾文档。与其在检索算法上反复折腾不如花时间把文档整理干净、结构理清楚、元数据补全。这部分工作枯燥但回报最实在。还有一点RAG 和微调不是对立的。RAG 解决的是知识从哪来微调解决的是模型怎么说话。企业场景下通常是 RAG 提供事实微调调整风格和格式两者配合使用。别指望 RAG 能教会模型一种全新的表达方式那是微调的活。最后分享一个我常用的排查技巧当模型答错时先看检索结果对不对。如果检索回来的内容里根本没有正确答案那是检索的问题去调切块和检索如果检索结果里有正确答案但模型没用那是生成的问题去调 prompt 和上下文组装。这个二分法能帮你快速定位问题所在省下大量瞎调的时间。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑