
先給大家看一條真實(shí)到不能再真實(shí)的搜索日志用戶在得物App里輸入“女朋友說想要那種很軟很舒服的拖鞋”然后退出重進(jìn)又搜了一次“可愛毛絨棉拖鞋”。如果你只做向量檢索第一條query基本就是廢的——它太口語、太冗長、太像聊天了。但用戶確實(shí)有明確購買意圖只是表達(dá)方式和商品標(biāo)題隔著十萬八千里。這其實(shí)就是我最近一直在琢磨的問題當(dāng)向量檢索的天花板就頂在那兒的時(shí)候交易搜索的召回還能怎么突破得物交易搜索團(tuán)隊(duì)給出的答案是不只在原來“查候選集”的框框里繼續(xù)卷而是直接用生成式模型把“候選”本身給造出來。這篇文章我就把這次從“向量召回”到“生成式召回”的范式轉(zhuǎn)換過程、關(guān)鍵設(shè)計(jì)、以及我們踩過的那些坑完整地拆開聊一聊。1. 向量檢索卷到邊際收益為負(fù)問題到底出在哪先說一個(gè)可能有點(diǎn)反常識的結(jié)論目前在電商搜索里向量召回早就不是那個(gè)“上了就能漲幾個(gè)點(diǎn)”的萬能藥了。相反在最成熟的類目上它已經(jīng)進(jìn)入了一個(gè)邊際收益遞減甚至為負(fù)的階段。1.1 用戶query遠(yuǎn)比我們想象的更“長”和“模糊”傳統(tǒng)向量檢索的流程大家都很熟query側(cè)編碼器把輸入query編碼成一個(gè)向量商品側(cè)編碼器把商品標(biāo)題、屬性、標(biāo)簽編碼成一個(gè)向量然后做ANN近鄰檢索。這套框架最舒服的區(qū)間是處理“詞面匹配不上但語義接近”的場景比如用戶搜“老爹鞋”商品標(biāo)題里寫的是“復(fù)古運(yùn)動(dòng)鞋”。但用戶一旦說出“給男朋友買的打籃球穿的鞋不要太貴但要有牌面”整個(gè)框架就開始難受了。這種query有三個(gè)特點(diǎn)讓雙塔難以下咽長度失衡訓(xùn)練時(shí)我們幾乎看不到那么長的query線上來了一個(gè)17個(gè)詞的句子embedding早就被壓扁變形了。意圖復(fù)合這句話里有“買給男友”、“打籃球”、“價(jià)格敏感”、“品牌調(diào)性”四個(gè)子意圖。向量只能把它壓成一個(gè)點(diǎn)信息全擠在一起哪個(gè)意圖都表達(dá)不充分??谡Z實(shí)體對不齊商品側(cè)是規(guī)范化的“籃球鞋 實(shí)戰(zhàn) 耐磨 中幫”query側(cè)是“打籃球穿的鞋”。字面完全對不上向量要硬學(xué)這種映射樣本永遠(yuǎn)不夠。用我自己的話說就是向量檢索擅長的是“模糊匹配”而交易搜索里越來越值錢的是“意圖理解”——這兩者之間有本質(zhì)差異。1.2 雙塔ANN的固有斷層你的召回上限被embedding質(zhì)量焊死了長期以來我們都把優(yōu)化焦點(diǎn)放在“如何訓(xùn)練更好的embedding”。今天加一個(gè)難負(fù)樣本明天換一個(gè)更強(qiáng)的底座模型后天把frozen tower換成端到端聯(lián)合訓(xùn)練。這確實(shí)有效但大家心里都清楚一件事召回結(jié)果的天花板由embedding空間的表達(dá)能力決定。這個(gè)天花板有多硬我舉個(gè)具體的例子。某個(gè)頭部品牌做了一款“聯(lián)名限定球鞋”標(biāo)題是“XX品牌 x 潮玩IP 聯(lián)名限量款 高幫板鞋”。它上線第一周搜索側(cè)曝光幾乎為零。因?yàn)樵趀mbedding空間里query“限量聯(lián)名鞋”和這雙鞋的向量距離并沒有顯著近于其他普通板鞋。向量模型學(xué)到的是“板鞋”這個(gè)相對泛化的語義至于“聯(lián)名”、“限定”、“潮玩IP”這些強(qiáng)交易屬性的詞在預(yù)訓(xùn)練和微調(diào)數(shù)據(jù)里都太稀疏了根本不足以把它和其他板鞋拉開距離。這就導(dǎo)致一個(gè)尷尬的局面你花大力氣把embedding訓(xùn)練得越來越好結(jié)果只是把同一批候選的排序做得更精細(xì)而候選本身從一開始就沒變過。召回的上限是“候選生成機(jī)制”決定的不是embedding決定的。想清楚這一點(diǎn)你會(huì)發(fā)現(xiàn)繼續(xù)卷向量檢索的性價(jià)比遠(yuǎn)沒有想象中那么高。1.3 向量召回的“語義”是近義不是“意圖推演”我再補(bǔ)一刀。很多人把向量召回叫“語義召回”這其實(shí)是個(gè)不小的誤會(huì)。向量模型做的是“表征相似度”它能把“跑鞋”“跑步鞋”拉得很近但它不會(huì)推理。什么叫推理用戶搜“晴天穿的鞋”和“雨天穿的鞋”向量模型大概率認(rèn)為這倆很相似——因?yàn)樽置胬锒加小按┑男?。但真?shí)世界的商品邏輯是晴天對應(yīng)板鞋、帆布鞋雨天對應(yīng)防滑短靴、雨鞋。向量模型不知道這些。它只知道字符和詞頻層面的共現(xiàn)規(guī)律而交易搜索里真正有價(jià)值的是常識與商品知識的組合推理。這也是我們決定嘗試生成式召回的起點(diǎn)與其繼續(xù)在“表征空間”里打轉(zhuǎn)不如讓模型直接“想”出符合用戶意圖的商品描述然后再用這套描述去檢索商品庫。這就是范式的躍遷——從“查候選”到“造候選”。2. 生成式召回不是在“多路召回”里再加一路很多團(tuán)隊(duì)一聽“生成式召回”就認(rèn)為哦就是用大模型改寫一下query或者擴(kuò)寫幾個(gè)同義詞然后丟給ES和向量檢索去查。這確實(shí)是生成式與大模型最常見的結(jié)合點(diǎn)但得物做這件事的姿勢不太一樣。2.1 從“查候選”到“造候選”召回邏輯的倒轉(zhuǎn)傳統(tǒng)召回不管多少路本質(zhì)都是“給定query去索引里查一個(gè)候選集合”。生成式召回的思路剛好倒過來給定query先用模型生成若干個(gè)“理想的商品側(cè)描述”再用這些描述作為檢索條件去找真實(shí)商品。舉個(gè)例子。用戶搜“見導(dǎo)師穿的正式一點(diǎn)的衣服”。傳統(tǒng)向量召回拿到這個(gè)query直接編碼去找距離近的商品效果可以想見。生成式召回這一步會(huì)先讓模型輸出中間產(chǎn)物比如商務(wù)襯衫 長袖 免燙 純色正裝西褲 男士 修身 垂感商務(wù)休閑皮鞋 黑色 真皮 軟底你發(fā)現(xiàn)問題沒有我們根本沒有在“相似性”層面做努力而是先把抽象的query翻譯成了具體的“商品語言”。這些生成出來的描述隨便哪一條拿去和商品庫做匹配都比原始query好使得多。這等于把老路子里的“編碼-比對”問題變成了一個(gè)“文本生成-精確匹配”問題。2.2 得物場景的生成目標(biāo)設(shè)計(jì)不能光“像人話”要“像商品標(biāo)題”既然要“造候選”那到底造什么形態(tài)的中間產(chǎn)物這個(gè)設(shè)計(jì)決策直接決定了項(xiàng)目的走向。我們對比過三條路線路線生成產(chǎn)物示例優(yōu)點(diǎn)缺點(diǎn)Query改寫“見導(dǎo)師的正式著裝”理解成本低仍然偏泛和商品側(cè)語言有g(shù)ap商品描述擴(kuò)寫“正式商務(wù)風(fēng)格男士襯衫”貼近商品側(cè)表達(dá)容易丟失多意圖覆蓋面窄多意圖拆分屬性補(bǔ)全多個(gè)候選描述每個(gè)帶具體屬性詞信息密度高召回精準(zhǔn)可并行對生成模型要求最高需要強(qiáng)約束我們最終選了第三條路。原因很簡單query里往往藏著不止一個(gè)需求而一個(gè)向量只能表達(dá)一個(gè)點(diǎn)。我上面那個(gè)“見導(dǎo)師”的例子模型只要能識別出“上裝、下裝、鞋履”這三個(gè)方向然后每個(gè)方向生成1-2個(gè)商品側(cè)描述整個(gè)候選集的結(jié)構(gòu)就和以前完全不同了。在實(shí)操層面我們讓生成模型輸出的不是一段自然語言而是一組“商品畫像元組”[類目詞, 屬性詞集合, 風(fēng)格詞, 性別/人群詞]。這樣做有兩個(gè)直接好處一是后接檢索模塊時(shí)解析成本極低不需要再對自由文本做切詞和NER二是約束了模型的輸出空間幻覺率明顯下降。生成的“畫風(fēng)”是否像商品標(biāo)題直接決定了后面能否召回好東西——這個(gè)細(xì)節(jié)我覺得是全文最值得強(qiáng)調(diào)的設(shè)計(jì)之一。2.3 生成式與向量檢索是互補(bǔ)關(guān)系不是替代關(guān)系這里必須澄清一個(gè)誤區(qū)生成式召回不是要把向量檢索干掉而是把向量檢索從“獨(dú)挑大梁”的位置上解放出來。我們在實(shí)踐中形成的分工是這樣的對于“字面上就能匹配”或者“embedding空間里表達(dá)得很清晰”的query向量檢索又快又準(zhǔn)沒必要換成生成式。對于“多意圖、口語化、知識依賴型”的長尾query生成式召回能構(gòu)造出一個(gè)向量檢索永遠(yuǎn)給不出的候選集合。所以最終線上架構(gòu)是先輕量分類把query分為“簡單意圖”和“復(fù)雜意圖”兩路。簡單意圖走傳統(tǒng)多路召回復(fù)雜意圖走生成式召回兩路結(jié)果在粗排階段合并由排序模型統(tǒng)一打分。從工程上看這不是“取代”而是“補(bǔ)位”。但恰好是這種補(bǔ)位把整個(gè)系統(tǒng)的召回邊界往外推了一大截。3. 交易搜索落地生成式召回的工程鏈路與關(guān)鍵拆解概念講明白了下面全是硬核的工程細(xì)節(jié)。這一章我按落地的時(shí)間順序來講從整體鏈路到每一環(huán)怎么做、為什么這么做。3.1 整體鏈路改寫-生成-校驗(yàn)-合并一步都不能省先看完整的數(shù)據(jù)流這是我們在線上穩(wěn)定運(yùn)行的單次召回全鏈路用戶query → 意圖識別與路由輕量模型30ms內(nèi)完成 → 生成式召回服務(wù)大模型生成商品畫像2-3個(gè)候選 → 結(jié)構(gòu)化校驗(yàn)類目映射、屬性歸一、非法詞過濾 → 多檢索器執(zhí)行ES短語匹配 向量召回 類目數(shù)據(jù)庫直查 → 候選合并與去重 → 粗排/精排你注意我沒有把“生成”這一步做完就送進(jìn)粗排中間強(qiáng)行加了一個(gè)結(jié)構(gòu)化校驗(yàn)層。這一層在demo階段大家都覺得是多余開銷但真實(shí)跑起來之后你會(huì)發(fā)現(xiàn)它是保命的一層。校驗(yàn)層主要干三件事類目映射模型輸出的是自由文本類目名比如“鞋靴”但庫里真實(shí)類目是“運(yùn)動(dòng)鞋-籃球鞋-高幫”需要一套映射關(guān)系把它歸一到真實(shí)葉子類目。屬性白名單過濾我們給生成模型一份“可被檢索的屬性詞表”比如顏色、材質(zhì)、款式、功能。模型輸出的詞如果不在白名單里直接丟棄。寧可少召回一點(diǎn)也不能讓模型自創(chuàng)一個(gè)庫里根本不存在的屬性。非法詞過濾品牌詞、敏感詞、競品詞都要在這里控一遍。說句實(shí)話這層校驗(yàn)我們一開始就沒打算省因?yàn)榇竽P蜕傻摹白杂啥取痹跈z索場景里就是一把雙刃劍。3.2 生成模型選型與線上延遲時(shí)延的賬要一筆一筆算生成式召回上線前團(tuán)隊(duì)內(nèi)部爭論最激烈的不是效果而是延遲。交易搜索的端到端延遲預(yù)算一般是在200ms左右召回到排序通常只能分到50-80ms。你讓一個(gè)大模型在線實(shí)時(shí)生成怎么算都超預(yù)算。我們的解法是分了兩步走。第一步離線全量生成緩存。對于高頻query我們離線跑一遍生成任務(wù)把所有生成結(jié)果落地到緩存表。線上直接查緩存命中率能做到70%以上耗時(shí)趨近于0。這一步把延遲問題解決了一大半。第二步在線輕量生成兜底。長尾query沒有緩存必須要實(shí)時(shí)生成。我們在這里沒有用動(dòng)輒幾十B的大模型而是部署了一個(gè)精簡的生成模型參數(shù)量控制在幾B以內(nèi)單次生成控制在150ms以內(nèi)。為了壓這個(gè)時(shí)延我們把解碼長度限制在64個(gè)token以內(nèi)、batch設(shè)置為1、并開了推理加速服務(wù)。實(shí)測下來全鏈路p99延遲只增加了18ms。你看看這個(gè)數(shù)字再想想生成式召回帶來的召回增量這筆賬其實(shí)相當(dāng)劃算。我們在評審時(shí)反復(fù)跟老板講了同一個(gè)觀點(diǎn)延遲增量本質(zhì)上是一次性的但召回邊界擴(kuò)展帶來的收益是持續(xù)性的。3.3 商品側(cè)的知識注入讓模型知道“庫存里到底有什么”一開始我們跑出來的生成結(jié)果有一個(gè)共性問題模型生成的東西很美但庫里沒有。比如用戶搜“復(fù)古跑鞋”模型生成“復(fù)古網(wǎng)面跑步鞋 元年配色 透氣”但庫里根本沒這批貨等于白生成。后來我們想明白了生成模型不能只在“query到商品語言”這個(gè)方向上訓(xùn)練還得把商品庫的分布知識灌進(jìn)去。具體做法是在訓(xùn)練階段做了一步“庫存感知約束”把高頻商品類目、常見屬性組合作為額外的結(jié)構(gòu)化Prompt輸入在解碼階段通過約束解碼constrained decoding把輸出限制為數(shù)據(jù)庫中真實(shí)出現(xiàn)過的類目詞和屬性詞組合訓(xùn)練數(shù)據(jù)里刻意加入“負(fù)例商品側(cè)描述”——這些描述語法正確、語義合理但庫里確實(shí)不存在——讓模型學(xué)會(huì)避坑。這步做完之后生成結(jié)果的“可檢索率”即生成結(jié)果能在庫中命中商品的比率從57%直接拉升到83%。我印象特別深刻的是之前模型特別愛生成“鴛鴦配色”這種詞庫里其實(shí)只有零星幾雙。自從注入了庫存感知約束這類“好看但沒貨”的生成明顯變少了。4. 效果驗(yàn)證與踩坑實(shí)錄哪些漲了、哪些白干、哪些翻車講完架構(gòu)和設(shè)計(jì)來聊點(diǎn)真實(shí)的。這部分我希望給同行們省下一些試錯(cuò)成本無論你是做搜索還是做推薦里面有幾條經(jīng)驗(yàn)是通用的。4.1 離線評測召回率只漲了3.4%但別急著下結(jié)論項(xiàng)目進(jìn)入評測階段第一版離線指標(biāo)出來的時(shí)候團(tuán)隊(duì)內(nèi)部反應(yīng)其實(shí)是分裂的。生成式召回疊加線上整體召回率漲了3.4個(gè)百分點(diǎn)。有同學(xué)覺得“就這”但負(fù)責(zé)搜索和商品的同學(xué)都很興奮。原因在于這3.4%的構(gòu)成很不均勻。我們把召回增益按query類型拆開看Query類型占比召回增益轉(zhuǎn)化增益品牌詞/型號詞42%0.3%0.1%品類詞31%2.1%1.4%長尾口語/多意圖詞27%9.6%6.2%看出來沒有增量幾乎全部來自長尾口語和多意圖query。這正好印證了我們的判斷在簡單query上繼續(xù)做向量檢索的精細(xì)優(yōu)化收益已經(jīng)很低了。而在復(fù)雜query上生成式召回幾乎是“憑空變出一個(gè)新候選集”。所以這里我建議所有準(zhǔn)備做類似項(xiàng)目的團(tuán)隊(duì)不要只看大盤召回率一定要按query難度分層去看。大盤不漲不代表方法無效很可能是你的基線在簡單query上太強(qiáng)了增量被稀釋了。4.2 線上A/B測試轉(zhuǎn)化率提升的來源根本不是你想的那個(gè)線上實(shí)驗(yàn)我們跑了三周實(shí)驗(yàn)組和對照組整體轉(zhuǎn)化率差值是4.7%。這個(gè)數(shù)字放到交易搜索場景里算是很可觀的。但讓我最意外的不是這個(gè)數(shù)字本身而是我們后來在分析時(shí)發(fā)現(xiàn)轉(zhuǎn)化率提升的最大來源不是新增成交而是無效曝光減少。什么意思生成式召回補(bǔ)進(jìn)來的候選因?yàn)楦N合用戶意圖所以粗排之后真正能進(jìn)入用戶視野的商品和用戶原本想要的越來越一致。用戶看到的商品越來越“對味”點(diǎn)進(jìn)去發(fā)現(xiàn)不是想要的、然后跳出的情況大幅減少平臺(tái)的搜索滿意度指標(biāo)隨之上漲。這么說吧那一批“搜見導(dǎo)師裝”被生成式召回精準(zhǔn)補(bǔ)上商務(wù)襯衫的用戶他們點(diǎn)進(jìn)去之后大概率會(huì)下單或者至少會(huì)深度瀏覽。而在老系統(tǒng)里他們可能翻了三頁都沒找到合意的東西然后帶著負(fù)反饋離開。好的召回不只是帶來成交還能減少用戶反復(fù)尋找的挫敗感。4.3 踩坑記錄三個(gè)翻車現(xiàn)場和最終解法任何項(xiàng)目都不可能一帆風(fēng)順這里分享三個(gè)我們真實(shí)踩過、且花了不小代價(jià)才填平的坑??右欢嘁鈭D拆分的“泛化災(zāi)難”。第一版生成模型上線后我們發(fā)現(xiàn)它對“泛意圖”query的處理有問題。比如用戶搜“禮物”模型一口氣生成了“口紅禮盒”“球鞋”“藍(lán)牙音箱”“鍵盤”十幾個(gè)方向的商品畫像。這看起來“很聰明”但實(shí)際檢索出來的商品五花八門排序模型根本不知道該給誰高分。后來我們在意圖拆分層加了一個(gè)頻次約束只有用戶query明確包含“送/給/買給”這類對象指示詞時(shí)才允許多意圖拆分否則默認(rèn)走單意圖主路徑??佣蓛?nèi)容的“自嗨”傾向。生成模型特別喜歡輸出“高顏值”“小眾設(shè)計(jì)”“炸街”這類在社區(qū)內(nèi)容里常見、但在商品檢索里毫無用處的形容詞。這些詞在商品庫的標(biāo)題和屬性里基本不出現(xiàn)。所以無論模型怎么生成檢索器都匹配不到。后面我們做了兩件事一是把生成模型的訓(xùn)練數(shù)據(jù)從“社區(qū)文案”替換為“商品/搜索共現(xiàn)語料”二是在解碼端直接把這些低信息詞加入“禁止輸出清單”。效果立竿見影無效生成率從41%降到了12%??尤龝r(shí)延基尼系數(shù)拉滿——緩存命中率在高峰期驟降。因?yàn)楦哳lquery離線緩存做得好我們把在線生成能力砍得很“薄”。結(jié)果一到晚上潮玩新品首發(fā)這類熱點(diǎn)時(shí)段大量全新query涌入緩存命中率直接往下掉在線生成服務(wù)被瞬間打到限流。這個(gè)問題本質(zhì)上不是模型問題是流量預(yù)估問題。解決方案分兩步短期先給在線生成服務(wù)擴(kuò)容并加了熱點(diǎn)Query的提前預(yù)生成長期則是把生成服務(wù)做成“按需彈性擴(kuò)縮容”和運(yùn)營側(cè)的營銷日歷聯(lián)動(dòng)。5. 生成式召回對搜索系統(tǒng)設(shè)計(jì)思路的改變值得被記住的三條經(jīng)驗(yàn)項(xiàng)目上線并穩(wěn)定運(yùn)行之后我自己的認(rèn)知也發(fā)生了一些變化。這些變化不是那種“項(xiàng)目復(fù)盤PPT里的總結(jié)”而是真真切切影響了我后續(xù)做系統(tǒng)設(shè)計(jì)時(shí)的判斷方式。5.1 兜底的不是模型是“約束和控制”做生成式召回項(xiàng)目之前我的第一反應(yīng)是“模型越強(qiáng)越好”。但經(jīng)歷過上面那些坑之后我現(xiàn)在的看法是在召回這個(gè)環(huán)節(jié)模型的“想象力”必須被刻意壓制。召回側(cè)的核心訴求不是“生成一個(gè)漂亮的句子”而是“生成一個(gè)能精準(zhǔn)命中庫內(nèi)商品的檢索條件”。你的約束條件建得越扎實(shí)你的召回結(jié)果就越穩(wěn)定。所以如果讓我給同行一個(gè)建議我會(huì)說在動(dòng)手調(diào)模型之前先去把商品庫的Schema、類目樹、屬性詞典徹底吃透。好模型的貢獻(xiàn)可能占30%剩下的70%全在“約束設(shè)計(jì)”和“資源組織方式”上。5.2 召回問題本質(zhì)上是“候選生成機(jī)制”的問題以前討論召回優(yōu)化大家最常問的是“你的閾值調(diào)到了多少”“難負(fù)樣本怎么挖的”“embedding維度換多大”。生成式召回上線之后我越來越覺得這些問題的層次都太低了。真正值得問的問題是你的系統(tǒng)為這個(gè)query準(zhǔn)備了哪些候選這些候選是從哪來的如果換一種候選生成機(jī)制結(jié)果會(huì)有什么不同向量檢索的貢獻(xiàn)在于“高效地從一個(gè)大池子里撈東西”它的瓶頸在于“只能撈池子里的”。而生成式召回提供的是一個(gè)完全不同的視野先理解用戶打算買什么再倒推出什么商品能滿足它最后才回庫里去撈。這兩個(gè)視角放在一起同類問題瞬間打開。5.3 別把“范式躍遷”想得太玄它就是一次很樸實(shí)的重新分工“范式躍遷”這個(gè)詞最近被用得有點(diǎn)爛大街好像不換一套模型架構(gòu)就不配叫躍遷。但就我們在得物交易搜索的實(shí)踐來看真正的躍遷反而是很樸實(shí)的你知道原來那套東西的邊界在哪了你也看到了一條繞開這個(gè)邊界的路你愿意投入資源把它走通。沒有什么一蹴而就的魔法模型有的只是幾十個(gè)版本的結(jié)構(gòu)化約束、數(shù)以萬計(jì)的badcase分析、和一次次延遲優(yōu)化。技術(shù)在往前走但真正有價(jià)值的不是名詞本身而是那些在約束條件下做出的真金白銀的取舍。生成式召回的下一步我們還在探索多模態(tài)方向的擴(kuò)展以及如何讓生成結(jié)果與排序模型更深度地聯(lián)動(dòng)。這些如果后面有新的進(jìn)展和踩坑我會(huì)再出來同步。