Commit 0342d897a243f63462f74765362a2e008709d5ff
1 parent
41f0b2e9
搜索API对接指南 拆分
Showing
10 changed files
with
2513 additions
and
0 deletions
Show diff stats
| ... | ... | @@ -0,0 +1,110 @@ |
| 1 | +# 搜索API对接指南-00-总览与快速开始 | |
| 2 | + | |
| 3 | +本文档旨在为搜索服务的使用方提供完整的API对接指南,包括接口说明、请求参数、响应格式和使用示例。 | |
| 4 | +拆分目录: | |
| 5 | +- `-01-搜索接口(POST /search/ 与响应)` | |
| 6 | +- `-02-搜索建议与即时搜索` | |
| 7 | +- `-03-获取文档(GET /search/{doc_id})` | |
| 8 | +- `-05-索引接口(Indexer)` | |
| 9 | +- `-06-管理接口(Admin)` | |
| 10 | +- `-07-微服务接口(Embedding/Reranker/Translation)` | |
| 11 | +- `-08-数据模型与字段速查` | |
| 12 | +- `-10-接口级压测脚本` | |
| 13 | + | |
| 14 | +## 快速开始 | |
| 15 | + | |
| 16 | +### 1.1 基础信息 | |
| 17 | + | |
| 18 | +- **Base URL**: `http://43.166.252.75:6002` | |
| 19 | +- **协议**: HTTP/HTTPS | |
| 20 | +- **数据格式**: JSON | |
| 21 | +- **字符编码**: UTF-8 | |
| 22 | +- **请求方法**: POST(搜索接口) | |
| 23 | + | |
| 24 | +**重要提示**: `tenant_id` 通过 HTTP Header `X-Tenant-ID` 传递,不在请求体中。 | |
| 25 | + | |
| 26 | +**环境与凭证**:MySQL、Redis、Elasticsearch 等外部服务的 AI 生产地址与凭证见 [QUICKSTART.md §1.6](./QUICKSTART.md#16-外部服务与-env含生产凭证)。 | |
| 27 | + | |
| 28 | +### 1.2 最简单的搜索请求 | |
| 29 | + | |
| 30 | +```bash | |
| 31 | +curl -X POST "http://43.166.252.75:6002/search/" \ | |
| 32 | + -H "Content-Type: application/json" \ | |
| 33 | + -H "X-Tenant-ID: 162" \ | |
| 34 | + -d '{"query": "芭比娃娃"}' | |
| 35 | +``` | |
| 36 | + | |
| 37 | +### 1.3 带过滤与分页的搜索 | |
| 38 | + | |
| 39 | +```bash | |
| 40 | +curl -X POST "http://43.166.252.75:6002/search/" \ | |
| 41 | + -H "Content-Type: application/json" \ | |
| 42 | + -H "X-Tenant-ID: 162" \ | |
| 43 | + -d '{ | |
| 44 | + "query": "芭比娃娃", | |
| 45 | + "size": 5, | |
| 46 | + "from": 10, | |
| 47 | + "range_filters": { | |
| 48 | + "min_price": { | |
| 49 | + "gte": 50, | |
| 50 | + "lte": 200 | |
| 51 | + }, | |
| 52 | + "create_time": { | |
| 53 | + "gte": "2020-01-01T00:00:00Z" | |
| 54 | + } | |
| 55 | + }, | |
| 56 | + "sort_by": "price", | |
| 57 | + "sort_order": "asc" | |
| 58 | + }' | |
| 59 | +``` | |
| 60 | + | |
| 61 | +### 1.4 开启分面的搜索 | |
| 62 | + | |
| 63 | +```bash | |
| 64 | +curl -X POST "http://43.166.252.75:6002/search/" \ | |
| 65 | + -H "Content-Type: application/json" \ | |
| 66 | + -H "X-Tenant-ID: 162" \ | |
| 67 | + -d '{ | |
| 68 | + "query": "芭比娃娃", | |
| 69 | + "facets": [ | |
| 70 | + {"field": "category1_name", "size": 10, "type": "terms"}, | |
| 71 | + {"field": "specifications.color", "size": 10, "type": "terms"}, | |
| 72 | + {"field": "specifications.size", "size": 10, "type": "terms"} | |
| 73 | + ], | |
| 74 | + "min_score": 0.2 | |
| 75 | + }' | |
| 76 | +``` | |
| 77 | + | |
| 78 | +--- | |
| 79 | + | |
| 80 | +## 接口概览 | |
| 81 | + | |
| 82 | +| 接口 | HTTP Method | Endpoint | 说明 | | |
| 83 | +|------|------|------|------| | |
| 84 | +| 搜索 | POST | `/search/` | 执行搜索查询 | | |
| 85 | +| 搜索建议 | GET | `/search/suggestions` | 搜索建议(自动补全/热词,多语言) | | |
| 86 | +| 即时搜索 | GET | `/search/instant` | 即时搜索预留接口(当前返回 `501 Not Implemented`) | | |
| 87 | +| 获取文档 | GET | `/search/{doc_id}` | 获取单个文档 | | |
| 88 | +| 全量索引 | POST | `/indexer/reindex` | 全量索引接口(导入数据,不删除索引,仅推荐自测使用) | | |
| 89 | +| 增量索引 | POST | `/indexer/index` | 增量索引接口(指定SPU ID列表进行索引,支持自动检测删除和显式删除,仅推荐自测使用) | | |
| 90 | +| 查询文档 | POST | `/indexer/documents` | 查询SPU文档数据(不写入ES) | | |
| 91 | +| 构建ES文档(正式对接) | POST | `/indexer/build-docs` | 基于上游提供的 MySQL 行数据构建 ES doc,不写入 ES,供 Java 等调用后自行写入 | | |
| 92 | +| 构建ES文档(测试用) | POST | `/indexer/build-docs-from-db` | 仅在测试/调试时使用,根据 `tenant_id + spu_ids` 内部查库并构建 ES doc | | |
| 93 | +| 内容理解字段生成 | POST | `/indexer/enrich-content` | 根据商品标题批量生成 qanchors、semantic_attributes、tags,供微服务组合方式使用 | | |
| 94 | +| 索引健康检查 | GET | `/indexer/health` | 检查索引服务状态 | | |
| 95 | +| 健康检查 | GET | `/admin/health` | 服务健康检查 | | |
| 96 | +| 获取配置 | GET | `/admin/config` | 获取租户配置 | | |
| 97 | +| 索引统计 | GET | `/admin/stats` | 获取租户索引统计信息(需 tenant_id) | | |
| 98 | + | |
| 99 | +**微服务(独立端口或 Indexer 内,外部可直连)**: | |
| 100 | + | |
| 101 | +| 服务 | 端口 | 接口 | 说明 | | |
| 102 | +|------|------|------|------| | |
| 103 | +| 向量服务(文本) | 6005 | `POST /embed/text` | 文本向量化 | | |
| 104 | +| 向量服务(图片) | 6008 | `POST /embed/image` | 图片向量化 | | |
| 105 | +| 翻译服务 | 6006 | `POST /translate` | 文本翻译(支持 qwen-mt / llm / deepl / 本地模型) | | |
| 106 | +| 重排服务 | 6007 | `POST /rerank` | 检索结果重排 | | |
| 107 | +| 内容理解(Indexer 内) | 6004 | `POST /indexer/enrich-content` | 根据商品标题生成 qanchors、tags 等,供 indexer 微服务组合方式使用 | | |
| 108 | + | |
| 109 | +--- | |
| 110 | + | ... | ... |
| ... | ... | @@ -0,0 +1,903 @@ |
| 1 | +# 搜索API对接指南-01-搜索接口(POST /search/ 与响应) | |
| 2 | + | |
| 3 | +本篇以 `POST /search/` 为主线,包含: | |
| 4 | +- 请求参数:`3.2`、过滤器:`3.3`、分面:`3.4`、SKU筛选维度:`3.5` | |
| 5 | +- 响应格式:第 `4` 章(4.1~4.5) | |
| 6 | +- 常见场景示例:第 `8` 章(示例整体并入本篇,避免散落) | |
| 7 | + | |
| 8 | +## 搜索接口 | |
| 9 | + | |
| 10 | +### 3.1 接口信息 | |
| 11 | + | |
| 12 | +- **端点**: `POST /search/` | |
| 13 | +- **描述**: 执行文本搜索查询,支持多语言、过滤器和分面搜索 | |
| 14 | +- **租户标识**:`tenant_id` 通过 HTTP 请求头 **`X-Tenant-ID`** 传递(推荐);也可通过 URL query 参数 **`tenant_id`** 传递。**不要放在请求体中。** | |
| 15 | + | |
| 16 | +**请求示例(推荐)**: | |
| 17 | + | |
| 18 | +```python | |
| 19 | +url = f"{base_url.rstrip('/')}/search/" | |
| 20 | +headers = { | |
| 21 | + "Content-Type": "application/json", | |
| 22 | + "X-Tenant-ID": "162", # 租户ID,必填 | |
| 23 | +} | |
| 24 | +response = requests.post(url, headers=headers, json={"query": "芭比娃娃"}) | |
| 25 | +``` | |
| 26 | + | |
| 27 | +### 3.2 请求参数 | |
| 28 | + | |
| 29 | +#### 完整请求体结构 | |
| 30 | + | |
| 31 | +```json | |
| 32 | +{ | |
| 33 | + "query": "string (required)", | |
| 34 | + "size": 10, | |
| 35 | + "from": 0, | |
| 36 | + "language": "zh", | |
| 37 | + "filters": {}, | |
| 38 | + "range_filters": {}, | |
| 39 | + "facets": [], | |
| 40 | + "sort_by": "string", | |
| 41 | + "sort_order": "desc", | |
| 42 | + "min_score": 0.0, | |
| 43 | + "sku_filter_dimension": ["string"], | |
| 44 | + "debug": false, | |
| 45 | + "enable_rerank": null, | |
| 46 | + "rerank_query_template": "{query}", | |
| 47 | + "rerank_doc_template": "{title}", | |
| 48 | + "user_id": "string", | |
| 49 | + "session_id": "string" | |
| 50 | +} | |
| 51 | +``` | |
| 52 | + | |
| 53 | +#### 参数详细说明 | |
| 54 | + | |
| 55 | +| 参数 | 类型 | 必填 | 默认值 | 说明 | | |
| 56 | +|------|------|------|--------|------| | |
| 57 | +| `query` | string | Y | - | 搜索查询字符串(统一文本检索策略) | | |
| 58 | +| `size` | integer | N | 10 | 返回结果数量(1-100) | | |
| 59 | +| `from` | integer | N | 0 | 分页偏移量(用于分页) | | |
| 60 | +| `language` | string | N | "zh" | 返回语言:`zh`(中文)或 `en`(英文)。后端会根据此参数选择对应的中英文字段返回 | | |
| 61 | +| `filters` | object | N | null | 精确匹配过滤器(见[过滤器详解](#33-过滤器详解)) | | |
| 62 | +| `range_filters` | object | N | null | 数值范围过滤器(见[过滤器详解](#33-过滤器详解)) | | |
| 63 | +| `facets` | array | N | null | 分面配置(见[分面配置](#34-分面配置)) | | |
| 64 | +| `sort_by` | string | N | null | 排序字段名。支持:`price`(价格)、`sales`(销量)、`create_time`(创建时间)、`update_time`(更新时间)。默认按相关性排序 | | |
| 65 | +| `sort_order` | string | N | "desc" | 排序方向:`asc`(升序)或 `desc`(降序)。注意:`price`+`asc`=价格从低到高,`price`+`desc`=价格从高到低(后端自动映射为min_price或max_price) | | |
| 66 | +| `min_score` | float | N | null | 最小相关性分数阈值 | | |
| 67 | +| `sku_filter_dimension` | array[string] | N | null | 子SKU筛选维度列表(见[SKU筛选维度](#35-sku筛选维度)) | | |
| 68 | +| `debug` | boolean | N | false | 是否返回调试信息 | | |
| 69 | +| `enable_rerank` | boolean/null | N | null | 是否开启重排(调用外部重排服务对 ES 结果进行二次排序)。不传/传 null 使用服务端 `rerank.enabled`(默认开启)。开启后会先对 ES TopN(`rerank_window`)重排,再按分页截取;若 `from+size>1000`,则不重排,直接按分页从 ES 返回 | | |
| 70 | +| `rerank_query_template` | string | N | null | 重排 query 模板(可选)。支持 `{query}` 占位符;不传则使用服务端配置 | | |
| 71 | +| `rerank_doc_template` | string | N | null | 重排 doc 模板(可选)。支持 `{title} {brief} {vendor} {description} {category_path}`;不传则使用服务端配置 | | |
| 72 | +| `user_id` | string | N | null | 用户ID(用于个性化,预留) | | |
| 73 | +| `session_id` | string | N | null | 会话ID(用于分析,预留) | | |
| 74 | + | |
| 75 | +### 3.3 过滤器详解 | |
| 76 | + | |
| 77 | +#### 3.3.1 精确匹配过滤器 (filters) | |
| 78 | + | |
| 79 | +用于精确匹配或多值匹配。对于普通字段,数组表示 OR 逻辑(匹配任意一个值);对于 specifications 字段,按维度分组处理。**任意字段名加 `_all` 后缀**表示多值 AND 逻辑(必须同时匹配所有值)。 | |
| 80 | + | |
| 81 | +**格式**: | |
| 82 | + | |
| 83 | +```json | |
| 84 | +{ | |
| 85 | + "filters": { | |
| 86 | + "category_name": "手机", // 可以为单值 或者 数组 匹配数组中任意一个(OR) | |
| 87 | + "category1_name": "服装", // 可以为单值 或者 数组 匹配数组中任意一个(OR) | |
| 88 | + "category2_name": "男装", // 可以为单值 或者 数组 匹配数组中任意一个(OR) | |
| 89 | + "category3_name": "衬衫", // 可以为单值 或者 数组 匹配数组中任意一个(OR) | |
| 90 | + "vendor.zh.keyword": ["奇乐", "品牌A"], // 可以为单值 或者 数组 匹配数组中任意一个(OR) | |
| 91 | + "tags": "手机", // 可以为单值 或者 数组 匹配数组中任意一个(OR) | |
| 92 | + "tags_all": ["手机", "促销", "新品"], // *_all:多值为 AND,必须同时包含所有标签 | |
| 93 | + "category1_name_all": ["服装", "男装"], // 同上,适用于任意可过滤字段 | |
| 94 | + // specifications 嵌套过滤(特殊格式) | |
| 95 | + "specifications": { | |
| 96 | + "name": "color", | |
| 97 | + "value": "white" | |
| 98 | + } | |
| 99 | + } | |
| 100 | +} | |
| 101 | +``` | |
| 102 | + | |
| 103 | +**支持的值类型**: | |
| 104 | +- 字符串:精确匹配 | |
| 105 | +- 整数:精确匹配 | |
| 106 | +- 布尔值:精确匹配 | |
| 107 | +- 数组:匹配任意值(OR 逻辑);若字段名以 `_all` 结尾,则数组表示 AND 逻辑(必须同时匹配所有值) | |
| 108 | +- 对象:specifications 嵌套过滤(见下文) | |
| 109 | + | |
| 110 | +**`*_all` 语义(多值 AND)**: | |
| 111 | +- 任意过滤字段均可使用 `_all` 后缀,对应 ES 字段名为去掉 `_all` 后的名称。 | |
| 112 | +- 例如:`tags_all: ["A", "B"]` 表示文档的 `tags` 必须**同时包含** A 和 B;`vendor.zh.keyword_all: ["奇乐", "品牌A"]` 表示同时匹配两个品牌(通常用于 keyword 多值场景)。 | |
| 113 | +- `specifications_all`:传列表 `[{"name":"color","value":"white"},{"name":"size","value":"256GB"}]` 时,表示所有列出的规格条件都要满足(与 `specifications` 多维度时的 AND 一致;若同维度多值则要求文档同时满足多个值,一般用于嵌套多值场景)。 | |
| 114 | + | |
| 115 | +**Specifications 嵌套过滤**: | |
| 116 | + | |
| 117 | +`specifications` 是嵌套字段,支持按规格名称和值进行过滤。 | |
| 118 | + | |
| 119 | +**单个规格过滤**: | |
| 120 | + | |
| 121 | +```json | |
| 122 | +{ | |
| 123 | + "filters": { | |
| 124 | + "specifications": { | |
| 125 | + "name": "color", | |
| 126 | + "value": "white" | |
| 127 | + } | |
| 128 | + } | |
| 129 | +} | |
| 130 | +``` | |
| 131 | + | |
| 132 | +查询规格名称为"color"且值为"white"的商品。 | |
| 133 | + | |
| 134 | +**多个规格过滤(按维度分组)**: | |
| 135 | + | |
| 136 | +```json | |
| 137 | +{ | |
| 138 | + "filters": { | |
| 139 | + "specifications": [ | |
| 140 | + {"name": "color", "value": "white"}, | |
| 141 | + {"name": "size", "value": "256GB"} | |
| 142 | + ] | |
| 143 | + } | |
| 144 | +} | |
| 145 | +``` | |
| 146 | + | |
| 147 | +查询同时满足所有规格的商品(color=white **且** size=256GB)。 | |
| 148 | + | |
| 149 | +**相同维度的多个值(OR 逻辑)**: | |
| 150 | + | |
| 151 | +```json | |
| 152 | +{ | |
| 153 | + "filters": { | |
| 154 | + "specifications": [ | |
| 155 | + {"name": "size", "value": "3"}, | |
| 156 | + {"name": "size", "value": "4"}, | |
| 157 | + {"name": "size", "value": "5"}, | |
| 158 | + {"name": "color", "value": "green"} | |
| 159 | + ] | |
| 160 | + } | |
| 161 | +} | |
| 162 | +``` | |
| 163 | + | |
| 164 | +查询满足 (size=3 **或** size=4 **或** size=5) **且** color=green 的商品。 | |
| 165 | + | |
| 166 | +**过滤逻辑说明**: | |
| 167 | +- **不同维度**(不同的 `name`)之间是 **AND** 关系(求交集) | |
| 168 | +- **相同维度**(相同的 `name`)的多个值之间是 **OR** 关系(求并集) | |
| 169 | + | |
| 170 | +**常用过滤字段**(详见[常用字段列表](./搜索API对接指南-08-数据模型与字段速查.md#93-常用字段列表)): | |
| 171 | +- `category_name`: 类目名称 | |
| 172 | +- `category1_name`, `category2_name`, `category3_name`: 多级类目 | |
| 173 | +- `category_id`: 类目ID | |
| 174 | +- `vendor.zh.keyword`, `vendor.en.keyword`: 供应商/品牌(使用keyword子字段) | |
| 175 | +- `tags`: 标签(keyword类型,支持数组) | |
| 176 | +- `option1_name`, `option2_name`, `option3_name`: 选项名称 | |
| 177 | +- `specifications`: 规格过滤(嵌套字段,格式见上文) | |
| 178 | +- 以上任意字段均可加 `_all` 后缀表示多值 AND,如 `tags_all`、`category1_name_all`。 | |
| 179 | + | |
| 180 | +#### 3.3.2 范围过滤器 (range_filters) | |
| 181 | + | |
| 182 | +用于数值字段的范围过滤。 | |
| 183 | + | |
| 184 | +**格式**: | |
| 185 | + | |
| 186 | +```json | |
| 187 | +{ | |
| 188 | + "range_filters": { | |
| 189 | + "min_price": { | |
| 190 | + "gte": 50, // 大于等于 | |
| 191 | + "lte": 200 // 小于等于 | |
| 192 | + }, | |
| 193 | + "max_price": { | |
| 194 | + "gt": 100 // 大于 | |
| 195 | + }, | |
| 196 | + "create_time": { | |
| 197 | + "gte": "2024-01-01T00:00:00Z" // 日期时间字符串 | |
| 198 | + } | |
| 199 | + } | |
| 200 | +} | |
| 201 | +``` | |
| 202 | + | |
| 203 | +**支持的操作符**: | |
| 204 | +- `gte`: 大于等于 (>=) | |
| 205 | +- `gt`: 大于 (>) | |
| 206 | +- `lte`: 小于等于 (<=) | |
| 207 | +- `lt`: 小于 (<) | |
| 208 | + | |
| 209 | +**注意**: 至少需要指定一个操作符。 | |
| 210 | + | |
| 211 | +**常用范围字段**(详见[常用字段列表](./搜索API对接指南-08-数据模型与字段速查.md#93-常用字段列表)): | |
| 212 | +- `min_price`: 最低价格 | |
| 213 | +- `max_price`: 最高价格 | |
| 214 | +- `compare_at_price`: 原价 | |
| 215 | +- `create_time`: 创建时间 | |
| 216 | +- `update_time`: 更新时间 | |
| 217 | + | |
| 218 | +### 3.4 分面配置 | |
| 219 | + | |
| 220 | +用于生成分面统计(分组聚合),常用于构建筛选器UI。 | |
| 221 | + | |
| 222 | +#### 3.4.1 配置格式 | |
| 223 | + | |
| 224 | +```json | |
| 225 | +{ | |
| 226 | + "facets": [ | |
| 227 | + { | |
| 228 | + "field": "category1_name", | |
| 229 | + "size": 15, | |
| 230 | + "type": "terms", | |
| 231 | + "disjunctive": false | |
| 232 | + }, | |
| 233 | + { | |
| 234 | + "field": "brand_name", | |
| 235 | + "size": 10, | |
| 236 | + "type": "terms", | |
| 237 | + "disjunctive": true | |
| 238 | + }, | |
| 239 | + { | |
| 240 | + "field": "specifications.color", | |
| 241 | + "size": 20, | |
| 242 | + "type": "terms", | |
| 243 | + "disjunctive": true | |
| 244 | + }, | |
| 245 | + { | |
| 246 | + "field": "min_price", | |
| 247 | + "type": "range", | |
| 248 | + "ranges": [ | |
| 249 | + {"key": "0-50", "to": 50}, | |
| 250 | + {"key": "50-100", "from": 50, "to": 100}, | |
| 251 | + {"key": "100-200", "from": 100, "to": 200}, | |
| 252 | + {"key": "200+", "from": 200} | |
| 253 | + ] | |
| 254 | + } | |
| 255 | + ] | |
| 256 | +} | |
| 257 | +``` | |
| 258 | + | |
| 259 | +#### 3.4.2 Facet 字段说明 | |
| 260 | + | |
| 261 | +| 字段 | 类型 | 必填 | 默认值 | 说明 | | |
| 262 | +|------|------|------|--------|------| | |
| 263 | +| `field` | string | 是 | - | 分面字段名 | | |
| 264 | +| `size` | int | 否 | 10 | 返回的分面值数量(1-100) | | |
| 265 | +| `type` | string | 否 | "terms" | 分面类型:`terms`(词条聚合)或 `range`(范围聚合) | | |
| 266 | +| `disjunctive` | bool | 否 | false | 是否支持多选(disjunctive faceting)。启用后,选中该分面的过滤器时,仍会显示其他可选项 | | |
| 267 | +| `ranges` | array | 否 | null | 范围配置(仅 `type="range"` 时需要) | | |
| 268 | + | |
| 269 | +#### 3.4.3 disjunctive字段说明 | |
| 270 | + | |
| 271 | +**重要特性**: `disjunctive` 字段控制分面的行为模式。启用后,选中该分面的过滤器时,仍会显示其他可选项 | |
| 272 | + | |
| 273 | +**标准模式 (disjunctive: false)**: | |
| 274 | +- **行为**: 选中某个分面值后,该分面只显示选中的值 | |
| 275 | +- **适用场景**: 层级类目、互斥选择 | |
| 276 | +- **示例**: 类目下钻(玩具 > 娃娃 > 芭比) | |
| 277 | + | |
| 278 | +**Multi-Select 模式 (disjunctive: true)** ⭐: | |
| 279 | +- **行为**: 选中某个分面值后,该分面仍显示所有可选项 | |
| 280 | +- **适用场景**: 颜色、品牌、尺码等可切换属性 | |
| 281 | +- **示例**: 选择了"红色"后,仍能看到"蓝色"、"绿色"等选项 | |
| 282 | + | |
| 283 | +**推荐配置**: | |
| 284 | + | |
| 285 | +| 分面类型 | disjunctive | 原因 | | |
| 286 | +|---------|-------------|------| | |
| 287 | +| 颜色 | `true` | 用户需要切换颜色 | | |
| 288 | +| 品牌 | `true` | 用户需要比较品牌 | | |
| 289 | +| 尺码 | `true` | 用户需要查看其他尺码 | | |
| 290 | +| 类目 | `false` | 层级下钻 | | |
| 291 | +| 价格区间 | `false` | 互斥选择 | | |
| 292 | + | |
| 293 | +#### 3.4.4 规格分面说明 | |
| 294 | + | |
| 295 | +`specifications` 是嵌套字段,支持两种分面模式: | |
| 296 | + | |
| 297 | +**模式1:所有规格名称的分面**: | |
| 298 | + | |
| 299 | +```json | |
| 300 | +{ | |
| 301 | + "facets": [ | |
| 302 | + { | |
| 303 | + "field": "specifications", | |
| 304 | + "size": 10, | |
| 305 | + "type": "terms" | |
| 306 | + } | |
| 307 | + ] | |
| 308 | +} | |
| 309 | +``` | |
| 310 | + | |
| 311 | +返回所有规格名称(name)及其对应的值(value)列表。每个 name 会生成一个独立的分面结果。 | |
| 312 | + | |
| 313 | +**模式2:指定规格名称的分面**: | |
| 314 | + | |
| 315 | +```json | |
| 316 | +{ | |
| 317 | + "facets": [ | |
| 318 | + { | |
| 319 | + "field": "specifications.color", | |
| 320 | + "size": 20, | |
| 321 | + "type": "terms", | |
| 322 | + "disjunctive": true | |
| 323 | + }, | |
| 324 | + { | |
| 325 | + "field": "specifications.size", | |
| 326 | + "size": 15, | |
| 327 | + "type": "terms", | |
| 328 | + "disjunctive": true | |
| 329 | + } | |
| 330 | + ] | |
| 331 | +} | |
| 332 | +``` | |
| 333 | + | |
| 334 | +只返回指定规格名称的值列表。格式:`specifications.{name}`,其中 `{name}` 是规格名称(如"color"、"size"、"material")。 | |
| 335 | + | |
| 336 | +**返回格式示例**: | |
| 337 | + | |
| 338 | +```json | |
| 339 | +{ | |
| 340 | + "facets": [ | |
| 341 | + { | |
| 342 | + "field": "specifications.color", | |
| 343 | + "label": "color", | |
| 344 | + "type": "terms", | |
| 345 | + "values": [ | |
| 346 | + {"value": "white", "count": 50, "selected": true}, // ✓ selected 字段由后端标记 | |
| 347 | + {"value": "black", "count": 30, "selected": false}, | |
| 348 | + {"value": "red", "count": 20, "selected": false} | |
| 349 | + ] | |
| 350 | + }, | |
| 351 | + { | |
| 352 | + "field": "specifications.size", | |
| 353 | + "label": "size", | |
| 354 | + "type": "terms", | |
| 355 | + "values": [ | |
| 356 | + {"value": "256GB", "count": 40, "selected": false}, | |
| 357 | + {"value": "512GB", "count": 20, "selected": false} | |
| 358 | + ] | |
| 359 | + } | |
| 360 | + ] | |
| 361 | +} | |
| 362 | +``` | |
| 363 | + | |
| 364 | +### 3.5 SKU筛选维度 | |
| 365 | + | |
| 366 | +**功能说明**: | |
| 367 | +`sku_filter_dimension` 用于控制搜索列表页中 **每个 SPU 下方可切换的子款式(子 SKU)维度**,为字符串列表。 | |
| 368 | +在店铺的 **主题装修配置** 中,商家可以为店铺设置一个或多个子款式筛选维度(例如 `color`、`size`),前端列表页会在每个 SPU 下展示这些维度对应的子 SKU 列表,用户可以通过点击不同维度值(如不同颜色)来切换展示的子款式。 | |
| 369 | +当指定 `sku_filter_dimension` 后,后端会根据店铺的这项配置,从所有 SKU 中筛选出这些维度组合对应的子 SKU 数据:系统会按指定维度**组合**对 SKU 进行分组,每个维度组合只返回第一个 SKU(从简实现,选择该组合下的第一款),其余不在这些维度组合中的子 SKU 将不返回。 | |
| 370 | + | |
| 371 | +**支持的维度值**: | |
| 372 | +1. **直接选项字段**: `option1`、`option2`、`option3` | |
| 373 | + - 直接使用对应的 `option1_value`、`option2_value`、`option3_value` 字段进行分组 | |
| 374 | + | |
| 375 | +2. **规格/选项名称**: 通过 `option1_name`、`option2_name`、`option3_name` 匹配 | |
| 376 | + - 例如:如果 `option1_name` 为 `"color"`,则可以使用 `sku_filter_dimension: ["color"]` 来按颜色分组 | |
| 377 | + | |
| 378 | +**示例**: | |
| 379 | + | |
| 380 | +**按颜色筛选(假设 option1_name = "color")**: | |
| 381 | + | |
| 382 | +```json | |
| 383 | +{ | |
| 384 | + "query": "芭比娃娃", | |
| 385 | + "sku_filter_dimension": ["color"] | |
| 386 | +} | |
| 387 | +``` | |
| 388 | + | |
| 389 | +**按选项1筛选**: | |
| 390 | + | |
| 391 | +```json | |
| 392 | +{ | |
| 393 | + "query": "芭比娃娃", | |
| 394 | + "sku_filter_dimension": ["option1"] | |
| 395 | +} | |
| 396 | +``` | |
| 397 | + | |
| 398 | +**按颜色 + 尺寸组合筛选(假设 option1_name = "color", option2_name = "size")**: | |
| 399 | + | |
| 400 | +```json | |
| 401 | +{ | |
| 402 | + "query": "芭比娃娃", | |
| 403 | + "sku_filter_dimension": ["color", "size"] | |
| 404 | +} | |
| 405 | +``` | |
| 406 | + | |
| 407 | +## 响应格式说明 | |
| 408 | + | |
| 409 | +### 4.1 标准响应结构 | |
| 410 | + | |
| 411 | +```json | |
| 412 | +{ | |
| 413 | + "results": [ | |
| 414 | + { | |
| 415 | + "spu_id": "12345", | |
| 416 | + "title": "芭比时尚娃娃", | |
| 417 | + "brief": "高品质芭比娃娃", | |
| 418 | + "description": "详细描述...", | |
| 419 | + "vendor": "美泰", | |
| 420 | + "category": "玩具", | |
| 421 | + "category_path": "玩具/娃娃/时尚", | |
| 422 | + "category_name": "时尚", | |
| 423 | + "category_id": "cat_001", | |
| 424 | + "category_level": 3, | |
| 425 | + "category1_name": "玩具", | |
| 426 | + "category2_name": "娃娃", | |
| 427 | + "category3_name": "时尚", | |
| 428 | + "tags": ["娃娃", "玩具", "女孩"], | |
| 429 | + "price": 89.99, | |
| 430 | + "compare_at_price": 129.99, | |
| 431 | + "currency": "USD", | |
| 432 | + "image_url": "https://example.com/image.jpg", | |
| 433 | + "in_stock": true, | |
| 434 | + "sku_prices": [89.99, 99.99, 109.99], | |
| 435 | + "sku_weights": [100, 150, 200], | |
| 436 | + "sku_weight_units": ["g", "g", "g"], | |
| 437 | + "total_inventory": 500, | |
| 438 | + "option1_name": "color", | |
| 439 | + "option2_name": "size", | |
| 440 | + "option3_name": null, | |
| 441 | + "specifications": [ | |
| 442 | + {"sku_id": "sku_001", "name": "color", "value": "pink"}, | |
| 443 | + {"sku_id": "sku_001", "name": "size", "value": "standard"} | |
| 444 | + ], | |
| 445 | + "skus": [ | |
| 446 | + { | |
| 447 | + "sku_id": "67890", | |
| 448 | + "price": 89.99, | |
| 449 | + "compare_at_price": 129.99, | |
| 450 | + "sku": "BARBIE-001", | |
| 451 | + "stock": 100, | |
| 452 | + "weight": 0.1, | |
| 453 | + "weight_unit": "kg", | |
| 454 | + "option1_value": "pink", | |
| 455 | + "option2_value": "standard", | |
| 456 | + "option3_value": null, | |
| 457 | + "image_src": "https://example.com/sku1.jpg" | |
| 458 | + } | |
| 459 | + ], | |
| 460 | + "relevance_score": 8.5 | |
| 461 | + } | |
| 462 | + ], | |
| 463 | + "total": 118, | |
| 464 | + "max_score": 8.5, | |
| 465 | + "facets": [ | |
| 466 | + { | |
| 467 | + "field": "category1_name", | |
| 468 | + "label": "category1_name", | |
| 469 | + "type": "terms", | |
| 470 | + "values": [ | |
| 471 | + { | |
| 472 | + "value": "玩具", | |
| 473 | + "label": "玩具", | |
| 474 | + "count": 85, | |
| 475 | + "selected": false | |
| 476 | + } | |
| 477 | + ] | |
| 478 | + }, | |
| 479 | + { | |
| 480 | + "field": "specifications.color", | |
| 481 | + "label": "color", | |
| 482 | + "type": "terms", | |
| 483 | + "values": [ | |
| 484 | + { | |
| 485 | + "value": "pink", | |
| 486 | + "label": "pink", | |
| 487 | + "count": 30, | |
| 488 | + "selected": false | |
| 489 | + } | |
| 490 | + ] | |
| 491 | + } | |
| 492 | + ], | |
| 493 | + "query_info": { | |
| 494 | + "original_query": "芭比娃娃", | |
| 495 | + "query_normalized": "芭比娃娃", | |
| 496 | + "rewritten_query": "芭比娃娃", | |
| 497 | + "detected_language": "zh", | |
| 498 | + "translations": { | |
| 499 | + "en": "barbie doll" | |
| 500 | + }, | |
| 501 | + "domain": "default" | |
| 502 | + }, | |
| 503 | + "suggestions": [], | |
| 504 | + "related_searches": [], | |
| 505 | + "took_ms": 45, | |
| 506 | + "performance_info": null, | |
| 507 | + "debug_info": null | |
| 508 | +} | |
| 509 | +``` | |
| 510 | + | |
| 511 | +### 4.2 响应字段说明 | |
| 512 | + | |
| 513 | +| 字段 | 类型 | 说明 | | |
| 514 | +|------|------|------| | |
| 515 | +| `results` | array | 搜索结果列表(SpuResult对象数组) | | |
| 516 | +| `results[].spu_id` | string | SPU ID | | |
| 517 | +| `results[].title` | string | 商品标题 | | |
| 518 | +| `results[].price` | float | 价格(min_price) | | |
| 519 | +| `results[].skus` | array | SKU列表(如果指定了`sku_filter_dimension`,则按维度过滤后的SKU) | | |
| 520 | +| `results[].relevance_score` | float | 相关性分数 | | |
| 521 | +| `total` | integer | 匹配的总文档数 | | |
| 522 | +| `max_score` | float | 最高相关性分数 | | |
| 523 | +| `facets` | array | 分面统计结果 | | |
| 524 | +| `query_info` | object | query处理信息 | | |
| 525 | +| `took_ms` | integer | 搜索耗时(毫秒) | | |
| 526 | +| `debug_info` | object/null | 调试信息,仅当请求传 `debug=true` 时返回 | | |
| 527 | + | |
| 528 | +#### 4.2.1 query_info 说明 | |
| 529 | + | |
| 530 | +`query_info` 包含本次搜索的查询解析与处理结果: | |
| 531 | + | |
| 532 | +| 子字段 | 类型 | 说明 | | |
| 533 | +|--------|------|------| | |
| 534 | +| `original_query` | string | 用户原始查询 | | |
| 535 | +| `query_normalized` | string | 归一化后的查询(去空白、大小写等预处理,用于后续解析与改写) | | |
| 536 | +| `rewritten_query` | string | 重写后的查询(同义词/词典扩展等) | | |
| 537 | +| `detected_language` | string | 检测到的查询语言(如 `zh`、`en`) | | |
| 538 | +| `translations` | object | 翻译结果,键为语言代码,值为翻译文本 | | |
| 539 | +| `domain` | string | 查询域(如 `default`、`title`、`brand` 等) | | |
| 540 | + | |
| 541 | +#### 4.2.2 debug_info 说明 | |
| 542 | + | |
| 543 | +`debug_info` 主要用于检索效果评估、融合打分分析与 bad case 排查。 | |
| 544 | + | |
| 545 | +`debug_info.query_analysis` 常见字段: | |
| 546 | + | |
| 547 | +| 子字段 | 类型 | 说明 | | |
| 548 | +|--------|------|------| | |
| 549 | +| `original_query` | string | 原始查询 | | |
| 550 | +| `query_normalized` | string | 归一化后的查询 | | |
| 551 | +| `rewritten_query` | string | 重写后的查询 | | |
| 552 | +| `detected_language` | string | 检测到的语言 | | |
| 553 | +| `translations` | object | 翻译结果 | | |
| 554 | +| `query_text_by_lang` | object | 实际参与检索的多语言 query 文本 | | |
| 555 | +| `search_langs` | array[string] | 实际参与检索的语言列表 | | |
| 556 | +| `supplemental_search_langs` | array[string] | 因 mixed query 补入的附加语言列表 | | |
| 557 | +| `has_vector` | boolean | 是否生成了向量 | | |
| 558 | + | |
| 559 | +`debug_info.per_result[]` 常见字段: | |
| 560 | + | |
| 561 | +| 子字段 | 类型 | 说明 | | |
| 562 | +|--------|------|------| | |
| 563 | +| `spu_id` | string | 结果 SPU ID | | |
| 564 | +| `es_score` | float | ES 原始 `_score` | | |
| 565 | +| `rerank_score` | float | 重排分数 | | |
| 566 | +| `text_score` | float | 文本相关性大分(由 `base_query` / `base_query_trans_*` / `fallback_original_query_*` 聚合而来) | | |
| 567 | +| `text_source_score` | float | `base_query` 分数 | | |
| 568 | +| `text_translation_score` | float | `base_query_trans_*` 里的最大分数 | | |
| 569 | +| `text_fallback_score` | float | `fallback_original_query_*` 里的最大分数 | | |
| 570 | +| `text_primary_score` | float | 文本大分中的主证据部分 | | |
| 571 | +| `text_support_score` | float | 文本大分中的辅助证据部分 | | |
| 572 | +| `knn_score` | float | `knn_query` 分数 | | |
| 573 | +| `fused_score` | float | 最终融合分数 | | |
| 574 | +| `matched_queries` | object/array | ES named queries 命中详情 | | |
| 575 | + | |
| 576 | +### 4.3 SpuResult字段说明 | |
| 577 | + | |
| 578 | +| 字段 | 类型 | 说明 | | |
| 579 | +|------|------|------| | |
| 580 | +| `spu_id` | string | SPU ID | | |
| 581 | +| `title` | string | 商品标题(根据language参数自动选择 `title.zh` 或 `title.en`) | | |
| 582 | +| `brief` | string | 商品短描述(根据language参数自动选择) | | |
| 583 | +| `description` | string | 商品详细描述(根据language参数自动选择) | | |
| 584 | +| `vendor` | string | 供应商/品牌(根据language参数自动选择) | | |
| 585 | +| `category` | string | 类目(兼容字段,等同于category_name) | | |
| 586 | +| `category_path` | string | 类目路径(多级,用于面包屑,根据language参数自动选择) | | |
| 587 | +| `category_name` | string | 类目名称(展示用,根据language参数自动选择) | | |
| 588 | +| `category_id` | string | 类目ID | | |
| 589 | +| `category_level` | integer | 类目层级(1/2/3) | | |
| 590 | +| `category1_name` | string | 一级类目名称 | | |
| 591 | +| `category2_name` | string | 二级类目名称 | | |
| 592 | +| `category3_name` | string | 三级类目名称 | | |
| 593 | +| `tags` | array[string] | 标签列表 | | |
| 594 | +| `price` | float | 价格(min_price) | | |
| 595 | +| `compare_at_price` | float | 原价 | | |
| 596 | +| `currency` | string | 货币单位(默认USD) | | |
| 597 | +| `image_url` | string | 主图URL | | |
| 598 | +| `in_stock` | boolean | 是否有库存(任意SKU有库存即为true) | | |
| 599 | +| `sku_prices` | array[float] | 所有SKU价格列表 | | |
| 600 | +| `sku_weights` | array[integer] | 所有SKU重量列表 | | |
| 601 | +| `sku_weight_units` | array[string] | 所有SKU重量单位列表 | | |
| 602 | +| `total_inventory` | integer | 总库存 | | |
| 603 | +| `sales` | integer | 销量(展示销量) | | |
| 604 | +| `option1_name` | string | 选项1名称(如"color") | | |
| 605 | +| `option2_name` | string | 选项2名称(如"size") | | |
| 606 | +| `option3_name` | string | 选项3名称 | | |
| 607 | +| `specifications` | array[object] | 规格列表(与ES specifications字段对应) | | |
| 608 | +| `skus` | array | SKU 列表 | | |
| 609 | +| `relevance_score` | float | 相关性分数(默认为 ES 原始分数;当开启 AI 搜索时为融合后的最终分数) | | |
| 610 | + | |
| 611 | +### 4.4 SkuResult字段说明 | |
| 612 | + | |
| 613 | +| 字段 | 类型 | 说明 | | |
| 614 | +|------|------|------| | |
| 615 | +| `sku_id` | string | SKU ID | | |
| 616 | +| `price` | float | 价格 | | |
| 617 | +| `compare_at_price` | float | 原价 | | |
| 618 | +| `sku` | string | SKU编码(sku_code) | | |
| 619 | +| `stock` | integer | 库存数量 | | |
| 620 | +| `weight` | float | 重量 | | |
| 621 | +| `weight_unit` | string | 重量单位 | | |
| 622 | +| `option1_value` | string | 选项1取值(如color值) | | |
| 623 | +| `option2_value` | string | 选项2取值(如size值) | | |
| 624 | +| `option3_value` | string | 选项3取值 | | |
| 625 | +| `image_src` | string | SKU图片地址 | | |
| 626 | + | |
| 627 | +### 4.5 多语言字段说明 | |
| 628 | + | |
| 629 | +- `title`, `brief`, `description`, `vendor`, `category_path`, `category_name` 会根据请求的 `language` 参数自动选择对应的中英文字段 | |
| 630 | +- `language="zh"`: 优先返回 `*_zh` 字段,如果为空则回退到 `*_en` 字段 | |
| 631 | +- `language="en"`: 优先返回 `*_en` 字段,如果为空则回退到 `*_zh` 字段 | |
| 632 | + | |
| 633 | +--- | |
| 634 | + | |
| 635 | +## 8. 常见场景示例 | |
| 636 | + | |
| 637 | +以下示例仅展示**请求体**(body);实际调用时请加上请求头 `X-Tenant-ID: <租户ID>`(或 URL 参数 `tenant_id`),参见 [3.1 接口信息](#31-接口信息)。 | |
| 638 | + | |
| 639 | +### 8.1 基础搜索与排序 | |
| 640 | + | |
| 641 | +**按价格从低到高排序**: | |
| 642 | + | |
| 643 | +```json | |
| 644 | +{ | |
| 645 | + "query": "玩具", | |
| 646 | + "size": 20, | |
| 647 | + "from": 0, | |
| 648 | + "sort_by": "price", | |
| 649 | + "sort_order": "asc" | |
| 650 | +} | |
| 651 | +``` | |
| 652 | + | |
| 653 | +**按价格从高到低排序**: | |
| 654 | + | |
| 655 | +```json | |
| 656 | +{ | |
| 657 | + "query": "玩具", | |
| 658 | + "size": 20, | |
| 659 | + "from": 0, | |
| 660 | + "sort_by": "price", | |
| 661 | + "sort_order": "desc" | |
| 662 | +} | |
| 663 | +``` | |
| 664 | + | |
| 665 | +**按销量从高到低排序**: | |
| 666 | + | |
| 667 | +```json | |
| 668 | +{ | |
| 669 | + "query": "玩具", | |
| 670 | + "size": 20, | |
| 671 | + "from": 0, | |
| 672 | + "sort_by": "sales", | |
| 673 | + "sort_order": "desc" | |
| 674 | +} | |
| 675 | +``` | |
| 676 | + | |
| 677 | +**按默认(相关性)排序**: | |
| 678 | + | |
| 679 | +```json | |
| 680 | +{ | |
| 681 | + "query": "玩具", | |
| 682 | + "size": 20, | |
| 683 | + "from": 0 | |
| 684 | +} | |
| 685 | +``` | |
| 686 | + | |
| 687 | +### 8.2 过滤搜索 | |
| 688 | + | |
| 689 | +**需求**: 搜索"玩具",筛选类目为"益智玩具",价格在50-200之间 | |
| 690 | + | |
| 691 | +```json | |
| 692 | +{ | |
| 693 | + "query": "玩具", | |
| 694 | + "size": 20, | |
| 695 | + "language": "zh", | |
| 696 | + "filters": { | |
| 697 | + "category_name": "益智玩具" | |
| 698 | + }, | |
| 699 | + "range_filters": { | |
| 700 | + "min_price": { | |
| 701 | + "gte": 50, | |
| 702 | + "lte": 200 | |
| 703 | + } | |
| 704 | + } | |
| 705 | +} | |
| 706 | +``` | |
| 707 | + | |
| 708 | +**需求**: 搜索"手机",筛选多个品牌,价格范围 | |
| 709 | + | |
| 710 | +```json | |
| 711 | +{ | |
| 712 | + "query": "手机", | |
| 713 | + "size": 20, | |
| 714 | + "language": "zh", | |
| 715 | + "filters": { | |
| 716 | + "vendor.zh.keyword": ["品牌A", "品牌B"] | |
| 717 | + }, | |
| 718 | + "range_filters": { | |
| 719 | + "min_price": { | |
| 720 | + "gte": 50, | |
| 721 | + "lte": 200 | |
| 722 | + } | |
| 723 | + } | |
| 724 | +} | |
| 725 | +``` | |
| 726 | + | |
| 727 | +### 8.3 分面搜索 | |
| 728 | + | |
| 729 | +**需求**: 搜索"玩具",获取类目和规格的分面统计,用于构建筛选器 | |
| 730 | + | |
| 731 | +```json | |
| 732 | +{ | |
| 733 | + "query": "玩具", | |
| 734 | + "size": 20, | |
| 735 | + "language": "zh", | |
| 736 | + "facets": [ | |
| 737 | + {"field": "category1_name", "size": 15, "type": "terms"}, | |
| 738 | + {"field": "category2_name", "size": 10, "type": "terms"}, | |
| 739 | + {"field": "specifications", "size": 10, "type": "terms"} | |
| 740 | + ] | |
| 741 | +} | |
| 742 | +``` | |
| 743 | + | |
| 744 | +**需求**: 搜索"手机",获取价格区间和规格的分面统计 | |
| 745 | + | |
| 746 | +```json | |
| 747 | +{ | |
| 748 | + "query": "手机", | |
| 749 | + "size": 20, | |
| 750 | + "language": "zh", | |
| 751 | + "facets": [ | |
| 752 | + { | |
| 753 | + "field": "min_price", | |
| 754 | + "type": "range", | |
| 755 | + "ranges": [ | |
| 756 | + {"key": "0-50", "to": 50}, | |
| 757 | + {"key": "50-100", "from": 50, "to": 100}, | |
| 758 | + {"key": "100-200", "from": 100, "to": 200}, | |
| 759 | + {"key": "200+", "from": 200} | |
| 760 | + ] | |
| 761 | + }, | |
| 762 | + { | |
| 763 | + "field": "specifications", | |
| 764 | + "size": 10, | |
| 765 | + "type": "terms" | |
| 766 | + } | |
| 767 | + ] | |
| 768 | +} | |
| 769 | +``` | |
| 770 | + | |
| 771 | +### 8.4 规格过滤与分面 | |
| 772 | + | |
| 773 | +**需求**: 搜索"手机",筛选color为"white"的商品 | |
| 774 | + | |
| 775 | +```json | |
| 776 | +{ | |
| 777 | + "query": "手机", | |
| 778 | + "size": 20, | |
| 779 | + "language": "zh", | |
| 780 | + "filters": { | |
| 781 | + "specifications": { | |
| 782 | + "name": "color", | |
| 783 | + "value": "white" | |
| 784 | + } | |
| 785 | + } | |
| 786 | +} | |
| 787 | +``` | |
| 788 | + | |
| 789 | +**需求**: 搜索"手机",筛选color为"white"且size为"256GB"的商品 | |
| 790 | + | |
| 791 | +```json | |
| 792 | +{ | |
| 793 | + "query": "手机", | |
| 794 | + "size": 20, | |
| 795 | + "language": "zh", | |
| 796 | + "filters": { | |
| 797 | + "specifications": [ | |
| 798 | + {"name": "color", "value": "white"}, | |
| 799 | + {"name": "size", "value": "256GB"} | |
| 800 | + ] | |
| 801 | + } | |
| 802 | +} | |
| 803 | +``` | |
| 804 | + | |
| 805 | +**需求**: 搜索"手机",筛选size为"3"、"4"或"5",且color为"green"的商品 | |
| 806 | + | |
| 807 | +```json | |
| 808 | +{ | |
| 809 | + "query": "手机", | |
| 810 | + "size": 20, | |
| 811 | + "language": "zh", | |
| 812 | + "filters": { | |
| 813 | + "specifications": [ | |
| 814 | + {"name": "size", "value": "3"}, | |
| 815 | + {"name": "size", "value": "4"}, | |
| 816 | + {"name": "size", "value": "5"}, | |
| 817 | + {"name": "color", "value": "green"} | |
| 818 | + ] | |
| 819 | + } | |
| 820 | +} | |
| 821 | +``` | |
| 822 | + | |
| 823 | +**需求**: 搜索"手机",获取所有规格的分面统计 | |
| 824 | + | |
| 825 | +```json | |
| 826 | +{ | |
| 827 | + "query": "手机", | |
| 828 | + "size": 20, | |
| 829 | + "language": "zh", | |
| 830 | + "facets": [ | |
| 831 | + {"field": "specifications", "size": 10, "type": "terms"} | |
| 832 | + ] | |
| 833 | +} | |
| 834 | +``` | |
| 835 | + | |
| 836 | +**需求**: 只获取"color"和"size"规格的分面统计 | |
| 837 | + | |
| 838 | +```json | |
| 839 | +{ | |
| 840 | + "query": "手机", | |
| 841 | + "size": 20, | |
| 842 | + "language": "zh", | |
| 843 | + "facets": [ | |
| 844 | + {"field": "specifications.color", "size": 20, "type": "terms"}, | |
| 845 | + {"field": "specifications.size", "size": 15, "type": "terms"} | |
| 846 | + ] | |
| 847 | +} | |
| 848 | +``` | |
| 849 | + | |
| 850 | +**需求**: 搜索"手机",筛选类目和规格,并获取对应的分面统计 | |
| 851 | + | |
| 852 | +```json | |
| 853 | +{ | |
| 854 | + "query": "手机", | |
| 855 | + "size": 20, | |
| 856 | + "language": "zh", | |
| 857 | + "filters": { | |
| 858 | + "category_name": "手机", | |
| 859 | + "specifications": { | |
| 860 | + "name": "color", | |
| 861 | + "value": "white" | |
| 862 | + } | |
| 863 | + }, | |
| 864 | + "facets": [ | |
| 865 | + {"field": "category1_name", "size": 15, "type": "terms"}, | |
| 866 | + {"field": "category2_name", "size": 10, "type": "terms"}, | |
| 867 | + {"field": "specifications.color", "size": 20, "type": "terms"}, | |
| 868 | + {"field": "specifications.size", "size": 15, "type": "terms"} | |
| 869 | + ] | |
| 870 | +} | |
| 871 | +``` | |
| 872 | + | |
| 873 | +### 8.5 SKU筛选 | |
| 874 | + | |
| 875 | +**需求**: 搜索"芭比娃娃",每个SPU下按颜色筛选,每种颜色只显示一个SKU | |
| 876 | + | |
| 877 | +```json | |
| 878 | +{ | |
| 879 | + "query": "芭比娃娃", | |
| 880 | + "size": 20, | |
| 881 | + "sku_filter_dimension": ["color"] | |
| 882 | +} | |
| 883 | +``` | |
| 884 | + | |
| 885 | +**说明**: | |
| 886 | +- 如果 `option1_name` 为 `"color"`,则使用 `sku_filter_dimension: ["color"]` 可以按颜色分组 | |
| 887 | +- 每个SPU下,每种颜色只会返回第一个SKU | |
| 888 | +- 如果维度不匹配,返回所有SKU(不进行过滤) | |
| 889 | + | |
| 890 | +### 8.6 分页查询 | |
| 891 | + | |
| 892 | +**需求**: 获取第2页结果(每页20条) | |
| 893 | + | |
| 894 | +```json | |
| 895 | +{ | |
| 896 | + "query": "手机", | |
| 897 | + "size": 20, | |
| 898 | + "from": 20 | |
| 899 | +} | |
| 900 | +``` | |
| 901 | + | |
| 902 | +--- | |
| 903 | + | ... | ... |
| ... | ... | @@ -0,0 +1,81 @@ |
| 1 | +# 搜索API对接指南-02-搜索建议与即时搜索 | |
| 2 | + | |
| 3 | +本篇面向前端联想词/搜索框团队,独立阅读 `GET /search/suggestions` 与 `GET /search/instant`。 | |
| 4 | + | |
| 5 | +## 搜索接口 | |
| 6 | + | |
| 7 | +### 3.7 搜索建议接口 | |
| 8 | + | |
| 9 | +- **端点**: `GET /search/suggestions` | |
| 10 | +- **描述**: 返回搜索建议(自动补全/热词),支持多语言。 | |
| 11 | + | |
| 12 | +#### 查询参数 | |
| 13 | + | |
| 14 | +| 参数 | 类型 | 必填 | 默认值 | 描述 | | |
| 15 | +|------|------|------|--------|------| | |
| 16 | +| `q` | string | Y | - | 查询字符串(至少 1 个字符) | | |
| 17 | +| `size` | integer | N | 10 | 返回建议数量(1-50) | | |
| 18 | +| `language` | string | N | `en` | 请求语言,如 `zh` / `en` / `ar` / `ru`,用于路由到对应语种 suggestion 索引 | | |
| 19 | +| `debug` | bool | N | `false` | 是否开启调试(目前主要用于排查 suggestion 排序与语言解析) | | |
| 20 | + | |
| 21 | +> **租户标识**:同 [-01-搜索接口](./搜索API对接指南-01-搜索接口.md#31-接口信息),通过请求头 `X-Tenant-ID` 或 query 参数 `tenant_id` 传递。 | |
| 22 | + | |
| 23 | +#### 响应示例 | |
| 24 | + | |
| 25 | +```json | |
| 26 | +{ | |
| 27 | + "query": "iph", | |
| 28 | + "language": "en", | |
| 29 | + "resolved_language": "en", | |
| 30 | + "suggestions": [ | |
| 31 | + { | |
| 32 | + "text": "iphone 15", | |
| 33 | + "lang": "en", | |
| 34 | + "score": 12.37, | |
| 35 | + "rank_score": 5.1, | |
| 36 | + "sources": ["query_log", "qanchor"], | |
| 37 | + "lang_source": "log_field", | |
| 38 | + "lang_confidence": 1.0, | |
| 39 | + "lang_conflict": false | |
| 40 | + } | |
| 41 | + ], | |
| 42 | + "took_ms": 12 | |
| 43 | +} | |
| 44 | +``` | |
| 45 | + | |
| 46 | +#### 请求示例 | |
| 47 | + | |
| 48 | +```bash | |
| 49 | +curl "http://localhost:6002/search/suggestions?q=芭&size=5&language=zh" \ | |
| 50 | + -H "X-Tenant-ID: 162" | |
| 51 | +``` | |
| 52 | + | |
| 53 | +### 3.8 即时搜索接口 | |
| 54 | + | |
| 55 | +> ⚠️ 当前版本未开放该能力。接口会明确返回 `501 Not Implemented`,避免误用未完成实现。 | |
| 56 | + | |
| 57 | +- **端点**: `GET /search/instant` | |
| 58 | +- **描述**: 即时搜索预留端点,后续会在独立实现完成后开放。 | |
| 59 | + | |
| 60 | +#### 查询参数 | |
| 61 | + | |
| 62 | +| 参数 | 类型 | 必填 | 默认值 | 描述 | | |
| 63 | +|------|------|------|--------|------| | |
| 64 | +| `q` | string | Y | - | 搜索查询(至少 2 个字符) | | |
| 65 | +| `size` | integer | N | 5 | 返回结果数量(1-20) | | |
| 66 | + | |
| 67 | +#### 请求示例 | |
| 68 | + | |
| 69 | +```bash | |
| 70 | +curl "http://localhost:6002/search/instant?q=玩具&size=5" | |
| 71 | +``` | |
| 72 | + | |
| 73 | +#### 当前响应 | |
| 74 | + | |
| 75 | +```json | |
| 76 | +{ | |
| 77 | + "error": "/search/instant is not implemented yet. Use POST /search/ for production traffic.", | |
| 78 | + "status_code": 501 | |
| 79 | +} | |
| 80 | +``` | |
| 81 | + | ... | ... |
| ... | ... | @@ -0,0 +1,40 @@ |
| 1 | +# 搜索API对接指南-03-获取文档(GET /search/{doc_id}) | |
| 2 | + | |
| 3 | +用于点击结果后的详情页回源,或排查某个文档在检索侧的字段情况。 | |
| 4 | + | |
| 5 | +## 搜索接口 | |
| 6 | + | |
| 7 | +### 3.9 获取单个文档 | |
| 8 | + | |
| 9 | +- **端点**: `GET /search/{doc_id}` | |
| 10 | +- **描述**: 根据文档 ID 获取单个商品详情,用于点击结果后的详情页或排查问题。 | |
| 11 | +- **租户标识**:同 [-01-搜索接口](./搜索API对接指南-01-搜索接口.md#31-接口信息),通过请求头 `X-Tenant-ID` 或 query 参数 `tenant_id` 传递。 | |
| 12 | + | |
| 13 | +#### 路径参数 | |
| 14 | + | |
| 15 | +| 参数 | 类型 | 描述 | | |
| 16 | +|------|------|------| | |
| 17 | +| `doc_id` | string | 商品或文档 ID | | |
| 18 | + | |
| 19 | +#### 响应示例 | |
| 20 | + | |
| 21 | +```json | |
| 22 | +{ | |
| 23 | + "id": "12345", | |
| 24 | + "source": { | |
| 25 | + "title": { | |
| 26 | + "zh": "芭比时尚娃娃" | |
| 27 | + }, | |
| 28 | + "min_price": 89.99, | |
| 29 | + "category1_name": "玩具" | |
| 30 | + } | |
| 31 | +} | |
| 32 | +``` | |
| 33 | + | |
| 34 | +#### 请求示例 | |
| 35 | + | |
| 36 | +```bash | |
| 37 | +curl "http://localhost:6002/search/12345" -H "X-Tenant-ID: 162" | |
| 38 | +# 或使用 query 参数:curl "http://localhost:6002/search/12345?tenant_id=162" | |
| 39 | +``` | |
| 40 | + | ... | ... |
| ... | ... | @@ -0,0 +1,767 @@ |
| 1 | +# 搜索API对接指南-05-索引接口(Indexer) | |
| 2 | + | |
| 3 | +本篇覆盖数据同步/索引构建相关的所有接口(原文第 5 章),用于 `external indexer` 和 `Indexer 服务` 的对接。 | |
| 4 | + | |
| 5 | +## 索引接口 | |
| 6 | + | |
| 7 | +本节内容与 `api/routes/indexer.py` 中的索引相关服务一致,包含以下接口: | |
| 8 | + | |
| 9 | +| 接口 | 方法 | 路径 | 说明 | | |
| 10 | +|------|------|------|------| | |
| 11 | +| 全量重建索引 | POST | `/indexer/reindex` | 将指定租户所有 SPU 导入 ES(不删现有索引) | | |
| 12 | +| 增量索引 | POST | `/indexer/index` | 按 SPU ID 列表索引/删除,支持自动检测删除与显式删除 | | |
| 13 | +| 查询文档 | POST | `/indexer/documents` | 按 SPU ID 列表查询 ES 文档,不写入 ES | | |
| 14 | +| 构建 ES 文档(正式) | POST | `/indexer/build-docs` | 由上游提供 MySQL 行数据,返回 ES-ready 文档,不写 ES | | |
| 15 | +| 构建 ES 文档(测试) | POST | `/indexer/build-docs-from-db` | 由本服务查库并构建文档,仅测试/调试用 | | |
| 16 | +| 内容理解字段生成 | POST | `/indexer/enrich-content` | 根据商品标题批量生成 qanchors、semantic_attributes、tags(供微服务组合方式使用) | | |
| 17 | +| 索引健康检查 | GET | `/indexer/health` | 检查索引服务与数据库连接状态 | | |
| 18 | + | |
| 19 | +#### 5.0 支撑外部 indexer 的三种方式 | |
| 20 | + | |
| 21 | +本服务对**外部 indexer 程序**(如 Java 索引系统)提供三种对接方式,可按需选择: | |
| 22 | + | |
| 23 | +| 方式 | 说明 | 适用场景 | | |
| 24 | +|------|------|----------| | |
| 25 | +| **1)doc 填充接口** | 调用 `POST /indexer/build-docs` 或 `POST /indexer/build-docs-from-db`,由本服务基于 MySQL 行数据构建完整 ES 文档(含多语言、向量、规格等),**不写入 ES**,由调用方自行写入。 | 希望一站式拿到 ES-ready doc,由己方控制写 ES 的时机与索引名。 | | |
| 26 | +| **2)微服务组合** | 单独调用**翻译**、**向量化**、**内容理解字段生成**等接口,由 indexer 程序自己组装 doc 并写入 ES。翻译与向量化为独立微服务(见第 7 节);内容理解为 Indexer 服务内接口 `POST /indexer/enrich-content`。 | 需要灵活编排、或希望将 LLM/向量等耗时步骤与主链路解耦(如异步补齐 qanchors/tags)。 | | |
| 27 | +| **3)本服务直接写 ES** | 调用全量索引 `POST /indexer/reindex`、增量索引 `POST /indexer/index`(指定 SPU ID 列表),由本服务从 MySQL 拉数并直接写入 ES。 | 自建运维、联调或不需要由 Java 写 ES 的场景。 | | |
| 28 | + | |
| 29 | +- **方式 1** 与 **方式 2** 下,ES 的写入方均为外部 indexer(或 Java),职责清晰。 | |
| 30 | +- **方式 3** 下,本服务同时负责读库、构建 doc 与写 ES。 | |
| 31 | + | |
| 32 | +### 5.1 为租户创建索引 | |
| 33 | + | |
| 34 | +为租户创建索引需要两个步骤: | |
| 35 | + | |
| 36 | +1. **创建索引结构**(可选,仅在需要更新 mapping 或在新环境首次创建时执行) | |
| 37 | + - 使用脚本创建 ES 索引结构(基于 `mappings/search_products.json`) | |
| 38 | + - 如果索引已存在,会提示用户确认(会删除现有数据) | |
| 39 | + | |
| 40 | +2. **导入数据**(必需) | |
| 41 | + - 使用全量索引接口 `/indexer/reindex` 导入数据 | |
| 42 | + | |
| 43 | +**创建索引结构(支持多环境 namespace)**: | |
| 44 | + | |
| 45 | +```bash | |
| 46 | +# 以 UAT 环境为例: | |
| 47 | +# 1. 准备 UAT 环境的 .env(包含 UAT 的 ES_HOST/DB_HOST 等) | |
| 48 | +# 2. 设置环境前缀(也可以直接在 .env 中配置): | |
| 49 | +export RUNTIME_ENV=uat | |
| 50 | +export ES_INDEX_NAMESPACE=uat_ | |
| 51 | + | |
| 52 | +# 3. 为 tenant_id=170 创建索引结构 | |
| 53 | +./scripts/create_tenant_index.sh 170 | |
| 54 | +``` | |
| 55 | + | |
| 56 | +脚本会自动从项目根目录的 `.env` 文件加载 ES 配置,并根据 `ES_INDEX_NAMESPACE` 创建: | |
| 57 | + | |
| 58 | +- prod 环境(ES_INDEX_NAMESPACE 为空):`search_products_tenant_170` | |
| 59 | +- UAT 环境(ES_INDEX_NAMESPACE=uat_):`uat_search_products_tenant_170` | |
| 60 | + | |
| 61 | +**注意事项**: | |
| 62 | +- ⚠️ 如果索引已存在,脚本会提示确认,确认后会删除现有数据 | |
| 63 | +- 创建索引后,**必须**调用 `/indexer/reindex` 导入数据 | |
| 64 | +- 如果只是更新数据而不需要修改索引结构,直接使用 `/indexer/reindex` 即可 | |
| 65 | + | |
| 66 | +--- | |
| 67 | + | |
| 68 | +### 5.2 全量索引接口 | |
| 69 | + | |
| 70 | +- **端点**: `POST /indexer/reindex` | |
| 71 | +- **描述**: 全量索引,将指定租户的所有SPU数据导入到ES索引(不会删除现有索引)。**推荐仅用于自测/运维场景**;生产环境下更推荐由 Java 等上游控制调度与写 ES。 | |
| 72 | + | |
| 73 | +#### 请求参数 | |
| 74 | + | |
| 75 | +```json | |
| 76 | +{ | |
| 77 | + "tenant_id": "162", | |
| 78 | + "batch_size": 500 | |
| 79 | +} | |
| 80 | +``` | |
| 81 | + | |
| 82 | +| 参数 | 类型 | 必填 | 默认值 | 说明 | | |
| 83 | +|------|------|------|--------|------| | |
| 84 | +| `tenant_id` | string | Y | - | 租户ID | | |
| 85 | +| `batch_size` | integer | N | 500 | 批量导入大小 | | |
| 86 | + | |
| 87 | +#### 响应格式 | |
| 88 | + | |
| 89 | +**成功响应(200 OK)**(示例,实际 `index_name` 会带上 tenant 和环境前缀): | |
| 90 | + | |
| 91 | +```json | |
| 92 | +{ | |
| 93 | + "success": true, | |
| 94 | + "total": 1000, | |
| 95 | + "indexed": 1000, | |
| 96 | + "failed": 0, | |
| 97 | + "elapsed_time": 12.34, | |
| 98 | + "index_name": "search_products_tenant_162", | |
| 99 | + "tenant_id": "162" | |
| 100 | +} | |
| 101 | +``` | |
| 102 | + | |
| 103 | +**错误响应**: | |
| 104 | +- `400 Bad Request`: 参数错误 | |
| 105 | +- `503 Service Unavailable`: 服务未初始化 | |
| 106 | + | |
| 107 | +#### 请求示例 | |
| 108 | + | |
| 109 | +**全量索引(不会删除现有索引)**: | |
| 110 | + | |
| 111 | +```bash | |
| 112 | +curl -X POST "http://localhost:6004/indexer/reindex" \ | |
| 113 | + -H "Content-Type: application/json" \ | |
| 114 | + -d '{ | |
| 115 | + "tenant_id": "162", | |
| 116 | + "batch_size": 500 | |
| 117 | + }' | |
| 118 | +``` | |
| 119 | + | |
| 120 | +**查看日志**: | |
| 121 | + | |
| 122 | +```bash | |
| 123 | +# 查看API日志(包含索引操作日志) | |
| 124 | +tail -f logs/api.log | |
| 125 | + | |
| 126 | +# 或者查看所有日志文件 | |
| 127 | +tail -f logs/*.log | |
| 128 | +``` | |
| 129 | + | |
| 130 | +> ⚠️ **重要提示**:如需 **创建索引结构**,请参考 [5.1 为租户创建索引](#51-为租户创建索引) 章节,使用 `./scripts/create_tenant_index.sh <tenant_id>`。创建后需要调用 `/indexer/reindex` 导入数据。 | |
| 131 | + | |
| 132 | +**查看索引日志**: | |
| 133 | + | |
| 134 | +索引操作的所有关键信息都会记录到 `logs/indexer.log` 文件中(JSON 格式),包括: | |
| 135 | +- 请求开始和结束时间 | |
| 136 | +- 租户ID、SPU ID、操作类型 | |
| 137 | +- 每个SPU的处理状态 | |
| 138 | +- ES批量写入结果 | |
| 139 | +- 成功/失败统计和详细错误信息 | |
| 140 | + | |
| 141 | +```bash | |
| 142 | +# 实时查看索引日志(包含全量和增量索引的所有操作) | |
| 143 | +tail -f logs/indexer.log | |
| 144 | + | |
| 145 | +# 使用 grep 查询(简单方式) | |
| 146 | +# 查看全量索引日志 | |
| 147 | +grep "\"index_type\":\"bulk\"" logs/indexer.log | tail -100 | |
| 148 | + | |
| 149 | +# 查看增量索引日志 | |
| 150 | +grep "\"index_type\":\"incremental\"" logs/indexer.log | tail -100 | |
| 151 | + | |
| 152 | +# 查看特定租户的索引日志 | |
| 153 | +grep "\"tenant_id\":\"162\"" logs/indexer.log | tail -100 | |
| 154 | + | |
| 155 | +# 使用 jq 查询(推荐,更精确的 JSON 查询) | |
| 156 | +# 安装 jq: sudo apt-get install jq 或 brew install jq | |
| 157 | + | |
| 158 | +# 查看全量索引日志 | |
| 159 | +cat logs/indexer.log | jq 'select(.index_type == "bulk")' | tail -100 | |
| 160 | + | |
| 161 | +# 查看增量索引日志 | |
| 162 | +cat logs/indexer.log | jq 'select(.index_type == "incremental")' | tail -100 | |
| 163 | + | |
| 164 | +# 查看特定租户的索引日志 | |
| 165 | +cat logs/indexer.log | jq 'select(.tenant_id == "162")' | tail -100 | |
| 166 | + | |
| 167 | +# 查看失败的索引操作 | |
| 168 | +cat logs/indexer.log | jq 'select(.operation == "request_complete" and .failed_count > 0)' | |
| 169 | + | |
| 170 | +# 查看特定SPU的处理日志 | |
| 171 | +cat logs/indexer.log | jq 'select(.spu_id == "123")' | |
| 172 | + | |
| 173 | +# 查看最近的索引请求统计 | |
| 174 | +cat logs/indexer.log | jq 'select(.operation == "request_complete") | {timestamp, index_type, tenant_id, total_count, success_count, failed_count, elapsed_time}' | |
| 175 | +``` | |
| 176 | + | |
| 177 | +### 5.3 增量索引接口 | |
| 178 | + | |
| 179 | +- **端点**: `POST /indexer/index` | |
| 180 | +- **描述**: 增量索引接口,根据指定的SPU ID列表进行索引,直接将数据写入ES。用于增量更新指定商品。**推荐仅作为内部/调试入口**;正式对接建议改用 `/indexer/build-docs`,由上游写 ES。 | |
| 181 | + | |
| 182 | +**删除说明**: | |
| 183 | +- `spu_ids`中的SPU:如果数据库`deleted=1`,自动从ES删除,响应状态为`deleted` | |
| 184 | +- `delete_spu_ids`中的SPU:直接删除,响应状态为`deleted`、`not_found`或`failed` | |
| 185 | + | |
| 186 | +#### 请求参数 | |
| 187 | + | |
| 188 | +```json | |
| 189 | +{ | |
| 190 | + "tenant_id": "162", | |
| 191 | + "spu_ids": ["123", "456", "789"], | |
| 192 | + "delete_spu_ids": ["100", "101"] | |
| 193 | +} | |
| 194 | +``` | |
| 195 | + | |
| 196 | +| 参数 | 类型 | 必填 | 说明 | | |
| 197 | +|------|------|------|------| | |
| 198 | +| `tenant_id` | string | Y | 租户ID | | |
| 199 | +| `spu_ids` | array[string] | N | SPU ID列表(1-100个),要索引的SPU。如果为空,则只执行删除操作 | | |
| 200 | +| `delete_spu_ids` | array[string] | N | 显式指定要删除的SPU ID列表(1-100个),可选。无论数据库状态如何,都会从ES中删除这些SPU | | |
| 201 | + | |
| 202 | +**注意**: | |
| 203 | +- `spu_ids` 和 `delete_spu_ids` 不能同时为空 | |
| 204 | +- 每个列表最多支持100个SPU ID | |
| 205 | +- 如果SPU在`spu_ids`中且数据库`deleted=1`,会自动从ES删除(自动检测删除) | |
| 206 | + | |
| 207 | +#### 响应格式 | |
| 208 | + | |
| 209 | +```json | |
| 210 | +{ | |
| 211 | + "spu_ids": [ | |
| 212 | + { | |
| 213 | + "spu_id": "123", | |
| 214 | + "status": "indexed" | |
| 215 | + }, | |
| 216 | + { | |
| 217 | + "spu_id": "456", | |
| 218 | + "status": "deleted" | |
| 219 | + }, | |
| 220 | + { | |
| 221 | + "spu_id": "789", | |
| 222 | + "status": "failed", | |
| 223 | + "msg": "SPU not found (unexpected)" | |
| 224 | + } | |
| 225 | + ], | |
| 226 | + "delete_spu_ids": [ | |
| 227 | + { | |
| 228 | + "spu_id": "100", | |
| 229 | + "status": "deleted" | |
| 230 | + }, | |
| 231 | + { | |
| 232 | + "spu_id": "101", | |
| 233 | + "status": "not_found" | |
| 234 | + }, | |
| 235 | + { | |
| 236 | + "spu_id": "102", | |
| 237 | + "status": "failed", | |
| 238 | + "msg": "Failed to delete from ES: Connection timeout" | |
| 239 | + } | |
| 240 | + ], | |
| 241 | + "total": 6, | |
| 242 | + "success_count": 4, | |
| 243 | + "failed_count": 2, | |
| 244 | + "elapsed_time": 1.23, | |
| 245 | + "index_name": "search_products", | |
| 246 | + "tenant_id": "162" | |
| 247 | +} | |
| 248 | +``` | |
| 249 | + | |
| 250 | +| 字段 | 类型 | 说明 | | |
| 251 | +|------|------|------| | |
| 252 | +| `spu_ids` | array | spu_ids对应的响应列表,每个元素包含 `spu_id` 和 `status` | | |
| 253 | +| `spu_ids[].status` | string | 状态:`indexed`(已索引)、`deleted`(已删除,自动检测)、`failed`(失败) | | |
| 254 | +| `spu_ids[].msg` | string | 当status为`failed`时,包含失败原因(可选) | | |
| 255 | +| `delete_spu_ids` | array | delete_spu_ids对应的响应列表,每个元素包含 `spu_id` 和 `status` | | |
| 256 | +| `delete_spu_ids[].status` | string | 状态:`deleted`(已删除)、`not_found`(ES中不存在)、`failed`(失败) | | |
| 257 | +| `delete_spu_ids[].msg` | string | 当status为`failed`时,包含失败原因(可选) | | |
| 258 | +| `total` | integer | 总处理数量(spu_ids数量 + delete_spu_ids数量) | | |
| 259 | +| `success_count` | integer | 成功数量(indexed + deleted + not_found) | | |
| 260 | +| `failed_count` | integer | 失败数量 | | |
| 261 | +| `elapsed_time` | float | 耗时(秒) | | |
| 262 | +| `index_name` | string | 索引名称 | | |
| 263 | +| `tenant_id` | string | 租户ID | | |
| 264 | + | |
| 265 | +**状态说明**: | |
| 266 | +- `spu_ids` 的状态: | |
| 267 | + - `indexed`: SPU已成功索引到ES | |
| 268 | + - `deleted`: SPU在数据库中被标记为deleted=1,已从ES删除(自动检测) | |
| 269 | + - `failed`: 处理失败,会包含`msg`字段说明失败原因 | |
| 270 | +- `delete_spu_ids` 的状态: | |
| 271 | + - `deleted`: SPU已从ES成功删除 | |
| 272 | + - `not_found`: SPU在ES中不存在(也算成功,可能已经被删除过) | |
| 273 | + - `failed`: 删除失败,会包含`msg`字段说明失败原因 | |
| 274 | + | |
| 275 | +#### 请求示例 | |
| 276 | + | |
| 277 | +**示例1:普通增量索引(自动检测删除)**: | |
| 278 | + | |
| 279 | +```bash | |
| 280 | +curl -X POST "http://localhost:6004/indexer/index" \ | |
| 281 | + -H "Content-Type: application/json" \ | |
| 282 | + -d '{ | |
| 283 | + "tenant_id": "162", | |
| 284 | + "spu_ids": ["123", "456", "789"] | |
| 285 | + }' | |
| 286 | +``` | |
| 287 | + | |
| 288 | +说明:如果SPU 456在数据库中`deleted=1`,会自动从ES删除,在响应中`spu_ids`列表里456的状态为`deleted`。 | |
| 289 | + | |
| 290 | +**示例2:显式删除(批量删除)**: | |
| 291 | + | |
| 292 | +```bash | |
| 293 | +curl -X POST "http://localhost:6004/indexer/index" \ | |
| 294 | + -H "Content-Type: application/json" \ | |
| 295 | + -d '{ | |
| 296 | + "tenant_id": "162", | |
| 297 | + "spu_ids": ["123", "456"], | |
| 298 | + "delete_spu_ids": ["100", "101", "102"] | |
| 299 | + }' | |
| 300 | +``` | |
| 301 | + | |
| 302 | +说明:SPU 100、101、102会被显式删除,无论数据库状态如何。 | |
| 303 | + | |
| 304 | +**示例3:仅删除(不索引)**: | |
| 305 | + | |
| 306 | +```bash | |
| 307 | +curl -X POST "http://localhost:6004/indexer/index" \ | |
| 308 | + -H "Content-Type: application/json" \ | |
| 309 | + -d '{ | |
| 310 | + "tenant_id": "162", | |
| 311 | + "spu_ids": [], | |
| 312 | + "delete_spu_ids": ["100", "101"] | |
| 313 | + }' | |
| 314 | +``` | |
| 315 | + | |
| 316 | +说明:只执行删除操作,不进行索引。 | |
| 317 | + | |
| 318 | +**示例4:混合操作(索引+删除)**: | |
| 319 | + | |
| 320 | +```bash | |
| 321 | +curl -X POST "http://localhost:6004/indexer/index" \ | |
| 322 | + -H "Content-Type: application/json" \ | |
| 323 | + -d '{ | |
| 324 | + "tenant_id": "162", | |
| 325 | + "spu_ids": ["123", "456", "789"], | |
| 326 | + "delete_spu_ids": ["100", "101"] | |
| 327 | + }' | |
| 328 | +``` | |
| 329 | + | |
| 330 | +说明:同时执行索引和删除操作。 | |
| 331 | + | |
| 332 | +#### 日志说明 | |
| 333 | + | |
| 334 | +增量索引操作的所有关键信息都会记录到 `logs/indexer.log` 文件中(JSON格式),包括: | |
| 335 | +- 请求开始和结束时间 | |
| 336 | +- 每个SPU的处理状态(获取、转换、索引、删除) | |
| 337 | +- ES批量写入结果 | |
| 338 | +- 成功/失败统计 | |
| 339 | +- 详细的错误信息 | |
| 340 | + | |
| 341 | +日志查询方式请参考[5.1节查看索引日志](#51-全量重建索引接口)部分。 | |
| 342 | + | |
| 343 | +### 5.4 查询文档接口 | |
| 344 | + | |
| 345 | +- **端点**: `POST /indexer/documents` | |
| 346 | +- **描述**: 查询文档接口,根据SPU ID列表获取ES文档数据(**不写入ES**)。用于查看、调试或验证SPU数据。 | |
| 347 | + | |
| 348 | +#### 请求参数 | |
| 349 | + | |
| 350 | +```json | |
| 351 | +{ | |
| 352 | + "tenant_id": "162", | |
| 353 | + "spu_ids": ["123", "456", "789"] | |
| 354 | +} | |
| 355 | +``` | |
| 356 | + | |
| 357 | +| 参数 | 类型 | 必填 | 说明 | | |
| 358 | +|------|------|------|------| | |
| 359 | +| `tenant_id` | string | Y | 租户ID | | |
| 360 | +| `spu_ids` | array[string] | Y | SPU ID列表(1-100个) | | |
| 361 | + | |
| 362 | +#### 响应格式 | |
| 363 | + | |
| 364 | +```json | |
| 365 | +{ | |
| 366 | + "success": [ | |
| 367 | + { | |
| 368 | + "spu_id": "123", | |
| 369 | + "document": { | |
| 370 | + "tenant_id": "162", | |
| 371 | + "spu_id": "123", | |
| 372 | + "title": { | |
| 373 | + "zh": "商品标题" | |
| 374 | + }, | |
| 375 | + ... | |
| 376 | + } | |
| 377 | + }, | |
| 378 | + { | |
| 379 | + "spu_id": "456", | |
| 380 | + "document": {...} | |
| 381 | + } | |
| 382 | + ], | |
| 383 | + "failed": [ | |
| 384 | + { | |
| 385 | + "spu_id": "789", | |
| 386 | + "error": "SPU not found or deleted" | |
| 387 | + } | |
| 388 | + ], | |
| 389 | + "total": 3, | |
| 390 | + "success_count": 2, | |
| 391 | + "failed_count": 1 | |
| 392 | +} | |
| 393 | +``` | |
| 394 | + | |
| 395 | +| 字段 | 类型 | 说明 | | |
| 396 | +|------|------|------| | |
| 397 | +| `success` | array | 成功获取的SPU列表,每个元素包含 `spu_id` 和 `document`(完整的ES文档数据) | | |
| 398 | +| `failed` | array | 失败的SPU列表,每个元素包含 `spu_id` 和 `error`(失败原因) | | |
| 399 | +| `total` | integer | 总SPU数量 | | |
| 400 | +| `success_count` | integer | 成功数量 | | |
| 401 | +| `failed_count` | integer | 失败数量 | | |
| 402 | + | |
| 403 | +#### 请求示例 | |
| 404 | + | |
| 405 | +**单个SPU查询**: | |
| 406 | + | |
| 407 | +```bash | |
| 408 | +curl -X POST "http://localhost:6004/indexer/documents" \ | |
| 409 | + -H "Content-Type: application/json" \ | |
| 410 | + -d '{ | |
| 411 | + "tenant_id": "162", | |
| 412 | + "spu_ids": ["123"] | |
| 413 | + }' | |
| 414 | +``` | |
| 415 | + | |
| 416 | +**批量SPU查询**: | |
| 417 | + | |
| 418 | +```bash | |
| 419 | +curl -X POST "http://localhost:6004/indexer/documents" \ | |
| 420 | + -H "Content-Type: application/json" \ | |
| 421 | + -d '{ | |
| 422 | + "tenant_id": "162", | |
| 423 | + "spu_ids": ["123", "456", "789"] | |
| 424 | + }' | |
| 425 | +``` | |
| 426 | + | |
| 427 | +#### 与 `/indexer/index` 的区别 | |
| 428 | + | |
| 429 | +| 接口 | 功能 | 是否写入ES | 返回内容 | | |
| 430 | +|------|------|-----------|----------| | |
| 431 | +| `/indexer/documents` | 查询SPU文档数据 | 否 | 返回完整的ES文档数据 | | |
| 432 | +| `/indexer/index` | 增量索引 | 是 | 返回成功/失败列表和统计信息 | | |
| 433 | + | |
| 434 | +**使用场景**: | |
| 435 | +- `/indexer/documents`:用于查看、调试或验证SPU数据,不修改ES索引 | |
| 436 | +- `/indexer/index`:用于实际的增量索引操作,将更新的SPU数据同步到ES | |
| 437 | + | |
| 438 | +### 5.5 索引健康检查接口 | |
| 439 | + | |
| 440 | +- **端点**: `GET /indexer/health` | |
| 441 | +- **描述**: 检查索引服务健康状态(与 `api/routes/indexer.py` 中 `indexer_health_check` 一致) | |
| 442 | + | |
| 443 | +#### 响应格式 | |
| 444 | + | |
| 445 | +```json | |
| 446 | +{ | |
| 447 | + "status": "available", | |
| 448 | + "database": "connected", | |
| 449 | + "preloaded_data": { | |
| 450 | + "category_mappings": 150 | |
| 451 | + } | |
| 452 | +} | |
| 453 | +``` | |
| 454 | + | |
| 455 | +| 字段 | 类型 | 说明 | | |
| 456 | +|------|------|------| | |
| 457 | +| `status` | string | `available`(服务可用)、`unavailable`(未初始化)、`error`(异常) | | |
| 458 | +| `database` | string | 数据库连接状态,如 `connected` 或 `disconnected: ...` | | |
| 459 | +| `preloaded_data.category_mappings` | integer | 已加载的分类映射数量 | | |
| 460 | + | |
| 461 | +#### 请求示例 | |
| 462 | + | |
| 463 | +```bash | |
| 464 | +curl -X GET "http://localhost:6004/indexer/health" | |
| 465 | +``` | |
| 466 | + | |
| 467 | +### 5.6 文档构建接口(正式对接推荐) | |
| 468 | + | |
| 469 | +#### 5.6.1 `POST /indexer/build-docs` | |
| 470 | + | |
| 471 | +- **描述**: | |
| 472 | + 基于调用方(通常是 Java 索引程序)提供的 **MySQL 行数据** 构建 ES 文档(doc),**不写入 ES**。 | |
| 473 | + 由本服务负责“如何构建 doc”(多语言、翻译、向量、规格聚合等),由调用方负责“何时调度 + 如何写 ES”。 | |
| 474 | + | |
| 475 | +#### 请求参数 | |
| 476 | + | |
| 477 | +```json | |
| 478 | +{ | |
| 479 | + "tenant_id": "170", | |
| 480 | + "items": [ | |
| 481 | + { | |
| 482 | + "spu": { "id": 223167, "tenant_id": 170, "title": "..." }, | |
| 483 | + "skus": [ | |
| 484 | + { "id": 3988393, "spu_id": 223167, "price": 25.99, "compare_at_price": 25.99 } | |
| 485 | + ], | |
| 486 | + "options": [] | |
| 487 | + } | |
| 488 | + ] | |
| 489 | +} | |
| 490 | +``` | |
| 491 | + | |
| 492 | +| 参数 | 类型 | 必填 | 说明 | | |
| 493 | +|------|------|------|------| | |
| 494 | +| `tenant_id` | string | Y | 租户 ID | | |
| 495 | +| `items` | array | Y | 需构建 doc 的 SPU 列表(每项含 `spu`、`skus`、`options`),**单次最多 200 条** | | |
| 496 | + | |
| 497 | +> `spu` / `skus` / `options` 字段应当直接使用从 `shoplazza_product_spu` / `shoplazza_product_sku` / `shoplazza_product_option` 查询出的行字段。 | |
| 498 | + | |
| 499 | +#### 请求示例(完整 curl) | |
| 500 | + | |
| 501 | +> 完整请求体参考 `scripts/test_build_docs_api.py` 中的 `build_sample_request()`。 | |
| 502 | + | |
| 503 | +```bash | |
| 504 | +# 单条 SPU 示例(含 spu、skus、options) | |
| 505 | +curl -X POST "http://localhost:6004/indexer/build-docs" \ | |
| 506 | + -H "Content-Type: application/json" \ | |
| 507 | + -d '{ | |
| 508 | + "tenant_id": "162", | |
| 509 | + "items": [ | |
| 510 | + { | |
| 511 | + "spu": { | |
| 512 | + "id": 10001, | |
| 513 | + "tenant_id": "162", | |
| 514 | + "title": "测试T恤 纯棉短袖", | |
| 515 | + "brief": "舒适纯棉,多色可选", | |
| 516 | + "description": "这是一款适合日常穿着的纯棉T恤,透气吸汗。", | |
| 517 | + "vendor": "测试品牌", | |
| 518 | + "category": "服装/上衣/T恤", | |
| 519 | + "category_id": 100, | |
| 520 | + "category_level": 2, | |
| 521 | + "category_path": "服装/上衣/T恤", | |
| 522 | + "fake_sales": 1280, | |
| 523 | + "image_src": "https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg", | |
| 524 | + "tags": "T恤,纯棉,短袖,夏季", | |
| 525 | + "create_time": "2024-01-01T00:00:00Z", | |
| 526 | + "update_time": "2024-01-01T00:00:00Z" | |
| 527 | + }, | |
| 528 | + "skus": [ | |
| 529 | + { | |
| 530 | + "id": 20001, | |
| 531 | + "spu_id": 10001, | |
| 532 | + "price": 99.0, | |
| 533 | + "compare_at_price": 129.0, | |
| 534 | + "sku": "SKU-TSHIRT-001", | |
| 535 | + "inventory_quantity": 50, | |
| 536 | + "option1": "黑色", | |
| 537 | + "option2": "M", | |
| 538 | + "option3": null | |
| 539 | + }, | |
| 540 | + { | |
| 541 | + "id": 20002, | |
| 542 | + "spu_id": 10001, | |
| 543 | + "price": 99.0, | |
| 544 | + "compare_at_price": 129.0, | |
| 545 | + "sku": "SKU-TSHIRT-002", | |
| 546 | + "inventory_quantity": 30, | |
| 547 | + "option1": "白色", | |
| 548 | + "option2": "L", | |
| 549 | + "option3": null | |
| 550 | + } | |
| 551 | + ], | |
| 552 | + "options": [ | |
| 553 | + {"id": 1, "position": 1, "name": "颜色"}, | |
| 554 | + {"id": 2, "position": 2, "name": "尺码"} | |
| 555 | + ] | |
| 556 | + } | |
| 557 | + ] | |
| 558 | +}' | |
| 559 | +``` | |
| 560 | + | |
| 561 | +生产环境替换 `localhost:6004` 为实际 Indexer 地址,如 `http://43.166.252.75:6004`。 | |
| 562 | + | |
| 563 | +#### 响应示例(节选) | |
| 564 | + | |
| 565 | +```json | |
| 566 | +{ | |
| 567 | + "tenant_id": "170", | |
| 568 | + "docs": [ | |
| 569 | + { | |
| 570 | + "tenant_id": "170", | |
| 571 | + "spu_id": "223167", | |
| 572 | + "title": { "en": "...", "zh": "..." }, | |
| 573 | + "tags": ["Floerns", "Clothing", "Shoes & Jewelry"], | |
| 574 | + "skus": [ | |
| 575 | + { | |
| 576 | + "sku_id": "3988393", | |
| 577 | + "price": 25.99, | |
| 578 | + "compare_at_price": 25.99, | |
| 579 | + "stock": 100 | |
| 580 | + } | |
| 581 | + ], | |
| 582 | + "min_price": 25.99, | |
| 583 | + "max_price": 25.99, | |
| 584 | + "compare_at_price": 25.99, | |
| 585 | + "total_inventory": 100, | |
| 586 | + "title_embedding": [/* 1024 维向量 */] | |
| 587 | + // 其余字段与 mappings/search_products.json 一致 | |
| 588 | + } | |
| 589 | + ], | |
| 590 | + "total": 1, | |
| 591 | + "success_count": 1, | |
| 592 | + "failed_count": 0, | |
| 593 | + "failed": [] | |
| 594 | +} | |
| 595 | +``` | |
| 596 | + | |
| 597 | +| 字段 | 类型 | 说明 | | |
| 598 | +|------|------|------| | |
| 599 | +| `tenant_id` | string | 租户 ID | | |
| 600 | +| `docs` | array | 构建成功的 ES 文档列表,与 `mappings/search_products.json` 一致 | | |
| 601 | +| `total` | integer | 请求的 items 总数 | | |
| 602 | +| `success_count` | integer | 成功构建数量 | | |
| 603 | +| `failed_count` | integer | 失败数量 | | |
| 604 | +| `failed` | array | 失败项列表,每项含 `spu_id`、`error` | | |
| 605 | + | |
| 606 | +#### 使用建议 | |
| 607 | + | |
| 608 | +- **生产环境推荐流程**: | |
| 609 | + 1. Java 根据业务逻辑决定哪些 SPU 需要(全量/增量)处理; | |
| 610 | + 2. Java 从 MySQL 查询 SPU/SKU/Option 行,拼成 `items`; | |
| 611 | + 3. 调用 `/indexer/build-docs` 获取 ES-ready `docs`; | |
| 612 | + 4. Java 使用自己的 ES 客户端写入 `search_products_tenant_{tenant_id}`。 | |
| 613 | + | |
| 614 | +### 5.7 文档构建接口(测试 / 自测) | |
| 615 | + | |
| 616 | +#### 5.7.1 `POST /indexer/build-docs-from-db` | |
| 617 | + | |
| 618 | +- **描述**: | |
| 619 | + 仅用于测试/调试:调用方只提供 `tenant_id` 和 `spu_ids`,由 indexer 服务内部从 MySQL 查询 SPU/SKU/Option,然后调用与 `/indexer/build-docs` 相同的文档构建逻辑,返回 ES-ready doc。**生产环境请使用 `/indexer/build-docs`,由上游查库并写 ES。** | |
| 620 | + | |
| 621 | +#### 请求参数 | |
| 622 | + | |
| 623 | +```json | |
| 624 | +{ | |
| 625 | + "tenant_id": "170", | |
| 626 | + "spu_ids": ["223167", "223168"] | |
| 627 | +} | |
| 628 | +``` | |
| 629 | + | |
| 630 | +| 参数 | 类型 | 必填 | 说明 | | |
| 631 | +|------|------|------|------| | |
| 632 | +| `tenant_id` | string | Y | 租户 ID | | |
| 633 | +| `spu_ids` | array[string] | Y | SPU ID 列表,**单次最多 200 个** | | |
| 634 | + | |
| 635 | +#### 响应格式 | |
| 636 | + | |
| 637 | +与 `/indexer/build-docs` 相同:`tenant_id`、`docs`、`total`、`success_count`、`failed_count`、`failed`。 | |
| 638 | + | |
| 639 | +#### 请求示例 | |
| 640 | + | |
| 641 | +```bash | |
| 642 | +curl -X POST "http://127.0.0.1:6004/indexer/build-docs-from-db" \ | |
| 643 | + -H "Content-Type: application/json" \ | |
| 644 | + -d '{"tenant_id": "170", "spu_ids": ["223167"]}' | |
| 645 | +``` | |
| 646 | + | |
| 647 | +返回结构与 `/indexer/build-docs` 相同,可直接用于对比 ES 实际文档或调试字段映射问题。 | |
| 648 | + | |
| 649 | +### 5.8 内容理解字段生成接口 | |
| 650 | + | |
| 651 | +- **端点**: `POST /indexer/enrich-content` | |
| 652 | +- **描述**: 根据商品内容信息批量生成 **qanchors**(锚文本)、**semantic_attributes**(语义属性)、**tags**(细分标签),供外部 indexer 在「微服务组合」方式下自行拼装 doc 时使用。请求以 `items[]` 传入商品内容字段(必填/可选见下表)。内部逻辑与 `indexer.product_enrich` 一致,支持多语言与 Redis 缓存;单次请求在线程池中执行,避免阻塞其他接口。 | |
| 653 | + | |
| 654 | +#### 请求参数 | |
| 655 | + | |
| 656 | +```json | |
| 657 | +{ | |
| 658 | + "tenant_id": "170", | |
| 659 | + "items": [ | |
| 660 | + { | |
| 661 | + "spu_id": "223167", | |
| 662 | + "title": "纯棉短袖T恤 夏季男装", | |
| 663 | + "brief": "夏季透气纯棉短袖,舒适亲肤", | |
| 664 | + "description": "100%棉,圆领版型,适合日常通勤与休闲穿搭。", | |
| 665 | + "image_url": "https://example.com/images/223167.jpg" | |
| 666 | + }, | |
| 667 | + { | |
| 668 | + "spu_id": "223168", | |
| 669 | + "title": "12PCS Dolls with Bottles", | |
| 670 | + "image_url": "https://example.com/images/223168.jpg" | |
| 671 | + } | |
| 672 | + ], | |
| 673 | + "languages": ["zh", "en"] | |
| 674 | +} | |
| 675 | +``` | |
| 676 | + | |
| 677 | +| 参数 | 类型 | 必填 | 默认值 | 说明 | | |
| 678 | +|------|------|------|--------|------| | |
| 679 | +| `tenant_id` | string | Y | - | 租户 ID。目前仅用于记录日志,不产生实际作用| | |
| 680 | +| `items` | array | Y | - | 待分析列表;**单次最多 50 条** | | |
| 681 | +| `languages` | array[string] | N | `["zh", "en"]` | 目标语言,需在支持范围内:`zh`、`en`、`de`、`ru`、`fr` | | |
| 682 | + | |
| 683 | +`items[]` 字段说明: | |
| 684 | + | |
| 685 | +| 字段 | 类型 | 必填 | 说明 | | |
| 686 | +|------|------|------|------| | |
| 687 | +| `spu_id` | string | Y | SPU ID,用于回填结果;目前仅用于记录日志,不产生实际作用| | |
| 688 | +| `title` | string | Y | 商品标题 | | |
| 689 | +| `image_url` | string | N | 商品主图 URL;当前会参与内容缓存键,后续可用于图像/多模态内容理解 | | |
| 690 | +| `brief` | string | N | 商品简介/短描述;当前会参与内容缓存键 | | |
| 691 | +| `description` | string | N | 商品详情/长描述;当前会参与内容缓存键 | | |
| 692 | + | |
| 693 | +缓存说明: | |
| 694 | + | |
| 695 | +- 内容缓存键仅由 `target_lang + items[]` 中会影响内容理解结果的输入文本构成,目前包括:`title`、`brief`、`description`、`image_url` 的规范化内容 hash。 | |
| 696 | +- `tenant_id`、`spu_id` 只用于请求归属与结果回填,不参与缓存键。 | |
| 697 | +- 因此,输入内容不变时可跨请求直接命中缓存;任一输入字段变化时,会自然落到新的缓存 key。 | |
| 698 | + | |
| 699 | +批量请求建议: | |
| 700 | +- **全量**:强烈建议 尽可能 **20 个 SPU/doc** 攒成一个批次后再请求一次。 | |
| 701 | +- **增量**:可按时效要求设置时间窗口(例如 **5 分钟**),在窗口内尽可能攒到 **20 个**;达到 20 或窗口到期就发送一次请求。 | |
| 702 | +- 允许超过20,服务内部会拆分成小批次逐个处理。也允许小于20,但是将造成费用和耗时的成本上升,特别是每次请求一个doc的情况。 | |
| 703 | + | |
| 704 | +#### 响应格式 | |
| 705 | + | |
| 706 | +```json | |
| 707 | +{ | |
| 708 | + "tenant_id": "170", | |
| 709 | + "total": 2, | |
| 710 | + "results": [ | |
| 711 | + { | |
| 712 | + "spu_id": "223167", | |
| 713 | + "qanchors": { | |
| 714 | + "zh": "短袖T恤,纯棉,男装,夏季", | |
| 715 | + "en": "cotton t-shirt, short sleeve, men, summer" | |
| 716 | + }, | |
| 717 | + "semantic_attributes": [ | |
| 718 | + { "lang": "zh", "name": "tags", "value": "纯棉" }, | |
| 719 | + { "lang": "zh", "name": "usage_scene", "value": "日常" }, | |
| 720 | + { "lang": "en", "name": "tags", "value": "cotton" } | |
| 721 | + ], | |
| 722 | + "tags": ["纯棉", "短袖", "男装", "cotton", "short sleeve"] | |
| 723 | + }, | |
| 724 | + { | |
| 725 | + "spu_id": "223168", | |
| 726 | + "qanchors": { "en": "dolls, toys, 12pcs" }, | |
| 727 | + "semantic_attributes": [], | |
| 728 | + "tags": ["dolls", "toys"] | |
| 729 | + } | |
| 730 | + ] | |
| 731 | +} | |
| 732 | +``` | |
| 733 | + | |
| 734 | +| 字段 | 类型 | 说明 | | |
| 735 | +|------|------|------| | |
| 736 | +| `results` | array | 与请求 `items` 一一对应,每项含 `spu_id`、`qanchors`、`semantic_attributes`、`tags` | | |
| 737 | +| `results[].qanchors` | object | 按语言键的锚文本(逗号分隔短语),可写入 ES 文档的 `qanchors.{lang}` | | |
| 738 | +| `results[].semantic_attributes` | array | 语义属性列表,每项为 `{ "lang", "name", "value" }`,可写入 ES 的 `semantic_attributes` nested 字段 | | |
| 739 | +| `results[].tags` | array | 从语义属性中抽取的 `name=tags` 的 value 集合,可与业务原有 `tags` 合并后写入 ES 的 `tags` 字段 | | |
| 740 | +| `results[].error` | string | 若该条处理失败(如 LLM 异常),会在此字段返回错误信息 | | |
| 741 | + | |
| 742 | +**错误响应**: | |
| 743 | +- `400`: `items` 为空或超过 50 条 | |
| 744 | +- `503`: 未配置 `DASHSCOPE_API_KEY`,内容理解服务不可用 | |
| 745 | + | |
| 746 | +#### 请求示例 | |
| 747 | + | |
| 748 | +```bash | |
| 749 | +curl -X POST "http://localhost:6004/indexer/enrich-content" \ | |
| 750 | + -H "Content-Type: application/json" \ | |
| 751 | + -d '{ | |
| 752 | + "tenant_id": "170", | |
| 753 | + "items": [ | |
| 754 | + { | |
| 755 | + "spu_id": "223167", | |
| 756 | + "title": "纯棉短袖T恤 夏季男装", | |
| 757 | + "brief": "夏季透气纯棉短袖,舒适亲肤", | |
| 758 | + "description": "100%棉,圆领版型,适合日常通勤与休闲穿搭。", | |
| 759 | + "image_url": "https://example.com/images/223167.jpg" | |
| 760 | + } | |
| 761 | + ], | |
| 762 | + "languages": ["zh", "en"] | |
| 763 | + }' | |
| 764 | +``` | |
| 765 | + | |
| 766 | +--- | |
| 767 | + | ... | ... |
| ... | ... | @@ -0,0 +1,53 @@ |
| 1 | +# 搜索API对接指南-06-管理接口(Admin) | |
| 2 | + | |
| 3 | +用于查看服务健康状态、获取租户配置与索引统计信息(原文第 6 章)。 | |
| 4 | + | |
| 5 | +## 管理接口 | |
| 6 | + | |
| 7 | +### 6.1 健康检查 | |
| 8 | + | |
| 9 | +- **端点**: `GET /admin/health` | |
| 10 | +- **描述**: 检查服务与依赖(如 Elasticsearch)状态。 | |
| 11 | + | |
| 12 | +```json | |
| 13 | +{ | |
| 14 | + "status": "healthy", | |
| 15 | + "elasticsearch": "connected", | |
| 16 | + "tenant_id": "tenant1" | |
| 17 | +} | |
| 18 | +``` | |
| 19 | + | |
| 20 | +### 6.2 获取配置 | |
| 21 | + | |
| 22 | +- **端点**: `GET /admin/config` | |
| 23 | +- **描述**: 返回当前租户的脱敏配置,便于核对索引及排序表达式。 | |
| 24 | + | |
| 25 | +```json | |
| 26 | +{ | |
| 27 | + "tenant_id": "tenant1", | |
| 28 | + "tenant_name": "Tenant1 Test Instance", | |
| 29 | + "es_index_name": "search_tenant1", | |
| 30 | + "num_fields": 20, | |
| 31 | + "num_indexes": 4, | |
| 32 | + "supported_languages": ["zh", "en", "ru"], | |
| 33 | + "spu_enabled": false | |
| 34 | +} | |
| 35 | +``` | |
| 36 | + | |
| 37 | +### 6.3 索引统计 | |
| 38 | + | |
| 39 | +- **端点**: `GET /admin/stats` | |
| 40 | +- **描述**: 获取指定租户索引文档数量与磁盘大小,方便监控。 | |
| 41 | +- **租户标识**:通过请求头 `X-Tenant-ID` 或 query 参数 `tenant_id` 传递(必填)。 | |
| 42 | + | |
| 43 | +```json | |
| 44 | +{ | |
| 45 | + "tenant_id": "162", | |
| 46 | + "index_name": "search_products_tenant_162", | |
| 47 | + "document_count": 10000, | |
| 48 | + "size_mb": 523.45 | |
| 49 | +} | |
| 50 | +``` | |
| 51 | + | |
| 52 | +--- | |
| 53 | + | ... | ... |
docs/搜索API对接指南-07-微服务接口(Embedding-Reranker-Translation).md
0 → 100644
| ... | ... | @@ -0,0 +1,401 @@ |
| 1 | +# 搜索API对接指南-07-微服务接口(Embedding-Reranker-Translation) | |
| 2 | + | |
| 3 | +本篇覆盖向量服务(Embedding)、重排服务(Reranker)、翻译服务(Translation)以及 Indexer 服务内的内容理解字段生成(原文第 7 章)。 | |
| 4 | + | |
| 5 | +## 7. 微服务接口(向量、重排、翻译) | |
| 6 | + | |
| 7 | +以下三个微服务独立部署,**外部系统可直接调用**。它们被搜索后端(6002)和索引服务(6004)内部使用,也可供其他业务系统直接对接。 | |
| 8 | + | |
| 9 | +| 服务 | 默认端口 | Base URL | 说明 | | |
| 10 | +|------|----------|----------|------| | |
| 11 | +| 向量服务(文本) | 6005 | `http://localhost:6005` | 文本向量化,用于 query/doc 语义检索 | | |
| 12 | +| 向量服务(图片) | 6008 | `http://localhost:6008` | 图片向量化,用于以图搜图 | | |
| 13 | +| 翻译服务 | 6006 | `http://localhost:6006` | 多语言翻译(云端与本地模型统一入口) | | |
| 14 | +| 重排服务 | 6007 | `http://localhost:6007` | 对检索结果进行二次排序 | | |
| 15 | + | |
| 16 | +生产环境请将 `localhost` 替换为实际服务地址。 | |
| 17 | +服务管理入口与完整启停规则见:`docs/Usage-Guide.md` -> `服务管理总览`。 | |
| 18 | + | |
| 19 | +### 7.1 向量服务(Embedding) | |
| 20 | + | |
| 21 | +- **Base URL**: | |
| 22 | + - 文本:`http://localhost:6005`(可通过 `EMBEDDING_TEXT_SERVICE_URL` 覆盖) | |
| 23 | + - 图片:`http://localhost:6008`(可通过 `EMBEDDING_IMAGE_SERVICE_URL` 覆盖) | |
| 24 | +- **启动**: | |
| 25 | + - 文本:`./scripts/start_embedding_text_service.sh` | |
| 26 | + - 图片:`./scripts/start_embedding_image_service.sh` | |
| 27 | +- **依赖**: | |
| 28 | + - 文本向量后端默认走 TEI(`http://127.0.0.1:8080`) | |
| 29 | + - 图片向量依赖 `cnclip`(`grpc://127.0.0.1:51000`) | |
| 30 | + - TEI 默认使用 GPU(`TEI_DEVICE=cuda`);当配置为 GPU 且不可用时会启动失败(不会自动降级到 CPU) | |
| 31 | + - cnclip 默认使用 `cuda`;若配置为 `cuda` 但 GPU 不可用会启动失败(不会自动降级到 `cpu`) | |
| 32 | + - 当前单机部署建议保持单实例,通过**文本/图片拆分 + 独立限流**隔离压力 | |
| 33 | + | |
| 34 | +补充说明: | |
| 35 | + | |
| 36 | +- 文本和图片现在已经拆成**不同进程 / 不同端口**,避免图片下载与编码波动影响文本向量化。 | |
| 37 | +- 服务端对 text / image 有**独立 admission control**: | |
| 38 | + - `TEXT_MAX_INFLIGHT` | |
| 39 | + - `IMAGE_MAX_INFLIGHT` | |
| 40 | +- 当超过处理能力时,服务会直接返回过载错误,而不是无限排队。 | |
| 41 | +- `GET /health` 会返回各自的 `limits`、`stats`、`cache_enabled` 等状态;`GET /ready` 用于就绪探针。 | |
| 42 | + | |
| 43 | +#### 7.1.1 `POST /embed/text` — 文本向量化 | |
| 44 | + | |
| 45 | +将文本列表转为 1024 维向量,用于语义搜索、文档索引等。 | |
| 46 | + | |
| 47 | +**请求体**(JSON 数组): | |
| 48 | + | |
| 49 | +```json | |
| 50 | +["文本1", "文本2", "文本3"] | |
| 51 | +``` | |
| 52 | + | |
| 53 | +**响应**(JSON 数组,与输入一一对应): | |
| 54 | + | |
| 55 | +```json | |
| 56 | +[[0.01, -0.02, ...], [0.03, 0.01, ...], ...] | |
| 57 | +``` | |
| 58 | + | |
| 59 | +**完整 curl 示例**: | |
| 60 | + | |
| 61 | +```bash | |
| 62 | +curl -X POST "http://localhost:6005/embed/text?normalize=true" \ | |
| 63 | + -H "Content-Type: application/json" \ | |
| 64 | + -d '["芭比娃娃 儿童玩具", "纯棉T恤 短袖"]' | |
| 65 | +``` | |
| 66 | + | |
| 67 | +#### 7.1.2 `POST /embed/image` — 图片向量化 | |
| 68 | + | |
| 69 | +将图片 URL 或路径转为向量,用于以图搜图。 | |
| 70 | + | |
| 71 | +前置条件:`cnclip` 服务已启动(默认端口 `51000`)。若未启动,图片 embedding 服务启动会失败或请求返回错误。 | |
| 72 | + | |
| 73 | +**请求体**(JSON 数组): | |
| 74 | + | |
| 75 | +```json | |
| 76 | +["https://example.com/image1.jpg", "https://example.com/image2.jpg"] | |
| 77 | +``` | |
| 78 | + | |
| 79 | +**响应**(JSON 数组,与输入一一对应): | |
| 80 | + | |
| 81 | +```json | |
| 82 | +[[0.01, -0.02, ...], [0.03, 0.01, ...], ...] | |
| 83 | +``` | |
| 84 | + | |
| 85 | +**完整 curl 示例**: | |
| 86 | + | |
| 87 | +```bash | |
| 88 | +curl -X POST "http://localhost:6008/embed/image?normalize=true" \ | |
| 89 | + -H "Content-Type: application/json" \ | |
| 90 | + -d '["https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg"]' | |
| 91 | +``` | |
| 92 | + | |
| 93 | +#### 7.1.3 `GET /health` — 健康检查 | |
| 94 | + | |
| 95 | +```bash | |
| 96 | +curl "http://localhost:6005/health" | |
| 97 | +curl "http://localhost:6008/health" | |
| 98 | +``` | |
| 99 | + | |
| 100 | +返回中会包含: | |
| 101 | + | |
| 102 | +- `service_kind`:`text` / `image` / `all` | |
| 103 | +- `cache_enabled`:text/image Redis 缓存是否可用 | |
| 104 | +- `limits`:当前 inflight limit、active、rejected_total 等 | |
| 105 | +- `stats`:request_total、cache_hits、cache_misses、avg_latency_ms 等 | |
| 106 | + | |
| 107 | +#### 7.1.4 `GET /ready` — 就绪检查 | |
| 108 | + | |
| 109 | +```bash | |
| 110 | +curl "http://localhost:6005/ready" | |
| 111 | +curl "http://localhost:6008/ready" | |
| 112 | +``` | |
| 113 | + | |
| 114 | +#### 7.1.5 缓存与限流说明 | |
| 115 | + | |
| 116 | +- 文本与图片都会先查 Redis 向量缓存。 | |
| 117 | +- Redis 中 value 仍是 **BF16 bytes**,读取后恢复成 `float32` 返回。 | |
| 118 | +- cache key 已区分 `normalize=true/false`,避免不同归一化策略命中同一条缓存。 | |
| 119 | +- 当服务端发现请求是 **full-cache-hit** 时,会直接返回,不占用模型并发槽位。 | |
| 120 | +- 当服务端发现超过 `TEXT_MAX_INFLIGHT` / `IMAGE_MAX_INFLIGHT` 时,会直接拒绝,而不是无限排队。 | |
| 121 | + | |
| 122 | +#### 7.1.6 TEI 统一调优建议(主服务) | |
| 123 | + | |
| 124 | +使用单套主服务即可同时兼顾: | |
| 125 | +- 在线 query 向量化(低延迟,常见 `batch=1~4`) | |
| 126 | +- 索引构建向量化(高吞吐,常见 `batch=15~20`) | |
| 127 | + | |
| 128 | +统一启动(主链路): | |
| 129 | + | |
| 130 | +```bash | |
| 131 | +./scripts/start_tei_service.sh | |
| 132 | +./scripts/service_ctl.sh restart embedding | |
| 133 | +``` | |
| 134 | + | |
| 135 | +默认端口: | |
| 136 | +- TEI: `http://127.0.0.1:8080` | |
| 137 | +- 文本向量服务(`/embed/text`): `http://127.0.0.1:6005` | |
| 138 | +- 图片向量服务(`/embed/image`): `http://127.0.0.1:6008` | |
| 139 | + | |
| 140 | +当前主 TEI 启动默认值(已按 T4/短文本场景调优): | |
| 141 | +- `TEI_MAX_BATCH_TOKENS=4096` | |
| 142 | +- `TEI_MAX_CLIENT_BATCH_SIZE=24` | |
| 143 | +- `TEI_DTYPE=float16` | |
| 144 | + | |
| 145 | +### 7.2 重排服务(Reranker) | |
| 146 | + | |
| 147 | +- **Base URL**: `http://localhost:6007`(可通过 `RERANKER_SERVICE_URL` 覆盖) | |
| 148 | +- **启动**: `./scripts/start_reranker.sh` | |
| 149 | + | |
| 150 | +说明:默认后端为 `qwen3_vllm`(`Qwen/Qwen3-Reranker-0.6B`),需要可用 GPU 显存。 | |
| 151 | + | |
| 152 | +补充:`docs` 的请求大小与模型推理 `batch size` 解耦。即使一次传入 1000 条文档,服务端也会按 `services.rerank.backends.qwen3_vllm.infer_batch_size` 自动拆分;若 `sort_by_doc_length=true`,会先按文档长度排序后分批,减少 padding,再按原输入顺序返回分数。`length_sort_mode` 可选 `char`(更快)或 `token`(更精确)。 | |
| 153 | + | |
| 154 | +#### 7.2.1 `POST /rerank` — 结果重排 | |
| 155 | + | |
| 156 | +根据 query 与 doc 的相关性对文档列表重新打分排序。 | |
| 157 | + | |
| 158 | +**请求体**: | |
| 159 | +```json | |
| 160 | +{ | |
| 161 | + "query": "玩具 芭比", | |
| 162 | + "docs": [ | |
| 163 | + "12PCS 6 Types of Dolls with Bottles", | |
| 164 | + "纯棉T恤 短袖 夏季" | |
| 165 | + ], | |
| 166 | + "normalize": true | |
| 167 | +} | |
| 168 | +``` | |
| 169 | + | |
| 170 | +| 参数 | 类型 | 必填 | 说明 | | |
| 171 | +|------|------|------|------| | |
| 172 | +| `query` | string | Y | 搜索查询 | | |
| 173 | +| `docs` | array[string] | Y | 待重排的文档列表(单次最多由服务端配置限制) | | |
| 174 | +| `normalize` | boolean | N | 是否对分数做 sigmoid 归一化,默认 true | | |
| 175 | + | |
| 176 | +**响应**: | |
| 177 | +```json | |
| 178 | +{ | |
| 179 | + "scores": [0.92, 0.15], | |
| 180 | + "meta": { | |
| 181 | + "service_elapsed_ms": 45.2, | |
| 182 | + "input_docs": 2, | |
| 183 | + "unique_docs": 2 | |
| 184 | + } | |
| 185 | +} | |
| 186 | +``` | |
| 187 | + | |
| 188 | +**完整 curl 示例**: | |
| 189 | +```bash | |
| 190 | +curl -X POST "http://localhost:6007/rerank" \ | |
| 191 | + -H "Content-Type: application/json" \ | |
| 192 | + -d '{ | |
| 193 | + "query": "玩具 芭比", | |
| 194 | + "docs": ["12PCS 6 Types of Dolls with Bottles", "纯棉T恤 短袖"], | |
| 195 | + "top_n":386, | |
| 196 | + "normalize": true | |
| 197 | + }' | |
| 198 | +``` | |
| 199 | + | |
| 200 | +#### 7.2.2 `GET /health` — 健康检查 | |
| 201 | + | |
| 202 | +```bash | |
| 203 | +curl "http://localhost:6007/health" | |
| 204 | +``` | |
| 205 | + | |
| 206 | +### 7.3 翻译服务(Translation) | |
| 207 | + | |
| 208 | +- **Base URL**: `http://localhost:6006`(以 `config/config.yaml -> services.translation.service_url` 为准) | |
| 209 | +- **启动**: `./scripts/start_translator.sh` | |
| 210 | + | |
| 211 | +#### 7.3.1 `POST /translate` — 文本翻译 | |
| 212 | + | |
| 213 | +支持 translator service 内所有已启用 capability,适用于商品名称、描述、query 等电商场景。当前可配置能力包括 `qwen-mt`、`llm`、`deepl` 以及本地模型 `nllb-200-distilled-600m`、`opus-mt-zh-en`、`opus-mt-en-zh`。 | |
| 214 | + | |
| 215 | +**请求体**(支持单条字符串或字符串列表): | |
| 216 | +```json | |
| 217 | +{ | |
| 218 | + "text": "商品名称", | |
| 219 | + "target_lang": "en", | |
| 220 | + "source_lang": "zh", | |
| 221 | + "model": "qwen-mt", | |
| 222 | + "scene": "sku_name" | |
| 223 | +} | |
| 224 | +``` | |
| 225 | + | |
| 226 | +也支持批量列表形式: | |
| 227 | +```json | |
| 228 | +{ | |
| 229 | + "text": ["商品名称1", "商品名称2"], | |
| 230 | + "target_lang": "en", | |
| 231 | + "source_lang": "zh", | |
| 232 | + "model": "qwen-mt", | |
| 233 | + "scene": "sku_name" | |
| 234 | +} | |
| 235 | +``` | |
| 236 | + | |
| 237 | +| 参数 | 类型 | 必填 | 说明 | | |
| 238 | +|------|------|------|------| | |
| 239 | +| `text` | string \| string[] | Y | 待翻译文本,既支持单条字符串,也支持字符串列表(批量翻译) | | |
| 240 | +| `target_lang` | string | Y | 目标语言:`zh`、`en`、`ru` 等 | | |
| 241 | +| `source_lang` | string | N | 源语言。云端模型可不传;`nllb-200-distilled-600m` 建议显式传入 | | |
| 242 | +| `model` | string | N | 已启用 capability 名称,如 `qwen-mt`、`llm`、`deepl`、`nllb-200-distilled-600m`、`opus-mt-zh-en`、`opus-mt-en-zh` | | |
| 243 | +| `scene` | string | N | 翻译场景参数,与 `model` 配套使用;当前标准值为 `sku_name`、`ecommerce_search_query`、`general` | | |
| 244 | + | |
| 245 | +说明: | |
| 246 | +- 外部接口不接受 `prompt`;LLM prompt 由服务端按 `scene` 自动生成。 | |
| 247 | +- 传入未定义的 `scene` 或未启用的 `model` 会返回 `400`。 | |
| 248 | + | |
| 249 | +**SKU 名称场景选型建议**: | |
| 250 | +- 批量 SKU 名称翻译,优先考虑本地大吞吐方案时,可使用 `"model": "nllb-200-distilled-600m"`(该模型"scene":参数无效)。 | |
| 251 | +- 如果目标是更高质量,且可以接受更慢速度与额外 LLM API 费用,可使用 `"model": "llm"` + `"scene": "sku_name"`。 | |
| 252 | +- 如果是en-zh互译、期待更高的速度,可以考虑`opus-mt-zh-en` / `opus-mt-en-zh`。(质量未详细评测,一些文章说比blib-200-600m更好,但是我看了些case感觉要差不少) | |
| 253 | + | |
| 254 | +**实时翻译选型建议**: | |
| 255 | +- 在线 query 翻译如果只是 `en/zh` 互译,优先使用 `opus-mt-zh-en` 或 `opus-mt-en-zh`,它们是当前已测本地模型里延迟最低的一档。 | |
| 256 | +- 如果涉及其他语言,或对质量要求高于本地轻量模型,优先考虑 `deepl`。 | |
| 257 | +- `nllb-200-distilled-600m` 不建议作为在线 query 翻译默认方案;我们在 `Tesla T4` 上测到 `batch_size=1` 时,`zh -> en` p50 约 `292.54 ms`、p95 约 `624.12 ms`,`en -> zh` p50 约 `481.61 ms`、p95 约 `1171.71 ms`。 | |
| 258 | + | |
| 259 | +**Batch Size / 调用方式建议**: | |
| 260 | +- 本接口支持 `text: string[]`;离线或批量索引翻译时,应尽量合并请求,让底层 backend 发挥批处理能力。 | |
| 261 | +- `nllb-200-distilled-600m` 在当前 `Tesla T4` 压测中,推荐配置是 `batch_size=16`、`max_new_tokens=64`、`attn_implementation=sdpa`;继续升到 `batch_size=32` 虽可能提高吞吐,但 tail latency 会明显变差。 | |
| 262 | +- 在线 query 场景可直接把“单条请求”理解为 `batch_size=1`;更关注 request latency,而不是离线吞吐。 | |
| 263 | +- `opus-mt-zh-en` / `opus-mt-en-zh` 当前生产配置也是 `batch_size=16`,适合作为中英互译的低延迟本地默认值;若走在线单条调用,同样按 `batch_size=1` 理解即可。 | |
| 264 | +- `llm` 按单条请求即可。 | |
| 265 | + | |
| 266 | +**响应**: | |
| 267 | +```json | |
| 268 | +{ | |
| 269 | + "text": "商品名称", | |
| 270 | + "target_lang": "en", | |
| 271 | + "source_lang": "zh", | |
| 272 | + "translated_text": "Product name", | |
| 273 | + "status": "success", | |
| 274 | + "model": "qwen-mt", | |
| 275 | + "scene": "sku_name" | |
| 276 | +} | |
| 277 | +``` | |
| 278 | + | |
| 279 | +当请求为列表形式时,`text` 与 `translated_text` 均为等长数组: | |
| 280 | +```json | |
| 281 | +{ | |
| 282 | + "text": ["商品名称1", "商品名称2"], | |
| 283 | + "target_lang": "en", | |
| 284 | + "source_lang": "zh", | |
| 285 | + "translated_text": ["Product name 1", "Product name 2"], | |
| 286 | + "status": "success", | |
| 287 | + "model": "qwen-mt", | |
| 288 | + "scene": "sku_name" | |
| 289 | +} | |
| 290 | +``` | |
| 291 | + | |
| 292 | +> **失败语义(批量)**:当 `text` 为列表时,如果其中某条翻译失败,对应位置返回 `null`(即 `translated_text[i] = null`),并保持数组长度与顺序不变;接口整体仍返回 `status="success"`,用于避免“部分失败”导致整批请求失败。 | |
| 293 | + | |
| 294 | +> **实现提示(可忽略)**:服务端会尽可能使用底层 backend 的批量能力(若支持),否则自动拆分逐条翻译;无论采用哪种方式,上述批量契约保持一致。 | |
| 295 | + | |
| 296 | +**完整 curl 示例**: | |
| 297 | + | |
| 298 | +中文 → 英文: | |
| 299 | +```bash | |
| 300 | +curl -X POST "http://localhost:6006/translate" \ | |
| 301 | + -H "Content-Type: application/json" \ | |
| 302 | + -d '{ | |
| 303 | + "text": "商品名称", | |
| 304 | + "target_lang": "en", | |
| 305 | + "source_lang": "zh" | |
| 306 | + }' | |
| 307 | +``` | |
| 308 | + | |
| 309 | +俄文 → 英文: | |
| 310 | +```bash | |
| 311 | +curl -X POST "http://localhost:6006/translate" \ | |
| 312 | + -H "Content-Type: application/json" \ | |
| 313 | + -d '{ | |
| 314 | + "text": "Название товара", | |
| 315 | + "target_lang": "en", | |
| 316 | + "source_lang": "ru" | |
| 317 | + }' | |
| 318 | +``` | |
| 319 | + | |
| 320 | +使用 DeepL 模型: | |
| 321 | +```bash | |
| 322 | +curl -X POST "http://localhost:6006/translate" \ | |
| 323 | + -H "Content-Type: application/json" \ | |
| 324 | + -d '{ | |
| 325 | + "text": "商品名称", | |
| 326 | + "target_lang": "en", | |
| 327 | + "source_lang": "zh", | |
| 328 | + "model": "deepl" | |
| 329 | + }' | |
| 330 | +``` | |
| 331 | + | |
| 332 | +使用本地 OPUS 模型(中文 → 英文): | |
| 333 | +```bash | |
| 334 | +curl -X POST "http://localhost:6006/translate" \ | |
| 335 | + -H "Content-Type: application/json" \ | |
| 336 | + -d '{ | |
| 337 | + "text": "蓝牙耳机", | |
| 338 | + "target_lang": "en", | |
| 339 | + "source_lang": "zh", | |
| 340 | + "model": "opus-mt-zh-en", | |
| 341 | + "scene": "sku_name" | |
| 342 | + }' | |
| 343 | +``` | |
| 344 | + | |
| 345 | +使用本地 NLLB 做 SKU 名称批量翻译: | |
| 346 | +```bash | |
| 347 | +curl -X POST "http://localhost:6006/translate" \ | |
| 348 | + -H "Content-Type: application/json" \ | |
| 349 | + -d '{ | |
| 350 | + "text": ["商品名称1", "商品名称2", "商品名称3"], | |
| 351 | + "target_lang": "en", | |
| 352 | + "source_lang": "zh", | |
| 353 | + "model": "nllb-200-distilled-600m", | |
| 354 | + "scene": "sku_name" | |
| 355 | + }' | |
| 356 | +``` | |
| 357 | + | |
| 358 | +使用 LLM 做高质量 SKU 名称翻译: | |
| 359 | +```bash | |
| 360 | +curl -X POST "http://localhost:6006/translate" \ | |
| 361 | + -H "Content-Type: application/json" \ | |
| 362 | + -d '{ | |
| 363 | + "text": "男士偏光飞行员太阳镜", | |
| 364 | + "target_lang": "en", | |
| 365 | + "source_lang": "zh", | |
| 366 | + "model": "llm", | |
| 367 | + "scene": "sku_name" | |
| 368 | + }' | |
| 369 | +``` | |
| 370 | + | |
| 371 | +#### 7.3.2 `GET /health` — 健康检查 | |
| 372 | + | |
| 373 | +```bash | |
| 374 | +curl "http://localhost:6006/health" | |
| 375 | +``` | |
| 376 | + | |
| 377 | +典型响应: | |
| 378 | +```json | |
| 379 | +{ | |
| 380 | + "status": "healthy", | |
| 381 | + "service": "translation", | |
| 382 | + "default_model": "llm", | |
| 383 | + "default_scene": "general", | |
| 384 | + "available_models": ["qwen-mt", "llm", "opus-mt-zh-en"], | |
| 385 | + "enabled_capabilities": ["qwen-mt", "llm", "opus-mt-zh-en"], | |
| 386 | + "loaded_models": ["llm"] | |
| 387 | +} | |
| 388 | +``` | |
| 389 | + | |
| 390 | +### 7.4 内容理解字段生成(Indexer 服务内) | |
| 391 | + | |
| 392 | +内容理解字段生成接口部署在 **Indexer 服务**(默认端口 6004)内,与「翻译、向量化」等独立端口微服务并列,供采用**微服务组合**方式的 indexer 调用。 | |
| 393 | + | |
| 394 | +- **Base URL**: Indexer 服务地址,如 `http://localhost:6004` | |
| 395 | +- **路径**: `POST /indexer/enrich-content` | |
| 396 | +- **说明**: 根据商品标题批量生成 `qanchors`、`semantic_attributes`、`tags`,用于拼装 ES 文档。内部使用大模型(需配置 `DASHSCOPE_API_KEY`),支持多语言与 Redis 缓存;单次最多 50 条,建议批量调用以提升效率。 | |
| 397 | + | |
| 398 | +请求/响应格式、示例及错误码见 [-05-索引接口(Indexer)](./搜索API对接指南-05-索引接口(Indexer).md#58-内容理解字段生成接口)。 | |
| 399 | + | |
| 400 | +--- | |
| 401 | + | ... | ... |
| ... | ... | @@ -0,0 +1,97 @@ |
| 1 | +# 搜索API对接指南-08-数据模型与字段速查 | |
| 2 | + | |
| 3 | +本篇覆盖原文第 9 章:商品字段定义、字段类型速查、常用字段列表、支持的分析器。 | |
| 4 | + | |
| 5 | +## 9. 数据模型 | |
| 6 | + | |
| 7 | +### 9.1 商品字段定义 | |
| 8 | + | |
| 9 | +| 字段名 | 类型 | 描述 | | |
| 10 | +|--------|------|------| | |
| 11 | +| `tenant_id` | keyword | 租户ID(多租户隔离) | | |
| 12 | +| `spu_id` | keyword | SPU ID | | |
| 13 | +| `title.<lang>` | object/text | 商品标题(多语言对象,如 `title.zh`, `title.en`) | | |
| 14 | +| `brief.<lang>` | object/text | 商品短描述(多语言对象,如 `brief.zh`, `brief.en`) | | |
| 15 | +| `description.<lang>` | object/text | 商品详细描述(多语言对象,如 `description.zh`, `description.en`) | | |
| 16 | +| `vendor.<lang>` | object/text | 供应商/品牌(多语言对象,且带 keyword 子字段,如 `vendor.zh.keyword`) | | |
| 17 | +| `category_path.<lang>` | object/text | 类目路径(多语言对象,用于搜索,如 `category_path.zh`) | | |
| 18 | +| `category_name_text.<lang>` | object/text | 类目名称(多语言对象,用于搜索,如 `category_name_text.zh`) | | |
| 19 | +| `category_id` | keyword | 类目ID | | |
| 20 | +| `category_name` | keyword | 类目名称(用于过滤) | | |
| 21 | +| `category_level` | integer | 类目层级 | | |
| 22 | +| `category1_name`, `category2_name`, `category3_name` | keyword | 多级类目名称(用于过滤和分面) | | |
| 23 | +| `tags` | keyword | 标签(数组) | | |
| 24 | +| `specifications` | nested | 规格(嵌套对象数组) | | |
| 25 | +| `option1_name`, `option2_name`, `option3_name` | keyword | 选项名称 | | |
| 26 | +| `min_price`, `max_price` | float | 最低/最高价格 | | |
| 27 | +| `compare_at_price` | float | 原价 | | |
| 28 | +| `sku_prices` | float | SKU价格列表(数组) | | |
| 29 | +| `sku_weights` | long | SKU重量列表(数组) | | |
| 30 | +| `sku_weight_units` | keyword | SKU重量单位列表(数组) | | |
| 31 | +| `total_inventory` | long | 总库存 | | |
| 32 | +| `sales` | long | 销量(展示销量) | | |
| 33 | +| `skus` | nested | SKU详细信息(嵌套对象数组) | | |
| 34 | +| `create_time`, `update_time` | date | 创建/更新时间 | | |
| 35 | +| `title_embedding` | dense_vector | 标题向量(1024维,仅用于搜索) | | |
| 36 | +| `image_embedding` | nested | 图片向量(嵌套,仅用于搜索) | | |
| 37 | + | |
| 38 | +> 所有租户共享统一的索引结构。文本字段支持中英文双语,后端根据 `language` 参数自动选择对应字段返回。 | |
| 39 | + | |
| 40 | +### 9.2 字段类型速查 | |
| 41 | + | |
| 42 | +| 类型 | ES Mapping | 用途 | | |
| 43 | +|------|------------|------| | |
| 44 | +| `text` | `text` | 全文检索(支持中英文分析器) | | |
| 45 | +| `keyword` | `keyword` | 精确匹配、聚合、排序 | | |
| 46 | +| `integer` | `integer` | 整数 | | |
| 47 | +| `long` | `long` | 长整数 | | |
| 48 | +| `float` | `float` | 浮点数 | | |
| 49 | +| `date` | `date` | 日期时间 | | |
| 50 | +| `nested` | `nested` | 嵌套对象(specifications, skus, image_embedding) | | |
| 51 | +| `dense_vector` | `dense_vector` | 向量字段(title_embedding,仅用于搜索) | | |
| 52 | + | |
| 53 | +### 9.3 常用字段列表 | |
| 54 | + | |
| 55 | +#### 过滤字段 | |
| 56 | + | |
| 57 | +- `category_name`: 类目名称 | |
| 58 | +- `category1_name`, `category2_name`, `category3_name`: 多级类目 | |
| 59 | +- `category_id`: 类目ID | |
| 60 | +- `vendor.zh.keyword`, `vendor.en.keyword`: 供应商/品牌(使用keyword子字段) | |
| 61 | +- `tags`: 标签(keyword类型) | |
| 62 | +- `option1_name`, `option2_name`, `option3_name`: 选项名称 | |
| 63 | +- `specifications`: 规格过滤(嵌套字段,格式见[过滤器详解](./搜索API对接指南-01-搜索接口.md#33-过滤器详解)) | |
| 64 | + | |
| 65 | +#### 范围字段 | |
| 66 | + | |
| 67 | +- `min_price`: 最低价格 | |
| 68 | +- `max_price`: 最高价格 | |
| 69 | +- `compare_at_price`: 原价 | |
| 70 | +- `create_time`: 创建时间 | |
| 71 | +- `update_time`: 更新时间 | |
| 72 | + | |
| 73 | +#### 排序字段 | |
| 74 | + | |
| 75 | +- `price`: 价格(后端自动根据sort_order映射:asc→min_price,desc→max_price) | |
| 76 | +- `sales`: 销量 | |
| 77 | +- `create_time`: 创建时间 | |
| 78 | +- `update_time`: 更新时间 | |
| 79 | +- `relevance_score`: 相关性分数(默认,不指定sort_by时使用) | |
| 80 | + | |
| 81 | +**注意**: 前端只需传 `price`,后端会自动处理: | |
| 82 | +- `sort_by: "price"` + `sort_order: "asc"` → 按 `min_price` 升序(价格从低到高) | |
| 83 | +- `sort_by: "price"` + `sort_order: "desc"` → 按 `max_price` 降序(价格从高到低) | |
| 84 | + | |
| 85 | +### 9.4 支持的分析器 | |
| 86 | + | |
| 87 | +| 分析器 | 语言 | 描述 | | |
| 88 | +|--------|------|------| | |
| 89 | +| `index_ik` | 中文 | 中文索引分析器(用于中文字段) | | |
| 90 | +| `query_ik` | 中文 | 中文查询分析器(用于中文字段) | | |
| 91 | +| `hanlp_index` ⚠️ TODO(暂不支持) | 中文 | 中文索引分析器(用于中文字段) | | |
| 92 | +| `hanlp_standard` ⚠️ TODO(暂不支持) | 中文 | 中文查询分析器(用于中文字段) | | |
| 93 | +| `english` | 英文 | 标准英文分析器(用于英文字段) | | |
| 94 | +| `lowercase` | - | 小写标准化器(用于keyword子字段) | | |
| 95 | + | |
| 96 | +--- | |
| 97 | + | ... | ... |
| ... | ... | @@ -0,0 +1,61 @@ |
| 1 | +# 搜索API对接指南-10-接口级压测脚本 | |
| 2 | + | |
| 3 | +原文第 10 章:压测脚本与用例。 | |
| 4 | + | |
| 5 | +## 10. 接口级压测脚本 | |
| 6 | + | |
| 7 | +仓库提供统一压测脚本:`scripts/perf_api_benchmark.py`,用于对以下接口做并发压测: | |
| 8 | + | |
| 9 | +- 后端搜索:`POST /search/` | |
| 10 | +- 搜索建议:`GET /search/suggestions` | |
| 11 | +- 向量服务:`POST /embed/text` | |
| 12 | +- 翻译服务:`POST /translate` | |
| 13 | +- 重排服务:`POST /rerank` | |
| 14 | + | |
| 15 | +说明:脚本对 `embed_text` 场景会校验返回向量内容有效性(必须是有限数值,不允许 `null/NaN/Inf`),不是只看 HTTP 200。 | |
| 16 | + | |
| 17 | +### 10.1 快速示例 | |
| 18 | + | |
| 19 | +```bash | |
| 20 | +# suggest 压测(tenant 162) | |
| 21 | +python scripts/perf_api_benchmark.py \ | |
| 22 | + --scenario backend_suggest \ | |
| 23 | + --tenant-id 162 \ | |
| 24 | + --duration 30 \ | |
| 25 | + --concurrency 50 | |
| 26 | + | |
| 27 | +# search 压测 | |
| 28 | +python scripts/perf_api_benchmark.py \ | |
| 29 | + --scenario backend_search \ | |
| 30 | + --tenant-id 162 \ | |
| 31 | + --duration 30 \ | |
| 32 | + --concurrency 20 | |
| 33 | + | |
| 34 | +# 全链路压测(search + suggest + embedding + translate + rerank) | |
| 35 | +python scripts/perf_api_benchmark.py \ | |
| 36 | + --scenario all \ | |
| 37 | + --tenant-id 162 \ | |
| 38 | + --duration 60 \ | |
| 39 | + --concurrency 30 \ | |
| 40 | + --output perf_reports/all.json | |
| 41 | +``` | |
| 42 | + | |
| 43 | +### 10.2 自定义用例 | |
| 44 | + | |
| 45 | +可通过 `--cases-file` 覆盖默认请求模板。示例文件: | |
| 46 | + | |
| 47 | +```bash | |
| 48 | +scripts/perf_cases.json.example | |
| 49 | +``` | |
| 50 | + | |
| 51 | +执行示例: | |
| 52 | + | |
| 53 | +```bash | |
| 54 | +python scripts/perf_api_benchmark.py \ | |
| 55 | + --scenario all \ | |
| 56 | + --tenant-id 162 \ | |
| 57 | + --cases-file scripts/perf_cases.json.example \ | |
| 58 | + --duration 60 \ | |
| 59 | + --concurrency 40 | |
| 60 | +``` | |
| 61 | + | ... | ... |
docs/搜索API对接指南.md renamed to docs/搜索API对接指南—拆分前版本存档.md