RAG答非所问?先查向量检索,再怪模型
发布时间:2026/10/31 8:28:58 锦皓数字建站

最近排查了一个典型的RAG答非所问案例知识库里明明有答案系统给出的却是一本正经的胡说八道。团队里第一反应是模型不行有人甚至打算换大模型。我把召回结果打出来一看问题根本不在模型而在向量检索那一层——召回的top-5片段里前三条和用户问题压根不是一回事。模型拿到错误材料自然只能给出错误回答。这类问题我在多个RAG项目里都遇到过。大多数人出问题先怀疑模型但真实情况往往相反RAG的答非所问大概率是向量这一侧出了问题。这篇就按我的实际排查路径来写讲清楚为什么先查向量以及具体怎么查。1. 为什么答非所问大概率不是模型的问题RAG的完整链路是文档预处理、切片、向量化、存入向量数据库、检索召回、重排序、拼接提示词、模型生成回答。模型只是整条链路的最后一段它的输入质量完全取决于前面几步。如果召回片段里混入了大量不相关内容或者漏掉了关键片段模型再强大也没办法无中生有。拿我这次的案例来说用户的问题是如何修改企业认证信息知识库里明明有《企业认证修改操作手册》但召回返回的却是《账号注册流程》《个人认证常见问题》这类相邻文档。模型读到这些片段自然答成了注册账号需要准备XX材料。问题出在召回排序——正确的片段没有被排在前面。1.1 模型幻觉和检索错误的区别很多人把RAG回答错误统一归为模型幻觉这个分类太粗了。我习惯把RAG错误分成两类检索错误正确答案在知识库里但没被召回或者排名太靠后。模型根本看不到正确答案。生成错误检索到了正确答案但模型理解偏差或提示词设计不佳导致没用上这些材料。区分这两类有一个非常简单的办法把召回的top-k片段直接打印出来看。如果片段里根本没有答案这是检索错误如果片段里有答案但模型没用上这才是生成错误。这个动作成本极低却是排障的第一步。1.2 链路中各环节的抛锅关系RAG出问题每一环节都可能互相抛锅。我的经验是必须先做环节隔离排查顺序环节快速验证方法常见误区1文档解析直接打印解析后的文本以为是解析问题其实是切片问题2切片质量检查切片内容是否语义完整以为模型笨其实是切片断句断错了3向量化查embedding模型与文本匹配度以为检索算法差其实是向量本身没表达好4相似度检索打印top-k片段和相似度分数以为重排序不够准其实是top-k直接就是错的5重排序观察重排序后的顺序变化以为模型Chat能力弱其实排序环节搞反了6提示词与生成用固定正确答案测提示词模板判断是否真正到了模型环节按照这个顺序排查我遇到的答非所问绝大多数都倒在前四个环节上。先查向量不仅省时间还能避免拿个大模型反复调提示词做无用功。2. 召回结果验证先确认向量检索拿到的东西对不对正式排查的第一步不是改参数而是把检索结果完整地暴露出来。很多人用LangChain或LangChain4j封装的RAG链路一条链跑完直接输出最终答案中间过程是个黑盒出了问题根本无从下手。正确做法是拆开链路单独执行检索步骤输出原始召回片段。2.1 最小化复现单独跑检索链路我习惯做一个最小化验证脚本输入用户问题只跑向量检索不拼接提示词不调用生成模型把召回的top-k片段连同相似度分数直接打印出来from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_community.vectorstores import Chroma # 加载向量库 embedding_model HuggingFaceEmbeddings( model_name./models/bge-large-zh-v1.5 ) vectordb Chroma( persist_directory./data/company_kb, embedding_functionembedding_model ) # 单独执行检索不走生成链路 question 如何修改企业认证信息 docs vectordb.similarity_search_with_score(question, k10) # 打印出召回片段和相似度分数先不看生成结果 for i, (doc, score) in enumerate(docs): print(f[Top-{i1}] 相似度: {score:.4f}) print(doc.page_content[:200]) print(---)这里有个细节不同向量数据库返回的score含义不同。Chroma返回的是距离数值越小越相似而Milvus、FAISS等可能返回的是余弦相似度数值越大越相似。我先查清楚当前库的score含义再判断阈值别把方向搞反了。2.2 判断召回内容是否匹配的四个观察点拿到召回片段后我一般按四个维度做判断关键实体是否出现用户问题中的核心实体比如企业认证修改是否出现在片段中没出现说明embedding没有把关键信息编码进去。语义相关度片段是否完整覆盖了问题的意图比如修改流程问题召回里有没有变更提交材料这些关联信息。正确答案排名手动在库里搜索已知存在正确答案的文档确认它是否在top-k内。排名越靠后检索参数越可能需要调整。分数断层观察相似度分数分布。正常情况下top-1到top-5分数应逐渐下降如果出现断层——比如前三个分数很高后面突然骤降——很可能检索逻辑或切片上有问题。我刚入行时犯过一个低级错误只看了生成结果完全没看召回片段调了一个星期提示词越调越差。后来养成习惯任何一次回答异常先把检索结果打出来看问题立刻水落石出。2.3 一个被忽略的细节查询语句与文档的语言差异还有一类答非所问现象如果只改动检索参数可能无法根治。一位做企业知识库的朋友遇到这种情况文档里全是正式名称简称用户提问却用的是口语说法。比如系统文档里写实名认证主体变更用户问的是换绑公司主体。向量检索结果往往把这类问题匹配到账号注销资料修改等无关片段。问题根源在于embedding模型对中英文混合、口语化表达和正式表述的编码效果差异明显embedding模型需要保证查询用词与索引文档在语义空间上的匹配度。这时候就要引入查询改写Query Rewrite先让LLM把口语化问题改写成文档侧的表达方式再去做向量检索。query_rewrite_prompt 将用户问题改写成知识库文档中常用的正式书面表达仅输出改写后的结果。 用户问题{query} 这类查询改写在遇到用户怎么问和系统怎么存不一致的场景时非常管用能大幅提升召回质量。3. 切片不当导致的关联信息断裂召回内容匹配但答案依然不对这类问题往往是切片环节出了问题。知识库文档被切分成固定长度片段后原本完整的语义被切断关键词被截进不同的片段里。检索时系统在错误片段里找自然找不到完整答案。3.1 固定长度切割的缺陷最常见的是按固定字符长度切比如每512个字符一刀切。这种切法的弊病在于语义边界完全被忽略一句话可能在中间被截断。我在一个电商客服项目中就遇到过关于退货规则的说明把一半切到了售后退款文档里另一半留在售后政策文档里。用户问退货退款规则时检索到的片段拼不出一段完整规则模型只好自行脑补给出错误答案。采用固定长度切片时至少要设置重叠区域让上下文信息流通。但更好的方案是结合实际文档结构来切优先保持语义完整。比如优先按照文档已有的标题、段落边界来切不要强行按字符数均分。3.2 结构化切片的一些务实做法我个人的经验是切片方案直接在配置阶段就定型不要等到上线后再因为召回质量差而返工。常用的做法有几种按语义段落切分优先使用文档中的标题、列表、段落层级作为自然边界段落过长的再结合固定长度二次切分。父子块结构将长文本切分为较大的父块再拆成多个较小的子块用于检索命中子块后把父块内容一并送入模型提升上下文完整度。自定义分隔符对包含表格、代码、特殊格式的文档优先识别这些结构作为切分点。以LangChain4j为例如果使用Easy RAG这类工具自动搭建知识库其默认切片策略可能只是基础的按长度切割。对于有一定复杂度的文档我会换成自定义的TextSegment或DocumentSplitter保证切片粒度合理。// 简单示例按段落分割器处理 return DocumentSplitter .recursive(400, 200) // 每段400字符重叠200字符 .split(document);这个参数的具体取值依赖于你的文档类型没有放之四海而皆准的标准。代码或多步骤流程文档建议更小块300字符左右叙述性文档可以使用更大的分割块1000字符左右。重叠区域建议设置为块长的1/4到1/3较为稳妥。3.3 切片效果验证用黄金问题测召回切片好不好不要凭感觉判断。我的做法是为知识库预设20到30个黄金测试问题每个问题对应文档中明确存在的一段标准回答。然后跑一轮检索统计标准答案片段是否出现在召回结果中以及它的平均排名。如果答案片段频繁缺席大概率是切片把答案切散到了多个片段中或切分点恰好把关键句子截断。此时调整切片策略再用同一批黄金问题回归测试。这个流程比每次等用户反馈问题要快得多。4. Embedding模型选型与向量化配置很多答非所问案例最后都收束到一个问题上embedding模型和文档场景不匹配。通用型embedding模型可以处理好常见问题但面对特定领域或行业词汇往往无法提供足够精准的语义向量。4.1 通用向量模型在垂直领域的乏力表现有同行问为什么用了明星级通用向量模型在垂直领域的效果还是那么不理想一个很重要的原因向量模型训练语料中该垂直领域的专业文本占比很低。例如用通用模型处理医疗文档当文档中出现左室射血分数心源性猝死冠脉CTA这类词汇时通用模型可能难以在语义空间中正确区分。检索结果就会把包含这些术语的片段和完全不相关的通用文本关联在一起。解决方向有三个选垂直领域向量模型如果不想过多折腾优先找领域特定的embedding模型或微调版本。对通用模型做微调基于领域语料对embedding模型进行持续训练使其理解专业术语的语义关系。引入外部知识增强在切片阶段对专业术语进行标注或扩展比如把左室射血分数标注为心脏功能指标之一提升通用模型的可匹配性。4.2 本地向量模型部署需要注意的内存与维度问题最近本地化部署RAG的方案很流行一个重要原因就是避免组件相互占用、模型在线服务不稳定。很多人直接用Ollama拉起一个向量模型减少联网依赖。但有一个细节需要注意向量模型实际运行时的内存占用和批次大小问题。一个维度为768或1024的向量模型处理大批量文档时如果没有合理控制批量大小容易出现内存溢出或推理速度骤降。我在本地用Ollama跑embedding模型时一般会在服务端配置和调用端同步控制from langchain_community.embeddings import OllamaEmbeddings embeddings OllamaEmbeddings( modelbge-m3, base_urlhttp://localhost:11434 )批量向量化时不要在一次性传入超大文档集合分批处理每批64条左右既稳定又不算太慢。如果同时要跑生成模型和向量模型两块模型会争抢算力。建议分开两套服务部署或使用独立的推理框架避免互相挤占资源导致响应变慢或请求失败。4.3 注意查询文本先做清洗另一类容易忽略的问题是查询文本本身很脏。比如用户提问带上请问帮忙谢谢这些礼貌性口头禅以及特殊符号、多余空格、emoji等容易干扰向量化效果降低检索质量。我一般在查询进入embedding前做一层轻量清洗去掉无意义的口水词保留核心实体和意图。更进一步的方案是结合意图识别做查询改写把用户提问转换成更适合检索的表达。这部分成本不高但效果提升非常明显。5. 向量检索与重排序之间的参数调配当embedding模型本身没问题、切片也相对合理时答非所问还可能来自检索参数配置失当。常见的有top-k取值太小、相似度阈值设置不合理、重排序选择不当。5.1 Top-K与相似度阈值怎么定top-k太小正确答案可能根本进不了候选集。我见过有人直接把k3少量正确片段排在第四模型完全看不到答案。top-k太大又容易引入过多噪音片段。不同场景下的合理取值我没法给出绝对标准一般从k10起步根据黄金问题的命中情况再调整。# 从10开始然后观察召回命中情况 docs vectordb.similarity_search_with_score( question, k10 )如果k10时正确答案稳定排在5到8名说明整体检索质量不差可以保持k10或尝试用重排序模型把正确答案提到最前位。如果k20时正确答案才开始出现说明检索本身就有偏差。继续调大k只是治标不治本。相似度阈值的使用也要小心。阈值设太高容易被切成多个片段的文档无人问津阈值设太低又会把大量无关文档送进候选集。配合top-k和重排序来综合评估比较好不要单独依赖阈值解决问题。5.2 重排序不是万能药重排序模型能对召回的候选片段重新打分把语义更匹配的片段排到前面。但它的前提是正确片段至少要进入召回候选集。如果正确片段压根没进top-k重排序再强也救不回来。我实际用过的方案是把重排序放在top-20到top-30这一级别。先召回较宽的候选集再用重排序模型过滤出最相关的5到10条再送入生成环节。这种做法比单纯增大top-k更有效因为重排序模型能减少噪音片段带来的干扰。选择重排序模型时优先关注模型本身是否对中文、领域术语有较好效果。以bge-reranker为例它有不同参数规模版本大模型效果更好但推理开销更高。部署重排序模型和向量模型尽量分开保证各自的响应速度。5.3 混合检索的必要性纯向量检索的局限性一直没有变它对关键词精确匹配的敏感度相对较弱。在RAG场景中如果用户查询包含了一段精确的编号、型号或特定词汇但文档中的出处却用的是全称或同义表述向量检索有时会给出较弱的相似度分数。这时候引入关键词检索BM25做混合检索会明显提升命中率。一个较稳的方案同时跑向量检索和BM25检索各自返回候选集然后用重排序模型统一打分排序。# 伪代码示例混合检索 vector_results vectordb.similarity_search_with_score(question, k20) bm25_results bm25_search(question, k20) # 合并候选集去除重复片段 candidates merge_candidates(vector_results, bm25_results) # 重排序模型对候选集重新打分 reranked reranker_model.rerank(question, candidates, top_k5)混合检索能覆盖向量检索可能遗漏的精确匹配场景在垂直文档库中收益不错。缺点是系统复杂度会上升。如果你的核心场景是模糊的语义搜索、不加混合检索也行如果经常涉及精确编号、型号、专有名词查询那很有必要加上。6. 向量数据库集成过程中的配置陷阱向量数据库集成与优化展开来可以讲很久但更常被问的是当已经选定了某个向量数据库集成时还有哪些容易踩的坑。我在这部分集中说几个常见的点供参考。6.1 索引类型与查询参数不匹配向量数据库的索引类型对检索性能和召回质量有明显影响。以HNSW索引为例在追求高召回率时需要适当调大M和efConstruction参数查询时efSearch参数控制搜索复杂度。比如默认的efSearch40可能比efSearch256召回率低很多如果答案片段被排在候选区边缘调大efSearch可能就会拿到正确结果。我把索引参数调整理解为用内存换精度索引构建参数决定了向量空间的连通性查询参数决定了搜索的穷尽程度。调参时结合召回效果避免拍脑袋。6.2 元数据过滤与混合检索配合向量数据库不仅存向量还能存元数据比如来源、作者、时间、分类等。如果在向量检索前能利用元数据先做一轮过滤检索效率和准确性都会改善。我做过的一个合同模板检索项目每个合同有部门属性、签约年份、合同类型。用户查询去年的采购合同模板如果先按年份去年、类型采购做元数据过滤再走向量检索就比直接全库向量检索准得多。缺失这一步常常导致向量检索返回一批看起来有点像、但年份完全不对的旧模板。6.3 知识库越积越乱的问题RAG知识库并不是只增不改的。文档更新后旧版本切片如果不做同步清理会导致同一知识点有多个相互冲突的版本。这同样会造成答非所问模型同时看到修改前后的两段描述自然不知道应该采信哪个。现在我维护RAG知识库时会建立一套规范文档入库记录来源信息文档更新后同步废弃旧版本切片定期做向量库清理。很多第一次搭建RAG的人没有这个意识等到回答质量肉眼可见下滑时才回头检查那时已经积累了大量陈旧数据。7. 排查实战一个企业知识库问题的完整流程最后用我最近处理的一个真实案例完整走一遍排查流程帮助理解。问题现象企业内部知识库已经接入全套RAG用户问出差报销流程是什么系统回答的是公司考勤管理制度完全答非所问。我的排查步骤先打印召回结果发现top-5片段分别来自考勤制度、节日福利、办公用品申请、带薪年假、报销制度。报销制度被排在第五位相似度分数和第一位相差不大但候选片段里混入了太多不相关文档。接着检查切片。报销制度文档使用了较多条款编号和表格固定长度切片把编号和描述切散了。有的片段只有条款编号没有内容有的只有表格内容没有说明文字。正确方案改按条款段落切分一个条款一条切片表格单独处理。然后检查embedding模型。当时用的是通用中文向量模型对报销差旅发票之类常见词匹配尚可但对差旅费报销单审批流这类组合词表达不够精确。这个场景暂不换模型因为词语不算特别生僻先通过查询改写把口语问题转换为文档常用说法。最后调整检索参数top-k从默认的3改为10引入重排序把候选集统一打分。经过这三步报销制度相关片段稳定排在召回前两名生成的答案有了实质改善。整个排查过程花费约半天其中大部分时间花在切片调整后的黄金问题回归上。相比一开始直接怀疑模型、准备换掉生成模型这个排查路径效率和成本都低很多。7.1 排查心得效率高低在于方法顺序总结成一句先查向量再怪模型背后的逻辑是先隔离链路再定位问题而不是凭直觉猜原因。看召回确认检索层拿到了什么材料。检查切片确认材料是否完整。检查向量模型确认语义表达是否准确。检查检索参数确认候选集范围和排序是否合理。最后才看生成确认模型是否有效利用召回材料。从打印召回结果开始到定位切片问题到调整query embedding和混合检索再到最终验证每一步都有明确的操作方法和判断依据。希望这篇排查思路能帮助遇到RAG答非所问问题的朋友少走一些弯路。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。