这道题其实问的是,当[[embedding]]模型把语义相近但任务不相关的片段排到前面时,我们怎么拆解和解决。核心矛盾是,Embedding的训练目标是语义相似度,而[[RAG]]业务要的是任务相关性,这两个东西天然有gap。举个例子,用户搜“苹果手机的电池寿命”,结果召回一段“苹果的营养价值”,因为“苹果”这个词向量靠得近,但用户要的是售后,不是营养学。
我会从三个层面去分析。先说查询侧。如果query本身就有歧义,比如“苹果”,我能做的是查询重写:让LLM根据上下文把意图补全,比如改成“苹果公司手机电池寿命”。或者用HyDE,先让模型生成一个假设的回答,再用这个回答去检索,这样embedding空间会更贴近业务意图。但这里有个前提,就是LLM本身不能太差,否则生成的假设回答会带偏。
再一个,检索侧。我很少只依赖向量检索,因为纯语义匹配容易飘。实际落地我通常走[[Hybrid Search]],把[[BM25]]的关键词匹配和向量检索结合起来,BM25能兜住精确匹配的场景,比如订单号、错误码。然后最关键的一步是[[重排]],用[[Cross-Encoder]]模型把召回的top-K候选做一次精细打分,因为它能看query和doc的交互,比双编码器更准。另外,我会在训练Rerank模型时做负样本硬挖掘,专门把那些“语义相似但业务无关”的样本挑出来当负例,强迫模型学会区分。
还有数据侧,这是根本。如果业务场景比较固定,比如电商客服退款,我会收集真实用户query和业务标注的相关文档对,去微调embedding模型,或者用[[指令微调]]的方式,显式告诉模型“相似不等于相关”。但微调成本高,不是所有场景都值得,所以我一般先看重排能不能解决大部分问题,解决不了再考虑动模型。
这里有个延伸点:如果检索到的内容本身就不完整,比如一个长文档被切碎成多个chunk,每个chunk语义上跟query相关但拼起来才构成完整答案,这时候单纯优化检索还不够,还需要做[[Parent Document]]召回,先召回叶子chunk,再返回父文档做下游推理。
所以整体我更倾向把问题分层:先确认是不是query不够精确,再查检索策略有没有覆盖多路,最后看重排能不能兜底。上线我会特别关注重排的延迟和召回率,如果重排模型太大导致延迟超标,我会考虑用更轻量的[[ColBERT]]或者[[蒸馏]]模型。如果业务数据量够大,数据侧微调才是治本。
总的来说,我会把这个问题看成是“语义空间和业务空间的对齐问题”,而不仅仅是一个检索召回的问题。落地时,我会优先用重排加混合检索快速止血,同时推动数据侧积累标注,做模型微调。这样既快又稳。