bcada818
tangwang
last
|
1
|
# 相关性检索优化说明(当前实现)
|
7bc756c5
tangwang
优化 ES 查询构建
|
2
|
|
bcada818
tangwang
last
|
3
|
## 1. 文档目标
|
7bc756c5
tangwang
优化 ES 查询构建
|
4
|
|
0536222c
tangwang
query parser优化
|
5
|
本文描述当前代码中的文本检索策略,重点覆盖:
|
7bc756c5
tangwang
优化 ES 查询构建
|
6
|
|
bcada818
tangwang
last
|
7
8
|
- 多语言检索路由(`detector` / `translator` / `indexed` 的关系)
- 统一文本召回表达式(无布尔 AST 分支)
|
0536222c
tangwang
query parser优化
|
9
|
- 解析层与检索表达式层的职责边界
|
c90f80ed
tangwang
相关性优化
|
10
|
- 重排融合打分与调试字段
|
bcada818
tangwang
last
|
11
|
- 典型场景下实际生成的 ES 查询结构
|
7bc756c5
tangwang
优化 ES 查询构建
|
12
|
|
bcada818
tangwang
last
|
13
|
> 说明:向量召回(KNN)是另一维度,本篇仅简要提及,不展开。
|
7bc756c5
tangwang
优化 ES 查询构建
|
14
|
|
bcada818
tangwang
last
|
15
|
## 2. 核心流程
|
7bc756c5
tangwang
优化 ES 查询构建
|
16
|
|
bcada818
tangwang
last
|
17
|
查询链路(文本相关):
|
7bc756c5
tangwang
优化 ES 查询构建
|
18
|
|
bcada818
tangwang
last
|
19
|
1. `QueryParser.parse()`
|
35da3813
tangwang
中英混写query的优化逻辑,不适...
|
20
|
负责产出解析事实:`query_normalized`、`rewritten_query`、`detected_language`、`translations`、`query_vector`、`query_tokens`。
|
0536222c
tangwang
query parser优化
|
21
|
2. `Searcher.search()`
|
35da3813
tangwang
中英混写query的优化逻辑,不适...
|
22
|
负责读取租户 `index_languages`,并将其传给 `QueryParser` 作为 `target_languages`(控制翻译目标语种);`ESQueryBuilder` 仅根据 `detected_language` 与各条译文构建子句字段,不再接收 `index_languages`。
|
bcada818
tangwang
last
|
23
|
2. `ESQueryBuilder._build_advanced_text_query()`
|
0536222c
tangwang
query parser优化
|
24
|
基于 `rewritten_query + detected_language + translations + index_languages` 构建 `base_query` 与 `base_query_trans_*`;并按语言动态拼接 `title/brief/description/vendor/category_*` 的 `.{lang}` 字段,叠加 shared 字段(`tags`、`option*_values`)。
|
bcada818
tangwang
last
|
25
26
|
3. `build_query()`
统一走文本策略,不再有布尔 AST 枝路。
|
7bc756c5
tangwang
优化 ES 查询构建
|
27
|
|
bcada818
tangwang
last
|
28
|
## 3. 能力矩阵(Detector / Translator / Indexed)
|
7bc756c5
tangwang
优化 ES 查询构建
|
29
|
|
bcada818
tangwang
last
|
30
|
三类能力的职责边界:
|
7bc756c5
tangwang
优化 ES 查询构建
|
31
|
|
bcada818
tangwang
last
|
32
33
34
|
- **Detector**:识别 query 源语言(`detected_language`)
- **Indexed**:租户可检索语言集合(`tenant_config.*.index_languages`)
- **Translator**:源语言到目标语言的可翻译能力及实时成功率
|
7bc756c5
tangwang
优化 ES 查询构建
|
35
|
|
bcada818
tangwang
last
|
36
|
### 3.1 决策规则
|
7bc756c5
tangwang
优化 ES 查询构建
|
37
|
|
bcada818
tangwang
last
|
38
39
40
41
|
1. 若 `detected_language in index_languages`:
源语言字段做主召回;其他语言走翻译补召回(低权重)。
2. 若 `detected_language not in index_languages`:
翻译到 `index_languages` 是主路径;源语言字段仅作弱召回。
|
0536222c
tangwang
query parser优化
|
42
43
|
3. 若翻译部分失败或全部失败:
当前实现不会再额外生成“原文打到其他语种字段”的兜底子句;系统保留 `base_query` 并继续执行,可观测性由 `translations` / warning / 命名子句分数提供。
|
7bc756c5
tangwang
优化 ES 查询构建
|
44
|
|
e874eb50
tangwang
docs
|
45
|
### 3.2 翻译与向量:并发提交与共享超时
|
7bc756c5
tangwang
优化 ES 查询构建
|
46
|
|
0536222c
tangwang
query parser优化
|
47
|
`QueryParser.parse()` 内对翻译与向量采用线程池提交 + **一次** `concurrent.futures.wait`:
|
7bc756c5
tangwang
优化 ES 查询构建
|
48
|
|
0536222c
tangwang
query parser优化
|
49
50
|
- **翻译**:对调用方传入的 `target_languages` 中、除 `detected_language` 外的每个目标语种各提交一个 `translator.translate` 任务(多目标时并发执行)。
- **查询向量**:若开启 `enable_text_embedding`,再提交一个 `text_encoder.encode` 任务。
|
e874eb50
tangwang
docs
|
51
|
- 上述任务进入**同一** future 集合;例如租户索引为 `[zh, en]` 且检测语种**不在**索引内时,常为 **2 路翻译 + 1 路向量,共 3 个任务并发**,共用超时。
|
7bc756c5
tangwang
优化 ES 查询构建
|
52
|
|
0536222c
tangwang
query parser优化
|
53
|
**等待预算(毫秒)**由 `detected_language` 是否属于调用方传入的 `target_languages` 决定(`query_config`):
|
e874eb50
tangwang
docs
|
54
55
56
57
58
|
- **在索引内**:`translation_embedding_wait_budget_ms_source_in_index`(默认较短,如 80ms)— 主召回已能打在源语种字段,翻译/向量稍慢可容忍。
- **不在索引内**:`translation_embedding_wait_budget_ms_source_not_in_index`(默认较长,如 200ms)— 翻译对可检索文本更关键,给足时间。
超时未完成的任务会被丢弃并记 warning,解析继续(可能无部分译文或无数向量)。
|
7bc756c5
tangwang
优化 ES 查询构建
|
59
|
|
bcada818
tangwang
last
|
60
|
## 4. 统一文本召回表达式
|
ea118f2b
tangwang
build_query:根据 qu...
|
61
|
|
bcada818
tangwang
last
|
62
|
每个语言子句的基础形态:
|
ea118f2b
tangwang
build_query:根据 qu...
|
63
|
|
bcada818
tangwang
last
|
64
65
66
|
```json
{
"multi_match": {
|
0536222c
tangwang
query parser优化
|
67
|
"_name": "base_query|base_query_trans_xx",
|
bcada818
tangwang
last
|
68
69
70
71
72
73
74
75
|
"query": "<text>",
"fields": ["title.xx^3.0", "brief.xx^1.5", "...", "tags", "option1_values^0.5", "..."],
"minimum_should_match": "75%",
"tie_breaker": 0.9,
"boost": "<按策略决定,可省略>"
}
}
```
|
ea118f2b
tangwang
build_query:根据 qu...
|
76
|
|
bcada818
tangwang
last
|
77
|
最终按 `bool.should` 组合,`minimum_should_match: 1`。
|
ea118f2b
tangwang
build_query:根据 qu...
|
78
|
|
bcada818
tangwang
last
|
79
|
## 5. 关键配置项(文本策略)
|
7bc756c5
tangwang
优化 ES 查询构建
|
80
|
|
e874eb50
tangwang
docs
|
81
82
83
84
85
|
`query_config` 下与解析等待相关的项:
- `translation_embedding_wait_budget_ms_source_in_index`
- `translation_embedding_wait_budget_ms_source_not_in_index`
|
bcada818
tangwang
last
|
86
|
位于 `config/config.yaml -> query_config.text_query_strategy`:
|
7bc756c5
tangwang
优化 ES 查询构建
|
87
|
|
bcada818
tangwang
last
|
88
89
|
- `base_minimum_should_match`
- `translation_minimum_should_match`
|
0536222c
tangwang
query parser优化
|
90
|
- `translation_boost`(所有 `base_query_trans_*` 共用)
|
bcada818
tangwang
last
|
91
|
- `tie_breaker_base_query`
|
7bc756c5
tangwang
优化 ES 查询构建
|
92
|
|
c90f80ed
tangwang
相关性优化
|
93
94
|
说明:
|
0536222c
tangwang
query parser优化
|
95
|
- `phrase_query` / `keywords_query` 已从当前实现中移除,文本相关性只由 `base_query`、`base_query_trans_*` 两类子句组成。
|
c90f80ed
tangwang
相关性优化
|
96
|
|
bcada818
tangwang
last
|
97
|
## 6. 典型场景与实际 DSL
|
7bc756c5
tangwang
优化 ES 查询构建
|
98
|
|
bcada818
tangwang
last
|
99
|
以下示例来自当前 `ESQueryBuilder` 生成结果(已按当前代码验证)。
|
7bc756c5
tangwang
优化 ES 查询构建
|
100
|
|
bcada818
tangwang
last
|
101
|
### 场景 A:源语种已在索引语言中,且翻译成功
|
7bc756c5
tangwang
优化 ES 查询构建
|
102
|
|
bcada818
tangwang
last
|
103
104
|
- `detected_language=de`
- `index_languages=[de,en]`
|
0536222c
tangwang
query parser优化
|
105
106
|
- `rewritten_query="herren schuhe"`
- `translations={en:"men shoes"}`
|
7bc756c5
tangwang
优化 ES 查询构建
|
107
|
|
bcada818
tangwang
last
|
108
|
策略结果:
|
7bc756c5
tangwang
优化 ES 查询构建
|
109
|
|
0536222c
tangwang
query parser优化
|
110
|
- `base_query`:德语字段,**不写** `multi_match.boost`
|
bcada818
tangwang
last
|
111
|
- `base_query_trans_en`:英语字段,`boost=translation_boost`(默认 0.4)
|
7bc756c5
tangwang
优化 ES 查询构建
|
112
|
|
bcada818
tangwang
last
|
113
|
### 场景 B:源语种不在索引语言中,部分翻译缺失
|
7bc756c5
tangwang
优化 ES 查询构建
|
114
|
|
bcada818
tangwang
last
|
115
116
117
|
- `detected_language=de`
- `index_languages=[en,zh]`
- 只翻译出 `en`,`zh` 失败
|
7bc756c5
tangwang
优化 ES 查询构建
|
118
|
|
bcada818
tangwang
last
|
119
|
策略结果:
|
7bc756c5
tangwang
优化 ES 查询构建
|
120
|
|
0536222c
tangwang
query parser优化
|
121
122
123
|
- `base_query`(德语字段):**不写** `multi_match.boost`(默认 1.0)
- `base_query_trans_en`(英文字段):`boost=translation_boost`(如 0.4)
- 不会生成额外中文兜底子句
|
7bc756c5
tangwang
优化 ES 查询构建
|
124
|
|
bcada818
tangwang
last
|
125
|
### 场景 C:源语种不在索引语言中,翻译全部失败
|
7bc756c5
tangwang
优化 ES 查询构建
|
126
|
|
bcada818
tangwang
last
|
127
128
|
- `detected_language=de`
- `index_languages=[en,zh]`
|
0536222c
tangwang
query parser优化
|
129
|
- `translations={}`
|
7bc756c5
tangwang
优化 ES 查询构建
|
130
|
|
bcada818
tangwang
last
|
131
|
策略结果:
|
7bc756c5
tangwang
优化 ES 查询构建
|
132
|
|
0536222c
tangwang
query parser优化
|
133
134
|
- `base_query`(德语字段,**无** `boost` 字段)
- 不会生成 `base_query_trans_*`
|
7bc756c5
tangwang
优化 ES 查询构建
|
135
|
|
0536222c
tangwang
query parser优化
|
136
|
这意味着当前实现优先保证职责清晰与可解释性,而不是继续在 Builder 内部隐式制造“跨语种原文兜底”。
|
7bc756c5
tangwang
优化 ES 查询构建
|
137
|
|
0536222c
tangwang
query parser优化
|
138
|
## 7. QueryParser 与 Searcher / ESBuilder 的职责分工
|
7bc756c5
tangwang
优化 ES 查询构建
|
139
|
|
0536222c
tangwang
query parser优化
|
140
141
142
143
144
145
146
|
- `QueryParser` 负责“解析事实”:
- `query_normalized`
- `rewritten_query`
- `detected_language`
- `translations`
- `query_vector`
- `query_tokens`
|
0536222c
tangwang
query parser优化
|
147
148
149
|
- `Searcher` 负责“租户语境”:
- `index_languages`
- 将其传给 parser 作为 `target_languages`
|
bcada818
tangwang
last
|
150
151
152
|
- `ESQueryBuilder` 负责“表达式展开”:
- 动态字段组装
- 子句权重分配
|
0536222c
tangwang
query parser优化
|
153
154
|
- `base_query` / `base_query_trans_*` 子句拼接
- 跳过“与 base_query 文本和语言完全相同”的重复翻译子句
|
7bc756c5
tangwang
优化 ES 查询构建
|
155
|
|
0536222c
tangwang
query parser优化
|
156
|
这种分层让 parser 不再返回 ES 专用的“语言计划字段”,职责边界更清晰。
|
7bc756c5
tangwang
优化 ES 查询构建
|
157
|
|
c90f80ed
tangwang
相关性优化
|
158
159
160
161
162
163
|
## 8. 融合打分(Rerank + Text + KNN)
当前融合逻辑位于 `search/rerank_client.py`。
### 8.1 文本相关性大分
|
0536222c
tangwang
query parser优化
|
164
|
文本大分由两部分组成:
|
c90f80ed
tangwang
相关性优化
|
165
166
167
|
- `base_query`
- `base_query_trans_*`
|
c90f80ed
tangwang
相关性优化
|
168
169
170
171
172
|
聚合方式:
1. `source_score = base_query`
2. `translation_score = max(base_query_trans_*)`
|
0536222c
tangwang
query parser优化
|
173
|
3. 加权:
|
c90f80ed
tangwang
相关性优化
|
174
175
|
- `weighted_source = source_score`
- `weighted_translation = 0.8 * translation_score`
|
0536222c
tangwang
query parser优化
|
176
177
178
|
4. 合成:
- `primary = max(weighted_source, weighted_translation)`
- `support = weighted_source + weighted_translation - primary`
|
c90f80ed
tangwang
相关性优化
|
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
|
- `text_score = primary + 0.25 * support`
如果以上子分都缺失,则回退到 ES `_score` 作为 `text_score`,避免纯文本召回被误打成 0。
### 8.2 最终融合公式
```python
fused_score = (
(rerank_score + 0.00001) *
(text_score + 0.1) ** 0.35 *
(knn_score + 0.6) ** 0.2
)
```
设计意图:
- `rerank_score` 是主导信号
- `text_score` 保留乘法增益,但通过较低指数避免词法高分过度放大
- `knn_score` 保持弱参与,只作为语义召回补充
### 8.3 调试字段
开启 `debug=true` 后,`debug_info.per_result` 会暴露:
- `es_score`
- `rerank_score`
- `text_score`
- `text_source_score`
- `text_translation_score`
|
c90f80ed
tangwang
相关性优化
|
208
209
210
211
212
213
214
215
|
- `text_primary_score`
- `text_support_score`
- `knn_score`
- `fused_score`
- `matched_queries`
`debug_info.query_analysis` 还会暴露:
|
0536222c
tangwang
query parser优化
|
216
217
218
|
- `translations`
- `detected_language`
- `rewritten_query`
|
c90f80ed
tangwang
相关性优化
|
219
220
221
222
|
这些字段用于检索效果评估与 bad case 归因。
## 9. 兼容与注意事项
|
7bc756c5
tangwang
优化 ES 查询构建
|
223
|
|
bcada818
tangwang
last
|
224
225
|
1. 当前文本主链路已移除布尔 AST 分支。
2. 文档中的旧描述(如 `operator: AND` 固定开启)不再适用,当前实现未强制设置该参数。
|
0536222c
tangwang
query parser优化
|
226
|
3. `HanLP` 为必需依赖;当前 parser 不再提供轻量 fallback。
|
bcada818
tangwang
last
|
227
228
229
230
|
4. 若后续扩展到更多语种,请确保:
- mapping 中存在对应 `.<lang>` 字段
- `index_languages` 配置在支持列表内
- 翻译 provider 对目标语种可用
|
7bc756c5
tangwang
优化 ES 查询构建
|
231
|
|
c90f80ed
tangwang
相关性优化
|
232
233
234
235
236
237
238
239
240
241
242
|
## 10. 评估与复现
建议使用项目根目录虚拟环境:
```bash
cd /data/saas-search
source ./activate.sh
python -m pytest -q tests/test_rerank_client.py tests/test_es_query_builder.py tests/test_search_rerank_window.py tests/test_query_parser_mixed_language.py
./scripts/service_ctl.sh restart backend
sleep 3
./scripts/service_ctl.sh status backend
|
a345b01f
tangwang
eval framework
|
243
|
./scripts/evaluation/start_eval.sh.sh batch
|
c90f80ed
tangwang
相关性优化
|
244
245
|
```
|
3ac1f8d1
tangwang
评估标准优化
|
246
|
评估产物在 `artifacts/search_evaluation/`(如 `search_eval.sqlite3`、`batch_reports/` 下的 JSON/Markdown)。流程与参数说明见 [scripts/evaluation/README.md](../scripts/evaluation/README.md)。
|
c90f80ed
tangwang
相关性优化
|
247
248
|
## 11. 建议测试清单
|
7bc756c5
tangwang
优化 ES 查询构建
|
249
|
|
bcada818
tangwang
last
|
250
|
建议在 `tests/` 增加文本策略用例:
|
7bc756c5
tangwang
优化 ES 查询构建
|
251
|
|
bcada818
tangwang
last
|
252
|
1. 源语种在索引语言,翻译命中缓存
|
0536222c
tangwang
query parser优化
|
253
254
255
|
2. 源语种不在索引语言,翻译部分失败(验证仅保留 `base_query` + 成功翻译子句)
3. 源语种不在索引语言,翻译全部失败(验证无 `base_query_trans_*` 时仍可正常执行)
4. 非 `zh/en` 语种字段动态拼接(如 `de/fr/es`)
|
fb973d19
tangwang
configs
|
256
257
|
|
c3425429
tangwang
在以下文件中完成精排/融合清理工作...
|
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
|
# 搜索pipeline
**整体图**
这个 pipeline 现在可以理解成一条“先广召回,再逐层收窄、逐层加贵信号”的漏斗:
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 原逻辑返回。
这点非常重要,因为它决定了“贵模型只服务头部结果”。
**Step 1:Query 解析阶段**
在 [searcher.py:432](/data/saas-search/search/searcher.py#L432) 到 [searcher.py:469](/data/saas-search/search/searcher.py#L469):
`query_parser.parse()` 做几件事:
- 规范化 query
- 检测语言
- 可能做 rewrite
- 生成文本向量
- 如果有图搜,还会带图片向量
- 生成翻译结果
- 识别 style intent
这一步的结果存在 `parsed_query` 里,后面 ES 查询、style SKU 选择、fine/final rerank 全都依赖它。
**Step 2:ES Query 构建**
ES DSL 在 [searcher.py:471](/data/saas-search/search/searcher.py#L471) 开始,通过 [es_query_builder.py:181](/data/saas-search/search/es_query_builder.py#L181) 的 `build_query()` 生成。
这里的核心结构是:
- 文本召回 clause
- 文本向量 KNN clause
- 图片向量 KNN clause
- 它们一起放进 `bool.should`
- 过滤条件放进 `filter`
- facet 的多选条件走 `post_filter`
KNN 部分在 [es_query_builder.py:250](/data/saas-search/search/es_query_builder.py#L250) 之后:
- 文本向量 clause 名字固定叫 `knn_query`
- 图片向量 clause 名字固定叫 `image_knn_query`
而文本召回那边,后续 fusion 代码约定会去读:
- 原始 query 的 named query:`base_query`
- 翻译 query 的 named query:`base_query_trans_*`
也就是说,后面的粗排/精排/最终 rerank,并不是重新理解 ES score,而是从 `matched_queries` 里把这些命名子信号拆出来自己重算。
**Step 3:ES 召回**
在 [searcher.py:579](/data/saas-search/search/searcher.py#L579) 到 [searcher.py:627](/data/saas-search/search/searcher.py#L627)。
这里有个很关键的工程优化:
如果在 rerank window 内,第一次 ES 拉取时会把 `_source` 关掉,只取排序必需信号,见 [searcher.py:517](/data/saas-search/search/searcher.py#L517) 到 [searcher.py:523](/data/saas-search/search/searcher.py#L523)。
原因是:
- 粗排先只需要 `_score` 和 `matched_queries`
- 不需要一上来把 700 条完整商品详情都拉回来
- 等粗排收窄后,再补 fine/final rerank 需要的字段
这是现在这条 pipeline 很核心的性能设计点。
**Step 4:粗排**
粗排入口在 [searcher.py:638](/data/saas-search/search/searcher.py#L638),真正的打分在 [rerank_client.py:348](/data/saas-search/search/rerank_client.py#L348) 的 `coarse_resort_hits()`。
粗排只看两类信号:
- `text_score`
- `knn_score`
它们先都从统一 helper `_build_hit_signal_bundle()` 里拿,见 [rerank_client.py:246](/data/saas-search/search/rerank_client.py#L246)。
文本分怎么来,见 [rerank_client.py:200](/data/saas-search/search/rerank_client.py#L200):
- `source_score = matched_queries["base_query"]`
- `translation_score = max(base_query_trans_*)`
- `weighted_translation = 0.8 * translation_score`
- `primary_text = max(source, weighted_translation)`
- `support_text = 另一路`
- `text_score = primary_text + 0.25 * support_text`
这就是一个 text dismax 思路:
原 query 是主路,翻译 query 是辅助路,但不是简单相加。
向量分怎么来,见 [rerank_client.py:156](/data/saas-search/search/rerank_client.py#L156):
- `text_knn_score`
- `image_knn_score`
- 分别乘自己的 weight
- 取强的一路做主路
- 弱的一路按 `knn_tie_breaker` 做辅助
然后粗排融合公式在 [rerank_client.py:334](/data/saas-search/search/rerank_client.py#L334):
- `coarse_score = (text_score + text_bias)^text_exponent * (knn_score + knn_bias)^knn_exponent`
配置定义在 [schema.py:124](/data/saas-search/config/schema.py#L124) 和 [config.yaml:231](/data/saas-search/config/config.yaml#L231)。
算完后:
- 写入 `hit["_coarse_score"]`
- 按 `_coarse_score` 排序
- 留前 240,见 [searcher.py:645](/data/saas-search/search/searcher.py#L645)
**Step 5:粗排后补字段 + SKU 选择**
粗排完以后,`searcher` 会按 doc template 反推 fine/final rerank 需要哪些 `_source` 字段,然后只补这些字段,见 [searcher.py:669](/data/saas-search/search/searcher.py#L669)。
之后才做 style SKU 选择,见 [searcher.py:696](/data/saas-search/search/searcher.py#L696)。
为什么放这里?
因为现在 fine rank 也是 reranker,它也要吃 title suffix。
而 suffix 是 SKU 选择之后写到 hit 上的 `_style_rerank_suffix`。
真正把 suffix 拼进 doc 文本的地方在 [rerank_client.py:65](/data/saas-search/search/rerank_client.py#L65) 到 [rerank_client.py:74](/data/saas-search/search/rerank_client.py#L74)。
所以顺序必须是:
- 先粗排
- 再选 SKU
- 再用带 suffix 的 title 去跑 fine/final rerank
**Step 6:精排**
入口在 [searcher.py:711](/data/saas-search/search/searcher.py#L711),实现是 [rerank_client.py:603](/data/saas-search/search/rerank_client.py#L603) 的 `run_lightweight_rerank()`。
它会做三件事:
1. 用 `build_docs_from_hits()` 把每条商品变成 reranker 输入文本
2. 用 `service_profile="fine"` 调轻量服务
3. 不再只按 `fine_score` 排,而是按融合后的 `_fine_fused_score` 排
精排融合公式现在是:
- `fine_stage_score = fine_factor * text_factor * knn_factor * style_boost`
具体公共计算在 [rerank_client.py:286](/data/saas-search/search/rerank_client.py#L286) 的 `_compute_multiplicative_fusion()`:
- `fine_factor = (fine_score + fine_bias)^fine_exponent`
- `text_factor = (text_score + text_bias)^text_exponent`
- `knn_factor = (knn_score + knn_bias)^knn_exponent`
- 如果命中了 selected SKU,再乘 style boost
写回 hit 的字段见 [rerank_client.py:655](/data/saas-search/search/rerank_client.py#L655):
- `_fine_score`
- `_fine_fused_score`
- `_text_score`
- `_knn_score`
排序逻辑在 [rerank_client.py:683](/data/saas-search/search/rerank_client.py#L683):
按 `_fine_fused_score` 降序排,然后留前 80,见 [searcher.py:727](/data/saas-search/search/searcher.py#L727)。
这就是你这次特别关心的点:现在 fine rank 已经不是“模型裸分排序”,而是“模型分 + ES 文本/KNN 信号融合后排序”。
**Step 7:最终 rerank**
入口在 [searcher.py:767](/data/saas-search/search/searcher.py#L767),实现是 [rerank_client.py:538](/data/saas-search/search/rerank_client.py#L538) 的 `run_rerank()`。
它和 fine rank 很像,但多了一个更重的模型分 `rerank_score`。
最终公式是:
- `final_score = rerank_factor * fine_factor * text_factor * knn_factor * style_boost`
也就是:
- fine rank 产生的 `fine_score` 不会丢
- 到最终 rerank 时,它会继续作为一个乘法项参与最终融合
这个逻辑在 [rerank_client.py:468](/data/saas-search/search/rerank_client.py#L468) 到 [rerank_client.py:476](/data/saas-search/search/rerank_client.py#L476)。
算完后写入:
- `_rerank_score`
- `_fused_score`
然后按 `_fused_score` 排序,见 [rerank_client.py:531](/data/saas-search/search/rerank_client.py#L531)。
这里你可以把它理解成:
- fine rank 负责“轻量快速筛一遍,把 240 缩成 80”
- 最终 rerank 负责“用更贵模型做最终拍板”
- 但最终拍板时,不会忽略 fine rank 结果,而是把 fine score 当成一个先验信号保留进去
**Step 8:分页与字段补全**
多阶段排序只在头部窗口内完成。
真正返回给用户前,在 [searcher.py:828](/data/saas-search/search/searcher.py#L828) 之后还会做两件事:
- 先按 `from_:from_+size` 对最终 80 条切片
- 再按用户原始 `_source` 需求补回页面真正要显示的字段,见 [searcher.py:859](/data/saas-search/search/searcher.py#L859)
所以这条链路是“三次不同目的的数据访问”:
- 第一次 ES:只要排序信号
- 第二次按 id 回填:只要 fine/final rerank 需要字段
- 第三次按页面 ids 回填:只要最终页面显示字段
这也是为什么它性能上比“一次全量拉 700 条完整文档”更合理。
**Step 9:结果格式化与 debug funnel**
最后在 [searcher.py:906](/data/saas-search/search/searcher.py#L906) 进入结果处理。
这里会把每个商品的阶段信息组装成 `ranking_funnel`,见 [searcher.py:1068](/data/saas-search/search/searcher.py#L1068):
- `es_recall`
- `coarse_rank`
- `fine_rank`
- `rerank`
- `final_page`
其中:
- coarse stage 主要保留 text/translation/knn 的拆分信号
- fine/rerank stage 现在都保留 `fusion_inputs`、`fusion_factors`、`fusion_summary`
- `fusion_summary` 来自真实计算过程本身,见 [rerank_client.py:265](/data/saas-search/search/rerank_client.py#L265)
这点很重要,因为现在“实际排序逻辑”和“debug 展示逻辑”是同源的,不是两套各写一份。
**一句话总结这条 pipeline**
这条 pipeline 的本质是:
- ES 负责便宜的大范围召回
- 粗排负责只靠 ES 内置信号先做一次结构化筛选
- style SKU 选择负责把商品文本改造成更适合 reranker 理解的输入
- fine rank 负责用轻模型把候选进一步压缩
- final rerank 负责用重模型做最终判定
- 每一层都尽量复用前一层信号,而不是推翻重来
如果你愿意,我下一步可以继续按“一个具体 query 的真实流转样例”来讲,比如假设用户搜 `black dress`,我把它从 `parsed_query`、ES named queries、coarse/fine/final 的每个分数怎么出来,完整手推一遍。
|
fb973d19
tangwang
configs
|
490
491
492
493
494
495
496
497
498
499
500
501
|
## reranker方面:
BAAI/bge-reranker-v2-m3的一个严重badcase:
q=黑色中长半身裙
Rerank score: 0.0785
title.zh: 2026款韩版高腰显瘦雪尼尔包臀裙灯芯绒开叉中长款咖啡色半身裙女
title.en: 2026 Korean-style High-waisted Slimming Corduroy Skirt with Slit, Mid-Length Coffee-colored Skirt for Women
Rerank score: 0.9643
title.en: Black Half-high Collar Base Shirt Women's Autumn and Winter fleece-lined Contrast Color Pure Desire Design Sense Horn Sleeve Ruffled Inner Top
title.zh: 黑色高领半高领女士秋冬内搭加绒拼色纯欲设计荷叶边袖内衬上衣
|
ef5baa86
tangwang
混杂语言处理
|
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
|
qwen3-0.6b的严重badcase:
q=牛仔裤
Rerank score: 0.0002
title.en: Wrangler Womens Cowboy Cut Slim Fit Jean Bleach
title.zh: Wrangler 女士牛仔裤 牛仔剪裁 紧身版型 漂白色
Rerank score: 0.0168
title.en: Fleece Lined Tights Sheer Women - Fake Translucent Warm Pantyhose Leggings Sheer Thick Tights for Winter
title.zh: 加绒透肤女士连裤袜 - 仿透视保暖长筒袜 冬季厚款透肤连裤袜
Rerank score: 0.1366
title.en: Dockers Men's Classic Fit Workday Khaki Smart 360 FLEX Pants (Standard and Big & Tall)
title.zh: Dockers 男士经典版型工作日卡其色智能360度弹力裤(标准码与加大码)
Rerank score: 0.0981
title.en: Lazy One Pajama Shorts for Men, Men's Pajama Bottoms, Sleepwear
title.zh: 懒人男士睡裤,男式家居裤,睡眠服饰
|