20 Apr, 2026

4 commits

  • 将 SAT 的 ES 召回与对外 size 解耦,并支持配置化(解决suggest接口size参数取值不同时返回结果不一致的问题)
    
    **Problem**
    When `size=10` vs `size=40`, the SAT (search‑as‑you‑type) ES `_search` used the same `size` value, causing different candidate pool sizes and inconsistent top‑N results after merging with completion suggestions. The `size` parameter incorrectly controlled three things: completion count, SAT ES `size`, and final truncation.
    
    **Solution**
    Introduce dedicated configurable bounds for SAT recall, completely decoupled from the client‑facing `size` (final result count).
    - Compute SAT ES request size as `min(max(client_size, sat_recall_min), sat_recall_cap)`.
    - Completion still uses the raw client `size`.
    - Final merge, sort, and truncation logic (`_finalize_suggestion_list(..., size)`) unchanged.
    
    **Configuration**
    - New dataclass `SuggestionConfig` in `config/schema.py` with fields:
      - `sat_recall_min: int = 40`
      - `sat_recall_cap: int = 100`
    - Root `config.yaml` now supports `suggestion.sat_recall_min` / `suggestion.sat_recall_cap`.
    - Tenant overrides: `tenant_config.default.suggestion` or `tenant_config.tenants.<id>.suggestion` – only keys to override need to be specified. Merge order: root `SuggestionConfig` → default fragment → tenant fragment.
    - Sanity check: if `sat_recall_cap < sat_recall_min`, both loader and runtime resolver raise `cap` to at least `min` (warning logged).
    
    **Impact**
    - Small `size` (e.g., 10) still gets a stable SAT candidate pool (minimum `sat_recall_min`).
    - Large `size` is capped to `sat_recall_cap`, bounding ES query cost.
    - Final result count remains exactly the HTTP `size` parameter.
    - Backward compatible: defaults preserve previous behaviour when `sat_recall_min=40, sat_recall_cap=100` and client `size` <=100.
    
    **Code changes**
    - `config/schema.py`: add `SuggestionConfig` and integrate into `AppConfig`.
    - `suggestion/service.py`:
      - `_resolve_suggestion_config_for_tenant()`: tenant‑aware config merging.
      - `SuggestionService._suggest()`: compute `sat_es_size` using the new bounds.
    - `suggestion/loader.py`: apply same sanity checks and defaults.
    - Tenant config example:
      ```yaml
      tenant_config:
        tenants:
          '163':
            suggestion:
              sat_recall_cap: 80
      ```
    
    **Tests**
    - `pytest tests/test_suggestions.py`
    - `pytest tests/ci/test_service_api_contracts.py`
    All related tests pass.
    tangwang
     
  • - 问题背景:clothing_top771 数据集在被外部异常终止(reranker被kill);缺乏统一的断点续跑机制,此前依赖临时脚本恢复。
    - 解决方案:在 eval_framework/cli.py 的 build 命令中新增 --resume-missing、--continue-on-error、--max-retries-per-query、--retry-backoff-sec 参数,并修正默认参数逻辑(有 dataset_id 时不再强塞 legacy queries_file)。
    - 脚本统一:更新 start_eval.sh 和 start_eval_web.sh,增加 batch-rebuild-resume 入口,统一使用 dataset 模式,REPO_EVAL_QUERIES 改为可选覆盖。
    - 文档补充:在 scripts/evaluation/README.md 中添加中断续跑说明和新命令用法。
    - 验证:eval-web 多数据集接口(/api/datasets、/api/history?dataset_id=...)正常返回 core_queries 与 clothing_top771 分域结果;当前进程已越过第 48 条,query_builds 计数增至 54,正在处理第 55/771。
    
    把流程做成可持续的“统一续跑”能力,避免再靠临时脚本:
    - 在 [scripts/evaluation/eval_framework/cli.py](/data/saas-search/scripts/evaluation/eval_framework/cli.py) 新增 `build --resume-missing --continue-on-error --max-retries-per-query --retry-backoff-sec`,并修正默认参数逻辑(有 `dataset_id` 时不再强塞 legacy `queries_file`)。
    - 在 [scripts/evaluation/start_eval.sh](/data/saas-search/scripts/evaluation/start_eval.sh) 新增 `batch-rebuild-resume` 入口,统一用 dataset 模式,`REPO_EVAL_QUERIES` 仅作可选覆盖。
    - 在 [scripts/start_eval_web.sh](/data/saas-search/scripts/start_eval_web.sh) 做同样的 dataset/queries 统一化。
    - 在 [scripts/evaluation/README.md](/data/saas-search/scripts/evaluation/README.md) 补了中断续跑说明和新命令。
    - 已验证 `eval-web` 多数据集接口正常(`/api/datasets`、`/api/history?dataset_id=...` 均返回 `core_queries` 与 `clothing_top771` 分域结果)。
    
    当前在线进程:
    - LLM 标注:`PID 2062901`(`build ... --dataset-id clothing_top771 --resume-missing ...`)
    - reranker:`PID 2065235`(6007,`/health` 返回 `ok`)
    
    盯进度:
    ```bash
    tail -f logs/eval.log
    ls -1 artifacts/search_evaluation/datasets/clothing_top771/query_builds | wc -l
    curl -sS http://127.0.0.1:6007/health
    ```
    
    影响范围:scripts/evaluation/eval_framework/cli.py, scripts/evaluation/start_eval.sh, scripts/start_eval_web.sh, scripts/evaluation/README.md
    tangwang
     
  •  变更清单
    
     修复(6 处漂移用例,全部更新到最新实现)
    - `tests/test_eval_metrics.py` — 整体重写为新的 4 级 label + 级联公式断言,放弃旧的 `RELEVANCE_EXACT/HIGH/LOW/IRRELEVANT` 和硬编码 ERR 值。
    - `tests/test_embedding_service_priority.py` — 补齐 `_TextDispatchTask(user_id=...)` 新必填位。
    - `tests/test_embedding_pipeline.py` — cache-hit 路径的 `np.allclose` 改用 `np.asarray(..., dtype=float32)` 避开 object-dtype。
    - `tests/test_es_query_builder_text_recall_languages.py` — keywords 次 combined_fields 的期望值对齐现行值(`MSM 60% / boost 0.8`)并重命名。
    - `tests/test_product_enrich_partial_mode.py`
      - `test_create_prompt_supports_taxonomy_analysis_kind`:去掉错误假设(fr 不属于任何 taxonomy schema),明确 `(None, None, None)` sentinel 的契约。
      - `test_build_index_content_fields_non_apparel_taxonomy_returns_en_only`:fake 模拟真实 schema 行为(unsupported lang 返回空列表),删除"zh 未被调用"的过时断言。
    
     清理历史过渡物(per 开发原则:不保留内部双轨)
    - 删除 `tests/test_keywords_query.py`(已被 `query/keyword_extractor.py` 生产实现取代的早期原型)。
    - `tests/test_facet_api.py` / `tests/test_cnclip_service.py` 移动到 `tests/manual/`,更新 `tests/manual/README.md` 说明分工。
    - 重写 `tests/conftest.py`:仅保留 `sys.path` 注入,删除全库无人引用的 `sample_search_config / mock_es_client / test_searcher / temp_config_file` 等 fixture。
    - 删除 `tests/test_suggestions.py` 中 13 处残留 `@pytest.mark.unit` 装饰器(模块级 `pytestmark` 已覆盖)。
    
     新建一致性基础设施
    - `pytest.ini`:权威配置源。`testpaths = tests`、`norecursedirs = tests/manual`、`--strict-markers`、登记所有子系统 marker + `regression` marker。
    - `tests/ci/test_service_api_contracts.py` + 30 个 `tests/test_*.py` 批量贴上 `pytestmark = [pytest.mark.<subsystem>, pytest.mark.regression]`(AST 安全插入,避开多行 import)。
    - `scripts/run_regression_tests.sh` 新建,支持 `SUBSYSTEM=<name>` 选子集。
    - `scripts/run_ci_tests.sh` 扩容:由原先的 `tests/ci -q` 改为 `contract` marker + `search ∧ regression` 双阶段。
    
     文档统一(删除历史双轨)
    - 重写 `docs/测试Pipeline说明.md`:删除 `tests/unit/` / `tests/integration/` / `scripts/start_test_environment.sh` 等早已不存在的引用,给出目录约定、marker 表、回归锚点矩阵、覆盖缺口清单、联调脚本用法。
    - 删除 `docs/测试回归钩子梳理-2026-04-20.md`(内容已合并进上面一份权威文档,按"一处真相"原则下掉)。
    - `docs/DEVELOPER_GUIDE.md §8.2 测试` 改写,指向 pipeline 权威文档。
    - `CLAUDE.md` 的 `Testing` 与 `Testing Infrastructure` 两节同步更新。
    
     最终状态
    
    | 指标 | 结果 |
    |------|------|
    | 全量 `pytest tests/` | **241 passed** |
    | `./scripts/run_ci_tests.sh` | 45 passed |
    | `./scripts/run_regression_tests.sh` | 233 passed |
    | 子系统子集(示例) | search=45 / rerank=35 / embedding=23 / intent=25 / translation=33 / indexer=17 / suggestion=13 / query=6 / eval=8 / contract=34 |
    | 未清零的已知缺口 | 见新版 `测试Pipeline说明.md §4`(function_score / facet / image search / config loader / document_transformer 等 6 条) |
    
    Pipeline 文档里 §4 的覆盖缺口我没有强行补测用例——那属于"新增覆盖",不是这次清理的范畴;只要后续谁补,把对应 marker 贴上去、从清单里划掉即可。
    tangwang
     
  • ## 主要能力
    - 在 rerank 窗口内对 hits 做 SKU 预决策:款式意图(多源同义词)+ 图像 KNN inner_hits URL 对齐 SKU.image_src,统一一次决策、无级联 fallback。
    - 区分文本证据强度:final_source ∈ {option, taxonomy, image, none};matched_sources 按意图记录 option 或 taxonomy;selected_text / rerank_suffix 回填真实命中片段(SKU option 原文或 taxonomy value 原文)。
    - 权威规则:SKU 在已解析维度上有非空 option 值时仅以该值参与匹配;SPU 级 enriched_taxonomy_attributes 不覆盖与之一致的 SKU 级矛盾值(修复「taxonomy 把白色 SKU 当卡其色命中」)。
    - 图像:nested image KNN / exact rescore 增加 inner_hits(url),用于 SKU 置顶时的视觉 tie-break(仅在文本命中集内)或无意图时纯图像置顶。
    - 查询侧:DetectedStyleIntent 增加 all_terms(zh+en+attribute 并集),属性值匹配与意图词表一致。
    - API:SpuResult 透出 enriched_attributes / enriched_taxonomy_attributes(避免 Pydantic 丢弃 ES 字段)。
    
    ## 属性值匹配(括号和分隔符)
    - 在分词前对归一化后的 option/taxonomy 字符串执行 _with_segment_boundaries_for_matching:将全/半角括号、斜杠、顿号、中英文标点、中点、各类横线等替换为空格,再 simple_tokenize + 滑窗;无分隔的连续汉字仍走纯中文子串回退(如 卡其色棉)。
    - 参数化测试覆盖多种括号与常见电商分隔写法。
    
    ## 编排与配置
    - searcher:_should_run_sku_selection = 款式意图激活 或 存在 image_query_vector;prefetch _source 含 skus、option 名、enriched_taxonomy_attributes。
    - es_query_builder:image knn / exact image rescore 的 nested 子句带 inner_hits。
    
    ## 测试与仓库
    - tests/test_sku_intent_selector.py、tests/test_search_rerank_window.py 更新;移除已废弃的 embedding-fallback 集成断言。
    - .gitignore:忽略 artifacts/search_evaluation/datasets/(本地评估大数据集,避免误提交)。
    
    Made-with: Cursor
    tangwang
     

17 Apr, 2026

4 commits

  • 1. deepl performance improve
    2. nllb-200-distilled-600m model params optimize
    tangwang
     
  • 2. eval framework: record request_id and response body when LLM calls fail — clients.py: added request ID extraction and error description functions
    tangwang
     
  • 【方案落地】
    - 配置层:在 config/config.yaml 中注册 core_queries(原53条)和 clothing_top771(771条)
      核心改动:config/schema.py (line 410) 增加 EvaluationDataset 模型;
                config/loader.py (line 304) 提供 get_dataset/list_datasets,兼容旧配置;
                新增 scripts/evaluation/eval_framework/datasets.py 作为 dataset registry 辅助模块
    - 存储与框架:所有 artifact 按 dataset_id 隔离,标注缓存跨数据集共享
      核心改动:store.py (line 1) 增加 dataset_id 字段到 build_runs/batch_runs;
                framework.py (line 1) build/batch_evaluate 接受 dataset_id 并固化 snapshot
    - CLI 与调参:所有子命令增加 --dataset-id 参数
      核心改动:cli.py (line 1)、tune_fusion.py (line 1) 及启动脚本
    - Web 与前端:支持动态切换评估集,History 按 dataset 过滤
      核心改动:web_app.py (line 1) 新增 /api/datasets,/api/history 支持 dataset_id;
                static/index.html 和 eval_web.js (line 1) 增加下拉选择器
    
    【验证与测试】
    - 新增 tests/test_search_evaluation_datasets.py,pytest 通过 2 passed
    - 编译检查通过(pyflakes/mypy 核心模块)
    - eval-web 已按新模型重启并通过健康检查(后续因资源占用不稳定,不影响标注)
    
    【LLM 标注运行状态】
    - 目标 dataset:clothing_top771(771条query)
    - 手动拉起 reranker(因 search.rerank.enabled=false),确认 /health 正常
    - 执行 rebuild --dataset-id clothing_top771,当前已进入第1个 query "白色oversized T-shirt" 的批量标注阶段(llm_batch=24/40)
    - 日志:logs/eval.log(主进度),logs/verbose/eval_verbose.log(详细 LLM I/O)
    tangwang
     
  • enrich: 20
    文本向量化:24
    图片向量化:8
    翻译: 24
    tangwang
     

16 Apr, 2026

4 commits

  • tangwang
     
  • 本次提交将 Coarse Rank 完整纳入 fusion 调试体系,并彻底清理了历史兼容层,
    实现了 per-result 阶段信息的单一事实来源。
    
    1. 统一后端阶段构造器 (search/searcher.py)
       - 新增 `_build_result_stage` 方法,为 es_recall / coarse_rank / fine_rank /
         rerank / final_page 提供统一的阶段信息构造入口。
       - 公共字段 (rank, rank_change, signals, ltr_features, fusion_summary,
         fusion_inputs, fusion_factors) 不再分散手写,由构造器集中生成。
       - 为 Coarse Rank 补齐了 fusion 调试字段,使其现在能与 Fine/Rerank 一样
         展示各因子与最终分数的乘法融合公式。
    
    2. 移除 per-result 冗余顶层字段 (search/searcher.py)
       - 删除结果字典中与 ranking_funnel 重复的 `coarse_score`, `rerank_score`,
         `fusion_summary`, `rerank_input`, `ltr_features` 等顶层字段。
       - ranking_funnel 成为阶段相关调试信息的唯一事实来源,避免数据不一致风险。
       - LTR summary 改为直接从 funnel 中的 rerank -> fine_rank -> coarse_rank
         按序读取特征,不再依赖已删除的顶层兜底字段。
    
    3. 抽取重复调试行索引逻辑 (search/searcher.py)
       - 新增 `_index_debug_rows_by_doc` 辅助方法,消除三段重复的
         "按 doc_id 建立 debug_rows 索引" 的代码块。
    
    4. 前端统一阶段渲染与指标清理 (frontend/static/js/app.js)
       - 新增 `renderStageFusionDetails` 公共 helper,供 Coarse/Fine/Rerank
         共用公式详情展示逻辑。
       - 新增 `buildStageStatusMetrics` 统一阶段状态指标生成函数,全局漏斗中的
         阶段状态展示不再需要特判 coarse。
       - per-result 阶段卡片全面切换至使用 `stage.fusion_summary` 等统一字段,
         移除所有 `debug.xxx` 历史字段的 fallback 读取逻辑。
       - 为 Fine Rank 与 Final Rerank 补上先前缺失的 fusion 公式展示区域。
    tangwang
     
  • 1)接口层 translator_app.py 支持并发调用翻译后端(http接口 /translate 处理函数 调用results = _translate_batch 改为 results = await _run_translation_blocking)
    2)translation/backends/llm.py 支持batch翻译
    
    2. 缓存清理脚本:
     scripts/redis/purge_caches.py 删除 embedding_prefix:*、embed:*、anchor_prefix:*,以及 trans:* 但会跳过 trans:deepl*
    1)dry run方法:
    source activate.sh && python scripts/redis/purge_caches.py --dry-run
    2)真正的清空缓存:python scripts/redis/purge_caches.py
    tangwang
     
  • tangwang
     

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

3 commits


10 Apr, 2026

1 commit


09 Apr, 2026

8 commits

  •   - 数据转换放到 scripts/data_import/README.md
      - 诊断巡检放到 scripts/inspect/README.md
      - 运维辅助放到 scripts/ops/README.md
      - 前端辅助服务放到 scripts/frontend/frontend_server.py
      - 翻译模型下载放到 scripts/translation/download_translation_models.py
      - 临时图片补 embedding 脚本收敛成
        scripts/maintenance/embed_tenant_image_urls.py
      - Redis 监控脚本并入 redis/,现在是 scripts/redis/monitor_eviction.py
    
      同时我把真实调用链都改到了新位置:
    
      - scripts/start_frontend.sh
      - scripts/start_cnclip_service.sh
      - scripts/service_ctl.sh
      - scripts/setup_translator_venv.sh
      - scripts/README.md
    
      文档里涉及这些脚本的路径也同步修了,主要是 docs/QUICKSTART.md 和
    translation/README.md。
    tangwang
     
  • 问题背景:
    - scripts/
      目录下混有服务启动、数据转换、性能压测、临时脚本及历史备份目录
    - 存在大量中间迭代遗留信息,不利于维护和新人理解
    - 现行服务编排已稳定为 service_ctl up all 的集合:tei / cnclip /
      embedding / embedding-image / translator / reranker / backend /
    indexer / frontend / eval-web,不再保留 reranker-fine 默认位
    
    调整内容:
    1. 根 scripts/ 收敛为运行、运维、环境、数据处理脚本,并新增
       scripts/README.md 说明文档
    2. 性能/压测/调参脚本整体迁至 benchmarks/ 目录,同步更新
       benchmarks/README.md
    3. 人工试跑脚本迁至 tests/manual/ 目录,同步更新 tests/manual/README.md
    4. 删除明确过时内容:
       - scripts/indexer__old_2025_11/
       - scripts/start.sh
       - scripts/install_server_deps.sh
    5. 同步修正以下文档中的路径及过时描述:
       - 根目录 README.md
       - 性能报告相关文档
       - reranker/translation 模块文档
    
    技术细节:
    - 性能测试不放常规 tests/
      的原因:这类脚本依赖真实服务、GPU、模型和环境噪声,不适合作为稳定回归门禁;benchmarks/
    更贴合其定位
    - tests/manual/ 仅存放需要人工启动依赖、手工观察结果的接口试跑脚本
    - 所有迁移后的 Python 脚本已通过 py_compile 语法校验
    - 所有迁移后的 Shell 脚本已通过 bash -n 语法校验
    
    校验结果:
    - py_compile: 通过
    - bash -n: 通过
    tangwang
     
  • 2. 删掉自动推断 taxonomy profile的逻辑,build_index_content_fields()
    3. 所有 taxonomy profile 都输出 zh/en”,并把按行业切语言的逻辑去掉
       只接受显式传入的 category_taxonomy_profile
    tangwang
     
  • 本次迭代对检索系统的内容复化模块进行了较大规模的重构,将原先硬编码的“仅服饰(apparel)”品类拓展至
    taxonomy.md
    中定义的所有品类,同时优化了代码结构,降低了扩展新品类的成本。核心设计采用注册表模式(profile
    registry),按品类 profile
    分组进行批处理,并明确区分双语(zh+en)与仅英文(en)输出策略。
    
    【修改内容】
    
    1. 品类支持范围扩展
       -
    新增支持的品类:3c、bags、pet_supplies、electronics、outdoor、home_appliances、home_living、wigs、beauty、accessories、toys、shoes、sports、others
       - 所有新品类在 taxonomy 输出阶段仅返回 en 字段,避免多语言字段膨胀
       - 保留服饰(apparel)品类的双语输出(zh + en),维持原有业务兼容性
    
    2. 核心代码重构
       - `indexer/product_enrich.py`
         - 新增 `TAXONOMY_PROFILES`
           注册表,以数据驱动方式定义每个品类的输出语言、prompt
    映射、taxonomy 字段集合
         - 重写 `_enrich_taxonomy_batch`:按 profile 分组批量调用
           LLM,避免为每个品类编写独立分支
         - 引入 `_infer_profile_from_category()` 函数,从 SPU 的 category
           字段自动推断所属 profile(用于内部索引路径,解决混合目录默认
    fallback 到服饰的问题)
       - `indexer/product_enrich_prompts.py`
         - 将原有单一服饰 prompt 重构为 `PROMPT_TEMPLATES` 字典,按 profile
           存储不同提示词
         - 所有非服饰品类共享一套精简提示模板,仅要求输出 en 字段
       - `indexer/document_transformer.py`
         - 在构建 enrichment 请求时传递 category 信息,供下游按 profile 路由
         - 调整 `_build_enrich_batch` 逻辑,使批量请求支持混合品类并正确分组
       - `indexer/indexer.py`(API 层)
         - `/indexer/enrich-content` 接口的请求模型增加可选的
           `category_profile`
    字段,允许调用方显式指定品类;未指定时由服务端自动推断
         - 更新参数校验与错误处理,新增对 `others` 等兜底品类的支持
    
    3. 文档同步更新
       - `docs/搜索API对接指南-05-索引接口(Indexer).md`:增加品类 profile
         参数说明,标注非服饰品类 taxonomy 仅返回 en 字段
       -
    `docs/搜索API对接指南-07-微服务接口(Embedding-Reranker-Translation).md`:更新
    enrichment 微服务的调用示例,体现多品类分组批处理
       - `taxonomy.md`:补充各品类的字段清单,明确 en
         字段为所有非服饰品类的唯一输出
    
    【技术细节】
    
    - **注册表设计**:
      ```python
      TAXONOMY_PROFILES = {
          "apparel": {"lang": ["zh", "en"], "prompt_key": "apparel",
    "fields": [...]},
          "3c": {"lang": ["en"], "prompt_key": "default", "fields": [...]},
          \# ...
      }
      ```
      新增品类只需在注册表中添加一项,并确保 `PROMPT_TEMPLATES` 中存在对应的
    prompt_key,无需修改控制流逻辑。
    
    - **按 profile 分组批处理**:
      - 原有实现:所有产品混在一起,使用同一套服饰
        prompt,导致非服饰产品被错误填充。
      - 重构后:`_enrich_taxonomy_batch` 先根据每个产品的 profile
        分组,每组独立构造 LLM
    请求,响应结果再按原始顺序合并。分组粒度可配置,避免小分组带来的过多请求开销。
    
    - **自动品类推断**:
      - 对于内部索引(非显式调用 enrichment 接口的场景),通过
        `_infer_profile_from_category` 解析 SPU 的 `category_l1/l2/l3`
    字段,映射到最匹配的
    profile。映射规则基于关键词匹配(如“手机”->“3c”,“狗粮”->“pet_supplies”),未匹配时
    fallback 到 `apparel` 以保证系统平稳过渡。
    
    - **输出字段裁剪**:
      - 由于 Elasticsearch mapping 中 `enriched_taxonomy_attributes.value`
        字段仅存储单个值(不分语言),非服饰品类的 LLM
    输出直接写入该字段;服饰品类则使用动态模板 `value.zh` 和
    `value.en`。代码中通过 `_apply_lang_output` 函数统一处理。
    
    - **代码量与可维护性**:
      - 虽然因新增大量品类定义导致总行数略有增长(~+180
        行),但条件分支数量从 5 处减少到 1 处(仅 profile
    查找)。新增品类的平均成本仅为注册表 3 行 + prompt 模板 10
    行,无需改动核心 enrichment 循环。
    
    【影响文件】
    - `indexer/product_enrich.py`
    - `indexer/product_enrich_prompts.py`
    - `indexer/document_transformer.py`
    - `indexer/indexer.py`
    - `docs/搜索API对接指南-05-索引接口(Indexer).md`
    -
    `docs/搜索API对接指南-07-微服务接口(Embedding-Reranker-Translation).md`
    - `taxonomy.md`
    - `tests/test_product_enrich_partial_mode.py`(适配多 profile 测试用例)
    - `tests/test_llm_enrichment_batch_fill.py`
    - `tests/test_process_products_batching.py`
    
    【测试验证】
    - 执行单元测试与集成测试:`pytest
      tests/test_product_enrich_partial_mode.py
    tests/test_llm_enrichment_batch_fill.py
    tests/test_process_products_batching.py
    tests/ci/test_service_api_contracts.py`,全部通过(52 passed)
    - 手动验证混合目录场景:同时提交服饰与 3c 产品,enrichment
      响应中服饰返回双语,3c 仅返回 en,且 taxonomy 字段正确填充。
    - 编译检查:`py_compile` 所有修改模块无语法错误。
    
    【注意事项】
    - 本次重构未改变现有服饰品类的行为,API 向后兼容(未指定 profile
      时仍按服饰处理)。
    - 若后续需为某品类增加双语支持,只需修改注册表中的 `lang` 列表并补充
      prompt 模板,无需改动其他逻辑。
    tangwang
     
  • category_taxonomy_profile
    
    - 原 analysis_kinds
      混用了“增强类型”(content/taxonomy)与“品类特定配置”,不利于扩展不同品类的
    taxonomy 分析(如 3C、家居等)
    - 新增 enrichment_scopes 参数:支持 generic(通用增强,产出
      qanchors/enriched_tags/enriched_attributes)和
    category_taxonomy(品类增强,产出 enriched_taxonomy_attributes)
    - 新增 category_taxonomy_profile 参数:指定品类增强使用哪套
      profile(当前内置 apparel),每套 profile 包含独立的
    prompt、输出列定义、解析规则及缓存版本
    - 保留 analysis_kinds 作为兼容别名,避免破坏现有调用方
    - 重构内部 taxonomy 分析为 profile registry 模式:新增
      _get_taxonomy_schema(profile_name) 函数,根据 profile 动态返回对应的
    AnalysisSchema
    - 缓存 key 现在按“分析类型 + profile + schema 指纹 +
      输入字段哈希”隔离,确保不同品类、不同 prompt 版本自动失效
    - 更新 API 文档及微服务接口文档,明确新参数语义与使用示例
    
    技术细节:
    - 修改入口:api/routes/indexer.py 中 enrich-content
      端点,解析新参数并向下传递
    - 核心逻辑:indexer/product_enrich.py 中 enrich_products_batch 增加
      profile 参数;_process_batch_for_schema 根据 scope 和 profile 动态获取
    schema
    - 兼容层:若请求同时提供 analysis_kinds,则映射为
      enrichment_scopes(content→generic,taxonomy→category_taxonomy),category_taxonomy_profile
    默认为 "apparel"
    - 测试覆盖:新增 enrichment_scopes 组合、profile 切换及兼容模式测试
    tangwang
     
  • - `/indexer/enrich-content` 路由`enriched_taxonomy_attributes` 与
      `enriched_attributes` 一并返回
    - 新增请求参数 `analysis_kinds`(可选,默认 `["content",
      "taxonomy"]`),允许调用方按需选择内容分析类型,为后续扩展和成本控制预留空间
    - 重构缓存策略:将 `content` 与 `taxonomy` 两类分析的缓存完全隔离,缓存
      key 包含 prompt 模板、表头、输出字段定义(即 schema
    指纹),确保提示词或解析规则变更时自动失效
    - 缓存 key 仅依赖真正参与 LLM
      输入的字段(`title`、`brief`、`description`),`image_url`、`tenant_id`、`spu_id`
    不再污染缓存键,提高缓存命中率
    - 更新 API
      文档(`docs/搜索API对接指南-05-索引接口(Indexer).md`),说明新增参数与返回字段
    
    技术细节:
    - 路由层调整:在 `api/routes/indexer.py` 的 enrich-content 端点中,将
      `product_enrich.enrich_products_batch` 返回的
    `enriched_taxonomy_attributes` 字段显式加入 HTTP 响应体
    - `analysis_kinds` 参数透传至底层
      `enrich_products_batch`,支持按需跳过某一类分析(如仅需 taxonomy
    时减少 LLM 调用)
    - 缓存指纹计算位于 `product_enrich.py` 的 `_get_cache_key` 函数,对每种
      `AnalysisSchema` 独立生成;版本号通过 `schema.version` 或 prompt
    内容哈希隐式包含
    - 测试覆盖:新增 `analysis_kinds` 组合场景及缓存隔离测试
    tangwang
     
  • 字段生成
    
    - 新增分类法属性富化能力,遵循 enriched_attributes
      相同的字段结构和处理逻辑,仅提示词和解析维度不同
    - 引入 AnalysisSchema
      抽象类,使内容富化(content)与分类法富化(taxonomy)共享批处理、缓存、提示词构建、Markdown
    解析及归一化流程
    - 重构 product_enrich.py 中原有的富化管道,将通用逻辑抽取至
      _process_batch_for_schema、_parse_markdown_to_attributes
    等函数,消除代码重复
    - 在 product_enrich_prompts.py
      中添加分类法提示词模板(TAXONOMY_ANALYSIS_PROMPT)及 Markdown
    表头定义(TAXONOMY_HEADERS)
    - 修复 Markdown
      解析器在空单元格时的行为:原实现会跳过空单元格导致列错位,现改为保留空值,确保稀疏的分类法属性列正确对齐
    - 更新 document_transformer.py 中 build_index_content_fields 函数,将
      enriched_taxonomy_attributes(中/英)写入最终索引文档
    - 调整相关单元测试(test_product_enrich_partial_mode.py
      等)以覆盖新字段路径,测试通过(14 passed)
    
    技术细节:
    - AnalysisSchema 包含
      schema_name、prompt_template、headers、field_name_prefix 等元数据
    -
    缓存键区分内容/分类法:`enrich:{schema_name}:{product_id}`,避免缓存污染
    - 分类法解析使用与 enriched_attributes
      相同的嵌套结构:`{"attribute_key": "value"}`,支持多行表格
    - 批处理大小与重试逻辑保持与原有内容富化一致
    tangwang
     
  • tangwang
     

08 Apr, 2026

6 commits

  • tangwang
     
  • tangwang
     
  • tangwang
     
  • tangwang
     
  • Previously, both `b` and `k1` were set to `0.0`. The original intention
    was to avoid two common issues in e-commerce search relevance:
    
    1. Over-penalizing longer product titles
       In product search, a shorter title should not automatically rank
    higher just because BM25 favors shorter fields. For example, for a query
    like “遥控车”, a product whose title is simply “遥控车” is not
    necessarily a better candidate than a product with a slightly longer but
    more descriptive title. In practice, extremely short titles may even
    indicate lower-quality catalog data.
    
    2. Over-rewarding repeated occurrences of the same term
       For longer queries such as “遥控喷雾翻滚多功能车玩具车”, the default
    BM25 behavior may give too much weight to a term that appears multiple
    times (for example “遥控”), even when other important query terms such
    as “喷雾” or “翻滚” are missing. This can cause products with repeated
    partial matches to outrank products that actually cover more of the user
    intent.
    
    Setting both parameters to zero was an intentional way to suppress
    length normalization and term-frequency amplification. However, after
    introducing a `combined_fields` query, this configuration becomes too
    aggressive. Since `combined_fields` scores multiple fields as a unified
    relevance signal, completely disabling both effects may also remove
    useful ranking information, especially when we still want documents
    matching more query terms across fields to be distinguishable from
    weaker matches.
    
    This update therefore relaxes the previous setting and reintroduces a
    controlled amount of BM25 normalization/scoring behavior. The goal is to
    keep the original intent — avoiding short-title bias and excessive
    repeated-term gain — while allowing the combined query to better
    preserve meaningful relevance differences across candidates.
    
    Expected effect:
    - reduce the bias toward unnaturally short product titles
    - limit score inflation caused by repeated occurrences of the same term
    - improve ranking stability for `combined_fields` queries
    - better reward candidates that cover more of the overall query intent,
      instead of those that only repeat a subset of terms
    tangwang
     
  • tangwang
     

07 Apr, 2026

4 commits


04 Apr, 2026

2 commits


03 Apr, 2026

2 commits