cadc77b6
tangwang
索引字段名、变量名、API数据结构...
|
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
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
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
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
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
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
|
# 搜索引擎通用化开发进度
## 项目概述
对后端搜索技术 做通用化。
通用化的本质 是 对于各种业务数据、各种检索需求,都可以 用少量定制+配置化 来实现效果。
**通用化的本质**:对于各种业务数据、各种检索需求,都可以用少量定制+配置化来实现效果。
---
## 1. 原始数据层的约定
### 1.1 店匠主表
所有租户共用以下主表:
- `shoplazza_product_sku` - SKU级别商品数据
- `shoplazza_product_spu` - SPU级别商品数据
### 1.2 索引结构(SPU维度)
**统一索引架构**:
- 所有客户共享同一个Elasticsearch索引:`search_products`
- 索引粒度:SPU级别(每个文档代表一个SPU)
- 数据隔离:通过`tenant_id`字段实现租户隔离
- 嵌套结构:每个SPU文档包含嵌套的`skus`数组(SKU变体)
**索引文档结构**:
```json
{
"tenant_id": "1",
"spu_id": "123",
"title": "蓝牙耳机",
"skus": [
{
"sku_id": "456",
"title": "黑色",
"price": 199.99,
"sku": "SKU-123-1",
"stock": 50
}
],
"min_price": 199.99,
"max_price": 299.99
}
```
### 1.3 配置化方案
**配置分离原则**:
- **搜索配置**:只包含ES字段定义、查询域、排序规则等搜索相关配置
- **数据源配置**:不在搜索配置中,由Pipeline层(脚本)决定
- **数据导入流程**:写死的脚本,不依赖配置
统一通过配置文件定义:
1. ES 字段定义(字段类型、分析器、boost等)
2. ES mapping 结构生成
3. 查询域配置(indexes)
4. 排序和打分配置(function_score)
**注意**:配置中**不包含**以下内容:
- `mysql_config` - MySQL数据库配置
- `main_table` / `extension_table` - 数据表配置
- `source_table` / `source_column` - 字段数据源映射
---
## 2. 配置系统实现
### 2.1 应用结构配置(字段定义)
**配置文件位置**:`config/schema/{tenant_id}_config.yaml`
**配置内容**:定义了 ES 的输入数据有哪些字段、关联 MySQL 的哪些字段。
**实现情况**:
#### 字段类型支持
- **TEXT**:文本字段,支持多语言分析器
- **KEYWORD**:关键词字段,用于精确匹配和聚合
- **TEXT_EMBEDDING**:文本向量字段(1024维,dot_product相似度)
- **IMAGE_EMBEDDING**:图片向量字段(1024维,dot_product相似度)
- **INT/LONG**:整数类型
- **FLOAT/DOUBLE**:浮点数类型
- **DATE**:日期类型
- **BOOLEAN**:布尔类型
#### 分析器支持
- **chinese_ecommerce**:中文电商分词器(index_ansj/query_ansj)
- **english**:英文分析器
- **russian**:俄文分析器
- **arabic**:阿拉伯文分析器
- **spanish**:西班牙文分析器
- **japanese**:日文分析器
- **standard**:标准分析器
- **keyword**:关键词分析器
#### 字段配置示例(Base配置)
```yaml
fields:
# 租户隔离字段(必需)
- name: "tenant_id"
type: "KEYWORD"
required: true
index: true
store: true
# 商品标识字段
- name: "spu_id"
type: "KEYWORD"
required: true
index: true
store: true
# 文本搜索字段
- name: "title"
type: "TEXT"
analyzer: "chinese_ecommerce"
boost: 3.0
index: true
store: true
- name: "seo_keywords"
type: "TEXT"
analyzer: "chinese_ecommerce"
boost: 2.0
index: true
store: true
# 嵌套skus字段
- name: "skus"
type: "JSON"
nested: true
nested_properties:
sku_id:
type: "keyword"
price:
type: "float"
sku:
type: "keyword"
```
**注意**:配置中**不包含**`source_table`和`source_column`,数据源映射由Pipeline层决定。
**实现模块**:
- `config/config_loader.py` - 配置加载器
- `config/field_types.py` - 字段类型定义
- `indexer/mapping_generator.py` - ES mapping 生成器
- `indexer/data_transformer.py` - 数据转换器
### 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. 组合多个语言查询的结果,提高召回率
**实现模块**:
- `search/multilang_query_builder.py` - 多语言查询构建器
- `query/query_parser.py` - 查询解析器(支持语言检测和翻译)
---
## 3. 数据导入流程
### 3.1 数据源
**店匠标准表**(Base配置使用):
- `shoplazza_product_spu` - SPU级别商品数据
- `shoplazza_product_sku` - SKU级别商品数据
**其他客户表**(tenant1等):
- 使用各自的数据源表和扩展表
### 3.2 数据导入方式
**Pipeline层决定数据源**:
- 数据导入流程是写死的脚本,不依赖配置
- 配置只关注ES搜索相关的内容
- 数据源映射逻辑写死在转换器代码中
#### Base配置数据导入(店匠通用)
**脚本**:`scripts/ingest_shoplazza.py`
**数据流程**:
1. **数据加载**:从MySQL读取`shoplazza_product_spu`和`shoplazza_product_sku`表
2. **数据转换**(`indexer/spu_transformer.py`):
- 按`spu_id`和`tenant_id`关联SPU和SKU数据
- 将SKU数据聚合为嵌套的`skus`数组
- 计算扁平化价格字段(`min_price`, `max_price`, `compare_at_price`)
- 字段映射(写死在代码中,不依赖配置)
- 注入`tenant_id`字段
3. **索引创建**:
- 根据配置生成ES mapping
- 创建或更新`search_products`索引
4. **批量入库**:
- 批量写入ES(默认每批500条)
- 错误处理和重试机制
**命令行工具**:
```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
```
#### 其他客户数据导入
- 使用各自的数据转换器(如`indexer/data_transformer.py`)
- 数据源映射逻辑写死在各自的转换器中
- 共享`search_products`索引,通过`tenant_id`隔离
**实现模块**:
- `indexer/spu_transformer.py` - SPU数据转换器(Base配置)
- `indexer/data_transformer.py` - 通用数据转换器(其他客户)
- `indexer/bulk_indexer.py` - 批量索引器
- `scripts/ingest_shoplazza.py` - 店匠数据导入脚本
---
## 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
#### 工作流程
```
查询输入 → 语言检测 → 确定目标语言 → 翻译 → 多语言查询构建
```
#### 实现模块
- `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`,查询=`查询`)
- 检测查询语言
- 生成翻译
2. **多语言查询构建**:
- 如果域有 `language_field_mapping`:
- 使用检测到的语言查询对应字段(boost * 1.5)
- 使用翻译后的查询搜索其他语言字段(boost * 1.0)
- 如果域没有 `language_field_mapping`:
- 使用所有字段进行搜索
3. **查询组合**:
- 多个语言查询组合为 `should` 子句
- 提高召回率
#### 示例
```
查询: "芭比娃娃"
域: default
检测语言: zh
生成的查询:
- 中文查询 "芭比娃娃" → 搜索 name, categoryName, brandName (boost * 1.5)
- 英文翻译 "Barbie doll" → 搜索 enSpuName (boost * 1.0)
- 俄文翻译 "Кукла Барби" → 搜索 ruSkuName (boost * 1.0)
```
#### 实现模块
- `search/multilang_query_builder.py` - 多语言查询构建器
- `search/searcher.py` - 搜索器(使用多语言构建器)
### 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` - 搜索器(集成排序功能)
---
## 6. 已完成功能总结
### 6.1 配置系统
- ✅ 字段定义配置(类型、分析器、来源表/列)
- ✅ 索引域配置(多域查询、多语言映射)
- ✅ 查询配置(改写词典、翻译配置)
- ✅ 排序配置(表达式配置)
- ✅ 配置验证(字段存在性、类型检查、分析器匹配)
### 6.2 数据索引
- ✅ 数据转换(字段映射、类型转换)
- ✅ 向量生成(文本向量、图片向量)
- ✅ 向量缓存(避免重复计算)
- ✅ 批量索引(错误处理、重试机制)
- ✅ ES mapping 自动生成
### 6.3 查询处理
- ✅ 查询改写(词典配置)
- ✅ 语言检测
- ✅ 多语言翻译(DeepL API)
- ✅ 文本向量化(BGE-M3)
- ✅ 域提取(支持 `domain:query` 语法)
### 6.4 搜索功能
- ✅ 布尔表达式解析(AND, OR, RANK, ANDNOT, 括号)
- ✅ 多语言查询构建(语言路由、字段映射)
- ✅ 语义搜索(KNN 检索)
- ✅ 相关性排序(BM25 + 向量相似度)
- ✅ 结果聚合(Faceted Search)
### 6.5 API 服务
- ✅ RESTful API(FastAPI)
- ✅ 搜索接口(文本搜索、图片搜索)
- ✅ 文档查询接口
- ✅ 前端界面(HTML + JavaScript)
- ✅ 租户隔离(tenant_id过滤)
### 6.6 Base配置(店匠通用)
- ✅ SPU级别索引结构
- ✅ 嵌套skus字段
- ✅ 统一索引(search_products)
- ✅ 租户隔离(tenant_id)
- ✅ 配置简化(移除MySQL相关配置)
---
## 7. 技术栈
- **后端**:Python 3.6+
- **搜索引擎**:Elasticsearch
- **数据库**:MySQL(Shoplazza)
- **向量模型**:BGE-M3(文本)、CN-CLIP(图片)
- **翻译服务**:DeepL API
- **API 框架**:FastAPI
- **前端**:HTML + JavaScript
---
## 8. API响应格式
### 8.1 外部友好格式
API返回格式不包含ES内部字段(`_id`, `_score`, `_source`),使用外部友好的格式:
**响应结构**:
```json
{
"results": [
{
"spu_id": "123",
"title": "蓝牙耳机",
"skus": [
{
"sku_id": "456",
"price": 199.99,
"sku": "SKU-123-1",
"stock": 50
}
],
"relevance_score": 0.95
}
],
"total": 10,
"facets": [...],
"suggestions": [],
"related_searches": []
}
```
**主要变化**:
- 结构化结果(`SpuResult`和`SkuResult`)
- 嵌套skus数组
- 无ES内部字段
### 8.2 租户隔离
所有API请求必须提供`tenant_id`:
- 请求头:`X-Tenant-ID: 1`
- 或查询参数:`?tenant_id=1`
搜索时自动添加`tenant_id`过滤,确保数据隔离。
### 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 配置数据流
**输入格式**:`List[Union[str, FacetConfig]]`
- **简单模式**:字符串列表(字段名),使用默认配置
```json
["categoryName_keyword", "brandName_keyword"]
```
- **高级模式**:FacetConfig 对象列表,支持自定义配置
```json
[
{
"field": "categoryName_keyword",
"size": 15,
"type": "terms"
},
{
"field": "price",
"type": "range",
"ranges": [
{"key": "0-50", "to": 50},
{"key": "50-100", "from": 50, "to": 100}
]
}
]
```
**数据流**:
1. API 层:接收 `List[Union[str, FacetConfig]]`
2. Searcher 层:透传,不做转换
3. ES Query Builder:只接受 `str` 或 `FacetConfig`,自动处理两种格式
4. 输出:转换为 ES 聚合查询
#### 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
{
"price": {"gte": 50, "lte": 200},
"created_at": {"gte": "2023-01-01T00:00:00Z"}
}
```
**数据流**:
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 模型
#### 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] # 总文档数
```
**数据流**:
1. ES Response:返回聚合结果(字典格式)
2. Searcher 层:构建 `List[FacetResult]` 对象
3. API 层:直接返回 `List[FacetResult]`(Pydantic 自动序列化为 JSON)
**优势**:
- 类型安全:使用 Pydantic 模型确保数据结构一致性
- 自动序列化:模型自动转换为 JSON,无需手动处理
- 统一约定:所有响应都使用标准化的 Pydantic 模型
#### 8.3.5 统一约定的好处
1. **类型安全**:使用 Pydantic 模型提供运行时类型检查和验证
2. **代码一致性**:所有层使用相同的数据模型,减少转换错误
3. **自动文档**:FastAPI 自动生成 API 文档(基于 Pydantic 模型)
4. **易于维护**:修改数据结构只需更新模型定义
5. **数据验证**:自动验证输入数据,减少错误处理代码
**实现模块**:
- `api/models.py` - 所有 Pydantic 模型定义
- `api/result_formatter.py` - 结果格式化器(ES 响应 → Pydantic 模型)
- `search/es_query_builder.py` - ES 查询构建器(Pydantic 模型 → ES 查询)
## 9. 配置文件示例
**Base配置**(店匠通用):`config/schema/base/config.yaml`
**其他客户配置**:`config/schema/tenant1/config.yaml`
---
|