背景与问题
- 现有粗排/重排依赖 `knn_query` 和 `image_knn_query` 分数,但这两路分数来自 ANN 召回,并非所有进入 rerank_window (160) 的文档都同时命中文本和图片向量召回,导致部分文档得分为 0,影响融合公式的稳定性。
- 简单扩大 ANN 的 k 无法保证 lexical 召回带来的文档也包含两路向量分;二次查询或拉回向量本地计算均有额外开销且实现复杂。
解决方案
采用 ES rescore 机制,在第一次搜索的 `window_size` 内对每个文档执行精确的向量 script_score,并将分数以 named query 形式附加到 `matched_queries` 中,供后续 coarse/rerank 优先使用。
**设计决策**:
- **只补分,不改排序**:rescore 使用 `score_mode: total` 且 `rescore_query_weight: 0.0`,原始 `_score` 保持不变,避免干扰现有排序逻辑,风险最小。
- **精确分数命名**:`exact_text_knn_query` 和 `exact_image_knn_query`,便于客户端识别和回退。
- **可配置**:通过 `exact_knn_rescore_enabled` 开关和 `exact_knn_rescore_window` 控制窗口大小,默认 160。
技术实现细节
1. 配置扩展 (`config/config.yaml`, `config/loader.py`)
```yaml
exact_knn_rescore_enabled: true
exact_knn_rescore_window: 160
```
新增配置项并注入到 `RerankConfig`。
2. Searcher 构建 rescore 查询 (`search/searcher.py`)
- 在 `_build_es_search_request` 中,当 `enable_rerank=True` 且配置开启时,构造 rescore 对象:
- `window_size` = `exact_knn_rescore_window`
- `query` 为一个 `bool` 查询,内嵌两个 `script_score` 子查询,分别计算文本和图片向量的点积相似度:
```painless
// exact_text_knn_query
(dotProduct(params.query_vector, 'title_embedding') + 1.0) / 2.0
// exact_image_knn_query
(dotProduct(params.image_query_vector, 'image_embedding.vector') + 1.0) / 2.0
```
- 每个 `script_score` 都设置 `_name` 为对应的 named query。
- 注意:当前实现的脚本分数**尚未乘以 knn_text_boost / knn_image_boost**,保持与原始 ANN 分数尺度对齐的后续待办。
3. RerankClient 优先读取 exact 分数 (`search/rerank_client.py`)
- 在 `_extract_coarse_signals` 中,从文档的 `matched_queries` 里读取 `exact_text_knn_query` 和 `exact_image_knn_query` 分数。
- 若存在且值有效,则用作 `text_knn_score` / `image_knn_score`,并标记 `text_knn_source='exact_text_knn_query'`。
- 若不存在,则回退到原有的 `knn_query` / `image_knn_query` (ANN 分数)。
- 同时保留原始 ANN 分数到 `approx_text_knn_score` / `approx_image_knn_score` 供调试对比。
4. 调试信息增强
- `debug_info.per_result[*].ranking_funnel.coarse_rank.signals` 中输出 exact 分数、回退分数及来源标记,便于线上观察覆盖率和数值分布。
验证结果
- 通过单元测试 `tests/test_rerank_client.py` 和 `tests/test_search_rerank_window.py`,验证 exact 优先级、配置解析及 ES 请求体结构。
- 线上真实查询采样(6 个 query,top160)显示:
- **exact 覆盖率达到 100%**(文本和图片均有分),解决了原 ANN 部分缺失的问题。
- 但 exact 分数与原始 ANN 分数存在量级差异(ANN/exact 中位数比值约 4.1 倍),原因是 exact 脚本未乘 boost 因子。
- 当前排名影响:粗排 top10 重叠度最低降至 1/10,最大排名漂移超过 100。
后续计划
1. 对齐 exact 分与 ANN 分的尺度:在 script_score 中乘以 `knn_text_boost` / `knn_image_boost`,并对长查询额外乘 1.4。
2. 重新评估 top10 重叠度和漂移,若收敛则可将 coarse 融合公式整体迁移至 ES rescore 阶段。
3. 当前版本保持“只补分不改排序”的安全策略,已解决核心的分数缺失问题。
涉及文件
- `config/config.yaml`
- `config/loader.py`
- `search/searcher.py`
- `search/rerank_client.py`
- `tests/test_rerank_client.py`
- `tests/test_search_rerank_window.py`