43f1139f
tangwang
refactor: ES查询结构重...
|
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
|
# ES查询结构重构完成报告
**完成日期**: 2025-11-12
**核心原则**: 统一约定,保持简单,类型安全
---
## 问题回顾
### 原始问题
1. **Facets返回为空** - 已修复
2. **前端listing time筛选失败** - 已修复
3. **Filter不作用于KNN查询** - 已修复(核心问题)
### 根本原因
**KNN和query平级,导致filter只作用于query,KNN召回的结果没有被过滤。**
原结构(错误):
```json
{
"query": {
"bool": {
"must": [{"multi_match": {...}}],
"filter": [...] // 只作用于multi_match
}
},
"knn": {...} // 与query平级,filter不作用于它
}
```
---
## 解决方案
### 方案C:Function Score + Bool Should(已实施)
新结构(正确):
```json
{
"query": {
"function_score": {
"query": {
"bool": {
"must": [
{
"bool": {
"should": [
{"multi_match": {...}}, // 文本查询
{"knn": {...}} // KNN查询
],
"minimum_should_match": 1 // 至少匹配一个
}
}
],
"filter": [...] // 作用于整个查询
}
},
"functions": [
{
"filter": {"range": {"days_since_last_update": {"lte": 30}}},
"weight": 1.1
}
],
"score_mode": "sum",
"boost_mode": "multiply"
}
}
}
```
### 关键改进
1. **Filter统一作用** - 外层bool.filter同时作用于文本和KNN
2. **灵活召回** - 文本或KNN至少匹配一个(minimum_should_match=1)
3. **ES层打分** - function_score支持多种打分因子
4. **保留扩展性** - RerankEngine禁用但保留,未来可启用本地重排
---
## 修改文件清单
### 1. `/home/tw/SearchEngine/search/multilang_query_builder.py`
**修改点**:
- 重构 `build_multilang_query` 方法(156-210行)
- 新增 `_build_score_functions` 方法(212-237行)
**核心改动**:
```python
# 构建内层bool: 文本和KNN二选一
inner_bool_should = [query_clause]
if enable_knn and query_vector is not None:
inner_bool_should.append({"knn": {...}})
inner_bool = {
"bool": {
"should": inner_bool_should,
"minimum_should_match": 1
}
}
# 外层bool包含filter
outer_bool = {
"bool": {
"must": [inner_bool],
"filter": filter_clauses # 作用于整体
}
}
# function_score包裹
function_score_query = {
"function_score": {
"query": outer_bool,
"functions": self._build_score_functions(),
"score_mode": "sum",
"boost_mode": "multiply"
}
}
```
### 2. `/home/tw/SearchEngine/search/rerank_engine.py`(新建)
**来源**:从 `ranking_engine.py` 重命名
**修改**:
- 类名:`RankingEngine` → `RerankEngine`
- 添加 `enabled` 参数(默认False)
- 更新文档说明
**关键代码**:
```python
class RerankEngine:
"""本地重排引擎(当前禁用)"""
def __init__(self, ranking_expression: str, enabled: bool = False):
self.enabled = enabled
self.expression = ranking_expression
if enabled:
self.parsed_terms = self._parse_expression(ranking_expression)
def calculate_score(self, hit, base_score, knn_score=None):
if not self.enabled:
return base_score
# ... 原有逻辑
```
### 3. `/home/tw/SearchEngine/search/searcher.py`
**修改点**:
- 导入:`RankingEngine` → `RerankEngine`
- 初始化:`self.rerank_engine = RerankEngine(..., enabled=False)`
- 重排逻辑:检查 `self.rerank_engine.enabled`
### 4. `/home/tw/SearchEngine/search/__init__.py`
**修改**:
```python
from .rerank_engine import RerankEngine # 原 RankingEngine
```
### 5. `/home/tw/SearchEngine/search/ranking_engine.py`
**删除** - 已重命名为 `rerank_engine.py`
---
## 测试结果
### ✅ Test 1: Filter作用于文本查询
- 查询:"玩具"
- Filter: `categoryName_keyword = "桌面休闲玩具"`
- 结果:15 hits(正确过滤)
### ✅ Test 2: Filter作用于KNN查询
- 查询:"玩具"
- Range filter: `create_time >= "2023-01-01"`
- 结果:64 hits(正确过滤,KNN结果也被过滤)
- **验证**:所有返回结果的create_time都 >= 2023-01-01
### ✅ Test 3: Function Score时效性加权
- Function: `days_since_last_update <= 30 → weight 1.1`
- 结果:打分函数正常工作
### ✅ Test 4: 混合查询结构
- Inner bool.should包含2个子句:
- 文本查询(multi_match)
- KNN查询
- minimum_should_match=1(至少匹配一个)
### ✅ Test 5: Facets + Filters
- 返回正确的facets
- Selected字段正确标记
---
## ES Query 结构验证
### 完整查询示例
```json
{
"size": 5,
"from": 0,
"query": {
"function_score": {
"query": {
"bool": {
"must": [
{
"bool": {
"should": [
{
"bool": {
"should": [
{"multi_match": {"query": "玩具", "fields": [...]}}
],
"minimum_should_match": 1
}
},
{
"knn": {
"field": "name_embedding",
"query_vector": [...],
"k": 50,
"num_candidates": 200
}
}
],
"minimum_should_match": 1
}
}
],
"filter": [
{"term": {"categoryName_keyword": "桌面休闲玩具"}},
{"range": {"create_time": {"gte": "2023-01-01T00:00:00Z"}}}
]
}
},
"functions": [
{
"filter": {
"range": {"days_since_last_update": {"lte": 30}}
},
"weight": 1.1
}
],
"score_mode": "sum",
"boost_mode": "multiply"
}
}
}
```
### 结构分析
**三层嵌套**:
1. **最外层**:`function_score` - 支持额外打分因子
2. **外层bool**:包含`must`和`filter` - filter作用于所有查询
3. **内层bool**:包含`should`子句 - 文本OR KNN
**数据流**:
```
用户查询
→ 文本查询 OR KNN查询(至少一个匹配)
→ 应用filter(同时过滤文本和KNN结果)
→ 应用function_score加权
→ 返回最终结果
```
---
## 架构优势
### 1. 正确性
- ✅ Filter同时作用于文本和KNN
- ✅ 不会有未过滤的KNN结果混入
### 2. 灵活性
- ✅ 文本或KNN至少匹配一个(更高召回)
- ✅ Function score支持多种打分因子
- ✅ 保留RerankEngine用于未来扩展
### 3. 性能
- ✅ Filter在ES层执行(硬过滤,不参与打分)
- ✅ Function score在ES层执行(无需本地重排)
- ✅ 减少数据传输(已过滤)
### 4. 可维护性
- ✅ 查询结构清晰
- ✅ 统一约定,不做兼容
- ✅ 类型安全(Pydantic模型)
---
## 命名规范
### RankingEngine → RerankEngine
**语义区分**:
- **Ranking** - 排序、打分(通常指ES层的原生排序)
- **Rerank** - 重排序(通常指对ES结果的二次排序)
**新架构**:
- **ES层**:使用 `function_score` 进行打分和排序
- **应用层**:使用 `RerankEngine` 进行本地重排(当前禁用)
**状态**:
- `RerankEngine.enabled = False` - 暂时禁用
- 未来如需复杂个性化排序可启用
---
## 对比总结
| 方面 | 重构前 | 重构后 |
|------|-------|--------|
| **KNN位置** | 与query平级 | 在bool.should内 |
| **Filter作用** | 只作用于文本 | 同时作用于文本和KNN |
| **召回策略** | 文本必须匹配 | 文本OR KNN至少一个 |
| **打分方式** | 本地重排 | ES function_score |
| **时效性加权** | 本地计算 | ES function加权 |
| **Rerank** | RankingEngine启用 | RerankEngine禁用 |
| **前端错误** | 422错误 | 正常工作 |
---
## 后续优化建议
### 1. Function Score扩展
可添加更多打分因子:
```yaml
functions:
- filter: {range: {days_since_last_update: {lte: 30}}}
weight: 1.1
- filter: {term: {is_video: true}}
weight: 1.05
- field_value_factor:
field: sales_count
modifier: log1p
factor: 0.01
```
### 2. RerankEngine应用场景
未来如需启用本地重排:
- 实时个性化(基于用户画像)
- 复杂业务规则(无法用ES表达)
- A/B测试(不同排序策略)
### 3. 性能优化
- 添加查询缓存
- 优化embedding生成
- 监控function_score性能影响
### 4. 测试覆盖
- 添加集成测试
- 性能基准测试
- 边界情况测试
---
## 总结
### ✅ 核心成就
1. **修复Filter问题** - Filter现在同时作用于文本和KNN
2. **统一约定** - 全系统使用Pydantic模型,不做兼容
3. **优化打分** - 使用ES function_score,性能更好
4. **命名规范** - RerankEngine语义更清晰
5. **代码简洁** - 移除所有兼容代码
### 🎯 架构原则
**"统一约定,不做兼容,保持简单"**
- Pydantic模型贯穿全系统
- 单一数据流
- 明确的类型定义
- 清晰的职责划分
### 📊 代码质量
- ✅ 无Linter错误
- ✅ 类型安全
- ✅ 所有测试通过
- ✅ 代码简洁清晰
---
**版本**: v3.3
**状态**: ✅ 完成并通过测试
**下一步**: 根据业务需求调整function_score权重
|