ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从零构建RAG知识获取管道:切块、检索与Agentic进化

从零构建RAG知识获取管道:切块、检索与Agentic进化 走进 AI Agent 第四篇知识获取管道——RAG 基础做 Agent 做到第四篇终于要聊一个躲不开的话题知识获取管道也就是 RAG。前几篇我们把 Agent 的骨架搭起来了有规划、有记忆、有工具调用但很多朋友到了这一步都会发现同一个问题模型本身的知识是“死”的它的训练数据有截止时间它不知道你公司内部的流程、不知道你私有知识库里的那份说明书、不知道你 ERP 系统里那款产品的最新参数。你让 Agent 去做客服、做检索、做知识问答它一开口就瞎编。这个时候 RAG 就是绕不过去的一环。这篇我们只聊 RAG 最基础的部分不整花活。我从“为什么需要一条管道”讲起把索引、检索、生成这条链路掰开揉碎附上能直接跑起来的最小实现再把我实际踩过的坑和调参记录整理出来。不管你是刚接触 AI Agent 开发还是已经在做 RAG 项目想回炉基础这篇应该都能帮你在脑子里把这条管道理顺。1. 先搞清楚RAG 凭什么是 AI Agent 的“知识获取管道”1.1 Agent 不是只会聊天它是“会使用工具做事的系统”我们常说的 AI Agent本质上是一个循环感知输入、做出决策、调用工具、观察结果、再决策。这个循环里知识获取是贯穿始终的底料。Agent 要回答“公司报销流程是什么”它得先知道流程文档在哪Agent 要帮你查“上季度某产品销量”它得先知道数据表结构、知道怎么拼接查询条件。没有知识来源Agent 的规划能力再强也是空中楼阁。很多人一开始会把知识获取理解成“把文档喂给模型”或者“让模型联网搜索”这两种做法都太粗放。喂给模型受上下文窗口限制而且私有数据不可能每次都做全量微调联网搜索返回的内容杂、格式乱直接塞进 prompt 反而干扰模型判断。真正靠谱的做法是把知识获取设计成一条管道原始数据从源头进来经过清洗、切块、转成向量、建立索引然后在查询时快速召回最相关内容作为上下文注入给模型生成答案。这条管道就是 RAG。1.2 为什么“知识获取”必须是一条管道而不是一次调用我见过不少刚上手的朋友问我直接把整本手册贴到 prompt 里行不行短期看着行但稍一扩展就崩。第一上下文窗口是有限的实际业务文档动辄几十上百页塞不进去第二不是所有内容对当前问题都有用塞进去一堆无关内容模型反而被噪音带偏第三文档会更新你今天塞的是 v1.2明天更新到 v1.3麻烦就来了。管道化的核心价值是把“存储”和“理解”分开。存储层只管把你丢进来的文档切好、嵌好、存好理解层在每次查询时只取最相关的一小块。这样既绕开上下文限制又天然支持增量更新你只需要把新文档重新走一遍处理流程不需要动模型本身。这也解释了为什么 RAG 被叫做“知识获取管道”——数据从一端持续流入被处理成标准化的知识索引在另一端按需流出。它不是一个函数是一套流水线。1.3 RAG 不是什么先排掉三个常见误解第一个误解RAG 就是向量数据库。向量数据库只是管道里的一个存储组件RAG 还包括切块策略、检索逻辑、上下文拼装这些环节对效果的影响往往比向量库本身更大。第二个误解RAG 就是“搜索然后拼 prompt”。搜索只负责召回候选拼装 prompt 时怎么排、怎么避重复、怎么控制长度都有讲究否则召回对了答案照样错。第三个误解有了 RAG 就能消除幻觉。RAG 能显著降低幻觉但没法根治因为模型生成时仍可能过度发散你需要配合提示词约束和结果校验。把这些想清楚后面调参的时候你才知道问题出在哪个环节。2. 一条最小可用 RAG 管道的核心链路拆解2.1 链路总览索引阶段和查询阶段RAG 的完整流程分两个阶段。索引阶段是离线的把文档变成可检索的索引查询阶段是在线的每次用户提问时实时执行。索引阶段四步走加载文档、解析清洗、切块、转向量入库。查询阶段也是四步把用户问题转成向量、在索引里做相似度检索、把召回片段整理排序、拼进 prompt 让模型生成答案。听起来都很简单但每一步都有隐藏的设计决策。我建议你在动手写代码前先把这条链路在白纸上画出来标清楚每一步的输入输出是什么再研究每个环节选什么方案。否则一上来就写代码后面排查问题时你会很痛苦因为问题可能藏在任何一环。2.2 切块Chunking最容易被忽略的细节切块是整个管道里最“廉价”却最影响效果的动作。切块的目标是每一块都能独立表达一个完整语义单元同时又足够小方便向量检索精准命中。这里有个天然矛盾——块太大向量化后语义被稀释检索精度下降块太小单块信息量不足召回后拼不出完整答案。我常用的经验值是中文技术文档chunk size 设在 300 到 500 个 token 左右overlap 设 50 到 100。这个设置兼顾了语义完整性和检索精度。但这只是起点真正的策略要根据文档结构调整产品说明书可以按章节切FAQ 可以按问答对切对话记录按轮次切。我见过一个很好的做法叫父子切块——把文档切成大块保存上下文同时切成小块做检索检索到小块后回溯到所属的大块拼进 prompt。这样既保证命中精准又保证上下文完整后面我还会提到。2.3 向量化embedding 模型选型的底层逻辑embedding 模型负责把文本变成向量选型直接影响检索效果的底线。中文场景下我比较常用的是 BAAI 的 bge 系列和 M3E英文场景 OpenAI 的 text-embedding-3-small 也很好用。选择的关键指标有三个检索效果、向量维度、服务化成本。维度这个参数很多人会忽略。bge-large 是 1024 维text-embedding-3-small 是 1536 维维度越高理论上表达能力越强但存储和计算成本也越高。实际上对于百万级以下的知识库维度差异带来的效果差别远不如切块和检索策略的差别大。我的建议是别一上来就选最大的模型先用一个小模型把管道跑通再去对比替换 embedding。管道通不通和 embedding 选型关系不大效果好不好的瓶颈往往也不在 embedding。2.4 检索与重排召回和命中率的关系检索这一步的目标是“召回”从索引里取回 top-k 个最相关的片段。但“相关”是个很主观的词向量相似度高不等于语义准确所以出现了两类问题召回率低该回来的没回来和精度低回来的有一堆噪音。解决精度问题最有效的手段是加一层重排rerank。常用做法是先用向量检索取回 50 到 100 个候选再用 rerank 模型对候选逐一打分取 top 3 到 5 个注入 prompt。这个套路在实践中提升非常明显值得优先做。解决召回率问题则要考虑混合检索把向量相似度和传统 BM25 关键词匹配结合起来再做结果融合。很多 RAG 项目做到后面瓶颈不在模型而在“该回来的没回来”——这个问题下面会仔细讲。3. 从 0 到 1 搭建一个最小 RAG 管道能直接跑3.1 环境准备与依赖选择我先给出一套可复现的技术栈这套组合非常适合新手起步Python 3.10LangChain 作为编排框架FAISS 作为向量存储bge-small-zh-v1.5 作为 embedding 模型DeepSeek 或任意国内大模型 API 作为生成模型。这套组合的好处是全部开源、无需 GPU、免费额度内就能跑通全流程。安装依赖时只需要三个核心包langchain、langchain-community、langchain-chroma 或 faiss-cpu。我实际测试中直接用pip install langchain langchain-community faiss-cpu就够了。你在装包时尽量用干净的虚拟环境避免版本冲突。LangChain 版本迭代很快如果遇到 API 变更优先查当前版本的官方文档不要让旧教程误导你。3.2 核心流程代码解析第一步是加载和切块。我以一份纯文本产品手册为例from langchain_community.document_loaders import TextLoader from langchain_text_splitters import RecursiveCharacterTextSplitter loader TextLoader(product_manual.txt, encodingutf-8) documents loader.load() splitter RecursiveCharacterTextSplitter( chunk_size400, # 每个块约400个字符 chunk_overlap80 # 相邻块重叠80个字符 ) chunks splitter.split_documents(documents) print(f文档被切分为 {len(chunks)} 个块)这里用了RecursiveCharacterTextSplitter它是目前通用性最好的切分器它先按段落分隔符\n\n切不行就按句号、逗号不断降级直到满足长度要求。这种“由快到慢”的降级策略能尽可能保住语义边界。第二步是向量化和入库from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_community.vectorstores import FAISS embeddings HuggingFaceEmbeddings( model_nameBAAI/bge-small-zh-v1.5 ) vector_store FAISS.from_documents(chunks, embeddings) vector_store.save_local(faiss_index)模型首次运行会自动下载大约 100MB 左右。如果网络不稳定导致下载失败可以手动去 HuggingFace 下载后放到本地目录再把model_name改成本地路径。第三步是检索和生成这也是查询阶段的核心from langchain_community.vectorstores import FAISS from langchain_openai import ChatOpenAI # 加载索引 vector_store FAISS.load_local( faiss_index, embeddings, allow_dangerous_deserializationTrue ) # 定义检索器 retriever vector_store.as_retriever( search_typesimilarity, search_kwargs{k: 4} # 召回4个片段 ) # 构造生成链路 from langchain.chains import RetrievalQA from langchain.prompts import PromptTemplate prompt_template 你是一个严谨的客服助手。请仅根据以下知识片段回答问题。 如果片段中没有足够信息直接说“知识库中未找到相关信息”不要编造。 知识片段 {context} 用户问题{question} 回答 QA_PROMPT PromptTemplate( templateprompt_template, input_variables[context, question] ) llm ChatOpenAI( modeldeepseek-chat, api_key你的API_KEY, base_urlhttps://api.deepseek.com ) qa_chain RetrievalQA.from_chain_type( llmllm, retrieverretriever, chain_typestuff, chain_type_kwargs{prompt: QA_PROMPT} ) result qa_chain.invoke({query: 产品保修期是多久}) print(result[result])这里有个细节值得注意我用chain_typestuff意思是最简单的“把所有召回片段一次性塞进 prompt”。对于片段少、总量小的场景这个方式效果最好、速度最快。如果召回片段很多、总量超限才需要考虑 map_reduce 或 refine 之类的复杂链。新手起步就先用 stuff别过度设计。3.3 参数调优实操记录用对照实验说话管道跑通之后就该认真调参了。我直接分享一组真实对照实验帮助你看清楚不同参数对效果的影响。测试语料是 30 页的中文产品 FAQ共提出 20 个测试问题衡量指标是“首答案中信息完整且准确的比例”。第一组实验是不同 chunk_size 的对比chunk_sizeoverlap回答准确率现象2004055%回答细碎常缺上下文4008075%信息完整度明显提升80010060%单块语义太杂命中变差第二组实验是召回数量 top-k 的影响top-k回答准确率现象250%常漏关键信息475%表现最佳870%噪音增多模型被无关内容带偏这个结果很典型不是召回越多越好多了反而干扰生成。所以我后面用的生产配置基本固定为 chunk 400、overlap 80、top-k 4。第三组实验是给管道加 rerank。我用 bge-reranker-base 对召回候选重排序后再取 top 4结果准确率从 75% 提升到 88%。这个提升完全来自“把真正相关的片段排到最前面”而不是增加内容量。如果你有条件优先上 rerank投入产出比很高。3.4 中文场景的几个坑中文和英文在 RAG 管道里有几个天然差异。第一是分词问题RecursiveCharacterTextSplitter默认分隔符里包含中文句号和逗号但这不够我会手动加上“。”等标点符号让切块尽量落在句子的边界。第二是编码问题很多原始文档是 GBK 编码加载时一定要指定正确的编码否则文档内容变成乱码你后续白折腾。第三是 embedding 模型的中文能力差异很大。同样一段中文文本某些英文模型生成的向量会丢失语义细节直接导致检索结果“看着差不多实则差很远”。中文场景我建议直接选用中文优化的模型省很多事。第四是中文里同义词、简称很多用户搜索“保修”和文档里写的“质保”语义相同但字面不同这种情况下纯向量检索往往能命中但关键词检索就不行这也是我推荐混合检索的一个重要原因。4. 实战中的典型问题与排查技巧实录4.1 问题速查表先定位再修复我把实际项目里最常见的几类问题整理成一张速查表遇到问题先对号入座现象可能原因优先排查方向检索结果明显不相关切块太大语义混杂 / embedding 模型效果差调整 chunk_size检查 embedding 是否适合中文检索到相关内容但答案不对召回片段排错顺序 / prompt 约束不够加 rerank调整 prompt 强调只依据片段回答回复说“未找到”但其实库里有召回数量太少 / 片段被切碎提高 top-k尝试父子切块或大 chunk 重切答案张冠李戴文档里有多个相近实体在片段中保留文档来源元数据让模型引用出处索引很慢文档量大 / embedding 串行计算批量调用 embedding或提前做向量缓存这里面有一个反复出现的根因知识被“切碎了”。文档里一句话的前提条件在上一段结论在下一段切块之后模型只看到结论自然瞎猜。解决思路就是我在前面提过的父子切块或者干脆给 chunk 加宽让每块包含足够的上下文。4.2 命中率hit rate怎么量、怎么提热词里出现了“rag hit rate”这确实是评估 RAG 检索效果最重要的指标。命中率的定义不复杂对一组测试问题如果检索召回片段里包含能回答该问题的关键信息就算命中。比如我准备 20 个测试问题每个都预先标记好它对应文档里的答案片段然后跑检索看有多少问题的答案片段出现在召回结果里。实际操作中我建议建一个“金标准”评估集找业务人员写下 30 个典型问题并标注“完整答案在文档的哪个章节”。然后写一个脚本把每个问题和它的答案片段一起输入检索器计算命中率。这一步听上去麻烦但它能把“我觉得效果还行”变成可量化的数字。我见过太多团队在调参时凭感觉结果调了半天不知道是好了还是坏了。提升命中率有三个有效手段第一是混合检索把 BM25 和向量相似度融合第二是优化切块让块边界更贴合语义单元第三是加查询改写把用户口语化的问题改写成更规范的检索词。第三个手段一般归到 Agentic RAG 范畴下面会展开。4.3 知识割裂问题把答案从多个片段里“拼”起来做 RAG 做到后期你一定会遇到所谓“知识割裂”现象知识库里明明有完整答案但检索回来的片段东一块西一块模型拼不出连贯的结论。这个问题在医学资料、长文档、复杂产品手册里特别常见因为一个完整知识往往横跨多个段落甚至多张表格。我处理这个问题的经验是三层递进。第一层调整切块策略把语义相关的段落尽量归入同一块第二层引入父子切块检索小片段后回溯到大片段保证上下文完整第三层用结构化知识图谱辅助把文档里的实体和关系提取出来让检索不只看字面相似还能沿着关系链找到关联内容。前两层对大部分项目已经足够第三层通常只在知识关系复杂、检索要求高的场景才值得上。5. 从基础 RAG 到 Agentic RAG管道接下来的进化方向5.1 Agentic RAG 到底“Agentic”在哪聊完基础最后说一个最近大家问得很多的方向Agentic RAG。这个概念的兴起是因为基础 RAG 有几处被动的毛病用户问题含糊、需要多跳检索才能回答、需要查多个知识库才能拼出结论这些场景下基础 RAG 的“单轮检索即生成”很容易翻车。Agentic RAG 的核心是把检索从“一次性操作”变成“Agent 可反复调用的工具”。比如 Agent 接收问题后不是直接生成答案而是先分析这个问题是不是需要先查产品线再查价格是不是需要把口语化说法改写成规范术语然后它把“查询改写”“多库检索”“判断信息是否足够”都变成自己的行动选择。这和前几篇讲 Agent 的循环是一样的逻辑只不过工具变成了检索器。实际效果上Agentic RAG 在需要多跳推理的问题上提升非常明显代价是延迟更高、链路更长。5.2 Skill 如何与 RAG 结合很多人在问“skill 怎么和 rag 结合起来”我的理解是把 RAG 封装成 Agent 的一个 skill或者说一个可被调用的工具包。比如你可以把“产品知识问答”做成一个 skill它内部包含专用索引、专用 prompt 和调用入口Agent 在规划阶段根据用户问题判断是否调用这个 skill。这样做的最大好处是不同业务域的知识可以独立维护、独立优化Agent 像工具箱一样按需取用。我在实际项目中试过把多个 RAG skill 放进一个 Agent一个管产品手册一个管售后工单一个管内部流程。每个 skill 有自己的 embedding 模型、切块参数和 prompt 模板。用户问题进来后Agent 先判断应该调用哪个 skill甚至多个 skill 并行检索后汇总生成。这种结构在扩展知识库时非常舒服新加一个业务域只需要新增一个 skill不动原有链路。5.3 我个人的实操体会做到这一步我最大的感受是RAG 真正难的不是某个模型多强、某个框架多新而是你对整条管道的每个环节有没有分寸感。检索效果不好你先别急着换模型、换数据库先去检查切块边界和召回数量答案不对先别急着换大模型先看 prompt 是否给了足够的约束和出处要求。把基础环节调顺了再谈 Agentic 的进化才有意义。另外一个很值得做的动作是把你的评估集建好。我后来几乎所有的 RAG 优化都离不开那几十条金标准问题。有了这把尺子你换一个 embedding、调一个参数都能立刻看到涨跌而不是靠感觉拍脑袋。最后分享一个“后悔没早知道”的小技巧在生产环境里一定要把每个检索片段带上文档来源、页码等元数据并在 prompt 里要求模型生成答案时标注来源。这样即使用户遇到错误答案也能快速追溯是文档本身的问题还是检索、生成的问题。这个元数据设计一开始不花什么成本后期排查时能帮你省下大量时间。
返回列表