
1. RAG 到底在解决什么问题1.1 大模型的两个“天生缺陷”与知识割裂做 AI Agent 的人应该都有过这种体会你把一个 Agent 接到业务里它聊得头头是道一落到具体数据上就开始胡编。比如我问它“我们公司去年 Q3 华东区的退货率是多少”它没有这个数据但它绝不会说“我不知道”它会编一个看起来非常合理的数字给你。这就是大模型的第一个天生缺陷——知识截止与幻觉。第二个缺陷更隐蔽私有知识进不去。我把几十份产品手册、运维工单、客服话术扔给它它从参数上就学不进去因为模型训练的时候压根没见过这些文档。微调倒是可以但成本高、更新慢、每次新文档还要重新训根本跟不上业务节奏。所以 RAGRetrieval-Augmented Generation检索增强生成的本质很简单不把知识硬塞进模型而是把知识放在外面需要时由检索系统取回来再喂给模型做回答。它解决的就是大模型“不知道”和“记不住”的问题也顺带缓解了“知识割裂”——业务知识散落在 ERP、Wiki、工单系统、产品手册里RAG 把它们统一接进来变成模型能用的上下文。我见过太多团队一上来就追“Agent 工作流”结果发现 Agent 的核心决策质量完全取决于它拿到的上下文质量。这才是 RAG 在 Agent 体系里被叫做“知识获取管道”的原因——它是 Agent 的输入层不是可选项。1.2 RAG 在 Agent 里的定位不是辅助是基础设施很多人把 RAG 当成“做个问答机器人”的附属品这是认知上的误区。在一个完整的 AI Agent 架构里知识获取管道决定了 Agent 的上限。你可以把 Agent 理解为“大脑 手脚 资料库”。大脑是大模型负责推理和决策手脚是工具调用搜索、计算、操作业务系统资料库就是 RAG。大脑再强如果资料库里的资料查不到、查不准决策必然跑偏。结合我们实际做智能体产品的经验RAG 承担的工作包括知识接入把非结构化文档PDF、Word、Markdown、工单记录和结构化数据ERP 产品信息、数据库记录统一接入。知识切片把长文档切成模型上下文能容纳的片段同时保持语义完整。知识召回根据用户问题从海量切片中快速找出最相关的几段。知识融合把召回的片段和用户问题组装成高质量的 Prompt喂给生成模型。热词里有“解决了知识割裂 RAG”这正是 RAG 在 Agent 里的核心价值——它把原来散落在各个系统里的知识通过统一管道汇入 Agent 的决策上下文。后面我会用一个本地 ERP 产品检索的实战案例来具体展开。2. RAG 管道五步拆解从原始文档到可用知识RAG 基础阶段的管道可以拆成五个环节每一步都有讲究。我按真实开发顺序讲每步都会说到我之前踩过的坑。2.1 文档加载与清洗脏数据进脏结果出管道的第一步是加载文档。听起来简单实际做起来最烦。一个典型的企业知识库里有 PDF扫描件、电子版都有、Word、Excel、网页和工单文本格式五花八门。PDF 是最难处理的。电子版 PDF 还好用 PyPDF 或 pdfplumber 就能抽文字扫描件必须走 OCR否则抽出来全是乱码。我当时处理一批设备手册前 30 页是扫描图片直接抽出了大量空行和错字导致后面检索效果惨不忍睹。后来加了 OCR 环节但 OCR 结果质量取决于扫描质量倾斜、反光都会影响识别率需要做图像预处理。清洗阶段要处理的是空行、页眉页脚、表格错乱、多余换行。很多团队忽略清洗直接把抽取的文本按字符切分结果切出来的 chunk 里全是页眉和页码检索命中率自然上不去。提示加载与清洗阶段务必核对“进库内容的可读性”。如果抽出来的文本人眼都读不通后面所有环节的效果都会打折。这一条再怎么强调都不为过。2.2 切分策略chunk size 不只是数值选择文档清洗完下一步是切分。切分的目的是把长文档切成适合嵌入模型和 LLM 上下文窗口的“语义单元”。先看 chunk size。常见的做法是固定长度切分比如 embed 模型支持 512 token就把文本按 500 token 左右切。但是固定长度切分有个致命问题——它会把一个完整的段落或表格从中砍断。比如一段介绍产品保修政策的文字被切成两半一半进 chunk A一半进 chunk B用户问保修政策时检索只能召回 chunk A信息不完整。更靠谱的做法是“递归切分 分隔符优先级”。LangChain 的 RecursiveCharacterTextSplitter 就是这个思路先按段落分隔符\n\n切如果切出来的块还是太长再按句号、逗号、空格往下切。这样可以尽可能保持语义完整。切分时还要考虑chunk_overlap重叠长度。为什么需要重叠因为如果一句关键描述恰好落在两个 chunk 的交界处没有重叠的话它可能被切碎两个 chunk 里都是半个信息。我一般把 overlap 设为 chunk size 的 10%~15%。比如 chunk 512 tokenoverlap 设 50~80 token。这个比例不高不低既能保证交界信息的完整又不会让 index 膨胀太多。产品文档里我遇到过更复杂的情况一个 markdown 表格切成 token 块后丢失了表头。解决思路是切分前先把表格转成“字段: 值”的文本形式再走通用切分流程。实操心得切分不能“一刀切”。同一个知识库里可能有产品规格文档表格密集和操作手册段落密集建议按文档类型配置不同的切分策略。用 LangChain 的话就是给每种 loader 绑定不同的 splitter。别偷懒写一个全局 splitter检索质量会告诉你代价。2.3 向量化与索引构建选对 embedding 模型切完的 chunk 要变成向量才能被检索。这里的关键是选 embedding 模型。市面上常见的方案有三类我做了个对比方案特点适合场景OpenAI text-embedding-3-small/large效果稳API 调用数据出本地对数据脱敏要求不高的项目快速验证开源模型BGE / m3e / text2vec可本地部署数据不出去中文效果不错企业私有化部署数据敏感不能走外部 API商业化向量化服务阿里、腾讯、百度等国内网络友好中文效果好有免费额度不想自己维护模型对国内云依赖接受度高选型逻辑不复杂本地私有化就选开源 BGE 系业务快速上线且数据允许出网就选 API 型。我目前倾向开源 BGE 配本地部署理由是中国企业项目里“数据不出内网”是硬门槛API 型模型再好过不了信息安全评审就等于零。向量索引选什么中小规模几万到几十万 chunk用 FAISS 就够了简单、内存型、速度快。规模更大再上 Milvus 或 Qdrant。别一上来就上分布式向量库——运维复杂度会吃掉你的开发效率。FAISS 在千万级以内完全能打。索引构建阶段有个细节metadata 一定要存。每个向量关联的 chunk 原文、来源文档、页码、更新时间都要写入索引元数据。后面做结果过滤、引用溯源、增量更新都靠它。2.4 检索召回相似度不等于相关性检索环节最容易踩的误区是用户问了个问题embedding 相似度最高的一批 chunk 就一定是最有用的吗实测结果往往不是。举个真实场景用户问“这台设备的保修期是多久”知识库里有一篇文章讲“设备常见参数”里面包含保修期另一篇文章标题是“保修政策常见问题”内容也很相关。embedding 相似度排序时前者的向量可能更接近用户问题因为它整体都在讲参数而后者可能因为措辞差异排到了后面。但真实的答案其实在后者的内容里更权威。所以基础版 RAG 的检索召回要组合两种方式向量检索负责语义召回解决“关键词不同但意思相近”的问题。关键词检索负责精确命中解决专有名词、型号、编号这类问题。比如“设备型号 XYZ-T100”这种字符串向量检索的效果可能反而不如 BM25 精确匹配。这就是所谓的混合检索Hybrid Search。先各自召回一批候选再做融合排序。LangChain 里的 EnsembleRetriever 就是干这个的可以把 BM25 和向量检索按权重合并。召回参数也要说明白top_k取回多少个候选片段。基础场景 4~6 个即可多了会把不相关内容塞进上下文反而干扰生成。score_threshold相似度阈值低于阈值的直接丢弃。这个值要调试设高了会漏掉正确答案设低了会混入大量噪声。我一般先用测试集跑一遍看召回分布在 0.3~0.8 之间然后取 0.4~0.5 为阈值。2.5 重排与生成融合让模型看到“对的内容”检索完之后所有候选片段直接拼进 Prompt 是最粗放的做法。更好的思路是先做一个Rerank重排环节用专门的重排模型比如开源 BGE-reranker或 API 型 rerank 服务对候选片段做精排把相关性最高的排到最前面。为什么需要重排因为初检用的 embedding 模型擅长做“召回”不擅长做“精排”。召回阶段讲究的是“宁可多召回不要漏”精确度可以低一点重排阶段是拿一个更强的模型在更小的候选集上做精确判断。简单说召回求全重排求准。生成融合环节Prompt 的组装有一些经验性规范。我常用的模板结构是系统指令明确“你是企业知识助手仅基于提供的资料回答资料不够时直接说明不知道”。知识片段用清晰的标识符区分每个片段标注来源文档编号。用户问题放在最后。其中关键的一条是“资料不够时直接说不知道”。不写这条模型会强行把片段拼凑成答案编造细节。写了之后幻觉会大幅减少。实操心得把“没有检索到相关内容时请明确回答‘知识库中未找到相关信息’”写进指令比任何参数调优都更直接地降低幻觉率。这是我实验中效果最明显的一个改动。3. 实战基于 LangChain 快速搭起一个能跑的 RAG 管道3.1 框架与工具选型LangChain 还是 Spring AI做 Java 后端出身的朋友可能更关心 Spring AI做 Python 项目的则绕不开 LangChain。热词里同时出现了 “spring ai rag”“langchain4j rag” 和 “semantic kernel”我们实际团队三个都接触过简要说下结论框架语言特点适合团队LangChainPython生态最全RAG 组件最丰富文档多以 Python 为主的技术团队LangChain4jJava把 LangChain 的设计移植到 Java 生态传统 Java 服务端团队Spring AIJava主打 Spring Boot 原生集成和现有应用无缝接深度绑定 Spring 技术栈的团队Semantic KernelC# / Python微软出品企业级集成能力不错适合 .NET 场景微软技术栈团队对我来说做 RAG 原型验证首选 LangChain——组件之间可以灵活组合Debug 生态也成熟。Java 团队如果业务系统已经跑在 Spring Boot 上用 Spring AI 会省掉跨语言通信的麻烦从数据源到向量库可以一条链路下来。下面的实操演示用 LangChain OpenAI 兼容接口 FAISS 做示例。考虑到很多企业在国内部署需要本地化大模型OpenAI 接口这块我会提到“兼容接口”的通用做法。3.2 五步管道代码实现第一步初始化向量存储并构建文档加载器from langchain_community.document_loaders import PyPDFLoader, TextLoader from langchain.text_splitter import RecursiveCharacterTextSplitter # 加载 PDF 和 TXT loaders [ PyPDFLoader(docs/product_manual.pdf), TextLoader(docs/faq.txt, encodingutf-8), ] docs [] for loader in loaders: docs.extend(loader.load()) # 切分 splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap50, separators[\n\n, \n, 。, , , , , , ], ) chunks splitter.split_documents(docs) print(f切分成 {len(chunks)} 个片段)这里我把分隔符按中文语序做了调整先按段落分离再按句号等句末标点分离。保留标点符号的效果比纯按 \n 切分更好因为中文语义往往在一句话内才完整。第二步定义本地 embedding 和向量库from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_community.vectorstores import FAISS embedding HuggingFaceEmbeddings(model_nameBAAI/bge-large-zh-v1.5) vectorstore FAISS.from_documents(chunks, embedding) vectorstore.save_local(faiss_index)提示BGE 系列的查询指令query instruction通常会提升检索效果。以bge-large-zh-v1.5为例构建查询向量时建议在问题前面加“为这个句子生成表示以用于检索相关文章”。每个模型的要求不同具体看模型卡片。这个小技巧能让 top_k 命中率提升几个点。第三步构建混合检索 重排 生成问答链from langchain.retrievers import EnsembleRetriever from langchain_community.retrievers import BM25Retriever from langchain_community.cross_encoders import HuggingFaceCrossEncoder from langchain.retrievers.document_compressors import CrossEncoderReranker from langchain.chains.combine_documents import create_stuff_documents_chain from langchain.chains import create_retrieval_chain from langchain_openai import ChatOpenAI # 双路召回 bm25_retriever BM25Retriever.from_documents(chunks) bm25_retriever.k 8 vector_retriever vectorstore.as_retriever(search_kwargs{k: 8}) ensemble EnsembleRetriever( retrievers[bm25_retriever, vector_retriever], weights[0.3, 0.7], ) # 重排精排到 top 4 compressor CrossEncoderReranker( modelHuggingFaceCrossEncoder(model_nameBAAI/bge-reranker-large), top_n4, ) rerank_retriever ensemble | compressor # 生成模型 llm ChatOpenAI( modelqwen-max, api_keyyour-api-key, base_urlhttps://dashscope.aliyuncs.com/compatible-mode/v1, )双路召回的权重配比 0.3/0.7是我按照“向量为主要召回手段关键词为补充”的原则定的。如果你的知识库中型号、编号类查询占比高可以适度调高 BM25 的权重。第四步组装提示词链from langchain_core.prompts import ChatPromptTemplate prompt ChatPromptTemplate.from_messages([ (system, 你是一个企业内部知识助手。请依据以下资料回答问题。 规则 1. 只使用资料提供的信息不要自行编造。 2. 如果资料中没有相关信息明确回答“知识库中未找到相关信息”。 3. 回答时请标注信息来源格式为【来源文档名】。 资料 {context}), (human, {input}), ]) document_chain create_stuff_documents_chain(llm, prompt) rag_chain create_retrieval_chain(rerank_retriever, document_chain) answer rag_chain.invoke({input: 这台设备的保修期是多久}) print(answer[answer])这四步流程跑通后一个基础 RAG 管道就成型了。实际以“本地 ERP RAG LLM 产品检索”为场景的做法也可以照搬ERP 中的产品信息导出为结构化文本每一行产品的属性描述当成一个文档走同一套切分、索引、检索流程。3.3 核心参数速查与调优建议基础 RAG 调优时最该盯的参数我整理成一个表格参数建议初始值影响排查方向chunk_size300~500 token越大上下文越完整但噪声越多越小越精准但信息易不完整看召回片段是否频繁“半句话没说完”chunk_overlapchunk_size 的 10%~15%影响交界信息的完整性手动检查切分结果中是否出现句子被硬切断top_k初检 8~10精排 4~6影响候选集宽度与最终上下文长度观察答案所需关键信息是否总在候选之外score_threshold0.4~0.5过滤噪声片段在测试集上统计未命中问题的阈值分布混合检索权重BM25 0.3 / 向量 0.7精确匹配与语义匹配的平衡专有名词查询偏多则上调 BM25 权重rerank top_n4最终进入 Prompt 的片段数答案质量不达标时先试加大到 5~6这些参数没有“最优解”必须依赖自己的测试集来调。后面第四节我会讲具体怎么评估。4. 从 RAG 到 Agentic RAG瓶颈与进化方向4.1 为什么基础 RAG 的 hit rate 上不去热词里有个“rag hit rate”中文叫“命中率”指的是检索系统召回的片段中真正包含答案的比例。这是 RAG 项目里最让人揪心的指标——大部分团队卡在 60%~70% 上不去就开始怀疑模型怀疑向量库怀疑一切。但根因往往在管道设计上。我总结最常见的三个瓶颈第一查询和文档的表达错位。用户问的是口语化问题文档里是书面语表述embedding 模型如果够强能跨过去但常见的开源中文 embedding 模型对这种跨度比较大的语义匹配效果并不理想。解决方案不是盲目换更大的模型而是做查询改写——在检索之前用一个 LLM 把用户问题改写成更贴近文档表述的查询词。比如用户问“这个能质保多久”改写为“产品保修期时长保修政策覆盖范围”。第二切分破坏了答案上下文。前面反复强调 chunk 切分这里不重复只强调一句话——检查你的召回片段看看答案是不是经常被切成了两半。第三单轮检索的局限。对简单问题一轮检索就够了但复杂问题比如“对比一下 A 设备和 B 设备在保修政策上的差异”需要先检索出两个设备的资料再综合回答。基础 RAG 的一次检索只能拿到最相似的片段缺少这种多步推理能力。4.2 Agentic RAG让检索成为一项 Agent 能力热词里“agentic rag”已经是当前大热的方向。它和基础 RAG 的区别在哪基础 RAG 是固定的“检索-生成”流水线Agentic RAG 把检索权交给了 Agent让它自己决定何时检索、检索什么、要不要多次检索、用不用工具。举个例子。我们做过一个运维知识助手基础 RAG 版本只能回答“某设备参数是什么”这种直接问题。但用户经常问的是“这台设备的告警日志里出现了 A 错误码可能是什么原因”。这个问题需要两步检索先查错误码对应的常见故障再查该故障的处理步骤。基础 RAG 一步检索根本拿不全信息Agentic RAG 则会拆成多个检索动作依次执行甚至中间还会结合知识库里的故障树表。从工程实现看Agentic RAG 也不是什么玄学就是在 Agent 的 ReAct 循环里注册一个“知识检索工具”由 LLM 决定工具调用参数。基于 LangChain核心代码变得非常精简from langchain.tools import tool from langchain.agents import create_tool_calling_agent, AgentExecutor tool def search_knowledge(query: str) - str: 当回答需要产品知识时调用此工具检索产品文档和FAQ。 docs rerank_retriever.invoke(query) return \n\n.join(doc.page_content for doc in docs) agent create_tool_calling_agent(llm, [search_knowledge], prompt) executor AgentExecutor(agentagent, tools[search_knowledge])Agent 会在需要知识时自动调用search_knowledge而且它可以调用多次每次用一个新查询词。这天然解决了基础 RAG 单轮检索的瓶颈。4.3 效果评估用测试集约束迭代方向说到评估很多人直接“凭感觉”觉得回答还行就上线了。这在演示阶段没问题上线后发现问题就麻烦了。我在项目中推行的做法是建一个 50~100 条的最小评估集每条包含“用户问题、标准答案片段、期望召回的文档名”。用这个测试集每改一次参数或重构管道就跑一遍记录两个核心指标Hit rate召回命中率标准答案片段是否出现在召回的 top-k 中。基础 RAG 先盯这个指标。Answer correctness回答正确率由 LLM 作为裁判把 Agent 的回答与标准答案做对比打分或判定是否一致。我自己实测下来的体会是先把 hit rate 调到 90% 以上再谈回答正确率。如果 hit rate 只有 70%你调 Prompt、换生成模型都是白费——答案源头的信息都没拿到生成模型巧妇难为无米之炊。实操心得不要一开始就追求用 LLM 做自动评估。先用 20 条测试数据人工看检索日志精确定位是哪个环节丢了答案。人工看十轮之后你对管道的直觉会大幅提升之后再上自动评估体系才有意义。5. 常见问题与排查速查这一节整理项目中出现频率最高的问题按“现象-原因-处置”格式记录方便直接按图索骥。5.1 检索不到答案先查数据再查代码现象用户问题明明在知识库里有答案但回答是“未找到”。排查顺序有讲究排查步骤操作说明1直接在原始文档里搜答案关键词排除“文档里根本没有”的误会2查看检索日志中命中的 chunk 内容用 debug 模式打印检索结果看召回片段是否包含答案3检查 chunk 切分点答案是否被切到两个 chunk 里各剩一半4检查 embedding 模型换更强的模型试试或加查询改写5检查重排环节初检命中了精排把正确片段排到了后面被截断其中第二步最有价值——我建议开发阶段一定要给检索链路加日志把每个请求的初检前 10 和后重排 top 4 都打出来。这样问题定位往往只需要几分钟。5.2 回答质量差上下文太长还是指令不清现象检索到了答案但模型回答得东扯西拉。这时候排查方向有两个。第一个方向是上下文过长。模型上下文里塞了 4 段相关性不一的片段干扰信息过多。处置方式降低 top_n 到 3提高 score_threshold把弱相关片段过滤掉。第二个方向是系统指令没有约束生成行为。尤其是没写“资料不足时拒绝回答”和“引用来源”这两条模型容易放飞自我。处置方式把这两条规则强制写进 Prompt并在评估集上重新跑分。5.3 增量更新知识变了,旧答案还在问题企业知识库是动态的新文档要加进来旧文档要下架。但向量库里的旧 chunk 不会自动失效。我推荐的做法是“按文档粒度管理索引”每个 chunk 都带上 metadata 中的document_id和updated_at更新时先删除该 document_id 下所有 chunk 再写入新 chunk避免旧数据残留。还有一个实际观察到的怪问题更新文档后向量库索引膨胀检索变慢。这是因为 FAISS 的增删操作会留下无效空间定期做一次索引重建比如每晚触发能明显改善检索速度。5.4 成本与性能的平衡必要的优化时机基础 RAG 阶段性能瓶颈主要在 embedding 的批量耗时和重排模型的推理耗时。实测下来chunk 数量达到 10 万级后单次检索的耗时明显上升。优化方向按性价比排序缓存相同或相似的用户问题直接走缓存不重复检索。这是最省成本的一项。doc_id 过滤通过业务属性如“仅查华东区”先做 metadata 过滤缩小检索范围。重排模型换轻量版从 large 换到 small牺牲一点精排效果换速度。索引分段按文档目录维度拆分成多个小索引先路由再检索。这些优化在项目初期不需要做但当响应时间超过 3 秒或同时在线用户明显增长时就要认真考虑了。6. 写在最后的经验之谈说句实话RAG 基础阶段的核心工作不是写多少代码而是对“每一步都在干什么、为什么这么干”有清晰认知。我在带团队的过程中发现能跑通 RAG 代码的人不少能把 chunk 切分逻辑讲清楚、能把 hit rate 数据背后的问题定位准确的人很稀缺。如果让我给正在搭 RAG 管道的朋友三条最接地气的建议我会说第一先手工验证再自动化。别急着写评估框架先用 20 条数据人工看检索质量建立对管道的手感。第二参数记录成表格。每次调整 chunk size、overlap、top_k都记下对应的 hit rate 变化。我见过太多人调了两周参数最后说不清哪个改动带来了提升——这在项目复盘时非常被动。第三把 Agent 和 RAG 当成一个整体来设计。RAG 不是独立的问答接口它是 Agent 的知识来源。Agent 的规划、工具调用、多轮对话都会和 RAG 深度耦合基础阶段把管道质量做扎实后面接 Agentic 能力时会顺畅很多。RAG 这个方向远没到“已经过时”的阶段。即便有了更长的上下文窗口、更聪明的模型RAG 在可信度、知识可更新、业务可控性上的优势依然无法被替代。把管道基础打牢后面的路会越走越宽敞。