ARTICLE DETAIL

资讯详情

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

RAG构建AI Agent知识管道:从文档切分到检索重排

RAG构建AI Agent知识管道:从文档切分到检索重排 做AI Agent的人早晚都会撞上同一个问题模型再聪明不知道你业务里的具体情况就是纸老虎。我在前面几篇里聊过Agent的主体框架、工具调用和记忆机制这一篇要讲的是让Agent真正“懂行”的那个环节——用RAG搭出一条知识获取管道。简单说RAG就是把外部文档里的知识切碎、索引、检索再塞给大模型当上下文让模型基于真实资料回答而不是凭训练时的记忆硬编。这篇适合刚把Agent跑通、正琢磨怎么让它回答私有数据问题的朋友也适合想把检索效果做扎实的开发者。1. RAG 在 AI Agent 中的定位为什么说它是知识获取的主动脉1.1 从 Agent 闭环看 RAG 的位置现在聊Agent大家喜欢画一个“感知-规划-行动-记忆”的闭环。感知来自用户输入规划交给思维链或ReAct行动是调工具记忆分短期和长期。但这里有个经常被忽略的缝隙Agent的记忆尤其是长期记忆数据从哪来不能只靠用户每次喂也不能指望微调模型——微调成本高、周期长业务文档一更新就又失效了。RAG承担的角色就是“外部记忆的存取管道”。它在闭环里的位置介于记忆模块和工具模块之间既像记忆因为它把文档内容变成了可查询的向量又像工具因为它暴露一个“根据问题检索资料”的接口。Agent在规划时决定要不要调用它调用后拿到的结果直接注入上下文影响后续推理。你可以把它想象成给Agent配了一个资料柜平时不用翻遇到不确定的问题就去抽几个文件夹看看。没有这层管道Agent只能用模型参数里那点“死知识”遇到公司内部制度、产品手册、领域论文这类信息基本只能瞎猜。1.2 管道化思维一次理解 RAG 全流程RAG这个名字看着玄乎其实就是一条五段管道加载、切分、向量化、检索、生成。加载负责把PDF、Word、Markdown读成纯文本切分把长文本拆成合适大小的块向量化把每个块变成一串浮点数检索根据用户问题找出最相关的几个块生成把检索结果和问题组装成提示词交给大模型。前四步是离线准备和在线查询的混合生成是最后一步。管道化思维的关键在于每一段都可以单独替换、单独调优不要想着一个库吃遍天。比如加载阶段你换一个解析器影响只作用在文本质量上检索阶段你换个向量库不会牵动切分逻辑。很多初学者犯的错误是把整条管道当成一个黑盒工具来调用出了问题没法定位只能整体重来。把每一段的输入输出接口先定清楚后面调参和排查都轻松得多。2. 基础组件选型与环境准备2.1 核心组件选型分析动手之前先说选型因为这个坑我踩过看到什么新框架都想上最后项目一半时间花在切换组件上。我按三个层面给经验Embedding模型。中文场景优先看bge系列特别是bge-large-zh-v1.5语义理解和对齐都稳英文场景可以用text-embedding-3-small性价比高。如果团队资源紧张bge-small也能跑但检索精度会掉一截。注意Embedding模型的输出维度直接决定向量库的字段配置换了模型维度变了对不上索引要重建。向量数据库。练手项目选Chroma或FAISS装起来快、无需独立部署单机几百兆数据毫无压力。团队协作或数据量到千万级再考虑Milvus/Qdrant那些是服务端组件运维成本高一个量级。我之前用Chroma做原型验证三个月后才迁移到Milvus接口抽象得好迁移成本很低。LLM。Agent场景强调“能调用工具且支持长上下文”本地首选Qwen系列API侧看实际预算。长上下文现在普遍到128K但别天真地以为可以把所有文档塞进去——成本翻倍、延迟变大而且模型对中间部分的注意力本来就弱。RAG的正确用法是“少而精”地喂而不是“多而全”地灌。下面贴个组件对照表参数按我的实际配置写供参考环节推荐方案备选方案关键参数Embeddingbge-large-zh-v1.5text-embedding-3-small维度1024向量库Chroma 0.4.xFAISS / Qdrant距离度量余弦文档解析PyMuPDF BeautifulSoupUnstructured—切分器RecursiveCharacterTextSplitterMarkdownHeaderTextSplitterchunk_size500overlap80重排序bge-reranker-v2-m3Cohere Reranktop_k 重排到3~52.2 环境搭建实操环境这块我的经验是先用一个干净的虚拟环境别直接往系统Python里装依赖冲突会浪费你一下午。我用conda建了个rag-lab环境Python版本3.10实战下来和LangChain生态兼容性最好。conda create -n rag-lab python3.10 -y conda activate rag-lab pip install langchain langchain-community langchain-chroma pip install chromadb pymupdf beautifulsoup4 pip install sentence-transformers pip install bge-reranker-v2-m3 # 如果本机跑重排装完先别急着写业务代码跑一个冒烟测试加载一篇文档切分向量化检索把整条管道走通再回去看细节。我习惯把冒烟测试脚本单独存一份后面改任何组件都用它来回归省得每次从头排错。3. 从文档到向量库预处理链路的三个关键环节3.1 文档加载与格式清洗文档加载看起来简单实际是整条管道里最“脏”的环节。PDF分两种文字版PDF用PyMuPDF直接抽文本扫描版要先OCR。别在文字版PDF上跑OCR速度慢且会有识别噪声。我遇到最多的坑是PDF里的表格直接抽取会丢行列对应关系。处理方法也很糙但有效——用pdfplumber定位表格区域再按行转成文本表格内容一律转成“列名: 值”的平铺格式检索效果反而好。import fitz def load_pdf(path): doc fitz.open(path) pages [] for page in doc: text page.get_text(text) if text.strip(): pages.append(text) return \n\n.join(pages) text load_pdf(product_manual.pdf) print(text[:500])加载完统一做清洗去掉多余空行、统一换行符、删除页眉页脚里重复的标题。清洗这一步很多人跳过结果检索时总匹配到一些带“第1页/共20页”的噪声块直接影响答案质量。3.2 文本切分的经验参数切分是整个预处理链路中影响检索效果最大的环节比Embedding选型还关键。核心参数就两个chunk_size和chunk_overlap。chunk_size决定每个块多大chunk_size太大则块内主题混杂检索回来的块只有一半内容相关chunk_size太小则语义被截断向量表达不完整。它们控制相邻切块之间的重叠量保证被切开的那句话重新连上。我实测的中文场景经验值日常产品文档、规章制度chunk_size500左右overlap80按字符数算。技术手册或协议文本句子平均长度长可以放宽到800。营销文案、对话记录这类短句为主的内容缩小到300更稳。注意判断标准不是“看起来整齐”而是“切出来的块是否语义完整”。from langchain.text_splitter import RecursiveCharacterTextSplitter splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap80, separators[\n\n, \n, 。, , , , , ] ) chunks splitter.split_text(text) print(f共切出 {len(chunks)} 个块) print(chunks[0])separators顺序是有讲究的优先按段落拆再按句子拆最后才按字符硬切。中文场景必须把句号、感叹号、问号加进去否则一个块里会出现半句话。另外切分是按长度硬算的中文字符和英文token的边界不一样你要是用tokenizer计算长度会更精确但字符数方案已经够用不值得为此加复杂度。3.3 向量化与索引构建向量化选择哪款Embedding模型之前说过这里说说构建索引的实操要点。第一步给每个块编个稳定的ID我习惯用文档名加序号排查时一眼看出是哪个文件的哪一段第二步批量Embedding批量大小为32或64太大容易显存溢出第三步写入向量库时把原始文本、来源、页码作为metadata存进去检索结果回来直接能追溯到原文。from langchain_chroma import Chroma from langchain_community.embeddings import HuggingFaceEmbeddings # 伪造ID和metadata方便后续排查 for idx, chunk in enumerate(chunks): ids.append(fmanual_{idx}) metadatas.append({source: product_manual.pdf, chunk: idx}) embeddings HuggingFaceEmbeddings(model_nameBAAI/bge-large-zh-v1.5) vectorstore Chroma.from_texts( textschunks, embeddingembeddings, idsids, metadatasmetadatas, persist_directory./chroma_db ) print(f向量库已写入 {vectorstore._collection.count()} 条记录)这里有个新手必踩的坑bge系列模型在计算相似度前要先对查询文本加一句指令“为这个句子生成表示以用于检索相关文章”不加的话检索精度会明显下滑。细节不复杂忘了就掉点但在对比实验里很容易被忽略。4. 检索侧优化与 Agent 集成4.1 相似度检索与重排基础检索就是向量余弦相似度top_k取多少我做过大量测试top_k5到10是甜点区间太少容易漏太多会把噪声带进来。但真正有效提升精度的不是top_k是重排。相似度检索负责“广撒网”重排负责“精挑细选”。先取20条候选再用reranker逐一打分最后保留3到5条。这步能把召回率低的块剔掉对答案质量提升非常明显尤其当文档里有大量相似段落时。代价是重排比向量检索慢一个量级但换来的是答案准确度的大幅提升生产环境值得加。# 先粗召回20条 retriever vectorstore.as_retriever(search_kwargs{k: 20}) # 再用bge-reranker精排 from transformers import AutoModelForSequenceTraining, AutoTokenizer import torch tokenizer AutoTokenizer.from_pretrained(BAAI/bge-reranker-v2-m3) model AutoModelForSequenceTraining.from_pretrained(BAAI/bge-reranker-v2-m3) def rerank(query, documents): pairs [[query, doc] for doc in documents] with torch.no_grad(): inputs tokenizer(pairs, paddingTrue, truncationTrue, return_tensorspt, max_length512) scores model(**inputs).logits.view(-1) return sorted(zip(scores.tolist(), documents), reverseTrue, keylambda x: x[0])[:5]重排阶段的文本长度也要留心输入超过512个token会被截断所以重排前最好把文档按句子切一下对每个句子打分再聚合。这样重排结果更稳定代价是耗时翻倍。4.2 把 RAG 变成 Agent 的一条工具链RAG接入Agent时最自然的做法是把它包装成一个工具。Agent在规划阶段判断问题是否需要外部知识需要时就把调用参数传给工具拿到检索结果继续推理。工具名和描述要写得精准这直接影响Agent是否能正确调用。from langchain.tools import tool tool def knowledge_search(query: str) - str: 当用户询问产品参数、使用手册、内部制度时调用此工具 从知识库中检索相关内容并返回带来源的片段。 docs retriever.get_relevant_documents(query) # 重排 ranked rerank(query, [d.page_content for d in docs]) return \n\n.join([f[来源: {d.metadata[source]}] {text} for score, text in ranked])之前用LangChain搭Agent时把knowledge_search加入tools列表再配一个system提示词说明“涉及具体资料问题先调用knowledge_search不要直接回答”。这套接法省事且通用不管是ReAct还是Function Calling都支持。需要注意一个细节Agent的规划质量决定RAG的使用效率。如果你发现Agent经常在不需要外部知识时也调工具多半是工具描述里没写清楚适用条件反过来该调用时不调用是因为描述里没写明白“知识库里面有什么内容”。描述写得好不好直接影响Agent对工具的选择逻辑。4.3 提示词模板与上下文组装检索结果拿到手组装提示词也有门道。我见过最简单也最有效的模板结构固定角色用户问题检索片段输出规则。检索片段要标明来源输出规则要限定“只能基于片段回答没有相关内容就直说不知道”。from langchain.prompts import PromptTemplate PROMPT_TEMPLATE 你是企业内部知识助手。请严格基于以下资料回答问题。 如果资料中没有答案请直接回复“未在知识库中找到相关信息”不要编造。 【知识片段】 {context} 【用户问题】 {question} 输出要求 1. 优先引用资料原文注明来源 2. 不掺杂个人推测 3. 回答控制在200字以内 prompt PromptTemplate( templatePROMPT_TEMPLATE, input_variables[context, question] )更进阶的做法是查询改写用户问题可能含糊直接拿原始问句去检索效果差。比如用户问“那个功能怎么开”你需要先把它改写成“XX功能启用步骤”再进检索。Agent场景下这步可以由LLM完成但会让整个链路多一次模型调用延迟增加300到800毫秒需要权衡。5. 实战中的坑与排查方法5.1 高频问题速查表做这一章前我翻了一遍这几年攒的排查记录挑出十个出现频率最高的问题按“症状-原因-处理”整理成表这份表基本能覆盖大部分RAG排障场景。症状可能原因处理方式检索结果和问题完全无关Embedding模型没加bge查询指令对比加/不加的检索结果加上指令答案引用了错误章节chunk_size过大块内语义混杂缩小chunk_size加大overlap检索结果重复度极高top_k过大导致多块来自同一段落降低top_k或增加去重逻辑PDF表格内容检索不到表格区域被跳过或错位用pdfplumber把表格平铺成文本答案接近但关键数字错误检索回来的是被截断的半句调大overlap并检查切分边界每次查询都极慢向量库未建索引或并发不足持久化索引或迁移到服务端向量库文档更新后检索不到新内容旧索引未同步增量更新写增量更新任务按更新时间刷新调用Agent时工具名识别失败工具描述不够精确重写描述列出典型查询关键词本地Embedding占内存过高模型太大或未用半精度换轻量模型推理用fp16检索很快但答案质量差缺少重排或检索策略太简单加reranker或改用混合检索5.2 排查定位三板斧遇到问题先别急着调参用我固定的三板斧定位第一可视化检索结果打印召回的前几条看文本到底匹配了什么八成问题在切分或Embedding第二做“空跑测试”——用一个已知答案的问题去检索看正确的块有没有被召回没召回说明索引有问题召回了答案还错说明提示词或重排有问题第三把检索到的块单独丢给模型不走Agent排除工具调度环节的干扰。我遇到过一个特别迷惑的案例答案总是答非所问中间花了两天排查最后发现是PDF解析时把两列表格合并成了段落检索出来的“相关片段”本身就是错乱的。所以说第一步“可视化召回结果”这个习惯极其重要它能帮你把问题快速缩小到具体环节而不是在整个管道里瞎猜。还有个调试技巧值得分享记录每次查询的完整链路包括用户问题、召回前20的ID、重排后保留的ID、模型最终回复。跑几个样本后回来看日志很多问题一眼就能看出端倪比如“用户问题里有产品型号但召回列表里全是说明书导航页”——那一定是清洗阶段没把页眉笔画去干净。6. 把管道做实一个最小可用代码示例上面五章已经把原理、选型、实现拆分讲清楚了这里我压缩出一个最小可复制的完整脚本。它能验证你整条管道是否跑通也可以作为后续扩展的脚手架。from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_chroma import Chroma import fitz # 1. 加载 doc fitz.open(handbook.pdf) text \n.join(page.get_text(text) for page in doc) # 2. 切分 splitter RecursiveCharacterTextSplitter(chunk_size500, chunk_overlap80) chunks splitter.split_text(text) # 3. 向量化并写入 embeddings HuggingFaceEmbeddings(model_nameBAAI/bge-large-zh-v1.5) vectorstore Chroma.from_texts(chunks, embeddings, persist_directory./chroma_db) # 4. 检索 retriever vectorstore.as_retriever(search_kwargs{k: 5}) query 产品的保修期是多久 docs retriever.get_relevant_documents( 为这个句子生成表示以用于检索相关文章 query ) for i, doc in enumerate(docs): print(f--- 第{i1}条 ---) print(doc.page_content[:200])这份脚本能跑通说明你的环境、模型、向量库都没问题。之后再加reranker、重写器、查询改写都是在骨架上添肉。别一上来就想做Agentic RAG那些花活先把基础管道取信了再往复杂方向走效率最高。我在实际使用中的体会是RAG这条管道一开始的“够用”远比“完美”重要。先让它跑通让答案有依据、有来源、可验证然后再逐步优化切分和重排。等你跑完几个真实业务场景看到检索结果一次比一次精准会比任何架构图都更懂这条管道到底哪里该花力气。最后提醒一句上面所有调优经验都是我自己的实测数据环境一变参数结论也会变务必用你自己的语料重新测一遍别人的参数表格只能当起点不能当终点。
返回列表