dad3c867
tangwang
configs
|
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
|
增加款式意图识别模块。意图类型: 颜色,尺码(目前只需要支持这两种)
一、 意图判断
- 意图召回层:
每种意图,有一个召回词集合
对query(包括原始query、各种翻译query 都做匹配)
- 以颜色意图为例:
有一个词表,每一行 都逗号分割,互为同义词,行内第一个为标准化词
query匹配了其中任何一个词,都认为,具有颜色意图
匹配规则: 用细粒度、粗粒度分词,看是否有在词表中的。原始query分词、和每种翻译的分词,都要用。
二、 意图使用:
当前 SKU 置顶逻辑在「分页 + 详情回填」之后
流程是:run_rerank → 按 from/size 切片 → page fill → _apply_sku_sorting_for_page_hits → ResultFormatter
要改为:
1. 有款式意图的时候,才做sku筛选
2. sku筛选的时机,改为在reranker之前,对所有内容(rerank输入的所有spus)做sku筛选
3. 从仅 option1 扩展到多个维度,识别的意图,包含意图的维度名(color)和维度名的泛化词list(color、颜色、colour、colors...),遍历spu的option1_name,option2_name,option3_name字段,看哪个能匹配上意图的维度名list,哪个匹配上了,则在这个维度筛选。
1. 比如匹配到option2_name,那么取每一个sku的option2_values。如果没匹配到任何一个,那么把三个属性值都用空格拼接起来。这个值要记录下来。有两个作用:
1. 用来跟query匹配,看哪个更query相关性更高,以此进行最优sku筛选,把选出来的sku置顶,并替换spu的image_url
2. 用来做rerank doc的title补充,从而参与rerank
4. Rerank doc (有款式意图的时候)要带上属性后缀,拼接到title后面。在调用 run_rerank 前,对每条 hit 生成「用于重排的 doc 文本」(标题 + 可选后缀)
- sku筛选的规则也要优化:
现在的逻辑是,先做包含的判断,找到第一个 option_value被query包含的,则直接认为匹配。没有匹配的再用embedding相似度。
改为:
1. 第一轮:遍历完,如果有且仅有一个被query包含,那么认为匹配。
2. 第二轮:如果有多个符合(被query包含),跳到3。如果没有,对每个词都走泛化词表进行匹配。
3. 第三轮:如果有多个,那么对这多个,走embedding相关性取最高的。如果一个也没有,则对所有的走embedding相关性取最高的
这个sku筛选也需要提取为一个独立的模块。
细节备注:
intent 考虑由 QueryParser 编排、具体实现拆成独立模块,主义好,现有的分词等基础设施的复用,缺失的英文分词可以补充。
在重排窗口内,第一次 ES 查询会把 _source 裁成「重排模板需要的字段」,默认只有 title 等,不包含 skus / option*_name。因此,有意图的时候,需要给这一次的_source加上 skus / option*_name
先仔细理解需求,查看代码,深度思考应该如何设计,和当前的系统较好的融合,给出统一的设计,可以根据需要适当改造当前的实现,降低整个系统的复杂度,提高模块化程度,而不是打补丁。修改后的最终状态应该是要足够简单、清晰、无冗余和分叉,模块间低耦合。多步思考确认最佳施工方案之后才进行代码修改。
5. TODO: 搜索接口里,results[].skus 不是全量子 SKU:由 sku_filter_dimension 控制在应用层按维度分组折叠,每个「维度取值组合」只保留一条 SKU(组内第一条)。请求未传该字段时,Pydantic 默认是 ["option1"],等价于只按 option1_value 去重;服务端不会读取店铺主题的「主展示维」,需调用方与装修配置对齐并传入正确维度。因此当用户有款式等更细粒度意图、而款式落在 option2/option3(或对应 option*_name)时,若仍用默认只按 option1(常见为颜色)折叠,同一颜色下多种款式只会出现一条代表 SKU,无法从返回的 skus 里拿到该颜色下的全部款式行。(若业务需要全量子款,需传包含对应维度的 sku_filter_dimension,或传 null/[] 跳过折叠——以当前 ResultFormatter 实现为准。)
|
cda1cd62
tangwang
意图分析&应用 baseline
|
42
43
44
45
46
47
48
49
50
51
52
53
|
GPT5.4 主要思路:
我准备按这条线改,尽量把逻辑收束成两个新模块而不是继续堆在 Searcher 里:
QueryParser 负责产出统一的“款式意图分析结果”,包含:命中的意图类型、标准化值、命中的 query 变体、分词结果、维度候选词。
独立的 SKU 选择模块负责:维度解析、候选值抽取、三阶段匹配、选中 SKU 后的主图替换,以及生成 rerank 用的属性后缀。
Searcher 只编排:解析 query -> 首次 ES 召回 -> 若有款式意图则对 rerank 窗口内全部 hits 做 SKU 预筛选并补充 rerank doc 文本 -> run_rerank -> 分页 -> page fill -> ResultFormatter。这样旧的页内 SKU 置顶逻辑可以下沉或删除,避免前后两个阶段重复做同一件事。我再补看一下你最近写的意图草稿文档,确认有没有现成词表示例或边界条件,避免我这边把词表格式和你预期做偏。
|
2efad04b
tangwang
意图匹配的性能优化:
|
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
|
意图匹配的性能优化:
上面一版实现,性能上完全无法接受。因此进行了一轮策略简化
prompt
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
修改结果:
核心变化在 `search/sku_intent_selector.py` 和 `search/searcher.py`。
现在的策略是:
- `option_name` 只做规范化后 alias 精确匹配;任何意图维度没 resolve 到字段,就直接不选 SKU、不加 suffix。
- `option_value` 文本匹配改成顺序扫描 SKU,找到第一个命中的就直接返回,不再收集多个候选再比较。
- 文本匹配规则支持两种:
- query 命中的意图词被属性值包含
- 属性值被 query/translation 包含
- 只有当所有 SKU 都没有文本命中时,才做 embedding 选择。
- `prepare_hits()` 现在只产出决策和 `_style_rerank_suffix`,不再提前改 `_source`;真正的 `image_url` 替换和 SKU 置顶统一后移到 `apply_precomputed_decisions()`。
- 加了请求级缓存,避免同一个 `option_value`/`selection_text` 在一次请求里重复做文本判断、取向量和算相似度。
- 顺手删掉了旧的 generalized match / fallback_text 路径,代码比之前更短也更直。
另外补了回归测试,覆盖了:
- 翻译文本命中后正确选 SKU
- 多个文本命中时取第一个
- `option_name` 不命中 alias 时不做 SKU 选择
- 无文本命中时走 embedding fallback
验证过:
- `pytest tests/test_search_rerank_window.py -q` 通过
- 变更文件 lint 无报错
|
93be98cb
tangwang
清理过时的文档
|
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
|
------------------------------
---
## 1. 现状(与需求的差距)
**流水线**(`search/searcher.py`)大致是:
1. `QueryParser.parse` → `ParsedQuery`(含 `translations`、`query_tokens` 等)
2. 组 ES 查询;若在重排窗口内,第一次查询把 `_source` 裁成「重排模板所需字段」(`_resolve_rerank_source_filter`)
3. ES 搜索 → `run_rerank`(`rerank_client.build_docs_from_hits` 用 `{title}` 等拼 doc)
4. 按 `from/size` 切片 → **page fill** 用 `ids` 查询把当前页 `_source` 补全
5. `_apply_sku_sorting_for_page_hits`(仅 **option1**,先子串包含命中第一个,否则全量 option1 embedding)
6. `ResultFormatter`(`sku_filter_dimension` 只做**展示层**按维度折叠 SKU,与置顶逻辑独立)
**与需求冲突但必须一起解决的一点**:page fill 会用 ES 拉回来的 `_source`**整份覆盖**当前 hit(约 841–842 行)。若在 rerank **之前**只改内存里的 `skus` 顺序/`image_url`,**不**在 fill 后再处理一次,最终响应会被覆盖掉。因此「rerank 前对所有 window 内 hit 做 SKU 决策」和「用户看到的最终列表」之间,必须有一条**明确的数据契约**(见下文 §4)。
---
## 2. 模块划分(建议:`intent` + `sku_intent` 两层)
避免继续在 `Searcher` 里堆方法,建议新建小包,职责清晰、由 `Searcher` 编排。
| 模块 | 职责 |
|------|------|
| **`query/intent/`**(或 `search/intent/`,二选一以「离谁更近」为准;更推荐 **`query/intent`**,因为输入完全是 query 侧事实) | 加载词表、**意图召回**、多 query 变体 + 粗细分词、输出结构化 **`IntentProfile`** |
| **`search/sku_intent/`**(或 `intent/sku_selection.py`) | 根据 `IntentProfile` 解析 **option1/2/3** 哪一维、生成每 SKU 的**匹配文本**、三轮匹配规则、embedding 批处理、对 `_source` 做 **promote + image_url** |
| **`search/rerank_client.py`(薄扩展)** | 支持「每条 hit 的 doc 文本」:模板扩展或 **显式传入 per-hit 字符串列表**,避免把业务塞进 format 字符串 |
**`IntentProfile`(概念模型)建议包含**:
- `active_intents: Set[Literal["color","size"]]`(可扩展)
- 每种意图:`canonical_terms`(命中行的标准词)、`matched_surface_forms`(可选,用于 debug)
- **维度别名**:如 color → `{"color","颜色","colour",...}`(配置或独立小词表)
- 原始用于匹配的 token 集合:每个 query 变体 ×(细粒度 | 粗粒度),便于日志与单测
**词表**:
- **意图召回表**:每行逗号分隔同义词,首词标准化;颜色、尺码各一份(路径放 `config/` 或 `resources/intent/` + `config.yaml` 指路径)。
- **SKU 第二轮「泛化」表**(对 **option 取值** 做同义扩展):与意图召回表分开,避免语义混在一起。
---
## 3. 意图判断(与 `QueryParser` 的衔接)
需求:对 **原始 query + 各类翻译** 都做匹配;**细粒度 + 粗粒度** 分词。
现状:
- `ParsedQuery` 里 **`query_tokens` 只对 rewritten 后的 `query_text` 跑了一次 HanLP**(`query_parser.py` 269–274 行附近),**没有**对 `original_query`、各 `translations` 的 token 缓存。
- 已有 **`simple_tokenize_query`**(粗粒度)在 `query_parser.py`。
**建议**:
- 在 **`IntentDetector.detect(parsed_query, tokenizer_fn)`** 内统一生成「query 变体列表」:至少包含 `original_query`、`query_normalized`、`rewritten_query`、`translations` 的值(与当前 `_build_sku_query_texts` 思路一致,但升级为**结构化**)。
- 细粒度:复用 `QueryParser._get_query_tokens`(需把该方法暴露为公开 API 或注入同一 HanLP callable),对每个变体字符串调用。
- 粗粒度:对每个变体调用 `simple_tokenize_query`。
- 匹配逻辑:**任意变体 × 任意粒度** 的 token 落在「标准化 → 同义词闭包」上即视为命中该意图(与你描述的行内同义一致)。
**可选优化**:在 `parse()` 里顺带产出 `intent_profile`,减少一次遍历;但为控制 `QueryParser` 体积,更稳妥的是 **parse 之后**单独调 `IntentDetector`,依赖清晰。
---
## 4. 流水线改造(与 page fill 的契约)
目标顺序变为:
`ES(window)`(有意图时 `_source` 含 `skus` + `option*_name`)
→ **对每个 hit:SKU 决策 + 生成 rerank 用后缀/全文**
→ `run_rerank`(doc = 标题 + 后缀)
→ 切片
→ page fill
→ **最终响应前再应用一次 SKU 决策(或与 prefetch 结果合并)**
→ `ResultFormatter`
**为何最后还要一次?** 因为 page fill 会覆盖 `_source`,rerank 前内存里的 `skus` 顺序不能当作最终真相。
**推荐契约(降低复杂度)**:
1. **Rerank 前**:对 window 内每个 hit 计算 `SkuIntentDecision`(至少包含:`option_slot` 1/2/3、`candidate_sku_index` 或 `sku_id`、`rerank_suffix` 字符串)。可挂在 hit 的**非 ES 字段**上,例如 `hit["_intent_sku"] = {...}`(或只存 `rerank_doc_text` 全文)。
2. **`run_rerank`**:`build_docs_from_hits` 若发现 hit 上已有 `rerank_doc_text`(或 `style_suffix` + 模板),则优先使用,否则走原模板。
3. **Page fill 之后**:对**当前页** hit 再调用**同一** `SkuIntentSelector.apply(source, parsed_query, intent_profile)`(或根据 `_id` 合并 prefetch 决策)。这样最终 `image_url` / SKU 顺序与 rerank 一致,且不被 fill 冲掉。
若担心算两次 embedding:**第一次**在 window 全量上算 query 向量 + option 向量;第二次仅对当前页且可带缓存(按 `embed_key` 去重),一般量很小。
**不在重排窗口内**:没有「rerank 前全 window」这一步;可在 **ResultFormatter 前**对当前页 `es_hits` 用同一 `SkuIntentSelector`(仅当有意图时),与「有意图才做 SKU 筛选」一致。
---
## 5. `_resolve_rerank_source_filter` 与 ES 字段
需求:有意图时预取需包含 `skus`、`option1_name`、`option2_name`、`option3_name`。
建议签名扩展为:
`_resolve_rerank_source_filter(doc_template, intent_profile: Optional[IntentProfile])`
- 若 `intent_profile` 非空且含 color/size(或任意「款式意图」),在 `includes` 中**合并**上述字段(并与模板解析出的 `title` 等取并集)。
- 注意与全局 `source_fields` 的 tri-state 语义(`_apply_source_filter`)是否冲突:若租户配置 `_source` 白名单且不含 `skus`,需定义优先级——**建议**:「款式意图所需字段」作为**最低保证**合并进本次请求的 fetch includes,或在文档中写明限制。
---
## 6. 多维度 option 与「未匹配维度名」
需求逻辑可落到纯函数:
1. 对每个意图类型,有 **维度别名集合**(如 color)。
2. 依次与 `option1_name`、`option2_name`、`option3_name`(字符串,注意多语言:与 indexer 一致,可能是纯英文或中文)做 **casefold / 规范化** 后匹配别名表。
3. 命中则该 SKU 行的匹配字段为 `option{k}_value`;用于 embedding key 时继续用 `name:value` 形式(沿用现有 `_sku_option1_embedding_key` 思路,泛化为 `option_slot`)。
4. **若三个 name 都不匹配意图维度**:用 `option1_value`、`option2_value`、`option3_value` **空格拼接**成一条「兜底描述字符串」,供:
- 与 query 的包含/泛化/embedding 比较;
- 作为 `rerank_suffix` 的一部分(若你希望无明确维度时仍加强 rerank)。
**多意图同时存在**(如同时颜色+尺码):需要在产品层定规则,例如:
- 只对「主意图」排序(配置优先级 color > size),或
- 要求两个维度都满足的 SKU 优先,否则退化为单意图。
实现上可在 `SkuIntentSelector` 输入 `List[IntentType]` 与策略枚举,避免写死 if-else 散落。
---
## 7. 三轮 SKU 匹配规则(独立模块内)
从当前「第一个包含就返回」改为:
1. **第一轮**:统计「option 匹配文本被 **整条 query 文本** **包含**」的 SKU(或对每个 query 变体分别计,再合并——建议与你现有 `_build_sku_query_texts` 对齐);**若恰好 1 个** → 选中。
2. **第二轮**:若 0 个,对每个 SKU 的候选词走 **取值泛化表**(同义词行),再跑包含判断;仍统计「多个 / 零个」。
3. **第三轮**:
- 若 **多个** 满足包含(第一轮或第二轮)→ 仅在这多个上算 embedding,取相似度最高;
- 若 **仍 0 个** → 对 **全部** SKU 算 embedding,取最高。
实现上保持 **批量 encode**(与当前 `option1_values_to_encode` 去重逻辑类似),只是把「embed_key」从固定 option1 改为按 slot 动态生成。
---
## 8. `sku_filter_dimension`(API)与意图的关系
- **`sku_filter_dimension`**:客户端指定「结果里 SKU 列表如何按维度折叠」,在 `ResultFormatter._filter_skus_by_dimensions` 中实现。
- **意图 SKU 置顶**:服务端根据 query 推断维度与取值,改顺序与主图。
建议约定:
- **置顶 / 换图**仅在意图开启时执行;
- **`sku_filter_dimension` 仍只影响返回 SKU 条数结构**;若与意图维度冲突(例如意图命中 color,客户端只按 size 折叠),应用**文档说明优先级**:常见做法是 **先意图置顶,再 filter**(或相反,需在 PRD 写清)。
避免在 `ResultFormatter` 里再猜意图;意图结论由上游传入或在 Formatter 前已完成 `_source` 调整。
---
## 9. 配置与观测
- `config.yaml`:`intent.enabled`、`intent.lexicon_paths`、`intent.dimension_aliases`(或按类型分块)。
- `RequestContext` / `debug`:写入 `intent_profile`、`sku_intent_decision`、rerank 用的 doc 摘要,便于与 `docs/TODO-意图判断.md` 对齐。
---
## 10. 小结
- **核心架构**:**`IntentDetector`(query 侧)** + **`SkuIntentSelector`(search 侧)** + **`run_rerank` 的 per-hit doc 覆盖** + **`_resolve_rerank_source_filter` 条件 includes**。
- **必须处理 page fill 覆盖 `_source`**:rerank 前决策与 **fill 后再 apply 一次**(或等价合并策略),否则会出现「重排用了带后缀的 doc、返回结果却是未置顶 SKU」的不一致。
- **与现有系统融合点**:`ParsedQuery` 变体列表、HanLP + `simple_tokenize_query`、`TextEmbeddingEncoder`、`ResultFormatter` / `sku_filter_dimension` 的边界清晰,避免把意图逻辑复制到 `api/` 层。
若你后续希望把「多意图优先级」或「rerank 后缀格式」定成唯一产品规则,可以在实现前写进同一份 spec,模块接口会很好稳定下来。
|