背景 / 现象
在 2026 年初上线的某金融合规问答系统中,RAG 模块持续出现"用户问 A,系统答 B"的现象。典型场景如用户查询"2025 年反洗钱新规对跨境转账的影响",系统却返回了"2023 年境内支付结算管理办法"相关内容。初期排查聚焦于 prompt 优化和相似度阈值调整,但效果有限。进一步观察发现,问题并非集中在单一环节,而是贯穿了从文档入库到最终回答生成的全链路。
该系统采用标准 RAG 架构:文档经 OCR 解析后切分为 chunk,通过 BGE-M3 模型向量化并存入 Milvus;检索阶段使用余弦相似度,设定阈值为 0.7;召回 top-5 结果拼装进 prompt 交由 Qwen2-72B 生成答案。上线初期测试集表现良好,但真实流量中查准率不足 40%,且存在大量"语义相关但事实错误"的回复。
问题拆解
我们将问题拆解为四个层级,逐层验证假设:
- 入库层:文档是否完整解析?chunk 切分是否破坏语义完整性?
- 向量化层:embedding 模型是否适配领域术语?向量空间是否存在语义偏移?
- 检索层:相似度计算是否合理?阈值设定是否静态僵化?
- 上下文拼装层:prompt 是否引入噪声?是否混淆了不同文档的上下文?
通过日志埋点与影子流量回放,我们发现:
- 60% 的失败案例中,top-1 召回 chunk 与问题语义相关性低;
- 25% 的案例中,多个高相似度 chunk 来自同一文档的不同章节,导致上下文冲突;
- 15% 的案例中,embedding 对专业术语(如"FATF 建议""SWIFT MT103")的编码能力不足。
核心原因
1. 切分策略未考虑领域语义边界
原始系统采用固定长度切分(每 chunk 512 token),导致法律条文被强行截断。例如,"根据《反洗钱法》第三十二条,金融机构应在交易发生后 5 个工作日内提交可疑交易报告"被切分为两段,前段丢失"5 个工作日内"这一关键时间约束,后段缺失法律依据。这种切分破坏了法律文本的"条件-动作"结构,使 embedding 无法捕捉完整语义。
2. embedding 模型缺乏领域适配
BGE-M3 虽为通用多语言模型,但在金融合规领域表现不佳。测试显示,其对"客户身份识别""受益所有人"等术语的向量表示与通用语境高度重叠,导致"KYC 流程"与"客户满意度调查"在向量空间中距离过近。此外,模型对长文本的编码存在信息衰减,超过 256 token 的 chunk 向量质量显著下降。
3. 静态相似度阈值无法应对长尾查询
固定阈值 0.7 在高频通用问题(如"什么是反洗钱")上表现尚可,但在低频专业问题(如"FATF 第 16 条建议的本地化实施")中,即使 top-1 相似度仅为 0.52,系统仍强制召回,导致引入无关内容。反之,部分高价值 chunk 因表述差异(如"跨境资金流动监测" vs "跨境支付监控")被阈值过滤。
4. 上下文拼装未做去重与冲突检测
系统直接将 top-5 chunk 拼接进 prompt,未识别重复来源或逻辑冲突。例如,同一法规的新旧版本 chunk 同时被召回,导致模型混淆"废止条款"与"现行条款"。此外,prompt 模板未显式标注 chunk 来源,模型难以判断信息优先级。
实现方案
1. 切分策略:基于语义边界的动态切分
放弃固定长度切分,改用 Sentence-BERT 驱动的语义切分器:
- 使用
paraphrase-multilingual-MiniLM-L12-v2计算句子间相似度; - 当相邻句子相似度低于 0.6 时,视为语义边界,进行切分;
- 对法律条文、条款项等结构化文本,优先按章节标题切分。
此方案使 chunk 平均长度从 512 降至 320 token,但语义完整性提升 37%(基于人工评估)。
2. 向量化层:领域微调 + 混合编码
- 领域微调:使用 10 万条金融合规问答对微调 BGE-M3,重点增强术语区分度;
- 混合编码:对超过 256 token 的长 chunk,采用滑动窗口编码(步长 128),取各窗口向量均值,缓解信息衰减;
- 元数据注入:将文档类型(法规/指南/案例)、生效时间等作为附加特征拼接至向量。
微调后,术语区分度(类内距/类间距)提升 2.1 倍,长文本召回准确率提高 28%。
3. 检索层:动态阈值 + 分层召回
-
动态阈值:基于查询类型自动调整阈值:
- 通用问题(如"什么是 AML"):阈值 0.65;
- 专业问题(如"FATF 建议 16"):阈值 0.45,允许低相似度但高相关性召回;
- 阈值由分类器(基于查询长度、术语密度等特征)实时判定。
-
分层召回:
- 第一层:向量检索(Milvus),召回 top-20;
- 第二层:基于关键词的倒排索引(Elasticsearch),补充术语精确匹配;
- 第三层:重排序(Cross-Encoder),融合向量相似度与关键词匹配得分,输出 top-5。
此方案使长尾查询召回率提升 41%,误召回率下降 19%。
4. 上下文拼装:去重 + 冲突检测 + 来源标注
- 去重:基于 chunk 向量聚类(DBSCAN,eps=0.1),合并相似度 >0.9 的重复内容;
- 冲突检测:构建法规版本图谱,自动识别并剔除已废止条款;
- 来源标注 :在 prompt 中为每个 chunk 添加
[来源:《反洗钱法》2025版 第32条]前缀,帮助模型判断权威性。
风险与边界
- 性能开销:分层召回使 P99 延迟从 800ms 增至 1.4s,需通过缓存高频查询缓解;
- 领域依赖:微调 embedding 需持续维护领域语料,冷启动阶段效果有限;
- 冲突检测盲区:图谱未覆盖地方性规章,可能误判区域性条款;
- 阈值动态性风险:分类器误判可能导致阈值设置不当,需设置 fallback 为 0.6。
总结
RAG 检索查不准的本质是工程层面对"语义对齐"的忽视。单纯优化 prompt 或调整阈值如同在沙地上建楼。必须从切分、编码、检索、拼装四层协同设计,明确每层的职责边界:切分层保语义完整,向量化层保领域适配,检索层保灵活召回,拼装层保上下文纯净。最终,系统查准率从 40% 提升至 78%,且未引入额外幻觉。
技术补丁包
-
动态语义切分器 原理:基于句子间相似度检测语义边界,避免固定长度切分破坏上下文。 设计动机:解决法律、医疗等领域文本的结构化切分需求。 边界条件:对非结构化文本(如论坛帖子)效果有限,需 fallback 到固定长度。 落地建议:使用
sentence-transformers库实现,关键参数:similarity_threshold=0.6。 -
领域微调 embedding 模型 原理:在通用模型基础上,使用领域问答对进行继续训练,增强术语区分度。 设计动机:通用模型在专业领域存在语义漂移,微调可缩小领域 gap。 边界条件:需至少 5000 条高质量标注数据,否则易过拟合。 落地建议:使用
sentence-transformers的train_stsb脚本,学习率设为2e-5。 -
分层召回架构 原理:向量检索 + 关键词检索 + 重排序,兼顾语义与精确匹配。 设计动机:单一检索方式无法覆盖所有查询类型,分层可互补短板。 边界条件:增加系统复杂度,需监控各层延迟与召回贡献。 落地建议:Milvus 与 Elasticsearch 并行查询,重排序模型选用
cross-encoder/ms-marco-MiniLM-L-6-v2。 -
上下文冲突检测机制 原理:构建文档版本图谱,自动识别并过滤过期内容。 设计动机:避免新旧版本法规混淆导致事实错误。 边界条件:图谱需定期更新,无法覆盖未结构化的政策文件。 落地建议:使用 Neo4j 存储版本关系,通过定时任务同步官方发布。
-
动态相似度阈值策略 原理:基于查询特征(长度、术语密度)分类,动态调整召回阈值。 设计动机:静态阈值无法适应长尾分布,动态策略提升查全率。 边界条件:分类器需持续训练,避免误判导致阈值异常。 落地建议:使用 LightGBM 分类器,特征包括
query_length,term_count,is_question。
排查 Checklist
-
\] 检查 chunk 是否包含完整语义单元(如法律条款、操作步骤)
-
\] 分析 top-k 召回结果中是否存在重复或冲突内容
-
\] 检查 prompt 中是否清晰标注 chunk 来源与时效性
-
\] 定期评估领域微调模型的泛化能力,防止过拟合