
1. 从向量内卷说起交易搜索的边界在哪里这几年做搜索的人提到召回基本绕不开向量检索把商品标题、类目、属性用 embedding 表达然后 ANN 召回。社区里到处是HNSW 参数调优faiss 百万级吞吐双塔模型如何碾压 BM25之类的分享。得物交易搜索团队当然也经历过这个阶段但我今天想聊一个相反的话题向量检索真的把所有召回问题都解决了吗如果没解决问题出在哪先说结论。向量检索解决的是语义相似问题而交易搜索本质上不是找出相似是找出用户此时想要的那个商品。这两者之间有一道非常微妙的缝隙——用户搜aj1 芝加哥 42码他要的不是和这句 query 向量最接近的十个标题而是当前有货、价格合理、尺码匹配、能过鉴别、可以立即下单的那双鞋。向量检索擅长前者后者却需要另一层更强的约束能力。这种感受在做实际 case 复盘的时候会特别强烈。我们团队在处理用户反馈时经常看到两类问题一类是长尾 query 根本召不回商品另一类是召回结果看着像但完全不对——比如用户搜黑白熊猫 Dunk向量召回里混进大量黑白配色的其他鞋款因为它们的标题向量距离确实近。这不是调几个参数能解决的是检索范式本身的边界问题。所以从去年开始我们认真做了一件反惯性的事尝试把召回从查出来变成生成出来。1.1 向量检索确实好用但天花板也很明显先别急着否定向量检索。它最舒服的场景是描述性搜索用户心里有个大概的样子用自然语言描述系统返回相似结果。这种场景下向量召回几乎是不可替代的你很难让关键词匹配去理解复古做旧感这种描述。但交易搜索有大量 query 是指向性搜索用户已经知道要什么输入的是品牌、系列、型号、尺码、价格区间甚至稀缺性暗示。这种 query 里含有的信息高度结构化但形态又是非结构化的自然语言——dunk 低帮 熊猫 36.5。向量模型处理这种 query 时会把精确的数字、型号映射到某个语义空间里的模糊位置反而丢失了约束力。另一个天花板是索引的覆盖率。向量召回永远受限于库里有什么向量。如果某个商品由于标题写法、详情页表达方式比较特殊它的向量在语义空间中处于一个孤立位置那就算用户在找它也很难被召回到。这在交易场景里就是直接的 GMV 损失。我们统计过向量召回的头部命中率很高但尾部商品的曝光机会非常有限这恰恰和交易搜索长尾即利润的特点相悖。当然不能说向量内卷没有意义——它把召回的基线拉高了但基线越高边际收益越低。与其继续在 recallk 上扣零点几个点不如想一下能不能换一种完全不同的生成范式把召回这件事重新定义一遍。1.2 交易搜索特有的三个救不了做向量检索的时候遇到最多的问题总结起来是三类我觉得这三个问题是交易搜索特有的和通用搜索不太一样。第一类是实时库存与价格约束。用户搜椰子 350 灰橙 44向量召回只看语义不考虑这双鞋当前是否有货、价格是否在合理波动区间。等到排序阶段再过滤会发现候选里大量商品已经下架或无货。这不是排序的锅是召回阶段没有把可得性作为约束生成条件。第二类是结构化属性的精确匹配。鞋类商品的尺码、年份、配色、系列这些属性本质上是枚举值。向量把42码和42.5码映射到相近但不完全一致的位置可能召回时就把精确的尺码要求放宽了。但交易场景里用户对尺码极其敏感差半码就是完全不同的商品。第三类是跨语言与黑话。得物用户很多会用圈内黑话搜商品比如倒钩闪电倒钩芝加哥禁穿。这些词在商品标题里可能根本不出现商品详情页里也不一定有。如果模型没见过这种映射关系向量召回就无能为力而生成式模型恰恰可以借助大规模预训练知识来建立这种桥接。这三个问题都有一个共同点它们要求系统在召回阶段就理解用户要的不是语义相近而是一个满足特定条件的确定性目标。语义相近用向量做没问题但确定性目标需要更强的推理和组合能力。1.3 跳出惯性把召回当生成问题我自己的体会是做技术方案最怕的不是没有解法而是惯性思维把问题定义窄了。当我们意识到交易搜索的核心难点在于约束条件下的目标生成之后很自然会想到另一条路既然用户输入的是文本目标商品可以表达为一个结构化的描述序列那为什么不让模型直接生成出这个目标序列再通过倒排索引或精确匹配去落到商品上这个思路在业界不是全新的生成式检索Generative Retrieval在其他领域已经有一些探索但在电商交易搜索里落地的不多。得物交易搜索的体量和场景足够特殊——商品数量级不像全网那么大但结构化属性极强用户意图明确。这恰好是生成式召回能发挥优势的土壤。我们最终做的方案简单说就是用一个序列到序列模型接收用户 query 作为输入直接生成它对应的商品标识序列这个序列可以是商品 ID、SPU ID 或一组结构化属性三元组。生成结果再通过精确索引映射回候选商品集合完成召回。整个过程没有 ANN、没有向量距离计算完全走的是生成-映射链路。这个决定不是拍脑袋做的。我们花了两周时间做了可行性验证挑了一万条真实 query用生成模型预测商品 ID再对比向量召回在这批 query 上的 recall50。结果是有明显增益的尤其是在长尾 query 上。后面我会把整个落地路径拆开讲。2. 生成式召回的核心逻辑让模型直接写出候选2.1 一个直接写答案的类比先打个比方。传统召回像是你在图书馆里查书目卡片你描述想要的主题图书管理员根据卡片索引找到一批相关书籍给你。向量检索相当于把卡片做成了语义标签你描述得越模糊它越能给你找相似的。但如果你说的是我要那本红色封面的、第二版、定价39元的书图书管理员就得一条条翻卡片对属性了效率很低。生成式召回的做法完全反过来你把需求告诉一个对这家图书馆极其熟悉的助手它直接告诉你你要找的是第三排书架第7本甚至直接告诉你书上的编号。它不需要翻索引因为它把需求到目标的映射关系记在了自己的参数里。这就是生成式召回的直觉跳过相似度检索直接预测目标标识。当然这只是直觉层面的类比。落到工程上模型不是真的记得每一件商品而是学会了什么样的 query 会对应什么样的商品描述序列这个条件分布。我们用商品的 ID 或结构化属性作为生成目标等于把召回任务变成了一个受控文本生成任务。2.2 生成式召回在电商的前置条件不是所有场景都适合生成式召回。我拆了几个前置条件得物交易搜索恰好满足所以落地比较顺利。第一目标空间要足够稳定。商品 ID 或 SPU ID 总不能每天都在变。如果商品库每天大规模上下架模型刚学会生成某个 ID第二天商品就没了这个范式就很尴尬。得物的核心商品球鞋、潮品生命周期较长爆款甚至能卖好几年这给了模型充分的学习空间。第二Query 和目标之间存在相对稳定的映射。用户搜aj1 芝加哥目标商品集合在很长一段时间内不会有剧烈变化。模型可以从历史日志里学到这种映射而不是要求它每时每刻 follow 最新的库存。第三商品数不能大到离谱。生成模型要预测的 ID 词表如果达到亿级训练和解码的复杂度都不可控。得物的在线商品规模是千万级但要召回的核心交易商品可以进一步收敛通过两级结构先预测 SPU再映射到具体 SKU把词表控制在百万级。这个规模对生成模型来说是可以接受的。这三个条件缺一个生成式召回的效果都会大打折扣。这也是为什么这个思路在通用搜索里很少被采用但在垂直交易搜索里反而顺理成章。2.3 范式转变的核心从集合到分布向量召回本质上是在一个巨大的商品集合里做 Top-K 检索这个集合是显式维护的索引。生成式召回则完全不同——系统里没有一个物理的索引结构而是把query 到相关商品标识的映射编码进了模型的参数分布中。模型在解码时不是从集合中筛选而是从分布中采样或搜索。这个转变带来的一个直接好处是模型可以捕捉到看似不相关但实际是同一个目标的映射关系。比如用户输入红白黑 飞人 篮球鞋向量召回可能找出一堆红白黑配色的篮球鞋但生成式模型在经过训练后可能直接学会把这个描述映射到Air Jordan 1 Chicago这个核心商品 ID——即使两者在向量空间里的距离并不近。另一个更深的差异在于组合能力。生成模型的解码过程是对多个属性、多个约束的联合推理它天然可以把低帮 熊猫 36.5分解为低帮黑白配色Dunk 系列36.5尺码这几个要素然后生成一个满足所有约束的 SPU 序列。向量模型则更像是把所有要素压缩成一个向量再做相似度比较约束之间的组合爆炸会让它显得力不从心。不过范式转变也意味着要接受它的毛病生成是概率性的可能出错可能幻觉。这不是免费的午餐工程上必须配套约束解码、校验回退等机制。后面我会专门讲踩坑。3. 得物交易搜索的落地路径从样本构造到解码后处理3.1 整体链路生成模型的位置我们的整体链路长这样用户输入 query → 前置改写归一化、分词、黑话映射 → 生成式召回模型 → 生成候选序列 → 精确索引映射 → 候选商品集合 → 排序 → 最终结果。生成式召回并不是唯一召回路而是和向量召回、倒排召回并行最后做多路融合。生成式召回在这条链路里扮演的角色是精确目标捕捉器它的输出不是一堆相似商品而是一组高置信度的目标 ID 序列。这些序列再通过映射表拉出完整的商品候选进入后续排序阶段。这个定位决定了我们把模型设计成了生成目标候选而不是生成商品描述。从工程收益上看生成式召回主要解决两个问题一是把精确匹配从排序阶段提前到召回阶段减少无效计算二是为长尾黑话 query 提供向量召回拿不到的候选。这两点直接决定了生成模型的输入输出设计输入是完整的用户 query 文本输出是结构化的商品标识序列。3.2 训练样本从点击日志到 query-目标序列样本构造是整个项目里最耗时但也最关键的环节。我们用得物 APP 的搜索点击日志和下单日志构造了query → 目标商品的监督数据。具体分几步第一步提取有效 session。我们筛选出单个 session 内有点击、有收藏、有下单行为的 query-商品对按照用户行为强度给不同权重。下单的权重最高其次是收藏、点击。权重差异是为了让模型学到用户真正要的而不是用户随手点的。第二步把商品 ID 映射到层级化目标序列。直接用原始商品 ID 作为生成目标会让词表过大训练也很困难。我们设计了一套层级编码品类 ID 系列 ID 商品 ID。比如某双 Nike Dunk Low 熊猫会编码成[品类:鞋类] [系列:Dunk Low] [商品ID:xxxxx]。这样生成的时候模型先预测品类再预测系列最后预测具体商品相当于把一个大问题拆成三层子问题。第三步负样本构造。不能用点击日志就认为没点击的就是负样本因为曝光本身已经经过了排序筛选。我们采用 in-batch negative 加随机替换的组合方式同一个 batch 里其他 sample 的目标作为负样本同时随机替换一部分目标商品为不相关商品让模型学会区分。3.3 模型结构编码器-解码器的选择模型结构上我们选用的是标准的 Transformer encoder-decoder而不是纯 decoder 的 LLM 架构。原因有两个一是 encoder-decoder 对输入 query 的理解更充分encoder 端可以做双向注意力二是整体参数量可以控制在一个合理的范围保证线上推理的延迟因为召回阶段处于搜索链路前端延迟每涨 10ms整体体验都会有感知。encoder 的输入是把 query 字符序列做 token 化加上一些 query 层面的特征如 query 长度、是否含尺码词、是否含品牌词作为辅助输入。decoder 端的输出目标就是 3.2 里设计的层级化商品序列。训练损失就是标准的交叉熵但我们在解码时会对三层输出施加不同的约束。这里有一个细节对商品 ID 的预测我们不是在 decoder 里直接预测一长串数字 ID而是把商品 ID 也作为词表中的一个 token。为了让模型更好学我们给高频商品分配了更短的 ID类似频率-词表编码的思路。这样一方面缩小了词表规模另一方面让模型更容易记住高频目标。3.4 解码策略与后处理约束生成解码阶段我们用了 beam searchbeam size 取 8。这个值是实测出来的beam 太小容易漏掉正确目标beam 太大延迟上去了但收益不明显。beam search 结束后我们把生成的序列通过一个映射表转成具体的候选商品集合。后处理是整个流程里让模型可用的关键。我们加了三道约束第一道是索引校验。生成出的商品 ID 必须存在于当前在线索引中如果不存在商品已下架直接丢弃。这个校验成本很低但能过滤掉相当一部分幻觉输出。第二道是一致性校验。如果模型生成了[鞋类] [Dunk] [商品ID:xxx]但商品 ID:xxx 实际属于篮球鞋品类说明生成序列内部矛盾同样丢弃。这道校验能够有效防止品类对了但商品不对的错误。第三道是召回兜底。如果生成结果的候选商品不足 N 个我们不会硬撑而是把这条路的结果置空让多路融合阶段用其他召回路的结果补上。生成式召回的目标是提高精确率不是保证覆盖率这点定位一定要清醒。4. 生成召回不是单飞和向量检索怎么分工4.1 离线评估生成召回的业务指标怎么算离线评估阶段我们没有只用 recallk因为交易搜索更关心的是能不能把用户真正想买的那个商品找出来。所以我们定制了三个评估指标。第一个是精准命中率PrecisionStrict生成候选集合里是否包含用户最终下单的商品。这个指标最贴近业务目标但也最严格因为用户最终可能买了推荐出来的别的商品而不是最初搜索的目标。第二个是属性约束满足率生成候选商品中满足 query 中结构化约束尺码、配色、品牌、系列的比例。这个指标专门用来校验模型有没有把约束当回事。比如 query 里明确写了36.5那候选里就不应该出现 37.5 的商品。第三个是新增召回率生成式召回提供的候选里有多少是向量召回和倒排召回完全没有覆盖的。这个指标直接体现生成式召回的路增量价值。说实话做这种新范式不能只盯着整体 recall一定要单独量化它到底带来了什么新东西。从我们离线评测的结果看精准命中率在核心品类上比向量召回高出不少新增召回率在长尾 query 上有显著贡献。这坚定了我们继续往下推的信心。4.2 线上融合多路召回里的精确担当生成式召回不是要替代向量检索而是要和向量检索、倒排检索形成互补。我们在线上做的是多路召回融合每路有自己的特长向量召回负责宽语义相关、泛化表达、新型描述靠它保证召回的覆盖面。倒排召回负责快精确关键词匹配、类目导航、热词命中靠它保证实时响应和基础准确性。生成式召回负责准约束组合、黑话映射、目标锁定靠它提供高置信度的精确候选。融合策略也不是简单的并集。我们给生成式召回的候选打了更高的精排权重因为它本身就是为了精确目标设计的。同时会做去重如果一个商品在向量路、倒排路、生成路同时出现它的综合得分会有一个加成系数。这实际上是在告诉模型多路都认可的商品可信度更高。4.3 实验数据告诉我们的定位线上实验我们跑了大概三周分流量做对比。整体结论是有生成式召回参与的实验组搜索成交转化率GMV有一定比例的提升核心来源是长尾 query 的成交提升而在大热词上生成式召回的增益不明显因为头部商品的向量召回已经做得很好了。这个结果其实很有意思。它说明生成式召回不是更好用的向量检索而是另一种能力——它擅长的是高约束、长尾、低泛化表达的场景。我们内部总结了一句话来定义它的定位向量检索决定搜索的想象力上限生成式召回决定搜索的精确度下限。所以别被替代这个词误导。真正的价值在于你的召回体系里多了一个拥有完全不同能力的角色让整个系统从相似度驱动的检索器变成意图驱动的生成器 相似度驱动的检索器的组合。5. 踩坑记录与调优心得冷门商品、幻觉与长 Query5.1 冷门商品生成模型更容易放弃第一个踩得最深的坑是冷门商品的召回。向量检索对冷门商品的问题是召不到生成式召回对冷门商品的问题更麻烦模型可能学不会。原因不复杂。生成模型的目标是预测条件分布如果某个冷门商品在训练样本里只出现几次模型会倾向把所有概率都分配给高频商品。这就导致一个后果你搜一个冷门鞋款生成式召回返回的是几双热门鞋看起来合理但完全不对。我们后来做两个调整。一是把训练目标从商品 ID扩展到商品 ID 区别于其他商品的判别信号让模型在预测冷门商品时能借助额外的判别信息。二是引入复制机制如果 query 中出现了和某个商品强关联的词比如限量编号、特殊配色名模型可以直接从 query 中复制这个词作为生成目标的一部分而不是自己从词表里编造。这套机制参考了指针网络的思路冷门商品上的命中率有很明显的回升。5.2 幻觉生成的不一定存在生成式模型的老毛病就是幻觉在我们的场景里表现为生成一个格式完全正确、但在商品库里根本不存在的 ID 序列。最离谱的一次模型信心满满地生成了一个[鞋类] [Dunk] [商品ID:888888]但映射表里这个 ID 是空的。最开始我们想靠训练数据解决这个问题——加大真实样本的比重把不存在商品的样本作为负样本。但效果不明显因为生成模型的本质决定了它会在概率空间里无中生有。真正解决问题的是 3.4 里讲的索引校验后处理。我觉得这个经验很重要生成式召回上线必须配套一个可靠的校验层不要指望模型自己变诚实。另外我们还做了一个小改进对解码出来的商品序列计算一个存在性概率用商品在训练样本中的出现频率和最近一段时间的真实点击率做平滑作为候选的置信度加权。这样即使模型生成了一个不太常见的 ID只要它历史表现好仍然有机会进入候选。5.3 长 Query约束越多的 query 越难生成长 query 是另一个重灾区尤其那种包含了品牌 系列 配色 尺码 价格区间的完整描述。模型生成的序列经常顾此失彼往往预测对了品牌和系列却在尺码上出错。我们的解法是在训练和解码时分别做文章。训练侧我们把 query 做了结构化增强在 encoder 输入中显式加入品牌词系列词尺码词价格区间的分段标记让 encoder 明确知道每个位置上的 token 属于哪种约束。解码侧我们对品类和系列之外的属性尺码、价格采用约束解码不依赖模型生成而是直接从 query 中抽取对应属性值和目标序列拼接。相当于让模型只生成最复杂的部分——品类、系列、商品 ID——而尺码价格这些相对机械的约束用规则来解决。这个组合打法的效果非常明显长 query 上的属性约束满足率大幅提升生成模型的压力也小了很多。我觉得这背后有一个通用的思路不要把模型的力气浪费在它不擅长的事情上能用规则解决的属性就让规则去解决。6. 下一阶段朝着搜索即生成继续走现在生成式召回已经在得物交易搜索的部分流量上稳定运行但我觉得这只是开始。从检索到生成的范式切换值得往前再走几步。一个方向是多模态化。现在的商品标题、详情页文本已经能用但交易决策往往依赖图片信息——配色、鞋型、细节。我们正在尝试把商品的主图描述也纳入训练语料让模型在生成序列时能看到商品长什么样。另一个方向是和排序做更深的联动。目前生成式召回的结果只是作为排序的输入但如果让排序模型也感知到这个商品是生成路召回的它可能会做出更合理的打分策略这在特征工程上是一个值得探索的新维度。最后再分享一个我自己的体会。做搜索技术的人很容易陷入一个误区看到新方法就想替换旧方法看到新指标就想追平。但真正有价值的往往不是替换而是并轨——让不同范式的系统各自发挥所长。生成式召回让得物交易搜索的召回体系多了一种全新的视角这一个尝试本身就比任何具体指标提升都有意思。如果你也在做交易搜索不妨跳出向量检索的惯性想想你的场景里有哪些相似度解决不了但生成能解决的问题。