做 RAG 应用的朋友应该都有过这种体验知识库明明塞了几百份文档问个具体问题答案却要么答非所问要么一本正经地编出文档里根本没有的内容。我前几个月接手了一个内部知识库问答系统线上反馈最多的就是“搜不到”“答不对”“瞎编”当时第一反应是换更大的模型后来发现模型换了好几个问题原封不动。折腾一圈才意识到RAG 链路里任何一个环节掉链子最后都会以“答案不准”的形式暴露出来而这些问题绝大多数是可以定位、可以量化、可以修掉的。这篇文章我不会讲太多理论框架重点是我在优化 RAG 应用提升问答准确度这件事上实打实踩过的坑、验证过的方案以及最后沉淀下来的一套调优流程。内容面向正在做 RAG 落地的开发者尤其适合那些知识库问答已经跑通、但准确度一直提不上去的团队。文章会按“定位病灶—检索优化—重排精排—生成约束—评测迭代”这条线展开每一步都会给出可复现的配置和参数参考希望能帮你省掉几周自己摸索的时间。1. 先搞清楚 RAG 问答不准的病灶在哪1.1 RAG 链路的问题不会只出一环RAG 的标准链路其实就四段问句进来之后先做召回从向量库里捞一批相关片段然后做重排把相关度最高的内容排到前面接着把选中的片段拼进 Prompt喂给大模型最后大模型基于这些素材生成答案。看起来不复杂但每一段都有自己的坑而且这四个环节的问题会互相掩盖。比如检索结果里确实有正确答案但重排把它排到了第 8 位生成时上下文超长被截断了答案就变成了一堆废话——这时候你很难一眼判断到底是谁的锅。我自己的经验是遇到回答不准第一件事不是改 Prompt而是先把“检索质量”和“生成质量”拆开评估。做法很简单单独把检索结果打出来人眼判断前 10 条里有没有包含能直接回答问题的片段。如果前 5 条就有说明检索基本合格问题大概率在生成侧如果前 10 条都没有那你 Prompt 写得再好也白搭因为模型手上根本没有正确答案它只能靠脑补。这个二分法能帮你快速缩小排查范围避免在错误的环节上反复试。1.2 用一个小案例定位问题归属我挑一个实际遇到过的例子来说明。用户问的是“公司年假制度里入职满一年可以休几天”知识库里其实有一份详细的《考勤与休假管理制度》PDF里面明确写了“连续工作满 12 个月后可享受每年 5 个工作日的带薪年假”。但线上系统的回答却是“根据公司制度年假天数由部门主管根据绩效确定”听起来像那么回事实际上完全是错的。排查过程分两步。先把用户问题拿去跑检索看召回片段——结果发现召回的前 10 条全是从另一份《员工手册》里来的里面确实提到了“年假与绩效挂钩”的模糊表述而真正写清楚天数的制度文档因为分块太粗被一个长达 3000 字的超大块裹住了向量相似度被稀释压根没被召回来。这属于典型的检索侧问题。事后我把那个文档按条款粒度重新切块并做了关键词权重补偿同样的问题再问一次正确答案排到了第 2 条。这个案例说明两件事一是分块策略对召回的影响比很多人想象的大得多二是“答案不对”不一定是模型的错先看检索结果再下结论。2. 检索侧调优准确度的第一道闸门2.1 Embedding 模型选型要考虑语料场景检索侧的第一件事是选对 Embedding 模型。我见过不少团队直接用 OpenAI 的 text-embedding-ada-002 之类的 API 模型跑中文知识库结果检索效果稀烂追问半天才发现是 embedding 对中文长文本的语义理解不足。选型时先看你的语料是什么语言、什么领域。如果以中文为主我建议优先试开源的 bge-large-zh-v1.5、bge-m3 这类中文优化过的模型如果你的语料偏专业垂直领域比如法律、医疗、工业手册还要额外做领域微调或者用领域数据生成的 query-document 对做相似度校准。另一个被忽略的点是“查询侧”和“文档侧”的表示差异。用户问题通常很短文档片段通常很长两者直接做余弦相似度天然不对称。很多 embedding 模型支持为 query 和 passage 分别设置指令前缀在构造向量时加不同的 prompt 前缀比如给 query 加“为检索生成此查询的表示”给文档加“为检索生成此文档的表示”bge 系列就用这种方式显著拉高了对短句子的检索效果。我测试过同一个知识库不开这个前缀的召回命中率大概在 68%开了之后直接到 82%改动成本几乎为零强烈建议你查一下自己用的模型是否支持。2.2 分块策略决定召回粒度的上限分块是整个 RAG 里最“手艺人”的环节。拿前面说的年假案例把整份制度文档按固定字数 500 字切块最后得到的块可能是“上半段讲休假申请流程、下半段讲年假折算”语义被切得稀碎向量表征一个都不像。固定窗口分块适合通用网页文本但不适合条款式、结构化强的文档。我现在做知识库时默认按结构分块先把文档转成 Markdown用标题层级#、## 等划出最小语义单元再把属于同一个二级标题下的小节合并成块块长控制在 300 到 800 字之间。这样切出来的每个块都有相对完整的论点向量能表达清楚被召回后也能直接当成上下文喂给模型。分块完必须做的验证是拿一批真实用户问题去打检索看“黄金片段”人眼确认包含答案的那段能不能出现在前 5 条。如果经常出现在第 10 名开外优先考虑是不是这个块的边界切错了而不是急着换 embedding 模型。另外块与块之间的重叠也有讲究。我用的是 20% 到 30% 的滑动重叠主要是防止某个关键句正好落在切分边界上导致两边都不像。但重叠越大向量库里的重复内容越多检索噪音也越大这个需要实测平衡。我的建议是先不重叠跑一版看召回 effect 再决定要不要加。2.3 元数据过滤与混合检索补齐短板纯向量检索有一个天然短板它只看语义相似度不看“这个片段来自什么文档、什么日期、什么部门”。比如你问“最新的报销标准是多少”如果知识库里有 2022 版和 2024 版两份规定向量检索很可能把两个版本的片段都召回模型就傻眼了。解决办法是给每个块打上元数据标签版本号、发布日期、文档类型、适用范围在检索时用过滤器强制只召回最新版本。这一步的效果非常明显我见过只加一个日期过滤问答准确率就能提升十几个百分点。另一个补短板的手段是混合检索把向量召回和 BM25 关键词召回的结果做一个融合。向量擅长语义匹配BM25 擅长精确关键词命中。很多垂直场景里用户问的是专有名词或编号比如“AP-320 型压力表的检定周期”BM25 能精准锁定包含 AP-320 的片段而向量检索可能因为整体语义接近拉进来一堆不相关型号。我线上用的方案是向量召回前 50 条、BM25 召回前 30 条用 RRFReciprocal Rank Fusion做融合公式是每个文档的分数等于各列表中排名倒数的累加再兼顾两者权重。融合之后 TopK 的准确率比单用向量检索大概高 10% 到 15%。2.4 查询改写别忽略用户真实意图还有一个经常被遗忘的检索侧细节是查询改写。用户输入的原始问题往往很短比如“年假到底怎么算”“退款政策是什么”这种短句直接做向量检索表征出来的是一个泛化的“年假”“退款”概念而不是具体的问题。更靠谱的做法是在进入检索前加一个轻量级改写步骤让大模型把用户问题扩展成 2 到 3 个更具体的检索 query每个 query 分别去检索最后合并结果去重。我用过的改写策略是模板式指令效果稳定。Prompt 大致是根据用户问题生成最多 3 个用于知识库检索的短查询要求包含专有名词和关键限制条件。比如“年假到底怎么算”会生成“入职满一年年假天数”“年假计算规则”“未休年假补偿标准”三个子查询命中率明显比原始短句高。注意改写这一步不要让模型自由发挥太多否则生成一堆发散问法检索噪音反而变大限定 3 个以内比较稳妥。3. 召回之后的精排别让 TopK 拖后腿3.1 为什么粗召回后必须加一层精排向量检索或者混合检索召回的 TopK 结果是按相似度排的但这个相似度和“能不能直接回答用户问题”之间并不等价。我遇到过一个很典型的情况用户问“工伤认定的申请时限是多久”向量召回把科普定义、工伤预防措施、申请材料清单都排到了前面而真正写着“单位应在事故伤害发生之日起 30 日内提出申请”的那一条排在了第 6 位。用 Top5 直接喂给模型答案就漏了。这就是重排Rerank存在的意义。召回阶段目标是“宁可多捞不能漏网”所以 TopK 可以拉高到 50 甚至 100重排阶段目标是把“真正能回答问题”的片段排到最前面同时把不相关的挤出去。我用的是一个独立的 Rerank 模型它会拿用户问题和每个候选片段做深度交叉编码输出的分数比向量相似度更贴近“问答相关度”。实际效果很直观同一个问题不加 Rerank 时答案命中率在七成左右加了之后能稳定到八成五以上。3.2 常见 rerank 模型与部署策略目前常用的方案有两类第一类是开源模型比如 bge-reranker-base、bge-reranker-v2-m3中文效果不错单个片段推理耗时控制在几十毫秒级别对生产可接受第二类是一些商业 API 提供的 Rerank 服务效果通常更好但按量计费、数据要过一遍外部接口隐私敏感场景要谨慎。我自己的偏好是先用开源模型跑通流程确认链路没问题再考虑是否换更强的模型。Rerank 模型一般比 Embedding 模型大显存占用也高部署时建议单独起一个服务别和向量检索服务混在一台机器上抢资源。重排的时候要控制候选数量。候选太多会增加耗时候选太少可能一开始就漏了答案。我的参数是召回阶段取前 80 条重排后取前 6 条作为输入模型的上下文片段。这个 80/6 的组合是在“召回覆盖率”和“上下文洁净度”之间反复调出来的。如果你发现检索质量本身很差前 80 条全是无关内容那加大候选数也没用反而应该回到上游修分块和 embedding。3.3 上下文长度的资源分配策略重排之后真正喂给模型的是前 N 条片段但这里还有个隐藏问题上下文窗口是有限的。模型窗口很大不代表你把所有重排结果都塞进去效果就好——不相关的内容塞多了模型会被带偏相关的内容被挤到窗口边缘甚至截掉答案照样不准。我建议给上下文设定一个硬上限比如 2500 到 3000 个 token 的片段预算重排完成后按分数从高到低往预算里塞塞不下的宁可直接丢弃。具体操作时可以在 Prompt 里把每个片段前面加一个编号让模型输出答案时尽量引用编号方便排查到底哪条片段起了作用。我还会做一个简单的位置优先级处理重排分数最高的片段不一定放在 Prompt 最容易引起模型关注的位置。大模型对上下文开头和结尾的信息更敏感中间部分容易被忽略。所以我把高分段片段放开头和结尾中分段放中间低分段直接不放。这个操作调完之后模型漏用关键片段的情况减少了不少而且是零成本改动。4. 生成侧约束让模型只做“事实的搬运工”4.1 用 Prompt 把模型的默认行为改过来检索和重排做得再好生成侧不配合准确度也上不去。大模型的默认行为是“有知识就先答管你是不是知识库里的”。所以 Prompt 里最重要的不是格式化输出而是强约束它的信息边界。我的做法是在 system prompt 里显示写明只允许根据上下文内容回答问题不得使用训练数据中的知识如果上下文无法回答请直接回答“知识库中未找到相关信息”不能对上下文内容做任何推理引申或补充说明。这里有一个反直觉的细节如果只写“只能根据上下文回答”模型偶尔还是会嘴硬。把“请基于以下文档片段回答这些片段是唯一的信息来源。如果你不确定就说不知道。”这句明确加上效果会好很多。用“不知道”兜底尽管牺牲了一部分“回答率”但有实际价值——它把模型的幻觉从“一本正经地编”变成了“诚实说不知道”这在企业知识库场景里是正确的价值取向宁可答不上来也不能用错误信息误导用户。4.2 给上下文加上“位置身份卡”和引用要求为了让模型更明确哪些内容是给它用的素材我会在拼接上下文时给每个片段加上一个身份标记例如“片段来源文档标题章节名”再要求回答时引用来源编号。这不仅是流程规范更是一种隐性约束一旦要求模型“说明信息来自哪一段”模型编造的成本就变高了它会倾向于从已有片段中找答案而不是凭空发挥。引用做法是在每个片段前标注[1]、[2]这样的编号Prompt 里明确写“回答时请以 [编号] 的形式引用你使用的片段”。我对比过开不开引用约束的差异开了之后模型输出的内容与上下文的忠实度更高原因是它被迫逐条核对信息出处而不是笼统地“概括一下大意”。当然这会多消耗一点 token但换来的是可溯源的答案值得。4.3 兜底机制设置置信度阈值和主动拒答即使 Prompt 写得再严格模型依然有可能“自信地答错”。我的做法是在生成后加一个轻量的分类判断把问题和生成的答案、检索到的上下文一起再次交给模型或一个较小的分类模型判断“答案是否完全基于上下文”。如果分类器认为答案和上下文不一致就拦截掉这条回答直接返回“知识库中没有足够的信息”。这个兜底机制类似一道质检虽然增加了一次模型调用但对准确度要求高的场景非常值得。置信度阈值的设置取决于业务容忍度。内部知识库问答我会把阈值调得偏严宁可拒答率更高面向客户的服务则要平衡拒答太多用户体验差这时候可以退而求其次让模型给出“可能的答案”并明确标注置信度低的提示而不是直接拒绝。灵活调整拦截策略比一个固定规则跑到底更贴合实际。5. 评测集与回归把优化从玄学变成工程5.1 没有评测集一切调优都是手感做 RAG 优化最容易犯的一个错误是随手拿几个问题试一下感觉“效果好像变好了”然后上线。这种主观判断完全不可靠因为你根本不知道是参数波动还是真实改进。我吃过这个亏某次改完分块用手边 5 个问题测4 个变好了自信满满上线结果线上准确率反而掉了后来一查是那 5 个问题恰好都是新分块方式擅长的类型。解决这个问题只有一个笨办法建评测集。一开始不需要很大50 到 100 条就够了。每条包含三个部分真实用户问题、标准答案或者至少是“答案要点”、对应文档和分块路径。评测集来源要从线上日志里扒真实问题不要自己脑补问题因为自己脑补的问题往往太理想化。扒完之后分类标注比如“制度类”“价格类”“操作类”这样每次优化后还能看不同类别的涨跌情况定位更精细。5.2 四个核心指标怎么盯评测不能只看“回答对不对”这一个二值指标那样太粗糙。我常用四个指标第一个是检索命中率考察标准答案所在的片段有没有出现在召回前 10 条里。这个指标关注的是上游有没有“漏”。第二个是答案忠实度判断生成答案的内容是否完全基于检索到的上下文有没有引入外部知识或凭空发挥。这个可以用人工评也可以用模型辅助打分。第三个是答案有用性考察答案是否真的回答到了用户问题很多答案忠实但答偏了有用性就会差。第四个是端到端准确率即最终答案是否与标准答案核心要点一致。每个优化动作做完我至少跑一遍这四个指标记录变化。比如改分块策略先看检索命中率有没有提升如果命中率提升了但端到端准确率没变那问题就出在生成侧再回去调 Prompt。这套流程跑熟了之后优化过程从“我觉得”变成了“数据显示”决策速度快了很多。5.3 回归测试与线上灰度评测集建好之后最怕的是“修好一个地方弄坏另一个地方”。比如为了提升短问题召回给所有 query 加了改写结果长问题的改写反而搅乱了原有语义这类回归问题不跑测试根本发现不了。所以我每次改完配置都会跑一遍完整评测集把四项指标和上一次的结果做对比只要有任一项下降超过 5%这个改动就要重新评估不能直接上线。线上环境再补一个灰度策略先切 10% 流量跑两天对比线上日志里的用户反馈数据和之前基线的差异。如果线上效果验证没问题再逐步放开到 50%、100%。这套流程看着繁琐但能挡住很多“评测集过了但线上崩了”的意外。评测集毕竟是离线快照覆盖不了所有线上场景灰度是最便宜的安全带。6. 高频问题排查与实战避坑6.1 常见问题速查表我把这段时间在优化 RAG 过程中遇到的高频问题整理成了一张速查表方便你对照排查症状常见原因优先排查动作参考解法检索结果里根本没有正确答案分块太粗、embedding 和语料不匹配打印召回前 10 条人工核对黄金片段按结构重切块、换中文 embedding、加混合检索答案片片段正确但最终回答不对生成侧约束不足、上下文被无关片段污染检查 prompt 是否限制“只能基于上下文”强化 system prompt、加引用要求、收紧上下文预算回答过于笼统抓不住关键点高分段片段没被模型注意或上下文太长查看模型实际使用的片段编号调整片段位置优先级、压缩输入片段数量频繁说“不知道”召回片段不足、prompt 太严格检查召回数量和片段质量放宽召回数、优化重排策略、调整拒答阈值同一问题有时对有时错检索排序不稳定、不同问法召回差异大看日志里每次召回的片段集合增加查询改写、做结果集稳定化、减少随机性6.2 更换模型后的坑很多团队在优化 RAG 时会顺手把大模型从 7B 换成 70B或者从旧版换新版然后发现效果“莫名变差”。这通常不是模型变差了而是新模型的输出风格变了它更爱发挥、更愿意补充背景知识导致忠实度下降。遇到这种情况不要急着否定新模型先检查生成侧 prompt 是否需要跟着调整。新模型对 system prompt 的遵从度往往有差异需要微调措辞。另外换 embedding 模型之后老向量库里的向量和新 query 向量之间的匹配很容易出问题。如果无法一次性全量重建向量库至少要保证查询侧和文档侧用同一个模型和同一个版本否则新旧向量分布在不同的语义空间里检索质量必然受损。这一点很多人会漏掉我建议任何 embedding 模型升级都配一个完整的重建流程不要做增量混用。6.3 采集线上真实反馈持续迭代RAG 应用的优化没有终点线上线下用户会不断带来新的问法、新的文档、新的边界情况。我的习惯是在线上问答系统里加一个“答案是否有用”的反馈按钮同时定期从日志里抽“用户改写了问题再次提问”的 case——那意味着第一次回答大概率没满足需求。把这些 case 补充进评测集每周更新一次评测集的规模会越来越大系统对真实问题的覆盖也越做越稳。这个持续迭代的环节其实比一次性调优更重要。你把基础链路调到位之后接下来所有的提升都来自对真实问题的回应速度。评测集是你手里一个越用越准的标尺别怕它增加工作量它替你省下的排查时间远大于你标注这些问题花掉的时间。6.4 一些配置参数的参考基线最后分享一套我当前线上稳定跑着的指标供你作为起点参考但不要照抄最好按你自身语料实测调整分块采用基于标题结构的半自动切分块长 400 到 600 字重叠 15%召回阶段用向量加 BM25 混合检索从 80 条里用 rerank 挑出 6 条喂给模型上下文片段预算控制在 2500 token 以内最高分片段放在 Prompt 首尾Prompt 中强制标注来源编号并要求“不确定就回答不知道”加一道置信度质检兜底。这套基线在中文内部知识库场景下评测集的端到端准确率大约在 85% 左右距离上线可用还有优化空间但已经能扛住日常问答流量。我个人在实际操作中最大的体会是RAG 优化最怕“东一榔头西一棒子”今天调分块、明天换模型、后天改提示词每次改动都很合理但因为没有做 A/B 对比和评测回归最后根本不知道哪个动作起了作用。如果让我给你一个建议那就是先花三天时间把评测集建起来把四个指标跑通再开始调任何参数。有了这个底座后面每一步优化都是可累积的而不是原地打转。