From b73baf85cb520d7316d2514d24edfec5e8334b7a Mon Sep 17 00:00:00 2001 From: tangwang Date: Fri, 14 Nov 2025 12:18:56 +0800 Subject: [PATCH] 撰写接口文档 --- API_INTEGRATION_GUIDE.md | 791 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ES_QUERY_RESTRUCTURE_COMPLETE.md | 2 +- HighLevelDesign.md | 106 ---------------------------------------------------------------------------------------------------------- INDEX_FIELDS_DOCUMENTATION.md | 223 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ config/config.yaml | 4 ++-- config/config_loader.py | 2 +- 6 files changed, 1018 insertions(+), 110 deletions(-) create mode 100644 API_INTEGRATION_GUIDE.md create mode 100644 INDEX_FIELDS_DOCUMENTATION.md diff --git a/API_INTEGRATION_GUIDE.md b/API_INTEGRATION_GUIDE.md new file mode 100644 index 0000000..1639f83 --- /dev/null +++ b/API_INTEGRATION_GUIDE.md @@ -0,0 +1,791 @@ +# 搜索API接口对接指南 + +本文档为搜索服务的使用方提供完整的API对接指南,包括接口说明、请求参数、响应格式和使用示例。 + +## 目录 + +1. [快速开始](#快速开始) +2. [接口概览](#接口概览) +3. [文本搜索接口](#文本搜索接口) +4. [图片搜索接口](#图片搜索接口) +5. [响应格式说明](#响应格式说明) +6. [常见场景示例](#常见场景示例) +7. [错误处理](#错误处理) +8. [最佳实践](#最佳实践) + +--- + +## 快速开始 + +### 基础信息 + +- **Base URL**: `http://your-domain:6002` 或 `http://120.76.41.98:6002` +- **协议**: HTTP/HTTPS +- **数据格式**: JSON +- **字符编码**: UTF-8 +- **请求方法**: POST(搜索接口) + +### 最简单的搜索请求 + +```bash +curl -X POST "http://localhost:6002/search/" \ + -H "Content-Type: application/json" \ + -d '{ + "query": "芭比娃娃" + }' +``` + +### Python示例 + +```python +import requests + +url = "http://localhost:6002/search/" +response = requests.post(url, json={"query": "芭比娃娃"}) +data = response.json() +print(f"找到 {data['total']} 个结果") +``` + +### JavaScript示例 + +```javascript +const response = await fetch('http://localhost:6002/search/', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + query: '芭比娃娃' + }) +}); +const data = await response.json(); +console.log(`找到 ${data.total} 个结果`); +``` + +--- + +## 接口概览 + +| 接口 | 方法 | 路径 | 说明 | +|------|------|------|------| +| 文本搜索 | POST | `/search/` | 执行文本搜索查询 | +| 图片搜索 | POST | `/search/image` | 基于图片相似度搜索 | +| 搜索建议 | GET | `/search/suggestions` | 获取搜索建议(框架,暂未实现) | +| 获取文档 | GET | `/search/{doc_id}` | 根据ID获取单个文档 | +| 健康检查 | GET | `/admin/health` | 检查服务状态 | + +--- + +## 文本搜索接口 + +### 接口信息 + +- **端点**: `POST /search/` +- **描述**: 执行文本搜索查询,支持多语言、布尔表达式、过滤器和分面搜索 + +### 请求参数 + +#### 完整请求体结构 + +```json +{ + "query": "string (required)", + "size": 10, + "from": 0, + "filters": {}, + "range_filters": {}, + "facets": [], + "sort_by": "string", + "sort_order": "desc", + "min_score": 0.0, + "debug": false, + "user_id": "string", + "session_id": "string" +} +``` + +#### 参数详细说明 + +| 参数 | 类型 | 必填 | 默认值 | 说明 | +|------|------|------|--------|------| +| `query` | string | ✅ | - | 搜索查询字符串,支持布尔表达式(AND, OR, RANK, ANDNOT) | +| `size` | integer | ❌ | 10 | 返回结果数量(1-100) | +| `from` | integer | ❌ | 0 | 分页偏移量(用于分页) | +| `filters` | object | ❌ | null | 精确匹配过滤器(见下文) | +| `range_filters` | object | ❌ | null | 数值范围过滤器(见下文) | +| `facets` | array | ❌ | null | 分面配置(见下文) | +| `sort_by` | string | ❌ | null | 排序字段名(如 `min_price`, `max_price`, `title`) | +| `sort_order` | string | ❌ | "desc" | 排序方向:`asc`(升序)或 `desc`(降序) | +| `min_score` | float | ❌ | null | 最小相关性分数阈值 | +| `debug` | boolean | ❌ | false | 是否返回调试信息 | +| `user_id` | string | ❌ | null | 用户ID(用于个性化,预留) | +| `session_id` | string | ❌ | null | 会话ID(用于分析,预留) | + +### 过滤器详解 + +#### 1. 精确匹配过滤器 (filters) + +用于精确匹配或多值匹配(OR 逻辑)。 + +**格式**: +```json +{ + "filters": { + "category_keyword": "玩具", // 单值:精确匹配 + "vendor_keyword": ["乐高", "孩之宝"], // 数组:匹配任意值(OR) + "product_type_keyword": "益智玩具" // 单值:精确匹配 + } +} +``` + +**支持的值类型**: +- 字符串:精确匹配 +- 整数:精确匹配 +- 布尔值:精确匹配 +- 数组:匹配任意值(OR 逻辑) + +**常用过滤字段**: +- `category_keyword`: 类目 +- `vendor_keyword`: 品牌/供应商 +- `product_type_keyword`: 商品类型 +- `tags_keyword`: 标签 + +#### 2. 范围过滤器 (range_filters) + +用于数值字段的范围过滤。 + +**格式**: +```json +{ + "range_filters": { + "min_price": { + "gte": 50, // 大于等于 + "lte": 200 // 小于等于 + }, + "max_price": { + "gt": 100 // 大于 + }, + "create_time": { + "gte": "2024-01-01T00:00:00Z" // 日期时间字符串 + } + } +} +``` + +**支持的操作符**: +- `gte`: 大于等于 (>=) +- `gt`: 大于 (>) +- `lte`: 小于等于 (<=) +- `lt`: 小于 (<) + +**注意**: 至少需要指定一个操作符。 + +**常用范围字段**: +- `min_price`: 最低价格 +- `max_price`: 最高价格 +- `compare_at_price`: 原价 +- `create_time`: 创建时间 +- `update_time`: 更新时间 + +#### 3. 分面配置 (facets) + +用于生成分面统计(分组聚合),常用于构建筛选器UI。 + +**简单模式**(字符串数组): +```json +{ + "facets": ["category_keyword", "vendor_keyword"] +} +``` + +**高级模式**(配置对象数组): +```json +{ + "facets": [ + { + "field": "category_keyword", + "size": 15, + "type": "terms" + }, + { + "field": "min_price", + "type": "range", + "ranges": [ + {"key": "0-50", "to": 50}, + {"key": "50-100", "from": 50, "to": 100}, + {"key": "100-200", "from": 100, "to": 200}, + {"key": "200+", "from": 200} + ] + } + ] +} +``` + +**分面配置参数**: +- `field`: 字段名(必填) +- `size`: 返回的分组数量(默认:10,范围:1-100) +- `type`: 分面类型,`terms`(分组统计)或 `range`(范围统计) +- `ranges`: 范围定义(仅当 type='range' 时需要) + +### 布尔表达式语法 + +搜索查询支持布尔表达式,提供更灵活的搜索能力。 + +**支持的操作符**: + +| 操作符 | 描述 | 示例 | +|--------|------|------| +| `AND` | 所有词必须匹配 | `玩具 AND 乐高` | +| `OR` | 任意词匹配 | `芭比 OR 娃娃` | +| `ANDNOT` | 排除特定词 | `玩具 ANDNOT 电动` | +| `RANK` | 排序加权(不强制匹配) | `玩具 RANK 乐高` | +| `()` | 分组 | `玩具 AND (乐高 OR 芭比)` | + +**操作符优先级**(从高到低): +1. `()` - 括号 +2. `ANDNOT` - 排除 +3. `AND` - 与 +4. `OR` - 或 +5. `RANK` - 排序 + +**示例**: +``` +"芭比娃娃" // 简单查询 +"玩具 AND 乐高" // AND 查询 +"芭比 OR 娃娃" // OR 查询 +"玩具 ANDNOT 电动" // 排除查询 +"玩具 AND (乐高 OR 芭比)" // 复杂查询 +``` + +--- + +## 图片搜索接口 + +### 接口信息 + +- **端点**: `POST /search/image` +- **描述**: 基于图片相似度进行搜索,使用图片向量进行语义匹配 + +### 请求参数 + +```json +{ + "image_url": "string (required)", + "size": 10, + "filters": {}, + "range_filters": {} +} +``` + +### 参数说明 + +| 参数 | 类型 | 必填 | 默认值 | 描述 | +|------|------|------|--------|------| +| `image_url` | string | ✅ | - | 查询图片的 URL | +| `size` | integer | ❌ | 10 | 返回结果数量(1-100) | +| `filters` | object | ❌ | null | 精确匹配过滤器 | +| `range_filters` | object | ❌ | null | 数值范围过滤器 | + +### 请求示例 + +```bash +curl -X POST "http://localhost:6002/search/image" \ + -H "Content-Type: application/json" \ + -d '{ + "image_url": "https://example.com/barbie.jpg", + "size": 20, + "filters": { + "category_keyword": "玩具" + }, + "range_filters": { + "min_price": { + "lte": 100 + } + } + }' +``` + +--- + +## 响应格式说明 + +### 标准响应结构 + +```json +{ + "results": [ + { + "product_id": "12345", + "title": "芭比时尚娃娃", + "handle": "barbie-doll", + "description": "高品质芭比娃娃", + "vendor": "美泰", + "product_type": "玩具", + "tags": "娃娃, 玩具, 女孩", + "price": 89.99, + "compare_at_price": 129.99, + "currency": "USD", + "image_url": "https://example.com/image.jpg", + "in_stock": true, + "variants": [ + { + "variant_id": "67890", + "title": "粉色款", + "price": 89.99, + "compare_at_price": 129.99, + "sku": "BARBIE-001", + "stock": 100, + "options": { + "option1": "粉色", + "option2": "标准款" + } + } + ], + "relevance_score": 8.5 + } + ], + "total": 118, + "max_score": 8.5, + "facets": [ + { + "field": "category_keyword", + "label": "category_keyword", + "type": "terms", + "values": [ + { + "value": "玩具", + "label": "玩具", + "count": 85, + "selected": false + } + ] + } + ], + "query_info": { + "original_query": "芭比娃娃", + "detected_language": "zh", + "translations": { + "en": "barbie doll" + } + }, + "suggestions": [], + "related_searches": [], + "took_ms": 45, + "performance_info": null, + "debug_info": null +} +``` + +### 响应字段说明 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `results` | array | 搜索结果列表(ProductResult对象数组) | +| `results[].product_id` | string | 商品ID | +| `results[].title` | string | 商品标题 | +| `results[].price` | float | 价格(min_price) | +| `results[].variants` | array | 变体列表(SKU列表) | +| `results[].relevance_score` | float | 相关性分数 | +| `total` | integer | 匹配的总文档数 | +| `max_score` | float | 最高相关性分数 | +| `facets` | array | 分面统计结果 | +| `query_info` | object | 查询处理信息 | +| `took_ms` | integer | 搜索耗时(毫秒) | + +### ProductResult字段说明 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `product_id` | string | 商品ID(SPU ID) | +| `title` | string | 商品标题 | +| `handle` | string | 商品URL handle | +| `description` | string | 商品描述 | +| `vendor` | string | 供应商/品牌 | +| `product_type` | string | 商品类型 | +| `tags` | string | 标签 | +| `price` | float | 价格(min_price) | +| `compare_at_price` | float | 原价 | +| `currency` | string | 货币单位(默认USD) | +| `image_url` | string | 主图URL | +| `in_stock` | boolean | 是否有库存(任意变体有库存即为true) | +| `variants` | array | 变体列表 | +| `relevance_score` | float | 相关性分数 | + +### VariantResult字段说明 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `variant_id` | string | 变体ID(SKU ID) | +| `title` | string | 变体标题 | +| `price` | float | 价格 | +| `compare_at_price` | float | 原价 | +| `sku` | string | SKU编码 | +| `stock` | integer | 库存数量 | +| `options` | object | 选项(颜色、尺寸等) | + +--- + +## 常见场景示例 + +### 场景1:商品列表页搜索 + +**需求**: 搜索"玩具",按价格从低到高排序,显示前20个结果 + +```json +{ + "query": "玩具", + "size": 20, + "from": 0, + "sort_by": "min_price", + "sort_order": "asc" +} +``` + +### 场景2:带筛选的商品搜索 + +**需求**: 搜索"玩具",筛选类目为"益智玩具",价格在50-200之间 + +```json +{ + "query": "玩具", + "size": 20, + "filters": { + "category_keyword": "益智玩具" + }, + "range_filters": { + "min_price": { + "gte": 50, + "lte": 200 + } + } +} +``` + +### 场景3:带分面的商品搜索 + +**需求**: 搜索"玩具",获取类目和品牌的分面统计,用于构建筛选器 + +```json +{ + "query": "玩具", + "size": 20, + "facets": [ + "category_keyword", + "vendor_keyword" + ] +} +``` + +### 场景4:多条件组合搜索 + +**需求**: 搜索"玩具",筛选多个品牌,价格范围,并获取分面统计 + +```json +{ + "query": "玩具", + "size": 20, + "filters": { + "vendor_keyword": ["乐高", "孩之宝", "美泰"] + }, + "range_filters": { + "min_price": { + "gte": 50, + "lte": 200 + } + }, + "facets": [ + { + "field": "category_keyword", + "size": 15 + }, + { + "field": "min_price", + "type": "range", + "ranges": [ + {"key": "0-50", "to": 50}, + {"key": "50-100", "from": 50, "to": 100}, + {"key": "100-200", "from": 100, "to": 200}, + {"key": "200+", "from": 200} + ] + } + ], + "sort_by": "min_price", + "sort_order": "asc" +} +``` + +### 场景5:布尔表达式搜索 + +**需求**: 搜索包含"玩具"和"乐高"的商品,排除"电动" + +```json +{ + "query": "玩具 AND 乐高 ANDNOT 电动", + "size": 20 +} +``` + +### 场景6:分页查询 + +**需求**: 获取第2页结果(每页20条) + +```json +{ + "query": "玩具", + "size": 20, + "from": 20 +} +``` + +--- + +## 错误处理 + +### 错误响应格式 + +```json +{ + "error": "错误信息", + "detail": "详细错误信息(可选)" +} +``` + +### 常见错误码 + +| HTTP状态码 | 说明 | 处理建议 | +|-----------|------|---------| +| 200 | 成功 | - | +| 400 | 请求参数错误 | 检查请求参数格式和必填字段 | +| 404 | 接口不存在 | 检查接口路径 | +| 500 | 服务器内部错误 | 联系技术支持 | + +### 错误处理示例 + +**Python**: +```python +import requests + +try: + response = requests.post(url, json=payload, timeout=10) + response.raise_for_status() + data = response.json() +except requests.exceptions.HTTPError as e: + print(f"HTTP错误: {e}") + if response.status_code == 400: + error_data = response.json() + print(f"错误详情: {error_data.get('detail')}") +except requests.exceptions.RequestException as e: + print(f"请求异常: {e}") +``` + +**JavaScript**: +```javascript +try { + const response = await fetch(url, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload) + }); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.error || `HTTP ${response.status}`); + } + + const data = await response.json(); +} catch (error) { + console.error('搜索失败:', error.message); +} +``` + +--- + +### 5. 代码示例 + +**完整的搜索函数(Python)**: + +```python +import requests +from typing import Dict, Any, Optional, List + +class SearchClient: + def __init__(self, base_url: str = "http://localhost:6002"): + self.base_url = base_url + self.timeout = 10 + + def search( + self, + query: str, + size: int = 20, + from_: int = 0, + filters: Optional[Dict] = None, + range_filters: Optional[Dict] = None, + facets: Optional[List] = None, + sort_by: Optional[str] = None, + sort_order: str = "desc" + ) -> Dict[str, Any]: + """ + 执行搜索查询 + + Args: + query: 搜索查询字符串 + size: 返回结果数量 + from_: 分页偏移量 + filters: 精确匹配过滤器 + range_filters: 范围过滤器 + facets: 分面配置 + sort_by: 排序字段 + sort_order: 排序方向 + + Returns: + 搜索结果字典 + """ + url = f"{self.base_url}/search/" + payload = { + "query": query, + "size": size, + "from": from_, + } + + if filters: + payload["filters"] = filters + if range_filters: + payload["range_filters"] = range_filters + if facets: + payload["facets"] = facets + if sort_by: + payload["sort_by"] = sort_by + payload["sort_order"] = sort_order + + try: + response = requests.post( + url, + json=payload, + timeout=self.timeout + ) + response.raise_for_status() + return response.json() + except requests.exceptions.RequestException as e: + raise Exception(f"搜索请求失败: {e}") + +# 使用示例 +client = SearchClient() +result = client.search( + query="玩具", + size=20, + filters={"category_keyword": "益智玩具"}, + range_filters={"min_price": {"gte": 50, "lte": 200}}, + facets=["category_keyword", "vendor_keyword"], + sort_by="min_price", + sort_order="asc" +) + +print(f"找到 {result['total']} 个结果") +for product in result['results']: + print(f"{product['title']} - ¥{product['price']}") +``` + +**完整的搜索函数(JavaScript)**: + +```javascript +class SearchClient { + constructor(baseUrl = 'http://localhost:6002') { + this.baseUrl = baseUrl; + this.timeout = 10000; + } + + async search({ + query, + size = 20, + from = 0, + filters = null, + rangeFilters = null, + facets = null, + sortBy = null, + sortOrder = 'desc' + }) { + const url = `${this.baseUrl}/search/`; + const payload = { + query, + size, + from, + }; + + if (filters) payload.filters = filters; + if (rangeFilters) payload.range_filters = rangeFilters; + if (facets) payload.facets = facets; + if (sortBy) { + payload.sort_by = sortBy; + payload.sort_order = sortOrder; + } + + try { + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(payload), + signal: AbortSignal.timeout(this.timeout) + }); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.error || `HTTP ${response.status}`); + } + + return await response.json(); + } catch (error) { + throw new Error(`搜索请求失败: ${error.message}`); + } + } +} + +// 使用示例 +const client = new SearchClient(); +const result = await client.search({ + query: '玩具', + size: 20, + filters: { category_keyword: '益智玩具' }, + rangeFilters: { min_price: { gte: 50, lte: 200 } }, + facets: ['category_keyword', 'vendor_keyword'], + sortBy: 'min_price', + sortOrder: 'asc' +}); + +console.log(`找到 ${result.total} 个结果`); +result.results.forEach(product => { + console.log(`${product.title} - ¥${product.price}`); +}); +``` + +--- + +## 附录 + +### 常用字段列表 + +#### 过滤字段(使用 `*_keyword` 后缀) + +- `category_keyword`: 类目 +- `vendor_keyword`: 品牌/供应商 +- `product_type_keyword`: 商品类型 +- `tags_keyword`: 标签 + +#### 范围字段 + +- `min_price`: 最低价格 +- `max_price`: 最高价格 +- `compare_at_price`: 原价 +- `create_time`: 创建时间 +- `update_time`: 更新时间 + +#### 排序字段 + +- `min_price`: 最低价格 +- `max_price`: 最高价格 +- `title`: 标题(字母序) +- `create_time`: 创建时间 +- `update_time`: 更新时间 +- `relevance_score`: 相关性分数(默认) + diff --git a/ES_QUERY_RESTRUCTURE_COMPLETE.md b/ES_QUERY_RESTRUCTURE_COMPLETE.md index 6a97e3e..a2d9852 100644 --- a/ES_QUERY_RESTRUCTURE_COMPLETE.md +++ b/ES_QUERY_RESTRUCTURE_COMPLETE.md @@ -218,7 +218,7 @@ from .rerank_engine import RerankEngine # 原 RankingEngine }, { "knn": { - "field": "name_embedding", + "field": "title_embedding", "query_vector": [...], "k": 50, "num_candidates": 200 diff --git a/HighLevelDesign.md b/HighLevelDesign.md index 7497eb2..42b185d 100644 --- a/HighLevelDesign.md +++ b/HighLevelDesign.md @@ -51,112 +51,6 @@ updater varchar(64) update_time datetime deleted bit(1) -所有租户共用这个主表 - -### 每个租户的辅表 -各个租户,有自己的扩展表。 入索引的时候,商品主表 shoplazza_product_sku 的 id + shopid,拼接租户自己单独的扩展表(比如可以放一些自己的属性体系、各种语言的商品名、品牌名、标签、分类等) - -但是,各个租户,可能有不一样的业务数据,比如不同租户有不同的属性的体系、不同语言的商品标题(一般至少有中英文两种满足跨境的搜索需求),有不同的权重(提权)字段、业务过滤和聚合字段。 -能够统一的 只能是 sku表 按照一套配置规范、做一个配置文件,按照配置文件建设ES mapping结构以及做数据的入库。 - -## SearchEngine - -### IndexerConfig - @阿里opensearch电商行业.md, 有两套配置 -1. 应用结构配置 : 定义了ES的输入数据有哪些字段、关联mysql的哪些字段. -2. 索引结构配置 : 定义了ES的字段,每个字段的索引mapping配置,支持各个域的查询,包括默认的域的查询。索引配置预定一号了一堆分析方式 由 @商品数据源入ES配置规范.md 定义。 - - - -### 测试数据灌入 - -灌入数据、mysql到ES的自动同步,不在本项目的范围内,另外有java项目负责。 -但是,该项目 为了提供测试数据,需要 构造一个实例 tenant1. -我们为他构造一套应用配置和索引配置。 -灌入一批测试数据,可以些一个简单的 全量灌入的实现。 -数据源地址在:data/tenant1/goods_with_pic.5years_congku.csv.shuf.1w -请根据这里面的字段,建设辅助表(注意看哪些字段在主表有,哪些需要放到辅表) -然后写一个程序,将数据分别灌入主表和辅表。 - - -### queryParser -query分析,做以下几个事情: - -1. 查询改写。 配置词典的key是query,value是改写后的查询表达式,比如。比如品牌词 改写为在brand|query OR name|query,类别词、标签词等都可以放进去。纠错、规范化、查询改写等 都可以通过这个词典来配置。 -2. 翻译。配置需要得到的几种目标语言。 在tenant1测试案例中,我们配置 zh en两种语言。先对query做语言检测,如果query是中文那么要翻译一下en,如果是en那么要翻译zh,如果两者都不是那么zh en都需要翻译。 -3. 如果配置打开了text_embedding查询,并且query 包含了default域的查询,那么要把default域的查询词转向量,后面searcher会用这个向量参与查询。 -翻译代码参考: -``` -import requests -api_url = "https://api.deepl.com/v2/translate" -headers = { - "Authorization": "DeepL-Auth-Key YOUR_AUTH_KEY", - "Content-Type": "application/json", -} -payload = { - "text": ["要翻译的文本"], - "target_lang": "ZH", # 中文 -} - -response = requests.post(api_url, headers=headers, json=payload, timeout=10) - -if response.status_code == 200: - data = response.json() - translation = data["translations"][0]["text"] - print(translation) -``` - -### searcher - -支持多种检索表达式: -支持多种匹配方式,如AND、OR、RANK、NOTAND以及(),优先级从高到低为(),ANDNOT,AND,OR,RANK。 - -default域的相关性,是代码里面单独计算,是特定的深度定制优化的,暂时不做配置化。 - -暂时具体实现为 bm25()+0.2*text_embedding_relevence(也就是knn检索表达式的打分) -bm25() 包括多语言的打分:内部需要通过配置翻译为多种语言(配置几种目标语言 默认中文、英文,并且设置对应的检索域),然后分别到对应的字段搜索,中文字段到配置的中文title搜索,英文到对应的英文title搜索。 -bm25打分(base_query): -"multi_match": { - "query": search_query, - "fields": match_fields, - "minimum_should_match": "67%", - "tie_breaker": 0.9, - "boost": 1.0, # Low boost for auxiliary keyword query - "_name": "base_query" -} - -text_embedding_relevence: - knn_query = { - "knn": { - "field": text_embedding_field, - "query_vector": query_vector.tolist(), - "k": KNN_K, - "num_candidates": KNN_NUM_CANDIDATES - } - } - -支持配置化的排序打分: -default域 支持配置的排序方式: -| 场景 | 表达式 | 含义 | -|------|--------|------| -| 电商 | `text_re()+general_score*2+timeliness(end_time)` | 文本分、宝贝综合分值、过期时间 | - - -有一个配置,是否按照spu聚合,如果打开spu聚合,那么 要配置spu_id的字段,检索表达上需要加上: -es_query["aggs"]["unique_count"] = { - "cardinality": { - "field": spu_id_field_name - } -} -es_query["collapse"]["inner_hits"] = { - "_source": False, - "name": "top_docs", - "size": INNER_HITS_SIZE -} - - -## 相关配置 - ES_CONFIG = { 'host': 'http://localhost:9200', 'username': 'essa', diff --git a/INDEX_FIELDS_DOCUMENTATION.md b/INDEX_FIELDS_DOCUMENTATION.md new file mode 100644 index 0000000..3b85168 --- /dev/null +++ b/INDEX_FIELDS_DOCUMENTATION.md @@ -0,0 +1,223 @@ +# 索引字段说明文档 + +本文档详细说明了 Elasticsearch 索引中所有字段的类型、索引方式、数据来源等信息。 + +## 索引基本信息 + +- **索引名称**: `search_products` +- **索引级别**: SPU级别(商品级别) +- **数据结构**: SPU文档包含嵌套的variants(SKU)数组 + +## 字段说明表 + +### 基础字段 + +| 索引字段名 | ES字段类型 | 是否索引 | 索引方式 | 数据来源表 | 表中字段名 | 表中字段类型 | 说明 | +|-----------|-----------|---------|---------|-----------|-----------|-------------|------| +| tenant_id | KEYWORD | 是 | 精确匹配 | SPU表 | tenant_id | BIGINT | 租户ID,用于多租户隔离 | +| product_id | KEYWORD | 是 | 精确匹配 | SPU表 | id | BIGINT | 商品ID(SPU ID) | +| handle | KEYWORD | 是 | 精确匹配 | SPU表 | handle | VARCHAR(255) | 商品URL handle | + +### 文本搜索字段 + +| 索引字段名 | ES字段类型 | 是否索引 | 索引方式 | 数据来源表 | 表中字段名 | 表中字段类型 | Boost权重 | 说明 | +|-----------|-----------|---------|---------|-----------|-----------|-------------|-----------|------| +| title | TEXT | 是 | chinese_ecommerce分析器 | SPU表 | title | VARCHAR(512) | 3.0 | 商品标题,权重最高 | +| brief | TEXT | 是 | chinese_ecommerce分析器 | SPU表 | brief | VARCHAR(512) | 1.5 | 商品简介 | +| description | TEXT | 是 | chinese_ecommerce分析器 | SPU表 | description | TEXT | 1.0 | 商品详细描述 | + +### SEO字段 + +| 索引字段名 | ES字段类型 | 是否索引 | 索引方式 | 数据来源表 | 表中字段名 | 表中字段类型 | Boost权重 | 是否返回 | 说明 | +|-----------|-----------|---------|---------|-----------|-----------|-------------|-----------|---------|------| +| seo_title | TEXT | 是 | chinese_ecommerce分析器 | SPU表 | seo_title | VARCHAR(512) | 2.0 | 否 | SEO标题,用于提升相关性 | +| seo_description | TEXT | 是 | chinese_ecommerce分析器 | SPU表 | seo_description | TEXT | 1.5 | 否 | SEO描述 | +| seo_keywords | TEXT | 是 | chinese_ecommerce分析器 | SPU表 | seo_keywords | VARCHAR(1024) | 2.0 | 否 | SEO关键词 | + +### 分类和标签字段 + +| 索引字段名 | ES字段类型 | 是否索引 | 索引方式 | 数据来源表 | 表中字段名 | 表中字段类型 | Boost权重 | 是否返回 | 说明 | +|-----------|-----------|---------|---------|-----------|-----------|-------------|-----------|---------|------| +| vendor | TEXT | 是 | chinese_ecommerce分析器 | SPU表 | vendor | VARCHAR(255) | 1.5 | 是 | 供应商/品牌(文本搜索) | +| vendor_keyword | KEYWORD | 是 | 精确匹配 | SPU表 | vendor | VARCHAR(255) | - | 否 | 供应商/品牌(精确匹配,用于过滤) | +| product_type | TEXT | 是 | chinese_ecommerce分析器 | SPU表 | category | VARCHAR(255) | 1.5 | 是 | 商品类型(文本搜索) | +| product_type_keyword | KEYWORD | 是 | 精确匹配 | SPU表 | category | VARCHAR(255) | - | 否 | 商品类型(精确匹配,用于过滤) | +| tags | TEXT | 是 | chinese_ecommerce分析器 | SPU表 | tags | VARCHAR(1024) | 1.0 | 是 | 标签(文本搜索) | +| tags_keyword | KEYWORD | 是 | 精确匹配 | SPU表 | tags | VARCHAR(1024) | - | 否 | 标签(精确匹配,用于过滤) | +| category | TEXT | 是 | chinese_ecommerce分析器 | SPU表 | category | VARCHAR(255) | 1.5 | 是 | 类目(文本搜索) | +| category_keyword | KEYWORD | 是 | 精确匹配 | SPU表 | category | VARCHAR(255) | - | 否 | 类目(精确匹配,用于过滤) | + +### 价格字段 + +| 索引字段名 | ES字段类型 | 是否索引 | 索引方式 | 数据来源表 | 表中字段名 | 表中字段类型 | 说明 | +|-----------|-----------|---------|---------|-----------|-----------|-------------|------| +| min_price | FLOAT | 是 | 数值范围 | SKU表(聚合计算) | price | DECIMAL(10,2) | 最低价格(从所有SKU中取最小值) | +| max_price | FLOAT | 是 | 数值范围 | SKU表(聚合计算) | price | DECIMAL(10,2) | 最高价格(从所有SKU中取最大值) | +| compare_at_price | FLOAT | 是 | 数值范围 | SKU表(聚合计算) | compare_at_price | DECIMAL(10,2) | 原价(从所有SKU中取最大值) | + +**价格计算逻辑**: +- `min_price`: 取该SPU下所有SKU的price字段的最小值 +- `max_price`: 取该SPU下所有SKU的price字段的最大值 +- `compare_at_price`: 取该SPU下所有SKU的compare_at_price字段的最大值(如果存在) + +### 图片字段 + +| 索引字段名 | ES字段类型 | 是否索引 | 索引方式 | 数据来源表 | 表中字段名 | 表中字段类型 | 说明 | +|-----------|-----------|---------|---------|-----------|-----------|-------------|------| +| image_url | KEYWORD | 否 | 不索引 | SPU表 | image_src | VARCHAR(500) | 商品主图URL,仅用于展示 | + +### 文本嵌入字段 + +| 索引字段名 | ES字段类型 | 是否索引 | 索引方式 | 数据来源表 | 表中字段名 | 表中字段类型 | 说明 | +|-----------|-----------|---------|---------|-----------|-----------|-------------|------| +| title_embedding | TEXT_EMBEDDING | 是 | 向量相似度(dot_product) | 计算生成 | title | VARCHAR(512) | 标题的文本向量(1024维),用于语义搜索 | + +**说明**: +- 向量维度:1024 +- 相似度算法:dot_product(点积) +- 数据来源:基于title字段通过BGE-M3模型生成 + +### 时间字段 + +| 索引字段名 | ES字段类型 | 是否索引 | 索引方式 | 数据来源表 | 表中字段名 | 表中字段类型 | 是否返回 | 说明 | +|-----------|-----------|---------|---------|-----------|-----------|-------------|---------|------| +| create_time | DATE | 是 | 日期范围 | SPU表 | create_time | DATETIME | 是 | 创建时间 | +| update_time | DATE | 是 | 日期范围 | SPU表 | update_time | DATETIME | 是 | 更新时间 | +| shoplazza_created_at | DATE | 是 | 日期范围 | SPU表 | shoplazza_created_at | DATETIME | 否 | 店匠系统创建时间 | +| shoplazza_updated_at | DATE | 是 | 日期范围 | SPU表 | shoplazza_updated_at | DATETIME | 否 | 店匠系统更新时间 | + +### 嵌套Variants字段(SKU级别) + +| 索引字段名 | ES字段类型 | 是否索引 | 索引方式 | 数据来源表 | 表中字段名 | 表中字段类型 | 说明 | +|-----------|-----------|---------|---------|-----------|-----------|-------------|------| +| variants | JSON (nested) | 是 | 嵌套对象 | SKU表 | - | - | 商品变体数组(嵌套结构) | + +#### Variants子字段 + +| 索引字段名 | ES字段类型 | 是否索引 | 索引方式 | 数据来源表 | 表中字段名 | 表中字段类型 | 说明 | +|-----------|-----------|---------|---------|-----------|-----------|-------------|------| +| variants.variant_id | keyword | 是 | 精确匹配 | SKU表 | id | BIGINT | 变体ID(SKU ID) | +| variants.title | text | 是 | chinese_ecommerce分析器 | SKU表 | title | VARCHAR(500) | 变体标题 | +| variants.price | float | 是 | 数值范围 | SKU表 | price | DECIMAL(10,2) | 变体价格 | +| variants.compare_at_price | float | 是 | 数值范围 | SKU表 | compare_at_price | DECIMAL(10,2) | 变体原价 | +| variants.sku | keyword | 是 | 精确匹配 | SKU表 | sku | VARCHAR(100) | SKU编码 | +| variants.stock | long | 是 | 数值范围 | SKU表 | inventory_quantity | INT(11) | 库存数量 | +| variants.options | object | 是 | 对象 | SKU表 | option1/option2/option3 | VARCHAR(255) | 选项(颜色、尺寸等) | + +**Variants结构说明**: +- `variants` 是一个嵌套对象数组,每个元素代表一个SKU +- 使用ES的nested类型,支持对嵌套字段进行独立查询和过滤 +- `options` 对象包含 `option1`、`option2`、`option3` 三个字段,分别对应SKU表中的选项值 + +## 字段类型说明 + +### ES字段类型映射 + +| ES字段类型 | Elasticsearch映射 | 用途 | +|-----------|------------------|------| +| KEYWORD | keyword | 精确匹配、过滤、聚合、排序 | +| TEXT | text | 全文检索(支持分词) | +| FLOAT | float | 浮点数(价格、权重等) | +| LONG | long | 整数(库存、计数等) | +| DATE | date | 日期时间 | +| TEXT_EMBEDDING | dense_vector | 文本向量(1024维) | +| JSON | object/nested | 嵌套对象 | + +### 分析器说明 + +| 分析器名称 | 语言 | 说明 | +|-----------|------|------| +| chinese_ecommerce | 中文 | Ansj中文分词器(电商优化),用于中文文本的分词和搜索 | + +## 索引配置 + +### 索引设置 + +- **分片数**: 1 +- **副本数**: 0 +- **刷新间隔**: 30秒 + +### 查询域(Query Domains) + +系统定义了多个查询域,用于在不同场景下搜索不同的字段组合: + +1. **default(默认索引)**: 搜索所有文本字段 + - 包含字段:title, brief, description, seo_title, seo_description, seo_keywords, vendor, product_type, tags, category + - Boost: 1.0 + +2. **title(标题索引)**: 仅搜索标题相关字段 + - 包含字段:title, seo_title + - Boost: 2.0 + +3. **vendor(品牌索引)**: 仅搜索品牌字段 + - 包含字段:vendor + - Boost: 1.5 + +4. **category(类目索引)**: 仅搜索类目字段 + - 包含字段:category + - Boost: 1.5 + +5. **tags(标签索引)**: 搜索标签和SEO关键词 + - 包含字段:tags, seo_keywords + - Boost: 1.0 + +## 数据转换规则 + +### 数据类型转换 + +1. **BIGINT → KEYWORD**: 数字ID转换为字符串(如 `product_id`, `variant_id`) +2. **DECIMAL → FLOAT**: 价格字段从DECIMAL转换为FLOAT +3. **INT → LONG**: 库存数量从INT转换为LONG +4. **DATETIME → DATE**: 时间字段转换为ISO格式字符串 + +### 特殊处理 + +1. **价格聚合**: 从多个SKU的价格中计算min_price、max_price、compare_at_price +2. **图片URL处理**: 如果image_src不是完整URL,会自动添加协议前缀 +3. **选项合并**: 将SKU表的option1、option2、option3合并为options对象 + +## 注意事项 + +1. **多租户隔离**: 所有查询必须包含 `tenant_id` 过滤条件 +2. **嵌套查询**: 查询variants字段时需要使用nested查询语法 +3. **字段命名**: 用于过滤的字段应使用 `*_keyword` 后缀的字段 +4. **向量搜索**: title_embedding字段用于语义搜索,需要配合文本查询使用 +5. **Boost权重**: 不同字段的boost权重影响搜索结果的相关性排序 + +## 数据来源表结构 + +### SPU表(shoplazza_product_spu) + +主要字段: +- `id`: BIGINT - 主键ID +- `tenant_id`: BIGINT - 租户ID +- `handle`: VARCHAR(255) - URL handle +- `title`: VARCHAR(512) - 商品标题 +- `brief`: VARCHAR(512) - 商品简介 +- `description`: TEXT - 商品描述 +- `vendor`: VARCHAR(255) - 供应商/品牌 +- `category`: VARCHAR(255) - 类目 +- `tags`: VARCHAR(1024) - 标签 +- `seo_title`: VARCHAR(512) - SEO标题 +- `seo_description`: TEXT - SEO描述 +- `seo_keywords`: VARCHAR(1024) - SEO关键词 +- `image_src`: VARCHAR(500) - 图片URL +- `create_time`: DATETIME - 创建时间 +- `update_time`: DATETIME - 更新时间 +- `shoplazza_created_at`: DATETIME - 店匠创建时间 +- `shoplazza_updated_at`: DATETIME - 店匠更新时间 + +### SKU表(shoplazza_product_sku) + +主要字段: +- `id`: BIGINT - 主键ID(对应variant_id) +- `spu_id`: BIGINT - SPU ID(关联字段) +- `title`: VARCHAR(500) - 变体标题 +- `price`: DECIMAL(10,2) - 价格 +- `compare_at_price`: DECIMAL(10,2) - 原价 +- `sku`: VARCHAR(100) - SKU编码 +- `inventory_quantity`: INT(11) - 库存数量 +- `option1`: VARCHAR(255) - 选项1 +- `option2`: VARCHAR(255) - 选项2 +- `option3`: VARCHAR(255) - 选项3 + diff --git a/config/config.yaml b/config/config.yaml index e281804..5c3de46 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -169,7 +169,7 @@ fields: return_in_source: true # 文本嵌入字段(用于语义搜索) - - name: "name_embedding" + - name: "title_embedding" type: "TEXT_EMBEDDING" embedding_dims: 1024 embedding_similarity: "dot_product" @@ -296,7 +296,7 @@ query_config: enable_query_rewrite: true # Embedding field names (if not set, will auto-detect from fields) - text_embedding_field: "name_embedding" # Field name for text embeddings + text_embedding_field: "title_embedding" # Field name for text embeddings image_embedding_field: null # Field name for image embeddings (if not set, will auto-detect) # Translation API (DeepL) diff --git a/config/config_loader.py b/config/config_loader.py index e2be8e2..3cfee9e 100644 --- a/config/config_loader.py +++ b/config/config_loader.py @@ -55,7 +55,7 @@ class QueryConfig: translation_context: str = "e-commerce product search" # Context hint for translation # Embedding field names - if not set, will auto-detect from fields - text_embedding_field: Optional[str] = None # Field name for text embeddings (e.g., "name_embedding") + text_embedding_field: Optional[str] = None # Field name for text embeddings (e.g., "title_embedding") image_embedding_field: Optional[str] = None # Field name for image embeddings (e.g., "image_embedding") # ES source fields configuration - fields to return in search results -- libgit2 0.21.2