最近在做一个法律文书问答的RAG项目,本地测试时单条文档检索top5还挺准的,但一换成全量3000+文档的线上环境,回答质量明显下降,很多明明在库里的事实,模型却说“未找到相关信息”。我怀疑是向量检索环节出了问题,但看了日志,召回结果确实包含了相关片段,只是排序比较靠后。尝试调了chunk_size和overlap,也试过换embedding模型(bge-m3和text-embedding-3-small都试了),效果都不稳定。另外,线上并发一高,响应时间从2秒飙到8秒,怀疑是不是检索链路里有什么隐性问题。有没有大佬遇到过类似情况?该从哪些维度系统排查?是索引结构、召回策略,还是rerank环节的问题?求指点,感激不尽。
RAG项目上线后准确率暴跌,向量检索结果跟没调一样,求指点排查思路
全部回复
共 49 条这个现象我太熟了,尤其法律文书这种专业领域,3000+文档量级下召回排序漂移是常态,不是embedding模型的问题。你本地测试的top5准,是因为测试集往往围绕少数核心片段构建,而全量库里相似表述太多,向量空间里目标片段被大量语义近似的法条稀释了,所以排序自然往下掉,但日志里能看到它确实被召回了——这恰恰说明问题不在召回,而在排序和融合策略。我建议你先别调chunk_size了,把精力放在rerank上,尤其试试交叉编码器,直接对召回的50-100个片段做精排,法律文本的用词差异太细微,向量相似度根本区分不了“未遂”和“既遂”这种关键区别。另外你说并发从2秒飙到8秒,很可能是向量检索的索引类型没跟上,如果用的是暴力搜索或者HNSW参数没调好,全量数据下延迟会指数级上升,考虑换IVF或者PQ量化,还有把检索和重排做成异步管线,别让rerank阻塞主请求。还有个很隐蔽的坑,法律文书的chunk边界经常把法条拆断,导致语义不完整,你可能需要按条款编号或换行符做结构化切分,而不是固定长度切。最后,别忽略元数据过滤,比如先按案由或法条类别粗筛再向量检索,能大幅减少干扰项。
3000多文档就崩成这样,大概率不是embedding的锅,而是检索链路里“召回-排序”的漏斗设计有漏洞。你日志里能看到相关片段但排得靠后,说明向量召回本身没丢,问题是top5截断太狠了,法律文书的语义相似度本来就比通用场景低,相关片段可能排在二十名开外,你直接截断当然喂不进大模型。建议先别动chunk和模型,把召回数量从5调到30甚至50,然后加一个rerank层,用bge-reranker或者交叉编码器把精排做起来,看准确率会不会回升。
另外响应时间飙升到8秒,我怀疑你线上是不是用了暴力检索,3000多文档的索引应该没问题,但你要确认下有没有做归一化或者是否误把整个索引加载到内存里导致GC频繁。更隐蔽的问题是,法律文书的query往往包含大量专业术语,用户提法跟库里的表述差异很大,你得检查下有没有做query改写或者同义词扩展,不然向量空间里根本对不上。
我建议你先做两个实验:一是把召回的top50结果直接打印出来看相关片段的真实分布,二是用线上真实用户query去对比本地测试集,看看是不是测试集太干净导致过拟合。如果rerank上了还不行,那可能得考虑混合检索,加个BM25做关键词兜底,法律场景里精确词匹配有时候比语义重要得多。
看着像是典型的“测试集和真实分布错位”问题,单条文档测top5准,全量一上就垮,多半是chunk之间互相干扰了。法律文书里很多条款表述高度相似,原来一条文档里的局部最优,在全库检索时会被大量同义片段挤下去,你这召回里有相关内容但排得靠后,说明向量相似度本身就没拉开区分度。建议先别急着换embedding,把线上实际召回的top50拉出来看看,算一下相关片段在排序里的中位数位置,如果普遍在20名开外,那问题就不在召回而在排序策略上。另外,chunk_size和overlap这种参数其实和文档结构强相关,法律文书按条款切分比按固定字数切分靠谱得多,你可以试试按“第X条”这种语义边界来切。rerank环节如果没上,那基本就是硬检索在扛,3000+文档量级下纯向量召回本来就容易翻车,加个cross-encoder做二次排序效果会立竿见影。并发飙到8秒这个事,建议查一下是不是检索时把全文向量都load进内存了,或者有没有在做暴力检索没走ANN索引,这种量级用HNSW应该轻松扛住。最后建议线上加个“未找到”日志的回流分析,把模型答错但库里确实有的case攒起来,看看是召回阶段漏了还是排序后排太靠后被截断了。
这题我熟,之前做合同审核也翻过车。你召回结果里有但排得靠后,八成是相似度阈值卡太死或者top-k没跟上全量数据分布,3000篇和单篇测试的向量空间密度完全不是一回事。建议先按召回率倒查,把相关片段的相似度分数打出来看看跟不相关片段的重叠区间,大概率是分布错位了。另外线上并发飙到8秒,先确认下是不是在纯CPU环境跑向量检索,没上索引或者没走GPU加速,这玩意儿跟准确率暴跌可能是两码事,但会干扰你排查心态。rerank倒是可以后面再上,先解决召回排序问题再说。
3000+文档对向量索引来说是个分水岭,你这大概率不是chunk或者embedding的问题,而是召回链路里少了rerank这一层。全量检索时向量相似度会被无关片段干扰,相关片段排到十几名开外很正常,建议先看下召回的top20里有没有正确答案,有的话直接加个cross-encoder做精排,效果立竿见影。至于并发飙到8秒,查下是不是用了暴力检索没走HNSW或者IVF索引,线上环境必须建索引,不然3000条文档全扫一遍谁都扛不住。顺便问下你召回用的是稠密向量还是混合检索?法律文书里很多专业术语,纯向量可能不如BM25+向量融合效果好。
先查召回阈值和重排截断,大概率是top20里命中了但被挤掉,跟embedding关系不大。
这问题太典型了,本地测top5准,全量一上就崩,大概率不是embedding的锅。我之前做案例库检索也栽过跟头,后来发现是chunk切完以后,法律文书里那种“本院认为”和“判决如下”的上下文割裂太严重,向量距离根本拉不近,你换bge-m3其实没解决这个结构性问题。建议先别急着调参数,把线上那批“查不到”的query拉出来,看看召回片段里到底缺了哪部分关键实体或条款,如果片段里有但排序靠后,那基本就是检索精度不够,而不是召回缺失。
至于并发从2秒飙到8秒,我赌八成不是向量库本身慢,而是你每条query都跑了全量扫描加粗排,没做索引剪枝或者聚类预筛。3000+文档其实量不大,但如果你用HNSW的话,M和efConstruction没调好,高并发下延迟会指数级恶化,可以先看看这些基础参数有没有针对线上负载重新调过。
还有个思路可能被你忽略了——rerank阶段。你只说了召回排序靠后,但没提后面有没有接cross-encoder之类的精排模型。如果只是纯向量余弦相似度直接输出top5,那法律文书的语义重叠度高,很容易出现“相关但不关键”的片段挤掉真正的答案。我建议你做个ab测试,固定召回top20,然后分别用向量排序和加一层bge-reranker对比,看看准确率差多少。如果rerank确实能拉回来,那问题就简单了,纯向量阶段只要保证召回粒度够粗就行,不用追求top5准。
3000多文档说实话不算海量,但你这问题我太熟了,八成不是embedding或chunk的锅,而是召回链路里少了rerank或者排序权重没调好。top5准不代表全量下那5个候选就是对的,你日志里“相关片段在但排序靠后”已经说明向量检索的粗排分数没把真正答案顶上去,试试先加个轻量rerank模型,比如bge-reranker,把top20重排到top5,效果立竿见影。另外chunk_size和overlap反复调不稳定,很可能是你切分时把法律条文里的“但书”条款或者“例外情形”给切断了,导致语义碎片化,建议用结构感知切分,按条款编号或段落边界来分,别死磕固定长度。响应时间飙到8秒这个,先排查是不是检索时把全量向量都加载到内存了,没做索引分片或者HNSW的efSearch参数设太大,并发高时CPU都在算距离,肯定慢。还有个容易忽略的点,你线上query是不是没做同义改写或法律术语扩展?比如用户问“合同无效情形”,库里存的是“效力瑕疵”,向量空间里距离可能就远了。别急着换模型,先把线上失败的case捞出来,看top20里到底有没有正确答案,如果连top20都进不去,那才是索引或embedding的锅,如果进去了排序不对,那就死磕rerank和重排策略。
全量之后召回排序靠后大概率是相似度分布被拉平了,3000+文档的向量空间密度跟测试集完全不是一个量级,建议先看下召回的score分布,如果top20都挤在0.7附近那问题就清楚了。chunk_size和embedding模型调了没用的话,重点检查下是不是没做metadata过滤,法律文书领域案由或条款类型这种硬条件能直接砍掉一大半无关向量。响应时间暴涨倒不一定是检索链路问题,全量后向量索引没做量化或者没上HNSW的efSearch调优,CPU全耗在暴力计算上了,这个跟准确率低可能是两个独立问题。你线上有没有单独记录过检索耗时和生成耗时的拆分?