资讯动态

生成式召回在交易搜索中的实践:从向量检索到意图生成

发布时间:2026/9/29 16:28:51 来源:尧图企业网站定制
1. 从“卷向量”到“生成式召回”的范式转移1.1 为什么传统向量检索在交易搜索里越来越吃力做电商搜索的人都有一个共同感受向量检索这条路前几年是红利现在越来越像内卷。得物这类以交易为核心场景的平台用户搜索行为跟内容社区、通用网页搜索完全不是一回事。用户输入“aj1 低帮 白红 42码”背后要同时满足品牌、品类、配色、尺码四个维度的硬约束还要在几百毫秒内从千万级商品池里把最匹配的几十个结果捞出来。传统做法是把query和商品标题、属性、图像特征分别编码成向量然后在向量库里做近似最近邻搜索再叠加一堆规则过滤和排序模型。这套链路我实际搭过也调过问题非常明显。第一向量检索本质上是“相似度匹配”它擅长找语义相近的东西但对“必须满足”的硬性条件不敏感。你搜“42码”它可能给你推一双“41码但描述里提到42码参考”的鞋因为语义向量上两者很近。第二多路召回文本一路、图像一路、属性一路之后要做融合融合权重怎么定是个玄学调参调到怀疑人生。第三长尾query和模糊query的召回率上不去比如“送男朋友的生日礼物 运动风 五百以内”这种query没有明确商品词向量检索很容易召回一堆不相关的东西。注意向量检索不是没用而是它在“语义泛化”上强在“精确约束”和“组合推理”上弱。交易搜索恰恰是约束密集、组合复杂的场景这就是矛盾的根源。1.2 生成式召回到底“生成”了什么很多人一听“生成式召回”就以为是让大语言模型直接生成商品列表这个理解太窄了。得物交易搜索里说的生成式召回核心思路是把召回过程从“在向量空间里找最近邻”变成“让模型根据query生成一组候选标识比如商品ID、类目路径、属性组合”再用这些标识去倒排索引或KV存储里精确取回商品。换句话说生成的不是最终答案而是“取数的钥匙”。这个转变的意义在于模型不再只是做相似度打分而是做条件推理和组合决策。比如query是“夏天穿的透气跑鞋 男 黑色 预算600”生成式模型可以输出类似“类目运动鞋跑步鞋性别男颜色黑价格区间400-600属性透气/网面”这样的结构化召回意图然后系统拿这个意图去精确匹配。这比在向量空间里“碰运气”要可控得多。1.3 适合谁来参考这套思路这套东西不是只有大厂才能玩。如果你在做垂直电商搜索、本地生活搜索、或者任何有明确属性约束的检索场景只要你有结构化的商品/内容库并且能拿到一定的query-点击日志就可以尝试。门槛在于你需要一个大语言模型可以是开源的本地部署或API调用都行、一个能承载结构化召回的索引系统倒排、KV、或者带过滤条件的向量库都可以、以及一套把生成结果映射回真实商品的机制。算力约束下7B到13B级别的模型做意图生成已经够用不一定非要上超大模型。2. 核心细节解析生成式召回的三个关键环节2.1 意图生成让模型学会“说人话”也“说机器话”生成式召回的第一步是让模型把用户query翻译成机器能执行的召回意图。这里有个关键设计输出格式必须是结构化的、可枚举的、可校验的。我见过一些团队直接让模型输出自然语言描述结果下游根本没法用。正确的做法是定义一套召回意图的schema比如{ category_path: [运动鞋, 跑步鞋], brand: [Nike, Adidas], gender: male, color: [black], price_range: [400, 600], attributes: [透气, 网面], exclude: [篮球鞋, 休闲鞋] }模型的任务是把query映射成这个JSON。训练方式有两种一种是用历史query-点击日志构造弱监督数据把点击商品的属性反推成意图标签另一种是用大模型做few-shot生成再用规则校验和人工抽检过滤。得物这种量级的平台大概率是两者结合。实操心得schema不要设计得太细否则模型输出不稳定也不要太粗否则召回精度不够。我的经验是核心约束字段控制在6到10个每个字段的取值集合提前枚举好模型只做选择不做创造。2.2 多模态融合文本、图像、属性的统一意图空间得物交易搜索的一个特点是商品信息高度依赖图像。一双鞋的配色、材质、版型光看标题很难判断。所以生成式召回不能只处理文本query还要考虑多模态信号。具体做法是把商品的图像特征、文本描述、属性标签分别编码后映射到一个统一的意图空间里。用户query经过模型生成意图后在这个统一空间里做匹配。这里有个容易踩的坑多模态融合不是简单拼接向量。我试过直接把图像向量和文本向量concat起来做检索效果很差因为两个模态的分布差异太大。更好的做法是用交叉注意力或者门控融合让模型自己学习什么时候该看图像、什么时候该看文本。算力约束下可以用轻量级的融合层比如把图像特征降维到和文本特征同维度后用一个可学习的权重做加权求和。2.3 召回执行从意图到商品ID的精确映射生成式模型输出意图JSON之后下一步是执行召回。这一步的关键是“精确”和“快”。精确意味着意图里的每个约束都要被严格执行不能像向量检索那样“差不多就行”。快意味着整个链路要在几十毫秒内完成。我的做法是把意图JSON拆解成多个约束条件分别去倒排索引里查倒排列表然后做交集。比如“类目跑步鞋”查一个倒排“品牌Nike”查一个倒排“颜色黑”查一个倒排三个列表求交集。如果某个约束的倒排列表太长可以用跳表或者位图来加速。对于价格区间这种范围查询可以用B树或者有序数组做二分。最后把交集结果按热度或历史点击率排序取Top N送给后面的排序模型。注意生成式召回不排斥向量检索。实际上对于意图里无法结构化的部分比如“透气”这种模糊属性可以退化成向量检索来补充召回。两者是互补关系不是替代关系。3. 实操过程从零搭建一套生成式召回链路3.1 数据准备与意图标注第一步是准备训练数据。你需要三样东西用户query日志、query对应的点击商品、商品的属性表。得物这种平台肯定有现成的。如果没有可以从公开数据集或者自己爬取的商品库开始。意图标注的思路是对于每个query取它点击率最高的Top K个商品把这些商品的属性做交集或投票得到该query的意图标签。比如query“aj1 低帮 白红”Top 10点击商品里8个是“Air Jordan 1 Low”7个是“白红配色”6个是“男款”那么意图标签就可以生成对应的JSON。这个过程可以用Spark或Pandas批量处理。# 伪代码从点击日志生成意图标签 def build_intent_label(query, clicked_items): intent {} intent[brand] majority_vote([item.brand for item in clicked_items]) intent[category_path] majority_vote([item.category_path for item in clicked_items]) intent[color] majority_vote([item.color for item in clicked_items]) # 价格区间取点击商品价格的分位数 prices [item.price for item in clicked_items] intent[price_range] [percentile(prices, 20), percentile(prices, 80)] return intent3.2 模型选型与微调模型选型上我建议从开源模型入手。7B级别的模型如Qwen、Baichuan、Llama系列在意图生成任务上已经够用而且可以本地部署算力成本可控。如果追求更好的效果可以上13B或34B但推理延迟会明显增加。微调方式推荐LoRA因为全量微调成本太高而且容易过拟合。LoRA只训练低秩矩阵参数量少训练快效果也不差。训练数据就是前面构造的query-意图JSON对。损失函数用标准的交叉熵但要注意对JSON里的关键字段如类目、品牌加权让模型更关注这些字段的准确性。# LoRA微调示例基于HuggingFace PEFT from peft import LoraConfig, get_peft_model from transformers import AutoModelForCausalLM, AutoTokenizer model AutoModelForCausalLM.from_pretrained(Qwen/Qwen-7B) tokenizer AutoTokenizer.from_pretrained(Qwen/Qwen-7B) lora_config LoraConfig( r8, lora_alpha32, target_modules[q_proj, v_proj], lora_dropout0.1, biasnone ) model get_peft_model(model, lora_config) # 然后接标准的训练循环3.3 召回执行引擎的搭建召回执行引擎的核心是倒排索引和交集运算。如果你已经有Elasticsearch或类似系统可以直接用它的bool query做多条件过滤。但ES在超大规模下的性能可能不够得物这种量级大概率是自研的索引系统。我的建议是用位图Bitmap来存储每个属性值的倒排列表。比如“品牌Nike”对应一个位图第i位为1表示第i个商品是Nike。多个条件的交集就是位图的按位与运算速度极快。价格区间可以用有序数组加二分查找或者把价格分桶后也用位图。最后把位图转成商品ID列表按热度排序。# 位图交集示例 import numpy as np bitmap_nike np.array([1, 0, 1, 1, 0, 1]) # 品牌Nike bitmap_black np.array([1, 1, 0, 1, 0, 1]) # 颜色黑 bitmap_running np.array([1, 0, 1, 0, 0, 1]) # 类目跑步鞋 result bitmap_nike bitmap_black bitmap_running # result [1, 0, 0, 0, 0, 1]对应商品0和商品53.4 与现有排序链路的对接生成式召回的输出是候选商品ID列表后面还要接粗排、精排、重排。对接的时候要注意召回阶段不要做太多排序把排序留给后面的模型。召回的目标是“不漏”排序的目标是“排准”。所以生成式召回可以多召回一些比如Top 500让后面的排序模型去挑。另外生成式召回的意图JSON可以作为一个特征传给排序模型。比如“用户query的意图是跑步鞋黑色400-600”这个信息对排序模型判断用户偏好很有帮助。我实测下来把意图特征加进去CTR能提升3到5个百分点。4. 常见问题与排查技巧实录4.1 模型输出格式不稳定怎么办这是最常见的问题。模型有时候输出合法JSON有时候输出带解释的文字有时候字段名写错。解决办法有三层第一层是prompt里严格约束输出格式给出示例第二层是后处理校验用JSON parser解析解析失败就重试或降级到规则召回第三层是训练时加入格式错误的负样本让模型学会避免。实操心得我习惯在prompt最后加一句“只输出JSON不要输出任何其他文字”并且把temperature调到0.1以下这样输出稳定性会好很多。4.2 长尾query召回率低怎么破长尾query的特点是训练数据少模型没见过。解决办法是用大模型做数据增强把长尾query改写成多个短query分别生成意图后合并。另外可以引入检索增强生成RAG的思路从历史query库里找相似query的意图作为参考让模型基于参考生成。得物这种平台历史query库很大RAG的效果通常不错。4.3 多模态特征对齐不上怎么办图像特征和文本特征的维度、分布都不一样直接融合效果差。我的经验是先用一个投影层把图像特征映射到文本特征的维度然后用对比学习拉近匹配的图像-文本对的距离。如果算力有限可以先用预训练好的CLIP模型做零样本对齐再在业务数据上微调。4.4 召回延迟超标怎么优化生成式召回的延迟主要来自模型推理。优化手段包括模型量化INT8或INT4、推理加速TensorRT、ONNX Runtime、缓存高频query的意图结果、以及异步预生成。得物这种平台Top 10%的高频query可以提前生成好意图缓存起来长尾query再走实时推理。问题类型典型表现排查思路解决方案格式错误JSON解析失败检查prompt和temperature加格式约束降低随机性召回率低长尾query无结果看训练数据覆盖度数据增强RAG多模态不对齐图像召回不准检查特征维度和分布投影层对比学习延迟超标P99超过200ms看模型推理耗时量化缓存异步5. 算力约束下的落地策略与个人体会5.1 小团队怎么低成本起步不是每个团队都有得物那样的算力。我的建议是先用API调用大模型做意图生成验证效果。等效果确认了再考虑本地部署小模型。本地部署的话7B模型用一张24G显存的卡就能跑量化后甚至16G也行。索引系统可以用开源的Elasticsearch或Milvus初期数据量不大单机就能扛。5.2 生成式召回和向量检索怎么共存我的观点是短期内两者共存长期看生成式召回会吃掉向量检索的一部分场景但不会完全替代。向量检索在语义泛化、跨模态检索上仍有优势生成式召回在精确约束、组合推理上更强。实际系统里可以两路并行召回然后做融合排序。融合的时候生成式召回的权重可以给高一些因为它更准。5.3 我踩过的最大的坑最大的坑是过早追求端到端。一开始我想让模型直接输出商品ID结果发现模型根本记不住千万级商品ID而且商品上下架频繁ID映射很容易出错。后来改成输出结构化意图再用意图去查索引整个链路就稳了。这个教训是生成式模型擅长做“决策”不擅长做“记忆”。把记忆交给索引系统把决策交给模型各司其职。另一个坑是忽略了意图的可解释性。早期版本模型输出的意图JSON字段名很随意导致下游解析经常出错。后来我们定义了严格的schema并且加了校验层问题才解决。所以schema设计不是小事它决定了整个链路的稳定性。最后分享一个小技巧在意图生成之后加一个“意图置信度”字段。模型对自己的输出给一个0到1的分数低于阈值的意图走人工规则兜底。这个机制在冷启动阶段特别有用能避免模型胡言乱语导致召回崩溃。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑