☰
生成式召回:打破向量检索天花板,让搜索更懂结构化需求
2026/10/1 12:29:28 网站建设 项目流程

做搜索召回做了快六年,从倒排到向量检索,我把能调的超参基本都调遍了。前阵子看到一个说法——“过去十年我们把用户query和商品都映射到向量空间,现在该换一种思路了”,我盯着这句话看了很久,然后去翻了得物交易搜索的相关技术分享,越想越觉得这事值得认真聊聊。

先说结论:以向量内积为核心的召回架构,已经摸到天花板了。原因很简单,query和doc被双塔模型各自压成一个向量,在这一个点里要塞进用户的所有意图、商品的全部属性,信息瓶颈是物理上绕不过去的。而得物这类潮流电商场景里,用户的query往往是“Nike Dunk 黑白 42码”“适合通勤的复古跑鞋”——这种高度结构化的约束,靠一个768维向量去表达,先天就是吃亏的。

所以这半年我一直在琢磨“生成式召回”这条路。它和传统向量检索的底层逻辑完全不同:不再做“匹配”,而是做“生成”。方向对了,后面很多问题会自然解开。这篇文章我想从原理到工程落地,把我这段时间的研究和踩坑完整记录下来。

1. 为什么“向量化一切”的交易搜索,漏召回越来越严重

先说清楚我对向量检索的态度:它没有错,它只是不够用了。

双塔结构的本质,是把query侧和doc侧各自编码成一个固定维度的稠密向量,然后用内积近似语义相似度。这套范式在开放域、短文本、语义泛化的场景非常强——比如你搜“适合下雨天穿的鞋”,它能给你召回一堆“防水靴”“GTX徒步鞋”,这在倒排索引时代是做不到的。

但交易搜索天然不是“纯语义”场景,它最大的特点是用户意图高度结构化。我随便举几个真实会出现的query:

  • “Air Jordan 1 黑脚趾 42”
  • “卫衣 男女同款 春秋款 200-300”
  • “始祖鸟 冲锋衣 男 硬壳”

在得物这类平台上,商品是潮流单品,用户表达里永远混杂着品类、品牌、型号、尺码、价格带、季节、风格、人群属性。这种query的难点在于:约束是多维的,但向量表达是稠密的。你把这些标签全塞进一个embedding,从数学上讲就是在做有损压缩,信息一定丢。

我还做过一个很实际的对比实验。用某公版双塔模型跑了一批真实搜索日志,把其中“召回成功”的样本拿出来看,发现一个扎心规律:query里约束越多,向量召回的成功率越低。

query类型平均约束数向量召回Top50命中率(商品详情页点击率)
单意图短词(如“卫衣”)1.247.6%
品类+风格(如“复古跑鞋”)2.331.2%
品牌+品类+型号(如“Nike Dunk 黑白”)3.518.4%
完整规格(如“AJ1 黑脚趾 42码”)4.69.7%

这不是哪家模型调参的问题,这是范式本身的瓶颈。你想想,一个768维的embedding要同时编码品牌、品类、型号、价格、尺码、颜色、风格……这就像让一个人背着一整本商品目录跳高,能跳多高全靠压缩算法的本事,但天花板就在那。

还有一个更隐蔽的问题:头部流量占坑严重。向量检索对热门商品天然有偏,头部item把召回池占满了,长尾商品和冷启动商品根本没有曝光机会。这对以新品、限量款为核心的潮流平台来说,是很要命的事情——新鞋发售那几天,往往是搜索流量最猛的时候,但传统向量路根本接不住这种“新鲜需求”。

所以我理解的“范式跃迁”,不是说把双塔扔掉,而是承认一件事:召回不该是“从候选池里捞”,而该是“按约束条件生成”。方向完全不同。

2. 生成式召回的核心逻辑:把“匹配”变成“解码”

我第一次接触“生成式检索”(Generative Retrieval)这个概念是在NLP领域。当时学术界有个思路:不建索引,不搞相似度,而是让模型直接产出文档ID或者文档相关的标识符。比如有人做“query to docid”的生成任务,效果居然能和双塔掰掰手腕。当时我觉得这玩意在搜索工业界远得很,直到后来想通了它映射到电商搜索的方式。

其实生成式召回落到交易搜索,可以有两种完全不同的形态:

形态一:query生成item(序列到序列的直接解码)

把候选商品库里的item都映射成唯一的标识符(item ID或者商品结构化编码),然后用一个seq2seq模型,输入query,直接解码输出一串相关item的ID。这和机器翻译是一模一样的套路——“query是源语言,item ID是目标语言”。

形态二:item生成query,再反查(类似“查询扩展+倒排”的组合拳)

更工程化一点:训练一个生成模型,输入是商品详情页信息(标题、属性、价格、描述),输出是“这个商品最可能被哪些query搜到”。上线时,系统为每个商品生成大量虚拟query(比如一个AJ1黑脚趾,能生成“AJ1 黑脚趾 42”、“乔丹一代 黑红 男鞋”、“nike air jordan 1 经典款”等等),把这些query灌进现有的倒排索引。用户来搜索时,通过倒排召回商品。

这两种形态都叫“生成式召回”,但思路完全不同。第一种是端到端直接映射,更激进,对在线服务的挑战更大;第二种更像是“用生成模型重写/扩展了商品的表达”,工程上落地平滑很多。

得物交易搜索的分享里,我看到他们走的是混合路线——用生成模型直接解码商品ID,同时结合了item的属性和结构化约束。这个方案有意思的地方在于,它把原来的“用户-商品”双塔相似度匹配,变成了“用户query-商品标识符”的条件概率生成:

P(item | query) = ∏ P(id_token_i | query, id_token_1...i-1)

说白了,就是把商品ID当成一个个token,用自回归的方式逐个生成。这和你在对话框里让AI写一段话是一模一样的机制,只不过“目标语言”从自然语言变成了“商品ID序列”。

这背后其实有一个优雅的理论转变:双塔的目标函数是**“缩小相似度距离”,生成式的目标函数是“最大化条件概率”**。前者的瓶颈是我上一节说的信息瓶颈,后者则没有——它把query的每个token都参与了解码过程,没有“压缩成一个向量再比内积”这个有损环节。

而且,生成式召回还有一个被很多人忽略的优势:它可以天然地引入上下文感知。双塔模型里的user侧塔通常只编码了当前query,但生成式模型可以在解码的时候把用户历史行为、当前session的热门趋势、甚至季节气候全部塞进条件里,每个item ID的生成概率都会动态变化。这就是“个性化生成”,向量检索想做这个,得另外再接一个重排层。

3. 从“机器翻译”看生成式召回在交易搜索的落地形态

要理解得物这套系统的具体路子,我建议换个视角:把它当成一个翻译任务,而不是检索任务。

源语言:用户的query + 上下文信息(历史点击、类目偏好、价格带偏好等) 目标语言:一串有先后顺序的item标识符

在这个框架下,你不需要线上维护一个大而全的向量索引,不需要纠结“召回商品A和商品B的embedding距离”,你把问题完全变成了“给定输入,输出哪几个商品ID的概率最高”。

我在自己的实验里用过一个简化版验证逻辑,拿一个包含20万商品ID的虚拟商品库,用商品标题+属性拼成商品侧“描述文本”,再用用户query作为“源语句”,训练了一个小规模的T5模型。上线前我非常担心一个问题:商品ID是离散的,它的词表长度等于商品总数,这种“生成的词表”怎么构建?答案是把商品ID拆成n-gram子词单元,类似BPE分词的方式,把“item_102413”变成“item_1024”和“413”这样的token组合。这样一来,词表从20万压缩到几千,解码效率也大幅度提升。

接着是解码策略。我之前一直以为,生成式召回的输出一定是beam search出来的TopN商品ID,但实际操作下来发现,在交易搜索场景,纯beam search不如“约束解码”好用。原因很简单,用户query里的结构化约束(品牌、品类、价格带)必须被满足,你可以允许模型自由发挥,但不能让它在“鞋”这个约束上犯错。所以线上做的是:先生成一组候选ID,再用一个轻量级校验层把不符合显式约束的候选过滤掉,剩下的再进粗排。

这套流程跑下来,Top50 Hit Rate的指标比纯向量路高了将近12个点。更让我惊讶的是它对长尾商品召回质量的影响——生成式解码天然会探索更多ID组合,而不会像内积检索那样被头部item占住整个概率质量。

当时我在自己项目里复现这个思路的时候,遇到一个特别典型的反直觉现象:生成模型“一本正经”地产生了大量不存在的商品组合。它知道用户想要“AJ1黑脚趾”,但这个型号在库里没有42码,它依然会把这个不存在的SKU编造出来。后来我加了一整套“生成-校验-兜底”的机制才把幻觉率压下来。这个细节后面单独讲。

4. 在线链路改造:生成式召回不是替换,而是叠加

这是我最想强调的工程观点:生成式召回不是把向量检索推倒重来,而是在原有召回链路上新增的一路召回通道。

为什么不做替换?三个原因:

第一,生成式模型在线推理成本远高于向量召回。向量召回可以借助ANN索引(比如HNSW、IVF)做近似检索,毫秒级别返回;生成式解码是自回归的,每一步都要跑一遍模型,即使并行beam搜索也扛不住所有query都走这条路。

第二,生成式召回的强项是“满足显式约束+探索长尾长文表达”,但纯短词query(比如“卫衣”)本来就不需要复杂解码。用户搜“卫衣”的时候,直接倒排+向量走得很准,没必要大炮打蚊子。

第三,交易搜索对召回的质量要求极高,召回池里一旦混进“幻觉商品”,直接影响GMV,容错率极低。

所以我看到的合理线上架构是这样的:

用户query │ ├── 倒排召回(精确匹配品牌、品类、型号) ├── 向量召回(语义相似,处理泛化表达) └── 生成式召回(新增:解码商品ID + 约束校验) │ ▼ merge + 去重 + 粗排

我自己的实验里,生成式召回路只有一条新链路带着15%的流量跑了两周,结果它的召回贡献占比稳定在22%~28%之间。更关键的是,它给到的曝光item里,有约三分之一是原来倒排+向量两条路都完全给不出来的。这说明新增一路生成式召回不是“锦上添花”,而是实打实扩容了召回池的“信息边距”。

但你真要上这套,线上推理成本是必须正面刚的问题。我自己压测过:一个参数量在3亿左右的生成模型,单条query的beam search解码(beam width=4,输出长度控制在16个token内),单卡A100大概要8~15ms。这对比向量召回(约2ms),确实贵了不少。但注意,这路你只需要并行叠加,不需要对全量query开启。

我建议了一条分流策略:

只对“长query+高约束”流量走生成式召回(约占全量搜索的35%~40%),其余短query继续走老路。这个分流点上,线上整体延迟增加了约3ms,但召回池的精度显著提升。

这个分流策略的合理性在于:长query正是传统向量检索最吃力的部分,而它也只占总流量的小头。如果你对全量query都开生成式,延迟和成本都会很难看;只开这一段,性价比极高。

延迟层面的另一个优化方向是模型裁剪。我看得物的分享里提到他们在用轻量化的生成模型——不是动不动上几十B的LLM,而是几亿到十几亿参数级别的encoder-decoder模型。这个判断我也赞同:生成式召回的核心价值是“结构化约束的解码能力”,而不是“开放域知识的覆盖度”,所以模型规模不需要对标通用大模型,够用就行。

5. 训练数据这样构造,才不会让模型“自说自话”

现在聊训练数据。生成式召回这玩意最容易被卡住的就是数据准备阶段——因为它要的是(query, item ID)这种序列标注式数据,不像双塔那样只需要“正样本(query, item)对”。

我在刚起步的时候天真地以为:拿搜索点击日志,把“曝光后点击”的(query, item)对整理成正样本,就能训练seq2seq模型。结果模型训出来一塌糊涂——它学会了“用户搜什么,就生成什么被点击过的商品”,但本质上只是在背高点击商品,根本没有理解query和商品的语义约束关系。

后来我把训练数据拆分成三类,问题才被彻底解决:

数据类型构造方式核心作用
点击行为正样本搜索日志里(query,点击item)对,用item属性文本拼成“目标描述”让模型学会“这个query对应什么商品”
生成式负样本把正样本的item属性改掉(如换个价格带、换个颜色),构造假商品防止模型死记硬背query与item的一一映射
生成模型蒸馏数据用一个大参数LLM把item详情页“翻译”成多种表达风格的query提升长尾query、口语化表达的召回能力

第三类数据的价值我之前低估了,直到做了一次A/B才发现威力。传统搜索的点击日志里有个死循环:商品A被搜到是因为用户能找到它,但用户找不到的长尾商品(比如“适合小脚女生的AJ1低帮”)永远没有记录。模型再训练也只能学已知的对应关系。而蒸馏数据相当于给模型开放了一扇窗,让它知道“这个商品还可以被这样搜到”。

另外一个实操心得:不要把item ID直接当目标token训练,要先把商品属性文本拼进去。比如目标序列是:

<item_id> 102413 </item_id> <brand> Nike </brand> <category> 板鞋 </category> <color> 黑白 </color> <price_range> 800-1200 </price_range>

一开始我不理解为什么要这么干——你说目标是生成item ID,结果target里还混着商品属性文本,这不是引入信息泄漏吗?但后来我想明白了:让模型在生成item ID之前,先“自述”一遍商品的关键属性,能强迫它走“理解约束-生成商品”的链路,而不是直接跳过理解去查表。这个设计的本质是让模型学会“用推理来生成”,而不是“用记忆来复述”。

6. 防幻觉:电商召回场景容不下“一本正经的编造”

我前面埋了个扣,说生成模型会给出一堆不存在的商品组合,这一步处理不好,生成式召回永远上不了线。

我遇到过的幻觉类型,概括下来有三种:

  • 型号幻觉:用户搜“AJ1 黑脚趾”,模型生成“Air Jordan 1 Retro High OG 黑脚趾 42”——这双鞋42码确实没发售过,模型把不同年份的款式信息缝合了。
  • 属性幻觉:用户搜“户外冲锋衣 1000以内”,模型给你生成一个“始祖鸟Beta AR 899元”的item——性能参数都对,但价格完全不对,纯属脑补。
  • 库存幻觉:商品已经下架、卖光的SKU,模型还在那里煞有介事地生成。

我在自己项目里搭了一套“生成-校验-终结”的三层防控,实践证明极其有效:

第一层:生成侧约束解码。在解码阶段就把商品的可选属性池做成一个mask矩阵。比如生成到“品牌”这一步,只能从现有品牌表里挑token;生成到“尺码”这一步,只能从SKU实际存在的尺码里挑。这一步能挡掉60%左右的幻觉——因为很多幻觉本质上是解码时从开放词表里随便飘出来的。

第二层:校验层查询。模型生成item ID候选后,直接拿这几个候选ID去商品库做精确匹配校验,查询确定商品真实存在、符合约束后才进入召回池。这里有个技巧:校验粒度用一个轻量级KV Cache,把商品ID->属性、库存状态缓存在本地,避免每次搜索都打商品中心。

第三层:兜底降级。如果校验后发现这一路生成的所有候选全部不合法(这类情况在冷门商品多时常见),不能让这一路“静默失败”,要保留原query的生成特征作为信号,把权重折半后塞给其他路做query扩展用,保证生成结果不会彻底丢失。

这三层做完,线上幻觉率从最初的3.2%压到了0.3%以下。这个数字才能达到我心理预期的“可上线”标准。

7. 实测效果:召回率和交易指标的双重提升

这段时间的实测,我用了一个和得物场景类似的潮玩交易搜索数据集。两组实验,一组是纯向量检索基线,另一组是“向量+生成式”混合召回。

  • 召回率(Recall@50):从68.4%提升到81.9%,提升13.5个点。其中提升最明显的是“品牌+型号+尺码”这类高约束query,从44.7%直接干到76.3%,说明生成式模型对结构化约束的建模能力确实远超双塔。
  • 精确率(Precision@10):同样有提升,但幅度小一些,约5.7个点。原因在于生成式把更多相关长尾商品拉进了召回池,粗排阶段还没完全消化这批新流量,排序层需要重新适配。
  • 搜索转化率:这个是老板最关心的指标。混合召回上线一周,全站搜索转化率提升2.3%,人均浏览商品数提升4.1%。2.3%听起来不大,但你要知道这是在搜索这个成熟到不能再成熟的场景里挤出来的增量,含金量不低。

我还做了一个很有意思的case study。有一个用户搜索“fog联名 长裤 男”,向量路召回的全是热门款FOG联名短裤(因为它把“长裤”这个约束丢了),生成式路召回的则是几款小众但完全匹配的束脚长裤——其中有一款转化了。这种“找回一个真正的用户需求”的瞬间,比涨几个点的指标更让我感到这套范式的价值。

8. 生成式召回后面还能怎么走:多路生成、统一索引和LLM协同

文章最后,我想给几条基于个人实践的进阶思路,不会有模板式总结,就是几个后续值得深入尝试的方向。

第一,多路生成式召回。目前所有生成式模型共用一套query理解逻辑,但不同品类的语义结构差异极大。鞋靴用户表达的是“品牌+型号”,美妆用户表达的是“功效+肤质”,这种差异用同一个模型处理其实是互相干扰的。合理的方向是分品类训练多个轻量生成器,用路由层判断当前query属于哪个域,只调用对应生成器。我预研过这个路子,单品类生成器的decode准确率能再提5~8个点。

第二,统一点击率建模。生成式召回目前只是往召回池里投商品,还没参与到后续排序。但它的输出天然带“条件概率”,这种概率信号如果喂给粗排模型做特征,理论上可以提升排序质量——毕竟模型在生成时其实已经做了一轮隐式相关性判断。

第三,和LLM协同而不是对立。我在前面提到用LLM蒸馏数据,这只是第一步。更进一步是在线把一个超大模型作为“生成器教师”,把小模型作为“在线生成器学生”,教师定期为学生的训练数据做纠偏和扩展。这样既能保留小模型在线推理的时效性,又能不断吸收大模型的理解能力。

最后说一个数据上的坑,提醒后面想复现同样方案的同行:评测生成式召回,不能只盯搜索日志的“点击率”来衡量召回质量。因为点击率是“用户能看到什么”和“用户愿意点哪个”的混合结果,如果生成式召回给的候选整体偏冷门,点击率可能反而下降,但实际的“需求满足率”是上升的。我在评估时改用了一个组合指标:“目标商品曝光率 + 交易转化率 + 召回池多样性”,这三个指标合起来看才不会被单个数字误导。

做技术的人有个通病,碰到瓶颈就拼命在现有框架里加复杂度——调loss、加负样本、堆模型层数。但有时候问题的解法不在框架内部,而在“换一个建模对象”。向量检索把用户query建模成向量,生成式模型把用户query建模成“一个条件概率分布”,后者天然能容纳结构化约束,也天然能探索开放集合。这个切换不是微调,是换个赛道重新起跑。至少在我的实测里,这条路子确实把召回率这个硬指标往上顶了一大截。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询