最近在做一个法律文书问答的RAG项目,本地测试时单条文档检索top5还挺准的,但一换成全量3000+文档的线上环境,回答质量明显下降,很多明明在库里的事实,模型却说“未找到相关信息”。我怀疑是向量检索环节出了问题,但看了日志,召回结果确实包含了相关片段,只是排序比较靠后。尝试调了chunk_size和overlap,也试过换embedding模型(bge-m3和text-embedding-3-small都试了),效果都不稳定。另外,线上并发一高,响应时间从2秒飙到8秒,怀疑是不是检索链路里有什么隐性问题。有没有大佬遇到过类似情况?该从哪些维度系统排查?是索引结构、召回策略,还是rerank环节的问题?求指点,感激不尽。
RAG项目上线后准确率暴跌,向量检索结果跟没调一样,求指点排查思路
全部回复
共 56 条这个现象我太熟了,尤其法律文书这种专业领域,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全耗在暴力计算上了,这个跟准确率低可能是两个独立问题。你线上有没有单独记录过检索耗时和生成耗时的拆分?
全量库和本地测试差别很大,top5准不代表top20准,召回片段靠后基本就是排序问题。你日志里能看到相关片段说明索引没崩,重点该查rerank那层有没有真正生效,或者embedding对长法律条文截断太狠导致语义漂了。并发高延迟飙升建议看看向量库连接池和rerank模型是不是串行阻塞了,这块很容易被忽略。
这个现象挺典型的,本地小库和线上全量库表现差异大,大概率不是embedding本身的问题。你日志里召回片段包含了正确内容但排名靠后,说明检索的“广度”够了,“精度”没跟上,重点应该放在rerank和索引参数上。三千多文档如果用的是HNSW或IVF这类近似索引,nprobe或ef_search设太小,高维空间里真实近邻很容易被漏掉,建议先把检索参数调大做对照实验。另外并发一高延迟飙升,很可能是ANN索引在内存竞争或者Python侧串行打分拖累的,可以看看是不是每次请求都在重新初始化模型或重复计算query向量。chunk策略也值得再抠一下,法律文书条款交叉引用多,固定长度切分容易把关键上下文切断。可以试试先加个cross-encoder做精排,再配合metadata过滤缩小候选集,通常比单纯换embedding收益更明显。
你日志里召回片段排得靠后,这基本就说明是排序问题而不是检索没召回到,重点该放在rerank上。全量3000+文档后向量空间的分布变了,top5里混进大量语义相近但不相关的片段很正常,加个cross-encoder重排能救回来不少。并发高了变慢,八成是ANN索引参数没调好,ef_search或者nprobe太小会退化成近似暴力扫描。另外法律文书这种长文本,chunk切得太碎反而丢上下文,可以试试按条款结构切而不是按字数切。
我也踩过类似的坑,本地小库和大库完全两回事。先别急着怪embedding,重点看下线上索引是不是用的HNSW或者IVF,参数没调好召回质量会掉得厉害。另外你说并发一高就飙到8秒,很可能是检索时没做缓存或者rerank模型在CPU上跑,这块单独查一下。还有个容易忽略的点,全量库下top5里塞太多相似片段反而会稀释关键信息,试试加个轻量rerank或者调低召回数量看看。
你这个情况我去年做合同审查的时候几乎一模一样,本地几十篇文档跑得飞起,一上全量就拉胯。我后来发现根子不在embedding,而是索引分片和检索参数的问题——全量之后向量索引默认的nprobe或者ef_search没调,导致近似最近邻搜出来的候选集本身就偏了,top5里塞了一堆语义相近但不相关的片段,真正该排第一的被挤到后面去了。你可以先拿一批标注好的query做个recall@k的离线评估,固定embedding只调索引参数,看召回率有没有变化,这样能把索引层和模型层拆开。另外并发飙到8秒这个事,我怀疑是检索链路里同步阻塞了,比如faiss或者milvus的查询没走连接池,高并发下全堵在IO上,跟检索质量是两码事,得分开查。还有rerank环节别急着上,先把召回搞稳,不然rerank再强也是在垃圾里挑。你线上日志里召回片段排得靠后但确实命中了,这恰恰说明是排序问题不是召回问题,方向可能一开始就偏了。
你这个问题挺典型的,本地小数据量检索看着准,一上全量就露馅。3000+文档其实不算多,但排序靠后说明embedding对长文本的语义压缩不够,法律文书里相似条款太多了。建议先别急着换模型,把召回top50都打出来人工看看,重点查rerank那一步是不是压根没生效或者被截断了。另外并发高了响应变慢,八成是向量库连接池或者索引没建好,可以看看是不是每次请求都在重连。
你这情况我遇到过,本地top5准线上崩,大概率是纯向量召回在全量库里区分度不够。法律文书术语重复率高,相似片段扎堆,相关的那条很容易被挤出前几。建议先加个BM25混合召回,再上rerank把top50压到5条,基本能救回来。并发飙到8秒得看看是不是每次请求都在重算embedding或者索引没进内存,这块查下FAISS或Milvus的配置。