1. RAG 到底在解决什么问题1.1 从一次尴尬的问答说起去年年底我帮一个做工业设备维保的团队做技术咨询他们想用大模型做一个内部知识助手。第一版做出来特别简单就是把设备手册、故障处理记录、历史工单全部塞进提示词里然后让模型回答工程师的问题。Demo 阶段效果惊艳所有人都觉得这事成了。结果上线第三天就翻车了一位老师傅问“3号产线液压站压力波动超过0.5MPa时按照去年11月那份技改方案应该先查哪个阀”模型一本正经地回答说“请先检查电磁换向阀”但那份技改方案里明确写的是“先确认蓄能器氮气压力再排查比例阀”。答案错得离谱而且语气极其自信。这个场景几乎每天都在各种团队里重演。大模型本身是一个“参数化知识容器”它记住的是训练语料里的统计规律而不是你企业内部的、私有的、时效性极强的知识。你问它通用问题它很在行你问它“我们公司上周刚定的报销新规”它只能靠猜。更麻烦的是它猜错的时候不会告诉你“我不确定”而是用同样流畅的语气编一个听起来很合理的答案。这就是所谓的幻觉。RAGRetrieval-Augmented Generation检索增强生成就是冲着这个问题来的。它的核心思路非常朴素别让模型凭记忆答题先帮它把相关资料找出来摆在它面前让它照着资料回答。就像开卷考试学生不需要背下整本书但需要知道去哪一页找答案。RAG 要解决的就是“去哪一页找”和“怎么照着答”这两件事。1.2 RAG、微调、长上下文三条路怎么选很多人一上来就问“我该用 RAG 还是微调”。我的经验是先问自己三个问题知识更新频率多高知识量多大对答案可追溯性的要求多强方案适合场景知识更新成本可追溯性典型坑RAG知识频繁更新、量大、需要引用来源低改库即可强能给出原文出处检索不准导致答非所问微调固定领域风格、固定格式输出高要重新训练弱说不清依据知识更新要重训成本高长上下文单次任务、资料量小无中贵、慢、超长后注意力衰减我一般建议只要你的知识是“文档形态”且会变优先 RAG。微调更适合教模型“怎么说话”而不是“记住什么”。长上下文适合一次性分析比如“把这份合同总结一下”但不适合做长期知识库因为每次都要把全部资料塞进去成本和延迟都受不了。1.3 一条完整的 RAG 链路长什么样把 RAG 拆开看其实就是一条流水线我习惯把它分成两大阶段、六个环节离线建库阶段文档加载 → 文本切块 → 向量化 → 存入向量数据库。在线检索阶段用户提问 → 问题向量化 → 向量检索召回 → 重排 → 拼装提示词 → 大模型生成答案。这条链路里任何一个环节出问题最终答案都会崩。我见过太多团队把 90% 的精力花在“换个更强的模型”上结果检索环节召回的全是无关内容模型再强也只能对着垃圾资料编。RAG 的瓶颈几乎永远在检索不在生成。这句话你先记住后面每一节我都会反复印证它。2. 建库把文档变成模型能用的知识2.1 文档加载别小看格式清洗这一步建库的第一步是把各种格式的文档读进来。PDF、Word、Markdown、HTML、Excel、甚至扫描件LangChain 里对应一堆 Loader比如PyPDFLoader、UnstructuredWordDocumentLoader、TextLoader。看起来很简单但这里埋的坑最多。我踩过最狠的一次是 PDF 解析。一份 200 页的设备手册用默认的 PDF Loader 读出来表格全部错位页眉页脚混进正文双栏排版被读成一行一行的乱码。结果切块之后每个块都是语义破碎的碎片检索出来的内容驴唇不对马嘴。后来换成unstructured库配合strategyhi_res表格识别明显改善但速度慢了好几倍。我的实操建议是PDF 优先用unstructured或pdfplumber尤其是含表格的文档别用最基础的解析器。扫描件必须先做 OCR否则读出来是空白。OCR 质量直接决定后续一切。页眉页脚、页码、水印要清洗掉这些噪声会污染向量让检索跑偏。保留文档的层级结构比如标题、章节号后面切块时能派上大用场。提示文档加载阶段一定要抽样人工检查。随机抽 10 个块读一遍看看语义是否完整。这一步花 20 分钟能省你后面几天的调试。2.2 文本切块RAG 里最被低估的环节切块Chunking是 RAG 里最不起眼、却最影响效果的一步。为什么因为向量检索的最小单位就是块。块切得不好检索再准也没用。先说块大小。太小比如 100 字一个完整的意思被切碎检索出来缺上下文太大比如 2000 字一个块里混了好几个主题向量被“平均”了检索精度下降。业界常见的经验值是256 到 512 个 token但这只是起点不是标准答案。我一般用这个思路定块大小看你的知识颗粒度。如果用户的问题通常针对某个具体操作步骤块就切小一点300 token 左右如果问题需要一整段论述才能回答块就切大一点800 token 左右。没有万能值必须拿真实问题去测。再说切块策略。最粗暴的是固定长度切按字符数硬切简单但容易切断句子。好一点的是递归切分RecursiveCharacterTextSplitter按段落、句子、词的优先级依次尝试尽量在语义边界断开。再进一步是按文档结构切比如按 Markdown 标题、按章节切这样每个块天然是一个完整主题。from langchain.text_splitter import RecursiveCharacterTextSplitter splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap80, separators[\n\n, \n, 。, , , , ] ) chunks splitter.split_text(document_text)这里的chunk_overlap是块之间的重叠部分我一般设成块大小的 10% 到 20%。为什么要重叠因为切块难免在边界处丢信息重叠能让相邻块共享一部分上下文检索时不容易漏。比如一个关键结论正好卡在两个块的交界处没有重叠就可能两边都只拿到半句。注意中文文档的 separators 一定要加上中文标点。默认的英文分隔符对中文很不友好会把一整段中文当成一个“词”处理切出来的块质量很差。2.3 向量化Embedding 模型怎么选切完块下一步是把每个块转成向量。这一步用的是 Embedding 模型它把一段文本映射成一个高维浮点数组语义相近的文本在向量空间里距离更近。选 Embedding 模型我主要看四个维度语言支持、维度、性能、成本。模型语言维度特点适用场景text-embedding-3-small多语言1536便宜、快、效果均衡通用场景首选text-embedding-3-large多语言3072效果更好、更贵对精度要求高bge-large-zh中文1024中文效果好、可本地部署中文知识库、数据敏感m3e-base中文768轻量、本地资源受限的本地部署我的经验是中文知识库优先考虑 bge 系列或 m3e尤其是数据不能出内网的场景本地部署是刚需。如果追求省事且能接受调用外部服务text-embedding-3-small 是性价比很高的选择。这里有个关键点很多人忽略建库用的 Embedding 模型和检索时用的必须是同一个。你建库用 A 模型检索用 B 模型两个向量空间根本对不上检索结果就是随机的。我见过有人建库时用了一个模型后来觉得另一个更好直接换了检索端结果整个库废掉只能重建。还有一个细节是归一化。有些模型输出的向量没有归一化做余弦相似度之前要手动归一化否则距离计算会偏。LangChain 的很多封装会自动处理但自己写代码时要注意。2.4 向量数据库选型Milvus、Chroma、Qdrant 怎么挑向量数据库是 RAG 的存储层。市面上的选择很多我重点说三个最常被问到的Chroma、Qdrant、Milvus。Chroma是最容易上手的几行代码就能跑起来支持内存模式和本地持久化。适合原型验证、小规模知识库、个人项目。缺点是生产环境的扩展性一般数据量大了之后性能会吃紧。Qdrant是我个人最推荐的中小规模生产选择。它是 Rust 写的性能好支持过滤、混合检索部署也简单Docker 一条命令就能起来。API 设计清晰Python 客户端用起来很顺手。Milvus是重量级选手适合大规模、高并发的场景。它支持多种索引类型IVF、HNSW、DiskANN 等能水平扩展但部署和运维复杂度明显更高需要依赖 etcd、MinIO 等组件。如果你的数据量在百万级以下用 Milvus 有点杀鸡用牛刀。数据库上手难度扩展性适合规模部署方式Chroma极低一般万级以下内存/本地Qdrant低好十万到百万级Docker/云Milvus中高极好百万级以上集群选型的核心原则是别过度设计。我见过一个团队知识库总共就 3000 个块非要上 Milvus 集群结果运维成本比开发成本还高。先用 Chroma 或 Qdrant 跑通等数据量真的上来了再迁移迁移成本远低于你想象。from qdrant_client import QdrantClient from langchain.vectorstores import Qdrant client QdrantClient(path./qdrant_data) # 本地模式 vectorstore Qdrant( clientclient, collection_nameknowledge_base, embeddingsembedding_model ) vectorstore.add_documents(chunks)3. 检索决定 RAG 成败的关键一环3.1 相似度检索的基本原理检索的本质是把用户的问题也转成向量然后在向量库里找距离最近的几个块。距离度量常用余弦相似度值越接近 1 越相似。听起来简单但“最近”不等于“最相关”。向量检索是语义近似不是逻辑精确。比如用户问“设备过热怎么处理”向量检索可能召回“设备温度监测方案”语义上很近但用户要的是处理步骤不是监测方案。这就是为什么单纯靠向量检索不够后面要加各种优化。检索时有个参数叫top_k就是召回多少个块。太小可能漏掉关键信息太大噪声变多还会挤占提示词空间。我一般从 4 到 6 开始调配合重排使用。3.2 混合检索向量 关键词效果立竿见影纯向量检索有个软肋对专有名词、型号、编号不敏感。比如用户问“ERR-2047 报错怎么解决”向量模型可能把“ERR-2047”当成普通文本召回一堆泛泛的报错处理反而漏掉真正提到这个编号的文档。解决办法是混合检索向量检索负责语义匹配关键词检索BM25负责精确匹配两路结果融合。这样既能理解“过热”和“温度过高”是同一回事又能精准命中“ERR-2047”这种硬编码。Qdrant 和 Milvus 都支持混合检索LangChain 里也有EnsembleRetriever可以把多个检索器组合起来from langchain.retrievers import EnsembleRetriever, BM25Retriever bm25 BM25Retriever.from_documents(chunks) bm25.k 4 vector_retriever vectorstore.as_retriever(search_kwargs{k: 4}) ensemble EnsembleRetriever( retrievers[bm25, vector_retriever], weights[0.4, 0.6] )实测下来混合检索在专有名词密集的工业、医疗、法律场景里召回率提升非常明显。权重怎么设我一般让向量占 0.6关键词占 0.4具体还得拿测试集调。3.3 重排把最相关的顶到最前面混合检索召回了 8 个块但它们的相关性参差不齐。这时候需要一个重排模型Reranker来精排。重排模型通常是交叉编码器Cross-Encoder它把问题和每个块拼在一起打分精度比向量相似度高得多但速度慢所以只用在召回后的少量候选上。流程是向量检索召回 20 个 → 重排模型打分 → 取前 5 个送给大模型。这样既保证了召回广度又保证了精度。from langchain.retrievers import ContextualCompressionRetriever from langchain.retrievers.document_compressors import CrossEncoderReranker from langchain_community.cross_encoders import HuggingFaceCrossEncoder model HuggingFaceCrossEncoder(model_nameBAAI/bge-reranker-base) compressor CrossEncoderReranker(modelmodel, top_n5) retriever ContextualCompressionRetriever( base_compressorcompressor, base_retrieverensemble )重排是我认为性价比最高的优化手段之一。加一个重排模型往往比换一个更贵的生成模型效果提升更明显。因为生成模型再强喂给它的资料不对它也答不对。3.4 查询改写让用户的问题更好被检索用户提问往往很口语、很模糊。比如“那个东西坏了咋整”向量检索根本不知道“那个东西”是什么。查询改写就是在大模型检索之前先把问题改写成更适合检索的形式。常见做法有几种一是问题扩展把一个问题改写成多个相关查询分别检索再合并二是指代消解结合对话历史把“那个东西”替换成具体名词三是HyDE让模型先“假装”写一个答案再用这个答案去检索因为答案和文档的语义更接近。rewrite_prompt 根据对话历史把用户的问题改写成独立、完整、适合检索的查询。 对话历史{history} 用户问题{question} 改写后的查询查询改写对多轮对话场景尤其重要。单轮问答可能感觉不出来一旦用户开始追问“那它呢”“还有别的吗”没有改写就会检索得一塌糊涂。4. 生成把检索结果变成靠谱答案4.1 提示词拼装给模型立规矩检索到相关块之后要把它们和用户问题一起拼成提示词。这一步看似简单其实决定了模型会不会“跑偏”。我的提示词模板一般包含四部分角色设定、参考资料、用户问题、回答约束。关键是约束部分必须明确告诉模型只根据参考资料回答资料里没有就说不知道不要编。prompt_template 你是一个严谨的知识助手。请严格根据下面的参考资料回答用户问题。 参考资料 {context} 用户问题{question} 回答要求 1. 只使用参考资料中的信息不要编造。 2. 如果参考资料中没有相关信息直接回答“根据现有资料无法回答”。 3. 回答时标注信息来源的文档名称。 这个“不知道就说不知道”的约束极其重要。我做过对比测试不加这条约束模型在资料缺失时的幻觉率能到 40% 以上加上之后降到个位数。宁可它说“不知道”也不要它编一个错误答案尤其在工业、医疗这种场景错误答案的代价太高。4.2 引用溯源让答案可验证RAG 相比纯生成的一大优势是可追溯。答案里的每句话最好都能对应到具体的文档块。实现方式是在拼装提示词时给每个块编号要求模型在回答时引用编号前端再把编号映射回原文。这样做有两个好处一是用户能自己验证答案对不对二是出问题时你能快速定位是检索错了还是生成错了。我在项目里会强制要求模型输出引用哪怕牺牲一点流畅度也值得。4.3 生成模型的选择与参数调优生成模型的选择要看场景。如果对成本敏感中小参数模型配合好的检索往往够用如果对推理能力要求高比如需要综合多个块做推理就得上更强的模型。参数上temperature建议设低0 到 0.3 之间因为 RAG 要的是忠实于资料不是发挥创意。max_tokens要留够别让答案被截断。还有一个容易忽略的是上下文长度检索回来的块加上提示词不能超过模型的上下文窗口超了会被截断所以top_k不能无限大。5. 常见问题与排查实录5.1 检索不准的排查思路检索不准是最高频的问题。我的排查顺序是先看召回内容。把检索到的块打印出来人工判断相关性。如果召回的就是垃圾问题在检索层不在生成层。检查切块质量。块是不是语义破碎是不是太大混了多个主题检查 Embedding 模型。建库和检索是不是同一个模型模型是否适合你的语言和领域加混合检索和重排。这是提升召回最直接的手段。做查询改写。用户问题本身是否适合检索5.2 常见问题速查表现象可能原因解决方向答非所问召回内容不相关检查切块、加混合检索、加重排答案编造提示词约束不足加强“不知道就说不知道”约束漏掉关键信息top_k 太小或切块切断增大 top_k、加块重叠专有名词检索不到纯向量检索不敏感加 BM25 混合检索多轮对话检索乱指代未消解加查询改写响应慢重排或模型太大减少候选数、换轻量模型答案被截断max_tokens 或上下文超限调大参数、减少 top_k5.3 几个我踩过的坑坑一块重叠设太大。有次我把 overlap 设成块大小的一半结果检索出来一堆重复内容提示词被撑爆模型反而抓不住重点。重叠 10% 到 20% 就够了。坑二忽略元数据过滤。知识库里有多个版本的手册检索时没按版本过滤召回了旧版本的内容答案自然错。后来给每个块加了版本、部门、时间等元数据检索时先过滤再检索准确率大幅提升。坑三盲目追求大模型。有段时间我总觉得答案不好是模型不够强换了个更大的模型效果提升有限成本翻倍。后来发现瓶颈在检索把检索优化好小模型也能答得很准。坑四没有评测集。早期我全靠感觉调参改来改去不知道有没有变好。后来建了一个几十条真实问题的评测集每次改动都跑一遍才知道哪些优化真的有效。没有评测的调优都是玄学。6. 从 RAG 到 Agentic RAG 的演进6.1 传统 RAG 的天花板传统 RAG 是一条固定流水线检索一次生成一次。但真实问题往往需要多步推理。比如“对比 A 方案和 B 方案的成本差异”一次检索可能只召回 A 方案模型就答不全。再比如“根据故障现象推断原因”需要先检索现象再检索原因再关联。这就是传统 RAG 的天花板它不会“想”只会“查一次然后答”。6.2 Agentic RAG让模型自己决定怎么查Agentic RAG 的思路是把检索变成模型可以调用的工具让模型自己决定要不要检索、检索什么、检索几次、结果够不够、要不要再查。这就用到了 Agent 框架比如 LangChain 的 Agent、LangGraph 的状态机。LangChain 和 LangGraph 的区别这里顺带说一句LangChain 更偏向链式编排适合线性流程LangGraph 是图结构支持循环、分支、状态管理适合需要多步推理和条件跳转的 Agent 场景。做 Agentic RAGLangGraph 更合适。一个典型的 Agentic RAG 流程是模型先判断问题类型 → 决定检索策略 → 检索 → 评估结果是否充分 → 不充分就改写查询再检索 → 充分了再生成。这个循环让 RAG 从“一次性”变成“迭代式”能处理复杂得多的任务。6.3 什么时候该上 Agentic RAG我的建议是先用传统 RAG 跑通遇到多步推理的瓶颈再上 Agentic。Agentic RAG 更灵活但也更慢、更贵、更难调试。如果你的问题大多是“查一个事实”传统 RAG 完全够用。只有当问题需要“查多个事实再综合”时Agentic 的价值才体现出来。7. 一套可复用的落地清单最后把我这些年做 RAG 的经验浓缩成一份清单你照着走能少踩很多坑建库阶段文档清洗要彻底切块要按语义块大小拿真实问题测Embedding 模型建库检索必须一致。检索阶段混合检索是标配重排是性价比之王查询改写解决多轮和模糊问题。生成阶段提示词约束要硬引用溯源要强制temperature 要低。评测阶段一定要建评测集没有评测的调优都是玄学。演进阶段传统 RAG 够用就别上 Agentic需要多步推理再考虑。我个人在实际操作中的体会是RAG 这件事80% 的功夫在数据准备和检索优化上只有 20% 在模型和提示词上。很多人本末倒置天天研究换哪个模型却不肯花时间把文档切好、把检索调准。你把检索做扎实了哪怕用中等模型答案也能又准又稳。反过来检索一塌糊涂用再贵的模型也是白搭。这个道理我是在踩了无数次坑之后才真正明白的。