3a950275
tangwang
导入测试数据
|
1
|
# 搜索引擎通用化开发进度
|
b926f678
tangwang
多语言查询
|
2
|
|
3a950275
tangwang
导入测试数据
|
3
|
## 项目概述
|
b926f678
tangwang
多语言查询
|
4
5
|
对后端搜索技术 做通用化。
|
b926f678
tangwang
多语言查询
|
6
7
|
通用化的本质 是 对于各种业务数据、各种检索需求,都可以 用少量定制+配置化 来实现效果。
|
b926f678
tangwang
多语言查询
|
8
|
|
3a950275
tangwang
导入测试数据
|
9
10
11
12
13
14
|
**通用化的本质**:对于各种业务数据、各种检索需求,都可以用少量定制+配置化来实现效果。
---
## 1. 原始数据层的约定
|
3a950275
tangwang
导入测试数据
|
15
16
17
18
19
20
|
### 1.1 店匠主表
所有租户共用以下主表:
- `shoplazza_product_sku` - SKU级别商品数据
- `shoplazza_product_spu` - SPU级别商品数据
|
1f6d15fa
tangwang
重构:SPU级别索引、统一索引架构...
|
21
22
23
24
25
26
|
### 1.2 索引结构(SPU维度)
**统一索引架构**:
- 所有客户共享同一个Elasticsearch索引:`search_products`
- 索引粒度:SPU级别(每个文档代表一个SPU)
- 数据隔离:通过`tenant_id`字段实现租户隔离
|
cadc77b6
tangwang
索引字段名、变量名、API数据结构...
|
27
|
- 嵌套结构:每个SPU文档包含嵌套的`skus`数组
|
1f6d15fa
tangwang
重构:SPU级别索引、统一索引架构...
|
28
29
30
31
32
|
**索引文档结构**:
```json
{
"tenant_id": "1",
|
cadc77b6
tangwang
索引字段名、变量名、API数据结构...
|
33
|
"spu_id": "123",
|
ca91352a
tangwang
更新文档
|
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
|
"title_zh": "蓝牙耳机",
"title_en": "Bluetooth Headphones",
"brief_zh": "高品质蓝牙耳机",
"brief_en": "High-quality Bluetooth headphones",
"category_name": "电子产品",
"category_path_zh": "电子产品/音频设备/耳机",
"category_path_en": "Electronics/Audio/Headphones",
"category1_name": "电子产品",
"category2_name": "音频设备",
"category3_name": "耳机",
"vendor_zh": "品牌A",
"vendor_en": "Brand A",
"min_price": 199.99,
"max_price": 299.99,
"option1_name": "color",
"option2_name": "size",
"specifications": [
{
"sku_id": "456",
"name": "color",
"value": "black"
},
{
"sku_id": "456",
"name": "size",
"value": "large"
}
],
|
cadc77b6
tangwang
索引字段名、变量名、API数据结构...
|
62
|
"skus": [
|
1f6d15fa
tangwang
重构:SPU级别索引、统一索引架构...
|
63
|
{
|
cadc77b6
tangwang
索引字段名、变量名、API数据结构...
|
64
|
"sku_id": "456",
|
1f6d15fa
tangwang
重构:SPU级别索引、统一索引架构...
|
65
|
"price": 199.99,
|
ca91352a
tangwang
更新文档
|
66
67
68
69
70
71
72
73
74
|
"compare_at_price": 249.99,
"sku_code": "SKU-123-1",
"stock": 50,
"weight": 0.2,
"weight_unit": "kg",
"option1_value": "black",
"option2_value": "large",
"option3_value": null,
"image_src": "https://example.com/image.jpg"
|
1f6d15fa
tangwang
重构:SPU级别索引、统一索引架构...
|
75
76
|
}
],
|
ca91352a
tangwang
更新文档
|
77
78
79
80
81
82
83
|
"title_embedding": [0.1, 0.2, ...], // 1024维向量
"image_embedding": [
{
"vector": [0.1, 0.2, ...], // 1024维向量
"url": "https://example.com/image.jpg"
}
]
|
1f6d15fa
tangwang
重构:SPU级别索引、统一索引架构...
|
84
85
|
}
```
|
3a950275
tangwang
导入测试数据
|
86
|
|
ca91352a
tangwang
更新文档
|
87
|
### 1.3 索引结构简化方案
|
3a950275
tangwang
导入测试数据
|
88
|
|
ca91352a
tangwang
更新文档
|
89
90
91
92
93
|
**简化原则**:
- **硬编码映射**:ES mapping 结构直接定义在 JSON 文件中(`mappings/search_products.json`)
- **统一索引结构**:所有租户共享相同的索引结构,通过 `tenant_id` 隔离数据
- **数据源统一**:所有租户使用相同的 MySQL 表结构(店匠标准表)
- **查询配置硬编码**:查询相关配置(字段 boost、查询域等)硬编码在 `search/query_config.py`
|
1f6d15fa
tangwang
重构:SPU级别索引、统一索引架构...
|
94
|
|
ca91352a
tangwang
更新文档
|
95
96
97
98
99
100
101
102
|
**索引结构特点**:
1. **多语言字段**:所有文本字段支持中英文(`title_zh/en`, `brief_zh/en`, `description_zh/en`, `vendor_zh/en`, `category_path_zh/en`, `category_name_zh/en`)
2. **嵌套字段**:
- `skus`: SKU 嵌套数组(包含价格、库存、选项值等)
- `specifications`: 规格嵌套数组(包含 name、value、sku_id)
- `image_embedding`: 图片向量嵌套数组
3. **扁平化字段**:`sku_prices`, `sku_weights`, `total_inventory` 等用于过滤和排序
4. **向量字段**:`title_embedding`(1024维)用于语义搜索
|
1f6d15fa
tangwang
重构:SPU级别索引、统一索引架构...
|
103
|
|
ca91352a
tangwang
更新文档
|
104
105
106
107
|
**实现文件**:
- `mappings/search_products.json` - ES mapping 定义(硬编码)
- `search/query_config.py` - 查询配置(硬编码)
- `indexer/mapping_generator.py` - 加载 JSON mapping 并创建索引
|
3a950275
tangwang
导入测试数据
|
108
109
110
|
---
|
ca91352a
tangwang
更新文档
|
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
|
## 2. 索引结构实现
### 2.1 硬编码映射方案
**实现方式**:
- ES mapping 直接定义在 `mappings/search_products.json` 文件中
- 所有租户共享相同的索引结构
- 查询配置硬编码在 `search/query_config.py`
**索引字段结构**:
#### 基础字段
- `tenant_id` (keyword): 租户ID,用于数据隔离
- `spu_id` (keyword): SPU唯一标识
- `create_time`, `update_time` (date): 创建和更新时间
#### 多语言文本字段
- `title_zh/en` (text): 标题(中英文)
- `brief_zh/en` (text): 短描述(中英文)
- `description_zh/en` (text): 详细描述(中英文)
- `vendor_zh/en` (text): 供应商/品牌(中英文)
- `category_path_zh/en` (text): 类目路径(中英文)
- `category_name_zh/en` (text): 类目名称(中英文)
**分析器配置**:
- 中文字段:`hanlp_index`(索引时)/ `hanlp_standard`(查询时)
- 英文字段:`english`
- `vendor` 字段包含 `keyword` 子字段(normalizer: lowercase)
#### 分类字段
- `category_id` (keyword): 类目ID
- `category_name` (keyword): 类目名称
- `category_level` (integer): 类目层级
- `category1_name`, `category2_name`, `category3_name` (keyword): 多级类目名称
#### 规格字段(Specifications)
- `specifications` (nested): 规格嵌套数组
- `sku_id` (keyword): SKU ID
- `name` (keyword): 规格名称(如 "color", "size")
- `value` (keyword): 规格值(如 "white", "256GB")
**用途**:
- 支持按规格过滤:`{"specifications": {"name": "color", "value": "white"}}`
- 支持规格分面:`["specifications"]` 或 `["specifications.color"]`
#### SKU嵌套字段
- `skus` (nested): SKU嵌套数组
- `sku_id`, `price`, `compare_at_price`, `sku_code`
- `stock`, `weight`, `weight_unit`
- `option1_value`, `option2_value`, `option3_value`
- `image_src` (index: false)
#### 选项名称字段
- `option1_name`, `option2_name`, `option3_name` (keyword): 选项名称(如 "color", "size")
#### 扁平化字段
- `min_price`, `max_price`, `compare_at_price` (float): 价格字段
- `sku_prices` (float[]): 所有SKU价格数组
- `sku_weights` (long[]): 所有SKU重量数组
- `total_inventory` (long): 总库存
#### 向量字段
- `title_embedding` (dense_vector, 1024维): 标题向量,用于语义搜索
- `image_embedding` (nested): 图片向量数组
- `vector` (dense_vector, 1024维)
- `url` (text)
|
1f6d15fa
tangwang
重构:SPU级别索引、统一索引架构...
|
177
|
|
3a950275
tangwang
导入测试数据
|
178
|
**实现模块**:
|
ca91352a
tangwang
更新文档
|
179
180
181
|
- `mappings/search_products.json` - ES mapping 定义
- `indexer/mapping_generator.py` - 加载 JSON mapping 并创建索引
- `search/query_config.py` - 查询配置(字段 boost、查询域等)
|
3a950275
tangwang
导入测试数据
|
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
|
### 2.2 索引结构配置(查询域配置)
**配置内容**:定义了 ES 的字段索引 mapping 配置,支持各个域的查询,包括默认域的查询。
**实现情况**:
#### 域(Domain)配置
每个域定义了:
- 域名称(如 `default`, `title`, `category`, `brand`)
- 域标签(中文描述)
- 搜索字段列表
- 默认分析器
- 权重(boost)
- **多语言字段映射**(`language_field_mapping`)
#### 多语言字段映射
支持将不同语言的查询路由到对应的字段:
```yaml
indexes:
- name: "default"
label: "默认索引"
fields:
- "name"
- "enSpuName"
- "ruSkuName"
- "categoryName"
- "brandName"
analyzer: "chinese_ecommerce"
boost: 1.0
language_field_mapping:
zh:
- "name"
- "categoryName"
- "brandName"
en:
- "enSpuName"
ru:
- "ruSkuName"
- name: "title"
label: "标题索引"
fields:
- "name"
- "enSpuName"
- "ruSkuName"
analyzer: "chinese_ecommerce"
boost: 2.0
language_field_mapping:
zh:
- "name"
en:
- "enSpuName"
ru:
- "ruSkuName"
```
**工作原理**:
1. 检测查询语言(中文、英文、俄文等)
2. 如果查询语言在 `language_field_mapping` 中,使用原始查询搜索对应语言的字段
3. 将查询翻译到其他支持的语言,分别搜索对应语言的字段
4. 组合多个语言查询的结果,提高召回率
**实现模块**:
|
f7d3cf70
tangwang
更新文档
|
248
|
- `search/es_query_builder.py` - ES 查询构建器(单层架构)
|
3a950275
tangwang
导入测试数据
|
249
|
- `query/query_parser.py` - 查询解析器(支持语言检测和翻译)
|
ca91352a
tangwang
更新文档
|
250
|
- `search/query_config.py` - 查询配置(字段 boost、查询域等)
|
3a950275
tangwang
导入测试数据
|
251
252
253
|
---
|
1f6d15fa
tangwang
重构:SPU级别索引、统一索引架构...
|
254
|
## 3. 数据导入流程
|
3a950275
tangwang
导入测试数据
|
255
256
257
|
### 3.1 数据源
|
1f6d15fa
tangwang
重构:SPU级别索引、统一索引架构...
|
258
259
260
|
**店匠标准表**(Base配置使用):
- `shoplazza_product_spu` - SPU级别商品数据
- `shoplazza_product_sku` - SKU级别商品数据
|
3a950275
tangwang
导入测试数据
|
261
|
|
ae5a294d
tangwang
命名修改、代码清理
|
262
|
**其他客户表**(tenant1等):
|
1f6d15fa
tangwang
重构:SPU级别索引、统一索引架构...
|
263
|
- 使用各自的数据源表和扩展表
|
3a950275
tangwang
导入测试数据
|
264
|
|
1f6d15fa
tangwang
重构:SPU级别索引、统一索引架构...
|
265
|
### 3.2 数据导入方式
|
3a950275
tangwang
导入测试数据
|
266
|
|
ca91352a
tangwang
更新文档
|
267
268
269
270
|
**数据源统一**:
- 所有租户使用相同的MySQL表结构(店匠标准表)
- 数据转换逻辑写死在转换器代码中
- 索引结构硬编码,不依赖配置
|
3a950275
tangwang
导入测试数据
|
271
|
|
ca91352a
tangwang
更新文档
|
272
|
#### 数据导入流程(店匠通用)
|
1f6d15fa
tangwang
重构:SPU级别索引、统一索引架构...
|
273
274
|
**脚本**:`scripts/ingest_shoplazza.py`
|
3a950275
tangwang
导入测试数据
|
275
|
|
1f6d15fa
tangwang
重构:SPU级别索引、统一索引架构...
|
276
|
**数据流程**:
|
ca91352a
tangwang
更新文档
|
277
278
279
280
281
|
1. **数据加载**:
- 从MySQL读取`shoplazza_product_spu`表(SPU数据)
- 从MySQL读取`shoplazza_product_sku`表(SKU数据)
- 从MySQL读取`shoplazza_product_option`表(选项定义)
|
1f6d15fa
tangwang
重构:SPU级别索引、统一索引架构...
|
282
283
|
2. **数据转换**(`indexer/spu_transformer.py`):
- 按`spu_id`和`tenant_id`关联SPU和SKU数据
|
ca91352a
tangwang
更新文档
|
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
|
- **多语言字段映射**:
- MySQL的`title` → ES的`title_zh`(英文字段设为空)
- 其他文本字段类似处理
- **分类字段映射**:
- 从SPU表的`category_path`解析多级类目(`category1_name`, `category2_name`, `category3_name`)
- 映射`category_id`, `category_name`, `category_level`
- **规格字段构建**(`specifications`):
- 从`shoplazza_product_option`表获取选项名称(`name`)
- 从SKU的`option1/2/3`字段获取选项值(`value`)
- 构建嵌套数组:`[{"sku_id": "...", "name": "color", "value": "white"}, ...]`
- **选项名称映射**:
- 从`shoplazza_product_option`表获取`option1_name`, `option2_name`, `option3_name`
- **SKU嵌套数组构建**:
- 包含所有SKU字段(价格、库存、选项值、图片等)
- **扁平化字段计算**:
- `min_price`, `max_price`: 从所有SKU价格计算
- `sku_prices`: 所有SKU价格数组
- `total_inventory`: SKU库存总和
|
1f6d15fa
tangwang
重构:SPU级别索引、统一索引架构...
|
302
|
- 注入`tenant_id`字段
|
ca91352a
tangwang
更新文档
|
303
|
|
3a950275
tangwang
导入测试数据
|
304
|
3. **索引创建**:
|
ca91352a
tangwang
更新文档
|
305
|
- 从`mappings/search_products.json`加载ES mapping
|
1f6d15fa
tangwang
重构:SPU级别索引、统一索引架构...
|
306
|
- 创建或更新`search_products`索引
|
ca91352a
tangwang
更新文档
|
307
|
|
3a950275
tangwang
导入测试数据
|
308
|
4. **批量入库**:
|
1f6d15fa
tangwang
重构:SPU级别索引、统一索引架构...
|
309
|
- 批量写入ES(默认每批500条)
|
3a950275
tangwang
导入测试数据
|
310
311
|
- 错误处理和重试机制
|
1f6d15fa
tangwang
重构:SPU级别索引、统一索引架构...
|
312
313
314
315
316
317
318
319
320
321
322
323
324
|
**命令行工具**:
```bash
python scripts/ingest_shoplazza.py \
--db-host localhost \
--db-port 3306 \
--db-database saas \
--db-username root \
--db-password password \
--tenant-id "1" \
--config base \
--es-host http://localhost:9200 \
--recreate \
--batch-size 500
|
3a950275
tangwang
导入测试数据
|
325
326
|
```
|
1f6d15fa
tangwang
重构:SPU级别索引、统一索引架构...
|
327
328
329
330
331
|
#### 其他客户数据导入
- 使用各自的数据转换器(如`indexer/data_transformer.py`)
- 数据源映射逻辑写死在各自的转换器中
- 共享`search_products`索引,通过`tenant_id`隔离
|
3a950275
tangwang
导入测试数据
|
332
333
|
**实现模块**:
|
1f6d15fa
tangwang
重构:SPU级别索引、统一索引架构...
|
334
335
|
- `indexer/spu_transformer.py` - SPU数据转换器(Base配置)
- `indexer/data_transformer.py` - 通用数据转换器(其他客户)
|
3a950275
tangwang
导入测试数据
|
336
|
- `indexer/bulk_indexer.py` - 批量索引器
|
1f6d15fa
tangwang
重构:SPU级别索引、统一索引架构...
|
337
|
- `scripts/ingest_shoplazza.py` - 店匠数据导入脚本
|
3a950275
tangwang
导入测试数据
|
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
|
---
## 4. QueryParser 实现
### 4.1 查询改写(Query Rewriting)
配置词典的key是query,value是改写后的查询表达式,比如。比如品牌词 改写为在brand|query OR name|query,类别词、标签词等都可以放进去。纠错、规范化、查询改写等 都可以通过这个词典来配置。
**实现情况**:
#### 配置方式
在 `query_config.rewrite_dictionary` 中配置查询改写规则:
```yaml
query_config:
enable_query_rewrite: true
rewrite_dictionary:
"芭比": "brand:芭比 OR name:芭比娃娃"
"玩具": "category:玩具"
"消防": "category:消防 OR name:消防"
```
#### 功能特性
- **精确匹配**:查询完全匹配词典 key 时,替换为 value
- **部分匹配**:查询包含词典 key 时,替换该部分
- **支持布尔表达式**:value 可以是复杂的布尔表达式(AND, OR, 域查询等)
#### 实现模块
- `query/query_rewriter.py` - 查询改写器
- `query/query_parser.py` - 查询解析器(集成改写功能)
### 4.2 翻译(Translation)
**实现情况**:
#### 配置方式
```yaml
query_config:
supported_languages:
- "zh"
- "en"
- "ru"
default_language: "zh"
enable_translation: true
translation_service: "deepl"
translation_api_key: null # 通过环境变量设置
```
#### 功能特性
1. **语言检测**:自动检测查询语言
2. **智能翻译**:
- 如果查询是中文,翻译为英文、俄文
- 如果查询是英文,翻译为中文、俄文
- 如果查询是其他语言,翻译为所有支持的语言
3. **域感知翻译**:
- 如果域有 `language_field_mapping`,只翻译到映射中存在的语言
- 避免不必要的翻译,提高效率
4. **翻译缓存**:缓存翻译结果,避免重复调用 API
#### 工作流程
```
|
f7d3cf70
tangwang
更新文档
|
400
|
查询输入 → 语言检测 → 翻译 → 查询构建(filters and (text_recall or embedding_recall))
|
3a950275
tangwang
导入测试数据
|
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
|
```
#### 实现模块
- `query/language_detector.py` - 语言检测器
- `query/translator.py` - 翻译器(DeepL API)
- `query/query_parser.py` - 查询解析器(集成翻译功能)
### 4.3 文本向量化(Text Embedding)
如果配置打开了text_embedding查询,并且query 包含了default域的查询,那么要把default域的查询词转向量,后面searcher会用这个向量参与查询。
**实现情况**:
#### 配置方式
```yaml
query_config:
enable_text_embedding: true
```
#### 功能特性
1. **条件生成**:
- 仅当 `enable_text_embedding=true` 时生成向量
- 仅对 `default` 域查询生成向量
2. **向量模型**:BGE-M3 模型(1024维向量)
3. **用途**:用于语义搜索(KNN 检索)
#### 实现模块
- `embeddings/bge_encoder.py` - BGE 文本编码器
- `query/query_parser.py` - 查询解析器(集成向量生成)
---
## 5. Searcher 实现
参考opensearch,他们自己定义的一套索引结构配置、支持自定义的一套检索表达式、排序表达式,这是各个客户进行配置化的基础,包括索引结构配置、排序策略配置。
比如各种业务过滤策略 可以简单的通过表达式满足,比如brand|耐克 AND cate2|xxx。指定字段排序可以通过排序的表达式实现。
查询默认在default域,相也会对这个域的查询做一些相关性的重点优化,包括融合语义相关性、多语言相关性(可以基于配置 将查询翻译到指定语言并在对应的语言的字段进行查询)来弥补传统查询分析手段(比如查询改写 纠错 词权重等)的不足,也支持通过配置一些词表转为泛查询模式来优化相关性。
### 5.1 布尔表达式解析
**实现情况**:
#### 支持的运算符
- **AND**:所有项必须匹配
- **OR**:任意项匹配
- **RANK**:排序增强(类似 OR 但影响排序)
- **ANDNOT**:排除(第一项匹配,第二项不匹配)
- **()**:括号分组
#### 优先级(从高到低)
1. `()` - 括号
2. `ANDNOT` - 排除
3. `AND` - 与
4. `OR` - 或
5. `RANK` - 排序
#### 示例
```
laptop AND (gaming OR professional) ANDNOT cheap
```
#### 实现模块
- `search/boolean_parser.py` - 布尔表达式解析器
- `search/searcher.py` - 搜索器(集成布尔解析)
### 5.2 多语言搜索
**实现情况**:
#### 工作原理
1. **查询解析**:
- 提取域(如 `title:查询` → 域=`title`,查询=`查询`)
- 检测查询语言
- 生成翻译
|
f7d3cf70
tangwang
更新文档
|
476
477
478
|
2. **查询构建**(简化架构):
- **结构**: `filters AND (text_recall OR embedding_recall)`
- **filters**: 前端传递的过滤条件(永远起作用,放在 `filter` 中)
|
ca91352a
tangwang
更新文档
|
479
480
481
482
|
- 普通字段过滤:`{"category_name": "手机"}`
- 范围过滤:`{"min_price": {"gte": 50, "lte": 200}}`
- **Specifications嵌套过滤**:
- 单个规格:`{"specifications": {"name": "color", "value": "white"}}`
|
85f08823
tangwang
过滤逻辑
|
483
484
|
- 多个规格:`{"specifications": [{"name": "color", "value": "white"}, {"name": "size", "value": "256GB"}]}`
- 过滤逻辑:不同维度(不同name)是AND关系,相同维度(相同name)的多个值是OR关系
|
ca91352a
tangwang
更新文档
|
485
|
- 使用ES的`nested`查询实现
|
f7d3cf70
tangwang
更新文档
|
486
487
488
|
- **text_recall**: 文本相关性召回
- 同时搜索中英文字段(`title_zh/en`, `brief_zh/en`, `description_zh/en`, `vendor_zh/en`, `category_path_zh/en`, `category_name_zh/en`, `tags`)
- 使用 `multi_match` 查询,支持字段 boost
|
ca91352a
tangwang
更新文档
|
489
|
- 中文字段使用中文分词器,英文字段使用英文分析器
|
f7d3cf70
tangwang
更新文档
|
490
491
|
- **embedding_recall**: 向量召回(KNN)
- 使用 `title_embedding` 字段进行 KNN 搜索
|
ca91352a
tangwang
更新文档
|
492
|
- ES 自动与文本召回合并(OR逻辑)
|
f7d3cf70
tangwang
更新文档
|
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
|
- **function_score**: 包装召回部分,支持提权字段(新鲜度、销量等)
#### 查询结构示例
```json
{
"query": {
"bool": {
"must": [
{
"function_score": {
"query": {
"multi_match": {
"query": "手机",
"fields": [
"title_zh^3.0", "title_en^3.0",
"brief_zh^1.5", "brief_en^1.5",
...
]
}
},
"functions": [...]
}
}
],
"filter": [
{"term": {"tenant_id": "2"}},
{"term": {"category_name": "手机"}}
]
}
},
"knn": {
"field": "title_embedding",
"query_vector": [...],
"k": 50,
"boost": 0.2
}
}
|
3a950275
tangwang
导入测试数据
|
530
531
532
|
```
#### 实现模块
|
f7d3cf70
tangwang
更新文档
|
533
534
|
- `search/es_query_builder.py` - ES 查询构建器(单层架构,`build_query` 方法)
- `search/searcher.py` - 搜索器(使用 `ESQueryBuilder`)
|
3a950275
tangwang
导入测试数据
|
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
|
### 5.3 相关性计算(Ranking)
**实现情况**:
#### 当前实现
**公式**:`bm25() + 0.2 * text_embedding_relevance()`
- **bm25()**:BM25 文本相关性得分
- 包括多语言打分
- 内部通过配置翻译为多种语言
- 分别到对应的字段搜索
- 中文字段使用中文分词器,英文字段使用英文分词器
- **text_embedding_relevance()**:文本向量相关性得分(KNN 检索的打分)
- 权重:0.2
#### 配置方式
```yaml
ranking:
expression: "bm25() + 0.2*text_embedding_relevance()"
description: "BM25 text relevance combined with semantic embedding similarity"
```
#### 扩展性
- 支持表达式配置(未来可扩展)
- 支持自定义函数(如 `timeliness()`, `field_value()`)
#### 实现模块
- `search/ranking_engine.py` - 排序引擎
- `search/searcher.py` - 搜索器(集成排序功能)
|
b926f678
tangwang
多语言查询
|
565
|
|
3a950275
tangwang
导入测试数据
|
566
|
---
|
b926f678
tangwang
多语言查询
|
567
|
|
3a950275
tangwang
导入测试数据
|
568
|
## 6. 已完成功能总结
|
b926f678
tangwang
多语言查询
|
569
|
|
3a950275
tangwang
导入测试数据
|
570
571
572
573
574
575
|
### 6.1 配置系统
- ✅ 字段定义配置(类型、分析器、来源表/列)
- ✅ 索引域配置(多域查询、多语言映射)
- ✅ 查询配置(改写词典、翻译配置)
- ✅ 排序配置(表达式配置)
- ✅ 配置验证(字段存在性、类型检查、分析器匹配)
|
b926f678
tangwang
多语言查询
|
576
|
|
3a950275
tangwang
导入测试数据
|
577
578
579
580
581
582
|
### 6.2 数据索引
- ✅ 数据转换(字段映射、类型转换)
- ✅ 向量生成(文本向量、图片向量)
- ✅ 向量缓存(避免重复计算)
- ✅ 批量索引(错误处理、重试机制)
- ✅ ES mapping 自动生成
|
b926f678
tangwang
多语言查询
|
583
|
|
3a950275
tangwang
导入测试数据
|
584
585
586
587
588
589
|
### 6.3 查询处理
- ✅ 查询改写(词典配置)
- ✅ 语言检测
- ✅ 多语言翻译(DeepL API)
- ✅ 文本向量化(BGE-M3)
- ✅ 域提取(支持 `domain:query` 语法)
|
b926f678
tangwang
多语言查询
|
590
|
|
3a950275
tangwang
导入测试数据
|
591
592
|
### 6.4 搜索功能
- ✅ 布尔表达式解析(AND, OR, RANK, ANDNOT, 括号)
|
ca91352a
tangwang
更新文档
|
593
|
- ✅ 多语言查询构建(同时搜索中英文字段)
|
3a950275
tangwang
导入测试数据
|
594
595
596
|
- ✅ 语义搜索(KNN 检索)
- ✅ 相关性排序(BM25 + 向量相似度)
- ✅ 结果聚合(Faceted Search)
|
85f08823
tangwang
过滤逻辑
|
597
|
- ✅ Specifications嵌套过滤(单个和多个规格,按维度分组:不同维度AND,相同维度OR)
|
ca91352a
tangwang
更新文档
|
598
599
|
- ✅ Specifications嵌套分面(所有规格名称和指定规格名称)
- ✅ SKU筛选(按维度过滤,应用层实现)
|
b926f678
tangwang
多语言查询
|
600
|
|
3a950275
tangwang
导入测试数据
|
601
602
603
604
605
|
### 6.5 API 服务
- ✅ RESTful API(FastAPI)
- ✅ 搜索接口(文本搜索、图片搜索)
- ✅ 文档查询接口
- ✅ 前端界面(HTML + JavaScript)
|
1f6d15fa
tangwang
重构:SPU级别索引、统一索引架构...
|
606
607
|
- ✅ 租户隔离(tenant_id过滤)
|
ca91352a
tangwang
更新文档
|
608
|
### 6.6 索引结构(店匠通用)
|
1f6d15fa
tangwang
重构:SPU级别索引、统一索引架构...
|
609
|
- ✅ SPU级别索引结构
|
ca91352a
tangwang
更新文档
|
610
611
612
613
|
- ✅ 多语言字段支持(中英文)
- ✅ 嵌套字段(skus, specifications, image_embedding)
- ✅ 规格字段(specifications)支持过滤和分面
- ✅ 扁平化字段(价格、库存等)用于过滤和排序
|
1f6d15fa
tangwang
重构:SPU级别索引、统一索引架构...
|
614
615
|
- ✅ 统一索引(search_products)
- ✅ 租户隔离(tenant_id)
|
ca91352a
tangwang
更新文档
|
616
617
|
- ✅ 硬编码映射(mappings/search_products.json)
- ✅ 硬编码查询配置(search/query_config.py)
|
b926f678
tangwang
多语言查询
|
618
|
|
3a950275
tangwang
导入测试数据
|
619
|
---
|
b926f678
tangwang
多语言查询
|
620
|
|
3a950275
tangwang
导入测试数据
|
621
|
## 7. 技术栈
|
b926f678
tangwang
多语言查询
|
622
|
|
3a950275
tangwang
导入测试数据
|
623
624
625
626
627
628
629
|
- **后端**:Python 3.6+
- **搜索引擎**:Elasticsearch
- **数据库**:MySQL(Shoplazza)
- **向量模型**:BGE-M3(文本)、CN-CLIP(图片)
- **翻译服务**:DeepL API
- **API 框架**:FastAPI
- **前端**:HTML + JavaScript
|
b926f678
tangwang
多语言查询
|
630
|
|
3a950275
tangwang
导入测试数据
|
631
|
---
|
b926f678
tangwang
多语言查询
|
632
|
|
1f6d15fa
tangwang
重构:SPU级别索引、统一索引架构...
|
633
634
635
636
637
638
639
640
641
642
643
|
## 8. API响应格式
### 8.1 外部友好格式
API返回格式不包含ES内部字段(`_id`, `_score`, `_source`),使用外部友好的格式:
**响应结构**:
```json
{
"results": [
{
|
cadc77b6
tangwang
索引字段名、变量名、API数据结构...
|
644
|
"spu_id": "123",
|
1f6d15fa
tangwang
重构:SPU级别索引、统一索引架构...
|
645
|
"title": "蓝牙耳机",
|
cadc77b6
tangwang
索引字段名、变量名、API数据结构...
|
646
|
"skus": [
|
1f6d15fa
tangwang
重构:SPU级别索引、统一索引架构...
|
647
|
{
|
cadc77b6
tangwang
索引字段名、变量名、API数据结构...
|
648
|
"sku_id": "456",
|
1f6d15fa
tangwang
重构:SPU级别索引、统一索引架构...
|
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
|
"price": 199.99,
"sku": "SKU-123-1",
"stock": 50
}
],
"relevance_score": 0.95
}
],
"total": 10,
"facets": [...],
"suggestions": [],
"related_searches": []
}
```
**主要变化**:
|
cadc77b6
tangwang
索引字段名、变量名、API数据结构...
|
665
666
|
- 结构化结果(`SpuResult`和`SkuResult`)
- 嵌套skus数组
|
1f6d15fa
tangwang
重构:SPU级别索引、统一索引架构...
|
667
668
669
670
671
672
673
674
675
676
|
- 无ES内部字段
### 8.2 租户隔离
所有API请求必须提供`tenant_id`:
- 请求头:`X-Tenant-ID: 1`
- 或查询参数:`?tenant_id=1`
搜索时自动添加`tenant_id`过滤,确保数据隔离。
|
2c9fad19
tangwang
撰写接口文档
|
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
|
### 8.3 数据接口约定
**统一的数据约定格式**:所有API接口使用 Pydantic 模型进行数据验证和序列化。
#### 8.3.1 数据流模式
系统采用统一的数据流模式,确保数据在各层之间的一致性:
**数据流转路径**:
```
API Request (JSON)
↓
Pydantic 验证 → 结构化模型(RangeFilter, FacetConfig 等)
↓
Searcher(透传)
↓
ES Query Builder → model_dump() 转换为字典
↓
ES Query (字典)
↓
Elasticsearch
```
#### 8.3.2 Facets 配置数据流
|
13320ac6
tangwang
分面接口修改:
|
702
|
**输入格式**:`List[FacetConfig]`
|
2c9fad19
tangwang
撰写接口文档
|
703
|
|
13320ac6
tangwang
分面接口修改:
|
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
|
**配置对象列表**:所有分面配置必须使用 FacetConfig 对象
```json
[
{
"field": "category1_name",
"size": 15,
"type": "terms"
},
{
"field": "specifications.color",
"size": 20,
"type": "terms"
},
{
"field": "min_price",
"type": "range",
"ranges": [
{"key": "0-50", "to": 50},
{"key": "50-100", "from": 50, "to": 100}
]
}
]
```
|
ca91352a
tangwang
更新文档
|
727
|
|
13320ac6
tangwang
分面接口修改:
|
728
729
730
|
**Specifications 分面支持**:
- 所有规格名称:`field: "specifications"` - 返回所有 name 及其 value 列表
- 指定规格名称:`field: "specifications.color"` - 只返回指定 name 的 value 列表
|
2c9fad19
tangwang
撰写接口文档
|
731
732
|
**数据流**:
|
13320ac6
tangwang
分面接口修改:
|
733
734
735
|
1. API 层:接收 `List[FacetConfig]`,Pydantic 验证参数
2. Searcher 层:透传 FacetConfig 对象列表
3. ES Query Builder:解析 FacetConfig 对象
|
ca91352a
tangwang
更新文档
|
736
|
- 检测 `"specifications"` 或 `"specifications.{name}"` 格式
|
13320ac6
tangwang
分面接口修改:
|
737
738
739
|
- 构建对应的嵌套聚合查询或普通聚合查询
4. 输出:转换为 ES 聚合查询(包括 specifications 嵌套聚合)
5. Result Formatter:格式化 ES 聚合结果,处理 specifications 嵌套结构
|
2c9fad19
tangwang
撰写接口文档
|
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
|
#### 8.3.3 Range Filters 数据流
**输入格式**:`Dict[str, RangeFilter]`
**RangeFilter 模型**:
```python
class RangeFilter(BaseModel):
gte: Optional[Union[float, str]] # 大于等于
gt: Optional[Union[float, str]] # 大于
lte: Optional[Union[float, str]] # 小于等于
lt: Optional[Union[float, str]] # 小于
```
**示例**:
```json
{
|
ca91352a
tangwang
更新文档
|
757
758
|
"min_price": {"gte": 50, "lte": 200},
"create_time": {"gte": "2023-01-01T00:00:00Z"}
|
2c9fad19
tangwang
撰写接口文档
|
759
760
761
762
763
764
765
766
767
768
769
770
771
772
|
}
```
**数据流**:
1. API 层:接收 `Dict[str, RangeFilter]`,Pydantic 自动验证
2. Searcher 层:透传 `Dict[str, RangeFilter]`
3. ES Query Builder:调用 `range_filter.model_dump()` 转换为字典
4. 输出:ES range 查询(支持数值和日期)
**特性**:
- 自动验证:确保至少指定一个边界值(gte, gt, lte, lt)
- 类型支持:支持数值(float)和日期时间字符串(ISO 格式)
- 统一约定:所有范围过滤都使用 RangeFilter 模型
|
ca91352a
tangwang
更新文档
|
773
774
775
776
777
778
779
780
781
782
783
784
785
786
|
#### 8.3.3.1 Specifications 过滤数据流
**输入格式**:`Dict[str, Union[Dict[str, str], List[Dict[str, str]]]]`
**单个规格过滤**:
```json
{
"specifications": {
"name": "color",
"value": "white"
}
}
```
|
85f08823
tangwang
过滤逻辑
|
787
|
**多个规格过滤(按维度分组)**:
|
ca91352a
tangwang
更新文档
|
788
789
790
791
792
793
794
795
796
797
798
799
800
801
|
```json
{
"specifications": [
{"name": "color", "value": "white"},
{"name": "size", "value": "256GB"}
]
}
```
**数据流**:
1. API 层:接收 `filters` 字典,检测 `specifications` 键
2. Searcher 层:透传 `filters` 字典
3. ES Query Builder:检测 `specifications` 键,构建ES `nested` 查询
- 单个规格:构建单个 `nested` 查询
|
85f08823
tangwang
过滤逻辑
|
802
|
- 多个规格:按 name 维度分组,相同维度内使用 `should` 组合(OR逻辑),不同维度之间使用 `must` 组合(AND逻辑)
|
ca91352a
tangwang
更新文档
|
803
804
|
4. 输出:ES nested 查询(`nested.path=specifications` + `bool.must=[term(name), term(value)]`)
|
2c9fad19
tangwang
撰写接口文档
|
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
|
#### 8.3.4 响应 Facets 数据流
**输出格式**:`List[FacetResult]`
**FacetResult 模型**:
```python
class FacetResult(BaseModel):
field: str # 字段名
label: str # 显示标签
type: Literal["terms", "range"] # 分面类型
values: List[FacetValue] # 分面值列表
total_count: Optional[int] # 总文档数
```
**数据流**:
|
ca91352a
tangwang
更新文档
|
820
821
822
823
824
825
826
827
828
|
1. ES Response:返回聚合结果(字典格式,包括specifications嵌套聚合)
2. Result Formatter:格式化ES聚合结果
- 处理普通terms聚合
- 处理range聚合
- **处理specifications嵌套聚合**:
- 所有规格名称:解析 `by_name` 聚合结构
- 指定规格名称:解析 `filter_by_name` 聚合结构
3. Searcher 层:构建 `List[FacetResult]` 对象
4. API 层:直接返回 `List[FacetResult]`(Pydantic 自动序列化为 JSON)
|
2c9fad19
tangwang
撰写接口文档
|
829
830
831
832
833
834
|
**优势**:
- 类型安全:使用 Pydantic 模型确保数据结构一致性
- 自动序列化:模型自动转换为 JSON,无需手动处理
- 统一约定:所有响应都使用标准化的 Pydantic 模型
|
ca91352a
tangwang
更新文档
|
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
|
#### 8.3.5 SKU筛选数据流
**输入格式**:`Optional[str]`
**支持的维度值**:
- `option1`, `option2`, `option3`: 直接使用选项字段
- 规格名称(如 `color`, `size`): 通过 `option1_name`、`option2_name`、`option3_name` 匹配
**示例**:
```json
{
"query": "手机",
"sku_filter_dimension": "color"
}
```
**数据流**:
1. API 层:接收 `sku_filter_dimension` 字符串参数
2. Searcher 层:透传到 Result Formatter
3. Result Formatter:在格式化结果时,按指定维度对SKU进行分组
- 如果维度是 `option1/2/3`,直接使用对应的 `option1_value/2/3` 字段
- 如果维度是规格名称,通过 `option1_name/2/3` 匹配找到对应的 `option1_value/2/3`
- 每个分组选择第一个SKU返回
4. 输出:过滤后的SKU列表(每个维度值一个SKU)
**工作原理**:
1. 系统从ES返回所有SKU(不改变ES查询,保持性能)
2. 在结果格式化阶段,按指定维度对SKU进行分组
3. 每个分组选择第一个SKU返回
4. 如果维度不匹配或未找到,返回所有SKU(不进行过滤)
**性能说明**:
- ✅ **推荐方案**: 在应用层过滤(当前实现)
- ES查询简单,不需要nested查询和join
- 只对返回的结果(通常10-20个SPU)进行过滤,数据量小
- 实现简单,性能开销小
- ❌ **不推荐**: 在ES查询时过滤
- 需要nested查询和join,性能开销大
- 实现复杂
- 只对返回的结果需要过滤,不需要在ES层面过滤
#### 8.3.6 统一约定的好处
|
2c9fad19
tangwang
撰写接口文档
|
877
878
879
880
881
882
883
884
|
1. **类型安全**:使用 Pydantic 模型提供运行时类型检查和验证
2. **代码一致性**:所有层使用相同的数据模型,减少转换错误
3. **自动文档**:FastAPI 自动生成 API 文档(基于 Pydantic 模型)
4. **易于维护**:修改数据结构只需更新模型定义
5. **数据验证**:自动验证输入数据,减少错误处理代码
**实现模块**:
|
ca91352a
tangwang
更新文档
|
885
886
887
|
- `api/models.py` - 所有 Pydantic 模型定义(包括 `SearchRequest`, `FacetConfig`, `RangeFilter` 等)
- `api/result_formatter.py` - 结果格式化器(ES 响应 → Pydantic 模型,包括specifications分面处理和SKU筛选)
- `search/es_query_builder.py` - ES 查询构建器(Pydantic 模型 → ES 查询,包括specifications过滤和分面)
|
2c9fad19
tangwang
撰写接口文档
|
888
|
|
ca91352a
tangwang
更新文档
|
889
|
## 9. 索引结构文件
|
1f6d15fa
tangwang
重构:SPU级别索引、统一索引架构...
|
890
|
|
ca91352a
tangwang
更新文档
|
891
|
**硬编码映射**(店匠通用):`mappings/search_products.json`
|
b926f678
tangwang
多语言查询
|
892
|
|
ca91352a
tangwang
更新文档
|
893
|
**查询配置**(硬编码):`search/query_config.py`
|
b926f678
tangwang
多语言查询
|
894
|
|
3a950275
tangwang
导入测试数据
|
895
|
---
|