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
     

02 Apr, 2026

1 commit

  • 目前在54训练数据里面,拆分44条train + 10
    test,训练集显著提升但是test上不及基线
    作为基础设施保留,以后可以考虑扩大数据集进行使用
    tangwang
     

01 Apr, 2026

1 commit


30 Mar, 2026

2 commits

  • tangwang
     
  • …nt.py)、[search/searcher.py](/data/saas-search/search/searcher.py)、[frontend/static/js/app.js](/data/saas-search/frontend/static/js/app.js)
    以及
    [tests/test_rerank_client.py](/data/saas-search/tests/test_rerank_client.py)。
    
    主要修复内容如下:
    - 精排现依据融合阶段得分进行排序,而非仅依据原始的 `fine_score`。
    - 最终重排不再依赖独立的 `fine_scores`
      数组(该数组在精排排序后可能产生同步偏差),而是直接读取命中结果附带的
    `_fine_score`。
    -
    精排与最终重排现均通过同一计算路径生成融合调试信息,该路径同时也决定实际排序结果,从而保证记录逻辑与生产逻辑保持一致。
    -
    调试信息载荷更加清晰:精排和最终重排阶段都会暴露融合输入/因子以及规范的
    `fusion_summary`,前端界面现在会渲染该摘要信息。
    
    主要问题:阶段逻辑重复且存在并行的数据通道:一个通道用于计算排序,另一个通道用于组装调试字段,还有第三个通道用于传递辅助数组。这造成了潜在的差异风险。本次重构通过将阶段得分作为唯一事实来源,并让调试/前端直接消费其输出而非事后重构,降低了该风险。
    
    验证结果:
    - `./.venv/bin/python -m pytest -q tests/test_rerank_client.py
      tests/test_search_rerank_window.py`
    - `./.venv/bin/python -m py_compile search/rerank_client.py
      search/searcher.py`
    
    结果:`22 passed`。
    
    当前的主流程:
    
    1. Query 解析
    2. ES 召回
    3. 粗排:只用 ES 内部文本/KNN 信号
    4. 款式 SKU 选择 + title suffix
    5. 精排:轻量 reranker + 文本/KNN 融合
    6. 最终 rerank:重 reranker + fine score + 文本/KNN 融合
    7. 分页、补全字段、格式化返回
    
    主控代码在 [searcher.py](/data/saas-search/search/searcher.py),打分与
    rerank 细节在
    [rerank_client.py](/data/saas-search/search/rerank_client.py),配置定义在
    [schema.py](/data/saas-search/config/schema.py) 和
    [config.yaml](/data/saas-search/config/config.yaml)。
    
    **先看入口怎么决定走哪条路**
    在 [searcher.py:348](/data/saas-search/search/searcher.py#L348)
    开始,`search()` 先读租户语言、开关、窗口大小。
    关键判断在 [searcher.py:364](/data/saas-search/search/searcher.py#L364)
    到 [searcher.py:372](/data/saas-search/search/searcher.py#L372):
    
    - `rerank_window` 现在是 80,见
      [config.yaml:256](/data/saas-search/config/config.yaml#L256)
    - `coarse_rank.input_window` 是 700,`output_window` 是 240,见
      [config.yaml:231](/data/saas-search/config/config.yaml#L231)
    - `fine_rank.input_window` 是 240,`output_window` 是 80,见
      [config.yaml:245](/data/saas-search/config/config.yaml#L245)
    
    所以如果请求满足 `from_ + size <= rerank_window`,就进入完整漏斗:
    - ES 实际取前 `700`
    - 粗排后留 `240`
    - 精排后留 `80`
    - 最终 rerank 也只处理这 `80`
    - 最后再做分页切片
    
    如果请求页超出 80,就不走后面的多阶段漏斗,直接按 ES 原逻辑返回。
    tangwang
     

27 Mar, 2026

3 commits


26 Mar, 2026

1 commit


25 Mar, 2026

1 commit


24 Mar, 2026

3 commits

  • 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


18 Mar, 2026

2 commits

  • tangwang
     
  • 核心改动在 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

2 commits


12 Mar, 2026

1 commit


07 Mar, 2026

2 commits


05 Feb, 2026

2 commits

  • tangwang
     
  • - API:新增请求参数 ai_search,开启时在窗口内走重排流程
    - 配置:RerankConfig 移除 enabled/expression/description,仅保留 rerank_window 及
      service_url/timeout_sec/weight_es/weight_ai;默认超时 15s
    - 重排流程:ai_search 且 from+size<=rerank_window 时,ES 取前 rerank_window 条,
      调用外部 /rerank 服务,融合 ES 与重排分数后按 from/size 分页;否则不重排
    - search/rerank_client:新增模块,封装 build_docs、call_rerank_service、
      fuse_scores_and_resort、run_rerank;超时单独捕获并简短日志
    - search/searcher:移除 RerankEngine,enable_rerank=ai_search,使用 config.rerank 参数
    - 删除 search/rerank_engine.py(本地表达式重排),统一为外部服务一种实现
    - 文档:搜索 API 对接指南补充 ai_search 与 relevance_score 说明
    - 测试:conftest 中 rerank 配置改为新结构
    
    Co-authored-by: Cursor <cursoragent@cursor.com>
    tangwang