15 Apr, 2026

2 commits

  •  修改内容
    
    1. **新增配置项** (`config/config.yaml`)
       - `exact_knn_rescore_enabled`: 是否开启精确向量重打分,默认 true
       - `exact_knn_rescore_window`: 重打分窗口大小,默认 160(与 rerank_window 解耦,可独立配置)
    
    2. **ES 查询层改造** (`search/searcher.py`)
       - 在第一次 ES 搜索中,根据配置为 window_size 内的文档注入 rescore 阶段
       - rescore_query 中包含两个 named script_score 子句:
         - `exact_text_knn_query`: 对文本向量执行精确点积
         - `exact_image_knn_query`: 对图片向量执行精确点积
       - 当前采用 `score_mode=total` 且 `rescore_query_weight=0.0`,**只补分不改排序**,exact 分仅出现在 `matched_queries` 中
    
    3. **统一向量得分 Boost 逻辑** (`search/es_query_builder.py`)
       - 新增 `_get_knn_plan()` 方法,集中管理文本/图片 KNN 的 boost 计算规则
       - 支持长查询(token 数超过阈值)时文本 boost 额外乘 1.4 倍
       - 精确 rescore 与 ANN 召回**共用同一套 boost 规则**,确保分数量纲一致
       - 原有 ANN 查询构建逻辑同步迁移至该统一入口
    
    4. **融合阶段得分优先级调整** (`search/rerank_client.py`)
       - `_build_hit_signal_bundle()` 中统一处理向量得分读取
       - 优先从 `matched_queries` 读取 `exact_text_knn_query` / `exact_image_knn_query`
       - 若不存在则回退到原 `knn_query` / `image_knn_query`(ANN 得分)
       - 覆盖 coarse_rank、fine_rank、rerank 三个阶段,避免重复补丁
    
    5. **测试覆盖**
       - `tests/test_es_query_builder.py`: 验证 ANN 与 exact 共用 boost 规则
       - `tests/test_search_rerank_window.py`: 验证 rescore 窗口及 named query 正确注入
       - `tests/test_rerank_client.py`: 验证 exact 优先、回退 ANN 的逻辑
    
     技术细节
    
    - **精确向量计算脚本** (Painless)
      ```painless
      // 文本 (dotProduct + 1.0) / 2.0
      (dotProduct(params.query_vector, 'title_embedding') + 1.0) / 2.0
      // 图片同理,字段为 'image_embedding.vector'
      ```
      乘以统一的 boost(来自配置 `knn_text_boost` / `knn_image_boost` 及长查询放大因子)。
    
    - **named query 保留机制**
      - 主查询中已开启 `include_named_queries_score: true`
      - rescore 阶段命名的脚本得分会合并到每个 hit 的 `matched_queries` 中
      - 通过 `_extract_named_score()` 按名称提取,与原始 ANN 得分访问方式完全一致
    
    - **性能影响** (基于 top160、6 条真实查询、warm-up 后 3 轮平均)
      - `elasticsearch_search_primary` 耗时: 124.71ms → 136.60ms (+11.89ms, +9.53%)
      - `total_search` 受其他组件抖动影响较大,不作为主要参考
      - 该开销在可接受范围内,未出现超时或资源瓶颈
    
     配置示例
    
    ```yaml
    search:
      exact_knn_rescore_enabled: true
      exact_knn_rescore_window: 160
      knn_text_boost: 4.0
      knn_image_boost: 4.0
      long_query_token_threshold: 8
      long_query_text_boost_factor: 1.4
    ```
    
     已知问题与后续计划
    
    - 当前版本经过调参实验发现,开启 exact rescore 后部分 query(强类型约束 + 多风格/颜色相似)的主指标相比 baseline(exact=false)下降约 0.031(0.6009 → 0.5697)
    - 根因:exact 将 KNN 从稀疏辅助信号变为 dense 排序因子,coarse 阶段排序语义变化,单纯调整现有 `knn_bias/exponent` 无法完全恢复
    - 后续迭代方向:**coarse 阶段暂不强制使用 exact**,仅 fine/rerank 优先 exact;或 coarse 采用“ANN 优先,exact 只补缺失”策略,再重新评估
    
     相关文件
    
    - `config/config.yaml`
    - `search/searcher.py`
    - `search/es_query_builder.py`
    - `search/rerank_client.py`
    - `tests/test_es_query_builder.py`
    - `tests/test_search_rerank_window.py`
    - `tests/test_rerank_client.py`
    - `scripts/evaluation/exact_rescore_coarse_tuning_round2.json` (调参实验记录)
    tangwang
     
  •  背景与问题
    - 现有粗排/重排依赖 `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`
    tangwang
     

14 Apr, 2026

1 commit


01 Apr, 2026

1 commit


27 Mar, 2026

2 commits


25 Mar, 2026

1 commit

  • @config/dictionaries/style_intent_color.csv
    @config/dictionaries/style_intent_size.csv @query/style_intent.py
    @search/sku_intent_selector.py
    1. 两个csv词典,分为三列,
    - 英文关键词
    - 中文关键词
    - 标准属性名称词
    三列都可以允许逗号分割。补充的第三列使用在商品属性中,使用的是标准的英文名称
    2.
    判断意图的时候,中文词用中文翻译名去匹配,如果不存在中文翻译名,则用原始
    query,英文词同理
    3. SKU 选择的时候,用每一个 SKU 的属性名去匹配。
    匹配规则要大幅度简化,并做性能优化:
    1)文本匹配规则只需要看规范化后的属性值是否包含了词典配置的第三列"标准属性名称词",如果包含了,则认为匹配成功。
    找到第一个匹配成功的即可。如果都没有成功,后面也不再需要用向量匹配。
    暂时废弃向量匹配、双向匹配等复杂逻辑。
    tangwang
     

24 Mar, 2026

4 commits

  • 加了一个过滤/降权词典,query中有独立分词匹配到指定的触发词,将过滤带某些分词的商品(比如fitted/修身,过滤宽松、loose、relaxed、baggy,slouchy等商品)
    2. reranker的query使用翻译后的
    tangwang
     
  • 上面一版实现,性能上完全无法接受。因此进行了一轮策略简化
    
    style_sku_prepare_hits阶段耗时太长。请根据需求,思考优化的方法,给出性能优化的方案。
    1.
    _select_by_embedding,有缓存吗,option_value的值是有限的,之前已经算过的,就不用再算了。不仅仅是embedding相似的结果,整个option_value的匹配结果,是有包含、还是没包含,相似度多少,都不用重新计算。比如之前已经有一个sku的某个属性值叫做“卡其色”,已经算出来是否文本匹配了,那么不需要再去做文本匹配。如果已经算出来向量的相似度,那么不需要再去取向量以及计算相似度。
    2. 匹配可以适当的优化:
    匹配流程简化:
    1)找到第一个文本匹配的,如果有直接匹配成功。不需要考虑匹配多个的情况。
    2)如果全部都没有匹配,那么进行embedding筛选。
    
    匹配规则:
    option_name的匹配,直接看规范化后的option_name是不是意图维度的泛化词之一(比如颜色、color、colour),如果没有匹配的,现在应该是把所有维度都算上,这样匹配成本和比较成本太高了,去掉这些逻辑,这种情况不需要加后缀、不需要选择sku。
    ption_value的匹配。意图检测的时候,有匹配的query中的命中的词,这个词被包含在属性值中,那么就算匹配。属性值被包含在query(包括翻译文本)中,也算匹配。提高匹配的覆盖率。
    
    3.
    这一阶段得到sku选择的结果即可(选中的sku的id,也可以为空值表示没找到匹配成功的,这种情况不需要拼接title后缀给重排输入),但是不用着急做image_url的替换和sku的置顶。等最后填充的时候判断有选中sku的时候直接做替换和置顶即可。
    请你思考如何进行设计,提高性能的时候不带来复杂度的提升,可以适当的重构以降低修改后的代码行数。
    @search/sku_intent_selector.py @query/style_intent.py
    tangwang
     
  • tangwang
     
  • The backend now exposes a structured debug_info that is much closer to
    the real ranking pipeline:
    
    query_analysis now includes index_languages, query_tokens, query-vector
    summary, translation/enrichment plan, and translation debug.
    query_build now explains the ES recall plan: base-language clause,
    translated clauses, filters vs post-filters, KNN settings,
    function-score config, and related inputs.
    es_request distinguishes the logical DSL from the actual body sent to
    ES, including rerank prefetch _source.
    es_response now includes the initial ES ranking window stats used for
    score interpretation.
    rerank now includes execution state, templates, rendered rerank query
    text, window/top_n, service/meta, and the fusion formula.
    pagination now shows rerank-window fetch vs requested page plus
    page-fill details.
    For each result in debug_info.per_result, ranking debug is now much
    richer:
    
    initial rank and final rank
    raw ES score
    es_score_normalized = raw score / initial ES window max
    es_score_norm = min-max normalization over the initial ES window
    explicit normalization notes explaining that fusion does not directly
    consume an ES-normalized score
    rerank input details: doc template, title suffix, template field values,
    doc preview/length
    fusion breakdown: rerank_factor, text_factor, knn_factor, constants, raw
    inputs, final fused score
    text subcomponents: source/translation/weighted/primary/support/fallback
    evidence via matched_queries
    richer style-intent SKU debug, including selected SKU summary and intent
    texts
    tangwang
     

23 Mar, 2026

1 commit


22 Mar, 2026

1 commit


20 Mar, 2026

2 commits

  • tangwang
     
  • ResultFormatter.format_search_results() runs.
    
    What changed:
    
    For each final paginated SPU hit, the searcher now scans
    skus[].option1_value against the query text set built from the original
    query, normalized query, rewritten query, and translations.
    If no option1_value matches textually, it falls back to embedding
    similarity and picks the SKU with the highest inner product against the
    query embedding.
    The matched SKU is promoted to the front of the SPU’s skus list.
    The SPU-level image_url is replaced with that matched SKU’s image_src.
    I left api/result_formatter.py unchanged because it already preserves
    the SKU order and reads image_url from _source; updating the page hits
    in searcher makes the formatter return the desired result automatically.
    
    Verification:
    
    ReadLints on the edited files: no errors
    Passed targeted tests:
    pytest tests/test_search_rerank_window.py -k "translated_query or
    no_direct_option_match"
    tangwang
     

18 Mar, 2026

1 commit

  • 核心改动在 rerank_client.py (line 99):fuse_scores_and_resort 现在按
    rerank * knn * text 的平滑乘法公式计算,优先从 hit["matched_queries"]
    里取 base_query 和 knn_query,并把 _text_score / _knn_score
    一并写回调试字段。为了让 KNN 也有名字,我给 top-level knn 加了 name:
    "knn_query",见 es_query_builder.py (line 273)。搜索执行时会在 rerank
    窗口内打开 include_named_queries_score,并在显式排序时加上
    track_scores,见 searcher.py (line 400) 和 es_client.py (line 224)。
    tangwang
     

13 Mar, 2026

1 commit


12 Mar, 2026

2 commits