1. 从“卷向量”到“生成式召回”的范式思考
1.1 为什么传统向量检索在交易搜索场景里越来越吃力
做电商搜索的人都有一个共同感受:向量检索这几年被卷到了极致。从双塔模型到多负样本采样,从ANN索引调优到量化压缩,能榨的油水基本都榨干了。但真正落到交易搜索这种场景里,你会发现一个尴尬的事实——向量检索本质上还是在做“相似度匹配”,而不是在“理解意图”。
我拿得物这类交易平台的实际场景举例。用户搜“送男朋友生日礼物 篮球 学生党”,传统向量检索会怎么做?它把query编码成一个稠密向量,然后去和商品标题、属性、类目的向量做余弦相似度计算。问题来了:这个query里其实包含了四层信息——场景(生日礼物)、人群(男朋友/学生党)、品类(篮球)、预算暗示(学生党通常意味着价格敏感)。向量检索把这一整句话压成一个向量,信息密度被严重稀释,最后召回的往往是“篮球”这个品类词匹配度最高的商品,而不是“适合学生党预算的、有礼物属性的篮球”。
更麻烦的是,交易搜索的query分布是极度长尾的。得物上每天有大量“aj1 低帮 女款 樱花粉 38码”这种超具体的长query,也有“有没有那种看起来很贵但其实不贵的鞋”这种口语化表达。向量检索对前者还能靠属性匹配勉强应付,对后者基本束手无策——因为“看起来很贵但其实不贵”根本不是一个可以用余弦相似度衡量的语义空间。
我在实际项目里做过统计,传统向量召回在交易搜索的Top20结果中,真正符合用户完整意图的占比不到35%。这意味着大量流量被浪费在了“看起来相关但用户不点”的商品上。这个数字背后,是向量检索范式的根本性瓶颈:它只能做“表征匹配”,做不了“意图推理”。
1.2 生成式召回到底“生成”的是什么
很多人第一次听到“生成式召回”会懵——召回不是从索引里捞东西吗?生成是什么意思?难道让模型凭空造商品?
这里需要把概念掰开。生成式召回的核心不是生成商品,而是生成“召回路径”。传统向量检索是“query向量 → 索引 → TopK商品”这一条固定路径。生成式召回则是让LLM先理解query,然后生成一组“召回指令”或“召回策略”,再去执行。
举个例子。用户搜“夏天穿的透气跑鞋 预算500以内”。传统向量检索直接编码匹配。生成式召回的做法是:LLM先解析这个query,输出一个结构化意图——{品类: 跑鞋, 季节: 夏季, 功能: 透气, 价格上限: 500}。然后基于这个意图,生成多条召回路径:一条走“跑鞋+透气材质”的属性索引,一条走“500元以下跑鞋”的价格区间索引,一条走“夏季运动鞋”的类目索引。最后把多条路径的结果融合排序。
这背后的逻辑差异是本质性的。向量检索是单路径、表征驱动;生成式召回是多路径、意图驱动。前者像拿一张模糊的照片去人脸库比对,后者像先画出嫌疑人的特征画像再去排查。
得物交易搜索的场景里,这个差异带来的收益非常直接。我实测过一组对比:同样的query集,生成式召回在长尾query上的点击率提升了约22%,在口语化query上的转化率提升了约18%。这不是模型参数带来的边际收益,而是范式切换带来的结构性红利。
1.3 为什么现在是切入生成式召回的最佳时机
三年前提生成式召回,大概率会被当成PPT项目。原因很简单:LLM推理成本太高,延迟扛不住,而且 hallucination 问题在召回场景里是致命的——你总不能给用户召回一个根本不存在的商品。
但现在情况变了。小尺寸LLM的推理成本已经降到了可接受范围,7B级别的模型经过量化后,单次推理可以控制在几十毫秒。更重要的是,召回场景其实对生成内容的“准确性”要求没有想象中那么高——我们不需要LLM生成完美的商品描述,只需要它生成合理的召回意图和路径。即使LLM偶尔理解偏了,后面的多路召回和融合排序也能兜底。
另一个关键变化是多模态能力的成熟。得物这类平台的核心商品是鞋服,图片信息量极大。传统向量检索对图片的处理就是CLIP编码成一个向量,然后和文本向量做对齐。但生成式召回可以让LLM直接“看”图片,理解“这双鞋的配色是蒂芙尼绿还是薄荷绿”、“鞋型是老爹鞋还是板鞋”,然后生成更精准的召回条件。这个能力在交易搜索里价值巨大,因为用户经常搜“那个绿底的鞋”这种纯视觉描述。
所以我的判断是:现在不做生成式召回,一年后会被迫做,而且那时候的竞争门槛会更高。因为生成式召回的核心壁垒不在模型本身,而在“意图解析→路径生成→多路融合”这套工程体系的打磨,这需要时间积累。
2. 生成式召回的核心架构拆解
2.1 整体链路:从Query到结果的四层结构
得物交易搜索的生成式召回,我把它拆成四层。这个分层不是拍脑袋定的,而是根据实际工程落地时的职责边界来划分的。
第一层是Query理解层。这一层的输入是原始query,输出是结构化意图。核心组件是一个经过领域微调的LLM。为什么不用通用LLM?因为交易搜索的query里有大量黑话和缩写,比如“aj”指Air Jordan,“dunk”指Nike Dunk,“yeezy”指Yeezy系列。通用LLM对这些词的理解经常跑偏,必须用平台内的query-商品点击日志做微调。
第二层是召回路径生成层。拿到结构化意图后,这一层负责生成具体的召回策略。比如意图是{品类: 篮球鞋, 品牌: Nike, 价格: 500-800},那生成的路径可能包括:品牌+品类索引、价格区间+品类索引、相似款向量索引。这一层的核心是路径模板库+LLM动态编排。模板库保证基础路径的稳定性,LLM负责处理模板覆盖不到的长尾情况。
第三层是多路召回执行层。这一层是工程重头戏。每条召回路径对应一个召回器,可能是倒排索引、可能是向量索引、可能是图索引。关键问题是如何控制总延迟。我的做法是给每条路径设置动态超时,快路径优先返回,慢路径异步补充。同时用召回结果缓存来兜底,热门query的召回结果直接走缓存。
第四层是融合排序层。多路召回的结果需要去重、打分、融合。这里不能用简单的加权求和,因为不同路径的分数尺度不一样。我用的是Learning to Rank + 路径特征的方案,把“来自哪条路径”也作为一个特征输入排序模型。
这四层里,第一层和第二层是生成式召回区别于传统方案的核心,第三层和第四层是工程保障。很多团队做生成式召回失败,不是因为LLM不够强,而是因为第三层的延迟控制和第四层的融合策略没做好。
2.2 Query理解层的微调策略与意图Schema设计
Query理解层是整个生成式召回的地基。这一层如果解析错了,后面全错。我在实际项目里踩过的最大坑就是:一开始直接用通用LLM做zero-shot解析,结果发现它对交易场景的query理解准确率只有60%左右。
问题出在两个方面。一是领域词汇,通用LLM不知道“dunk熊猫”是指Nike Dunk黑白配色,它可能理解成“熊猫这种动物”。二是意图粒度,通用LLM倾向于把query解析得很粗,比如“aj1 低帮 女款”它可能只输出{品类: 运动鞋, 品牌: Jordan},丢掉了“低帮”和“女款”这两个关键筛选条件。
我的解决方案是两阶段微调。第一阶段用平台内的query-点击日志做继续预训练,让模型熟悉领域词汇。第二阶段用人工标注的意图Schema做指令微调。意图Schema的设计很关键,我最终定下来的字段包括:
| 字段名 | 类型 | 说明 | 示例 |
|---|---|---|---|
| category | string | 品类 | 篮球鞋 |
| brand | string | 品牌 | Nike |
| price_range | [min, max] | 价格区间 | [500, 800] |
| attributes | list | 属性列表 | [低帮, 透气, 白色] |
| scene | string | 场景 | 生日礼物 |
| crowd | string | 人群 | 学生党 |
| visual_desc | string | 视觉描述 | 绿底、厚底 |
这个Schema不是拍脑袋定的,而是从实际query日志里聚类出来的高频意图维度。我统计了得物交易搜索Top10万的query,发现90%以上的query都可以用这7个字段覆盖。剩下的长尾query再用一个other字段兜底。
微调数据方面,我用了约5万条人工标注的query-意图对,加上约50万条通过点击日志弱监督生成的样本。弱监督的逻辑是:如果用户搜了query A,然后点击了商品B,那B的属性就可以反推为A的意图的一部分。这个方法虽然噪声大,但胜在量大,能覆盖长尾。
注意:意图Schema不要设计得太细。我一开始加了“材质”“产地”“年份”等字段,结果发现标注成本飙升,而且模型在这些字段上的准确率很低。后来砍到7个核心字段,整体准确率反而提升了。
2.3 召回路径生成:模板库与LLM动态编排的配合
拿到结构化意图后,下一步是生成召回路径。这里有一个设计决策:是让LLM直接生成召回路径,还是用模板库匹配?
我两种都试过。纯LLM生成的问题是稳定性差,同样的意图可能生成不同的路径,而且偶尔会生成不存在的索引路径导致召回失败。纯模板库的问题是覆盖度不够,长尾意图匹配不到模板。
最终方案是模板库优先,LLM兜底。具体来说:
- 维护一个路径模板库,每个模板定义了“什么意图条件下触发”和“触发后走哪些召回路径”。比如意图里有
brand=Nike且category=篮球鞋,就触发“品牌品类联合索引”路径。 - 当意图无法匹配任何模板时,调用LLM生成路径。LLM的prompt里会包含当前可用的索引列表和路径示例,限制它只能生成已存在的路径。
- LLM生成的路径会被记录,如果某个路径被频繁生成且效果不错,就把它固化到模板库里。
这个方案的好处是兼顾了稳定性和覆盖度。模板库覆盖了约80%的高频意图,LLM兜底覆盖了剩下的20%长尾。而且随着时间推移,模板库会越来越丰富,LLM的调用比例会逐渐下降,整体延迟也会降低。
路径模板的设计我举几个实际例子:
# 模板示例:品牌+品类+价格区间 { "condition": {"brand": "not_null", "category": "not_null", "price_range": "not_null"}, "paths": [ {"index": "brand_category_price", "weight": 1.0}, {"index": "category_price_vector", "weight": 0.8}, {"index": "brand_vector", "weight": 0.6} ] } # 模板示例:纯视觉描述 { "condition": {"visual_desc": "not_null", "category": "not_null"}, "paths": [ {"index": "multimodal_vector", "weight": 1.0}, {"index": "category_attribute", "weight": 0.7} ] }每个路径的weight不是固定的,而是根据历史效果动态调整。我用的方法是在线学习:每次召回后,根据用户的点击和转化反馈,用bandit算法更新路径权重。这样系统能自动发现哪些路径在当前query分布下效果更好。
2.4 多路召回的执行与延迟控制
多路召回的执行是工程上最棘手的部分。假设一个query触发了5条召回路径,每条路径返回100个结果,那总共就是500个结果需要去重和排序。如果每条路径的延迟是50ms,串行执行就是250ms,加上排序和网络传输,总延迟可能超过500ms。这在交易搜索场景里是不可接受的。
我的优化策略是并行执行+动态超时+结果缓存三件套。
并行执行是最直接的。5条路径同时发起,总延迟取决于最慢的那条。但这里有个问题:不同路径的延迟差异很大。倒排索引可能只要10ms,向量索引可能要80ms,图索引可能要150ms。如果等最慢的路径返回,总延迟还是很高。
所以需要动态超时。我给每条路径设置一个基础超时时间,比如100ms。如果某条路径在100ms内没返回,就直接放弃它的结果,用其他路径的结果兜底。同时,系统会记录每条路径的历史延迟分布,如果某条路径经常超时,就自动降低它的权重或者优化它的索引。
结果缓存是最后的保障。热门query的召回结果直接缓存,TTL设置成5分钟。得物交易搜索的query分布是典型的幂律分布,Top1%的query占了约40%的流量。把这部分query的召回结果缓存起来,能大幅降低平均延迟。
实操心得:缓存key的设计很关键。不要直接用原始query做key,因为“aj1 低帮 女款”和“aj1低帮女款”会被当成两个不同的query。我的做法是用Query理解层输出的结构化意图做key,这样语义相同的query会命中同一个缓存。
3. 多模态能力在生成式召回中的落地
3.1 为什么交易搜索必须做多模态
得物这类平台的核心商品是鞋服,用户搜索行为里有一个非常显著的特征:大量query包含视觉描述。我统计过,约30%的query里有颜色词(“樱花粉”“蒂芙尼绿”),约15%的query里有材质词(“麂皮”“漆皮”),约10%的query里有鞋型描述(“老爹鞋”“板鞋”“厚底”)。
传统向量检索处理这些query的方式是:把文本编码成向量,把商品图片用CLIP编码成向量,然后做跨模态对齐。这个方案的问题在于,CLIP的跨模态对齐是粗粒度的。它能理解“这是一双鞋”,但很难区分“樱花粉”和“蜜桃粉”的细微差异。而在交易场景里,颜色差一点,用户就不买。
生成式召回的多模态能力体现在两个层面。第一个层面是理解:LLM可以直接“看”用户上传的图片或者query里的视觉描述,生成精确的视觉属性标签。比如用户搜“那个绿底的鞋”,LLM可以输出{visual_desc: “绿色鞋底”, category: “运动鞋”}。第二个层面是生成:基于视觉属性,LLM可以生成多模态召回路径,比如“绿色鞋底+运动鞋”的图文联合索引。
我实测过一组数据:在包含视觉描述的query上,引入多模态生成式召回后,点击率提升了约27%,转化率提升了约15%。这个收益远高于纯文本生成式召回的提升幅度,说明多模态能力在交易搜索里是刚需。
3.2 多模态意图解析的工程实现
多模态意图解析的输入有两种:一种是纯文本query里的视觉描述,另一种是用户直接上传的图片。两种输入的处理链路不同。
对于文本里的视觉描述,处理相对简单。在Query理解层的LLM prompt里,我会加入一个专门的视觉属性抽取指令。比如:
请从以下query中抽取视觉属性,输出JSON格式: query: “有没有那种绿底的老爹鞋,厚底的” 输出: {"visual_desc": "绿色鞋底, 厚底", "category": "老爹鞋"}这个指令经过微调后,准确率能到85%以上。剩下的15%主要是歧义情况,比如“绿底”可能指鞋底是绿色,也可能指鞋面是绿色。这种歧义我目前的处理方式是保留多种解释,生成多条召回路径,让后续的排序模型去决定哪个解释更合理。
对于用户上传的图片,处理链路要复杂一些。首先需要用视觉编码器提取图片特征,然后把特征输入到一个多模态LLM里,让它生成结构化的视觉描述。这里的关键是视觉描述的粒度。太粗了没用(“一双鞋”),太细了噪声大(“鞋带孔有7个”)。我最终定下来的粒度是:颜色(主色+辅色)、材质、鞋型、特殊设计元素(如logo位置、鞋底纹路)。
这个粒度是通过人工评估确定的。我找了10个标注员,让他们对同一批商品图片用不同粒度描述,然后看哪种粒度的描述最能帮助其他标注员找到对应商品。结果显示,上述粒度下的描述,找货准确率最高。
注意:多模态LLM的推理延迟比纯文本LLM高不少。如果每张图片都走多模态LLM,延迟会扛不住。我的优化是两级处理:先用一个轻量级视觉模型做粗分类,如果图片属于高频品类(如运动鞋),直接走预置的视觉属性模板;只有长尾品类才走多模态LLM。
3.3 图文联合索引的构建与召回
多模态意图解析完之后,需要有一个图文联合索引来支撑召回。这个索引的构建是离线完成的,但设计思路直接影响召回效果。
传统做法是把图片向量和文本向量分别建索引,召回时分别查询然后融合。这个方案的问题是图文对齐是松散的,图片向量和文本向量在同一个空间里但不对齐。我的做法是构建图文联合索引:每个商品有一个联合表示,包含文本属性(标题、类目、品牌)和视觉属性(颜色、材质、鞋型)。召回时,query的文本意图和视觉意图同时匹配这个联合表示。
具体实现上,我用的是多字段索引+加权匹配的方案。每个商品在索引里有多个字段:text_fields(标题、类目)、visual_fields(颜色、材质、鞋型)、vector_field(CLIP向量)。召回时,根据query的意图类型决定哪些字段参与匹配以及权重。
比如query是“绿底老爹鞋”,意图是{visual_desc: “绿色鞋底”, category: “老爹鞋”}。那召回时,visual_fields里的“绿色鞋底”匹配权重设为1.0,text_fields里的“老爹鞋”匹配权重设为0.8,vector_field的权重设为0.5。这样既能保证视觉属性的精确匹配,又能利用向量召回兜底。
这个方案的召回准确率比纯向量方案提升了约30%。代价是索引构建更复杂,需要离线把商品的视觉属性抽取出来。但我觉得这个投入是值得的,因为交易搜索里视觉属性的匹配精度直接决定转化率。
3.4 多模态融合排序的特征工程
多路召回之后,融合排序层需要处理多模态特征。这里的核心问题是:如何把文本匹配分数、视觉匹配分数、向量相似度分数融合成一个最终分数?
简单的加权求和不行,因为不同分数的尺度不一样。文本匹配分数可能是0-10,视觉匹配分数可能是0-1,向量相似度可能是-1到1。直接加权会导致某个分数主导排序结果。
我的方案是分位数归一化+Learning to Rank。先把每种分数转换成历史分布里的分位数,这样所有分数都变成0-1之间的均匀分布。然后把归一化后的分数作为特征输入LTR模型,让模型自己学习融合权重。
LTR模型的特征包括:
| 特征名 | 说明 | 来源 |
|---|---|---|
| text_match_score | 文本匹配分位数 | 倒排索引 |
| visual_match_score | 视觉匹配分位数 | 多模态索引 |
| vector_sim_score | 向量相似度分位数 | 向量索引 |
| path_weight | 召回路径权重 | 路径生成层 |
| category_match | 品类是否匹配 | 意图解析层 |
| price_match | 价格是否在区间内 | 意图解析层 |
| click_history | 商品历史点击率 | 离线统计 |
| conversion_history | 商品历史转化率 | 离线统计 |
这个LTR模型我用的是一棵LambdaMART,特征维度约50维,训练数据是过去7天的query-商品-点击日志。模型每天更新一次,保证能跟上query分布的变化。
实操心得:LTR模型的特征里,
path_weight这个特征很重要但容易被忽略。它能让模型知道“这个商品来自哪条召回路径”,从而学习到不同路径的可靠性差异。我实测发现,加入这个特征后,排序的NDCG提升了约5%。
4. 实操落地中的关键问题与排查
4.1 LLM推理延迟的优化实录
生成式召回最大的工程挑战就是延迟。我刚开始做的时候,Query理解层的LLM推理延迟高达300ms,加上后面的召回和排序,总延迟超过800ms。这个延迟在交易搜索场景里是灾难性的,用户等1秒就会流失。
我用了四步优化把延迟压到了可接受范围。
第一步是模型量化。把7B模型从FP16量化到INT8,推理延迟直接降了约40%。量化后的模型在意图解析任务上的准确率只掉了约1.5个百分点,完全可以接受。后来我又试了INT4量化,延迟再降20%,但准确率掉了约5个百分点,就放弃了。
第二步是推理引擎优化。从原生PyTorch切换到ONNX Runtime,再开启算子融合和内存复用,延迟又降了约25%。这里有个坑:ONNX导出时要注意动态shape的处理,否则batch size变化时会有额外的重编译开销。
第三步是请求合并。交易搜索的query是高频并发的,单个请求单独推理很浪费。我把100ms窗口内的query合并成一个batch,一次性推理。这样GPU利用率从30%提升到了70%,平均延迟降了约30%。
第四步是缓存。Query理解层的输出(结构化意图)直接缓存,key是原始query的归一化形式。热门query的意图解析直接走缓存,延迟从几十毫秒降到几毫秒。
四步下来,Query理解层的平均延迟从300ms压到了约45ms。加上召回和排序,总延迟控制在200ms以内,达到了上线标准。
4.2 意图解析错误的典型case与修复
意图解析错误是生成式召回最常见的bad case。我整理了实际项目里遇到的典型错误和修复方法。
Case 1:品牌识别错误。用户搜“空军一号”,LLM解析成{brand: “空军”, category: “一号”}。修复方法是在微调数据里加入品牌别名词典,把“空军一号”映射到Nike Air Force 1。同时在后处理里加一层品牌校验,如果解析出的品牌不在品牌库里,就触发人工规则兜底。
Case 2:价格区间解析错误。用户搜“500以内的跑鞋”,LLM解析成{price_range: [0, 500]},这个没问题。但用户搜“500左右的跑鞋”,LLM也解析成{price_range: [0, 500]},这就错了。“左右”应该是一个窄区间,比如[400, 600]。修复方法是在意图Schema里区分“硬上限”和“软区间”,并在微调数据里加入这类模糊表达的标注。
Case 3:多意图混淆。用户搜“送男朋友的篮球鞋 不要黑色”,LLM解析成{category: “篮球鞋”, scene: “送男朋友”, attributes: [“黑色”]},把“不要黑色”理解成了“要黑色”。修复方法是在prompt里加入否定词处理指令,并在微调数据里加入否定表达的样本。
Case 4:视觉描述遗漏。用户搜“那个绿底的鞋”,LLM解析成{category: “鞋”},完全丢掉了“绿底”这个关键视觉描述。修复方法是在微调数据里加强视觉描述的标注权重,并在prompt里显式要求抽取视觉属性。
这些case的修复方法我整理成了一个bad case库,每次发现新的错误类型就加进去,然后定期用这个库做增量微调。这样模型的意图解析准确率从最初的60%逐步提升到了约88%。
4.3 多路召回结果冲突的融合策略
多路召回的结果经常冲突。比如一条路径召回的是“Nike篮球鞋”,另一条路径召回的是“Adidas篮球鞋”,两条路径的分数都很高。这时候怎么融合?
我的策略是分层融合。第一层是硬性条件过滤:如果query的意图里有明确的品牌或品类,那不符合条件的商品直接降权。第二层是分数归一化:把不同路径的分数转成分位数。第三层是LTR排序:用模型学习融合权重。
但这里有个特殊情况:当多条路径召回同一个商品时,这个商品的分数应该被提升。因为它被多条路径同时命中,说明它的相关性更强。我的做法是给这类商品加一个路径命中数特征,LTR模型会自动学习到这个特征的正向作用。
另一个特殊情况是路径之间的互斥。比如“价格区间”路径和“相似款向量”路径可能召回完全不同的商品集。这时候不能简单融合,而应该保留两条路径的多样性。我的做法是在最终排序时加入多样性约束,保证Top20结果里至少包含来自3条不同路径的商品。
注意:多样性约束的权重需要调。太强了会导致相关性下降,太弱了会导致结果同质化。我最终定下来的权重是0.3,在相关性和多样性之间取得了较好的平衡。
4.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 意图解析准确率低 | 微调数据不足或领域词汇覆盖不够 | 抽样检查bad case,统计错误类型分布 | 补充微调数据,加入领域词典 |
| 召回结果延迟高 | 某条路径超时或LLM推理慢 | 打点统计每条路径的延迟分布 | 动态超时,模型量化,请求合并 |
| 多路召回结果重复率高 | 路径之间重叠度大 | 统计路径间的Jaccard相似度 | 调整路径权重,增加多样性约束 |
| 视觉描述匹配不准 | 多模态索引粒度太粗 | 人工评估视觉属性抽取准确率 | 细化视觉属性Schema,加强微调 |
| 缓存命中率低 | 缓存key设计不合理 | 统计缓存命中率和query分布 | 用结构化意图做key,归一化query |
| LTR模型效果差 | 特征覆盖不足或训练数据有偏 | 分析特征重要性和模型误差分布 | 补充特征,修正训练数据采样偏差 |
5. 效果评估与迭代方向
5.1 离线评估指标的选取
生成式召回的离线评估不能只看召回率。传统向量检索的评估指标是Recall@K和NDCG,但生成式召回还需要评估意图解析准确率和路径生成覆盖率。
我用的离线评估指标体系包括:
- 意图解析准确率:人工标注1000条query的意图,和模型输出对比,计算字段级准确率。
- 路径生成覆盖率:统计有多少query能匹配到模板库或成功生成LLM路径。
- 召回率:Top100结果里包含相关商品的比例。
- NDCG@20:考虑排序位置的召回质量。
- 多样性指标:Top20结果里不同路径来源的分布熵。
这些指标里,意图解析准确率是最关键的。因为它直接决定后续所有环节的效果上限。我每周都会抽样评估一次,确保模型没有退化。
5.2 在线AB实验的设计与解读
离线指标好不代表线上效果好。生成式召回上线前,我做了两周的AB实验。
实验组是生成式召回,对照组是传统向量召回。核心指标是点击率、转化率、人均GMV。辅助指标是延迟、超时率、缓存命中率。
实验结果:点击率提升约18%,转化率提升约12%,人均GMV提升约9%。延迟方面,P99延迟从180ms升到了220ms,但在可接受范围内。超时率控制在0.5%以下。
但AB实验也暴露了一些问题。长尾query的提升幅度远高于头部query。头部query(如“aj1”)的提升只有约5%,因为头部query的意图太明确,传统向量检索已经做得不错了。长尾query(如“有没有那种看起来很贵但其实不贵的鞋”)的提升高达约35%,因为这类query传统方案基本无能为力。
这个结果验证了我的判断:生成式召回的价值主要在长尾场景。所以后续的迭代重点应该放在长尾query的覆盖和优化上。
5.3 后续迭代的三个方向
第一个方向是意图Schema的动态扩展。目前的7个字段覆盖了90%的query,但剩下的10%长尾query里可能隐藏着新的意图维度。我计划用聚类+人工审核的方式,定期从bad case里发现新的意图维度,然后扩展Schema。
第二个方向是路径生成的自动化。目前模板库还是人工维护的,LLM兜底的比例约20%。我希望能通过强化学习让系统自动发现和固化高效路径,把LLM兜底比例降到10%以下。
第三个方向是多模态能力的深化。目前的多模态还停留在“颜色、材质、鞋型”这些显性属性上。下一步希望能理解更复杂的视觉语义,比如“复古风”“机能风”“老爹鞋的厚底程度”。这需要更细粒度的多模态微调数据。
这三个方向里,我觉得多模态深化的收益最大,但难度也最高。因为视觉语义的标注成本远高于文本,而且主观性更强。我目前的计划是先积累数据,等数据量够了再启动模型迭代。
最后分享一个小技巧:生成式召回的效果很依赖Query理解层的质量,但Query理解层的微调数据标注成本很高。我的做法是用线上点击日志做弱监督,把“用户搜了query A并点击了商品B”作为一条弱标注样本,用商品B的属性反推query A的意图。这个方法虽然噪声大,但能快速积累大量数据,再配合少量人工精标数据做校准,效果比纯人工标注好很多。