Commit 3a950275b31399928b9d15724fc6b7d0fb14e2dd
1 parent
b926f678
导入测试数据
Showing
3 changed files
with
551 additions
and
36 deletions
Show diff stats
data/customer1/ingest_customer1.py
| @@ -9,9 +9,11 @@ import sys | @@ -9,9 +9,11 @@ import sys | ||
| 9 | import os | 9 | import os |
| 10 | import pandas as pd | 10 | import pandas as pd |
| 11 | import argparse | 11 | import argparse |
| 12 | +from typing import Optional | ||
| 12 | 13 | ||
| 13 | -# Add parent directory to path | ||
| 14 | -sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | 14 | +# Add parent directory to path (go up 3 levels: customer1 -> data -> SearchEngine -> root) |
| 15 | +project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | ||
| 16 | +sys.path.insert(0, project_root) | ||
| 15 | 17 | ||
| 16 | from config import ConfigLoader | 18 | from config import ConfigLoader |
| 17 | from utils import ESClient, get_connection_from_config | 19 | from utils import ESClient, get_connection_from_config |
| @@ -53,6 +55,8 @@ def main(): | @@ -53,6 +55,8 @@ def main(): | ||
| 53 | parser.add_argument('--batch-size', type=int, default=100, help='Batch size for processing') | 55 | parser.add_argument('--batch-size', type=int, default=100, help='Batch size for processing') |
| 54 | parser.add_argument('--recreate-index', action='store_true', help='Recreate index if exists') | 56 | parser.add_argument('--recreate-index', action='store_true', help='Recreate index if exists') |
| 55 | parser.add_argument('--es-host', default='http://localhost:9200', help='Elasticsearch host') | 57 | parser.add_argument('--es-host', default='http://localhost:9200', help='Elasticsearch host') |
| 58 | + parser.add_argument('--es-username', default=None, help='Elasticsearch username (or set ES_USERNAME env var)') | ||
| 59 | + parser.add_argument('--es-password', default=None, help='Elasticsearch password (or set ES_PASSWORD env var)') | ||
| 56 | parser.add_argument('--skip-embeddings', action='store_true', help='Skip embedding generation') | 60 | parser.add_argument('--skip-embeddings', action='store_true', help='Skip embedding generation') |
| 57 | args = parser.parse_args() | 61 | args = parser.parse_args() |
| 58 | 62 | ||
| @@ -80,11 +84,40 @@ def main(): | @@ -80,11 +84,40 @@ def main(): | ||
| 80 | 84 | ||
| 81 | # Initialize Elasticsearch client | 85 | # Initialize Elasticsearch client |
| 82 | print(f"\n[2/6] Connecting to Elasticsearch: {args.es_host}") | 86 | print(f"\n[2/6] Connecting to Elasticsearch: {args.es_host}") |
| 83 | - os.environ['ES_HOST'] = args.es_host | ||
| 84 | - es_client = ESClient(hosts=[args.es_host]) | 87 | + |
| 88 | + # Get credentials: prioritize command-line args, then environment variables, then .env file | ||
| 89 | + es_username = args.es_username | ||
| 90 | + es_password = args.es_password | ||
| 91 | + | ||
| 92 | + # If not provided via args, try to load from .env file via env_config | ||
| 93 | + if not es_username or not es_password: | ||
| 94 | + try: | ||
| 95 | + from config.env_config import get_es_config | ||
| 96 | + es_config = get_es_config() | ||
| 97 | + es_username = es_username or es_config.get('username') | ||
| 98 | + es_password = es_password or es_config.get('password') | ||
| 99 | + except Exception: | ||
| 100 | + # Fallback to environment variables | ||
| 101 | + es_username = es_username or os.getenv('ES_USERNAME') | ||
| 102 | + es_password = es_password or os.getenv('ES_PASSWORD') | ||
| 103 | + | ||
| 104 | + # Create ES client with credentials if available | ||
| 105 | + if es_username and es_password: | ||
| 106 | + print(f" Using authentication: {es_username}") | ||
| 107 | + es_client = ESClient(hosts=[args.es_host], username=es_username, password=es_password) | ||
| 108 | + else: | ||
| 109 | + print(f" Warning: No authentication credentials found") | ||
| 110 | + print(f" Attempting connection without authentication (will fail if ES requires auth)") | ||
| 111 | + es_client = ESClient(hosts=[args.es_host]) | ||
| 85 | 112 | ||
| 86 | if not es_client.ping(): | 113 | if not es_client.ping(): |
| 87 | print("Failed to connect to Elasticsearch") | 114 | print("Failed to connect to Elasticsearch") |
| 115 | + print("\nTroubleshooting:") | ||
| 116 | + print(" 1. Check if Elasticsearch is running: curl http://localhost:9200") | ||
| 117 | + print(" 2. If ES requires authentication, provide credentials:") | ||
| 118 | + print(" - Use --es-username and --es-password arguments, or") | ||
| 119 | + print(" - Set ES_USERNAME and ES_PASSWORD environment variables") | ||
| 120 | + print(" 3. Verify the host URL is correct: --es-host") | ||
| 88 | return 1 | 121 | return 1 |
| 89 | 122 | ||
| 90 | print("Connected to Elasticsearch successfully") | 123 | print("Connected to Elasticsearch successfully") |
start_all.sh
当前开发进度.md
| 1 | +# 搜索引擎通用化开发进度 | ||
| 1 | 2 | ||
| 3 | +## 项目概述 | ||
| 2 | 4 | ||
| 3 | 对后端搜索技术 做通用化。 | 5 | 对后端搜索技术 做通用化。 |
| 4 | - | ||
| 5 | 通用化的本质 是 对于各种业务数据、各种检索需求,都可以 用少量定制+配置化 来实现效果。 | 6 | 通用化的本质 是 对于各种业务数据、各种检索需求,都可以 用少量定制+配置化 来实现效果。 |
| 6 | 7 | ||
| 7 | -## 1. 原始数据层的约定。 | ||
| 8 | -### 店匠主表 | ||
| 9 | -shoplazza_product_sku | ||
| 10 | -shoplazza_product_spu | ||
| 11 | -所有租户共用这个主表 | ||
| 12 | 8 | ||
| 13 | -### 每个租户的辅表 | ||
| 14 | -各个租户,有自己的扩展表。 入索引的时候,商品主表 shoplazza_product_sku 的 id + shopid,拼接租户自己单独的扩展表(比如可以放一些自己的属性体系、各种语言的商品名、品牌名、标签、分类等) | 9 | +**通用化的本质**:对于各种业务数据、各种检索需求,都可以用少量定制+配置化来实现效果。 |
| 10 | + | ||
| 11 | +--- | ||
| 12 | + | ||
| 13 | +## 1. 原始数据层的约定 | ||
| 14 | + | ||
| 15 | +所有租户共用主表、独立配置和扩展表,有自己独立的ES索引。 | ||
| 16 | + | ||
| 17 | +### 1.1 店匠主表 | ||
| 18 | + | ||
| 19 | +所有租户共用以下主表: | ||
| 20 | +- `shoplazza_product_sku` - SKU级别商品数据 | ||
| 21 | +- `shoplazza_product_spu` - SPU级别商品数据 | ||
| 22 | + | ||
| 23 | +### 1.2 每个租户的扩展表 | ||
| 24 | + | ||
| 25 | +各个租户有自己的扩展表,不同的租户根据不同的业务需要、以及不同的数据源,来定制自己的扩展表: | ||
| 26 | +- 自定义属性体系 | ||
| 27 | +- 多语言商品标题(中文、英文、俄文等) | ||
| 28 | +- 品牌名、不同的类目和标签体系 | ||
| 29 | +- 业务过滤和聚合字段 | ||
| 30 | +- 权重(提权)字段 | ||
| 31 | + | ||
| 32 | +**数据关联方式**: | ||
| 33 | +- 入索引时,商品主表 `shoplazza_product_sku` 的 `id` + `shopid` 与租户扩展表关联 | ||
| 34 | +- 例如:`customer1_extension` 表存储 customer1 的自定义字段 | ||
| 35 | + | ||
| 36 | +### 1.3 配置化方案 | ||
| 37 | + | ||
| 38 | +统一通过配置文件定义: | ||
| 39 | +1. ES 字段定义(字段类型、分析器、来源表/列) | ||
| 40 | +2. ES mapping 结构生成 | ||
| 41 | +3. 数据入库映射关系 | ||
| 42 | + | ||
| 43 | +--- | ||
| 44 | + | ||
| 45 | +## 2. 配置系统实现 | ||
| 46 | + | ||
| 47 | +### 2.1 应用结构配置(字段定义) | ||
| 48 | + | ||
| 49 | +**配置文件位置**:`config/schema/{customer_id}_config.yaml` | ||
| 50 | + | ||
| 51 | +**配置内容**:定义了 ES 的输入数据有哪些字段、关联 MySQL 的哪些字段。 | ||
| 52 | + | ||
| 53 | +**实现情况**: | ||
| 54 | + | ||
| 55 | +#### 字段类型支持 | ||
| 56 | +- **TEXT**:文本字段,支持多语言分析器 | ||
| 57 | +- **KEYWORD**:关键词字段,用于精确匹配和聚合 | ||
| 58 | +- **TEXT_EMBEDDING**:文本向量字段(1024维,dot_product相似度) | ||
| 59 | +- **IMAGE_EMBEDDING**:图片向量字段(1024维,dot_product相似度) | ||
| 60 | +- **INT/LONG**:整数类型 | ||
| 61 | +- **FLOAT/DOUBLE**:浮点数类型 | ||
| 62 | +- **DATE**:日期类型 | ||
| 63 | +- **BOOLEAN**:布尔类型 | ||
| 64 | + | ||
| 65 | +#### 分析器支持 | ||
| 66 | +- **chinese_ecommerce**:中文电商分词器(index_ansj/query_ansj) | ||
| 67 | +- **english**:英文分析器 | ||
| 68 | +- **russian**:俄文分析器 | ||
| 69 | +- **arabic**:阿拉伯文分析器 | ||
| 70 | +- **spanish**:西班牙文分析器 | ||
| 71 | +- **japanese**:日文分析器 | ||
| 72 | +- **standard**:标准分析器 | ||
| 73 | +- **keyword**:关键词分析器 | ||
| 74 | + | ||
| 75 | +#### 字段配置示例 | ||
| 76 | + | ||
| 77 | +```yaml | ||
| 78 | +fields: | ||
| 79 | + # 主键字段 | ||
| 80 | + - name: "skuId" | ||
| 81 | + type: "LONG" | ||
| 82 | + source_table: "main" # 主表 | ||
| 83 | + source_column: "id" | ||
| 84 | + required: true | ||
| 85 | + index: true | ||
| 86 | + store: true | ||
| 87 | + | ||
| 88 | + # 多语言文本字段 | ||
| 89 | + - name: "name" | ||
| 90 | + type: "TEXT" | ||
| 91 | + source_table: "extension" # 扩展表 | ||
| 92 | + source_column: "name" | ||
| 93 | + analyzer: "chinese_ecommerce" | ||
| 94 | + boost: 2.0 | ||
| 95 | + index: true | ||
| 96 | + store: true | ||
| 97 | + | ||
| 98 | + - name: "enSpuName" | ||
| 99 | + type: "TEXT" | ||
| 100 | + source_table: "extension" | ||
| 101 | + source_column: "enSpuName" | ||
| 102 | + analyzer: "english" | ||
| 103 | + boost: 2.0 | ||
| 104 | + | ||
| 105 | + - name: "ruSkuName" | ||
| 106 | + type: "TEXT" | ||
| 107 | + source_table: "extension" | ||
| 108 | + source_column: "ruSkuName" | ||
| 109 | + analyzer: "russian" | ||
| 110 | + boost: 2.0 | ||
| 111 | + | ||
| 112 | + # 文本向量字段 | ||
| 113 | + - name: "name_embedding" | ||
| 114 | + type: "TEXT_EMBEDDING" | ||
| 115 | + source_table: "extension" | ||
| 116 | + source_column: "name" | ||
| 117 | + embedding_dims: 1024 | ||
| 118 | + embedding_similarity: "dot_product" | ||
| 119 | + index: true | ||
| 120 | + | ||
| 121 | + # 图片向量字段 | ||
| 122 | + - name: "image_embedding" | ||
| 123 | + type: "IMAGE_EMBEDDING" | ||
| 124 | + source_table: "extension" | ||
| 125 | + source_column: "imageUrl" | ||
| 126 | + embedding_dims: 1024 | ||
| 127 | + embedding_similarity: "dot_product" | ||
| 128 | + nested: false | ||
| 129 | +``` | ||
| 130 | + | ||
| 131 | +**实现模块**: | ||
| 132 | +- `config/config_loader.py` - 配置加载器 | ||
| 133 | +- `config/field_types.py` - 字段类型定义 | ||
| 134 | +- `indexer/mapping_generator.py` - ES mapping 生成器 | ||
| 135 | +- `indexer/data_transformer.py` - 数据转换器 | ||
| 136 | + | ||
| 137 | +### 2.2 索引结构配置(查询域配置) | ||
| 138 | + | ||
| 139 | +**配置内容**:定义了 ES 的字段索引 mapping 配置,支持各个域的查询,包括默认域的查询。 | ||
| 140 | + | ||
| 141 | +**实现情况**: | ||
| 142 | + | ||
| 143 | +#### 域(Domain)配置 | ||
| 144 | +每个域定义了: | ||
| 145 | +- 域名称(如 `default`, `title`, `category`, `brand`) | ||
| 146 | +- 域标签(中文描述) | ||
| 147 | +- 搜索字段列表 | ||
| 148 | +- 默认分析器 | ||
| 149 | +- 权重(boost) | ||
| 150 | +- **多语言字段映射**(`language_field_mapping`) | ||
| 151 | + | ||
| 152 | +#### 多语言字段映射 | ||
| 153 | + | ||
| 154 | +支持将不同语言的查询路由到对应的字段: | ||
| 155 | + | ||
| 156 | +```yaml | ||
| 157 | +indexes: | ||
| 158 | + - name: "default" | ||
| 159 | + label: "默认索引" | ||
| 160 | + fields: | ||
| 161 | + - "name" | ||
| 162 | + - "enSpuName" | ||
| 163 | + - "ruSkuName" | ||
| 164 | + - "categoryName" | ||
| 165 | + - "brandName" | ||
| 166 | + analyzer: "chinese_ecommerce" | ||
| 167 | + boost: 1.0 | ||
| 168 | + language_field_mapping: | ||
| 169 | + zh: | ||
| 170 | + - "name" | ||
| 171 | + - "categoryName" | ||
| 172 | + - "brandName" | ||
| 173 | + en: | ||
| 174 | + - "enSpuName" | ||
| 175 | + ru: | ||
| 176 | + - "ruSkuName" | ||
| 177 | + | ||
| 178 | + - name: "title" | ||
| 179 | + label: "标题索引" | ||
| 180 | + fields: | ||
| 181 | + - "name" | ||
| 182 | + - "enSpuName" | ||
| 183 | + - "ruSkuName" | ||
| 184 | + analyzer: "chinese_ecommerce" | ||
| 185 | + boost: 2.0 | ||
| 186 | + language_field_mapping: | ||
| 187 | + zh: | ||
| 188 | + - "name" | ||
| 189 | + en: | ||
| 190 | + - "enSpuName" | ||
| 191 | + ru: | ||
| 192 | + - "ruSkuName" | ||
| 193 | +``` | ||
| 194 | + | ||
| 195 | +**工作原理**: | ||
| 196 | +1. 检测查询语言(中文、英文、俄文等) | ||
| 197 | +2. 如果查询语言在 `language_field_mapping` 中,使用原始查询搜索对应语言的字段 | ||
| 198 | +3. 将查询翻译到其他支持的语言,分别搜索对应语言的字段 | ||
| 199 | +4. 组合多个语言查询的结果,提高召回率 | ||
| 200 | + | ||
| 201 | +**实现模块**: | ||
| 202 | +- `search/multilang_query_builder.py` - 多语言查询构建器 | ||
| 203 | +- `query/query_parser.py` - 查询解析器(支持语言检测和翻译) | ||
| 204 | + | ||
| 205 | +--- | ||
| 206 | + | ||
| 207 | +## 3. 测试数据灌入 | ||
| 208 | + | ||
| 209 | +### 3.1 数据源 | ||
| 210 | + | ||
| 211 | +**主表**:`shoplazza_product_sku` | ||
| 212 | +- 所有租户共用 | ||
| 213 | +- 包含基础商品信息(id, shopid 等) | ||
| 214 | + | ||
| 215 | +**扩展表**:`customer1_extension` | ||
| 216 | +- 每个租户独立 | ||
| 217 | +- 包含自定义字段和多语言字段 | ||
| 218 | + | ||
| 219 | +### 3.2 数据灌入方式 | ||
| 220 | + | ||
| 221 | +**实现情况**: | ||
| 222 | + | ||
| 223 | +#### 命令行工具 | ||
| 224 | +```bash | ||
| 225 | +python main.py ingest \ | ||
| 226 | + --customer customer1 \ | ||
| 227 | + --csv-file data/customer1_data.csv \ | ||
| 228 | + --es-host http://localhost:9200 \ | ||
| 229 | + --recreate \ | ||
| 230 | + --batch-size 100 | ||
| 231 | +``` | ||
| 232 | + | ||
| 233 | +#### 数据流程 | ||
| 234 | +1. **数据加载**:从 CSV 文件或 MySQL 数据库加载数据 | ||
| 235 | +2. **数据转换**: | ||
| 236 | + - 字段映射(根据配置将源字段映射到 ES 字段) | ||
| 237 | + - 类型转换(字符串、数字、日期等) | ||
| 238 | + - 向量生成(文本向量、图片向量) | ||
| 239 | + - 向量缓存(避免重复计算) | ||
| 240 | +3. **索引创建**: | ||
| 241 | + - 根据配置生成 ES mapping | ||
| 242 | + - 创建或更新索引 | ||
| 243 | +4. **批量入库**: | ||
| 244 | + - 批量写入 ES(默认每批 500 条) | ||
| 245 | + - 错误处理和重试机制 | ||
| 246 | + | ||
| 247 | +#### 配置映射示例 | ||
| 248 | + | ||
| 249 | +**customer1_config.yaml** 配置: | ||
| 250 | +```yaml | ||
| 251 | +main_table: "shoplazza_product_sku" | ||
| 252 | +extension_table: "customer1_extension" | ||
| 253 | +es_index_name: "search_customer1" | ||
| 254 | + | ||
| 255 | +fields: | ||
| 256 | + - name: "skuId" | ||
| 257 | + source_table: "main" | ||
| 258 | + source_column: "id" | ||
| 259 | + - name: "name" | ||
| 260 | + source_table: "extension" | ||
| 261 | + source_column: "name" | ||
| 262 | + - name: "enSpuName" | ||
| 263 | + source_table: "extension" | ||
| 264 | + source_column: "enSpuName" | ||
| 265 | +``` | ||
| 266 | + | ||
| 267 | +**数据转换**: | ||
| 268 | +- 主表字段:直接从 `shoplazza_product_sku` 表的 `id` 字段读取 | ||
| 269 | +- 扩展表字段:从 `customer1_extension` 表的对应列读取 | ||
| 270 | +- 向量字段:对源文本/图片生成向量并缓存 | ||
| 271 | + | ||
| 272 | +**实现模块**: | ||
| 273 | +- `indexer/data_transformer.py` - 数据转换器 | ||
| 274 | +- `indexer/bulk_indexer.py` - 批量索引器 | ||
| 275 | +- `indexer/indexing_pipeline.py` - 索引流水线 | ||
| 276 | +- `embeddings/bge_encoder.py` - 文本向量编码器 | ||
| 277 | +- `embeddings/clip_image_encoder.py` - 图片向量编码器 | ||
| 278 | + | ||
| 279 | +--- | ||
| 280 | + | ||
| 281 | +## 4. QueryParser 实现 | ||
| 282 | + | ||
| 283 | + | ||
| 284 | +### 4.1 查询改写(Query Rewriting) | ||
| 285 | + | ||
| 286 | +配置词典的key是query,value是改写后的查询表达式,比如。比如品牌词 改写为在brand|query OR name|query,类别词、标签词等都可以放进去。纠错、规范化、查询改写等 都可以通过这个词典来配置。 | ||
| 287 | +**实现情况**: | ||
| 288 | + | ||
| 289 | +#### 配置方式 | ||
| 290 | +在 `query_config.rewrite_dictionary` 中配置查询改写规则: | ||
| 291 | + | ||
| 292 | +```yaml | ||
| 293 | +query_config: | ||
| 294 | + enable_query_rewrite: true | ||
| 295 | + rewrite_dictionary: | ||
| 296 | + "芭比": "brand:芭比 OR name:芭比娃娃" | ||
| 297 | + "玩具": "category:玩具" | ||
| 298 | + "消防": "category:消防 OR name:消防" | ||
| 299 | +``` | ||
| 300 | + | ||
| 301 | +#### 功能特性 | ||
| 302 | +- **精确匹配**:查询完全匹配词典 key 时,替换为 value | ||
| 303 | +- **部分匹配**:查询包含词典 key 时,替换该部分 | ||
| 304 | +- **支持布尔表达式**:value 可以是复杂的布尔表达式(AND, OR, 域查询等) | ||
| 305 | + | ||
| 306 | +#### 实现模块 | ||
| 307 | +- `query/query_rewriter.py` - 查询改写器 | ||
| 308 | +- `query/query_parser.py` - 查询解析器(集成改写功能) | ||
| 309 | + | ||
| 310 | +### 4.2 翻译(Translation) | ||
| 311 | + | ||
| 312 | +**实现情况**: | ||
| 313 | + | ||
| 314 | +#### 配置方式 | ||
| 315 | +```yaml | ||
| 316 | +query_config: | ||
| 317 | + supported_languages: | ||
| 318 | + - "zh" | ||
| 319 | + - "en" | ||
| 320 | + - "ru" | ||
| 321 | + default_language: "zh" | ||
| 322 | + enable_translation: true | ||
| 323 | + translation_service: "deepl" | ||
| 324 | + translation_api_key: null # 通过环境变量设置 | ||
| 325 | +``` | ||
| 326 | + | ||
| 327 | +#### 功能特性 | ||
| 328 | +1. **语言检测**:自动检测查询语言 | ||
| 329 | +2. **智能翻译**: | ||
| 330 | + - 如果查询是中文,翻译为英文、俄文 | ||
| 331 | + - 如果查询是英文,翻译为中文、俄文 | ||
| 332 | + - 如果查询是其他语言,翻译为所有支持的语言 | ||
| 333 | +3. **域感知翻译**: | ||
| 334 | + - 如果域有 `language_field_mapping`,只翻译到映射中存在的语言 | ||
| 335 | + - 避免不必要的翻译,提高效率 | ||
| 336 | +4. **翻译缓存**:缓存翻译结果,避免重复调用 API | ||
| 337 | + | ||
| 338 | +#### 工作流程 | ||
| 339 | +``` | ||
| 340 | +查询输入 → 语言检测 → 确定目标语言 → 翻译 → 多语言查询构建 | ||
| 341 | +``` | ||
| 342 | + | ||
| 343 | +#### 实现模块 | ||
| 344 | +- `query/language_detector.py` - 语言检测器 | ||
| 345 | +- `query/translator.py` - 翻译器(DeepL API) | ||
| 346 | +- `query/query_parser.py` - 查询解析器(集成翻译功能) | ||
| 347 | + | ||
| 348 | +### 4.3 文本向量化(Text Embedding) | ||
| 349 | + | ||
| 350 | +如果配置打开了text_embedding查询,并且query 包含了default域的查询,那么要把default域的查询词转向量,后面searcher会用这个向量参与查询。 | ||
| 351 | + | ||
| 352 | +**实现情况**: | ||
| 353 | + | ||
| 354 | +#### 配置方式 | ||
| 355 | +```yaml | ||
| 356 | +query_config: | ||
| 357 | + enable_text_embedding: true | ||
| 358 | +``` | ||
| 359 | + | ||
| 360 | +#### 功能特性 | ||
| 361 | +1. **条件生成**: | ||
| 362 | + - 仅当 `enable_text_embedding=true` 时生成向量 | ||
| 363 | + - 仅对 `default` 域查询生成向量 | ||
| 364 | +2. **向量模型**:BGE-M3 模型(1024维向量) | ||
| 365 | +3. **用途**:用于语义搜索(KNN 检索) | ||
| 366 | + | ||
| 367 | +#### 实现模块 | ||
| 368 | +- `embeddings/bge_encoder.py` - BGE 文本编码器 | ||
| 369 | +- `query/query_parser.py` - 查询解析器(集成向量生成) | ||
| 370 | + | ||
| 371 | +--- | ||
| 372 | + | ||
| 373 | +## 5. Searcher 实现 | ||
| 374 | + | ||
| 375 | +参考opensearch,他们自己定义的一套索引结构配置、支持自定义的一套检索表达式、排序表达式,这是各个客户进行配置化的基础,包括索引结构配置、排序策略配置。 | ||
| 376 | +比如各种业务过滤策略 可以简单的通过表达式满足,比如brand|耐克 AND cate2|xxx。指定字段排序可以通过排序的表达式实现。 | ||
| 377 | + | ||
| 378 | +查询默认在default域,相也会对这个域的查询做一些相关性的重点优化,包括融合语义相关性、多语言相关性(可以基于配置 将查询翻译到指定语言并在对应的语言的字段进行查询)来弥补传统查询分析手段(比如查询改写 纠错 词权重等)的不足,也支持通过配置一些词表转为泛查询模式来优化相关性。 | ||
| 379 | + | ||
| 380 | +### 5.1 布尔表达式解析 | ||
| 381 | + | ||
| 382 | +**实现情况**: | ||
| 383 | + | ||
| 384 | +#### 支持的运算符 | ||
| 385 | +- **AND**:所有项必须匹配 | ||
| 386 | +- **OR**:任意项匹配 | ||
| 387 | +- **RANK**:排序增强(类似 OR 但影响排序) | ||
| 388 | +- **ANDNOT**:排除(第一项匹配,第二项不匹配) | ||
| 389 | +- **()**:括号分组 | ||
| 390 | + | ||
| 391 | +#### 优先级(从高到低) | ||
| 392 | +1. `()` - 括号 | ||
| 393 | +2. `ANDNOT` - 排除 | ||
| 394 | +3. `AND` - 与 | ||
| 395 | +4. `OR` - 或 | ||
| 396 | +5. `RANK` - 排序 | ||
| 397 | + | ||
| 398 | +#### 示例 | ||
| 399 | +``` | ||
| 400 | +laptop AND (gaming OR professional) ANDNOT cheap | ||
| 401 | +``` | ||
| 402 | + | ||
| 403 | +#### 实现模块 | ||
| 404 | +- `search/boolean_parser.py` - 布尔表达式解析器 | ||
| 405 | +- `search/searcher.py` - 搜索器(集成布尔解析) | ||
| 406 | + | ||
| 407 | +### 5.2 多语言搜索 | ||
| 408 | + | ||
| 409 | +**实现情况**: | ||
| 410 | + | ||
| 411 | +#### 工作原理 | ||
| 412 | +1. **查询解析**: | ||
| 413 | + - 提取域(如 `title:查询` → 域=`title`,查询=`查询`) | ||
| 414 | + - 检测查询语言 | ||
| 415 | + - 生成翻译 | ||
| 416 | +2. **多语言查询构建**: | ||
| 417 | + - 如果域有 `language_field_mapping`: | ||
| 418 | + - 使用检测到的语言查询对应字段(boost * 1.5) | ||
| 419 | + - 使用翻译后的查询搜索其他语言字段(boost * 1.0) | ||
| 420 | + - 如果域没有 `language_field_mapping`: | ||
| 421 | + - 使用所有字段进行搜索 | ||
| 422 | +3. **查询组合**: | ||
| 423 | + - 多个语言查询组合为 `should` 子句 | ||
| 424 | + - 提高召回率 | ||
| 425 | + | ||
| 426 | +#### 示例 | ||
| 427 | +``` | ||
| 428 | +查询: "芭比娃娃" | ||
| 429 | +域: default | ||
| 430 | +检测语言: zh | ||
| 431 | + | ||
| 432 | +生成的查询: | ||
| 433 | +- 中文查询 "芭比娃娃" → 搜索 name, categoryName, brandName (boost * 1.5) | ||
| 434 | +- 英文翻译 "Barbie doll" → 搜索 enSpuName (boost * 1.0) | ||
| 435 | +- 俄文翻译 "Кукла Барби" → 搜索 ruSkuName (boost * 1.0) | ||
| 436 | +``` | ||
| 437 | + | ||
| 438 | +#### 实现模块 | ||
| 439 | +- `search/multilang_query_builder.py` - 多语言查询构建器 | ||
| 440 | +- `search/searcher.py` - 搜索器(使用多语言构建器) | ||
| 441 | + | ||
| 442 | +### 5.3 相关性计算(Ranking) | ||
| 443 | + | ||
| 444 | +**实现情况**: | ||
| 445 | + | ||
| 446 | +#### 当前实现 | ||
| 447 | +**公式**:`bm25() + 0.2 * text_embedding_relevance()` | ||
| 448 | + | ||
| 449 | +- **bm25()**:BM25 文本相关性得分 | ||
| 450 | + - 包括多语言打分 | ||
| 451 | + - 内部通过配置翻译为多种语言 | ||
| 452 | + - 分别到对应的字段搜索 | ||
| 453 | + - 中文字段使用中文分词器,英文字段使用英文分词器 | ||
| 454 | +- **text_embedding_relevance()**:文本向量相关性得分(KNN 检索的打分) | ||
| 455 | + - 权重:0.2 | ||
| 456 | + | ||
| 457 | +#### 配置方式 | ||
| 458 | +```yaml | ||
| 459 | +ranking: | ||
| 460 | + expression: "bm25() + 0.2*text_embedding_relevance()" | ||
| 461 | + description: "BM25 text relevance combined with semantic embedding similarity" | ||
| 462 | +``` | ||
| 463 | + | ||
| 464 | +#### 扩展性 | ||
| 465 | +- 支持表达式配置(未来可扩展) | ||
| 466 | +- 支持自定义函数(如 `timeliness()`, `field_value()`) | ||
| 467 | + | ||
| 468 | +#### 实现模块 | ||
| 469 | +- `search/ranking_engine.py` - 排序引擎 | ||
| 470 | +- `search/searcher.py` - 搜索器(集成排序功能) | ||
| 15 | 471 | ||
| 16 | -但是,各个租户,可能有不一样的业务数据,比如不同租户有不同的属性的体系、不同语言的商品标题(一般至少有中英文两种满足跨境的搜索需求),有不同的权重(提权)字段、业务过滤和聚合字段。 | ||
| 17 | -能够统一的 只能是 sku表 按照一套配置规范、做一个配置文件,按照配置文件建设ES mapping结构以及做数据的入库。 | 472 | +--- |
| 18 | 473 | ||
| 19 | -1. 应用结构配置 : 定义了ES的输入数据有哪些字段、关联mysql的哪些字段. | ||
| 20 | - 请帮我补充具体实现的一些配置 | 474 | +## 6. 已完成功能总结 |
| 21 | 475 | ||
| 476 | +### 6.1 配置系统 | ||
| 477 | +- ✅ 字段定义配置(类型、分析器、来源表/列) | ||
| 478 | +- ✅ 索引域配置(多域查询、多语言映射) | ||
| 479 | +- ✅ 查询配置(改写词典、翻译配置) | ||
| 480 | +- ✅ 排序配置(表达式配置) | ||
| 481 | +- ✅ 配置验证(字段存在性、类型检查、分析器匹配) | ||
| 22 | 482 | ||
| 23 | -2。 索引结构配置 : 定义了ES的字段,每个字段的索引mapping配置,支持各个域的查询,包括默认的域的查询。索引配置预定一号了一堆分析方式 | ||
| 24 | - 请帮我补充具体实现的一些配置 | 483 | +### 6.2 数据索引 |
| 484 | +- ✅ 数据转换(字段映射、类型转换) | ||
| 485 | +- ✅ 向量生成(文本向量、图片向量) | ||
| 486 | +- ✅ 向量缓存(避免重复计算) | ||
| 487 | +- ✅ 批量索引(错误处理、重试机制) | ||
| 488 | +- ✅ ES mapping 自动生成 | ||
| 25 | 489 | ||
| 26 | -## 测试数据灌入 | 490 | +### 6.3 查询处理 |
| 491 | +- ✅ 查询改写(词典配置) | ||
| 492 | +- ✅ 语言检测 | ||
| 493 | +- ✅ 多语言翻译(DeepL API) | ||
| 494 | +- ✅ 文本向量化(BGE-M3) | ||
| 495 | +- ✅ 域提取(支持 `domain:query` 语法) | ||
| 27 | 496 | ||
| 28 | -灌入数据、mysql到ES的自动同步,不在本项目的范围内,但是,该项目 为了提供测试数据,需要 构造一个实例 customer1. | ||
| 29 | -我们为他构造一套应用配置和索引配置。 | ||
| 30 | -暂时是随机抽了我们自己的1w数据,建设辅助表,然后写一个程序,将数据分别灌入主表和辅表。 | 497 | +### 6.4 搜索功能 |
| 498 | +- ✅ 布尔表达式解析(AND, OR, RANK, ANDNOT, 括号) | ||
| 499 | +- ✅ 多语言查询构建(语言路由、字段映射) | ||
| 500 | +- ✅ 语义搜索(KNN 检索) | ||
| 501 | +- ✅ 相关性排序(BM25 + 向量相似度) | ||
| 502 | +- ✅ 结果聚合(Faceted Search) | ||
| 31 | 503 | ||
| 32 | -请帮我补充具体,当前测试数据灌入的具体的配置和方式,比如辅助表的内容 对应的应用结构配置 索引配置 等等。 | 504 | +### 6.5 API 服务 |
| 505 | +- ✅ RESTful API(FastAPI) | ||
| 506 | +- ✅ 搜索接口(文本搜索、图片搜索) | ||
| 507 | +- ✅ 文档查询接口 | ||
| 508 | +- ✅ 前端界面(HTML + JavaScript) | ||
| 33 | 509 | ||
| 34 | -## queryParser | 510 | +--- |
| 35 | 511 | ||
| 36 | -1. 查询改写。 配置词典的key是query,value是改写后的查询表达式,比如。比如品牌词 改写为在brand|query OR name|query,类别词、标签词等都可以放进去。纠错、规范化、查询改写等 都可以通过这个词典来配置。 | ||
| 37 | -2. 翻译。配置需要得到的几种目标语言。 在customer1测试案例中,我们配置 zh en两种语言。先对query做语言检测,如果query是中文那么要翻译一下en,如果是en那么要翻译zh,如果两者都不是那么zh en都需要翻译。 | ||
| 38 | -3. 如果配置打开了text_embedding查询,并且query 包含了default域的查询,那么要把default域的查询词转向量,后面searcher会用这个向量参与查询。 | 512 | +## 7. 技术栈 |
| 39 | 513 | ||
| 40 | -也帮我补充一些具体实现情况 | 514 | +- **后端**:Python 3.6+ |
| 515 | +- **搜索引擎**:Elasticsearch | ||
| 516 | +- **数据库**:MySQL(Shoplazza) | ||
| 517 | +- **向量模型**:BGE-M3(文本)、CN-CLIP(图片) | ||
| 518 | +- **翻译服务**:DeepL API | ||
| 519 | +- **API 框架**:FastAPI | ||
| 520 | +- **前端**:HTML + JavaScript | ||
| 41 | 521 | ||
| 42 | -## searcher | 522 | +--- |
| 43 | 523 | ||
| 44 | -支持多种检索表达式: | ||
| 45 | -支持多种匹配方式,如AND、OR、RANK、NOTAND以及(),优先级从高到低为(),ANDNOT,AND,OR,RANK。 | 524 | +## 8. 配置文件示例 |
| 46 | 525 | ||
| 47 | -## default域的相关性,是代码里面单独计算,是特定的深度定制优化的,暂时不做配置化。 | 526 | +完整配置示例请参考:`config/schema/customer1_config.yaml` |
| 48 | 527 | ||
| 49 | -暂时具体实现为 bm25()+0.2*text_embedding_relevence(也就是knn检索表达式的打分) | ||
| 50 | -bm25() 包括多语言的打分:内部需要通过配置翻译为多种语言(配置几种目标语言 默认中文、英文,并且设置对应的检索域),然后分别到对应的字段搜索,中文字段到配置的中文title搜索,英文到对应的英文title搜索。 | 528 | +--- |
| 51 | 529 | ||
| 52 | -也帮我补充一些具体实现情况 | 530 | +## 9. 相关文档 |
| 53 | 531 | ||
| 532 | +- `MULTILANG_FEATURE.md` - 多语言功能详细说明 | ||
| 533 | +- `QUICKSTART.md` - 快速开始指南 | ||
| 534 | +- `HighLevelDesign.md` - 高层设计文档 | ||
| 535 | +- `IMPLEMENTATION_SUMMARY.md` - 实现总结 | ||
| 536 | +- `商品数据源入ES配置规范.md` - 数据源配置规范 |