
先说一个判断RAG 真正要解决的不是“能不能回答”而是“回答可不可控”。这个词全称是 Retrieval-Augmented Generation检索增强生成。很多人把它理解成“给大模型加一个知识库”这个理解不算错但如果只停在这一层后面很容易踩坑。真正上手做的时候你会发现影响最终效果的往往不是模型本身而是文档切分、Embedding 选型、检索排序、Prompt 组织、批量任务稳定性这些不起眼的环节。这篇文章适合两类人。一类是刚接触大模型想从零搭建一套 RAG 项目的开发者另一类是自己搭过 Demo但发现效果不稳定、批量跑不了、上线心里没底的工程师。我会按一条完整的项目路径来拆先说明 RAG 解决什么问题再把环境、步骤、参数、验证方法一条条铺开最后给出我实际排查问题时会优先看的几个方向。全程按普通开发环境来写不依赖特殊资源也没有绕不过去的配置门槛。1. RAG 解决的不是“多一个知识库”而是“可控生成”1.1 为什么大模型需要外部检索而不是直接靠训练数据回答问题大模型本身的能力来自训练阶段的“记忆”。你问它常识、历史、常见编码问题它往往能给出像模像样的回答。但只要问题涉及到私有文档、实时数据、企业内部流程、某个版本的新功能说明大模型就会出现两种情况一种是不知道硬编一个答案另一种是知道一点点然后把细节补全。这两种情况都会产生同一个结果——幻觉。RAG 的思路就是换一个方式不逼模型“回忆”而是先把相关资料从文档库里检索出来再把资料和问题一起交给模型让模型基于真实资料生成答案。这样一来答案的来源是可控的生成过程只是对已有材料的归纳和总结。这也是 RAG 和微调的边界。微调是调整模型本身的权重适合让模型学会一种语气、一种输出格式、一种领域知识的内在表达方式。但当你有很多随时会变动的文档资料时每次新增资料都重新微调一次成本和周期都不现实。RAG 更适合“知识频繁更新、答案需要溯源、输入输出格式相对固定”的场景。长上下文模型出现之后也有人讨论“直接把所有文档都塞进上下文行不行”这个方向可以在文本量小的场景里用但文档一多、检索成本一上来它的延迟和 token 消耗就会明显变大也不适合需要精确命中某一段落的知识库问答。1.2 一个完整 RAG 项目包含哪些环节一个最小可用的 RAG 项目至少包含以下环节文档加载从 PDF、Word、Markdown、TXT、网页等来源读取文本内容。文本切分把长文档拆成适合检索的片段这一步叫 Chunking。向量化用 Embedding 模型把每个文本片段转成向量。索引存储把向量和原文一起写入向量数据库或本地索引文件。检索用户提问时先把问题也转成向量然后做相似度检索。重排序可选但推荐把 TopK 的结果按相关性重新排序。生成把检索结果和原始问题组装成 Prompt交给大模型生成答案。大多数入门项目会在第二步到第五步之间反复折腾因为前三步决定了检索质量检索质量又决定了生成质量。生成环节反而是最稳定的只要输入 Prompt 组织得当主流大模型都能输出不错的结果。2. 跑通第一个 RAG 前先把环境补到“能跑”水平2.1 本地运行 RAG 的最小环境条件先说明一个观点不要一上来就追求高配。RAG 项目里真正吃资源的只有两个地方——Embedding 模型和大语言模型。如果你只用 API 调用大语言模型本地只需要跑一个轻量级 Embedding 模型普通 CPU 机器都能跑。一般开发机建议配置在 8GB 内存以上Python 版本用 3.9 或 3.10磁盘预留 10GB 以上放模型和依赖库。如果你想完全本地化不调用任何远程 API那就需要部署一个对话模型。7B 级别的开源模型在量化之后需要 6GB 到 10GB 显存左右才能跑得比较顺集成显卡或纯 CPU 环境能跑但速度会明显下降适合测试不适合并行压测。很多人会在这一步纠结“用哪个框架”。我的建议是第一次跑通项目时不要用太重的东西。你可以先用 LangChain、LlamaIndex 这类基础框架也可以直接用原生 Python 手写一个 RAG pipeline。手写一遍的好处是你能真正理解每一步发生了什么而不是把整个流程当成黑盒。等理解清楚之后再切换到 Dify、FastGPT、AnythingLLM 这类低代码平台或者迁移到生产级技术栈会容易很多。2.2 依赖库安装和模型下载策略写代码之前先把需要的库装好。一个典型的 Python 环境会包含下面这些核心组件组件作用建议文档解析库读取 PDF、Word、Markdown 等格式按实际文件类型选择不一定要全装文本切分工具按长度、分隔符、递归规则切分文本优先使用框架自带的 RecursiveCharacterTextSplitter 这类通用切分器Embedding 模型把文本转成向量本地小模型或 API 调用均可向量存储保存向量并支持相似度检索小数据量用 FAISS 或 Chroma大数据量用 Milvus、Qdrant、ElasticsearchLLM 客户端调用本地模型或远程 API对应不同模型服务方式装依赖时有一个很常见的坑框架版本互相冲突。LangChain 不同版本的 API 变化很大如果照着网上旧教程写代码经常会遇到“类已经迁移到新目录”“方法已经改名”这类报错。我的建议是先不要安装最新版全家桶固定一个你熟悉教程对应的版本组合跑通之后再统一升级。如果安装过程中遇到依赖冲突优先看错误信息里提示的包名而不是盲目升级整个环境。模型下载也需要注意。Embedding 模型一般体积不大几百 MB 到 1GB 左右对话模型则可能达到数 GB 到十几 GB。下载前先确认模型格式和运行环境是否匹配。如果使用 Ollama 这类工具部署本地模型它会自动处理模型目录和加载逻辑对新手最友好。如果直接通过 Hugging Face Transformers 加载模型需要提前确认模型格式、分词器配置和显存占用。第一次搭环境时建议把所有模型和依赖都提前下载好再开始写代码。否则在调试过程中反复下载很难判断是网络问题、依赖问题还是代码问题。2.3 为什么不要直接调参数而是先跑通最小闭环我见过很多新手第一次跑 RAG上来就把 chunk_size 调到 1000top_k 调到 10还同时开多个 embedding 模型对比效果。结果就是代码一运行报错一层接一层根本分不清是模型加载失败还是参数设置错误。正确顺序应该是先用最少的代码跑通“加载文档 - 切分 - 向量化 - 检索 - 生成”这条主链路。数据量先控制在几页文档最好是一篇 Markdown 或一份短 PDF。所有参数先使用默认值不要追求最佳效果。确认每一步的输出格式正确再开始调参。这里说的“输出格式正确”包括切分后得到的是字符串列表Embedding 后得到的是固定维度向量检索后返回包含原文内容和相似度分数的结果最终 LLM 能基于这些内容生成答案。只要每一步的输出都符合预期这套 RAG 就算是跑通了。3. 手把手从零搭建一套最小 RAG 系统3.1 整体流程和代码组织方式下面我会给出一套基于 Python 的 RAG 核心流程示例不绑定特定框架的完整封装重点是用代码展示链路本身。你可以把它当作理解原理的骨架也可以直接拿去做二次开发。整个流程分为四段文档加载切分、向量化入库、检索、生成。from typing import List # 1. 文档加载与切分示例 def load_documents(file_path: str) - str: with open(file_path, r, encodingutf-8) as f: text f.read() return text def split_text(text: str, chunk_size: int 500, overlap: int 50) - List[str]: # 简化版切分逻辑实际项目建议用框架自带的递归切分器 chunks [] start 0 while start len(text): end start chunk_size chunks.append(text[start:end]) start end - overlap return chunks # 2. 向量化示例伪代码实际需调用 embedding 模型 def embed_texts(texts: List[str]): # 这里替换为你的 embedding 模型或 API 调用 # 返回的应该是一个二维向量数组 pass # 3. 存储和检索示例伪代码 def store_vectors(vectors, texts): # 写入 FAISS / Chroma / Milvus 等向量库 pass def search(query_vector, top_k5): # 在向量库中做相似度检索 # 返回 (文本内容, 相似度分数) 的列表 pass # 4. 生成示例伪代码 def generate_answer(question: str, contexts: List[str]): prompt f请基于以下资料回答问题。 资料 {chr(10).join(contexts)} 问题{question} 要求如果资料中没有对应答案请直接说明资料中未找到不要编造。 # 调用大模型 API 或本地模型 pass这里要强调一点代码只是流程骨架实际项目需要根据你选择的框架改写成可用代码。但核心逻辑是一致的——数据从文档变成文本片段再变成向量检索时把问题变成向量去匹配最后把命中的文本合并成上下文喂给模型。3.2 文档加载和文本切分的细节文档加载这一步最容易忽略的是格式差异。PDF 文件看起来是文本实际上很多是图片扫描件直接读取会得到一堆乱码或空白Word 文件需要额外解析库Markdown 文件相对简单但要考虑是否保留代码块和表格结构。所以上线前一定要确认你的资料是什么格式再选择合适的解析方案。如果文档是纯文本或 Markdown优先用最简单的读取方式只有遇到 PDF 和复杂 Office 文件才需要引入专门的解析工具。文本切分决定了检索粒度的上限。切分太粗一个 Chunk 包含太多无关信息检索精度会下降切分太细一个完整知识点被打散到多个 Chunk检索时容易漏掉关键内容。常见的做法是按固定字符数切分同时设置一个 overlap 重叠区域避免关键句恰好被切断。也可以按段落、标题、换行符等结构切分先用结构切分再对超长段落做长度切分。这里有一个非常实用的调试技巧。切分完之后不要直接进下一步先打印前几个 Chunk 看看内容是否完整。如果你发现某段文字从中间断开说明切分策略需要调整如果某个 Chunk 里混杂了多个不同主题说明切分粒度可能太粗。3.3 Embedding 和向量存储Embedding 的作用是把一段文字映射成数值向量。这个向量的特点是语义相近的文本在向量空间里的距离也更近。当你把切分好的 Chunk 全部向量化并存入向量库之后用户的查询问题也会被转成向量然后通过余弦相似度或点积找到最接近的若干个 Chunk。选择 Embedding 模型时需要注意三个点。第一是维度不同模型的输出维度不同从几百维到几千维都有维度越高不代表效果越好还受计算资源限制。第二是语言能力如果你的资料主要是中文优先选择中文语料训练效果好的模型或者使用支持多语言的模型。第三是部署方式本地模型延迟低但质量可能弱于大厂 APIAPI 模型质量高但需要网络请求和费用。第一次做项目时先用默认模型跑通链路再对比不同模型对检索效果的影响。向量存储方面小数据量用 FAISS 或 Chroma 就够了。FAISS 是一个向量索引库适合在单机内存中检索几十万条向量Chroma 更像是轻量级向量数据库自带持久化功能。如果要处理千万级数据、需要分布式部署、高并发查询就要上 Milvus、Qdrant、Elasticsearch 这类更完整的系统。学习阶段不要在这上面花太多时间先用本地文件型的向量库把业务逻辑跑通。3.4 检索和 TopK 的设置检索阶段最常见的参数是 top_k也就是返回多少个相关文本片段。top_k 太小可能漏掉关键资料top_k 太大大量无关片段会混入上下文反而干扰模型生成。一般建议先设 4 到 6跑起来之后看具体效果再调整。如果你发现模型经常因为资料不足而回答不出来可以考虑把 top_k 调大一点或者在 Prompt 里加入“如果资料不足请说明缺少哪方面信息”的约束。除了 top_k还要看相似度分数。很多向量库会返回一个 distance 或 score分数本身不是重点重点是判断“这次检索的置信度到底怎么样”。如果你发现返回的 Chunk 分数普遍偏低说明文档库里可能根本没有相关内容。这个信号非常重要它能帮你区分“模型答错了”和“资料库缺资料”。3.5 生成环节的 Prompt 组织生成环节的 Prompt 直接决定答案的格式和边界。一个推荐的 Prompt 结构包含三部分角色与任务说明、参考资料、用户问题、输出约束。角色说明告诉模型“你是一个基于资料回答问题的助手”参考资料区域把检索到的 Chunk 拼接进去输出约束里写明“只能基于资料回答资料中没有就明确说明”。这里要注意上下文长度的限制。当 top_k 偏大、每个 Chunk 又很长时拼出来的 Prompt 可能超过模型的上下文窗口。解决方式有两种一是限制每个 Chunk 的长度二是在拼接前做一次截断。但截断要小心不能把关键信息切掉。更稳妥的办法是先控制 chunk_size、overlap 和 top_k 三者之间的总量关系。比如 chunk_size 500、top_k 4总文本量大约是 2000 字左右大多数模型可以接受如果 chunk_size 1000、top_k 8总量可能到 8000 字就需要确认模型上下文够不够。3.6 最小闭环先看结果正不正确再优化质量跑通最小闭环之后第一件事不是改参数而是拿三五个“你确定知道答案”的问题去测试。比如你上传了一篇关于某产品安装步骤的文档就提问“这个产品安装前需要准备什么”。如果答案能从文档里找到对应描述说明整条链路是通的。如果答案胡说八道就要按链路逐级排查先看检索是否命中了正确段落再看 Prompt 是否把上下文传对了最后看模型是否理解了输出约束。这一步不建议跳过。很多人在 Demo 阶段直接拿随机问题去测效果不好就开始调参数结果调了半天发现是文档没加载进去白白浪费几小时。4. 深入 RAG 工作原理检索质量决定答案天花板4.1 切分策略从“能跑”到“跑得好”的分水岭切分阶段做得好不好直接影响检索阶段能不能命中答案。固定长度切分的问题是它不考虑语义边界。比如一段技术文档讲“如何配置数据库连接池”切分点可能正好落在“连接”和“池”之间导致 Chunk 语义破碎。实际使用中我一般会先尝试“递归字符切分”也就是按层级分隔符逐步切分优先按段落切段落太长再按句子边界切句子还是太长再按字符数切。这样既能保留结构又能控制长度。另外要特别注意标题层级信息。很多文档的关键信息分布在标题和正文之间。如果你在切分时丢弃了标题Chunk 就缺少上下文。比如一段文字写“要求内存不低于 16GB”如果没有前缀“部署环境硬件要求”模型很可能不知道这段文字在说什么。所以切分时最好把所在章节的标题拼到 Chunk 前面或者在文档解析阶段保留元数据检索时把这些元数据一并返回。对于企业文档还需要考虑表格和列表的解析。纯文本切分无法理解表格结构一个表格被强行切分后单元格内容会脱离表头检索结果自然不准。这种情况下要么使用保留表格结构的解析工具要么把表格转成文字描述后再切分例如把“模型名称Qwen参数量7B”转成一行文字。4.2 Embedding 模型选型与更新时机Embedding 模型是 RAG 系统里最容易被低估的组件。很多人做完项目之后觉得效果不好第一反应是换大语言模型其实更值得先检查的是 Embedding 模型。如果你的文档术语很专业比如医疗、法律、金融、工业制造通用 Embedding 模型可能无法很好理解这些领域词汇的语义关系。这时可以尝试领域微调的 Embedding 模型或者使用多个模型对比测试。按我的经验判断 Embedding 模型是否合适的成本并不高。你可以准备 20 到 50 个“问题-期望命中文档”的测试对然后计算检索命中率。命中率高说明 Embedding 能理解你的资料内容命中率低再考虑换模型或调整切分策略。这里有一个原则检索阶段解决不了的问题生成阶段再强的模型也兜不住。如果检索回来的内容根本不是正确答案大模型再厉害也只能基于错误上下文胡说。4.3 相似度计算向量距离不等于绝对答案向量存储和检索通常使用余弦相似度或欧氏距离。余弦相似度更常用因为它对向量长度不敏感更适合文本语义比较。但要注意向量检索的本质是“求近似”不是“精确匹配”。医院里提到“心脏支架”和汽车配件里提到“支架”表面文字不完全一样语义完全不同反之“高血压”和“血压偏高”文字不重叠但语义接近。所以向量检索的差距就体现在它能否识别这种语义相关性。如果你发现检索结果里混入大量语义无关内容可以考虑增加“关键字过滤”作为前置条件。也就是先用经典的关键词检索或全文检索把候选集过滤一遍再用向量检索做语义排序。这种方案在不少 RAG 实战项目里效果很好尤其是专业术语密集、文档量大的场景。4.4 重排序Rerank为什么越来越重要Rerank 是一种对检索结果做二次排序的技术。向量检索的第一阶段目标是“召回”也就是找到一批可能相关的候选文本Rerank 的第二阶段目标是“精排”也就是在这批候选文本里选出真正最相关的几条。为什么需要两步走因为向量检索速度快但在某些场景下精度不够Rerank 模型往往更精细但计算成本高。如果直接用 Rerank 模型对所有文档排序性能压力会很大。所以合理的架构是“先用向量检索粗召回再用 Rerank 精排”。加了 Rerank 之后top_k 可以稍微调大一点。比如向量检索先召回 10 条Rerank 之后再取前 3 条。这样即使向量召回阶段漏掉了一两条相关文本只要它还在候选集里Rerank 还有机会把它捞回来。很多 RAG 实战调优案例里Rerank 带来的效果提升比更换大语言模型更明显。4.5 多路召回不只是向量检索这一条路多路召回指的是同时用多种方式查询文档库然后把结果合并去重。常见的召回方式包括向量检索、关键字检索BM25、SQL 查询、知识图谱查询等。向量检索擅长语义关键字检索擅长精确匹配。在专业文档场景很多关键信息是特定编号、设备型号、人名、地名向量检索不一定能精确命中但关键字搜索可以。所以把两路结果合并往往能提高召回率。实现多路召回后需要处理结果合并和排序问题。通常做法是各路召回结果各自给出相关性分数再做归一化或加权融合最后按融合分数排序。这个环节如果做得不好多路召回反而会引入更多噪声。建议先从“向量检索 BM25”两路开始用测试集观察合并后的效果再决定是否调整权重。4.6 Query 改写别让原始问题直接进检索用户提问经常是口语化、省略主语、指代不明。比如用户先问“Qwen 支持哪些参数”再问“量化后效果怎么样”第二个问题里的“量化”和“效果”都需要结合对话上下文才能理解。如果直接把第二个问题拿去检索很可能找不到准确答案。这时需要对 Query 做改写。简单的方式是把多轮对话整理成一个独立问题再去检索更复杂的做法是让大模型生成多个相关关键词再做多路检索。Query 改写还有一个作用把问题拆成多个子问题。比如用户问“如何部署 RAG 并评估效果”这个问题包含“部署”和“评估”两个主题。如果只做一次检索返回的 Chunk 可能只覆盖其中一个主题。把问题拆分后分别检索再把结果合并给生成模型答案会更完整。5. 从 Demo 走向企业级实战工程问题远比模型问题多5.1 批量处理文档命名、去重、失败重试、断点续跑Demo 阶段处理一两篇文档很轻松但到了企业级场景文档数量可能是几千甚至几万篇。这时最先暴露的问题不是检索效果而是批量处理的健壮性。文档加载可能失败网络请求可能超时向量库写入可能报错。如果整个流程没有失败重试机制一个小错误就会中断整个导入任务。批量处理的第一步是规范文档命名。建议在文档元数据中记录来源、版本、上传时间和唯一 ID。这样做的原因是检索结果返回时你需要告诉用户“这条答案来自哪份文档的哪个段落”。如果没有可追溯的文档 ID生成答案后用户无法核对知识库的可信度就大大降低了。第二步是设计失败重试和断点续跑机制。对每一条文档记录状态待处理、处理中、成功、失败。失败的任务可以自动重试 2 到 3 次每次间隔几秒。如果重试后仍然失败就把文档标记为失败并跳过不要中断整个队列。处理过程中定期保存向量库的索引文件和切分中间结果这样程序意外中断后可以从最近的断点继续而不是从头再来。第三步是去重。同一份文档可能被重复上传或者经过多次修改生成多个版本。如果不做去重向量库中会出现大量重复 Chunk导致检索结果重复生成答案时上下文冗余。简单做法是用文件哈希判断是否处理过同一文件更完整做法是维护文档版本表用文档 ID 和更新时间管理新旧版本替换。5.2 日志和监控RAG 项目必须记录哪些信息RAG 项目在调试阶段最让人头疼的问题就是“不知道哪一步出了问题”。所以日志设计在项目一开始就要考虑。建议每个请求都分配一个请求 ID日志里记录以下信息用户原始问题。Query 改写后的问题。各路召回返回的文本内容和相似度分数。Rerank 后的结果集合。最终拼装给 LLM 的 Prompt。LLM 返回的完整答案。各阶段耗时和 token 消耗。有了这些日志排查问题时就比较顺畅。比如用户说“我换了个说法答案就不对了”你可以对比两次请求的检索结果很快发现是不是 Query 改写导致检索范围发生变化。又比如用户抱怨回答太长你可以直接看 Prompt 里的 top_k 是不是太大把太多文本塞给了模型。监控方面至少要关注四个指标单次请求总耗时、检索阶段耗时、生成阶段耗时、token 消耗量。当这些指标出现明显波动时说明系统可能有性能瓶颈或成本异常。比如检索阶段耗时突然变大可能是向量库索引需要重建也可能是文档数量增长导致查询变慢。5.3 性能与成本控制RAG 项目的成本主要由三部分构成向量化成本、存储成本、推理成本。文档量大时向量化成本不能忽略尤其是调用外部 API 做 Embedding每转一篇文档都会产生费用。推理成本是大模型代币费用或本地 GPU 资源消耗它和用户请求量、top_k、输出长度直接相关。控制成本可以从几个角度入手。第一是缓存对相同或相似的问题做缓存短时间内不重复触发检索和生成。第二是控制 Prompt 长度不要让多余的上下文进入模型。第三是选择合适的模型策略简单问题用小模型复杂问题用大模型或者用本地模型处理高频简单请求用高精度模型处理低频复杂请求。这种方式叫模型路由在 RAG 项目里非常实用。性能优化也要分阶段。如果你的系统主要是内部使用并发量不高先把逻辑拆清晰加上缓存就足够了。如果要做成对外服务就要考虑异步任务、队列、并发控制和限流。文档上传和向量化属于耗时任务不应该阻塞用户请求用户可以先把文档上传系统在后台处理完后再通知索引完成。查询接口和生成接口也要拆分避免长回答请求拖垮整个服务。5.4 接口化设计与异步任务企业级 RAG 项目一般要提供给多个系统调用所以接口设计很重要。建议把功能拆成几个独立接口文档上传接口接收文件创建文档任务。文档处理状态查询接口查看任务是否完成。问答接口接收问题返回答案和引用来源。文档删除接口删除对应的向量和元数据。问答接口是最核心的。它需要支持同步返回和异步返回两种模式。短问题可以用同步接口等待完整答案生成后一次性返回长文档生成可能要几十秒更适合用异步任务先返回一个任务 ID客户端轮询任务状态拿到最终结果。异步化还有一个好处方便做失败重试和用户反馈收集。接口层的鉴权、限流、幂等设计也需要提前考虑。对外服务时要给每次调用分配 API Key限制调用频率。上传接口要支持同样的文档重复上传时返回已有结果避免重复向量化浪费资源。回答接口要考虑用户反馈比如“这个答案是否有帮助”收集起来用于后续评估和调优。5.5 知识库更新新增、修改、删除不能只靠重新导入知识库内容的生命周期管理是一个容易被忽略的关键点。实际业务中文档会新增、会修改、会废弃。很多初版 RAG 系统只有“导入”功能没有“更新”和“删除”功能。结果就是旧版本文档的向量一直留在库里用户提问时检索到过期信息生成答案与实际政策矛盾。正确的做法是每一篇文档都有唯一标识文档内容变更后先根据文档 ID 删除旧的向量数据再重新切分、向量化、写入新数据。删除接口要同时清理向量库和元数据表。这个流程在接口层面要原子化避免删除成功但写入失败导致文档丢失也避免写入成功但删除失败导致新旧版本同时存在。如果你的知识库文档会经常更新我强烈建议先设计“文档版本号”字段。每次更新文档时版本号递增问答结果里记录版本号用户就能确认自己看到的是不是最新内容。6. RAG 项目效果评估不是“看起来不错”而是“可量化”6.1 为什么必须做 RAG 测评很多 RAG 项目在演示时效果很好一投入真实业务就变差。原因通常是演示用例是精心挑选的真实问题分布复杂。为了判断系统是否真的可用必须建立一套可复用的评估机制。RAG 测评不是可有可无的加分项而是决定项目能不能上线的关键环节。评估的意义有两点。第一它能告诉你当前系统的基线水平。第二它能让你在调整参数时知道调整是变好还是变坏。没有评估集你调 chunk_size 或 top_k 之后只能凭感觉判断“好像效果变了”这是不科学的。有了评估集你可以量化对比不同方案在同一批问题上的表现。6.2 评估指标检索命中率、答案准确率、忠实度RAG 系统涉及检索和生成两个阶段评估也应该分阶段进行。指标衡量内容判断方式检索命中率检索结果是否包含正确答案所在文档检查 top_k 返回的 Chunk 是否包含标注为“正确答案”的文本片段答案准确率生成答案是否与人工标注答案一致人工或 LLM 打分比对语义一致性忠实度生成答案是否完全来自检索资料检查答案中是否有资料之外的编造内容忠实度这个指标特别容易被忽略。一个 RAG 系统可能在 80% 的问题上回答流畅但其中有相当一部分答案是模型根据自己的知识生成的而不是来自资料库。这在企业场景非常危险。所以评估时不能只看答案对不对还要看答案能否在资料库里找到对应依据。有一种简单但实用的做法是“引用溯源”要求模型在生成答案时每个关键结论都标注来源是第几段资料。然后人工抽查这些引用是否正确。如果模型引用了不存在的段落说明 Prompt 约束不够或者上下文组织有问题。6.3 评估集的构建方法评估集不需要一开始做得很大但要覆盖典型场景。建议准备 100 到 200 条“问题-期望答案-来源文档”三元组。对于每个问题不仅标注标准答案还要标注答案来源于哪篇文档的哪个段落。这样检索阶段和生成阶段可以分别打分。评估问题可以分成几种类型事实性问题答案明确例如“这个产品的内存要求是多少”。归纳性问题答案需要综合多个段落例如“部署这个系统需要考虑哪些硬件条件”。跨文档问题答案分散在多篇文档里例如“比较 A 方案和 B 方案的优缺点”。边界问题资料库中没有答案例如“这个系统支持 8 卡分布式吗”如果文档里没写系统应该回答不知道。构建评估集时建议让实际业务人员参与。因为他们最了解用户会问什么问题。只有技术团队自己想出来的问题往往和真实使用场景有偏差。6.4 迭代优化方法一次只改一个变量评估集建立之后优化就有了基准。但要注意一次只改一个变量。比如今天调整 chunk_size明天调整 top_k后天换 Embedding 模型。每次调整后跑同一套评估集记录所有指标。这样才能确定某个参数对效果的影响。根据我的经验优化的常见顺序是先优化切分和检索阶段确保检索命中率足够高然后加 Rerank 看能不能提升精确度再调整 Prompt 约束改善答案格式和忠实度最后才考虑换更强大的大语言模型。如果检索阶段命中率已经很低换再强的 LLM 也补不会来。所以要把更多时间花在“如何让正确答案被检索出来”这个目标上而不是沉迷于对比模型输出。7. 常见问题排查链路从现象到根因7.1 检索结果为空或完全不相关如果问答系统经常回答“资料中未找到”或者回答明显与问题无关先排查检索阶段。按以下顺序检查确认文档是否成功切分切分结果是否非空。确认 Embedding 模型是否成功运行向量维度是否一致。确认向量库中是否有数据查询是否有返回。用几个已知问题手动检索看返回的 Chunk 是否包含正确答案。如果手动检索没有命中检查文档本身是否包含答案如果包含调整切分策略或换 Embedding 模型。这个阶段最常出现的问题是向量库是空的、Embedding 模型加载失败、文档切分后内容丢失。日志里通常能看到报错线索但很多人习惯直接看答案质量跳过了这些基础检查。7.2 答案不准确但不是完全错误回答“沾边但不够准确”通常是检索到了相关段落但返回的 Chunk 不够精准或者 top_k 里混入太多无关内容。建议按以下方向调整调大 Rerank 权重让最相关的 Chunk 排在前面。调小 top_k减少干扰信息。检查 chunk_size 是否太大段落中混入了多个主题。优化 Prompt强调“只能根据第一条资料回答”“如果资料有冲突请说明”。如果调整后效果没有明显改善需要回到评估集上确认问题是什么。是检索阶段没召回相关文本还是生成阶段没有用好召回文本。这一步的判断非常关键能帮你避免盲目调参。7.3 回答速度太慢回答速度慢可能发生在多个环节。首先判断是检索慢还是生成慢。可以在日志里看每一段的耗时占比。如果检索慢可能是向量库数据量太大且没有建立合适的索引也可能是 Rerank 模型处理候选集太慢。如果生成慢可能是 Prompt 太长、模型太大、输出太长。针对不同的瓶颈做不同的优化。比如生成慢可以缩短输出长度、使用流式输出、换更小的模型检索慢可以增加索引类型、减少粗召回数量、Rerank 只处理前 20 条候选。7.4 运行过程中内存或显存不足内存和显存不足通常发生在 Embedding 模型或大语言模型加载阶段。低配置环境里即使能加载模型也可能在批量处理时因为累积数据导致内存溢出。解决办法是减小 batch_size逐条处理文档或者使用量化模型降低显存占用。如果本地机器确实资源有限可以考虑把大语言模型放到远程 API 调用本地只保留轻量级 Embedding 模型。还有一个容易被忽略的问题多个模型同时加载进内存。如果你的项目里同时加载了 Embedding 模型、Rerank 模型、大语言模型内存占用会叠加。建议评估一下每个模型是否真正需要常驻内存。Rerank 模型可以在检索阶段临时加载并释放大语言模型也可以和 Embedding 模型分进程部署避免互相影响。7.5 回答的内容格式不对如果模型返回的答案不是预期格式比如应该输出 JSON 却输出了纯文本应该输出表格却输出了列表问题基本在 Prompt 没有约束格式。建议在 Prompt 中给出明确的格式示例并加上“严格按以下格式输出”的说明。如果你需要的是结构化输出有些模型框架支持 JSON Mode 或结构化生成可以直接通过参数开启效果比在 Prompt 里提示更稳定。生成完成后还需要做一层校验格式不对就重新请求一次避免脏数据流向下游系统。7.6 多轮对话场景效果变差RAG 在多轮对话场景中经常出现上下文混乱。用户问完 A 问题后又问“那第二种方法呢”系统如果没有上下文记忆根本不知道“那”指代什么。排查顺序是先看对话历史是否传给模型再看 Query 改写是否把指代消解成完整问题最后看系统是否把无关的历史对话混入了检索。多轮 RAG 的关键在于“只检索当前完整问题历史信息只用于理解和改写”不要把多轮历史全部塞进检索 Query。实践经验是对话历史里只保留与当前问题最相关的 1 到 2 轮不要盲目拼接大量历史。历史过长会导致检索 Query 混乱也增加模型输入的 token 消耗。最后想说的话RAG 项目看起来链路简单但真正做好是一个持续迭代的过程。最初跑通 Demo 通常只需要半天但要把准确率、稳定性和成本都做到可状态需要投入更多时间在切分、检索、Rerank、评估这些环节上。我个人更建议先把单任务跑稳再考虑批量和接口。不要一上来就追求复杂架构先让最小闭环在真实文档上验证通过再逐步加入多路召回、Rerank、异步任务和企业级权限。真正落地时最需要盯住的不是模型有多强而是输入格式、日志、失败重试和知识库更新这些基础工程问题。很多坑不是工具能力不够而是前置环境和数据没有处理干净。把这些问题一件件理顺你会发现 RAG 的回报率比想象中高很多。