dfb45131
tangwang
issues文档整理
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
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
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
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
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
|
# 1-prompt
现在索引是spu粒度的,sku是作为spu的nested属性。召回和计算exact_image_knn_query都会计算图片的相似度,image_embedding字段是一个list,跟sku是一一对应的。
我希望,是因为哪个image_embedding被召回的、或者重排的时候,是取的哪个元素的exact_image_knn_query得分(image_embedding这个list内最高的得分),我希望将这个sku前置。
之前在重排之前有这个流程:
1. 有款式意图的时候,才做sku筛选
2. sku筛选的时机,改为在reranker之前,对所有内容(rerank输入的所有spus)做sku筛选
若有款式意图则对 rerank 窗口内全部 hits 做 SKU 预筛选并补充 rerank doc 文本
Searcher 只编排:解析 query -> 首次 ES 召回 -> 若有款式意图则对 rerank 窗口内全部 hits 做 SKU 预筛选并补充 rerank doc 文本 -> run_rerank -> 分页
但是,现在是关闭重排的,所以,这个流程可能没了?请你检查。
我的总的意图是,如果有款式方面的意图,需要做spu内部的sku的排序,
这两点也需要检查:
1. 之前的流程,不知道是否仍然有效(因为现在关闭了reranker)
2. 现在补充了image_embedding的召回和exact_image_knn_query得分,那么应该能知道哪个sku是最匹配的,希望把这个信号利用上,
1)召回的时候,是哪个图片向量相似度最高
2)重排的时候是用哪个算的相似度?这个信号如何利用
"rescore_query": {
"bool": {
"should": [
{
"nested": {
"path": "image_embedding",
"_name": "exact_image_knn_query",
"score_mode": "max",
"query": {
"script_score": {
"query": {
"exists": {
"field": "image_embedding.vector"
}
},
"script": {
"source": "((dotProduct(params.query_vector, 'image_embedding.vector') + 1.0) / 2.0) * params.boost",
"params": {
最终组织结果的时候,sku的排序,要把这个信号利用进来,要按照图片的相似度排序。
是直接用图片的信号,把图片得分最高的url,用于sku的筛选(选取对应url的sku。因为skus和image_embedding是两个字段,skus里面有image_src,可以用于url匹配,选取对应url的sku作为图片得分最高的sku)还是跟以前的策略融合在一起,还是,请思考
可以以query=白色T恤进行实验
# 1-response
我仔细读了当前 `search/searcher.py`、`search/sku_intent_selector.py`、`search/es_query_builder.py`、`mappings/search_products.json` 和 `indexer/document_transformer.py`,先回答你的两个核对问题,再给出如何把图片信号融入的设计。
---
## 1. 关闭 rerank 之后,原来的款式意图 SKU 流程是否还有效?
**仍然有效。** 关键点:
- `in_rank_window = (from_ + size) <= rerank_window`(`searcher.py:529`)只依赖**配置里的 `rerank_window`**,跟请求/配置里的 `do_rerank` 没关系。
- 进入 `in_rank_window` 分支后,款式意图处理是**无条件**执行的:
- `searcher.py:1070-1081`:在 coarse 完成、ranking 窗口 source fill 之后,调用 `_apply_style_intent_to_hits` 算出 `style_intent_decisions`(带 `_style_rerank_suffix`)。
- `searcher.py:1083-1154`:fine 和 rerank 阶段现在都通过 `_run_optional_stage` 包装,`enabled=False` 时走 passthrough,**不会丢**前面挂在 hit 上的信号。
- `searcher.py:1205-1209`:page fill 覆盖 `_source` 之后,再调一次 `apply_precomputed_decisions`,把 SKU 置顶 + `image_url` 替换最终写回。
- 非 rank-window 分支(`searcher.py:1225-1231`)走 `_apply_style_intent_to_hits` 并在 `result_processing` 前 `apply_precomputed_decisions`(1254-1259)。
所以"先 prepare、后 apply"的契约没断。唯一的"废功"是 `_style_rerank_suffix`——关掉重排之后它挂在 hit 上但没人读;代价很小、一旦你重新打开 rerank 又立刻生效,建议保留。
---
## 2. 当前 `image_embedding` / `exact_image_knn_query` 的信号完全**没**喂到 SKU 选择里
我去看了索引 & 查询两端:
- `mappings/search_products.json:1054` 的 `image_embedding` 是 nested,每个元素 `{vector, url}`;索引时(`indexer/document_transformer.py:490-536`)把 SPU 主图 + SKU 图片 URL 合起来去重后逐个编码,**`url` 就是 `skus[].image_src` 里能对得上的那串**。
- `es_query_builder.py:193-281` 里 `build_image_knn_clause` 和 `build_exact_image_knn_rescore_clause` 都是 nested + `score_mode: max`,但**都没带 `inner_hits`**。所以 ES 只告诉我们"nested 里最高分是多少",不告诉我们是哪条 url 命中的。
- `StyleSkuSelector` 当前完全靠"意图词 × `optionN_value` 文本匹配"选 SKU(`sku_intent_selector.py:252-284`),没有任何 image 通路。
也就是说:**"哪个 image_embedding 被召回 / 被 rescore 命中"这个你已经花代价算出来的信号,下游根本拿不到**。
---
## 3. 融合设计(建议方案)
我倾向**融合**而不是二选一,理由是两类信号各有盲区:
| 信号 | 擅长 | 盲区 |
|---|---|---|
| 文本款式意图(现有) | "白色"能命中 option `color=white` | 店家把颜色塞在 title 或图里、option 是数字码时失效 |
| 图片 KNN 最佳 url(新增) | 视觉接近 query(文本走 CN-CLIP 后也和图片空间对齐) | 可能选出"卖相最好"的主图,和明确的颜色诉求冲突 |
### 契约上的改动最小化:依旧 `SkuSelectionDecision` + `apply_precomputed_decisions`
只是在 prepare 阶段多一个候选源、decision 里多几个字段做 debug 和融合依据。
### 具体改动点
**a. `es_query_builder.py`:给两个 nested image 查询都加 `inner_hits`**
- `build_exact_image_knn_rescore_clause`:`{"inner_hits": {"name": "exact_image_knn_hits", "size": 1, "_source": ["url"]}}`
- `build_image_knn_clause`:`{"inner_hits": {"name": "image_knn_hits", "size": 1, "_source": ["url"]}}`
两个都加的原因:rescore 是 `in_rank_window` 才有;不在窗口内时还能吃到初召回的 inner_hits。代价:每个 hit 多返回一个 `{url, _score}`,很轻。
**b. `sku_intent_selector.py`:扩展 decision,新增 image-pick 通路**
```python
@dataclass(frozen=True)
class SkuSelectionDecision:
... # 保留现有字段
image_pick_sku_id: Optional[str] = None
image_pick_url: Optional[str] = None
image_pick_score: Optional[float] = None
final_source: str = "text" # "text" | "image" | "none"
```
- 新增 `_pick_sku_by_image(hit, source)`:读 `hit["inner_hits"]["exact_image_knn_hits"]`(缺省回退到 `image_knn_hits`),取 top url,在 `skus[]` 里按 `image_src` 归一化后等值匹配(注意处理 `//` 协议无关前缀、大小写、querystring 这些脏数据,按 indexer 那边的写入规则来)。
- `_select_for_source` 的新决策树:
1. `text_pick` = 现在的文本匹配结果。
2. `image_pick` = 上述 helper 的结果。
3. 有款式意图:
- `text_pick` 非空 → 选 `text_pick`,`final_source="text"`;把 `image_pick` 存进 decision 里仅用于 debug/验证(两者一致时在 debug 打标,不一致时也不翻盘——query 显式指名了颜色/尺码,视觉信号不该覆盖)。
- `text_pick` 为空但 `image_pick` 非空 → 选 `image_pick`,`final_source="image"`。
4. 无款式意图:
- 若 `image_pick` 的分数 ≥ 阈值(配置项,例如 `query_config.image_sku_selection_min_score`),选 `image_pick`。否则不做 SKU 选择(保持 SPU 原顺序)。
- `_apply_decision_to_source` 无需大改,仍然是"把选中 SKU 顶到前面 + 替换 `image_url` 为该 SKU 的 `image_src`";只是 `selected_sku_id` 的来源更丰富了。
**c. `searcher.py`:把 image-pick 路径也纳入 "prepare → apply"**
- 把"`_has_style_intent` 才 prepare"改成"`_has_style_intent` 或 `has_image_inner_hits` 就 prepare"。命名上把 `_apply_style_intent_to_hits` 改成 `_apply_sku_selection_to_hits` 更准确(可做可不做,看你对改动面范围的容忍度)。
- `_resolve_rerank_source_filter` 已经在有款式意图时补了 `skus`/`option*_name`;image-only 路径还需要 `skus`(至少 `sku_id`、`image_src`),所以触发条件要扩到"只要可能选 SKU"。
- inner_hits 会带在 top-level `hit` 上,不会被 page fill 的 `_source` 覆盖影响,但 page fill 用的是 `ids` 查询,**本来就不会有 inner_hits**。这正好符合现有契约:**prepare 时把 image_pick 落到 `SkuSelectionDecision` 里**,apply 阶段不再依赖 inner_hits,就不会丢。
- `context.store_intermediate_result("style_intent_profile", ...)` 附近把 image-pick 的诊断也写进 debug。
**d. 配置**
```yaml
query_config:
image_sku_selection_enabled: true # 总开关
image_sku_selection_min_score: 0.55 # 无款式意图时的保护阈值
image_sku_selection_when_text_missing: true # 有意图但文本未命中时回退到图片
```
### 我为什么不建议"用图片分直接替换原策略"
两个理由:
1. 你现在的文本策略对明确的词面命中是**最可信**的信号;query 说"白色",结果给了视觉更漂亮但色号不同的 SKU,这是产品上的倒退。
2. 图片 KNN 的 max score 在 nested score_mode=max 下是"最相似那张图的分",但 CN-CLIP 的文本→图像相似度阈值校准很敏感,不同品类/拍摄风格分布差异大。拿来**做候选**稳,拿来**做唯一决定**不稳。
---
## 4. `query=白色T恤` 的实验
改完之后可以这样验证:
```bash
source activate.sh
python main.py search "白色T恤" --tenant-id 1 --size 10 --debug \
| jq '.debug_info.per_result[] | {spu_id, style_intent_sku, first_sku: .skus[0]?}'
```
预期关注点:
- **有 color option 的 SPU**:`style_intent_sku.final_source == "text"`,并且 `image_pick_sku_id` 多数情况下与 text 选的一致(一致率是个好指标)。
- **没有 color option、但多图的 SPU**:`final_source == "image"`,`skus[0].image_src` 的图肉眼是白色。
- **两者冲突**:debug 里应能看到 `selected_sku_id != image_pick_sku_id` 的案例,确认当前默认"文本优先"没有被图片翻盘。
- 分别跑 `--debug` 对比改前/改后同一 query 的 `results[].image_url` 和 `skus[0].image_src`,观察白色主图替换的命中率提升。
如果想更系统,可以扩 `scripts/evaluation/` 里已有的回归 case,加一个"图片颜色是否匹配意图"的人工抽检指标。
---
## 5. 两个我想先和你确认的产品决策
1. **无款式意图时**,是否允许图片信号单独触发 SKU 置顶?(我倾向:允许,但带阈值保护,默认开。)
2. **文本命中 vs 图片命中冲突**时,是硬性文本优先,还是做分数融合(先各自 min-max 归一再取加权 max)?我倾向硬性文本优先——实现简单、行为可解释;融合方案留作后续 A/B。
告诉我你的取舍,我就按上面的改动清单一次性改掉(`es_query_builder.py` 两个 inner_hits、`sku_intent_selector.py` 扩字段 + 新通路、`searcher.py` 触发条件扩容 + 命名收敛、配置项、配套单测),并跑 `白色T恤` 的实际 debug 对照给你看。
# 2-prompt
1. 无款式意图时,允许图片信号单独触发 SKU 置顶
2. 文本命中,硬性文本优先,当匹配失败的时候("selected_sku_id": null)使用图片最相近的
3. es中有这个字段enriched_taxonomy_attributes:
"enriched_taxonomy_attributes": [
{
"name": "Color",
文本匹配的时候,考虑更全面一点:
1)把这个字段考虑进去,enriched_taxonomy_attributes内Color对应的value匹配的时候
我在 source_fields:加了enriched_taxonomy_attributes之后好像还是没返回这个字段,你检查下是不是能拿到,可以考虑对query=卡其色裙子 首条结果验证下
4. 还是颜色意图文本匹配的问题 query=卡其色裙子的首条结果:
"selected_sku_id": null,
"specifications": [
{
"value_text": {...},
"name": "颜色",
"sku_id": "447652",
"value_keyword": "卡其色v领收腰长裙【常规款】"
},
意图检测是成功的:
intent_detection:
active: yes
intents:
intent_type: color
dimension_aliases: color, colors, colour, colours, 颜色, 色, 色系
matched_term: 卡其色
canonical_value: beige
但是 "selected_sku_id": null, 颜色没有匹配上,value_keyword 包含了“卡其色”,没有匹配成功,因此也应该没有享受到提权
# 2-response
好,那就直接在当前 agent 模式下按计划施工。施工顺序按"先不破坏现有行为、再扩新通路":
**Phase 1**(修复 + 显字段,低风险)
1. `api/models.py` 扩 `SpuResult` 两个富化字段
2. `api/result_formatter.py` 透传
3. `sku_intent_selector.py`:扩匹配词集合(zh+en+attr)+ 收敛为子串匹配;加 `enriched_taxonomy_attributes` 作为 SPU 级确认
**Phase 2**(image 回退通路)
4. `es_query_builder.py` 两个 nested image 查询加 `inner_hits`
5. `sku_intent_selector.py` 扩 `SkuSelectionDecision` + `_pick_sku_by_image` + text-first/image-fallback 决策树
6. `searcher.py` 触发条件扩为"款式意图 OR image inner_hits",`_resolve_rerank_source_filter` 无意图时也带 `skus` 字段
7. `config.yaml` 加 `image_sku_selection.*`
**Phase 3**(调试 + 回归)
8. debug payload 扩 text_pick/image_pick/final_source
9. 单测补四个关键场景
10. 跑 `卡其色裙子` / `白色T恤` 对照
开始吗?两件事需要你拍板后我再动:
1. **`enriched_taxonomy_attributes` 的语义级别**:你要的是"作为另一个 SKU-级 value 匹配源",还是我上面提议的"仅作 SPU 级确认信号,不直接决定 SKU"?我倾向后者——因为这个字段是 SPU 级的,不能单独指定某个 SKU。前者会带来"SKU A 其实不是卡其色、却被 SPU 级信号顶上去"的错案。
2. **image-pick 的分数阈值**:无意图时建议给个阈值(`min_score_no_intent`),有意图 fallback 时不加阈值。你接受这个不对称吗?接受的话我用 `0.55` 起步,上线后调。
你确认这两点,我就一把改完。
# 3-prompt
请综合考虑、全部完成,enriched_taxonomy_attributes的Color对应到值是和option1_name/option1_values/option2/option3一样对待的,就是当前使用的属性值匹配,多一个字段来源,即,即使对于颜色意图,同时把me对应的option value和enriched_taxonomy_attributes的Color对应到值一起判断,要考虑统一设计、精炼代码。image-pick设计和代码保持精炼,遵循统一的最优的设计,尽量不要fallback。
# 3-response
27/27 通过。
## 问题 2:是不是所有"选中"都享受 1.2x 提权?
**是的,目前无差别地都享受,这确实是个设计缺口。** 机制如下:
```490:490:search/rerank_client.py
return bool(str(hit.get("_style_rerank_suffix") or "").strip())
```
```641:642:search/rerank_client.py
sku_selected = _has_selected_sku(hit)
style_boost = style_intent_selected_sku_boost if sku_selected else 1.0
```
只要 `prepare_hits` 给 hit 写了 `_style_rerank_suffix`(只要 `selected_text` 非空),下游 `run_rerank` / `run_lightweight_rerank` 就把 `style_boost=1.2` 乘进 fused score。
当前 `_style_rerank_suffix` 的写入条件是 `decision.rerank_suffix` 非空 —— 等价于 `final_source ∈ {"text", "image"}`。也就是说:
| 选中来源 (`final_source`) | 是否吃 1.2x 款式提权(现状) |
|---|---|
| `text`(自有值命中意图词) | ✅ |
| `image`(仅图像相似度置顶) | ✅ ← 语义上不该等价 |
| `none` | ❌ |
把"图像置顶"和"文本命中"按同一强度做 SPU 级提权,存在两个隐患:
1. **置信度不同**:文本命中是显式属性匹配(强信号),图像 pick 是基于视觉相近(弱信号、还可能是 SPU 主图而非 SKU 图);给同样 1.2x 会让"图像近似 → SPU 排名拔高"发生在没有真正属性命中的情况下。
2. **无意图纯图像触发时更夸张**:query 无任何款式意图,但只要 image_knn 的 top url 能对上某个 SKU,整条 SPU 就吃到 1.2x,等于让向量相似度二次乘进融合。
建议的区分策略(**只改 rerank_client**,不改决策侧):
- 文本命中(`final_source="text"`)→ 保留 1.2x(可配置)
- 图像命中(`final_source="image"`)→ 1.0x(不加提权,仅做 SKU 置顶 + 改封面图)
最简实现:把"是否命中"从"后缀是否非空"改为"后缀非空 AND 是文本来源"。具体做法:
1. `prepare_hits` 只在 `final_source == "text"` 时写 `_style_rerank_suffix`;`image` 时不写,但仍把决策放进 `decisions`(`apply_precomputed_decisions` 照常置顶 SKU + 换封面图)。
2. 对应的 rerank doc 后缀也就不含图像源的颜色/尺码值(避免把一个"靠图选的 SKU"当作文本证据喂给 reranker)。
要我现在就把这条改了吗?或者你更希望 image 源也吃提权但用一个更小的系数(比如 1.05),这样保留视觉加分但不等同于文本命中。
# 4-prompt
两个问题,请修复:
1. option匹配,应该要能成功匹配才对:
{
"sku_id": "441679",
"price": 13,
"compare_at_price": 0,
"sku_code": "",
"stock": 100,
"weight": 0,
"weight_unit": "kg",
"option1_value": "卡其色(无内衬)",
2. 即使option没有匹配、而是依靠enriched_taxonomy_attributes 匹配的,那么rerank_suffix selected_text 也要翻译真实的匹配项,final_source也最好 enriched_taxonomy_attributes 和option有所区分
Selected SKU
{
"selected_sku_id": "441670",
"rerank_suffix": "白色(无内衬)",
"selected_text": "白色(无内衬)",
"final_source": "text",
# 4-response
## 改动摘要
### 问题 1(option 匹配)
我上一条消息里修的 "SKU 自有值优先于 taxonomy" 就解决了这个问题。用你给的样本跑:
```
query=卡其色裙子
skus=[441670:白色(无内衬), 441679:卡其色(无内衬)]
→ selected_sku_id: 441679
selected_text: 卡其色(无内衬)
final_source: option
```
全角括号之所以能命中是两个机制叠加:
- 全角 `(` 不在 `[\u4e00-\u9fff]` 范围内,分词器按 `["卡其色", "无内衬"]` 切分;
- 纯中文词 `卡其色` 落入 "pure-Han 子串回退" 分支,即使分词切分不一致也能匹配。
加了一个贴着真实数据命名的回归 case `test_zh_color_intent_matches_noisy_option_value_with_fullwidth_parens` 锁死。
### 问题 2(区分 taxonomy / option,`selected_text` 反映真实匹配项)
**决策结构扩展**:
```python
SkuSelectionDecision:
final_source: "option" | "taxonomy" | "image" | "none"
matched_sources: Dict[intent_type, "option"|"taxonomy"] # 逐意图细分
selected_text / rerank_suffix: 真正命中的原始文本(option 源为 sku.optionN_value;taxonomy 源为 enriched_taxonomy_attributes 对应 value)
```
**匹配逻辑**(`_find_text_matched_skus`):per-intent 走两条互斥路径,保留权威原则:
- SKU 在该维度有自有值 → 只用自有值判断 → 命中时 `source=option`
- SKU 该维度值为空(槽未解析 或 optionN 值空)→ 用 SPU taxonomy 判断 → 命中时 `source=taxonomy`
**整体来源聚合**:任一意图靠 taxonomy 命中 → 整体 `final_source="taxonomy"`;全部由 option 命中 → `final_source="option"`。这保留了"最弱文本证据"的含义,下游(比如 rerank boost)可以据此做差异化。
**真实匹配项回填示例**:
- `option1_name=颜色, option1_value=卡其色(无内衬)`:`selected_text="卡其色(无内衬)"`,`final_source=option`
- `option1_name=Style, enriched_taxonomy_attributes=[{Color, {zh:"卡其色"}}]`:`selected_text="卡其色"`(来自 taxonomy 原文),`final_source=taxonomy`
### 与 1.2x 款式提权的衔接(回答你上一条的后半问)
当前 `rerank_client` 判断 "是否选中" 的依据仍是 `_style_rerank_suffix` 是否非空——也就是说 `option` / `taxonomy` / `image` 三种来源目前仍一视同仁都吃 1.2x。**现在有了 `final_source` 作为可区分的信号,你想怎么分级,我马上就能改**。常见选择:
- `option`:1.2x(强信号)
- `taxonomy`:1.1x(SPU 级,弱一档)
- `image`:1.0x(不加提权,仅做 SKU 置顶 + 换封面)
要不要我把这三档都拉到 `config.yaml` 里做成可配?或者先把 image 档位降到 1.0x、其它保持不变?
# 5-prompt
q=藏青百褶半身裙
第五条结果
"spu_id": "68501",
Selected SKU
{
"selected_sku_id": null,
"rerank_suffix": "",
"selected_text": "",
"final_source": "none",
"matched_sources": {},
"resolved_dimensions": {
"color": "option1_value"
},
"image_pick": null
}
为什么image_pick也没生效
# 5-response
## 为什么当时 `image_pick` 也是 `null`
`image_pick` **只**来自 ES 返回的 `inner_hits`(`exact_image_knn_query_hits` 优先,其次 `image_knn_query_hits`),再用其中的 **URL** 去对齐 `skus[].image_src`。下面任一情况都会变成 `null`:
1. **没有 `inner_hits`**
- 例如:该文档不在 **exact KNN rescore** 的 `window_size` 内(若 `exact_knn_rescore_window` 配得很小,排第 5 条也可能拿不到 exact 的 inner_hits)。你这边配置是 160,第 5 条一般仍在窗口内。
- 或:召回阶段 **nested image KNN** 对该父文档没有产生 inner_hits(只靠文本等子句进结果时,有时 nested 子句侧没有可返回的条目)。
2. **有 inner_hits,但 URL 对不上任何 SKU**(最常见)
- `inner_hits` 里是 `https://cdn/a.jpg?w=800`,SKU 里是 `https://cdn/a.jpg`
- 或 CDN 域名不同、路径相同
- 或 `_source.url` 与索引里 `image_embedding` 的 canonical 写法不一致,而 SKU 用的是另一种写法
3. **以前预取 `_source` 里没有 `image_embedding`**
- ES 在 inner_hit 里给了 `_nested.offset`,但 `_source.url` 不可靠时,**无法**用 `image_embedding[offset].url` 再解析一次。
---
## 本次代码上做了什么(已提交到当前分支)
1. **URL 对齐放宽**:去掉 query、路径归一、`host+path` / 仅 path / 文件名 多级比对,减少「明明是一张图却匹配不上」。
2. **`_nested.offset` + `image_embedding[offset].url` 回退**:inner_hit 里若带 offset,会同时尝试 `_source.url` 和父文档里 `image_embedding[offset].url`(与索引侧一致)。
3. **`ImagePick.url`** 改为记录**真正用来对齐成功的那条候选 URL**(便于 debug)。
4. **Searcher**:在存在 **图像查询向量**(`_has_image_signal`)时,预取 `_source` 里增加 **`image_embedding`**,否则 offset 回退拿不到数据。
这样在「藏青百褶半身裙」这类 **文本未命中 SKU、指望图像兜底** 的场景里,只要 ES 给了 inner_hits 且最终能和我们归一化后的 SKU 图对齐,`image_pick` 就会非空;若仍为空,请在 **debug** 里看该条 hit 是否还有 `inner_hits`,以及 `exact_knn_rescore_window`、是否关掉了 rescore 等。
---
## 关于「藏青」文本为什么也可能没命中
词表里 **navy** 已含「藏青、藏蓝、深蓝」。若 SKU 的 `option1_value` 写成 **和词表不一致** 的表述(例如只有英文色号、或「青色系」等),仍可能文本全挂,这时更依赖上面的 **图像对齐**;若图像也对不上,就会是 `final_source: none` + `image_pick: null`。
# 6-prompt
# 6-response
# TODO
"inner_hits": {
"name": "exact_image_knn_query_hits",
"size": 1,
"_source": [
"url"
]
}
inner_hits获取exact_image_knn_query_hits的目的是:是因为哪个
参考:
feat(search): 款式意图下统一 SKU 选取(option/taxonomy/图像)与属性值匹配增强
## 主要能力
- 在 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/(本地评估大数据集,避免误提交)。
"rescore": {
"window_size": 160,
"query": {
"score_mode": "total",
"query_weight": 1,
"rescore_query_weight": 0,
"rescore_query": {
"bool": {
"should": [
{
"script_score": {
"_name": "exact_text_knn_query",
"query": {
"exists": {
"field": "title_embedding"
}
},
"script": {
"source": "((dotProduct(params.query_vector, 'title_embedding') + 1.0) / 2.0) * params.boost",
"params": {
"boost": 4
}
}
}
},
{
"nested": {
"path": "image_embedding",
"_name": "exact_image_knn_query",
"score_mode": "max",
"query": {
"script_score": {
"query": {
"exists": {
"field": "image_embedding.vector"
}
},
"script": {
"source": "((dotProduct(params.query_vector, 'image_embedding.vector') + 1.0) / 2.0) * params.boost",
"params": {
"boost": 4
}
}
}
},
"inner_hits": {
"name": "exact_image_knn_query_hits",
"size": 1,
"_source": [
"url"
]
}
}
}
],
"minimum_should_match": 1
}
}
}
},
|