搜索API接口对接指南
本文档为搜索服务的使用方提供完整的API对接指南,包括接口说明、请求参数、响应格式和使用示例。
目录
-
- 4.1 标准响应结构
- 4.2 响应字段说明
- 4.2.1 query_info 说明
- 4.3 SpuResult字段说明
- 4.4 SkuResult字段说明
- 4.5 多语言字段说明
-
- 5.0 支撑外部 indexer 的三种方式
- 5.1 为租户创建索引
- 5.2 全量索引接口
- 5.3 增量索引接口
- 5.4 查询文档接口
- 5.5 索引健康检查接口
- 5.6 文档构建接口(正式对接)
- 5.7 文档构建接口(测试/自测)
- 5.8 内容理解字段生成接口
-
- 7.1 向量服务(Embedding)
- 7.2 重排服务(Reranker)
- 7.3 翻译服务(Translation)
- 7.4 内容理解字段生成(Indexer 服务内)
快速开始
1.1 基础信息
- Base URL:
http://43.166.252.75:6002 - 协议: HTTP/HTTPS
- 数据格式: JSON
- 字符编码: UTF-8
- 请求方法: POST(搜索接口)
重要提示: tenant_id 通过 HTTP Header X-Tenant-ID 传递,不在请求体中。
环境与凭证:MySQL、Redis、Elasticsearch 等外部服务的 AI 生产地址与凭证见 QUICKSTART.md §1.6。
1.2 最简单的搜索请求
curl -X POST "http://43.166.252.75:6002/search/" \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: 162" \
-d '{"query": "芭比娃娃"}'
1.3 带过滤与分页的搜索
curl -X POST "http://43.166.252.75:6002/search/" \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: 162" \
-d '{
"query": "芭比娃娃",
"size": 5,
"from": 10,
"range_filters": {
"min_price": {
"gte": 50,
"lte": 200
},
"create_time": {
"gte": "2020-01-01T00:00:00Z"
}
},
"sort_by": "price",
"sort_order": "asc"
}'
1.4 开启分面的搜索
curl -X POST "http://43.166.252.75:6002/search/" \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: 162" \
-d '{
"query": "芭比娃娃",
"facets": [
{"field": "category1_name", "size": 10, "type": "terms"},
{"field": "specifications.color", "size": 10, "type": "terms"},
{"field": "specifications.size", "size": 10, "type": "terms"}
],
"min_score": 0.2
}'
接口概览
| 接口 | HTTP Method | Endpoint | 说明 |
|---|---|---|---|
| 搜索 | POST | /search/ |
执行搜索查询 |
| 搜索建议 | GET | /search/suggestions |
搜索建议(自动补全/热词,多语言) |
| 即时搜索 | GET | /search/instant |
即时搜索预留接口(当前返回 501 Not Implemented) |
| 获取文档 | GET | /search/{doc_id} |
获取单个文档 |
| 全量索引 | POST | /indexer/reindex |
全量索引接口(导入数据,不删除索引,仅推荐自测使用) |
| 增量索引 | POST | /indexer/index |
增量索引接口(指定SPU ID列表进行索引,支持自动检测删除和显式删除,仅推荐自测使用) |
| 查询文档 | POST | /indexer/documents |
查询SPU文档数据(不写入ES) |
| 构建ES文档(正式对接) | POST | /indexer/build-docs |
基于上游提供的 MySQL 行数据构建 ES doc,不写入 ES,供 Java 等调用后自行写入 |
| 构建ES文档(测试用) | POST | /indexer/build-docs-from-db |
仅在测试/调试时使用,根据 tenant_id + spu_ids 内部查库并构建 ES doc |
| 内容理解字段生成 | POST | /indexer/enrich-content |
根据商品标题批量生成 qanchors、semantic_attributes、tags,供微服务组合方式使用 |
| 索引健康检查 | GET | /indexer/health |
检查索引服务状态 |
| 健康检查 | GET | /admin/health |
服务健康检查 |
| 获取配置 | GET | /admin/config |
获取租户配置 |
| 索引统计 | GET | /admin/stats |
获取租户索引统计信息(需 tenant_id) |
微服务(独立端口或 Indexer 内,外部可直连):
| 服务 | 端口 | 接口 | 说明 |
|---|---|---|---|
| 向量服务 | 6005 | POST /embed/text |
文本向量化 |
| 向量服务 | 6005 | POST /embed/image |
图片向量化 |
| 翻译服务 | 6006 | POST /translate |
文本翻译(Qwen/DeepL) |
| 重排服务 | 6007 | POST /rerank |
检索结果重排 |
| 内容理解(Indexer 内) | 6004 | POST /indexer/enrich-content |
根据商品标题生成 qanchors、tags 等,供 indexer 微服务组合方式使用 |
搜索接口
3.1 接口信息
- 端点:
POST /search/ - 描述: 执行文本搜索查询,支持多语言、过滤器和分面搜索
- 租户标识:
tenant_id通过 HTTP 请求头X-Tenant-ID传递(推荐);也可通过 URL query 参数tenant_id传递。不要放在请求体中。
请求示例(推荐):
url = f"{base_url.rstrip('/')}/search/"
headers = {
"Content-Type": "application/json",
"X-Tenant-ID": "162", # 租户ID,必填
}
response = requests.post(url, headers=headers, json={"query": "芭比娃娃"})
3.2 请求参数
完整请求体结构
{
"query": "string (required)",
"size": 10,
"from": 0,
"language": "zh",
"filters": {},
"range_filters": {},
"facets": [],
"sort_by": "string",
"sort_order": "desc",
"min_score": 0.0,
"sku_filter_dimension": ["string"],
"debug": false,
"enable_rerank": null,
"rerank_query_template": "{query}",
"rerank_doc_template": "{title}",
"user_id": "string",
"session_id": "string"
}
参数详细说明
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
query |
string | Y | - | 搜索查询字符串(统一文本检索策略) |
size |
integer | N | 10 | 返回结果数量(1-100) |
from |
integer | N | 0 | 分页偏移量(用于分页) |
language |
string | N | "zh" | 返回语言:zh(中文)或 en(英文)。后端会根据此参数选择对应的中英文字段返回 |
filters |
object | N | null | 精确匹配过滤器(见过滤器详解) |
range_filters |
object | N | null | 数值范围过滤器(见过滤器详解) |
facets |
array | N | null | 分面配置(见分面配置) |
sort_by |
string | N | null | 排序字段名。支持:price(价格)、sales(销量)、create_time(创建时间)、update_time(更新时间)。默认按相关性排序 |
sort_order |
string | N | "desc" | 排序方向:asc(升序)或 desc(降序)。注意:price+asc=价格从低到高,price+desc=价格从高到低(后端自动映射为min_price或max_price) |
min_score |
float | N | null | 最小相关性分数阈值 |
sku_filter_dimension |
array[string] | N | null | 子SKU筛选维度列表(见SKU筛选维度) |
debug |
boolean | N | false | 是否返回调试信息 |
enable_rerank |
boolean/null | N | null | 是否开启重排(调用外部重排服务对 ES 结果进行二次排序)。不传/传 null 使用服务端 rerank.enabled(默认开启)。开启后会先对 ES TopN(rerank_window)重排,再按分页截取;若 from+size>1000,则不重排,直接按分页从 ES 返回 |
rerank_query_template |
string | N | null | 重排 query 模板(可选)。支持 {query} 占位符;不传则使用服务端配置 |
rerank_doc_template |
string | N | null | 重排 doc 模板(可选)。支持 {title} {brief} {vendor} {description} {category_path};不传则使用服务端配置 |
user_id |
string | N | null | 用户ID(用于个性化,预留) |
session_id |
string | N | null | 会话ID(用于分析,预留) |
3.3 过滤器详解
3.3.1 精确匹配过滤器 (filters)
用于精确匹配或多值匹配。对于普通字段,数组表示 OR 逻辑(匹配任意一个值);对于 specifications 字段,按维度分组处理。任意字段名加 _all 后缀表示多值 AND 逻辑(必须同时匹配所有值)。
格式:
{
"filters": {
"category_name": "手机", // 可以为单值 或者 数组 匹配数组中任意一个(OR)
"category1_name": "服装", // 可以为单值 或者 数组 匹配数组中任意一个(OR)
"category2_name": "男装", // 可以为单值 或者 数组 匹配数组中任意一个(OR)
"category3_name": "衬衫", // 可以为单值 或者 数组 匹配数组中任意一个(OR)
"vendor.zh.keyword": ["奇乐", "品牌A"], // 可以为单值 或者 数组 匹配数组中任意一个(OR)
"tags": "手机", // 可以为单值 或者 数组 匹配数组中任意一个(OR)
"tags_all": ["手机", "促销", "新品"], // *_all:多值为 AND,必须同时包含所有标签
"category1_name_all": ["服装", "男装"], // 同上,适用于任意可过滤字段
// specifications 嵌套过滤(特殊格式)
"specifications": {
"name": "color",
"value": "white"
}
}
}
支持的值类型:
- 字符串:精确匹配
- 整数:精确匹配
- 布尔值:精确匹配
- 数组:匹配任意值(OR 逻辑);若字段名以
_all结尾,则数组表示 AND 逻辑(必须同时匹配所有值) - 对象:specifications 嵌套过滤(见下文)
*_all 语义(多值 AND):
- 任意过滤字段均可使用
_all后缀,对应 ES 字段名为去掉_all后的名称。 - 例如:
tags_all: ["A", "B"]表示文档的tags必须同时包含 A 和 B;vendor.zh.keyword_all: ["奇乐", "品牌A"]表示同时匹配两个品牌(通常用于 keyword 多值场景)。 specifications_all:传列表[{"name":"color","value":"white"},{"name":"size","value":"256GB"}]时,表示所有列出的规格条件都要满足(与specifications多维度时的 AND 一致;若同维度多值则要求文档同时满足多个值,一般用于嵌套多值场景)。
Specifications 嵌套过滤:
specifications 是嵌套字段,支持按规格名称和值进行过滤。
单个规格过滤:
{
"filters": {
"specifications": {
"name": "color",
"value": "white"
}
}
}
查询规格名称为"color"且值为"white"的商品。
多个规格过滤(按维度分组):
{
"filters": {
"specifications": [
{"name": "color", "value": "white"},
{"name": "size", "value": "256GB"}
]
}
}
查询同时满足所有规格的商品(color=white 且 size=256GB)。
相同维度的多个值(OR 逻辑):
{
"filters": {
"specifications": [
{"name": "size", "value": "3"},
{"name": "size", "value": "4"},
{"name": "size", "value": "5"},
{"name": "color", "value": "green"}
]
}
}
查询满足 (size=3 或 size=4 或 size=5) 且 color=green 的商品。
过滤逻辑说明:
- 不同维度(不同的
name)之间是 AND 关系(求交集) - 相同维度(相同的
name)的多个值之间是 OR 关系(求并集)
常用过滤字段(详见常用字段列表):
category_name: 类目名称category1_name,category2_name,category3_name: 多级类目category_id: 类目IDvendor.zh.keyword,vendor.en.keyword: 供应商/品牌(使用keyword子字段)tags: 标签(keyword类型,支持数组)option1_name,option2_name,option3_name: 选项名称specifications: 规格过滤(嵌套字段,格式见上文)- 以上任意字段均可加
_all后缀表示多值 AND,如tags_all、category1_name_all。
3.3.2 范围过滤器 (range_filters)
用于数值字段的范围过滤。
格式:
{
"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.4 分面配置
用于生成分面统计(分组聚合),常用于构建筛选器UI。
3.4.1 配置格式
{
"facets": [
{
"field": "category1_name",
"size": 15,
"type": "terms",
"disjunctive": false
},
{
"field": "brand_name",
"size": 10,
"type": "terms",
"disjunctive": true
},
{
"field": "specifications.color",
"size": 20,
"type": "terms",
"disjunctive": true
},
{
"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}
]
}
]
}
3.4.2 Facet 字段说明
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
field |
string | 是 | - | 分面字段名 |
size |
int | 否 | 10 | 返回的分面值数量(1-100) |
type |
string | 否 | "terms" | 分面类型:terms(词条聚合)或 range(范围聚合) |
disjunctive |
bool | 否 | false | 是否支持多选(disjunctive faceting)。启用后,选中该分面的过滤器时,仍会显示其他可选项 |
ranges |
array | 否 | null | 范围配置(仅 type="range" 时需要) |
3.4.3 disjunctive字段说明
重要特性: disjunctive 字段控制分面的行为模式。启用后,选中该分面的过滤器时,仍会显示其他可选项
标准模式 (disjunctive: false):
- 行为: 选中某个分面值后,该分面只显示选中的值
- 适用场景: 层级类目、互斥选择
- 示例: 类目下钻(玩具 > 娃娃 > 芭比)
Multi-Select 模式 (disjunctive: true) ⭐:
- 行为: 选中某个分面值后,该分面仍显示所有可选项
- 适用场景: 颜色、品牌、尺码等可切换属性
- 示例: 选择了"红色"后,仍能看到"蓝色"、"绿色"等选项
推荐配置:
| 分面类型 | disjunctive | 原因 |
|---|---|---|
| 颜色 | true |
用户需要切换颜色 |
| 品牌 | true |
用户需要比较品牌 |
| 尺码 | true |
用户需要查看其他尺码 |
| 类目 | false |
层级下钻 |
| 价格区间 | false |
互斥选择 |
3.4.4 规格分面说明
specifications 是嵌套字段,支持两种分面模式:
模式1:所有规格名称的分面:
{
"facets": [
{
"field": "specifications",
"size": 10,
"type": "terms"
}
]
}
返回所有规格名称(name)及其对应的值(value)列表。每个 name 会生成一个独立的分面结果。
模式2:指定规格名称的分面:
{
"facets": [
{
"field": "specifications.color",
"size": 20,
"type": "terms",
"disjunctive": true
},
{
"field": "specifications.size",
"size": 15,
"type": "terms",
"disjunctive": true
}
]
}
只返回指定规格名称的值列表。格式:specifications.{name},其中 {name} 是规格名称(如"color"、"size"、"material")。
返回格式示例:
{
"facets": [
{
"field": "specifications.color",
"label": "color",
"type": "terms",
"values": [
{"value": "white", "count": 50, "selected": true}, // ✓ selected 字段由后端标记
{"value": "black", "count": 30, "selected": false},
{"value": "red", "count": 20, "selected": false}
]
},
{
"field": "specifications.size",
"label": "size",
"type": "terms",
"values": [
{"value": "256GB", "count": 40, "selected": false},
{"value": "512GB", "count": 20, "selected": false}
]
}
]
}
3.5 SKU筛选维度
功能说明:
sku_filter_dimension 用于控制搜索列表页中 每个 SPU 下方可切换的子款式(子 SKU)维度,为字符串列表。
在店铺的 主题装修配置 中,商家可以为店铺设置一个或多个子款式筛选维度(例如 color、size),前端列表页会在每个 SPU 下展示这些维度对应的子 SKU 列表,用户可以通过点击不同维度值(如不同颜色)来切换展示的子款式。
当指定 sku_filter_dimension 后,后端会根据店铺的这项配置,从所有 SKU 中筛选出这些维度组合对应的子 SKU 数据:系统会按指定维度组合对 SKU 进行分组,每个维度组合只返回第一个 SKU(从简实现,选择该组合下的第一款),其余不在这些维度组合中的子 SKU 将不返回。
支持的维度值:
直接选项字段:
option1、option2、option3- 直接使用对应的
option1_value、option2_value、option3_value字段进行分组
- 直接使用对应的
规格/选项名称: 通过
option1_name、option2_name、option3_name匹配- 例如:如果
option1_name为"color",则可以使用sku_filter_dimension: ["color"]来按颜色分组
- 例如:如果
示例:
按颜色筛选(假设 option1_name = "color"):
{
"query": "芭比娃娃",
"sku_filter_dimension": ["color"]
}
按选项1筛选:
{
"query": "芭比娃娃",
"sku_filter_dimension": ["option1"]
}
按颜色 + 尺寸组合筛选(假设 option1_name = "color", option2_name = "size"):
{
"query": "芭比娃娃",
"sku_filter_dimension": ["color", "size"]
}
3.7 搜索建议接口
- 端点:
GET /search/suggestions - 描述: 返回搜索建议(自动补全/热词),支持多语言。
查询参数
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
q |
string | Y | - | 查询字符串(至少 1 个字符) |
size |
integer | N | 10 | 返回建议数量(1-50) |
language |
string | N | en |
请求语言,如 zh / en / ar / ru,用于路由到对应语种 suggestion 索引 |
debug |
bool | N | false |
是否开启调试(目前主要用于排查 suggestion 排序与语言解析) |
租户标识:同 3.1,通过请求头
X-Tenant-ID或 query 参数tenant_id传递。
响应示例
{
"query": "iph",
"language": "en",
"resolved_language": "en",
"suggestions": [
{
"text": "iphone 15",
"lang": "en",
"score": 12.37,
"rank_score": 5.1,
"sources": ["query_log", "qanchor"],
"lang_source": "log_field",
"lang_confidence": 1.0,
"lang_conflict": false
}
],
"took_ms": 12
}
请求示例
curl "http://localhost:6002/search/suggestions?q=芭&size=5&language=zh" \
-H "X-Tenant-ID: 162"
3.8 即时搜索接口
⚠️ 当前版本未开放该能力。接口会明确返回
501 Not Implemented,避免误用未完成实现。
- 端点:
GET /search/instant - 描述: 即时搜索预留端点,后续会在独立实现完成后开放。
查询参数
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
q |
string | Y | - | 搜索查询(至少 2 个字符) |
size |
integer | N | 5 | 返回结果数量(1-20) |
请求示例
curl "http://localhost:6002/search/instant?q=玩具&size=5"
当前响应
{
"error": "/search/instant is not implemented yet. Use POST /search/ for production traffic.",
"status_code": 501
}
3.9 获取单个文档
- 端点:
GET /search/{doc_id} - 描述: 根据文档 ID 获取单个商品详情,用于点击结果后的详情页或排查问题。
- 租户标识:同 3.1,通过请求头
X-Tenant-ID或 query 参数tenant_id传递。
路径参数
| 参数 | 类型 | 描述 |
|---|---|---|
doc_id |
string | 商品或文档 ID |
响应示例
{
"id": "12345",
"source": {
"title": {
"zh": "芭比时尚娃娃"
},
"min_price": 89.99,
"category1_name": "玩具"
}
}
请求示例
curl "http://localhost:6002/search/12345" -H "X-Tenant-ID: 162"
# 或使用 query 参数:curl "http://localhost:6002/search/12345?tenant_id=162"
响应格式说明
4.1 标准响应结构
{
"results": [
{
"spu_id": "12345",
"title": "芭比时尚娃娃",
"brief": "高品质芭比娃娃",
"description": "详细描述...",
"vendor": "美泰",
"category": "玩具",
"category_path": "玩具/娃娃/时尚",
"category_name": "时尚",
"category_id": "cat_001",
"category_level": 3,
"category1_name": "玩具",
"category2_name": "娃娃",
"category3_name": "时尚",
"tags": ["娃娃", "玩具", "女孩"],
"price": 89.99,
"compare_at_price": 129.99,
"currency": "USD",
"image_url": "https://example.com/image.jpg",
"in_stock": true,
"sku_prices": [89.99, 99.99, 109.99],
"sku_weights": [100, 150, 200],
"sku_weight_units": ["g", "g", "g"],
"total_inventory": 500,
"option1_name": "color",
"option2_name": "size",
"option3_name": null,
"specifications": [
{"sku_id": "sku_001", "name": "color", "value": "pink"},
{"sku_id": "sku_001", "name": "size", "value": "standard"}
],
"skus": [
{
"sku_id": "67890",
"price": 89.99,
"compare_at_price": 129.99,
"sku": "BARBIE-001",
"stock": 100,
"weight": 0.1,
"weight_unit": "kg",
"option1_value": "pink",
"option2_value": "standard",
"option3_value": null,
"image_src": "https://example.com/sku1.jpg"
}
],
"relevance_score": 8.5
}
],
"total": 118,
"max_score": 8.5,
"facets": [
{
"field": "category1_name",
"label": "category1_name",
"type": "terms",
"values": [
{
"value": "玩具",
"label": "玩具",
"count": 85,
"selected": false
}
]
},
{
"field": "specifications.color",
"label": "color",
"type": "terms",
"values": [
{
"value": "pink",
"label": "pink",
"count": 30,
"selected": false
}
]
}
],
"query_info": {
"original_query": "芭比娃娃",
"query_normalized": "芭比娃娃",
"rewritten_query": "芭比娃娃",
"detected_language": "zh",
"translations": {
"en": "barbie doll"
},
"domain": "default"
},
"suggestions": [],
"related_searches": [],
"took_ms": 45,
"performance_info": null,
"debug_info": null
}
4.2 响应字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
results |
array | 搜索结果列表(SpuResult对象数组) |
results[].spu_id |
string | SPU ID |
results[].title |
string | 商品标题 |
results[].price |
float | 价格(min_price) |
results[].skus |
array | SKU列表(如果指定了sku_filter_dimension,则按维度过滤后的SKU) |
results[].relevance_score |
float | 相关性分数 |
total |
integer | 匹配的总文档数 |
max_score |
float | 最高相关性分数 |
facets |
array | 分面统计结果 |
query_info |
object | query处理信息 |
took_ms |
integer | 搜索耗时(毫秒) |
4.2.1 query_info 说明
query_info 包含本次搜索的查询解析与处理结果:
| 子字段 | 类型 | 说明 |
|---|---|---|
original_query |
string | 用户原始查询 |
query_normalized |
string | 归一化后的查询(去空白、大小写等预处理,用于后续解析与改写) |
rewritten_query |
string | 重写后的查询(同义词/词典扩展等) |
detected_language |
string | 检测到的查询语言(如 zh、en) |
translations |
object | 翻译结果,键为语言代码,值为翻译文本 |
domain |
string | 查询域(如 default、title、brand 等) |
4.3 SpuResult字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
spu_id |
string | SPU ID |
title |
string | 商品标题(根据language参数自动选择 title.zh 或 title.en) |
brief |
string | 商品短描述(根据language参数自动选择) |
description |
string | 商品详细描述(根据language参数自动选择) |
vendor |
string | 供应商/品牌(根据language参数自动选择) |
category |
string | 类目(兼容字段,等同于category_name) |
category_path |
string | 类目路径(多级,用于面包屑,根据language参数自动选择) |
category_name |
string | 类目名称(展示用,根据language参数自动选择) |
category_id |
string | 类目ID |
category_level |
integer | 类目层级(1/2/3) |
category1_name |
string | 一级类目名称 |
category2_name |
string | 二级类目名称 |
category3_name |
string | 三级类目名称 |
tags |
array[string] | 标签列表 |
price |
float | 价格(min_price) |
compare_at_price |
float | 原价 |
currency |
string | 货币单位(默认USD) |
image_url |
string | 主图URL |
in_stock |
boolean | 是否有库存(任意SKU有库存即为true) |
sku_prices |
array[float] | 所有SKU价格列表 |
sku_weights |
array[integer] | 所有SKU重量列表 |
sku_weight_units |
array[string] | 所有SKU重量单位列表 |
total_inventory |
integer | 总库存 |
sales |
integer | 销量(展示销量) |
option1_name |
string | 选项1名称(如"color") |
option2_name |
string | 选项2名称(如"size") |
option3_name |
string | 选项3名称 |
specifications |
array[object] | 规格列表(与ES specifications字段对应) |
skus |
array | SKU 列表 |
relevance_score |
float | 相关性分数(默认为 ES 原始分数;当开启 AI 搜索时为融合后的最终分数) |
4.4 SkuResult字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
sku_id |
string | SKU ID |
price |
float | 价格 |
compare_at_price |
float | 原价 |
sku |
string | SKU编码(sku_code) |
stock |
integer | 库存数量 |
weight |
float | 重量 |
weight_unit |
string | 重量单位 |
option1_value |
string | 选项1取值(如color值) |
option2_value |
string | 选项2取值(如size值) |
option3_value |
string | 选项3取值 |
image_src |
string | SKU图片地址 |
4.5 多语言字段说明
title,brief,description,vendor,category_path,category_name会根据请求的language参数自动选择对应的中英文字段language="zh": 优先返回*_zh字段,如果为空则回退到*_en字段language="en": 优先返回*_en字段,如果为空则回退到*_zh字段
索引接口
本节内容与 api/routes/indexer.py 中的索引相关服务一致,包含以下接口:
| 接口 | 方法 | 路径 | 说明 |
|---|---|---|---|
| 全量重建索引 | POST | /indexer/reindex |
将指定租户所有 SPU 导入 ES(不删现有索引) |
| 增量索引 | POST | /indexer/index |
按 SPU ID 列表索引/删除,支持自动检测删除与显式删除 |
| 查询文档 | POST | /indexer/documents |
按 SPU ID 列表查询 ES 文档,不写入 ES |
| 构建 ES 文档(正式) | POST | /indexer/build-docs |
由上游提供 MySQL 行数据,返回 ES-ready 文档,不写 ES |
| 构建 ES 文档(测试) | POST | /indexer/build-docs-from-db |
由本服务查库并构建文档,仅测试/调试用 |
| 内容理解字段生成 | POST | /indexer/enrich-content |
根据商品标题批量生成 qanchors、semantic_attributes、tags(供微服务组合方式使用) |
| 索引健康检查 | GET | /indexer/health |
检查索引服务与数据库连接状态 |
5.0 支撑外部 indexer 的三种方式
本服务对外部 indexer 程序(如 Java 索引系统)提供三种对接方式,可按需选择:
| 方式 | 说明 | 适用场景 |
|---|---|---|
| 1)doc 填充接口 | 调用 POST /indexer/build-docs 或 POST /indexer/build-docs-from-db,由本服务基于 MySQL 行数据构建完整 ES 文档(含多语言、向量、规格等),不写入 ES,由调用方自行写入。 |
希望一站式拿到 ES-ready doc,由己方控制写 ES 的时机与索引名。 |
| 2)微服务组合 | 单独调用翻译、向量化、内容理解字段生成等接口,由 indexer 程序自己组装 doc 并写入 ES。翻译与向量化为独立微服务(见第 7 节);内容理解为 Indexer 服务内接口 POST /indexer/enrich-content。 |
需要灵活编排、或希望将 LLM/向量等耗时步骤与主链路解耦(如异步补齐 qanchors/tags)。 |
| 3)本服务直接写 ES | 调用全量索引 POST /indexer/reindex、增量索引 POST /indexer/index(指定 SPU ID 列表),由本服务从 MySQL 拉数并直接写入 ES。 |
自建运维、联调或不需要由 Java 写 ES 的场景。 |
- 方式 1 与 方式 2 下,ES 的写入方均为外部 indexer(或 Java),职责清晰。
- 方式 3 下,本服务同时负责读库、构建 doc 与写 ES。
5.1 为租户创建索引
为租户创建索引需要两个步骤:
创建索引结构(可选,仅在需要更新 mapping 或在新环境首次创建时执行)
- 使用脚本创建 ES 索引结构(基于
mappings/search_products.json) - 如果索引已存在,会提示用户确认(会删除现有数据)
- 使用脚本创建 ES 索引结构(基于
导入数据(必需)
- 使用全量索引接口
/indexer/reindex导入数据
- 使用全量索引接口
创建索引结构(支持多环境 namespace):
# 以 UAT 环境为例:
# 1. 准备 UAT 环境的 .env(包含 UAT 的 ES_HOST/DB_HOST 等)
# 2. 设置环境前缀(也可以直接在 .env 中配置):
export RUNTIME_ENV=uat
export ES_INDEX_NAMESPACE=uat_
# 3. 为 tenant_id=170 创建索引结构
./scripts/create_tenant_index.sh 170
脚本会自动从项目根目录的 .env 文件加载 ES 配置,并根据 ES_INDEX_NAMESPACE 创建:
- prod 环境(ES_INDEX_NAMESPACE 为空):
search_products_tenant_170 - UAT 环境(ES_INDEX_NAMESPACE=uat_):
uat_search_products_tenant_170
注意事项:
- ⚠️ 如果索引已存在,脚本会提示确认,确认后会删除现有数据
- 创建索引后,必须调用
/indexer/reindex导入数据 - 如果只是更新数据而不需要修改索引结构,直接使用
/indexer/reindex即可
5.2 全量索引接口
- 端点:
POST /indexer/reindex - 描述: 全量索引,将指定租户的所有SPU数据导入到ES索引(不会删除现有索引)。推荐仅用于自测/运维场景;生产环境下更推荐由 Java 等上游控制调度与写 ES。
请求参数
{
"tenant_id": "162",
"batch_size": 500
}
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
tenant_id |
string | Y | - | 租户ID |
batch_size |
integer | N | 500 | 批量导入大小 |
响应格式
成功响应(200 OK)(示例,实际 index_name 会带上 tenant 和环境前缀):
{
"success": true,
"total": 1000,
"indexed": 1000,
"failed": 0,
"elapsed_time": 12.34,
"index_name": "search_products_tenant_162",
"tenant_id": "162"
}
错误响应:
400 Bad Request: 参数错误503 Service Unavailable: 服务未初始化
请求示例
全量索引(不会删除现有索引):
curl -X POST "http://localhost:6004/indexer/reindex" \
-H "Content-Type: application/json" \
-d '{
"tenant_id": "162",
"batch_size": 500
}'
查看日志:
# 查看API日志(包含索引操作日志)
tail -f logs/api.log
# 或者查看所有日志文件
tail -f logs/*.log
⚠️ 重要提示:如需 创建索引结构,请参考 5.1 为租户创建索引 章节,使用
./scripts/create_tenant_index.sh <tenant_id>。创建后需要调用/indexer/reindex导入数据。
查看索引日志:
索引操作的所有关键信息都会记录到 logs/indexer.log 文件中(JSON 格式),包括:
- 请求开始和结束时间
- 租户ID、SPU ID、操作类型
- 每个SPU的处理状态
- ES批量写入结果
- 成功/失败统计和详细错误信息
# 实时查看索引日志(包含全量和增量索引的所有操作)
tail -f logs/indexer.log
# 使用 grep 查询(简单方式)
# 查看全量索引日志
grep "\"index_type\":\"bulk\"" logs/indexer.log | tail -100
# 查看增量索引日志
grep "\"index_type\":\"incremental\"" logs/indexer.log | tail -100
# 查看特定租户的索引日志
grep "\"tenant_id\":\"162\"" logs/indexer.log | tail -100
# 使用 jq 查询(推荐,更精确的 JSON 查询)
# 安装 jq: sudo apt-get install jq 或 brew install jq
# 查看全量索引日志
cat logs/indexer.log | jq 'select(.index_type == "bulk")' | tail -100
# 查看增量索引日志
cat logs/indexer.log | jq 'select(.index_type == "incremental")' | tail -100
# 查看特定租户的索引日志
cat logs/indexer.log | jq 'select(.tenant_id == "162")' | tail -100
# 查看失败的索引操作
cat logs/indexer.log | jq 'select(.operation == "request_complete" and .failed_count > 0)'
# 查看特定SPU的处理日志
cat logs/indexer.log | jq 'select(.spu_id == "123")'
# 查看最近的索引请求统计
cat logs/indexer.log | jq 'select(.operation == "request_complete") | {timestamp, index_type, tenant_id, total_count, success_count, failed_count, elapsed_time}'
5.3 增量索引接口
- 端点:
POST /indexer/index - 描述: 增量索引接口,根据指定的SPU ID列表进行索引,直接将数据写入ES。用于增量更新指定商品。推荐仅作为内部/调试入口;正式对接建议改用
/indexer/build-docs,由上游写 ES。
删除说明:
spu_ids中的SPU:如果数据库deleted=1,自动从ES删除,响应状态为deleteddelete_spu_ids中的SPU:直接删除,响应状态为deleted、not_found或failed
请求参数
{
"tenant_id": "162",
"spu_ids": ["123", "456", "789"],
"delete_spu_ids": ["100", "101"]
}
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
tenant_id |
string | Y | 租户ID |
spu_ids |
array[string] | N | SPU ID列表(1-100个),要索引的SPU。如果为空,则只执行删除操作 |
delete_spu_ids |
array[string] | N | 显式指定要删除的SPU ID列表(1-100个),可选。无论数据库状态如何,都会从ES中删除这些SPU |
注意:
spu_ids和delete_spu_ids不能同时为空- 每个列表最多支持100个SPU ID
- 如果SPU在
spu_ids中且数据库deleted=1,会自动从ES删除(自动检测删除)
响应格式
{
"spu_ids": [
{
"spu_id": "123",
"status": "indexed"
},
{
"spu_id": "456",
"status": "deleted"
},
{
"spu_id": "789",
"status": "failed",
"msg": "SPU not found (unexpected)"
}
],
"delete_spu_ids": [
{
"spu_id": "100",
"status": "deleted"
},
{
"spu_id": "101",
"status": "not_found"
},
{
"spu_id": "102",
"status": "failed",
"msg": "Failed to delete from ES: Connection timeout"
}
],
"total": 6,
"success_count": 4,
"failed_count": 2,
"elapsed_time": 1.23,
"index_name": "search_products",
"tenant_id": "162"
}
| 字段 | 类型 | 说明 |
|---|---|---|
spu_ids |
array | spu_ids对应的响应列表,每个元素包含 spu_id 和 status |
spu_ids[].status |
string | 状态:indexed(已索引)、deleted(已删除,自动检测)、failed(失败) |
spu_ids[].msg |
string | 当status为failed时,包含失败原因(可选) |
delete_spu_ids |
array | delete_spu_ids对应的响应列表,每个元素包含 spu_id 和 status |
delete_spu_ids[].status |
string | 状态:deleted(已删除)、not_found(ES中不存在)、failed(失败) |
delete_spu_ids[].msg |
string | 当status为failed时,包含失败原因(可选) |
total |
integer | 总处理数量(spu_ids数量 + delete_spu_ids数量) |
success_count |
integer | 成功数量(indexed + deleted + not_found) |
failed_count |
integer | 失败数量 |
elapsed_time |
float | 耗时(秒) |
index_name |
string | 索引名称 |
tenant_id |
string | 租户ID |
状态说明:
spu_ids的状态:indexed: SPU已成功索引到ESdeleted: SPU在数据库中被标记为deleted=1,已从ES删除(自动检测)failed: 处理失败,会包含msg字段说明失败原因
delete_spu_ids的状态:deleted: SPU已从ES成功删除not_found: SPU在ES中不存在(也算成功,可能已经被删除过)failed: 删除失败,会包含msg字段说明失败原因
请求示例
示例1:普通增量索引(自动检测删除):
curl -X POST "http://localhost:6004/indexer/index" \
-H "Content-Type: application/json" \
-d '{
"tenant_id": "162",
"spu_ids": ["123", "456", "789"]
}'
说明:如果SPU 456在数据库中deleted=1,会自动从ES删除,在响应中spu_ids列表里456的状态为deleted。
示例2:显式删除(批量删除):
curl -X POST "http://localhost:6004/indexer/index" \
-H "Content-Type: application/json" \
-d '{
"tenant_id": "162",
"spu_ids": ["123", "456"],
"delete_spu_ids": ["100", "101", "102"]
}'
说明:SPU 100、101、102会被显式删除,无论数据库状态如何。
示例3:仅删除(不索引):
curl -X POST "http://localhost:6004/indexer/index" \
-H "Content-Type: application/json" \
-d '{
"tenant_id": "162",
"spu_ids": [],
"delete_spu_ids": ["100", "101"]
}'
说明:只执行删除操作,不进行索引。
示例4:混合操作(索引+删除):
curl -X POST "http://localhost:6004/indexer/index" \
-H "Content-Type: application/json" \
-d '{
"tenant_id": "162",
"spu_ids": ["123", "456", "789"],
"delete_spu_ids": ["100", "101"]
}'
说明:同时执行索引和删除操作。
日志说明
增量索引操作的所有关键信息都会记录到 logs/indexer.log 文件中(JSON格式),包括:
- 请求开始和结束时间
- 每个SPU的处理状态(获取、转换、索引、删除)
- ES批量写入结果
- 成功/失败统计
- 详细的错误信息
日志查询方式请参考5.1节查看索引日志部分。
5.4 查询文档接口
- 端点:
POST /indexer/documents - 描述: 查询文档接口,根据SPU ID列表获取ES文档数据(不写入ES)。用于查看、调试或验证SPU数据。
请求参数
{
"tenant_id": "162",
"spu_ids": ["123", "456", "789"]
}
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
tenant_id |
string | Y | 租户ID |
spu_ids |
array[string] | Y | SPU ID列表(1-100个) |
响应格式
{
"success": [
{
"spu_id": "123",
"document": {
"tenant_id": "162",
"spu_id": "123",
"title": {
"zh": "商品标题"
},
...
}
},
{
"spu_id": "456",
"document": {...}
}
],
"failed": [
{
"spu_id": "789",
"error": "SPU not found or deleted"
}
],
"total": 3,
"success_count": 2,
"failed_count": 1
}
| 字段 | 类型 | 说明 |
|---|---|---|
success |
array | 成功获取的SPU列表,每个元素包含 spu_id 和 document(完整的ES文档数据) |
failed |
array | 失败的SPU列表,每个元素包含 spu_id 和 error(失败原因) |
total |
integer | 总SPU数量 |
success_count |
integer | 成功数量 |
failed_count |
integer | 失败数量 |
请求示例
单个SPU查询:
curl -X POST "http://localhost:6004/indexer/documents" \
-H "Content-Type: application/json" \
-d '{
"tenant_id": "162",
"spu_ids": ["123"]
}'
批量SPU查询:
curl -X POST "http://localhost:6004/indexer/documents" \
-H "Content-Type: application/json" \
-d '{
"tenant_id": "162",
"spu_ids": ["123", "456", "789"]
}'
与 /indexer/index 的区别
| 接口 | 功能 | 是否写入ES | 返回内容 |
|---|---|---|---|
/indexer/documents |
查询SPU文档数据 | 否 | 返回完整的ES文档数据 |
/indexer/index |
增量索引 | 是 | 返回成功/失败列表和统计信息 |
使用场景:
/indexer/documents:用于查看、调试或验证SPU数据,不修改ES索引/indexer/index:用于实际的增量索引操作,将更新的SPU数据同步到ES
5.5 索引健康检查接口
- 端点:
GET /indexer/health - 描述: 检查索引服务健康状态(与
api/routes/indexer.py中indexer_health_check一致)
响应格式
{
"status": "available",
"database": "connected",
"preloaded_data": {
"category_mappings": 150
}
}
| 字段 | 类型 | 说明 |
|---|---|---|
status |
string | available(服务可用)、unavailable(未初始化)、error(异常) |
database |
string | 数据库连接状态,如 connected 或 disconnected: ... |
preloaded_data.category_mappings |
integer | 已加载的分类映射数量 |
请求示例
curl -X GET "http://localhost:6004/indexer/health"
5.6 文档构建接口(正式对接推荐)
5.6.1 POST /indexer/build-docs
- 描述:
基于调用方(通常是 Java 索引程序)提供的 MySQL 行数据 构建 ES 文档(doc),不写入 ES。
由本服务负责“如何构建 doc”(多语言、翻译、向量、规格聚合等),由调用方负责“何时调度 + 如何写 ES”。
请求参数
{
"tenant_id": "170",
"items": [
{
"spu": { "id": 223167, "tenant_id": 170, "title": "..." },
"skus": [
{ "id": 3988393, "spu_id": 223167, "price": 25.99, "compare_at_price": 25.99 }
],
"options": []
}
]
}
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
tenant_id |
string | Y | 租户 ID |
items |
array | Y | 需构建 doc 的 SPU 列表(每项含 spu、skus、options),单次最多 200 条 |
spu/skus/options字段应当直接使用从shoplazza_product_spu/shoplazza_product_sku/shoplazza_product_option查询出的行字段。
请求示例(完整 curl)
完整请求体参考
scripts/test_build_docs_api.py中的build_sample_request()。
# 单条 SPU 示例(含 spu、skus、options)
curl -X POST "http://localhost:6004/indexer/build-docs" \
-H "Content-Type: application/json" \
-d '{
"tenant_id": "162",
"items": [
{
"spu": {
"id": 10001,
"tenant_id": "162",
"title": "测试T恤 纯棉短袖",
"brief": "舒适纯棉,多色可选",
"description": "这是一款适合日常穿着的纯棉T恤,透气吸汗。",
"vendor": "测试品牌",
"category": "服装/上衣/T恤",
"category_id": 100,
"category_level": 2,
"category_path": "服装/上衣/T恤",
"fake_sales": 1280,
"image_src": "https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg",
"tags": "T恤,纯棉,短袖,夏季",
"create_time": "2024-01-01T00:00:00Z",
"update_time": "2024-01-01T00:00:00Z"
},
"skus": [
{
"id": 20001,
"spu_id": 10001,
"price": 99.0,
"compare_at_price": 129.0,
"sku": "SKU-TSHIRT-001",
"inventory_quantity": 50,
"option1": "黑色",
"option2": "M",
"option3": null
},
{
"id": 20002,
"spu_id": 10001,
"price": 99.0,
"compare_at_price": 129.0,
"sku": "SKU-TSHIRT-002",
"inventory_quantity": 30,
"option1": "白色",
"option2": "L",
"option3": null
}
],
"options": [
{"id": 1, "position": 1, "name": "颜色"},
{"id": 2, "position": 2, "name": "尺码"}
]
}
]
}'
生产环境替换 localhost:6004 为实际 Indexer 地址,如 http://43.166.252.75:6004。
响应示例(节选)
{
"tenant_id": "170",
"docs": [
{
"tenant_id": "170",
"spu_id": "223167",
"title": { "en": "...", "zh": "..." },
"tags": ["Floerns", "Clothing", "Shoes & Jewelry"],
"skus": [
{
"sku_id": "3988393",
"price": 25.99,
"compare_at_price": 25.99,
"stock": 100
}
],
"min_price": 25.99,
"max_price": 25.99,
"compare_at_price": 25.99,
"total_inventory": 100,
"title_embedding": [/* 1024 维向量 */]
// 其余字段与 mappings/search_products.json 一致
}
],
"total": 1,
"success_count": 1,
"failed_count": 0,
"failed": []
}
| 字段 | 类型 | 说明 |
|---|---|---|
tenant_id |
string | 租户 ID |
docs |
array | 构建成功的 ES 文档列表,与 mappings/search_products.json 一致 |
total |
integer | 请求的 items 总数 |
success_count |
integer | 成功构建数量 |
failed_count |
integer | 失败数量 |
failed |
array | 失败项列表,每项含 spu_id、error |
使用建议
- 生产环境推荐流程:
- Java 根据业务逻辑决定哪些 SPU 需要(全量/增量)处理;
- Java 从 MySQL 查询 SPU/SKU/Option 行,拼成
items; - 调用
/indexer/build-docs获取 ES-readydocs; - Java 使用自己的 ES 客户端写入
search_products_tenant_{tenant_id}。
5.7 文档构建接口(测试 / 自测)
5.7.1 POST /indexer/build-docs-from-db
- 描述:
仅用于测试/调试:调用方只提供tenant_id和spu_ids,由 indexer 服务内部从 MySQL 查询 SPU/SKU/Option,然后调用与/indexer/build-docs相同的文档构建逻辑,返回 ES-ready doc。生产环境请使用/indexer/build-docs,由上游查库并写 ES。
请求参数
{
"tenant_id": "170",
"spu_ids": ["223167", "223168"]
}
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
tenant_id |
string | Y | 租户 ID |
spu_ids |
array[string] | Y | SPU ID 列表,单次最多 200 个 |
响应格式
与 /indexer/build-docs 相同:tenant_id、docs、total、success_count、failed_count、failed。
请求示例
curl -X POST "http://127.0.0.1:6004/indexer/build-docs-from-db" \
-H "Content-Type: application/json" \
-d '{"tenant_id": "170", "spu_ids": ["223167"]}'
返回结构与 /indexer/build-docs 相同,可直接用于对比 ES 实际文档或调试字段映射问题。
5.8 内容理解字段生成接口
- 端点:
POST /indexer/enrich-content - 描述: 根据商品内容信息批量生成 qanchors(锚文本)、semantic_attributes(语义属性)、tags(细分标签),供外部 indexer 在「微服务组合」方式下自行拼装 doc 时使用。请求以
items[]传入商品内容字段(必填/可选见下表)。内部逻辑与indexer.process_products一致,支持多语言与 Redis 缓存;单次请求在线程池中执行,避免阻塞其他接口。
请求参数
{
"tenant_id": "170",
"items": [
{
"spu_id": "223167",
"title": "纯棉短袖T恤 夏季男装",
"brief": "夏季透气纯棉短袖,舒适亲肤",
"description": "100%棉,圆领版型,适合日常通勤与休闲穿搭。",
"image_url": "https://example.com/images/223167.jpg"
},
{
"spu_id": "223168",
"title": "12PCS Dolls with Bottles",
"image_url": "https://example.com/images/223168.jpg"
}
],
"languages": ["zh", "en"]
}
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
tenant_id |
string | Y | - | 租户 ID,用于缓存隔离 |
items |
array | Y | - | 待分析列表;单次最多 50 条 |
languages |
array[string] | N | ["zh", "en"] |
目标语言,需在支持范围内:zh、en、de、ru、fr |
items[] 字段说明:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
spu_id |
string | Y | SPU ID,用于回填结果与缓存键 |
title |
string | Y | 商品标题 |
image_url |
string | N | 商品主图 URL(预留:后续可用于图像/多模态内容理解) |
brief |
string | N | 商品简介/短描述(预留) |
description |
string | N | 商品详情/长描述(预留) |
批量请求建议:
- 全量:强烈建议 尽可能 20 个 SPU/doc 攒成一个批次后再请求一次。
- 增量:可按时效要求设置时间窗口(例如 5 分钟),在窗口内尽可能攒到 20 个;达到 20 或窗口到期就发送一次请求。
- 允许超过20,服务内部会拆分成小批次逐个处理。也允许小于20,但是将造成费用和耗时的成本上升,特别是每次请求一个doc的情况。
响应格式
{
"tenant_id": "170",
"total": 2,
"results": [
{
"spu_id": "223167",
"qanchors": {
"zh": "短袖T恤,纯棉,男装,夏季",
"en": "cotton t-shirt, short sleeve, men, summer"
},
"semantic_attributes": [
{ "lang": "zh", "name": "tags", "value": "纯棉" },
{ "lang": "zh", "name": "usage_scene", "value": "日常" },
{ "lang": "en", "name": "tags", "value": "cotton" }
],
"tags": ["纯棉", "短袖", "男装", "cotton", "short sleeve"]
},
{
"spu_id": "223168",
"qanchors": { "en": "dolls, toys, 12pcs" },
"semantic_attributes": [],
"tags": ["dolls", "toys"]
}
]
}
| 字段 | 类型 | 说明 |
|---|---|---|
results |
array | 与请求 items 一一对应,每项含 spu_id、qanchors、semantic_attributes、tags |
results[].qanchors |
object | 按语言键的锚文本(逗号分隔短语),可写入 ES 文档的 qanchors.{lang} |
results[].semantic_attributes |
array | 语义属性列表,每项为 { "lang", "name", "value" },可写入 ES 的 semantic_attributes nested 字段 |
results[].tags |
array | 从语义属性中抽取的 name=tags 的 value 集合,可与业务原有 tags 合并后写入 ES 的 tags 字段 |
results[].error |
string | 若该条处理失败(如 LLM 异常),会在此字段返回错误信息 |
错误响应:
400:items为空或超过 50 条503: 未配置DASHSCOPE_API_KEY,内容理解服务不可用
请求示例
curl -X POST "http://localhost:6004/indexer/enrich-content" \
-H "Content-Type: application/json" \
-d '{
"tenant_id": "170",
"items": [
{
"spu_id": "223167",
"title": "纯棉短袖T恤 夏季男装",
"brief": "夏季透气纯棉短袖,舒适亲肤",
"description": "100%棉,圆领版型,适合日常通勤与休闲穿搭。",
"image_url": "https://example.com/images/223167.jpg"
}
],
"languages": ["zh", "en"]
}'
管理接口
6.1 健康检查
- 端点:
GET /admin/health - 描述: 检查服务与依赖(如 Elasticsearch)状态。
{
"status": "healthy",
"elasticsearch": "connected",
"tenant_id": "tenant1"
}
6.2 获取配置
- 端点:
GET /admin/config - 描述: 返回当前租户的脱敏配置,便于核对索引及排序表达式。
{
"tenant_id": "tenant1",
"tenant_name": "Tenant1 Test Instance",
"es_index_name": "search_tenant1",
"num_fields": 20,
"num_indexes": 4,
"supported_languages": ["zh", "en", "ru"],
"spu_enabled": false
}
6.3 索引统计
- 端点:
GET /admin/stats - 描述: 获取指定租户索引文档数量与磁盘大小,方便监控。
- 租户标识:通过请求头
X-Tenant-ID或 query 参数tenant_id传递(必填)。
{
"tenant_id": "162",
"index_name": "search_products_tenant_162",
"document_count": 10000,
"size_mb": 523.45
}
7. 微服务接口(向量、重排、翻译)
以下三个微服务独立部署,外部系统可直接调用。它们被搜索后端(6002)和索引服务(6004)内部使用,也可供其他业务系统直接对接。
| 服务 | 默认端口 | Base URL | 说明 |
|---|---|---|---|
| 向量服务 | 6005 | http://localhost:6005 |
文本/图片向量化,用于语义搜索与以图搜图 |
| 翻译服务 | 6006 | http://localhost:6006 |
多语言翻译(Qwen/DeepL) |
| 重排服务 | 6007 | http://localhost:6007 |
对检索结果进行二次排序 |
生产环境请将 localhost 替换为实际服务地址。
服务管理入口与完整启停规则见:docs/Usage-Guide.md -> 服务管理总览。
7.1 向量服务(Embedding)
- Base URL:
http://localhost:6005(可通过EMBEDDING_SERVICE_URL覆盖) - 启动:
./scripts/start_embedding_service.sh - 依赖:
- 文本向量后端默认走 TEI(
http://127.0.0.1:8080) - 图片向量依赖
cnclip(grpc://127.0.0.1:51000) - TEI 默认使用 GPU(
TEI_DEVICE=cuda);当配置为 GPU 且不可用时会启动失败(不会自动降级到 CPU) - cnclip 默认使用
cuda;若配置为cuda但 GPU 不可用会启动失败(不会自动降级到cpu)
- 文本向量后端默认走 TEI(
7.1.1 POST /embed/text — 文本向量化
将文本列表转为 1024 维向量,用于语义搜索、文档索引等。
请求体(JSON 数组):
["文本1", "文本2", "文本3"]
响应(JSON 数组,与输入一一对应):
[[0.01, -0.02, ...], [0.03, 0.01, ...], ...]
完整 curl 示例:
curl -X POST "http://localhost:6005/embed/text" \
-H "Content-Type: application/json" \
-d '["芭比娃娃 儿童玩具", "纯棉T恤 短袖"]'
7.1.2 POST /embed/image — 图片向量化
将图片 URL 或路径转为向量,用于以图搜图。
前置条件:cnclip 服务已启动(默认端口 51000)。若未启动,/embed/image 会返回 500。
请求体(JSON 数组):
["https://example.com/image1.jpg", "https://example.com/image2.jpg"]
响应(JSON 数组,与输入一一对应):
[[0.01, -0.02, ...], [0.03, 0.01, ...], ...]
完整 curl 示例:
curl -X POST "http://localhost:6005/embed/image" \
-H "Content-Type: application/json" \
-d '["https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg"]'
7.1.3 GET /health — 健康检查
curl "http://localhost:6005/health"
7.1.4 TEI 统一调优建议(主服务)
使用单套主服务即可同时兼顾:
- 在线 query 向量化(低延迟,常见
batch=1~4) - 索引构建向量化(高吞吐,常见
batch=15~20)
统一启动(主链路):
./scripts/start_tei_service.sh
./scripts/service_ctl.sh restart embedding
默认端口:
- TEI:
http://127.0.0.1:8080 - 向量服务(
/embed/text):http://127.0.0.1:6005
当前主 TEI 启动默认值(已按 T4/短文本场景调优):
TEI_MAX_BATCH_TOKENS=4096TEI_MAX_CLIENT_BATCH_SIZE=24TEI_DTYPE=float16
7.2 重排服务(Reranker)
- Base URL:
http://localhost:6007(可通过RERANKER_SERVICE_URL覆盖) - 启动:
./scripts/start_reranker.sh
说明:默认后端为 qwen3_vllm(Qwen/Qwen3-Reranker-0.6B),需要可用 GPU 显存。
补充:docs 的请求大小与模型推理 batch size 解耦。即使一次传入 1000 条文档,服务端也会按 services.rerank.backends.qwen3_vllm.infer_batch_size 自动拆分;若 sort_by_doc_length=true,会先按文档长度排序后分批,减少 padding,再按原输入顺序返回分数。length_sort_mode 可选 char(更快)或 token(更精确)。
7.2.1 POST /rerank — 结果重排
根据 query 与 doc 的相关性对文档列表重新打分排序。
请求体:
{
"query": "玩具 芭比",
"docs": [
"12PCS 6 Types of Dolls with Bottles",
"纯棉T恤 短袖 夏季"
],
"normalize": true
}
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
query |
string | Y | 搜索查询 |
docs |
array[string] | Y | 待重排的文档列表(单次最多由服务端配置限制) |
normalize |
boolean | N | 是否对分数做 sigmoid 归一化,默认 true |
响应:
{
"scores": [0.92, 0.15],
"meta": {
"service_elapsed_ms": 45.2,
"input_docs": 2,
"unique_docs": 2
}
}
完整 curl 示例:
curl -X POST "http://localhost:6007/rerank" \
-H "Content-Type: application/json" \
-d '{
"query": "玩具 芭比",
"docs": ["12PCS 6 Types of Dolls with Bottles", "纯棉T恤 短袖"],
"top_n":386,
"normalize": true
}'
7.2.2 GET /health — 健康检查
curl "http://localhost:6007/health"
7.3 翻译服务(Translation)
- Base URL:
http://localhost:6006(可通过TRANSLATION_SERVICE_URL覆盖) - 启动:
./scripts/start_translator.sh
7.3.1 POST /translate — 文本翻译
支持 Qwen(默认)与 DeepL 模型,适用于商品名称、描述等电商场景。
请求体:
{
"text": "商品名称",
"target_lang": "en",
"source_lang": "zh",
"model": "qwen"
}
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
text |
string | Y | 待翻译文本 |
target_lang |
string | Y | 目标语言:zh、en、ru 等 |
source_lang |
string | N | 源语言,不传则自动检测 |
model |
string | N | qwen(默认)或 deepl |
响应:
{
"text": "商品名称",
"target_lang": "en",
"source_lang": "zh",
"translated_text": "Product name",
"status": "success",
"model": "qwen"
}
完整 curl 示例:
中文 → 英文:
curl -X POST "http://localhost:6006/translate" \
-H "Content-Type: application/json" \
-d '{
"text": "商品名称",
"target_lang": "en",
"source_lang": "zh"
}'
俄文 → 英文:
curl -X POST "http://localhost:6006/translate" \
-H "Content-Type: application/json" \
-d '{
"text": "Название товара",
"target_lang": "en",
"source_lang": "ru"
}'
使用 DeepL 模型:
curl -X POST "http://localhost:6006/translate" \
-H "Content-Type: application/json" \
-d '{
"text": "商品名称",
"target_lang": "en",
"source_lang": "zh",
"model": "deepl"
}'
7.3.2 GET /health — 健康检查
curl "http://localhost:6006/health"
7.4 内容理解字段生成(Indexer 服务内)
内容理解字段生成接口部署在 Indexer 服务(默认端口 6004)内,与「翻译、向量化」等独立端口微服务并列,供采用微服务组合方式的 indexer 调用。
- Base URL: Indexer 服务地址,如
http://localhost:6004 - 路径:
POST /indexer/enrich-content - 说明: 根据商品标题批量生成
qanchors、semantic_attributes、tags,用于拼装 ES 文档。内部使用大模型(需配置DASHSCOPE_API_KEY),支持多语言与 Redis 缓存;单次最多 50 条,建议批量调用以提升效率。
请求/响应格式、示例及错误码见 5.8 内容理解字段生成接口。
8. 常见场景示例
以下示例仅展示请求体(body);实际调用时请加上请求头 X-Tenant-ID: <租户ID>(或 URL 参数 tenant_id),参见 3.1 接口信息。
8.1 基础搜索与排序
按价格从低到高排序:
{
"query": "玩具",
"size": 20,
"from": 0,
"sort_by": "price",
"sort_order": "asc"
}
按价格从高到低排序:
{
"query": "玩具",
"size": 20,
"from": 0,
"sort_by": "price",
"sort_order": "desc"
}
按销量从高到低排序:
{
"query": "玩具",
"size": 20,
"from": 0,
"sort_by": "sales",
"sort_order": "desc"
}
按默认(相关性)排序:
{
"query": "玩具",
"size": 20,
"from": 0
}
8.2 过滤搜索
需求: 搜索"玩具",筛选类目为"益智玩具",价格在50-200之间
{
"query": "玩具",
"size": 20,
"language": "zh",
"filters": {
"category_name": "益智玩具"
},
"range_filters": {
"min_price": {
"gte": 50,
"lte": 200
}
}
}
需求: 搜索"手机",筛选多个品牌,价格范围
{
"query": "手机",
"size": 20,
"language": "zh",
"filters": {
"vendor.zh.keyword": ["品牌A", "品牌B"]
},
"range_filters": {
"min_price": {
"gte": 50,
"lte": 200
}
}
}
8.3 分面搜索
需求: 搜索"玩具",获取类目和规格的分面统计,用于构建筛选器
{
"query": "玩具",
"size": 20,
"language": "zh",
"facets": [
{"field": "category1_name", "size": 15, "type": "terms"},
{"field": "category2_name", "size": 10, "type": "terms"},
{"field": "specifications", "size": 10, "type": "terms"}
]
}
需求: 搜索"手机",获取价格区间和规格的分面统计
{
"query": "手机",
"size": 20,
"language": "zh",
"facets": [
{
"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": "specifications",
"size": 10,
"type": "terms"
}
]
}
8.4 规格过滤与分面
需求: 搜索"手机",筛选color为"white"的商品
{
"query": "手机",
"size": 20,
"language": "zh",
"filters": {
"specifications": {
"name": "color",
"value": "white"
}
}
}
需求: 搜索"手机",筛选color为"white"且size为"256GB"的商品
{
"query": "手机",
"size": 20,
"language": "zh",
"filters": {
"specifications": [
{"name": "color", "value": "white"},
{"name": "size", "value": "256GB"}
]
}
}
需求: 搜索"手机",筛选size为"3"、"4"或"5",且color为"green"的商品
{
"query": "手机",
"size": 20,
"language": "zh",
"filters": {
"specifications": [
{"name": "size", "value": "3"},
{"name": "size", "value": "4"},
{"name": "size", "value": "5"},
{"name": "color", "value": "green"}
]
}
}
需求: 搜索"手机",获取所有规格的分面统计
{
"query": "手机",
"size": 20,
"language": "zh",
"facets": [
{"field": "specifications", "size": 10, "type": "terms"}
]
}
需求: 只获取"color"和"size"规格的分面统计
{
"query": "手机",
"size": 20,
"language": "zh",
"facets": [
{"field": "specifications.color", "size": 20, "type": "terms"},
{"field": "specifications.size", "size": 15, "type": "terms"}
]
}
需求: 搜索"手机",筛选类目和规格,并获取对应的分面统计
{
"query": "手机",
"size": 20,
"language": "zh",
"filters": {
"category_name": "手机",
"specifications": {
"name": "color",
"value": "white"
}
},
"facets": [
{"field": "category1_name", "size": 15, "type": "terms"},
{"field": "category2_name", "size": 10, "type": "terms"},
{"field": "specifications.color", "size": 20, "type": "terms"},
{"field": "specifications.size", "size": 15, "type": "terms"}
]
}
8.5 SKU筛选
需求: 搜索"芭比娃娃",每个SPU下按颜色筛选,每种颜色只显示一个SKU
{
"query": "芭比娃娃",
"size": 20,
"sku_filter_dimension": ["color"]
}
说明:
- 如果
option1_name为"color",则使用sku_filter_dimension: ["color"]可以按颜色分组 - 每个SPU下,每种颜色只会返回第一个SKU
- 如果维度不匹配,返回所有SKU(不进行过滤)
8.7 分页查询
需求: 获取第2页结果(每页20条)
{
"query": "手机",
"size": 20,
"from": 20
}
9. 数据模型
9.1 商品字段定义
| 字段名 | 类型 | 描述 |
|---|---|---|
tenant_id |
keyword | 租户ID(多租户隔离) |
spu_id |
keyword | SPU ID |
title.<lang> |
object/text | 商品标题(多语言对象,如 title.zh, title.en) |
brief.<lang> |
object/text | 商品短描述(多语言对象,如 brief.zh, brief.en) |
description.<lang> |
object/text | 商品详细描述(多语言对象,如 description.zh, description.en) |
vendor.<lang> |
object/text | 供应商/品牌(多语言对象,且带 keyword 子字段,如 vendor.zh.keyword) |
category_path.<lang> |
object/text | 类目路径(多语言对象,用于搜索,如 category_path.zh) |
category_name_text.<lang> |
object/text | 类目名称(多语言对象,用于搜索,如 category_name_text.zh) |
category_id |
keyword | 类目ID |
category_name |
keyword | 类目名称(用于过滤) |
category_level |
integer | 类目层级 |
category1_name, category2_name, category3_name |
keyword | 多级类目名称(用于过滤和分面) |
tags |
keyword | 标签(数组) |
specifications |
nested | 规格(嵌套对象数组) |
option1_name, option2_name, option3_name |
keyword | 选项名称 |
min_price, max_price |
float | 最低/最高价格 |
compare_at_price |
float | 原价 |
sku_prices |
float | SKU价格列表(数组) |
sku_weights |
long | SKU重量列表(数组) |
sku_weight_units |
keyword | SKU重量单位列表(数组) |
total_inventory |
long | 总库存 |
sales |
long | 销量(展示销量) |
skus |
nested | SKU详细信息(嵌套对象数组) |
create_time, update_time |
date | 创建/更新时间 |
title_embedding |
dense_vector | 标题向量(1024维,仅用于搜索) |
image_embedding |
nested | 图片向量(嵌套,仅用于搜索) |
所有租户共享统一的索引结构。文本字段支持中英文双语,后端根据
language参数自动选择对应字段返回。
9.2 字段类型速查
| 类型 | ES Mapping | 用途 |
|---|---|---|
text |
text |
全文检索(支持中英文分析器) |
keyword |
keyword |
精确匹配、聚合、排序 |
integer |
integer |
整数 |
long |
long |
长整数 |
float |
float |
浮点数 |
date |
date |
日期时间 |
nested |
nested |
嵌套对象(specifications, skus, image_embedding) |
dense_vector |
dense_vector |
向量字段(title_embedding,仅用于搜索) |
9.3 常用字段列表
过滤字段
category_name: 类目名称category1_name,category2_name,category3_name: 多级类目category_id: 类目IDvendor.zh.keyword,vendor.en.keyword: 供应商/品牌(使用keyword子字段)tags: 标签(keyword类型)option1_name,option2_name,option3_name: 选项名称specifications: 规格过滤(嵌套字段,格式见过滤器详解)
范围字段
min_price: 最低价格max_price: 最高价格compare_at_price: 原价create_time: 创建时间update_time: 更新时间
排序字段
price: 价格(后端自动根据sort_order映射:asc→min_price,desc→max_price)sales: 销量create_time: 创建时间update_time: 更新时间relevance_score: 相关性分数(默认,不指定sort_by时使用)
注意: 前端只需传 price,后端会自动处理:
sort_by: "price"+sort_order: "asc"→ 按min_price升序(价格从低到高)sort_by: "price"+sort_order: "desc"→ 按max_price降序(价格从高到低)
9.4 支持的分析器
| 分析器 | 语言 | 描述 |
|---|---|---|
index_ik |
中文 | 中文索引分析器(用于中文字段) |
query_ik |
中文 | 中文查询分析器(用于中文字段) |
hanlp_index ⚠️ TODO(暂不支持) |
中文 | 中文索引分析器(用于中文字段) |
hanlp_standard ⚠️ TODO(暂不支持) |
中文 | 中文查询分析器(用于中文字段) |
english |
英文 | 标准英文分析器(用于英文字段) |
lowercase |
- | 小写标准化器(用于keyword子字段) |
10. 接口级压测脚本
仓库提供统一压测脚本:scripts/perf_api_benchmark.py,用于对以下接口做并发压测:
- 后端搜索:
POST /search/ - 搜索建议:
GET /search/suggestions - 向量服务:
POST /embed/text - 翻译服务:
POST /translate - 重排服务:
POST /rerank
说明:脚本对 embed_text 场景会校验返回向量内容有效性(必须是有限数值,不允许 null/NaN/Inf),不是只看 HTTP 200。
10.1 快速示例
# suggest 压测(tenant 162)
python scripts/perf_api_benchmark.py \
--scenario backend_suggest \
--tenant-id 162 \
--duration 30 \
--concurrency 50
# search 压测
python scripts/perf_api_benchmark.py \
--scenario backend_search \
--tenant-id 162 \
--duration 30 \
--concurrency 20
# 全链路压测(search + suggest + embedding + translate + rerank)
python scripts/perf_api_benchmark.py \
--scenario all \
--tenant-id 162 \
--duration 60 \
--concurrency 30 \
--output perf_reports/all.json
10.2 自定义用例
可通过 --cases-file 覆盖默认请求模板。示例文件:
scripts/perf_cases.json.example
执行示例:
python scripts/perf_api_benchmark.py \
--scenario all \
--tenant-id 162 \
--cases-file scripts/perf_cases.json.example \
--duration 60 \
--concurrency 40