面试知识库

M3.6 · 商品路径接入 cross-encoder 精排 —— 开发文档(面试向)#

讲为什么这么做、踩了哪个坑,不讲代码。是 M3.5(召回 hybrid 升级)之后的一个小而完整的补腿。

一句话概括#

项目技术栈一直写着「BGE-Reranker 精排」,但这条精排腿之前只接在品类知识库(category_insight), 商品检索(item_search)是裸召回直出——hybrid 粗排完就交给 item_picker,中间缺了 cross-encoder。 这一轮把已经造好的 RerankerClient 接进商品路径,让商品检索也走完整的「召回→精排」。

1. 为什么要补:召回和精排是两种不同的「看」#

hybrid 召回(dense+稀疏)本质是双塔各算各的——query 编码成一个向量、商品编码成另一个,最后比相似度, 两者之间没有交叉特征。所以粗排池里常夹着「相关但跑题」的货:查「旅行收纳包」混进一个只命中 「旅行」二字的洗漱包。

cross-encoder 精排不一样:它把 query 和候选拼进同一个模型一起判相关性,能看到词与词的交叉,更准—— 但也更慢,所以只能对粗排出来的几十条做,不能对全库做。召回管「广」、精排管「准」,各司其职。 这是 检索系统的标准两段式,商品路径之前缺了后半段。

2. 关键设计:funnel 怎么搭#

做成一个漏斗:粗召回 top_k × N(默认 3 倍)条进精排池 → cross-encoder 重排 → 截回 top_k。 粗召回多捞一些,给精排留「把埋在 top_k 边缘的好货捞上来」的空间——这正是 funnel 比「召回完直接截」强的地方。

两个容易做错、特意想清楚的点:

  • truncated 信号看「返回数」不看「池子」。 截断信号是给下游判断「要不要换更窄的 query」用的, 语义是「还有没有比我返回的 top_k 更多的货」。所以它必须比 capped_k(实际返回数),不能比 pool_k (精排池大小)——否则池子一放大,本该报「还有更多」的场景会被误判成「就这些」。

  • 复用同一个 RerankerClient,不另造。category_insight 用的是同一个 get_reranker() 单例 (远程 /rerank + 本地回退)。同质复用,不给商品路径搞一套专用精排。

3. 踩的坑:别让「测试 stub」去动真实排序#

接完一跑功能验收,立刻发现不对:配置里没有 RERANKER_ENDPOINT,精排退化到本地 Jaccard 回退, 而这个回退把商品排序搞乱了

刨根问底,这个本地回退本就是个 stub——它在 reranker 模块里写得很清楚:存在的意义是「没有真精排 服务时,让调用链、短路逻辑在离线/CI 下照样测得通」,不是真精排。它按 query×候选的字符/词 token 重叠 (Jaccard)打分。问题来了:

  • 跨语言时它完全空转:中文 query 切成单字 {旅,行,收,纳,包},候选标题却是英文 {sweet, style, bubble, bag…}——两边零重叠,所有候选都得 0 分。
  • 同语言时它是粗糙覆盖:Jaccard 只看字面词重叠,会拿这个糙信号去盖掉已经不错的 hybrid 排序, 反而可能把语义相关但用词不同的好货压下去。

所以正确做法是:只有配了真 cross-encoder(reranker.remote 为真)才重排;没有真模型就直接输出 hybrid 序, 不让一个测试 stub 去扰动生产级排序。改完再验,离线下商品检索干净地直出 hybrid 序(实测「旅行收纳包」top3 全是 travel/storage 包),配上真 /rerank 端点精排才接管。

面试怎么讲:这个坑的价值不在「写对一行 if」,在于想清楚一个组件存在的目的边界。本地回退是为「链路 可测」而生的 stub,不是为「生产排序」而生的。把它无差别地用在真实排序上,就是用错了工具——跨语言空转、 同语言帮倒忙。识别出「这是 stub、不该让它动真序」,比这一行 if reranker.remote 本身重要。

4. 交付与边界#

交付:item_search 走完整「hybrid 召回 → cross-encoder 精排 → top_k」;复用现成 RerankerClient; 新增重排单测(假 reranker 验证输出顺序确由精排分决定);自动门(ruff/format/mypy/pytest 123)全绿; 真实索引端到端验证(离线直出 hybrid、配端点则精排接管)。

诚实的边界:

  • 真精排要配 RERANKER_ENDPOINT(现成 BGE-Reranker-v2-m3 的 HTTP 服务)。离线/CI/未配端点时 不重排、直出 hybrid 序——这是有意为之,不是降级 bug。
  • 精排池倍数 RERANK_POOL_MULT 暂用默认 3,最优值要有召回金标(ESCI)才能调——和融合权重、 BGE-M3 sparse 升级一样,都等 ground truth 落地再精调,没金标盲调没意义。
  • 个性化只在 dense 召回通路(fuse(query,user)),精排目前只用 query×候选,不掺个性化——保持 「精排判相关性、个性化交给召回与 item_picker」的分工。