Commit 16204531164db13f666377e654fd3311e38b5234
1 parent
0342d897
docs
Showing
4 changed files
with
233 additions
and
2496 deletions
Show diff stats
docs/TODO.txt
| 1 | 1 | ||
| 2 | 2 | ||
| 3 | 3 | ||
| 4 | +先阅读图片和文本embedding相关的代码: | ||
| 5 | +@embeddings/README.md @embeddings/server.py @docs/搜索API对接指南-07-微服务接口(Embedding-Reranker-Translation).md @embeddings/image_encoder.py @embeddings/text_encoder.py | ||
| 6 | +目前有TEXT_MAX_INFLIGHT / IMAGE_MAX_INFLIGHT 准入限制,超限返回过载状态码。 | ||
| 7 | + | ||
| 8 | +embedding服务(包括图片和文本的embedding),要支持 priority 查询参数,priority > 0:不计入上述 inflight、不会因准入被拒绝; | ||
| 9 | +priority == 0(默认,适合做索引之类的离线任务):仍走原有 TEXT_MAX_INFLIGHT / IMAGE_MAX_INFLIGHT 准入;超限返回过载状态码。 | ||
| 10 | +priority > 0(或者==1)(适合在线请求):不会因准入被拒绝,但是仍然需要占用inflight,这样保证在线请求不被限制,并且在线请求很多的时候可以拒绝掉离线的请求。 | ||
| 11 | + | ||
| 12 | +除了限制规则的修改,更进一步的,看有没有什么简单的机制,可以保证这种请求是优先处理的(priority=1的相比=0的更优先被处理)。 | ||
| 13 | +关于技术方案,有Worker + 双队列、PriorityMutex等等。 | ||
| 14 | +需要优先级的能力,但是只需要两档,并不需要多级的优先级。不过如果支持多级优先级的方案更成熟、稳定,那么仍然可以考虑多优先级的方案。 | ||
| 15 | +在支持两档优先级的情况下,简单、成熟稳定、不带来复杂度、性能、稳定性方面的副作用,是最重要的。请先了解代码、需求,深度思考解决方案 | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | +配置体系的重构。 | ||
| 21 | + | ||
| 22 | +Referring to @docs/config-system-review-and-redesign.md , most of the modifications have been completed. Could you conduct a review to check what else needs improvement in the configuration documentation system? Are there any outstanding issues? | ||
| 23 | + | ||
| 24 | +一、仍然存在大量通过环境变量获取配置的地方 | ||
| 25 | +_SERVICE_KIND = (os.getenv("EMBEDDING_SERVICE_KIND", "all") or "all").strip().lower() | ||
| 26 | +if _SERVICE_KIND not in {"all", "text", "image"}: | ||
| 27 | + raise RuntimeError( | ||
| 28 | + f"Invalid EMBEDDING_SERVICE_KIND={_SERVICE_KIND!r}; expected all, text, or image" | ||
| 29 | + ) | ||
| 30 | +_TEXT_ENABLED_BY_ENV = os.getenv("EMBEDDING_ENABLE_TEXT_MODEL", "true").lower() in ("1", "true", "yes") | ||
| 31 | +_IMAGE_ENABLED_BY_ENV = os.getenv("EMBEDDING_ENABLE_IMAGE_MODEL", "true").lower() in ("1", "true", "yes") | ||
| 32 | +open_text_model = _TEXT_ENABLED_BY_ENV and _SERVICE_KIND in {"all", "text"} | ||
| 33 | +open_image_model = _IMAGE_ENABLED_BY_ENV and _SERVICE_KIND in {"all", "image"} | ||
| 34 | + | ||
| 35 | +_text_encode_lock = threading.Lock() | ||
| 36 | +_image_encode_lock = threading.Lock() | ||
| 37 | + | ||
| 38 | +_TEXT_MICROBATCH_WINDOW_SEC = max( | ||
| 39 | + 0.0, float(os.getenv("TEXT_MICROBATCH_WINDOW_MS", "4")) / 1000.0 | ||
| 40 | +) | ||
| 41 | +_TEXT_REQUEST_TIMEOUT_SEC = max( | ||
| 42 | + 1.0, float(os.getenv("TEXT_REQUEST_TIMEOUT_SEC", "30")) | ||
| 43 | +) | ||
| 44 | +_TEXT_MAX_INFLIGHT = max(1, int(os.getenv("TEXT_MAX_INFLIGHT", "32"))) | ||
| 45 | +_IMAGE_MAX_INFLIGHT = max(1, int(os.getenv("IMAGE_MAX_INFLIGHT", "1"))) | ||
| 46 | +_OVERLOAD_STATUS_CODE = int(os.getenv("EMBEDDING_OVERLOAD_STATUS_CODE", "503")) | ||
| 47 | +_LOG_PREVIEW_COUNT = max(1, int(os.getenv("EMBEDDING_LOG_PREVIEW_COUNT", "3"))) | ||
| 48 | +_LOG_TEXT_PREVIEW_CHARS = max(32, int(os.getenv("EMBEDDING_LOG_TEXT_PREVIEW_CHARS", "120"))) | ||
| 49 | +_LOG_IMAGE_PREVIEW_CHARS = max(32, int(os.getenv("EMBEDDING_LOG_IMAGE_PREVIEW_CHARS", "180"))) | ||
| 50 | +_VECTOR_PREVIEW_DIMS = max(1, int(os.getenv("EMBEDDING_VECTOR_PREVIEW_DIMS", "6"))) | ||
| 51 | +_CACHE_PREFIX = str(REDIS_CONFIG.get("embedding_cache_prefix", "embedding")).strip() or "embedding" | ||
| 52 | + | ||
| 53 | + | ||
| 54 | +看起来似乎并没有完全遵循这些原则? | ||
| 55 | +4. 重新设计的设计原则 | ||
| 56 | +重新设计应遵循以下规则。 | ||
| 57 | + | ||
| 58 | +4.1 单一逻辑配置系统 | ||
| 59 | +可以有多个文件,但不能有多个职责重叠的加载器。 | ||
| 60 | +必须有一个加载器管道,能够生成一个类型化的 AppConfig 对象。 | ||
| 61 | + | ||
| 62 | +4.2 配置文件负责声明,解析代码负责解释,环境变量负责运行时注入 | ||
| 63 | +职责应明确如下: | ||
| 64 | +配置文件 | ||
| 65 | +声明非敏感的目标行为和可部署的非敏感设置 | ||
| 66 | +解析逻辑 | ||
| 67 | +加载、合并、验证、规范化并暴露类型化的配置 | ||
| 68 | +绝不发明隐藏的业务行为 | ||
| 69 | +环境变量 | ||
| 70 | +承载密钥和少量运行时/进程相关的值 | ||
| 71 | +不随意地重新定义业务行为 | ||
| 72 | + | ||
| 73 | +4.3 整个系统采用单一的优先级规则 | ||
| 74 | +除非明确豁免,否则每个配置类别都应遵循相同的合并模型。 | ||
| 75 | + | ||
| 76 | +4.4 业务行为不得有静默的隐式后备 | ||
| 77 | +在启动时,如果必需的配置缺失或无效,应快速失败。 | ||
| 78 | +不要静默地回退到诸如硬编码语言列表之类的遗留行为。 | ||
| 79 | + | ||
| 80 | +4.5 有效配置必须可观测 | ||
| 81 | +每个服务都应能够展示: | ||
| 82 | +配置版本或哈希值 | ||
| 83 | +加载的源文件 | ||
| 84 | +环境名称 | ||
| 85 | +经过清理的有效配置 | ||
| 86 | + | ||
| 87 | +5. 推荐的目标设计 | ||
| 88 | + | ||
| 89 | +5.1 边界模型 | ||
| 90 | +使用三个清晰的层级。 | ||
| 91 | +层级 1:代码仓库管理的静态配置 | ||
| 92 | +目的: | ||
| 93 | +搜索行为 | ||
| 94 | +租户行为 | ||
| 95 | +提供商/后端注册表 | ||
| 96 | +非敏感的服务拓扑默认值 | ||
| 97 | +功能开关 | ||
| 98 | +示例: | ||
| 99 | +字段权重 | ||
| 100 | +查询策略 | ||
| 101 | +重排序融合参数 | ||
| 102 | +租户语言方案 | ||
| 103 | +翻译能力注册表 | ||
| 104 | +嵌入后端选择默认值 | ||
| 105 | + | ||
| 106 | +层级 2:特定环境的层叠配置 | ||
| 107 | +目的: | ||
| 108 | +按环境区分的非敏感差异 | ||
| 109 | +按环境区分的服务端点 | ||
| 110 | +按环境区分的资源大小默认值 | ||
| 111 | +开发/测试/生产环境的运维差异 | ||
| 112 | +示例: | ||
| 113 | +本地嵌入 URL 与生产环境嵌入 URL | ||
| 114 | +开发环境重排序后端与生产环境重排序后端 | ||
| 115 | +本地开发环境中较低的并发度 | ||
| 116 | + | ||
| 117 | +层级 3:环境变量 | ||
| 118 | +目的: | ||
| 119 | +密钥 | ||
| 120 | +绑定主机/端口 | ||
| 121 | +外部基础设施凭证 | ||
| 122 | +容器编排器的最后一步注入 | ||
| 123 | +示例: | ||
| 124 | +ES_HOST, ES_USERNAME, ES_PASSWORD | ||
| 125 | +DB_HOST, DB_USERNAME, DB_PASSWORD | ||
| 126 | +REDIS_HOST, REDIS_PASSWORD | ||
| 127 | +DASHSCOPE_API_KEY, DEEPL_AUTH_KEY | ||
| 128 | +API_HOST, API_PORT, INDEXER_PORT, TRANSLATION_PORT | ||
| 129 | +规则: | ||
| 130 | +环境变量不应成为选择业务行为(如翻译模型、嵌入后端或租户语言策略)的常规途径 | ||
| 131 | +如果允许对非敏感字段进行环境变量覆盖,则必须将其明确列出并记录为运维覆盖手段,而非隐藏的约定 | ||
| 132 | + | ||
| 133 | +5.2 统一的优先级 | ||
| 134 | +推荐的优先级顺序: | ||
| 135 | +代码中的模式(schema)默认值 | ||
| 136 | +config/base.yaml | ||
| 137 | +config/environments/<env>.yaml | ||
| 138 | +来自 config/tenants/ 的租户层叠配置 | ||
| 139 | +针对明确允许的运行时键的环境变量 | ||
| 140 | +仅适用于当前进程的命令行标志 | ||
| 141 | +重要规则: | ||
| 142 | +只能有一个模块实现此合并逻辑 | ||
| 143 | +任何业务模块不得直接调用 os.getenv() 来获取配置 | ||
| 144 | + | ||
| 145 | + | ||
| 146 | + | ||
| 147 | +二、cursor(composer2)帮我审查的 | ||
| 148 | + | ||
| 149 | +## 配置文档系统审查 | ||
| 150 | + | ||
| 151 | +这是对**配置文档系统**的简要审查,对照 [`docs/config-system-review-and-redesign.md`](docs/config-system-review-and-redesign.md) 和当前代码库进行。 | ||
| 152 | + | ||
| 153 | +### 实现已匹配的部分 | ||
| 154 | + | ||
| 155 | +代码库已朝着重新设计的目标迈进了很大一步: | ||
| 156 | + | ||
| 157 | +- **单一类型化根节点**:`AppConfig` 位于 [`config/schema.py`](config/schema.py),通过 [`config/loader.py`](config/loader.py) 和 [`get_app_config()`](config/loader.py) 加载。 | ||
| 158 | +- **精简的 `services_config`**:[`config/services_config.py`](config/services_config.py) 委托给 `get_app_config()`(解决了服务层的发现 A)。 | ||
| 159 | +- **租户目录**:[`config/tenant_config_loader.py`](config/tenant_config_loader.py) 使用 `get_app_config().tenants`。 | ||
| 160 | +- **重写资产路径**:`config/dictionaries/query_rewrite.dict` 已存在(解决了发现 E 中的文件名不匹配问题)。 | ||
| 161 | +- **可观测性**:[`GET /admin/config`](api/routes/admin.py) 返回**经过脱敏处理的有效配置树**;[`GET /admin/config/meta`](api/routes/admin.py) 暴露环境信息、`config_hash`、`loaded_files`、`deprecated_keys`(涵盖了 §5.10 的意图;文档中提议的是 `/admin/config/effective`,但实际实现位于 `/admin/config`)。 | ||
| 162 | + | ||
| 163 | +因此,**"单一加载器 + 有效配置可见性"** 的故事在代码中已基本实现;文档尚未完全跟上。 | ||
| 164 | + | ||
| 165 | +--- | ||
| 166 | + | ||
| 167 | +## 文档问题(影响最大) | ||
| 168 | + | ||
| 169 | +### 1. 管理 API 文档中关于 `/admin/config` 的描述错误 | ||
| 170 | + | ||
| 171 | +[`docs/搜索API对接指南.md`](docs/搜索API对接指南.md)(管理部分附近)和 [`docs/搜索API对接指南-06-管理接口(Admin).md`](docs/搜索API对接指南-06-管理接口(Admin).md) 仍将 `/admin/config` 描述为**按租户**的 JSON(包含 `tenant_id`、`es_index_name`、`supported_languages` 等字段)。实际实现返回的是 **`AppConfig.sanitized_dict()`**(完整的应用配置,敏感信息已脱敏),而不是租户摘要字段。 | ||
| 172 | + | ||
| 173 | +**这些指南中还缺少:** `GET /admin/config/meta`。 | ||
| 174 | + | ||
| 175 | +**健康检查:** 拆分指南中的示例包含了 [`HealthResponse`](api/models.py) 中不存在的字段(只有 `status` 和 `elasticsearch`)。 | ||
| 176 | + | ||
| 177 | +对于任何仅根据文档进行 API 集成的人来说,这是最明显的"未解决问题"。 | ||
| 178 | + | ||
| 179 | +### 2. 面向开发者的指南仍将 `services_config` 作为"配置解析器"的核心 | ||
| 180 | + | ||
| 181 | +[`docs/DEVELOPER_GUIDE.md`](docs/DEVELOPER_GUIDE.md) §5.2 仍说搜索配置由 **`ConfigLoader`** 加载,服务由 **`config/services_config`** "解析"。§6.2 仍将 **`config/services_config.py`** 列为主要的"解析入口"。[`docs/QUICKSTART.md`](docs/QUICKSTART.md) §3.1 仍说"配置解析:`config/services_config.py`"。 | ||
| 182 | + | ||
| 183 | +文档中准确的说法应该是:**规范入口是 `config/loader.py` + `get_app_config()`**;[`config/config_loader.py`](config/config_loader.py) 中的 `ConfigLoader` 包装了统一加载器;`services_config` 是现有调用点的**兼容性外观**。 | ||
| 184 | + | ||
| 185 | +### 3. 重新设计文档本身不是"活的"状态文档 | ||
| 186 | + | ||
| 187 | +[`docs/config-system-review-and-redesign.md`](docs/config-system-review-and-redesign.md) 读起来仍是**纯粹的问题陈述 + 目标**,没有简短的**"已实现 vs 剩余"**部分。这很容易让人假设什么都没做,或者重复工作。添加一个小附录(或一页 `config/README.md` —— 见下文)可以解决这个问题。 | ||
| 188 | + | ||
| 189 | +### 4. 缺少 `config/README.md`(§5.3 中推荐) | ||
| 190 | + | ||
| 191 | +仍然没有专门的 **`config/README.md`** 来描述:加载器入口点、高级优先级、字典存放位置、指向 `/admin/config` + `/admin/config/meta` 的链接,以及重新设计文档的链接。这是重新设计中明确的交付物,可以锚定"文档系统"。 | ||
| 192 | + | ||
| 193 | +### 5. 轻微的文档整洁问题 | ||
| 194 | + | ||
| 195 | +- [`docs/QUICKSTART.md`](docs/QUICKSTART.md) §1.9 环境变量项目后的行有一个多余字符:`---·`(可能是打字错误)。 | ||
| 196 | +- [`docs/DEVELOPER_GUIDE.md`](docs/DEVELOPER_GUIDE.md) §10 **文档索引**没有列出 `config-system-review-and-redesign.md` 或未来的 `config/README.md`。 | ||
| 197 | + | ||
| 198 | +--- | ||
| 199 | + | ||
| 200 | +## 重新设计目标与当前代码之间的差距(文档不应声称"已完成") | ||
| 201 | + | ||
| 202 | +这些影响文档的诚实度: | ||
| 203 | + | ||
| 204 | +| 主题 | 状态 | | ||
| 205 | +|--------|--------| | ||
| 206 | +| **`config dump` CLI**(§5.10) | `main.py` 中不存在;运维人员依赖 HTTP 或临时脚本。 | | ||
| 207 | +| **隐藏的 `["en", "zh"]` 回退**(阶段 3 / 发现 D) | 仍在 [`indexer/document_transformer.py`](indexer/document_transformer.py)、[`suggestion/builder.py`](suggestion/builder.py) 等中使用。 | | ||
| 208 | +| **加载器外的 `os.getenv`**(规则 1–2) | 仍在例如 [`embeddings/server.py`](embeddings/server.py)、[`reranker/server.py`](reranker/server.py)、[`api/app.py`](api/app.py) 中使用 —— 文档声称"仅加载器"将是夸大其词。 | | ||
| 209 | +| **拆分 `base.yaml` / `environments/` / `tenants/*.yaml`**(阶段 5) | 未采用;仍是单一的 [`config/config.yaml`](config/config.yaml)。 | | ||
| 210 | +| **遗留租户标志**(阶段 6 / 发现 H) | [`indexer/README.md`](indexer/README.md) 仍描述上游 MySQL 的 `translate_to_en` / `translate_to_zh`(这可能作为**上游模式**文档保留;需与 Python `tenant_config` 模型区分开来)。 | | ||
| 211 | + | ||
| 212 | +--- | ||
| 213 | + | ||
| 214 | +## 推荐的后续步骤(仅文档,按优先级排序) | ||
| 215 | + | ||
| 216 | +1. **修复管理 API 文档**(合并指南 + `-06-` 拆分):`/admin/config` 的响应格式,添加 `/admin/config/meta`,使健康检查示例与 [`HealthResponse`](api/models.py) 一致。 | ||
| 217 | +2. **更新 DEVELOPER_GUIDE §5–§6 和 QUICKSTART §1.9 / §3.1**,将 **`get_app_config()` / `loader.py`** 描述为主要入口,将 **`services_config`** 描述为适配器。 | ||
| 218 | +3. **添加 `config/README.md`**(简短的操作 + 开发者入口)。 | ||
| 219 | +4. **在 `config-system-review-and-redesign.md` 中添加**带日期的**实现状态表**(已交付 vs 推迟的内容),使审查文档不与现实矛盾。 | ||
| 220 | +5. **DEVELOPER_GUIDE §9 检查清单**:将"配置来自 `services_config`"替换为允许 `get_app_config()` 或精简适配器的语言,与 §6 保持一致。 | ||
| 221 | + | ||
| 222 | +如果需要,我可以在后续处理中为项目 1–3 和重新设计文档中的简短状态块应用补丁。 | ||
| 223 | + | ||
| 224 | + | ||
| 225 | + | ||
| 226 | + | ||
| 227 | + | ||
| 228 | + | ||
| 229 | + | ||
| 230 | + | ||
| 231 | + | ||
| 232 | + | ||
| 233 | + | ||
| 234 | + | ||
| 235 | + | ||
| 4 | nllb-200-distilled-600M性能优化 | 236 | nllb-200-distilled-600M性能优化 |
| 5 | 已完成(2026-03) | 237 | 已完成(2026-03) |
| 6 | - CTranslate2 迁移 + float16 转换 | 238 | - CTranslate2 迁移 + float16 转换 |
docs/搜索API速查表.md renamed to docs/搜索API对接指南-速查表.md
docs/搜索API对接指南—拆分前版本存档.md deleted
| @@ -1,2495 +0,0 @@ | @@ -1,2495 +0,0 @@ | ||
| 1 | -# 搜索API接口对接指南 | ||
| 2 | - | ||
| 3 | -本文档为搜索服务的使用方提供完整的API对接指南,包括接口说明、请求参数、响应格式和使用示例。 | ||
| 4 | - | ||
| 5 | -## 目录 | ||
| 6 | - | ||
| 7 | -1. [快速开始](#快速开始) | ||
| 8 | - - 1.1 [基础信息](#11-基础信息) | ||
| 9 | - - 1.2 [最简单的搜索请求](#12-最简单的搜索请求) | ||
| 10 | - - 1.3 [带过滤与分页的搜索](#13-带过滤与分页的搜索) | ||
| 11 | - - 1.4 [开启分面的搜索](#14-开启分面的搜索) | ||
| 12 | - | ||
| 13 | -2. [接口概览](#接口概览) | ||
| 14 | - | ||
| 15 | -3. [搜索接口](#搜索接口) | ||
| 16 | - - 3.1 [接口信息](#31-接口信息) | ||
| 17 | - - 3.2 [请求参数](#32-请求参数) | ||
| 18 | - - 3.3 [过滤器详解](#33-过滤器详解) | ||
| 19 | - - 3.4 [分面配置](#34-分面配置) | ||
| 20 | - - 3.5 [SKU筛选维度](#35-sku筛选维度) | ||
| 21 | - - 3.6 [搜索建议接口](#37-搜索建议接口) | ||
| 22 | - - 3.7 [即时搜索接口](#38-即时搜索接口) | ||
| 23 | - - 3.8 [获取单个文档](#39-获取单个文档) | ||
| 24 | - | ||
| 25 | -4. [响应格式说明](#响应格式说明) | ||
| 26 | - - 4.1 [标准响应结构](#41-标准响应结构) | ||
| 27 | - - 4.2 [响应字段说明](#42-响应字段说明) | ||
| 28 | - - 4.2.1 [query_info 说明](#421-query_info-说明) | ||
| 29 | - - 4.3 [SpuResult字段说明](#43-spuresult字段说明) | ||
| 30 | - - 4.4 [SkuResult字段说明](#44-skuresult字段说明) | ||
| 31 | - - 4.5 [多语言字段说明](#45-多语言字段说明) | ||
| 32 | - | ||
| 33 | -5. [索引接口](#索引接口) | ||
| 34 | - - 5.0 [支撑外部 indexer 的三种方式](#50-支撑外部-indexer-的三种方式) | ||
| 35 | - - 5.1 [为租户创建索引](#51-为租户创建索引) | ||
| 36 | - - 5.2 [全量索引接口](#52-全量索引接口) | ||
| 37 | - - 5.3 [增量索引接口](#53-增量索引接口) | ||
| 38 | - - 5.4 [查询文档接口](#54-查询文档接口) | ||
| 39 | - - 5.5 [索引健康检查接口](#55-索引健康检查接口) | ||
| 40 | - - 5.6 [文档构建接口(正式对接)](#56-文档构建接口正式对接推荐) | ||
| 41 | - - 5.7 [文档构建接口(测试/自测)](#57-文档构建接口测试--自测) | ||
| 42 | - - 5.8 [内容理解字段生成接口](#58-内容理解字段生成接口) | ||
| 43 | - | ||
| 44 | -6. [管理接口](#管理接口) | ||
| 45 | - - 6.1 [健康检查](#61-健康检查) | ||
| 46 | - - 6.2 [获取配置](#62-获取配置) | ||
| 47 | - - 6.3 [索引统计](#63-索引统计) | ||
| 48 | - | ||
| 49 | -7. [微服务接口(向量、重排、翻译、内容理解)](#7-微服务接口向量重排翻译) | ||
| 50 | - - 7.1 [向量服务(Embedding)](#71-向量服务embedding) | ||
| 51 | - - 7.2 [重排服务(Reranker)](#72-重排服务reranker) | ||
| 52 | - - 7.3 [翻译服务(Translation)](#73-翻译服务translation) | ||
| 53 | - - 7.4 [内容理解字段生成(Indexer 服务内)](#74-内容理解字段生成indexer-服务内) | ||
| 54 | - | ||
| 55 | -8. [常见场景示例](#8-常见场景示例) | ||
| 56 | - - 8.1 [基础搜索与排序](#81-基础搜索与排序) | ||
| 57 | - - 8.2 [过滤搜索](#82-过滤搜索) | ||
| 58 | - - 8.3 [分面搜索](#83-分面搜索) | ||
| 59 | - - 8.4 [规格过滤与分面](#84-规格过滤与分面) | ||
| 60 | - - 8.5 [SKU筛选](#85-sku筛选) | ||
| 61 | - - 8.6 [分页查询](#87-分页查询) | ||
| 62 | - | ||
| 63 | -9. [数据模型](#9-数据模型) | ||
| 64 | - - 9.1 [商品字段定义](#91-商品字段定义) | ||
| 65 | - - 9.2 [字段类型速查](#92-字段类型速查) | ||
| 66 | - - 9.3 [常用字段列表](#93-常用字段列表) | ||
| 67 | - - 9.4 [支持的分析器](#94-支持的分析器) | ||
| 68 | - | ||
| 69 | ---- | ||
| 70 | - | ||
| 71 | -## 快速开始 | ||
| 72 | - | ||
| 73 | -### 1.1 基础信息 | ||
| 74 | - | ||
| 75 | -- **Base URL**: `http://43.166.252.75:6002` | ||
| 76 | -- **协议**: HTTP/HTTPS | ||
| 77 | -- **数据格式**: JSON | ||
| 78 | -- **字符编码**: UTF-8 | ||
| 79 | -- **请求方法**: POST(搜索接口) | ||
| 80 | - | ||
| 81 | -**重要提示**: `tenant_id` 通过 HTTP Header `X-Tenant-ID` 传递,不在请求体中。 | ||
| 82 | - | ||
| 83 | -**环境与凭证**:MySQL、Redis、Elasticsearch 等外部服务的 AI 生产地址与凭证见 [QUICKSTART.md §1.6](./QUICKSTART.md#16-外部服务与-env含生产凭证)。 | ||
| 84 | - | ||
| 85 | -### 1.2 最简单的搜索请求 | ||
| 86 | - | ||
| 87 | -```bash | ||
| 88 | -curl -X POST "http://43.166.252.75:6002/search/" \ | ||
| 89 | - -H "Content-Type: application/json" \ | ||
| 90 | - -H "X-Tenant-ID: 162" \ | ||
| 91 | - -d '{"query": "芭比娃娃"}' | ||
| 92 | -``` | ||
| 93 | - | ||
| 94 | -### 1.3 带过滤与分页的搜索 | ||
| 95 | - | ||
| 96 | -```bash | ||
| 97 | -curl -X POST "http://43.166.252.75:6002/search/" \ | ||
| 98 | - -H "Content-Type: application/json" \ | ||
| 99 | - -H "X-Tenant-ID: 162" \ | ||
| 100 | - -d '{ | ||
| 101 | - "query": "芭比娃娃", | ||
| 102 | - "size": 5, | ||
| 103 | - "from": 10, | ||
| 104 | - "range_filters": { | ||
| 105 | - "min_price": { | ||
| 106 | - "gte": 50, | ||
| 107 | - "lte": 200 | ||
| 108 | - }, | ||
| 109 | - "create_time": { | ||
| 110 | - "gte": "2020-01-01T00:00:00Z" | ||
| 111 | - } | ||
| 112 | - }, | ||
| 113 | - "sort_by": "price", | ||
| 114 | - "sort_order": "asc" | ||
| 115 | - }' | ||
| 116 | -``` | ||
| 117 | - | ||
| 118 | -### 1.4 开启分面的搜索 | ||
| 119 | - | ||
| 120 | -```bash | ||
| 121 | -curl -X POST "http://43.166.252.75:6002/search/" \ | ||
| 122 | - -H "Content-Type: application/json" \ | ||
| 123 | - -H "X-Tenant-ID: 162" \ | ||
| 124 | - -d '{ | ||
| 125 | - "query": "芭比娃娃", | ||
| 126 | - "facets": [ | ||
| 127 | - {"field": "category1_name", "size": 10, "type": "terms"}, | ||
| 128 | - {"field": "specifications.color", "size": 10, "type": "terms"}, | ||
| 129 | - {"field": "specifications.size", "size": 10, "type": "terms"} | ||
| 130 | - ], | ||
| 131 | - "min_score": 0.2 | ||
| 132 | - }' | ||
| 133 | -``` | ||
| 134 | - | ||
| 135 | ---- | ||
| 136 | - | ||
| 137 | -## 接口概览 | ||
| 138 | - | ||
| 139 | -| 接口 | HTTP Method | Endpoint | 说明 | | ||
| 140 | -|------|------|------|------| | ||
| 141 | -| 搜索 | POST | `/search/` | 执行搜索查询 | | ||
| 142 | -| 搜索建议 | GET | `/search/suggestions` | 搜索建议(自动补全/热词,多语言) | | ||
| 143 | -| 即时搜索 | GET | `/search/instant` | 即时搜索预留接口(当前返回 `501 Not Implemented`) | | ||
| 144 | -| 获取文档 | GET | `/search/{doc_id}` | 获取单个文档 | | ||
| 145 | -| 全量索引 | POST | `/indexer/reindex` | 全量索引接口(导入数据,不删除索引,仅推荐自测使用) | | ||
| 146 | -| 增量索引 | POST | `/indexer/index` | 增量索引接口(指定SPU ID列表进行索引,支持自动检测删除和显式删除,仅推荐自测使用) | | ||
| 147 | -| 查询文档 | POST | `/indexer/documents` | 查询SPU文档数据(不写入ES) | | ||
| 148 | -| 构建ES文档(正式对接) | POST | `/indexer/build-docs` | 基于上游提供的 MySQL 行数据构建 ES doc,不写入 ES,供 Java 等调用后自行写入 | | ||
| 149 | -| 构建ES文档(测试用) | POST | `/indexer/build-docs-from-db` | 仅在测试/调试时使用,根据 `tenant_id + spu_ids` 内部查库并构建 ES doc | | ||
| 150 | -| 内容理解字段生成 | POST | `/indexer/enrich-content` | 根据商品标题批量生成 qanchors、semantic_attributes、tags,供微服务组合方式使用 | | ||
| 151 | -| 索引健康检查 | GET | `/indexer/health` | 检查索引服务状态 | | ||
| 152 | -| 健康检查 | GET | `/admin/health` | 服务健康检查 | | ||
| 153 | -| 获取配置 | GET | `/admin/config` | 获取租户配置 | | ||
| 154 | -| 索引统计 | GET | `/admin/stats` | 获取租户索引统计信息(需 tenant_id) | | ||
| 155 | - | ||
| 156 | -**微服务(独立端口或 Indexer 内,外部可直连)**: | ||
| 157 | - | ||
| 158 | -| 服务 | 端口 | 接口 | 说明 | | ||
| 159 | -|------|------|------|------| | ||
| 160 | -| 向量服务(文本) | 6005 | `POST /embed/text` | 文本向量化 | | ||
| 161 | -| 向量服务(图片) | 6008 | `POST /embed/image` | 图片向量化 | | ||
| 162 | -| 翻译服务 | 6006 | `POST /translate` | 文本翻译(支持 qwen-mt / llm / deepl / 本地模型) | | ||
| 163 | -| 重排服务 | 6007 | `POST /rerank` | 检索结果重排 | | ||
| 164 | -| 内容理解(Indexer 内) | 6004 | `POST /indexer/enrich-content` | 根据商品标题生成 qanchors、tags 等,供 indexer 微服务组合方式使用 | | ||
| 165 | - | ||
| 166 | ---- | ||
| 167 | - | ||
| 168 | -## 搜索接口 | ||
| 169 | - | ||
| 170 | -### 3.1 接口信息 | ||
| 171 | - | ||
| 172 | -- **端点**: `POST /search/` | ||
| 173 | -- **描述**: 执行文本搜索查询,支持多语言、过滤器和分面搜索 | ||
| 174 | -- **租户标识**:`tenant_id` 通过 HTTP 请求头 **`X-Tenant-ID`** 传递(推荐);也可通过 URL query 参数 **`tenant_id`** 传递。**不要放在请求体中。** | ||
| 175 | - | ||
| 176 | -**请求示例(推荐)**: | ||
| 177 | -```python | ||
| 178 | -url = f"{base_url.rstrip('/')}/search/" | ||
| 179 | -headers = { | ||
| 180 | - "Content-Type": "application/json", | ||
| 181 | - "X-Tenant-ID": "162", # 租户ID,必填 | ||
| 182 | -} | ||
| 183 | -response = requests.post(url, headers=headers, json={"query": "芭比娃娃"}) | ||
| 184 | -``` | ||
| 185 | - | ||
| 186 | -### 3.2 请求参数 | ||
| 187 | - | ||
| 188 | -#### 完整请求体结构 | ||
| 189 | - | ||
| 190 | -```json | ||
| 191 | -{ | ||
| 192 | - "query": "string (required)", | ||
| 193 | - "size": 10, | ||
| 194 | - "from": 0, | ||
| 195 | - "language": "zh", | ||
| 196 | - "filters": {}, | ||
| 197 | - "range_filters": {}, | ||
| 198 | - "facets": [], | ||
| 199 | - "sort_by": "string", | ||
| 200 | - "sort_order": "desc", | ||
| 201 | - "min_score": 0.0, | ||
| 202 | - "sku_filter_dimension": ["string"], | ||
| 203 | - "debug": false, | ||
| 204 | - "enable_rerank": null, | ||
| 205 | - "rerank_query_template": "{query}", | ||
| 206 | - "rerank_doc_template": "{title}", | ||
| 207 | - "user_id": "string", | ||
| 208 | - "session_id": "string" | ||
| 209 | -} | ||
| 210 | -``` | ||
| 211 | - | ||
| 212 | -#### 参数详细说明 | ||
| 213 | - | ||
| 214 | -| 参数 | 类型 | 必填 | 默认值 | 说明 | | ||
| 215 | -|------|------|------|--------|------| | ||
| 216 | -| `query` | string | Y | - | 搜索查询字符串(统一文本检索策略) | | ||
| 217 | -| `size` | integer | N | 10 | 返回结果数量(1-100) | | ||
| 218 | -| `from` | integer | N | 0 | 分页偏移量(用于分页) | | ||
| 219 | -| `language` | string | N | "zh" | 返回语言:`zh`(中文)或 `en`(英文)。后端会根据此参数选择对应的中英文字段返回 | | ||
| 220 | -| `filters` | object | N | null | 精确匹配过滤器(见[过滤器详解](#33-过滤器详解)) | | ||
| 221 | -| `range_filters` | object | N | null | 数值范围过滤器(见[过滤器详解](#33-过滤器详解)) | | ||
| 222 | -| `facets` | array | N | null | 分面配置(见[分面配置](#34-分面配置)) | | ||
| 223 | -| `sort_by` | string | N | null | 排序字段名。支持:`price`(价格)、`sales`(销量)、`create_time`(创建时间)、`update_time`(更新时间)。默认按相关性排序 | | ||
| 224 | -| `sort_order` | string | N | "desc" | 排序方向:`asc`(升序)或 `desc`(降序)。注意:`price`+`asc`=价格从低到高,`price`+`desc`=价格从高到低(后端自动映射为min_price或max_price) | | ||
| 225 | -| `min_score` | float | N | null | 最小相关性分数阈值 | | ||
| 226 | -| `sku_filter_dimension` | array[string] | N | null | 子SKU筛选维度列表(见[SKU筛选维度](#35-sku筛选维度)) | | ||
| 227 | -| `debug` | boolean | N | false | 是否返回调试信息 | | ||
| 228 | -| `enable_rerank` | boolean/null | N | null | 是否开启重排(调用外部重排服务对 ES 结果进行二次排序)。不传/传 null 使用服务端 `rerank.enabled`(默认开启)。开启后会先对 ES TopN(`rerank_window`)重排,再按分页截取;若 `from+size>1000`,则不重排,直接按分页从 ES 返回 | | ||
| 229 | -| `rerank_query_template` | string | N | null | 重排 query 模板(可选)。支持 `{query}` 占位符;不传则使用服务端配置 | | ||
| 230 | -| `rerank_doc_template` | string | N | null | 重排 doc 模板(可选)。支持 `{title} {brief} {vendor} {description} {category_path}`;不传则使用服务端配置 | | ||
| 231 | -| `user_id` | string | N | null | 用户ID(用于个性化,预留) | | ||
| 232 | -| `session_id` | string | N | null | 会话ID(用于分析,预留) | | ||
| 233 | - | ||
| 234 | -### 3.3 过滤器详解 | ||
| 235 | - | ||
| 236 | -#### 3.3.1 精确匹配过滤器 (filters) | ||
| 237 | - | ||
| 238 | -用于精确匹配或多值匹配。对于普通字段,数组表示 OR 逻辑(匹配任意一个值);对于 specifications 字段,按维度分组处理。**任意字段名加 `_all` 后缀**表示多值 AND 逻辑(必须同时匹配所有值)。 | ||
| 239 | - | ||
| 240 | -**格式**: | ||
| 241 | -```json | ||
| 242 | -{ | ||
| 243 | - "filters": { | ||
| 244 | - "category_name": "手机", // 可以为单值 或者 数组 匹配数组中任意一个(OR) | ||
| 245 | - "category1_name": "服装", // 可以为单值 或者 数组 匹配数组中任意一个(OR) | ||
| 246 | - "category2_name": "男装", // 可以为单值 或者 数组 匹配数组中任意一个(OR) | ||
| 247 | - "category3_name": "衬衫", // 可以为单值 或者 数组 匹配数组中任意一个(OR) | ||
| 248 | - "vendor.zh.keyword": ["奇乐", "品牌A"], // 可以为单值 或者 数组 匹配数组中任意一个(OR) | ||
| 249 | - "tags": "手机", // 可以为单值 或者 数组 匹配数组中任意一个(OR) | ||
| 250 | - "tags_all": ["手机", "促销", "新品"], // *_all:多值为 AND,必须同时包含所有标签 | ||
| 251 | - "category1_name_all": ["服装", "男装"], // 同上,适用于任意可过滤字段 | ||
| 252 | - // specifications 嵌套过滤(特殊格式) | ||
| 253 | - "specifications": { | ||
| 254 | - "name": "color", | ||
| 255 | - "value": "white" | ||
| 256 | - } | ||
| 257 | - } | ||
| 258 | -} | ||
| 259 | -``` | ||
| 260 | - | ||
| 261 | -**支持的值类型**: | ||
| 262 | -- 字符串:精确匹配 | ||
| 263 | -- 整数:精确匹配 | ||
| 264 | -- 布尔值:精确匹配 | ||
| 265 | -- 数组:匹配任意值(OR 逻辑);若字段名以 `_all` 结尾,则数组表示 AND 逻辑(必须同时匹配所有值) | ||
| 266 | -- 对象:specifications 嵌套过滤(见下文) | ||
| 267 | - | ||
| 268 | -**`*_all` 语义(多值 AND)**: | ||
| 269 | -- 任意过滤字段均可使用 `_all` 后缀,对应 ES 字段名为去掉 `_all` 后的名称。 | ||
| 270 | -- 例如:`tags_all: ["A", "B"]` 表示文档的 `tags` 必须**同时包含** A 和 B;`vendor.zh.keyword_all: ["奇乐", "品牌A"]` 表示同时匹配两个品牌(通常用于 keyword 多值场景)。 | ||
| 271 | -- `specifications_all`:传列表 `[{"name":"color","value":"white"},{"name":"size","value":"256GB"}]` 时,表示所有列出的规格条件都要满足(与 `specifications` 多维度时的 AND 一致;若同维度多值则要求文档同时满足多个值,一般用于嵌套多值场景)。 | ||
| 272 | - | ||
| 273 | -**Specifications 嵌套过滤**: | ||
| 274 | - | ||
| 275 | -`specifications` 是嵌套字段,支持按规格名称和值进行过滤。 | ||
| 276 | - | ||
| 277 | -**单个规格过滤**: | ||
| 278 | -```json | ||
| 279 | -{ | ||
| 280 | - "filters": { | ||
| 281 | - "specifications": { | ||
| 282 | - "name": "color", | ||
| 283 | - "value": "white" | ||
| 284 | - } | ||
| 285 | - } | ||
| 286 | -} | ||
| 287 | -``` | ||
| 288 | -查询规格名称为"color"且值为"white"的商品。 | ||
| 289 | - | ||
| 290 | -**多个规格过滤(按维度分组)**: | ||
| 291 | -```json | ||
| 292 | -{ | ||
| 293 | - "filters": { | ||
| 294 | - "specifications": [ | ||
| 295 | - {"name": "color", "value": "white"}, | ||
| 296 | - {"name": "size", "value": "256GB"} | ||
| 297 | - ] | ||
| 298 | - } | ||
| 299 | -} | ||
| 300 | -``` | ||
| 301 | -查询同时满足所有规格的商品(color=white **且** size=256GB)。 | ||
| 302 | - | ||
| 303 | -**相同维度的多个值(OR 逻辑)**: | ||
| 304 | -```json | ||
| 305 | -{ | ||
| 306 | - "filters": { | ||
| 307 | - "specifications": [ | ||
| 308 | - {"name": "size", "value": "3"}, | ||
| 309 | - {"name": "size", "value": "4"}, | ||
| 310 | - {"name": "size", "value": "5"}, | ||
| 311 | - {"name": "color", "value": "green"} | ||
| 312 | - ] | ||
| 313 | - } | ||
| 314 | -} | ||
| 315 | -``` | ||
| 316 | -查询满足 (size=3 **或** size=4 **或** size=5) **且** color=green 的商品。 | ||
| 317 | - | ||
| 318 | -**过滤逻辑说明**: | ||
| 319 | -- **不同维度**(不同的 `name`)之间是 **AND** 关系(求交集) | ||
| 320 | -- **相同维度**(相同的 `name`)的多个值之间是 **OR** 关系(求并集) | ||
| 321 | - | ||
| 322 | -**常用过滤字段**(详见[常用字段列表](#83-常用字段列表)): | ||
| 323 | -- `category_name`: 类目名称 | ||
| 324 | -- `category1_name`, `category2_name`, `category3_name`: 多级类目 | ||
| 325 | -- `category_id`: 类目ID | ||
| 326 | -- `vendor.zh.keyword`, `vendor.en.keyword`: 供应商/品牌(使用keyword子字段) | ||
| 327 | -- `tags`: 标签(keyword类型,支持数组) | ||
| 328 | -- `option1_name`, `option2_name`, `option3_name`: 选项名称 | ||
| 329 | -- `specifications`: 规格过滤(嵌套字段,格式见上文) | ||
| 330 | -- 以上任意字段均可加 `_all` 后缀表示多值 AND,如 `tags_all`、`category1_name_all`。 | ||
| 331 | - | ||
| 332 | -#### 3.3.2 范围过滤器 (range_filters) | ||
| 333 | - | ||
| 334 | -用于数值字段的范围过滤。 | ||
| 335 | - | ||
| 336 | -**格式**: | ||
| 337 | -```json | ||
| 338 | -{ | ||
| 339 | - "range_filters": { | ||
| 340 | - "min_price": { | ||
| 341 | - "gte": 50, // 大于等于 | ||
| 342 | - "lte": 200 // 小于等于 | ||
| 343 | - }, | ||
| 344 | - "max_price": { | ||
| 345 | - "gt": 100 // 大于 | ||
| 346 | - }, | ||
| 347 | - "create_time": { | ||
| 348 | - "gte": "2024-01-01T00:00:00Z" // 日期时间字符串 | ||
| 349 | - } | ||
| 350 | - } | ||
| 351 | -} | ||
| 352 | -``` | ||
| 353 | - | ||
| 354 | -**支持的操作符**: | ||
| 355 | -- `gte`: 大于等于 (>=) | ||
| 356 | -- `gt`: 大于 (>) | ||
| 357 | -- `lte`: 小于等于 (<=) | ||
| 358 | -- `lt`: 小于 (<) | ||
| 359 | - | ||
| 360 | -**注意**: 至少需要指定一个操作符。 | ||
| 361 | - | ||
| 362 | -**常用范围字段**(详见[常用字段列表](#83-常用字段列表)): | ||
| 363 | -- `min_price`: 最低价格 | ||
| 364 | -- `max_price`: 最高价格 | ||
| 365 | -- `compare_at_price`: 原价 | ||
| 366 | -- `create_time`: 创建时间 | ||
| 367 | -- `update_time`: 更新时间 | ||
| 368 | - | ||
| 369 | -### 3.4 分面配置 | ||
| 370 | - | ||
| 371 | -用于生成分面统计(分组聚合),常用于构建筛选器UI。 | ||
| 372 | - | ||
| 373 | -#### 3.4.1 配置格式 | ||
| 374 | - | ||
| 375 | -```json | ||
| 376 | -{ | ||
| 377 | - "facets": [ | ||
| 378 | - { | ||
| 379 | - "field": "category1_name", | ||
| 380 | - "size": 15, | ||
| 381 | - "type": "terms", | ||
| 382 | - "disjunctive": false | ||
| 383 | - }, | ||
| 384 | - { | ||
| 385 | - "field": "brand_name", | ||
| 386 | - "size": 10, | ||
| 387 | - "type": "terms", | ||
| 388 | - "disjunctive": true | ||
| 389 | - }, | ||
| 390 | - { | ||
| 391 | - "field": "specifications.color", | ||
| 392 | - "size": 20, | ||
| 393 | - "type": "terms", | ||
| 394 | - "disjunctive": true | ||
| 395 | - }, | ||
| 396 | - { | ||
| 397 | - "field": "min_price", | ||
| 398 | - "type": "range", | ||
| 399 | - "ranges": [ | ||
| 400 | - {"key": "0-50", "to": 50}, | ||
| 401 | - {"key": "50-100", "from": 50, "to": 100}, | ||
| 402 | - {"key": "100-200", "from": 100, "to": 200}, | ||
| 403 | - {"key": "200+", "from": 200} | ||
| 404 | - ] | ||
| 405 | - } | ||
| 406 | - ] | ||
| 407 | -} | ||
| 408 | -``` | ||
| 409 | - | ||
| 410 | -#### 3.4.2 Facet 字段说明 | ||
| 411 | - | ||
| 412 | -| 字段 | 类型 | 必填 | 默认值 | 说明 | | ||
| 413 | -|------|------|------|--------|------| | ||
| 414 | -| `field` | string | 是 | - | 分面字段名 | | ||
| 415 | -| `size` | int | 否 | 10 | 返回的分面值数量(1-100) | | ||
| 416 | -| `type` | string | 否 | "terms" | 分面类型:`terms`(词条聚合)或 `range`(范围聚合) | | ||
| 417 | -| `disjunctive` | bool | 否 | false | 是否支持多选(disjunctive faceting)。启用后,选中该分面的过滤器时,仍会显示其他可选项 | | ||
| 418 | -| `ranges` | array | 否 | null | 范围配置(仅 `type="range"` 时需要) | | ||
| 419 | - | ||
| 420 | -#### 3.4.3 disjunctive字段说明 | ||
| 421 | - | ||
| 422 | -**重要特性**: `disjunctive` 字段控制分面的行为模式。启用后,选中该分面的过滤器时,仍会显示其他可选项 | ||
| 423 | - | ||
| 424 | -**标准模式 (disjunctive: false)**: | ||
| 425 | -- **行为**: 选中某个分面值后,该分面只显示选中的值 | ||
| 426 | -- **适用场景**: 层级类目、互斥选择 | ||
| 427 | -- **示例**: 类目下钻(玩具 > 娃娃 > 芭比) | ||
| 428 | - | ||
| 429 | -**Multi-Select 模式 (disjunctive: true)** ⭐: | ||
| 430 | -- **行为**: 选中某个分面值后,该分面仍显示所有可选项 | ||
| 431 | -- **适用场景**: 颜色、品牌、尺码等可切换属性 | ||
| 432 | -- **示例**: 选择了"红色"后,仍能看到"蓝色"、"绿色"等选项 | ||
| 433 | - | ||
| 434 | -**推荐配置**: | ||
| 435 | - | ||
| 436 | -| 分面类型 | disjunctive | 原因 | | ||
| 437 | -|---------|-------------|------| | ||
| 438 | -| 颜色 | `true` | 用户需要切换颜色 | | ||
| 439 | -| 品牌 | `true` | 用户需要比较品牌 | | ||
| 440 | -| 尺码 | `true` | 用户需要查看其他尺码 | | ||
| 441 | -| 类目 | `false` | 层级下钻 | | ||
| 442 | -| 价格区间 | `false` | 互斥选择 | | ||
| 443 | - | ||
| 444 | -#### 3.4.4 规格分面说明 | ||
| 445 | - | ||
| 446 | -`specifications` 是嵌套字段,支持两种分面模式: | ||
| 447 | - | ||
| 448 | -**模式1:所有规格名称的分面**: | ||
| 449 | -```json | ||
| 450 | -{ | ||
| 451 | - "facets": [ | ||
| 452 | - { | ||
| 453 | - "field": "specifications", | ||
| 454 | - "size": 10, | ||
| 455 | - "type": "terms" | ||
| 456 | - } | ||
| 457 | - ] | ||
| 458 | -} | ||
| 459 | -``` | ||
| 460 | -返回所有规格名称(name)及其对应的值(value)列表。每个 name 会生成一个独立的分面结果。 | ||
| 461 | - | ||
| 462 | -**模式2:指定规格名称的分面**: | ||
| 463 | -```json | ||
| 464 | -{ | ||
| 465 | - "facets": [ | ||
| 466 | - { | ||
| 467 | - "field": "specifications.color", | ||
| 468 | - "size": 20, | ||
| 469 | - "type": "terms", | ||
| 470 | - "disjunctive": true | ||
| 471 | - }, | ||
| 472 | - { | ||
| 473 | - "field": "specifications.size", | ||
| 474 | - "size": 15, | ||
| 475 | - "type": "terms", | ||
| 476 | - "disjunctive": true | ||
| 477 | - } | ||
| 478 | - ] | ||
| 479 | -} | ||
| 480 | -``` | ||
| 481 | -只返回指定规格名称的值列表。格式:`specifications.{name}`,其中 `{name}` 是规格名称(如"color"、"size"、"material")。 | ||
| 482 | - | ||
| 483 | -**返回格式示例**: | ||
| 484 | -```json | ||
| 485 | -{ | ||
| 486 | - "facets": [ | ||
| 487 | - { | ||
| 488 | - "field": "specifications.color", | ||
| 489 | - "label": "color", | ||
| 490 | - "type": "terms", | ||
| 491 | - "values": [ | ||
| 492 | - {"value": "white", "count": 50, "selected": true}, // ✓ selected 字段由后端标记 | ||
| 493 | - {"value": "black", "count": 30, "selected": false}, | ||
| 494 | - {"value": "red", "count": 20, "selected": false} | ||
| 495 | - ] | ||
| 496 | - }, | ||
| 497 | - { | ||
| 498 | - "field": "specifications.size", | ||
| 499 | - "label": "size", | ||
| 500 | - "type": "terms", | ||
| 501 | - "values": [ | ||
| 502 | - {"value": "256GB", "count": 40, "selected": false}, | ||
| 503 | - {"value": "512GB", "count": 20, "selected": false} | ||
| 504 | - ] | ||
| 505 | - } | ||
| 506 | - ] | ||
| 507 | -} | ||
| 508 | -``` | ||
| 509 | - | ||
| 510 | -### 3.5 SKU筛选维度 | ||
| 511 | - | ||
| 512 | -**功能说明**: | ||
| 513 | -`sku_filter_dimension` 用于控制搜索列表页中 **每个 SPU 下方可切换的子款式(子 SKU)维度**,为字符串列表。 | ||
| 514 | -在店铺的 **主题装修配置** 中,商家可以为店铺设置一个或多个子款式筛选维度(例如 `color`、`size`),前端列表页会在每个 SPU 下展示这些维度对应的子 SKU 列表,用户可以通过点击不同维度值(如不同颜色)来切换展示的子款式。 | ||
| 515 | -当指定 `sku_filter_dimension` 后,后端会根据店铺的这项配置,从所有 SKU 中筛选出这些维度组合对应的子 SKU 数据:系统会按指定维度**组合**对 SKU 进行分组,每个维度组合只返回第一个 SKU(从简实现,选择该组合下的第一款),其余不在这些维度组合中的子 SKU 将不返回。 | ||
| 516 | - | ||
| 517 | -**支持的维度值**: | ||
| 518 | -1. **直接选项字段**: `option1`、`option2`、`option3` | ||
| 519 | - - 直接使用对应的 `option1_value`、`option2_value`、`option3_value` 字段进行分组 | ||
| 520 | - | ||
| 521 | -2. **规格/选项名称**: 通过 `option1_name`、`option2_name`、`option3_name` 匹配 | ||
| 522 | - - 例如:如果 `option1_name` 为 `"color"`,则可以使用 `sku_filter_dimension: ["color"]` 来按颜色分组 | ||
| 523 | - | ||
| 524 | -**示例**: | ||
| 525 | - | ||
| 526 | -**按颜色筛选(假设 option1_name = "color")**: | ||
| 527 | -```json | ||
| 528 | -{ | ||
| 529 | - "query": "芭比娃娃", | ||
| 530 | - "sku_filter_dimension": ["color"] | ||
| 531 | -} | ||
| 532 | -``` | ||
| 533 | - | ||
| 534 | -**按选项1筛选**: | ||
| 535 | -```json | ||
| 536 | -{ | ||
| 537 | - "query": "芭比娃娃", | ||
| 538 | - "sku_filter_dimension": ["option1"] | ||
| 539 | -} | ||
| 540 | -``` | ||
| 541 | - | ||
| 542 | -**按颜色 + 尺寸组合筛选(假设 option1_name = "color", option2_name = "size")**: | ||
| 543 | -```json | ||
| 544 | -{ | ||
| 545 | - "query": "芭比娃娃", | ||
| 546 | - "sku_filter_dimension": ["color", "size"] | ||
| 547 | -} | ||
| 548 | -``` | ||
| 549 | - | ||
| 550 | -### 3.7 搜索建议接口 | ||
| 551 | - | ||
| 552 | -- **端点**: `GET /search/suggestions` | ||
| 553 | -- **描述**: 返回搜索建议(自动补全/热词),支持多语言。 | ||
| 554 | - | ||
| 555 | -#### 查询参数 | ||
| 556 | - | ||
| 557 | -| 参数 | 类型 | 必填 | 默认值 | 描述 | | ||
| 558 | -|------|------|------|--------|------| | ||
| 559 | -| `q` | string | Y | - | 查询字符串(至少 1 个字符) | | ||
| 560 | -| `size` | integer | N | 10 | 返回建议数量(1-50) | | ||
| 561 | -| `language` | string | N | `en` | 请求语言,如 `zh` / `en` / `ar` / `ru`,用于路由到对应语种 suggestion 索引 | | ||
| 562 | -| `debug` | bool | N | `false` | 是否开启调试(目前主要用于排查 suggestion 排序与语言解析) | | ||
| 563 | - | ||
| 564 | -> **租户标识**:同 [3.1](#31-接口信息),通过请求头 `X-Tenant-ID` 或 query 参数 `tenant_id` 传递。 | ||
| 565 | - | ||
| 566 | -#### 响应示例 | ||
| 567 | - | ||
| 568 | -```json | ||
| 569 | -{ | ||
| 570 | - "query": "iph", | ||
| 571 | - "language": "en", | ||
| 572 | - "resolved_language": "en", | ||
| 573 | - "suggestions": [ | ||
| 574 | - { | ||
| 575 | - "text": "iphone 15", | ||
| 576 | - "lang": "en", | ||
| 577 | - "score": 12.37, | ||
| 578 | - "rank_score": 5.1, | ||
| 579 | - "sources": ["query_log", "qanchor"], | ||
| 580 | - "lang_source": "log_field", | ||
| 581 | - "lang_confidence": 1.0, | ||
| 582 | - "lang_conflict": false | ||
| 583 | - } | ||
| 584 | - ], | ||
| 585 | - "took_ms": 12 | ||
| 586 | -} | ||
| 587 | -``` | ||
| 588 | - | ||
| 589 | -#### 请求示例 | ||
| 590 | - | ||
| 591 | -```bash | ||
| 592 | -curl "http://localhost:6002/search/suggestions?q=芭&size=5&language=zh" \ | ||
| 593 | - -H "X-Tenant-ID: 162" | ||
| 594 | -``` | ||
| 595 | - | ||
| 596 | -### 3.8 即时搜索接口 | ||
| 597 | - | ||
| 598 | -> ⚠️ 当前版本未开放该能力。接口会明确返回 `501 Not Implemented`,避免误用未完成实现。 | ||
| 599 | - | ||
| 600 | -- **端点**: `GET /search/instant` | ||
| 601 | -- **描述**: 即时搜索预留端点,后续会在独立实现完成后开放。 | ||
| 602 | - | ||
| 603 | -#### 查询参数 | ||
| 604 | - | ||
| 605 | -| 参数 | 类型 | 必填 | 默认值 | 描述 | | ||
| 606 | -|------|------|------|--------|------| | ||
| 607 | -| `q` | string | Y | - | 搜索查询(至少 2 个字符) | | ||
| 608 | -| `size` | integer | N | 5 | 返回结果数量(1-20) | | ||
| 609 | - | ||
| 610 | -#### 请求示例 | ||
| 611 | - | ||
| 612 | -```bash | ||
| 613 | -curl "http://localhost:6002/search/instant?q=玩具&size=5" | ||
| 614 | -``` | ||
| 615 | - | ||
| 616 | -#### 当前响应 | ||
| 617 | - | ||
| 618 | -```json | ||
| 619 | -{ | ||
| 620 | - "error": "/search/instant is not implemented yet. Use POST /search/ for production traffic.", | ||
| 621 | - "status_code": 501 | ||
| 622 | -} | ||
| 623 | -``` | ||
| 624 | - | ||
| 625 | -### 3.9 获取单个文档 | ||
| 626 | - | ||
| 627 | -- **端点**: `GET /search/{doc_id}` | ||
| 628 | -- **描述**: 根据文档 ID 获取单个商品详情,用于点击结果后的详情页或排查问题。 | ||
| 629 | -- **租户标识**:同 [3.1](#31-接口信息),通过请求头 `X-Tenant-ID` 或 query 参数 `tenant_id` 传递。 | ||
| 630 | - | ||
| 631 | -#### 路径参数 | ||
| 632 | - | ||
| 633 | -| 参数 | 类型 | 描述 | | ||
| 634 | -|------|------|------| | ||
| 635 | -| `doc_id` | string | 商品或文档 ID | | ||
| 636 | - | ||
| 637 | -#### 响应示例 | ||
| 638 | - | ||
| 639 | -```json | ||
| 640 | -{ | ||
| 641 | - "id": "12345", | ||
| 642 | - "source": { | ||
| 643 | - "title": { | ||
| 644 | - "zh": "芭比时尚娃娃" | ||
| 645 | - }, | ||
| 646 | - "min_price": 89.99, | ||
| 647 | - "category1_name": "玩具" | ||
| 648 | - } | ||
| 649 | -} | ||
| 650 | -``` | ||
| 651 | - | ||
| 652 | -#### 请求示例 | ||
| 653 | - | ||
| 654 | -```bash | ||
| 655 | -curl "http://localhost:6002/search/12345" -H "X-Tenant-ID: 162" | ||
| 656 | -# 或使用 query 参数:curl "http://localhost:6002/search/12345?tenant_id=162" | ||
| 657 | -``` | ||
| 658 | - | ||
| 659 | ---- | ||
| 660 | - | ||
| 661 | -## 响应格式说明 | ||
| 662 | - | ||
| 663 | -### 4.1 标准响应结构 | ||
| 664 | - | ||
| 665 | -```json | ||
| 666 | -{ | ||
| 667 | - "results": [ | ||
| 668 | - { | ||
| 669 | - "spu_id": "12345", | ||
| 670 | - "title": "芭比时尚娃娃", | ||
| 671 | - "brief": "高品质芭比娃娃", | ||
| 672 | - "description": "详细描述...", | ||
| 673 | - "vendor": "美泰", | ||
| 674 | - "category": "玩具", | ||
| 675 | - "category_path": "玩具/娃娃/时尚", | ||
| 676 | - "category_name": "时尚", | ||
| 677 | - "category_id": "cat_001", | ||
| 678 | - "category_level": 3, | ||
| 679 | - "category1_name": "玩具", | ||
| 680 | - "category2_name": "娃娃", | ||
| 681 | - "category3_name": "时尚", | ||
| 682 | - "tags": ["娃娃", "玩具", "女孩"], | ||
| 683 | - "price": 89.99, | ||
| 684 | - "compare_at_price": 129.99, | ||
| 685 | - "currency": "USD", | ||
| 686 | - "image_url": "https://example.com/image.jpg", | ||
| 687 | - "in_stock": true, | ||
| 688 | - "sku_prices": [89.99, 99.99, 109.99], | ||
| 689 | - "sku_weights": [100, 150, 200], | ||
| 690 | - "sku_weight_units": ["g", "g", "g"], | ||
| 691 | - "total_inventory": 500, | ||
| 692 | - "option1_name": "color", | ||
| 693 | - "option2_name": "size", | ||
| 694 | - "option3_name": null, | ||
| 695 | - "specifications": [ | ||
| 696 | - {"sku_id": "sku_001", "name": "color", "value": "pink"}, | ||
| 697 | - {"sku_id": "sku_001", "name": "size", "value": "standard"} | ||
| 698 | - ], | ||
| 699 | - "skus": [ | ||
| 700 | - { | ||
| 701 | - "sku_id": "67890", | ||
| 702 | - "price": 89.99, | ||
| 703 | - "compare_at_price": 129.99, | ||
| 704 | - "sku": "BARBIE-001", | ||
| 705 | - "stock": 100, | ||
| 706 | - "weight": 0.1, | ||
| 707 | - "weight_unit": "kg", | ||
| 708 | - "option1_value": "pink", | ||
| 709 | - "option2_value": "standard", | ||
| 710 | - "option3_value": null, | ||
| 711 | - "image_src": "https://example.com/sku1.jpg" | ||
| 712 | - } | ||
| 713 | - ], | ||
| 714 | - "relevance_score": 8.5 | ||
| 715 | - } | ||
| 716 | - ], | ||
| 717 | - "total": 118, | ||
| 718 | - "max_score": 8.5, | ||
| 719 | - "facets": [ | ||
| 720 | - { | ||
| 721 | - "field": "category1_name", | ||
| 722 | - "label": "category1_name", | ||
| 723 | - "type": "terms", | ||
| 724 | - "values": [ | ||
| 725 | - { | ||
| 726 | - "value": "玩具", | ||
| 727 | - "label": "玩具", | ||
| 728 | - "count": 85, | ||
| 729 | - "selected": false | ||
| 730 | - } | ||
| 731 | - ] | ||
| 732 | - }, | ||
| 733 | - { | ||
| 734 | - "field": "specifications.color", | ||
| 735 | - "label": "color", | ||
| 736 | - "type": "terms", | ||
| 737 | - "values": [ | ||
| 738 | - { | ||
| 739 | - "value": "pink", | ||
| 740 | - "label": "pink", | ||
| 741 | - "count": 30, | ||
| 742 | - "selected": false | ||
| 743 | - } | ||
| 744 | - ] | ||
| 745 | - } | ||
| 746 | - ], | ||
| 747 | - "query_info": { | ||
| 748 | - "original_query": "芭比娃娃", | ||
| 749 | - "query_normalized": "芭比娃娃", | ||
| 750 | - "rewritten_query": "芭比娃娃", | ||
| 751 | - "detected_language": "zh", | ||
| 752 | - "translations": { | ||
| 753 | - "en": "barbie doll" | ||
| 754 | - }, | ||
| 755 | - "domain": "default" | ||
| 756 | - }, | ||
| 757 | - "suggestions": [], | ||
| 758 | - "related_searches": [], | ||
| 759 | - "took_ms": 45, | ||
| 760 | - "performance_info": null, | ||
| 761 | - "debug_info": null | ||
| 762 | -} | ||
| 763 | -``` | ||
| 764 | - | ||
| 765 | -### 4.2 响应字段说明 | ||
| 766 | - | ||
| 767 | -| 字段 | 类型 | 说明 | | ||
| 768 | -|------|------|------| | ||
| 769 | -| `results` | array | 搜索结果列表(SpuResult对象数组) | | ||
| 770 | -| `results[].spu_id` | string | SPU ID | | ||
| 771 | -| `results[].title` | string | 商品标题 | | ||
| 772 | -| `results[].price` | float | 价格(min_price) | | ||
| 773 | -| `results[].skus` | array | SKU列表(如果指定了`sku_filter_dimension`,则按维度过滤后的SKU) | | ||
| 774 | -| `results[].relevance_score` | float | 相关性分数 | | ||
| 775 | -| `total` | integer | 匹配的总文档数 | | ||
| 776 | -| `max_score` | float | 最高相关性分数 | | ||
| 777 | -| `facets` | array | 分面统计结果 | | ||
| 778 | -| `query_info` | object | query处理信息 | | ||
| 779 | -| `took_ms` | integer | 搜索耗时(毫秒) | | ||
| 780 | -| `debug_info` | object/null | 调试信息,仅当请求传 `debug=true` 时返回 | | ||
| 781 | - | ||
| 782 | -#### 4.2.1 query_info 说明 | ||
| 783 | - | ||
| 784 | -`query_info` 包含本次搜索的查询解析与处理结果: | ||
| 785 | - | ||
| 786 | -| 子字段 | 类型 | 说明 | | ||
| 787 | -|--------|------|------| | ||
| 788 | -| `original_query` | string | 用户原始查询 | | ||
| 789 | -| `query_normalized` | string | 归一化后的查询(去空白、大小写等预处理,用于后续解析与改写) | | ||
| 790 | -| `rewritten_query` | string | 重写后的查询(同义词/词典扩展等) | | ||
| 791 | -| `detected_language` | string | 检测到的查询语言(如 `zh`、`en`) | | ||
| 792 | -| `translations` | object | 翻译结果,键为语言代码,值为翻译文本 | | ||
| 793 | -| `domain` | string | 查询域(如 `default`、`title`、`brand` 等) | | ||
| 794 | - | ||
| 795 | -#### 4.2.2 debug_info 说明 | ||
| 796 | - | ||
| 797 | -`debug_info` 主要用于检索效果评估、融合打分分析与 bad case 排查。 | ||
| 798 | - | ||
| 799 | -`debug_info.query_analysis` 常见字段: | ||
| 800 | - | ||
| 801 | -| 子字段 | 类型 | 说明 | | ||
| 802 | -|--------|------|------| | ||
| 803 | -| `original_query` | string | 原始查询 | | ||
| 804 | -| `query_normalized` | string | 归一化后的查询 | | ||
| 805 | -| `rewritten_query` | string | 重写后的查询 | | ||
| 806 | -| `detected_language` | string | 检测到的语言 | | ||
| 807 | -| `translations` | object | 翻译结果 | | ||
| 808 | -| `query_text_by_lang` | object | 实际参与检索的多语言 query 文本 | | ||
| 809 | -| `search_langs` | array[string] | 实际参与检索的语言列表 | | ||
| 810 | -| `supplemental_search_langs` | array[string] | 因 mixed query 补入的附加语言列表 | | ||
| 811 | -| `has_vector` | boolean | 是否生成了向量 | | ||
| 812 | - | ||
| 813 | -`debug_info.per_result[]` 常见字段: | ||
| 814 | - | ||
| 815 | -| 子字段 | 类型 | 说明 | | ||
| 816 | -|--------|------|------| | ||
| 817 | -| `spu_id` | string | 结果 SPU ID | | ||
| 818 | -| `es_score` | float | ES 原始 `_score` | | ||
| 819 | -| `rerank_score` | float | 重排分数 | | ||
| 820 | -| `text_score` | float | 文本相关性大分(由 `base_query` / `base_query_trans_*` / `fallback_original_query_*` 聚合而来) | | ||
| 821 | -| `text_source_score` | float | `base_query` 分数 | | ||
| 822 | -| `text_translation_score` | float | `base_query_trans_*` 里的最大分数 | | ||
| 823 | -| `text_fallback_score` | float | `fallback_original_query_*` 里的最大分数 | | ||
| 824 | -| `text_primary_score` | float | 文本大分中的主证据部分 | | ||
| 825 | -| `text_support_score` | float | 文本大分中的辅助证据部分 | | ||
| 826 | -| `knn_score` | float | `knn_query` 分数 | | ||
| 827 | -| `fused_score` | float | 最终融合分数 | | ||
| 828 | -| `matched_queries` | object/array | ES named queries 命中详情 | | ||
| 829 | - | ||
| 830 | -### 4.3 SpuResult字段说明 | ||
| 831 | - | ||
| 832 | -| 字段 | 类型 | 说明 | | ||
| 833 | -|------|------|------| | ||
| 834 | -| `spu_id` | string | SPU ID | | ||
| 835 | -| `title` | string | 商品标题(根据language参数自动选择 `title.zh` 或 `title.en`) | | ||
| 836 | -| `brief` | string | 商品短描述(根据language参数自动选择) | | ||
| 837 | -| `description` | string | 商品详细描述(根据language参数自动选择) | | ||
| 838 | -| `vendor` | string | 供应商/品牌(根据language参数自动选择) | | ||
| 839 | -| `category` | string | 类目(兼容字段,等同于category_name) | | ||
| 840 | -| `category_path` | string | 类目路径(多级,用于面包屑,根据language参数自动选择) | | ||
| 841 | -| `category_name` | string | 类目名称(展示用,根据language参数自动选择) | | ||
| 842 | -| `category_id` | string | 类目ID | | ||
| 843 | -| `category_level` | integer | 类目层级(1/2/3) | | ||
| 844 | -| `category1_name` | string | 一级类目名称 | | ||
| 845 | -| `category2_name` | string | 二级类目名称 | | ||
| 846 | -| `category3_name` | string | 三级类目名称 | | ||
| 847 | -| `tags` | array[string] | 标签列表 | | ||
| 848 | -| `price` | float | 价格(min_price) | | ||
| 849 | -| `compare_at_price` | float | 原价 | | ||
| 850 | -| `currency` | string | 货币单位(默认USD) | | ||
| 851 | -| `image_url` | string | 主图URL | | ||
| 852 | -| `in_stock` | boolean | 是否有库存(任意SKU有库存即为true) | | ||
| 853 | -| `sku_prices` | array[float] | 所有SKU价格列表 | | ||
| 854 | -| `sku_weights` | array[integer] | 所有SKU重量列表 | | ||
| 855 | -| `sku_weight_units` | array[string] | 所有SKU重量单位列表 | | ||
| 856 | -| `total_inventory` | integer | 总库存 | | ||
| 857 | -| `sales` | integer | 销量(展示销量) | | ||
| 858 | -| `option1_name` | string | 选项1名称(如"color") | | ||
| 859 | -| `option2_name` | string | 选项2名称(如"size") | | ||
| 860 | -| `option3_name` | string | 选项3名称 | | ||
| 861 | -| `specifications` | array[object] | 规格列表(与ES specifications字段对应) | | ||
| 862 | -| `skus` | array | SKU 列表 | | ||
| 863 | -| `relevance_score` | float | 相关性分数(默认为 ES 原始分数;当开启 AI 搜索时为融合后的最终分数) | | ||
| 864 | - | ||
| 865 | -### 4.4 SkuResult字段说明 | ||
| 866 | - | ||
| 867 | -| 字段 | 类型 | 说明 | | ||
| 868 | -|------|------|------| | ||
| 869 | -| `sku_id` | string | SKU ID | | ||
| 870 | -| `price` | float | 价格 | | ||
| 871 | -| `compare_at_price` | float | 原价 | | ||
| 872 | -| `sku` | string | SKU编码(sku_code) | | ||
| 873 | -| `stock` | integer | 库存数量 | | ||
| 874 | -| `weight` | float | 重量 | | ||
| 875 | -| `weight_unit` | string | 重量单位 | | ||
| 876 | -| `option1_value` | string | 选项1取值(如color值) | | ||
| 877 | -| `option2_value` | string | 选项2取值(如size值) | | ||
| 878 | -| `option3_value` | string | 选项3取值 | | ||
| 879 | -| `image_src` | string | SKU图片地址 | | ||
| 880 | - | ||
| 881 | -### 4.5 多语言字段说明 | ||
| 882 | - | ||
| 883 | -- `title`, `brief`, `description`, `vendor`, `category_path`, `category_name` 会根据请求的 `language` 参数自动选择对应的中英文字段 | ||
| 884 | -- `language="zh"`: 优先返回 `*_zh` 字段,如果为空则回退到 `*_en` 字段 | ||
| 885 | -- `language="en"`: 优先返回 `*_en` 字段,如果为空则回退到 `*_zh` 字段 | ||
| 886 | - | ||
| 887 | ---- | ||
| 888 | - | ||
| 889 | -## 索引接口 | ||
| 890 | - | ||
| 891 | -本节内容与 `api/routes/indexer.py` 中的索引相关服务一致,包含以下接口: | ||
| 892 | - | ||
| 893 | -| 接口 | 方法 | 路径 | 说明 | | ||
| 894 | -|------|------|------|------| | ||
| 895 | -| 全量重建索引 | POST | `/indexer/reindex` | 将指定租户所有 SPU 导入 ES(不删现有索引) | | ||
| 896 | -| 增量索引 | POST | `/indexer/index` | 按 SPU ID 列表索引/删除,支持自动检测删除与显式删除 | | ||
| 897 | -| 查询文档 | POST | `/indexer/documents` | 按 SPU ID 列表查询 ES 文档,不写入 ES | | ||
| 898 | -| 构建 ES 文档(正式) | POST | `/indexer/build-docs` | 由上游提供 MySQL 行数据,返回 ES-ready 文档,不写 ES | | ||
| 899 | -| 构建 ES 文档(测试) | POST | `/indexer/build-docs-from-db` | 由本服务查库并构建文档,仅测试/调试用 | | ||
| 900 | -| 内容理解字段生成 | POST | `/indexer/enrich-content` | 根据商品标题批量生成 qanchors、semantic_attributes、tags(供微服务组合方式使用) | | ||
| 901 | -| 索引健康检查 | GET | `/indexer/health` | 检查索引服务与数据库连接状态 | | ||
| 902 | - | ||
| 903 | -#### 5.0 支撑外部 indexer 的三种方式 | ||
| 904 | - | ||
| 905 | -本服务对**外部 indexer 程序**(如 Java 索引系统)提供三种对接方式,可按需选择: | ||
| 906 | - | ||
| 907 | -| 方式 | 说明 | 适用场景 | | ||
| 908 | -|------|------|----------| | ||
| 909 | -| **1)doc 填充接口** | 调用 `POST /indexer/build-docs` 或 `POST /indexer/build-docs-from-db`,由本服务基于 MySQL 行数据构建完整 ES 文档(含多语言、向量、规格等),**不写入 ES**,由调用方自行写入。 | 希望一站式拿到 ES-ready doc,由己方控制写 ES 的时机与索引名。 | | ||
| 910 | -| **2)微服务组合** | 单独调用**翻译**、**向量化**、**内容理解字段生成**等接口,由 indexer 程序自己组装 doc 并写入 ES。翻译与向量化为独立微服务(见第 7 节);内容理解为 Indexer 服务内接口 `POST /indexer/enrich-content`。 | 需要灵活编排、或希望将 LLM/向量等耗时步骤与主链路解耦(如异步补齐 qanchors/tags)。 | | ||
| 911 | -| **3)本服务直接写 ES** | 调用全量索引 `POST /indexer/reindex`、增量索引 `POST /indexer/index`(指定 SPU ID 列表),由本服务从 MySQL 拉数并直接写入 ES。 | 自建运维、联调或不需要由 Java 写 ES 的场景。 | | ||
| 912 | - | ||
| 913 | -- **方式 1** 与 **方式 2** 下,ES 的写入方均为外部 indexer(或 Java),职责清晰。 | ||
| 914 | -- **方式 3** 下,本服务同时负责读库、构建 doc 与写 ES。 | ||
| 915 | - | ||
| 916 | -### 5.1 为租户创建索引 | ||
| 917 | - | ||
| 918 | -为租户创建索引需要两个步骤: | ||
| 919 | - | ||
| 920 | -1. **创建索引结构**(可选,仅在需要更新 mapping 或在新环境首次创建时执行) | ||
| 921 | - - 使用脚本创建 ES 索引结构(基于 `mappings/search_products.json`) | ||
| 922 | - - 如果索引已存在,会提示用户确认(会删除现有数据) | ||
| 923 | - | ||
| 924 | -2. **导入数据**(必需) | ||
| 925 | - - 使用全量索引接口 `/indexer/reindex` 导入数据 | ||
| 926 | - | ||
| 927 | -**创建索引结构(支持多环境 namespace)**: | ||
| 928 | - | ||
| 929 | -```bash | ||
| 930 | -# 以 UAT 环境为例: | ||
| 931 | -# 1. 准备 UAT 环境的 .env(包含 UAT 的 ES_HOST/DB_HOST 等) | ||
| 932 | -# 2. 设置环境前缀(也可以直接在 .env 中配置): | ||
| 933 | -export RUNTIME_ENV=uat | ||
| 934 | -export ES_INDEX_NAMESPACE=uat_ | ||
| 935 | - | ||
| 936 | -# 3. 为 tenant_id=170 创建索引结构 | ||
| 937 | -./scripts/create_tenant_index.sh 170 | ||
| 938 | -``` | ||
| 939 | - | ||
| 940 | -脚本会自动从项目根目录的 `.env` 文件加载 ES 配置,并根据 `ES_INDEX_NAMESPACE` 创建: | ||
| 941 | - | ||
| 942 | -- prod 环境(ES_INDEX_NAMESPACE 为空):`search_products_tenant_170` | ||
| 943 | -- UAT 环境(ES_INDEX_NAMESPACE=uat_):`uat_search_products_tenant_170` | ||
| 944 | - | ||
| 945 | -**注意事项**: | ||
| 946 | -- ⚠️ 如果索引已存在,脚本会提示确认,确认后会删除现有数据 | ||
| 947 | -- 创建索引后,**必须**调用 `/indexer/reindex` 导入数据 | ||
| 948 | -- 如果只是更新数据而不需要修改索引结构,直接使用 `/indexer/reindex` 即可 | ||
| 949 | - | ||
| 950 | ---- | ||
| 951 | - | ||
| 952 | -### 5.2 全量索引接口 | ||
| 953 | - | ||
| 954 | -- **端点**: `POST /indexer/reindex` | ||
| 955 | -- **描述**: 全量索引,将指定租户的所有SPU数据导入到ES索引(不会删除现有索引)。**推荐仅用于自测/运维场景**;生产环境下更推荐由 Java 等上游控制调度与写 ES。 | ||
| 956 | - | ||
| 957 | -#### 请求参数 | ||
| 958 | - | ||
| 959 | -```json | ||
| 960 | -{ | ||
| 961 | - "tenant_id": "162", | ||
| 962 | - "batch_size": 500 | ||
| 963 | -} | ||
| 964 | -``` | ||
| 965 | - | ||
| 966 | -| 参数 | 类型 | 必填 | 默认值 | 说明 | | ||
| 967 | -|------|------|------|--------|------| | ||
| 968 | -| `tenant_id` | string | Y | - | 租户ID | | ||
| 969 | -| `batch_size` | integer | N | 500 | 批量导入大小 | | ||
| 970 | - | ||
| 971 | -#### 响应格式 | ||
| 972 | - | ||
| 973 | -**成功响应(200 OK)**(示例,实际 `index_name` 会带上 tenant 和环境前缀): | ||
| 974 | -```json | ||
| 975 | -{ | ||
| 976 | - "success": true, | ||
| 977 | - "total": 1000, | ||
| 978 | - "indexed": 1000, | ||
| 979 | - "failed": 0, | ||
| 980 | - "elapsed_time": 12.34, | ||
| 981 | - "index_name": "search_products_tenant_162", | ||
| 982 | - "tenant_id": "162" | ||
| 983 | -} | ||
| 984 | -``` | ||
| 985 | - | ||
| 986 | -**错误响应**: | ||
| 987 | -- `400 Bad Request`: 参数错误 | ||
| 988 | -- `503 Service Unavailable`: 服务未初始化 | ||
| 989 | - | ||
| 990 | -#### 请求示例 | ||
| 991 | - | ||
| 992 | -**全量索引(不会删除现有索引)**: | ||
| 993 | -```bash | ||
| 994 | -curl -X POST "http://localhost:6004/indexer/reindex" \ | ||
| 995 | - -H "Content-Type: application/json" \ | ||
| 996 | - -d '{ | ||
| 997 | - "tenant_id": "162", | ||
| 998 | - "batch_size": 500 | ||
| 999 | - }' | ||
| 1000 | -``` | ||
| 1001 | - | ||
| 1002 | -**查看日志**: | ||
| 1003 | -```bash | ||
| 1004 | -# 查看API日志(包含索引操作日志) | ||
| 1005 | -tail -f logs/api.log | ||
| 1006 | - | ||
| 1007 | -# 或者查看所有日志文件 | ||
| 1008 | -tail -f logs/*.log | ||
| 1009 | -``` | ||
| 1010 | - | ||
| 1011 | -> ⚠️ **重要提示**:如需 **创建索引结构**,请参考 [5.1 为租户创建索引](#51-为租户创建索引) 章节,使用 `./scripts/create_tenant_index.sh <tenant_id>`。创建后需要调用 `/indexer/reindex` 导入数据。 | ||
| 1012 | - | ||
| 1013 | -**查看索引日志**: | ||
| 1014 | - | ||
| 1015 | -索引操作的所有关键信息都会记录到 `logs/indexer.log` 文件中(JSON 格式),包括: | ||
| 1016 | -- 请求开始和结束时间 | ||
| 1017 | -- 租户ID、SPU ID、操作类型 | ||
| 1018 | -- 每个SPU的处理状态 | ||
| 1019 | -- ES批量写入结果 | ||
| 1020 | -- 成功/失败统计和详细错误信息 | ||
| 1021 | - | ||
| 1022 | -```bash | ||
| 1023 | -# 实时查看索引日志(包含全量和增量索引的所有操作) | ||
| 1024 | -tail -f logs/indexer.log | ||
| 1025 | - | ||
| 1026 | -# 使用 grep 查询(简单方式) | ||
| 1027 | -# 查看全量索引日志 | ||
| 1028 | -grep "\"index_type\":\"bulk\"" logs/indexer.log | tail -100 | ||
| 1029 | - | ||
| 1030 | -# 查看增量索引日志 | ||
| 1031 | -grep "\"index_type\":\"incremental\"" logs/indexer.log | tail -100 | ||
| 1032 | - | ||
| 1033 | -# 查看特定租户的索引日志 | ||
| 1034 | -grep "\"tenant_id\":\"162\"" logs/indexer.log | tail -100 | ||
| 1035 | - | ||
| 1036 | -# 使用 jq 查询(推荐,更精确的 JSON 查询) | ||
| 1037 | -# 安装 jq: sudo apt-get install jq 或 brew install jq | ||
| 1038 | - | ||
| 1039 | -# 查看全量索引日志 | ||
| 1040 | -cat logs/indexer.log | jq 'select(.index_type == "bulk")' | tail -100 | ||
| 1041 | - | ||
| 1042 | -# 查看增量索引日志 | ||
| 1043 | -cat logs/indexer.log | jq 'select(.index_type == "incremental")' | tail -100 | ||
| 1044 | - | ||
| 1045 | -# 查看特定租户的索引日志 | ||
| 1046 | -cat logs/indexer.log | jq 'select(.tenant_id == "162")' | tail -100 | ||
| 1047 | - | ||
| 1048 | -# 查看失败的索引操作 | ||
| 1049 | -cat logs/indexer.log | jq 'select(.operation == "request_complete" and .failed_count > 0)' | ||
| 1050 | - | ||
| 1051 | -# 查看特定SPU的处理日志 | ||
| 1052 | -cat logs/indexer.log | jq 'select(.spu_id == "123")' | ||
| 1053 | - | ||
| 1054 | -# 查看最近的索引请求统计 | ||
| 1055 | -cat logs/indexer.log | jq 'select(.operation == "request_complete") | {timestamp, index_type, tenant_id, total_count, success_count, failed_count, elapsed_time}' | ||
| 1056 | -``` | ||
| 1057 | - | ||
| 1058 | -### 5.3 增量索引接口 | ||
| 1059 | - | ||
| 1060 | -- **端点**: `POST /indexer/index` | ||
| 1061 | -- **描述**: 增量索引接口,根据指定的SPU ID列表进行索引,直接将数据写入ES。用于增量更新指定商品。**推荐仅作为内部/调试入口**;正式对接建议改用 `/indexer/build-docs`,由上游写 ES。 | ||
| 1062 | - | ||
| 1063 | -**删除说明**: | ||
| 1064 | -- `spu_ids`中的SPU:如果数据库`deleted=1`,自动从ES删除,响应状态为`deleted` | ||
| 1065 | -- `delete_spu_ids`中的SPU:直接删除,响应状态为`deleted`、`not_found`或`failed` | ||
| 1066 | - | ||
| 1067 | -#### 请求参数 | ||
| 1068 | - | ||
| 1069 | -```json | ||
| 1070 | -{ | ||
| 1071 | - "tenant_id": "162", | ||
| 1072 | - "spu_ids": ["123", "456", "789"], | ||
| 1073 | - "delete_spu_ids": ["100", "101"] | ||
| 1074 | -} | ||
| 1075 | -``` | ||
| 1076 | - | ||
| 1077 | -| 参数 | 类型 | 必填 | 说明 | | ||
| 1078 | -|------|------|------|------| | ||
| 1079 | -| `tenant_id` | string | Y | 租户ID | | ||
| 1080 | -| `spu_ids` | array[string] | N | SPU ID列表(1-100个),要索引的SPU。如果为空,则只执行删除操作 | | ||
| 1081 | -| `delete_spu_ids` | array[string] | N | 显式指定要删除的SPU ID列表(1-100个),可选。无论数据库状态如何,都会从ES中删除这些SPU | | ||
| 1082 | - | ||
| 1083 | -**注意**: | ||
| 1084 | -- `spu_ids` 和 `delete_spu_ids` 不能同时为空 | ||
| 1085 | -- 每个列表最多支持100个SPU ID | ||
| 1086 | -- 如果SPU在`spu_ids`中且数据库`deleted=1`,会自动从ES删除(自动检测删除) | ||
| 1087 | - | ||
| 1088 | -#### 响应格式 | ||
| 1089 | - | ||
| 1090 | -```json | ||
| 1091 | -{ | ||
| 1092 | - "spu_ids": [ | ||
| 1093 | - { | ||
| 1094 | - "spu_id": "123", | ||
| 1095 | - "status": "indexed" | ||
| 1096 | - }, | ||
| 1097 | - { | ||
| 1098 | - "spu_id": "456", | ||
| 1099 | - "status": "deleted" | ||
| 1100 | - }, | ||
| 1101 | - { | ||
| 1102 | - "spu_id": "789", | ||
| 1103 | - "status": "failed", | ||
| 1104 | - "msg": "SPU not found (unexpected)" | ||
| 1105 | - } | ||
| 1106 | - ], | ||
| 1107 | - "delete_spu_ids": [ | ||
| 1108 | - { | ||
| 1109 | - "spu_id": "100", | ||
| 1110 | - "status": "deleted" | ||
| 1111 | - }, | ||
| 1112 | - { | ||
| 1113 | - "spu_id": "101", | ||
| 1114 | - "status": "not_found" | ||
| 1115 | - }, | ||
| 1116 | - { | ||
| 1117 | - "spu_id": "102", | ||
| 1118 | - "status": "failed", | ||
| 1119 | - "msg": "Failed to delete from ES: Connection timeout" | ||
| 1120 | - } | ||
| 1121 | - ], | ||
| 1122 | - "total": 6, | ||
| 1123 | - "success_count": 4, | ||
| 1124 | - "failed_count": 2, | ||
| 1125 | - "elapsed_time": 1.23, | ||
| 1126 | - "index_name": "search_products", | ||
| 1127 | - "tenant_id": "162" | ||
| 1128 | -} | ||
| 1129 | -``` | ||
| 1130 | - | ||
| 1131 | -| 字段 | 类型 | 说明 | | ||
| 1132 | -|------|------|------| | ||
| 1133 | -| `spu_ids` | array | spu_ids对应的响应列表,每个元素包含 `spu_id` 和 `status` | | ||
| 1134 | -| `spu_ids[].status` | string | 状态:`indexed`(已索引)、`deleted`(已删除,自动检测)、`failed`(失败) | | ||
| 1135 | -| `spu_ids[].msg` | string | 当status为`failed`时,包含失败原因(可选) | | ||
| 1136 | -| `delete_spu_ids` | array | delete_spu_ids对应的响应列表,每个元素包含 `spu_id` 和 `status` | | ||
| 1137 | -| `delete_spu_ids[].status` | string | 状态:`deleted`(已删除)、`not_found`(ES中不存在)、`failed`(失败) | | ||
| 1138 | -| `delete_spu_ids[].msg` | string | 当status为`failed`时,包含失败原因(可选) | | ||
| 1139 | -| `total` | integer | 总处理数量(spu_ids数量 + delete_spu_ids数量) | | ||
| 1140 | -| `success_count` | integer | 成功数量(indexed + deleted + not_found) | | ||
| 1141 | -| `failed_count` | integer | 失败数量 | | ||
| 1142 | -| `elapsed_time` | float | 耗时(秒) | | ||
| 1143 | -| `index_name` | string | 索引名称 | | ||
| 1144 | -| `tenant_id` | string | 租户ID | | ||
| 1145 | - | ||
| 1146 | -**状态说明**: | ||
| 1147 | -- `spu_ids` 的状态: | ||
| 1148 | - - `indexed`: SPU已成功索引到ES | ||
| 1149 | - - `deleted`: SPU在数据库中被标记为deleted=1,已从ES删除(自动检测) | ||
| 1150 | - - `failed`: 处理失败,会包含`msg`字段说明失败原因 | ||
| 1151 | -- `delete_spu_ids` 的状态: | ||
| 1152 | - - `deleted`: SPU已从ES成功删除 | ||
| 1153 | - - `not_found`: SPU在ES中不存在(也算成功,可能已经被删除过) | ||
| 1154 | - - `failed`: 删除失败,会包含`msg`字段说明失败原因 | ||
| 1155 | - | ||
| 1156 | -#### 请求示例 | ||
| 1157 | - | ||
| 1158 | -**示例1:普通增量索引(自动检测删除)**: | ||
| 1159 | -```bash | ||
| 1160 | -curl -X POST "http://localhost:6004/indexer/index" \ | ||
| 1161 | - -H "Content-Type: application/json" \ | ||
| 1162 | - -d '{ | ||
| 1163 | - "tenant_id": "162", | ||
| 1164 | - "spu_ids": ["123", "456", "789"] | ||
| 1165 | - }' | ||
| 1166 | -``` | ||
| 1167 | -说明:如果SPU 456在数据库中`deleted=1`,会自动从ES删除,在响应中`spu_ids`列表里456的状态为`deleted`。 | ||
| 1168 | - | ||
| 1169 | -**示例2:显式删除(批量删除)**: | ||
| 1170 | -```bash | ||
| 1171 | -curl -X POST "http://localhost:6004/indexer/index" \ | ||
| 1172 | - -H "Content-Type: application/json" \ | ||
| 1173 | - -d '{ | ||
| 1174 | - "tenant_id": "162", | ||
| 1175 | - "spu_ids": ["123", "456"], | ||
| 1176 | - "delete_spu_ids": ["100", "101", "102"] | ||
| 1177 | - }' | ||
| 1178 | -``` | ||
| 1179 | -说明:SPU 100、101、102会被显式删除,无论数据库状态如何。 | ||
| 1180 | - | ||
| 1181 | -**示例3:仅删除(不索引)**: | ||
| 1182 | -```bash | ||
| 1183 | -curl -X POST "http://localhost:6004/indexer/index" \ | ||
| 1184 | - -H "Content-Type: application/json" \ | ||
| 1185 | - -d '{ | ||
| 1186 | - "tenant_id": "162", | ||
| 1187 | - "spu_ids": [], | ||
| 1188 | - "delete_spu_ids": ["100", "101"] | ||
| 1189 | - }' | ||
| 1190 | -``` | ||
| 1191 | -说明:只执行删除操作,不进行索引。 | ||
| 1192 | - | ||
| 1193 | -**示例4:混合操作(索引+删除)**: | ||
| 1194 | -```bash | ||
| 1195 | -curl -X POST "http://localhost:6004/indexer/index" \ | ||
| 1196 | - -H "Content-Type: application/json" \ | ||
| 1197 | - -d '{ | ||
| 1198 | - "tenant_id": "162", | ||
| 1199 | - "spu_ids": ["123", "456", "789"], | ||
| 1200 | - "delete_spu_ids": ["100", "101"] | ||
| 1201 | - }' | ||
| 1202 | -``` | ||
| 1203 | -说明:同时执行索引和删除操作。 | ||
| 1204 | - | ||
| 1205 | -#### 日志说明 | ||
| 1206 | - | ||
| 1207 | -增量索引操作的所有关键信息都会记录到 `logs/indexer.log` 文件中(JSON格式),包括: | ||
| 1208 | -- 请求开始和结束时间 | ||
| 1209 | -- 每个SPU的处理状态(获取、转换、索引、删除) | ||
| 1210 | -- ES批量写入结果 | ||
| 1211 | -- 成功/失败统计 | ||
| 1212 | -- 详细的错误信息 | ||
| 1213 | - | ||
| 1214 | -日志查询方式请参考[5.1节查看索引日志](#51-全量重建索引接口)部分。 | ||
| 1215 | - | ||
| 1216 | -### 5.4 查询文档接口 | ||
| 1217 | - | ||
| 1218 | -- **端点**: `POST /indexer/documents` | ||
| 1219 | -- **描述**: 查询文档接口,根据SPU ID列表获取ES文档数据(**不写入ES**)。用于查看、调试或验证SPU数据。 | ||
| 1220 | - | ||
| 1221 | -#### 请求参数 | ||
| 1222 | - | ||
| 1223 | -```json | ||
| 1224 | -{ | ||
| 1225 | - "tenant_id": "162", | ||
| 1226 | - "spu_ids": ["123", "456", "789"] | ||
| 1227 | -} | ||
| 1228 | -``` | ||
| 1229 | - | ||
| 1230 | -| 参数 | 类型 | 必填 | 说明 | | ||
| 1231 | -|------|------|------|------| | ||
| 1232 | -| `tenant_id` | string | Y | 租户ID | | ||
| 1233 | -| `spu_ids` | array[string] | Y | SPU ID列表(1-100个) | | ||
| 1234 | - | ||
| 1235 | -#### 响应格式 | ||
| 1236 | - | ||
| 1237 | -```json | ||
| 1238 | -{ | ||
| 1239 | - "success": [ | ||
| 1240 | - { | ||
| 1241 | - "spu_id": "123", | ||
| 1242 | - "document": { | ||
| 1243 | - "tenant_id": "162", | ||
| 1244 | - "spu_id": "123", | ||
| 1245 | - "title": { | ||
| 1246 | - "zh": "商品标题" | ||
| 1247 | - }, | ||
| 1248 | - ... | ||
| 1249 | - } | ||
| 1250 | - }, | ||
| 1251 | - { | ||
| 1252 | - "spu_id": "456", | ||
| 1253 | - "document": {...} | ||
| 1254 | - } | ||
| 1255 | - ], | ||
| 1256 | - "failed": [ | ||
| 1257 | - { | ||
| 1258 | - "spu_id": "789", | ||
| 1259 | - "error": "SPU not found or deleted" | ||
| 1260 | - } | ||
| 1261 | - ], | ||
| 1262 | - "total": 3, | ||
| 1263 | - "success_count": 2, | ||
| 1264 | - "failed_count": 1 | ||
| 1265 | -} | ||
| 1266 | -``` | ||
| 1267 | - | ||
| 1268 | -| 字段 | 类型 | 说明 | | ||
| 1269 | -|------|------|------| | ||
| 1270 | -| `success` | array | 成功获取的SPU列表,每个元素包含 `spu_id` 和 `document`(完整的ES文档数据) | | ||
| 1271 | -| `failed` | array | 失败的SPU列表,每个元素包含 `spu_id` 和 `error`(失败原因) | | ||
| 1272 | -| `total` | integer | 总SPU数量 | | ||
| 1273 | -| `success_count` | integer | 成功数量 | | ||
| 1274 | -| `failed_count` | integer | 失败数量 | | ||
| 1275 | - | ||
| 1276 | -#### 请求示例 | ||
| 1277 | - | ||
| 1278 | -**单个SPU查询**: | ||
| 1279 | -```bash | ||
| 1280 | -curl -X POST "http://localhost:6004/indexer/documents" \ | ||
| 1281 | - -H "Content-Type: application/json" \ | ||
| 1282 | - -d '{ | ||
| 1283 | - "tenant_id": "162", | ||
| 1284 | - "spu_ids": ["123"] | ||
| 1285 | - }' | ||
| 1286 | -``` | ||
| 1287 | - | ||
| 1288 | -**批量SPU查询**: | ||
| 1289 | -```bash | ||
| 1290 | -curl -X POST "http://localhost:6004/indexer/documents" \ | ||
| 1291 | - -H "Content-Type: application/json" \ | ||
| 1292 | - -d '{ | ||
| 1293 | - "tenant_id": "162", | ||
| 1294 | - "spu_ids": ["123", "456", "789"] | ||
| 1295 | - }' | ||
| 1296 | -``` | ||
| 1297 | - | ||
| 1298 | -#### 与 `/indexer/index` 的区别 | ||
| 1299 | - | ||
| 1300 | -| 接口 | 功能 | 是否写入ES | 返回内容 | | ||
| 1301 | -|------|------|-----------|----------| | ||
| 1302 | -| `/indexer/documents` | 查询SPU文档数据 | 否 | 返回完整的ES文档数据 | | ||
| 1303 | -| `/indexer/index` | 增量索引 | 是 | 返回成功/失败列表和统计信息 | | ||
| 1304 | - | ||
| 1305 | -**使用场景**: | ||
| 1306 | -- `/indexer/documents`:用于查看、调试或验证SPU数据,不修改ES索引 | ||
| 1307 | -- `/indexer/index`:用于实际的增量索引操作,将更新的SPU数据同步到ES | ||
| 1308 | - | ||
| 1309 | -### 5.5 索引健康检查接口 | ||
| 1310 | - | ||
| 1311 | -- **端点**: `GET /indexer/health` | ||
| 1312 | -- **描述**: 检查索引服务健康状态(与 `api/routes/indexer.py` 中 `indexer_health_check` 一致) | ||
| 1313 | - | ||
| 1314 | -#### 响应格式 | ||
| 1315 | - | ||
| 1316 | -```json | ||
| 1317 | -{ | ||
| 1318 | - "status": "available", | ||
| 1319 | - "database": "connected", | ||
| 1320 | - "preloaded_data": { | ||
| 1321 | - "category_mappings": 150 | ||
| 1322 | - } | ||
| 1323 | -} | ||
| 1324 | -``` | ||
| 1325 | - | ||
| 1326 | -| 字段 | 类型 | 说明 | | ||
| 1327 | -|------|------|------| | ||
| 1328 | -| `status` | string | `available`(服务可用)、`unavailable`(未初始化)、`error`(异常) | | ||
| 1329 | -| `database` | string | 数据库连接状态,如 `connected` 或 `disconnected: ...` | | ||
| 1330 | -| `preloaded_data.category_mappings` | integer | 已加载的分类映射数量 | | ||
| 1331 | - | ||
| 1332 | -#### 请求示例 | ||
| 1333 | - | ||
| 1334 | -```bash | ||
| 1335 | -curl -X GET "http://localhost:6004/indexer/health" | ||
| 1336 | -``` | ||
| 1337 | - | ||
| 1338 | -### 5.6 文档构建接口(正式对接推荐) | ||
| 1339 | - | ||
| 1340 | -#### 5.6.1 `POST /indexer/build-docs` | ||
| 1341 | - | ||
| 1342 | -- **描述**: | ||
| 1343 | - 基于调用方(通常是 Java 索引程序)提供的 **MySQL 行数据** 构建 ES 文档(doc),**不写入 ES**。 | ||
| 1344 | - 由本服务负责“如何构建 doc”(多语言、翻译、向量、规格聚合等),由调用方负责“何时调度 + 如何写 ES”。 | ||
| 1345 | - | ||
| 1346 | -#### 请求参数 | ||
| 1347 | - | ||
| 1348 | -```json | ||
| 1349 | -{ | ||
| 1350 | - "tenant_id": "170", | ||
| 1351 | - "items": [ | ||
| 1352 | - { | ||
| 1353 | - "spu": { "id": 223167, "tenant_id": 170, "title": "..." }, | ||
| 1354 | - "skus": [ | ||
| 1355 | - { "id": 3988393, "spu_id": 223167, "price": 25.99, "compare_at_price": 25.99 } | ||
| 1356 | - ], | ||
| 1357 | - "options": [] | ||
| 1358 | - } | ||
| 1359 | - ] | ||
| 1360 | -} | ||
| 1361 | -``` | ||
| 1362 | - | ||
| 1363 | -| 参数 | 类型 | 必填 | 说明 | | ||
| 1364 | -|------|------|------|------| | ||
| 1365 | -| `tenant_id` | string | Y | 租户 ID | | ||
| 1366 | -| `items` | array | Y | 需构建 doc 的 SPU 列表(每项含 `spu`、`skus`、`options`),**单次最多 200 条** | | ||
| 1367 | - | ||
| 1368 | -> `spu` / `skus` / `options` 字段应当直接使用从 `shoplazza_product_spu` / `shoplazza_product_sku` / `shoplazza_product_option` 查询出的行字段。 | ||
| 1369 | - | ||
| 1370 | -#### 请求示例(完整 curl) | ||
| 1371 | - | ||
| 1372 | -> 完整请求体参考 `scripts/test_build_docs_api.py` 中的 `build_sample_request()`。 | ||
| 1373 | - | ||
| 1374 | -```bash | ||
| 1375 | -# 单条 SPU 示例(含 spu、skus、options) | ||
| 1376 | -curl -X POST "http://localhost:6004/indexer/build-docs" \ | ||
| 1377 | - -H "Content-Type: application/json" \ | ||
| 1378 | - -d '{ | ||
| 1379 | - "tenant_id": "162", | ||
| 1380 | - "items": [ | ||
| 1381 | - { | ||
| 1382 | - "spu": { | ||
| 1383 | - "id": 10001, | ||
| 1384 | - "tenant_id": "162", | ||
| 1385 | - "title": "测试T恤 纯棉短袖", | ||
| 1386 | - "brief": "舒适纯棉,多色可选", | ||
| 1387 | - "description": "这是一款适合日常穿着的纯棉T恤,透气吸汗。", | ||
| 1388 | - "vendor": "测试品牌", | ||
| 1389 | - "category": "服装/上衣/T恤", | ||
| 1390 | - "category_id": 100, | ||
| 1391 | - "category_level": 2, | ||
| 1392 | - "category_path": "服装/上衣/T恤", | ||
| 1393 | - "fake_sales": 1280, | ||
| 1394 | - "image_src": "https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg", | ||
| 1395 | - "tags": "T恤,纯棉,短袖,夏季", | ||
| 1396 | - "create_time": "2024-01-01T00:00:00Z", | ||
| 1397 | - "update_time": "2024-01-01T00:00:00Z" | ||
| 1398 | - }, | ||
| 1399 | - "skus": [ | ||
| 1400 | - { | ||
| 1401 | - "id": 20001, | ||
| 1402 | - "spu_id": 10001, | ||
| 1403 | - "price": 99.0, | ||
| 1404 | - "compare_at_price": 129.0, | ||
| 1405 | - "sku": "SKU-TSHIRT-001", | ||
| 1406 | - "inventory_quantity": 50, | ||
| 1407 | - "option1": "黑色", | ||
| 1408 | - "option2": "M", | ||
| 1409 | - "option3": null | ||
| 1410 | - }, | ||
| 1411 | - { | ||
| 1412 | - "id": 20002, | ||
| 1413 | - "spu_id": 10001, | ||
| 1414 | - "price": 99.0, | ||
| 1415 | - "compare_at_price": 129.0, | ||
| 1416 | - "sku": "SKU-TSHIRT-002", | ||
| 1417 | - "inventory_quantity": 30, | ||
| 1418 | - "option1": "白色", | ||
| 1419 | - "option2": "L", | ||
| 1420 | - "option3": null | ||
| 1421 | - } | ||
| 1422 | - ], | ||
| 1423 | - "options": [ | ||
| 1424 | - {"id": 1, "position": 1, "name": "颜色"}, | ||
| 1425 | - {"id": 2, "position": 2, "name": "尺码"} | ||
| 1426 | - ] | ||
| 1427 | - } | ||
| 1428 | - ] | ||
| 1429 | -}' | ||
| 1430 | -``` | ||
| 1431 | - | ||
| 1432 | -生产环境替换 `localhost:6004` 为实际 Indexer 地址,如 `http://43.166.252.75:6004`。 | ||
| 1433 | - | ||
| 1434 | -#### 响应示例(节选) | ||
| 1435 | - | ||
| 1436 | -```json | ||
| 1437 | -{ | ||
| 1438 | - "tenant_id": "170", | ||
| 1439 | - "docs": [ | ||
| 1440 | - { | ||
| 1441 | - "tenant_id": "170", | ||
| 1442 | - "spu_id": "223167", | ||
| 1443 | - "title": { "en": "...", "zh": "..." }, | ||
| 1444 | - "tags": ["Floerns", "Clothing", "Shoes & Jewelry"], | ||
| 1445 | - "skus": [ | ||
| 1446 | - { | ||
| 1447 | - "sku_id": "3988393", | ||
| 1448 | - "price": 25.99, | ||
| 1449 | - "compare_at_price": 25.99, | ||
| 1450 | - "stock": 100 | ||
| 1451 | - } | ||
| 1452 | - ], | ||
| 1453 | - "min_price": 25.99, | ||
| 1454 | - "max_price": 25.99, | ||
| 1455 | - "compare_at_price": 25.99, | ||
| 1456 | - "total_inventory": 100, | ||
| 1457 | - "title_embedding": [/* 1024 维向量 */] | ||
| 1458 | - // 其余字段与 mappings/search_products.json 一致 | ||
| 1459 | - } | ||
| 1460 | - ], | ||
| 1461 | - "total": 1, | ||
| 1462 | - "success_count": 1, | ||
| 1463 | - "failed_count": 0, | ||
| 1464 | - "failed": [] | ||
| 1465 | -} | ||
| 1466 | -``` | ||
| 1467 | - | ||
| 1468 | -| 字段 | 类型 | 说明 | | ||
| 1469 | -|------|------|------| | ||
| 1470 | -| `tenant_id` | string | 租户 ID | | ||
| 1471 | -| `docs` | array | 构建成功的 ES 文档列表,与 `mappings/search_products.json` 一致 | | ||
| 1472 | -| `total` | integer | 请求的 items 总数 | | ||
| 1473 | -| `success_count` | integer | 成功构建数量 | | ||
| 1474 | -| `failed_count` | integer | 失败数量 | | ||
| 1475 | -| `failed` | array | 失败项列表,每项含 `spu_id`、`error` | | ||
| 1476 | - | ||
| 1477 | -#### 使用建议 | ||
| 1478 | - | ||
| 1479 | -- **生产环境推荐流程**: | ||
| 1480 | - 1. Java 根据业务逻辑决定哪些 SPU 需要(全量/增量)处理; | ||
| 1481 | - 2. Java 从 MySQL 查询 SPU/SKU/Option 行,拼成 `items`; | ||
| 1482 | - 3. 调用 `/indexer/build-docs` 获取 ES-ready `docs`; | ||
| 1483 | - 4. Java 使用自己的 ES 客户端写入 `search_products_tenant_{tenant_id}`。 | ||
| 1484 | - | ||
| 1485 | -### 5.7 文档构建接口(测试 / 自测) | ||
| 1486 | - | ||
| 1487 | -#### 5.7.1 `POST /indexer/build-docs-from-db` | ||
| 1488 | - | ||
| 1489 | -- **描述**: | ||
| 1490 | - 仅用于测试/调试:调用方只提供 `tenant_id` 和 `spu_ids`,由 indexer 服务内部从 MySQL 查询 SPU/SKU/Option,然后调用与 `/indexer/build-docs` 相同的文档构建逻辑,返回 ES-ready doc。**生产环境请使用 `/indexer/build-docs`,由上游查库并写 ES。** | ||
| 1491 | - | ||
| 1492 | -#### 请求参数 | ||
| 1493 | - | ||
| 1494 | -```json | ||
| 1495 | -{ | ||
| 1496 | - "tenant_id": "170", | ||
| 1497 | - "spu_ids": ["223167", "223168"] | ||
| 1498 | -} | ||
| 1499 | -``` | ||
| 1500 | - | ||
| 1501 | -| 参数 | 类型 | 必填 | 说明 | | ||
| 1502 | -|------|------|------|------| | ||
| 1503 | -| `tenant_id` | string | Y | 租户 ID | | ||
| 1504 | -| `spu_ids` | array[string] | Y | SPU ID 列表,**单次最多 200 个** | | ||
| 1505 | - | ||
| 1506 | -#### 响应格式 | ||
| 1507 | - | ||
| 1508 | -与 `/indexer/build-docs` 相同:`tenant_id`、`docs`、`total`、`success_count`、`failed_count`、`failed`。 | ||
| 1509 | - | ||
| 1510 | -#### 请求示例 | ||
| 1511 | - | ||
| 1512 | -```bash | ||
| 1513 | -curl -X POST "http://127.0.0.1:6004/indexer/build-docs-from-db" \ | ||
| 1514 | - -H "Content-Type: application/json" \ | ||
| 1515 | - -d '{"tenant_id": "170", "spu_ids": ["223167"]}' | ||
| 1516 | -``` | ||
| 1517 | - | ||
| 1518 | -返回结构与 `/indexer/build-docs` 相同,可直接用于对比 ES 实际文档或调试字段映射问题。 | ||
| 1519 | - | ||
| 1520 | -### 5.8 内容理解字段生成接口 | ||
| 1521 | - | ||
| 1522 | -- **端点**: `POST /indexer/enrich-content` | ||
| 1523 | -- **描述**: 根据商品内容信息批量生成 **qanchors**(锚文本)、**semantic_attributes**(语义属性)、**tags**(细分标签),供外部 indexer 在「微服务组合」方式下自行拼装 doc 时使用。请求以 `items[]` 传入商品内容字段(必填/可选见下表)。内部逻辑与 `indexer.product_enrich` 一致,支持多语言与 Redis 缓存;单次请求在线程池中执行,避免阻塞其他接口。 | ||
| 1524 | - | ||
| 1525 | -#### 请求参数 | ||
| 1526 | - | ||
| 1527 | -```json | ||
| 1528 | -{ | ||
| 1529 | - "tenant_id": "170", | ||
| 1530 | - "items": [ | ||
| 1531 | - { | ||
| 1532 | - "spu_id": "223167", | ||
| 1533 | - "title": "纯棉短袖T恤 夏季男装", | ||
| 1534 | - "brief": "夏季透气纯棉短袖,舒适亲肤", | ||
| 1535 | - "description": "100%棉,圆领版型,适合日常通勤与休闲穿搭。", | ||
| 1536 | - "image_url": "https://example.com/images/223167.jpg" | ||
| 1537 | - }, | ||
| 1538 | - { | ||
| 1539 | - "spu_id": "223168", | ||
| 1540 | - "title": "12PCS Dolls with Bottles", | ||
| 1541 | - "image_url": "https://example.com/images/223168.jpg" | ||
| 1542 | - } | ||
| 1543 | - ], | ||
| 1544 | - "languages": ["zh", "en"] | ||
| 1545 | -} | ||
| 1546 | -``` | ||
| 1547 | - | ||
| 1548 | -| 参数 | 类型 | 必填 | 默认值 | 说明 | | ||
| 1549 | -|------|------|------|--------|------| | ||
| 1550 | -| `tenant_id` | string | Y | - | 租户 ID。目前仅用于记录日志,不产生实际作用| | ||
| 1551 | -| `items` | array | Y | - | 待分析列表;**单次最多 50 条** | | ||
| 1552 | -| `languages` | array[string] | N | `["zh", "en"]` | 目标语言,需在支持范围内:`zh`、`en`、`de`、`ru`、`fr` | | ||
| 1553 | - | ||
| 1554 | -`items[]` 字段说明: | ||
| 1555 | - | ||
| 1556 | -| 字段 | 类型 | 必填 | 说明 | | ||
| 1557 | -|------|------|------|------| | ||
| 1558 | -| `spu_id` | string | Y | SPU ID,用于回填结果;目前仅用于记录日志,不产生实际作用| | ||
| 1559 | -| `title` | string | Y | 商品标题 | | ||
| 1560 | -| `image_url` | string | N | 商品主图 URL;当前会参与内容缓存键,后续可用于图像/多模态内容理解 | | ||
| 1561 | -| `brief` | string | N | 商品简介/短描述;当前会参与内容缓存键 | | ||
| 1562 | -| `description` | string | N | 商品详情/长描述;当前会参与内容缓存键 | | ||
| 1563 | - | ||
| 1564 | -缓存说明: | ||
| 1565 | - | ||
| 1566 | -- 内容缓存键仅由 `target_lang + items[]` 中会影响内容理解结果的输入文本构成,目前包括:`title`、`brief`、`description`、`image_url` 的规范化内容 hash。 | ||
| 1567 | -- `tenant_id`、`spu_id` 只用于请求归属与结果回填,不参与缓存键。 | ||
| 1568 | -- 因此,输入内容不变时可跨请求直接命中缓存;任一输入字段变化时,会自然落到新的缓存 key。 | ||
| 1569 | - | ||
| 1570 | -批量请求建议: | ||
| 1571 | -- **全量**:强烈建议 尽可能 **20 个 SPU/doc** 攒成一个批次后再请求一次。 | ||
| 1572 | -- **增量**:可按时效要求设置时间窗口(例如 **5 分钟**),在窗口内尽可能攒到 **20 个**;达到 20 或窗口到期就发送一次请求。 | ||
| 1573 | -- 允许超过20,服务内部会拆分成小批次逐个处理。也允许小于20,但是将造成费用和耗时的成本上升,特别是每次请求一个doc的情况。 | ||
| 1574 | - | ||
| 1575 | -#### 响应格式 | ||
| 1576 | - | ||
| 1577 | -```json | ||
| 1578 | -{ | ||
| 1579 | - "tenant_id": "170", | ||
| 1580 | - "total": 2, | ||
| 1581 | - "results": [ | ||
| 1582 | - { | ||
| 1583 | - "spu_id": "223167", | ||
| 1584 | - "qanchors": { | ||
| 1585 | - "zh": "短袖T恤,纯棉,男装,夏季", | ||
| 1586 | - "en": "cotton t-shirt, short sleeve, men, summer" | ||
| 1587 | - }, | ||
| 1588 | - "semantic_attributes": [ | ||
| 1589 | - { "lang": "zh", "name": "tags", "value": "纯棉" }, | ||
| 1590 | - { "lang": "zh", "name": "usage_scene", "value": "日常" }, | ||
| 1591 | - { "lang": "en", "name": "tags", "value": "cotton" } | ||
| 1592 | - ], | ||
| 1593 | - "tags": ["纯棉", "短袖", "男装", "cotton", "short sleeve"] | ||
| 1594 | - }, | ||
| 1595 | - { | ||
| 1596 | - "spu_id": "223168", | ||
| 1597 | - "qanchors": { "en": "dolls, toys, 12pcs" }, | ||
| 1598 | - "semantic_attributes": [], | ||
| 1599 | - "tags": ["dolls", "toys"] | ||
| 1600 | - } | ||
| 1601 | - ] | ||
| 1602 | -} | ||
| 1603 | -``` | ||
| 1604 | - | ||
| 1605 | -| 字段 | 类型 | 说明 | | ||
| 1606 | -|------|------|------| | ||
| 1607 | -| `results` | array | 与请求 `items` 一一对应,每项含 `spu_id`、`qanchors`、`semantic_attributes`、`tags` | | ||
| 1608 | -| `results[].qanchors` | object | 按语言键的锚文本(逗号分隔短语),可写入 ES 文档的 `qanchors.{lang}` | | ||
| 1609 | -| `results[].semantic_attributes` | array | 语义属性列表,每项为 `{ "lang", "name", "value" }`,可写入 ES 的 `semantic_attributes` nested 字段 | | ||
| 1610 | -| `results[].tags` | array | 从语义属性中抽取的 `name=tags` 的 value 集合,可与业务原有 `tags` 合并后写入 ES 的 `tags` 字段 | | ||
| 1611 | -| `results[].error` | string | 若该条处理失败(如 LLM 异常),会在此字段返回错误信息 | | ||
| 1612 | - | ||
| 1613 | -**错误响应**: | ||
| 1614 | -- `400`: `items` 为空或超过 50 条 | ||
| 1615 | -- `503`: 未配置 `DASHSCOPE_API_KEY`,内容理解服务不可用 | ||
| 1616 | - | ||
| 1617 | -#### 请求示例 | ||
| 1618 | - | ||
| 1619 | -```bash | ||
| 1620 | -curl -X POST "http://localhost:6004/indexer/enrich-content" \ | ||
| 1621 | - -H "Content-Type: application/json" \ | ||
| 1622 | - -d '{ | ||
| 1623 | - "tenant_id": "170", | ||
| 1624 | - "items": [ | ||
| 1625 | - { | ||
| 1626 | - "spu_id": "223167", | ||
| 1627 | - "title": "纯棉短袖T恤 夏季男装", | ||
| 1628 | - "brief": "夏季透气纯棉短袖,舒适亲肤", | ||
| 1629 | - "description": "100%棉,圆领版型,适合日常通勤与休闲穿搭。", | ||
| 1630 | - "image_url": "https://example.com/images/223167.jpg" | ||
| 1631 | - } | ||
| 1632 | - ], | ||
| 1633 | - "languages": ["zh", "en"] | ||
| 1634 | - }' | ||
| 1635 | -``` | ||
| 1636 | - | ||
| 1637 | ---- | ||
| 1638 | - | ||
| 1639 | -## 管理接口 | ||
| 1640 | - | ||
| 1641 | -### 6.1 健康检查 | ||
| 1642 | - | ||
| 1643 | -- **端点**: `GET /admin/health` | ||
| 1644 | -- **描述**: 检查服务与依赖(如 Elasticsearch)状态。 | ||
| 1645 | - | ||
| 1646 | -```json | ||
| 1647 | -{ | ||
| 1648 | - "status": "healthy", | ||
| 1649 | - "elasticsearch": "connected", | ||
| 1650 | - "tenant_id": "tenant1" | ||
| 1651 | -} | ||
| 1652 | -``` | ||
| 1653 | - | ||
| 1654 | -### 6.2 获取配置 | ||
| 1655 | - | ||
| 1656 | -- **端点**: `GET /admin/config` | ||
| 1657 | -- **描述**: 返回当前租户的脱敏配置,便于核对索引及排序表达式。 | ||
| 1658 | - | ||
| 1659 | -```json | ||
| 1660 | -{ | ||
| 1661 | - "tenant_id": "tenant1", | ||
| 1662 | - "tenant_name": "Tenant1 Test Instance", | ||
| 1663 | - "es_index_name": "search_tenant1", | ||
| 1664 | - "num_fields": 20, | ||
| 1665 | - "num_indexes": 4, | ||
| 1666 | - "supported_languages": ["zh", "en", "ru"], | ||
| 1667 | - "spu_enabled": false | ||
| 1668 | -} | ||
| 1669 | -``` | ||
| 1670 | - | ||
| 1671 | -### 6.3 索引统计 | ||
| 1672 | - | ||
| 1673 | -- **端点**: `GET /admin/stats` | ||
| 1674 | -- **描述**: 获取指定租户索引文档数量与磁盘大小,方便监控。 | ||
| 1675 | -- **租户标识**:通过请求头 `X-Tenant-ID` 或 query 参数 `tenant_id` 传递(必填)。 | ||
| 1676 | - | ||
| 1677 | -```json | ||
| 1678 | -{ | ||
| 1679 | - "tenant_id": "162", | ||
| 1680 | - "index_name": "search_products_tenant_162", | ||
| 1681 | - "document_count": 10000, | ||
| 1682 | - "size_mb": 523.45 | ||
| 1683 | -} | ||
| 1684 | -``` | ||
| 1685 | - | ||
| 1686 | ---- | ||
| 1687 | - | ||
| 1688 | -## 7. 微服务接口(向量、重排、翻译) | ||
| 1689 | - | ||
| 1690 | -以下三个微服务独立部署,**外部系统可直接调用**。它们被搜索后端(6002)和索引服务(6004)内部使用,也可供其他业务系统直接对接。 | ||
| 1691 | - | ||
| 1692 | -| 服务 | 默认端口 | Base URL | 说明 | | ||
| 1693 | -|------|----------|----------|------| | ||
| 1694 | -| 向量服务(文本) | 6005 | `http://localhost:6005` | 文本向量化,用于 query/doc 语义检索 | | ||
| 1695 | -| 向量服务(图片) | 6008 | `http://localhost:6008` | 图片向量化,用于以图搜图 | | ||
| 1696 | -| 翻译服务 | 6006 | `http://localhost:6006` | 多语言翻译(云端与本地模型统一入口) | | ||
| 1697 | -| 重排服务 | 6007 | `http://localhost:6007` | 对检索结果进行二次排序 | | ||
| 1698 | - | ||
| 1699 | -生产环境请将 `localhost` 替换为实际服务地址。 | ||
| 1700 | -服务管理入口与完整启停规则见:`docs/Usage-Guide.md` -> `服务管理总览`。 | ||
| 1701 | - | ||
| 1702 | -### 7.1 向量服务(Embedding) | ||
| 1703 | - | ||
| 1704 | -- **Base URL**: | ||
| 1705 | - - 文本:`http://localhost:6005`(可通过 `EMBEDDING_TEXT_SERVICE_URL` 覆盖) | ||
| 1706 | - - 图片:`http://localhost:6008`(可通过 `EMBEDDING_IMAGE_SERVICE_URL` 覆盖) | ||
| 1707 | -- **启动**: | ||
| 1708 | - - 文本:`./scripts/start_embedding_text_service.sh` | ||
| 1709 | - - 图片:`./scripts/start_embedding_image_service.sh` | ||
| 1710 | -- **依赖**: | ||
| 1711 | - - 文本向量后端默认走 TEI(`http://127.0.0.1:8080`) | ||
| 1712 | - - 图片向量依赖 `cnclip`(`grpc://127.0.0.1:51000`) | ||
| 1713 | - - TEI 默认使用 GPU(`TEI_DEVICE=cuda`);当配置为 GPU 且不可用时会启动失败(不会自动降级到 CPU) | ||
| 1714 | - - cnclip 默认使用 `cuda`;若配置为 `cuda` 但 GPU 不可用会启动失败(不会自动降级到 `cpu`) | ||
| 1715 | - - 当前单机部署建议保持单实例,通过**文本/图片拆分 + 独立限流**隔离压力 | ||
| 1716 | - | ||
| 1717 | -补充说明: | ||
| 1718 | - | ||
| 1719 | -- 文本和图片现在已经拆成**不同进程 / 不同端口**,避免图片下载与编码波动影响文本向量化。 | ||
| 1720 | -- 服务端对 text / image 有**独立 admission control**: | ||
| 1721 | - - `TEXT_MAX_INFLIGHT` | ||
| 1722 | - - `IMAGE_MAX_INFLIGHT` | ||
| 1723 | -- 当超过处理能力时,服务会直接返回过载错误,而不是无限排队。 | ||
| 1724 | -- `GET /health` 会返回各自的 `limits`、`stats`、`cache_enabled` 等状态;`GET /ready` 用于就绪探针。 | ||
| 1725 | - | ||
| 1726 | -#### 7.1.1 `POST /embed/text` — 文本向量化 | ||
| 1727 | - | ||
| 1728 | -将文本列表转为 1024 维向量,用于语义搜索、文档索引等。 | ||
| 1729 | - | ||
| 1730 | -**请求体**(JSON 数组): | ||
| 1731 | -```json | ||
| 1732 | -["文本1", "文本2", "文本3"] | ||
| 1733 | -``` | ||
| 1734 | - | ||
| 1735 | -**响应**(JSON 数组,与输入一一对应): | ||
| 1736 | -```json | ||
| 1737 | -[[0.01, -0.02, ...], [0.03, 0.01, ...], ...] | ||
| 1738 | -``` | ||
| 1739 | - | ||
| 1740 | -**完整 curl 示例**: | ||
| 1741 | -```bash | ||
| 1742 | -curl -X POST "http://localhost:6005/embed/text?normalize=true" \ | ||
| 1743 | - -H "Content-Type: application/json" \ | ||
| 1744 | - -d '["芭比娃娃 儿童玩具", "纯棉T恤 短袖"]' | ||
| 1745 | -``` | ||
| 1746 | - | ||
| 1747 | -#### 7.1.2 `POST /embed/image` — 图片向量化 | ||
| 1748 | - | ||
| 1749 | -将图片 URL 或路径转为向量,用于以图搜图。 | ||
| 1750 | - | ||
| 1751 | -前置条件:`cnclip` 服务已启动(默认端口 `51000`)。若未启动,图片 embedding 服务启动会失败或请求返回错误。 | ||
| 1752 | - | ||
| 1753 | -**请求体**(JSON 数组): | ||
| 1754 | -```json | ||
| 1755 | -["https://example.com/image1.jpg", "https://example.com/image2.jpg"] | ||
| 1756 | -``` | ||
| 1757 | - | ||
| 1758 | -**响应**(JSON 数组,与输入一一对应): | ||
| 1759 | -```json | ||
| 1760 | -[[0.01, -0.02, ...], [0.03, 0.01, ...], ...] | ||
| 1761 | -``` | ||
| 1762 | - | ||
| 1763 | -**完整 curl 示例**: | ||
| 1764 | -```bash | ||
| 1765 | -curl -X POST "http://localhost:6008/embed/image?normalize=true" \ | ||
| 1766 | - -H "Content-Type: application/json" \ | ||
| 1767 | - -d '["https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg"]' | ||
| 1768 | -``` | ||
| 1769 | - | ||
| 1770 | -#### 7.1.3 `GET /health` — 健康检查 | ||
| 1771 | - | ||
| 1772 | -```bash | ||
| 1773 | -curl "http://localhost:6005/health" | ||
| 1774 | -curl "http://localhost:6008/health" | ||
| 1775 | -``` | ||
| 1776 | - | ||
| 1777 | -返回中会包含: | ||
| 1778 | - | ||
| 1779 | -- `service_kind`:`text` / `image` / `all` | ||
| 1780 | -- `cache_enabled`:text/image Redis 缓存是否可用 | ||
| 1781 | -- `limits`:当前 inflight limit、active、rejected_total 等 | ||
| 1782 | -- `stats`:request_total、cache_hits、cache_misses、avg_latency_ms 等 | ||
| 1783 | - | ||
| 1784 | -#### 7.1.4 `GET /ready` — 就绪检查 | ||
| 1785 | - | ||
| 1786 | -```bash | ||
| 1787 | -curl "http://localhost:6005/ready" | ||
| 1788 | -curl "http://localhost:6008/ready" | ||
| 1789 | -``` | ||
| 1790 | - | ||
| 1791 | -#### 7.1.5 缓存与限流说明 | ||
| 1792 | - | ||
| 1793 | -- 文本与图片都会先查 Redis 向量缓存。 | ||
| 1794 | -- Redis 中 value 仍是 **BF16 bytes**,读取后恢复成 `float32` 返回。 | ||
| 1795 | -- cache key 已区分 `normalize=true/false`,避免不同归一化策略命中同一条缓存。 | ||
| 1796 | -- 当服务端发现请求是 **full-cache-hit** 时,会直接返回,不占用模型并发槽位。 | ||
| 1797 | -- 当服务端发现超过 `TEXT_MAX_INFLIGHT` / `IMAGE_MAX_INFLIGHT` 时,会直接拒绝,而不是无限排队。 | ||
| 1798 | - | ||
| 1799 | -#### 7.1.6 TEI 统一调优建议(主服务) | ||
| 1800 | - | ||
| 1801 | -使用单套主服务即可同时兼顾: | ||
| 1802 | -- 在线 query 向量化(低延迟,常见 `batch=1~4`) | ||
| 1803 | -- 索引构建向量化(高吞吐,常见 `batch=15~20`) | ||
| 1804 | - | ||
| 1805 | -统一启动(主链路): | ||
| 1806 | - | ||
| 1807 | -```bash | ||
| 1808 | -./scripts/start_tei_service.sh | ||
| 1809 | -./scripts/service_ctl.sh restart embedding | ||
| 1810 | -``` | ||
| 1811 | - | ||
| 1812 | -默认端口: | ||
| 1813 | -- TEI: `http://127.0.0.1:8080` | ||
| 1814 | -- 文本向量服务(`/embed/text`): `http://127.0.0.1:6005` | ||
| 1815 | -- 图片向量服务(`/embed/image`): `http://127.0.0.1:6008` | ||
| 1816 | - | ||
| 1817 | -当前主 TEI 启动默认值(已按 T4/短文本场景调优): | ||
| 1818 | -- `TEI_MAX_BATCH_TOKENS=4096` | ||
| 1819 | -- `TEI_MAX_CLIENT_BATCH_SIZE=24` | ||
| 1820 | -- `TEI_DTYPE=float16` | ||
| 1821 | - | ||
| 1822 | -### 7.2 重排服务(Reranker) | ||
| 1823 | - | ||
| 1824 | -- **Base URL**: `http://localhost:6007`(可通过 `RERANKER_SERVICE_URL` 覆盖) | ||
| 1825 | -- **启动**: `./scripts/start_reranker.sh` | ||
| 1826 | - | ||
| 1827 | -说明:默认后端为 `qwen3_vllm`(`Qwen/Qwen3-Reranker-0.6B`),需要可用 GPU 显存。 | ||
| 1828 | -补充:`docs` 的请求大小与模型推理 `batch size` 解耦。即使一次传入 1000 条文档,服务端也会按 `services.rerank.backends.qwen3_vllm.infer_batch_size` 自动拆分;若 `sort_by_doc_length=true`,会先按文档长度排序后分批,减少 padding,再按原输入顺序返回分数。`length_sort_mode` 可选 `char`(更快)或 `token`(更精确)。 | ||
| 1829 | - | ||
| 1830 | -#### 7.2.1 `POST /rerank` — 结果重排 | ||
| 1831 | - | ||
| 1832 | -根据 query 与 doc 的相关性对文档列表重新打分排序。 | ||
| 1833 | - | ||
| 1834 | -**请求体**: | ||
| 1835 | -```json | ||
| 1836 | -{ | ||
| 1837 | - "query": "玩具 芭比", | ||
| 1838 | - "docs": [ | ||
| 1839 | - "12PCS 6 Types of Dolls with Bottles", | ||
| 1840 | - "纯棉T恤 短袖 夏季" | ||
| 1841 | - ], | ||
| 1842 | - "normalize": true | ||
| 1843 | -} | ||
| 1844 | -``` | ||
| 1845 | - | ||
| 1846 | -| 参数 | 类型 | 必填 | 说明 | | ||
| 1847 | -|------|------|------|------| | ||
| 1848 | -| `query` | string | Y | 搜索查询 | | ||
| 1849 | -| `docs` | array[string] | Y | 待重排的文档列表(单次最多由服务端配置限制) | | ||
| 1850 | -| `normalize` | boolean | N | 是否对分数做 sigmoid 归一化,默认 true | | ||
| 1851 | - | ||
| 1852 | -**响应**: | ||
| 1853 | -```json | ||
| 1854 | -{ | ||
| 1855 | - "scores": [0.92, 0.15], | ||
| 1856 | - "meta": { | ||
| 1857 | - "service_elapsed_ms": 45.2, | ||
| 1858 | - "input_docs": 2, | ||
| 1859 | - "unique_docs": 2 | ||
| 1860 | - } | ||
| 1861 | -} | ||
| 1862 | -``` | ||
| 1863 | - | ||
| 1864 | -**完整 curl 示例**: | ||
| 1865 | -```bash | ||
| 1866 | -curl -X POST "http://localhost:6007/rerank" \ | ||
| 1867 | - -H "Content-Type: application/json" \ | ||
| 1868 | - -d '{ | ||
| 1869 | - "query": "玩具 芭比", | ||
| 1870 | - "docs": ["12PCS 6 Types of Dolls with Bottles", "纯棉T恤 短袖"], | ||
| 1871 | - "top_n":386, | ||
| 1872 | - "normalize": true | ||
| 1873 | - }' | ||
| 1874 | - | ||
| 1875 | -``` | ||
| 1876 | - | ||
| 1877 | -#### 7.2.2 `GET /health` — 健康检查 | ||
| 1878 | - | ||
| 1879 | -```bash | ||
| 1880 | -curl "http://localhost:6007/health" | ||
| 1881 | -``` | ||
| 1882 | - | ||
| 1883 | -### 7.3 翻译服务(Translation) | ||
| 1884 | - | ||
| 1885 | -- **Base URL**: `http://localhost:6006`(以 `config/config.yaml -> services.translation.service_url` 为准) | ||
| 1886 | -- **启动**: `./scripts/start_translator.sh` | ||
| 1887 | - | ||
| 1888 | -#### 7.3.1 `POST /translate` — 文本翻译 | ||
| 1889 | - | ||
| 1890 | -支持 translator service 内所有已启用 capability,适用于商品名称、描述、query 等电商场景。当前可配置能力包括 `qwen-mt`、`llm`、`deepl` 以及本地模型 `nllb-200-distilled-600m`、`opus-mt-zh-en`、`opus-mt-en-zh`。 | ||
| 1891 | - | ||
| 1892 | -**请求体**(支持单条字符串或字符串列表): | ||
| 1893 | -```json | ||
| 1894 | -{ | ||
| 1895 | - "text": "商品名称", | ||
| 1896 | - "target_lang": "en", | ||
| 1897 | - "source_lang": "zh", | ||
| 1898 | - "model": "qwen-mt", | ||
| 1899 | - "scene": "sku_name" | ||
| 1900 | -} | ||
| 1901 | -``` | ||
| 1902 | - | ||
| 1903 | -也支持批量列表形式: | ||
| 1904 | -```json | ||
| 1905 | -{ | ||
| 1906 | - "text": ["商品名称1", "商品名称2"], | ||
| 1907 | - "target_lang": "en", | ||
| 1908 | - "source_lang": "zh", | ||
| 1909 | - "model": "qwen-mt", | ||
| 1910 | - "scene": "sku_name" | ||
| 1911 | -} | ||
| 1912 | -``` | ||
| 1913 | - | ||
| 1914 | -| 参数 | 类型 | 必填 | 说明 | | ||
| 1915 | -|------|------|------|------| | ||
| 1916 | -| `text` | string \| string[] | Y | 待翻译文本,既支持单条字符串,也支持字符串列表(批量翻译) | | ||
| 1917 | -| `target_lang` | string | Y | 目标语言:`zh`、`en`、`ru` 等 | | ||
| 1918 | -| `source_lang` | string | N | 源语言。云端模型可不传;`nllb-200-distilled-600m` 建议显式传入 | | ||
| 1919 | -| `model` | string | N | 已启用 capability 名称,如 `qwen-mt`、`llm`、`deepl`、`nllb-200-distilled-600m`、`opus-mt-zh-en`、`opus-mt-en-zh` | | ||
| 1920 | -| `scene` | string | N | 翻译场景参数,与 `model` 配套使用;当前标准值为 `sku_name`、`ecommerce_search_query`、`general` | | ||
| 1921 | - | ||
| 1922 | -说明: | ||
| 1923 | -- 外部接口不接受 `prompt`;LLM prompt 由服务端按 `scene` 自动生成。 | ||
| 1924 | -- 传入未定义的 `scene` 或未启用的 `model` 会返回 `400`。 | ||
| 1925 | - | ||
| 1926 | -**SKU 名称场景选型建议**: | ||
| 1927 | - | ||
| 1928 | -- 批量 SKU 名称翻译,优先考虑本地大吞吐方案时,可使用 `"model": "nllb-200-distilled-600m"`(该模型"scene":参数无效)。 | ||
| 1929 | -- 如果目标是更高质量,且可以接受更慢速度与额外 LLM API 费用,可使用 `"model": "llm"` + `"scene": "sku_name"`。 | ||
| 1930 | -- 如果是en-zh互译、期待更高的速度,可以考虑`opus-mt-zh-en` / `opus-mt-en-zh`。(质量未详细评测,一些文章说比blib-200-600m更好,但是我看了些case感觉要差不少) | ||
| 1931 | - | ||
| 1932 | -**实时翻译选型建议**: | ||
| 1933 | - | ||
| 1934 | -- 在线 query 翻译如果只是 `en/zh` 互译,优先使用 `opus-mt-zh-en` 或 `opus-mt-en-zh`,它们是当前已测本地模型里延迟最低的一档。 | ||
| 1935 | -- 如果涉及其他语言,或对质量要求高于本地轻量模型,优先考虑 `deepl`。 | ||
| 1936 | -- `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`。 | ||
| 1937 | - | ||
| 1938 | -**Batch Size / 调用方式建议**: | ||
| 1939 | - | ||
| 1940 | -- 本接口支持 `text: string[]`;离线或批量索引翻译时,应尽量合并请求,让底层 backend 发挥批处理能力。 | ||
| 1941 | -- `nllb-200-distilled-600m` 在当前 `Tesla T4` 压测中,推荐配置是 `batch_size=16`、`max_new_tokens=64`、`attn_implementation=sdpa`;继续升到 `batch_size=32` 虽可能提高吞吐,但 tail latency 会明显变差。 | ||
| 1942 | -- 在线 query 场景可直接把“单条请求”理解为 `batch_size=1`;更关注 request latency,而不是离线吞吐。 | ||
| 1943 | -- `opus-mt-zh-en` / `opus-mt-en-zh` 当前生产配置也是 `batch_size=16`,适合作为中英互译的低延迟本地默认值;若走在线单条调用,同样按 `batch_size=1` 理解即可。 | ||
| 1944 | -- `llm` 按单条请求即可。 | ||
| 1945 | - | ||
| 1946 | -**响应**: | ||
| 1947 | -```json | ||
| 1948 | -{ | ||
| 1949 | - "text": "商品名称", | ||
| 1950 | - "target_lang": "en", | ||
| 1951 | - "source_lang": "zh", | ||
| 1952 | - "translated_text": "Product name", | ||
| 1953 | - "status": "success", | ||
| 1954 | - "model": "qwen-mt", | ||
| 1955 | - "scene": "sku_name" | ||
| 1956 | -} | ||
| 1957 | -``` | ||
| 1958 | - | ||
| 1959 | -当请求为列表形式时,`text` 与 `translated_text` 均为等长数组: | ||
| 1960 | -```json | ||
| 1961 | -{ | ||
| 1962 | - "text": ["商品名称1", "商品名称2"], | ||
| 1963 | - "target_lang": "en", | ||
| 1964 | - "source_lang": "zh", | ||
| 1965 | - "translated_text": ["Product name 1", "Product name 2"], | ||
| 1966 | - "status": "success", | ||
| 1967 | - "model": "qwen-mt", | ||
| 1968 | - "scene": "sku_name" | ||
| 1969 | -} | ||
| 1970 | -``` | ||
| 1971 | - | ||
| 1972 | -> **失败语义(批量)**:当 `text` 为列表时,如果其中某条翻译失败,对应位置返回 `null`(即 `translated_text[i] = null`),并保持数组长度与顺序不变;接口整体仍返回 `status="success"`,用于避免“部分失败”导致整批请求失败。 | ||
| 1973 | - | ||
| 1974 | -> **实现提示(可忽略)**:服务端会尽可能使用底层 backend 的批量能力(若支持),否则自动拆分逐条翻译;无论采用哪种方式,上述批量契约保持一致。 | ||
| 1975 | - | ||
| 1976 | -**完整 curl 示例**: | ||
| 1977 | - | ||
| 1978 | -中文 → 英文: | ||
| 1979 | -```bash | ||
| 1980 | -curl -X POST "http://localhost:6006/translate" \ | ||
| 1981 | - -H "Content-Type: application/json" \ | ||
| 1982 | - -d '{ | ||
| 1983 | - "text": "商品名称", | ||
| 1984 | - "target_lang": "en", | ||
| 1985 | - "source_lang": "zh" | ||
| 1986 | - }' | ||
| 1987 | -``` | ||
| 1988 | - | ||
| 1989 | -俄文 → 英文: | ||
| 1990 | -```bash | ||
| 1991 | -curl -X POST "http://localhost:6006/translate" \ | ||
| 1992 | - -H "Content-Type: application/json" \ | ||
| 1993 | - -d '{ | ||
| 1994 | - "text": "Название товара", | ||
| 1995 | - "target_lang": "en", | ||
| 1996 | - "source_lang": "ru" | ||
| 1997 | - }' | ||
| 1998 | -``` | ||
| 1999 | - | ||
| 2000 | -使用 DeepL 模型: | ||
| 2001 | -```bash | ||
| 2002 | -curl -X POST "http://localhost:6006/translate" \ | ||
| 2003 | - -H "Content-Type: application/json" \ | ||
| 2004 | - -d '{ | ||
| 2005 | - "text": "商品名称", | ||
| 2006 | - "target_lang": "en", | ||
| 2007 | - "source_lang": "zh", | ||
| 2008 | - "model": "deepl" | ||
| 2009 | - }' | ||
| 2010 | -``` | ||
| 2011 | - | ||
| 2012 | -使用本地 OPUS 模型(中文 → 英文): | ||
| 2013 | -```bash | ||
| 2014 | -curl -X POST "http://localhost:6006/translate" \ | ||
| 2015 | - -H "Content-Type: application/json" \ | ||
| 2016 | - -d '{ | ||
| 2017 | - "text": "蓝牙耳机", | ||
| 2018 | - "target_lang": "en", | ||
| 2019 | - "source_lang": "zh", | ||
| 2020 | - "model": "opus-mt-zh-en", | ||
| 2021 | - "scene": "sku_name" | ||
| 2022 | - }' | ||
| 2023 | -``` | ||
| 2024 | - | ||
| 2025 | -使用本地 NLLB 做 SKU 名称批量翻译: | ||
| 2026 | -```bash | ||
| 2027 | -curl -X POST "http://localhost:6006/translate" \ | ||
| 2028 | - -H "Content-Type: application/json" \ | ||
| 2029 | - -d '{ | ||
| 2030 | - "text": ["商品名称1", "商品名称2", "商品名称3"], | ||
| 2031 | - "target_lang": "en", | ||
| 2032 | - "source_lang": "zh", | ||
| 2033 | - "model": "nllb-200-distilled-600m", | ||
| 2034 | - "scene": "sku_name" | ||
| 2035 | - }' | ||
| 2036 | -``` | ||
| 2037 | - | ||
| 2038 | -使用 LLM 做高质量 SKU 名称翻译: | ||
| 2039 | -```bash | ||
| 2040 | -curl -X POST "http://localhost:6006/translate" \ | ||
| 2041 | - -H "Content-Type: application/json" \ | ||
| 2042 | - -d '{ | ||
| 2043 | - "text": "男士偏光飞行员太阳镜", | ||
| 2044 | - "target_lang": "en", | ||
| 2045 | - "source_lang": "zh", | ||
| 2046 | - "model": "llm", | ||
| 2047 | - "scene": "sku_name" | ||
| 2048 | - }' | ||
| 2049 | -``` | ||
| 2050 | - | ||
| 2051 | -#### 7.3.2 `GET /health` — 健康检查 | ||
| 2052 | - | ||
| 2053 | -```bash | ||
| 2054 | -curl "http://localhost:6006/health" | ||
| 2055 | -``` | ||
| 2056 | - | ||
| 2057 | -典型响应: | ||
| 2058 | -```json | ||
| 2059 | -{ | ||
| 2060 | - "status": "healthy", | ||
| 2061 | - "service": "translation", | ||
| 2062 | - "default_model": "llm", | ||
| 2063 | - "default_scene": "general", | ||
| 2064 | - "available_models": ["qwen-mt", "llm", "opus-mt-zh-en"], | ||
| 2065 | - "enabled_capabilities": ["qwen-mt", "llm", "opus-mt-zh-en"], | ||
| 2066 | - "loaded_models": ["llm"] | ||
| 2067 | -} | ||
| 2068 | -``` | ||
| 2069 | - | ||
| 2070 | -### 7.4 内容理解字段生成(Indexer 服务内) | ||
| 2071 | - | ||
| 2072 | -内容理解字段生成接口部署在 **Indexer 服务**(默认端口 6004)内,与「翻译、向量化」等独立端口微服务并列,供采用**微服务组合**方式的 indexer 调用。 | ||
| 2073 | - | ||
| 2074 | -- **Base URL**: Indexer 服务地址,如 `http://localhost:6004` | ||
| 2075 | -- **路径**: `POST /indexer/enrich-content` | ||
| 2076 | -- **说明**: 根据商品标题批量生成 `qanchors`、`semantic_attributes`、`tags`,用于拼装 ES 文档。内部使用大模型(需配置 `DASHSCOPE_API_KEY`),支持多语言与 Redis 缓存;单次最多 50 条,建议批量调用以提升效率。 | ||
| 2077 | - | ||
| 2078 | -请求/响应格式、示例及错误码见 [5.8 内容理解字段生成接口](#58-内容理解字段生成接口)。 | ||
| 2079 | - | ||
| 2080 | ---- | ||
| 2081 | - | ||
| 2082 | -## 8. 常见场景示例 | ||
| 2083 | - | ||
| 2084 | -以下示例仅展示**请求体**(body);实际调用时请加上请求头 `X-Tenant-ID: <租户ID>`(或 URL 参数 `tenant_id`),参见 [3.1 接口信息](#31-接口信息)。 | ||
| 2085 | - | ||
| 2086 | -### 8.1 基础搜索与排序 | ||
| 2087 | - | ||
| 2088 | -**按价格从低到高排序**: | ||
| 2089 | -```json | ||
| 2090 | -{ | ||
| 2091 | - "query": "玩具", | ||
| 2092 | - "size": 20, | ||
| 2093 | - "from": 0, | ||
| 2094 | - "sort_by": "price", | ||
| 2095 | - "sort_order": "asc" | ||
| 2096 | -} | ||
| 2097 | -``` | ||
| 2098 | - | ||
| 2099 | -**按价格从高到低排序**: | ||
| 2100 | -```json | ||
| 2101 | -{ | ||
| 2102 | - "query": "玩具", | ||
| 2103 | - "size": 20, | ||
| 2104 | - "from": 0, | ||
| 2105 | - "sort_by": "price", | ||
| 2106 | - "sort_order": "desc" | ||
| 2107 | -} | ||
| 2108 | -``` | ||
| 2109 | - | ||
| 2110 | -**按销量从高到低排序**: | ||
| 2111 | -```json | ||
| 2112 | -{ | ||
| 2113 | - "query": "玩具", | ||
| 2114 | - "size": 20, | ||
| 2115 | - "from": 0, | ||
| 2116 | - "sort_by": "sales", | ||
| 2117 | - "sort_order": "desc" | ||
| 2118 | -} | ||
| 2119 | -``` | ||
| 2120 | - | ||
| 2121 | -**按默认(相关性)排序**: | ||
| 2122 | -```json | ||
| 2123 | -{ | ||
| 2124 | - "query": "玩具", | ||
| 2125 | - "size": 20, | ||
| 2126 | - "from": 0 | ||
| 2127 | -} | ||
| 2128 | -``` | ||
| 2129 | - | ||
| 2130 | -### 8.2 过滤搜索 | ||
| 2131 | - | ||
| 2132 | -**需求**: 搜索"玩具",筛选类目为"益智玩具",价格在50-200之间 | ||
| 2133 | - | ||
| 2134 | -```json | ||
| 2135 | -{ | ||
| 2136 | - "query": "玩具", | ||
| 2137 | - "size": 20, | ||
| 2138 | - "language": "zh", | ||
| 2139 | - "filters": { | ||
| 2140 | - "category_name": "益智玩具" | ||
| 2141 | - }, | ||
| 2142 | - "range_filters": { | ||
| 2143 | - "min_price": { | ||
| 2144 | - "gte": 50, | ||
| 2145 | - "lte": 200 | ||
| 2146 | - } | ||
| 2147 | - } | ||
| 2148 | -} | ||
| 2149 | -``` | ||
| 2150 | - | ||
| 2151 | -**需求**: 搜索"手机",筛选多个品牌,价格范围 | ||
| 2152 | - | ||
| 2153 | -```json | ||
| 2154 | -{ | ||
| 2155 | - "query": "手机", | ||
| 2156 | - "size": 20, | ||
| 2157 | - "language": "zh", | ||
| 2158 | - "filters": { | ||
| 2159 | - "vendor.zh.keyword": ["品牌A", "品牌B"] | ||
| 2160 | - }, | ||
| 2161 | - "range_filters": { | ||
| 2162 | - "min_price": { | ||
| 2163 | - "gte": 50, | ||
| 2164 | - "lte": 200 | ||
| 2165 | - } | ||
| 2166 | - } | ||
| 2167 | -} | ||
| 2168 | -``` | ||
| 2169 | - | ||
| 2170 | -### 8.3 分面搜索 | ||
| 2171 | - | ||
| 2172 | -**需求**: 搜索"玩具",获取类目和规格的分面统计,用于构建筛选器 | ||
| 2173 | - | ||
| 2174 | -```json | ||
| 2175 | -{ | ||
| 2176 | - "query": "玩具", | ||
| 2177 | - "size": 20, | ||
| 2178 | - "language": "zh", | ||
| 2179 | - "facets": [ | ||
| 2180 | - {"field": "category1_name", "size": 15, "type": "terms"}, | ||
| 2181 | - {"field": "category2_name", "size": 10, "type": "terms"}, | ||
| 2182 | - {"field": "specifications", "size": 10, "type": "terms"} | ||
| 2183 | - ] | ||
| 2184 | -} | ||
| 2185 | -``` | ||
| 2186 | - | ||
| 2187 | -**需求**: 搜索"手机",获取价格区间和规格的分面统计 | ||
| 2188 | - | ||
| 2189 | -```json | ||
| 2190 | -{ | ||
| 2191 | - "query": "手机", | ||
| 2192 | - "size": 20, | ||
| 2193 | - "language": "zh", | ||
| 2194 | - "facets": [ | ||
| 2195 | - { | ||
| 2196 | - "field": "min_price", | ||
| 2197 | - "type": "range", | ||
| 2198 | - "ranges": [ | ||
| 2199 | - {"key": "0-50", "to": 50}, | ||
| 2200 | - {"key": "50-100", "from": 50, "to": 100}, | ||
| 2201 | - {"key": "100-200", "from": 100, "to": 200}, | ||
| 2202 | - {"key": "200+", "from": 200} | ||
| 2203 | - ] | ||
| 2204 | - }, | ||
| 2205 | - { | ||
| 2206 | - "field": "specifications", | ||
| 2207 | - "size": 10, | ||
| 2208 | - "type": "terms" | ||
| 2209 | - } | ||
| 2210 | - ] | ||
| 2211 | -} | ||
| 2212 | -``` | ||
| 2213 | - | ||
| 2214 | -### 8.4 规格过滤与分面 | ||
| 2215 | - | ||
| 2216 | -**需求**: 搜索"手机",筛选color为"white"的商品 | ||
| 2217 | - | ||
| 2218 | -```json | ||
| 2219 | -{ | ||
| 2220 | - "query": "手机", | ||
| 2221 | - "size": 20, | ||
| 2222 | - "language": "zh", | ||
| 2223 | - "filters": { | ||
| 2224 | - "specifications": { | ||
| 2225 | - "name": "color", | ||
| 2226 | - "value": "white" | ||
| 2227 | - } | ||
| 2228 | - } | ||
| 2229 | -} | ||
| 2230 | -``` | ||
| 2231 | - | ||
| 2232 | -**需求**: 搜索"手机",筛选color为"white"且size为"256GB"的商品 | ||
| 2233 | - | ||
| 2234 | -```json | ||
| 2235 | -{ | ||
| 2236 | - "query": "手机", | ||
| 2237 | - "size": 20, | ||
| 2238 | - "language": "zh", | ||
| 2239 | - "filters": { | ||
| 2240 | - "specifications": [ | ||
| 2241 | - {"name": "color", "value": "white"}, | ||
| 2242 | - {"name": "size", "value": "256GB"} | ||
| 2243 | - ] | ||
| 2244 | - } | ||
| 2245 | -} | ||
| 2246 | -``` | ||
| 2247 | - | ||
| 2248 | -**需求**: 搜索"手机",筛选size为"3"、"4"或"5",且color为"green"的商品 | ||
| 2249 | - | ||
| 2250 | -```json | ||
| 2251 | -{ | ||
| 2252 | - "query": "手机", | ||
| 2253 | - "size": 20, | ||
| 2254 | - "language": "zh", | ||
| 2255 | - "filters": { | ||
| 2256 | - "specifications": [ | ||
| 2257 | - {"name": "size", "value": "3"}, | ||
| 2258 | - {"name": "size", "value": "4"}, | ||
| 2259 | - {"name": "size", "value": "5"}, | ||
| 2260 | - {"name": "color", "value": "green"} | ||
| 2261 | - ] | ||
| 2262 | - } | ||
| 2263 | -} | ||
| 2264 | -``` | ||
| 2265 | - | ||
| 2266 | -**需求**: 搜索"手机",获取所有规格的分面统计 | ||
| 2267 | - | ||
| 2268 | -```json | ||
| 2269 | -{ | ||
| 2270 | - "query": "手机", | ||
| 2271 | - "size": 20, | ||
| 2272 | - "language": "zh", | ||
| 2273 | - "facets": [ | ||
| 2274 | - {"field": "specifications", "size": 10, "type": "terms"} | ||
| 2275 | - ] | ||
| 2276 | -} | ||
| 2277 | -``` | ||
| 2278 | - | ||
| 2279 | -**需求**: 只获取"color"和"size"规格的分面统计 | ||
| 2280 | - | ||
| 2281 | -```json | ||
| 2282 | -{ | ||
| 2283 | - "query": "手机", | ||
| 2284 | - "size": 20, | ||
| 2285 | - "language": "zh", | ||
| 2286 | - "facets": [ | ||
| 2287 | - {"field": "specifications.color", "size": 20, "type": "terms"}, | ||
| 2288 | - {"field": "specifications.size", "size": 15, "type": "terms"} | ||
| 2289 | - ] | ||
| 2290 | -} | ||
| 2291 | -``` | ||
| 2292 | - | ||
| 2293 | -**需求**: 搜索"手机",筛选类目和规格,并获取对应的分面统计 | ||
| 2294 | - | ||
| 2295 | -```json | ||
| 2296 | -{ | ||
| 2297 | - "query": "手机", | ||
| 2298 | - "size": 20, | ||
| 2299 | - "language": "zh", | ||
| 2300 | - "filters": { | ||
| 2301 | - "category_name": "手机", | ||
| 2302 | - "specifications": { | ||
| 2303 | - "name": "color", | ||
| 2304 | - "value": "white" | ||
| 2305 | - } | ||
| 2306 | - }, | ||
| 2307 | - "facets": [ | ||
| 2308 | - {"field": "category1_name", "size": 15, "type": "terms"}, | ||
| 2309 | - {"field": "category2_name", "size": 10, "type": "terms"}, | ||
| 2310 | - {"field": "specifications.color", "size": 20, "type": "terms"}, | ||
| 2311 | - {"field": "specifications.size", "size": 15, "type": "terms"} | ||
| 2312 | - ] | ||
| 2313 | -} | ||
| 2314 | -``` | ||
| 2315 | - | ||
| 2316 | -### 8.5 SKU筛选 | ||
| 2317 | - | ||
| 2318 | -**需求**: 搜索"芭比娃娃",每个SPU下按颜色筛选,每种颜色只显示一个SKU | ||
| 2319 | - | ||
| 2320 | -```json | ||
| 2321 | -{ | ||
| 2322 | - "query": "芭比娃娃", | ||
| 2323 | - "size": 20, | ||
| 2324 | - "sku_filter_dimension": ["color"] | ||
| 2325 | -} | ||
| 2326 | -``` | ||
| 2327 | - | ||
| 2328 | -**说明**: | ||
| 2329 | -- 如果 `option1_name` 为 `"color"`,则使用 `sku_filter_dimension: ["color"]` 可以按颜色分组 | ||
| 2330 | -- 每个SPU下,每种颜色只会返回第一个SKU | ||
| 2331 | -- 如果维度不匹配,返回所有SKU(不进行过滤) | ||
| 2332 | - | ||
| 2333 | -### 8.7 分页查询 | ||
| 2334 | - | ||
| 2335 | -**需求**: 获取第2页结果(每页20条) | ||
| 2336 | - | ||
| 2337 | -```json | ||
| 2338 | -{ | ||
| 2339 | - "query": "手机", | ||
| 2340 | - "size": 20, | ||
| 2341 | - "from": 20 | ||
| 2342 | -} | ||
| 2343 | -``` | ||
| 2344 | - | ||
| 2345 | ---- | ||
| 2346 | - | ||
| 2347 | -## 9. 数据模型 | ||
| 2348 | - | ||
| 2349 | -### 9.1 商品字段定义 | ||
| 2350 | - | ||
| 2351 | -| 字段名 | 类型 | 描述 | | ||
| 2352 | -|--------|------|------| | ||
| 2353 | -| `tenant_id` | keyword | 租户ID(多租户隔离) | | ||
| 2354 | -| `spu_id` | keyword | SPU ID | | ||
| 2355 | -| `title.<lang>` | object/text | 商品标题(多语言对象,如 `title.zh`, `title.en`) | | ||
| 2356 | -| `brief.<lang>` | object/text | 商品短描述(多语言对象,如 `brief.zh`, `brief.en`) | | ||
| 2357 | -| `description.<lang>` | object/text | 商品详细描述(多语言对象,如 `description.zh`, `description.en`) | | ||
| 2358 | -| `vendor.<lang>` | object/text | 供应商/品牌(多语言对象,且带 keyword 子字段,如 `vendor.zh.keyword`) | | ||
| 2359 | -| `category_path.<lang>` | object/text | 类目路径(多语言对象,用于搜索,如 `category_path.zh`) | | ||
| 2360 | -| `category_name_text.<lang>` | object/text | 类目名称(多语言对象,用于搜索,如 `category_name_text.zh`) | | ||
| 2361 | -| `category_id` | keyword | 类目ID | | ||
| 2362 | -| `category_name` | keyword | 类目名称(用于过滤) | | ||
| 2363 | -| `category_level` | integer | 类目层级 | | ||
| 2364 | -| `category1_name`, `category2_name`, `category3_name` | keyword | 多级类目名称(用于过滤和分面) | | ||
| 2365 | -| `tags` | keyword | 标签(数组) | | ||
| 2366 | -| `specifications` | nested | 规格(嵌套对象数组) | | ||
| 2367 | -| `option1_name`, `option2_name`, `option3_name` | keyword | 选项名称 | | ||
| 2368 | -| `min_price`, `max_price` | float | 最低/最高价格 | | ||
| 2369 | -| `compare_at_price` | float | 原价 | | ||
| 2370 | -| `sku_prices` | float | SKU价格列表(数组) | | ||
| 2371 | -| `sku_weights` | long | SKU重量列表(数组) | | ||
| 2372 | -| `sku_weight_units` | keyword | SKU重量单位列表(数组) | | ||
| 2373 | -| `total_inventory` | long | 总库存 | | ||
| 2374 | -| `sales` | long | 销量(展示销量) | | ||
| 2375 | -| `skus` | nested | SKU详细信息(嵌套对象数组) | | ||
| 2376 | -| `create_time`, `update_time` | date | 创建/更新时间 | | ||
| 2377 | -| `title_embedding` | dense_vector | 标题向量(1024维,仅用于搜索) | | ||
| 2378 | -| `image_embedding` | nested | 图片向量(嵌套,仅用于搜索) | | ||
| 2379 | - | ||
| 2380 | -> 所有租户共享统一的索引结构。文本字段支持中英文双语,后端根据 `language` 参数自动选择对应字段返回。 | ||
| 2381 | - | ||
| 2382 | -### 9.2 字段类型速查 | ||
| 2383 | - | ||
| 2384 | -| 类型 | ES Mapping | 用途 | | ||
| 2385 | -|------|------------|------| | ||
| 2386 | -| `text` | `text` | 全文检索(支持中英文分析器) | | ||
| 2387 | -| `keyword` | `keyword` | 精确匹配、聚合、排序 | | ||
| 2388 | -| `integer` | `integer` | 整数 | | ||
| 2389 | -| `long` | `long` | 长整数 | | ||
| 2390 | -| `float` | `float` | 浮点数 | | ||
| 2391 | -| `date` | `date` | 日期时间 | | ||
| 2392 | -| `nested` | `nested` | 嵌套对象(specifications, skus, image_embedding) | | ||
| 2393 | -| `dense_vector` | `dense_vector` | 向量字段(title_embedding,仅用于搜索) | | ||
| 2394 | - | ||
| 2395 | -### 9.3 常用字段列表 | ||
| 2396 | - | ||
| 2397 | -#### 过滤字段 | ||
| 2398 | - | ||
| 2399 | -- `category_name`: 类目名称 | ||
| 2400 | -- `category1_name`, `category2_name`, `category3_name`: 多级类目 | ||
| 2401 | -- `category_id`: 类目ID | ||
| 2402 | -- `vendor.zh.keyword`, `vendor.en.keyword`: 供应商/品牌(使用keyword子字段) | ||
| 2403 | -- `tags`: 标签(keyword类型) | ||
| 2404 | -- `option1_name`, `option2_name`, `option3_name`: 选项名称 | ||
| 2405 | -- `specifications`: 规格过滤(嵌套字段,格式见[过滤器详解](#33-过滤器详解)) | ||
| 2406 | - | ||
| 2407 | -#### 范围字段 | ||
| 2408 | - | ||
| 2409 | -- `min_price`: 最低价格 | ||
| 2410 | -- `max_price`: 最高价格 | ||
| 2411 | -- `compare_at_price`: 原价 | ||
| 2412 | -- `create_time`: 创建时间 | ||
| 2413 | -- `update_time`: 更新时间 | ||
| 2414 | - | ||
| 2415 | -#### 排序字段 | ||
| 2416 | - | ||
| 2417 | -- `price`: 价格(后端自动根据sort_order映射:asc→min_price,desc→max_price) | ||
| 2418 | -- `sales`: 销量 | ||
| 2419 | -- `create_time`: 创建时间 | ||
| 2420 | -- `update_time`: 更新时间 | ||
| 2421 | -- `relevance_score`: 相关性分数(默认,不指定sort_by时使用) | ||
| 2422 | - | ||
| 2423 | -**注意**: 前端只需传 `price`,后端会自动处理: | ||
| 2424 | -- `sort_by: "price"` + `sort_order: "asc"` → 按 `min_price` 升序(价格从低到高) | ||
| 2425 | -- `sort_by: "price"` + `sort_order: "desc"` → 按 `max_price` 降序(价格从高到低) | ||
| 2426 | - | ||
| 2427 | -### 9.4 支持的分析器 | ||
| 2428 | - | ||
| 2429 | -| 分析器 | 语言 | 描述 | | ||
| 2430 | -|--------|------|------| | ||
| 2431 | -| `index_ik` | 中文 | 中文索引分析器(用于中文字段) | | ||
| 2432 | -| `query_ik` | 中文 | 中文查询分析器(用于中文字段) | | ||
| 2433 | -| `hanlp_index` ⚠️ TODO(暂不支持) | 中文 | 中文索引分析器(用于中文字段) | | ||
| 2434 | -| `hanlp_standard` ⚠️ TODO(暂不支持) | 中文 | 中文查询分析器(用于中文字段) | | ||
| 2435 | -| `english` | 英文 | 标准英文分析器(用于英文字段) | | ||
| 2436 | -| `lowercase` | - | 小写标准化器(用于keyword子字段) | | ||
| 2437 | - | ||
| 2438 | ---- | ||
| 2439 | - | ||
| 2440 | -## 10. 接口级压测脚本 | ||
| 2441 | - | ||
| 2442 | -仓库提供统一压测脚本:`scripts/perf_api_benchmark.py`,用于对以下接口做并发压测: | ||
| 2443 | - | ||
| 2444 | -- 后端搜索:`POST /search/` | ||
| 2445 | -- 搜索建议:`GET /search/suggestions` | ||
| 2446 | -- 向量服务:`POST /embed/text` | ||
| 2447 | -- 翻译服务:`POST /translate` | ||
| 2448 | -- 重排服务:`POST /rerank` | ||
| 2449 | - | ||
| 2450 | -说明:脚本对 `embed_text` 场景会校验返回向量内容有效性(必须是有限数值,不允许 `null/NaN/Inf`),不是只看 HTTP 200。 | ||
| 2451 | - | ||
| 2452 | -### 10.1 快速示例 | ||
| 2453 | - | ||
| 2454 | -```bash | ||
| 2455 | -# suggest 压测(tenant 162) | ||
| 2456 | -python scripts/perf_api_benchmark.py \ | ||
| 2457 | - --scenario backend_suggest \ | ||
| 2458 | - --tenant-id 162 \ | ||
| 2459 | - --duration 30 \ | ||
| 2460 | - --concurrency 50 | ||
| 2461 | - | ||
| 2462 | -# search 压测 | ||
| 2463 | -python scripts/perf_api_benchmark.py \ | ||
| 2464 | - --scenario backend_search \ | ||
| 2465 | - --tenant-id 162 \ | ||
| 2466 | - --duration 30 \ | ||
| 2467 | - --concurrency 20 | ||
| 2468 | - | ||
| 2469 | -# 全链路压测(search + suggest + embedding + translate + rerank) | ||
| 2470 | -python scripts/perf_api_benchmark.py \ | ||
| 2471 | - --scenario all \ | ||
| 2472 | - --tenant-id 162 \ | ||
| 2473 | - --duration 60 \ | ||
| 2474 | - --concurrency 30 \ | ||
| 2475 | - --output perf_reports/all.json | ||
| 2476 | -``` | ||
| 2477 | - | ||
| 2478 | -### 10.2 自定义用例 | ||
| 2479 | - | ||
| 2480 | -可通过 `--cases-file` 覆盖默认请求模板。示例文件: | ||
| 2481 | - | ||
| 2482 | -```bash | ||
| 2483 | -scripts/perf_cases.json.example | ||
| 2484 | -``` | ||
| 2485 | - | ||
| 2486 | -执行示例: | ||
| 2487 | - | ||
| 2488 | -```bash | ||
| 2489 | -python scripts/perf_api_benchmark.py \ | ||
| 2490 | - --scenario all \ | ||
| 2491 | - --tenant-id 162 \ | ||
| 2492 | - --cases-file scripts/perf_cases.json.example \ | ||
| 2493 | - --duration 60 \ | ||
| 2494 | - --concurrency 40 | ||
| 2495 | -``` |
embeddings/server.py
| @@ -129,7 +129,7 @@ _TEXT_REQUEST_TIMEOUT_SEC = max( | @@ -129,7 +129,7 @@ _TEXT_REQUEST_TIMEOUT_SEC = max( | ||
| 129 | 1.0, float(os.getenv("TEXT_REQUEST_TIMEOUT_SEC", "30")) | 129 | 1.0, float(os.getenv("TEXT_REQUEST_TIMEOUT_SEC", "30")) |
| 130 | ) | 130 | ) |
| 131 | _TEXT_MAX_INFLIGHT = max(1, int(os.getenv("TEXT_MAX_INFLIGHT", "32"))) | 131 | _TEXT_MAX_INFLIGHT = max(1, int(os.getenv("TEXT_MAX_INFLIGHT", "32"))) |
| 132 | -_IMAGE_MAX_INFLIGHT = max(1, int(os.getenv("IMAGE_MAX_INFLIGHT", "1"))) | 132 | +_IMAGE_MAX_INFLIGHT = max(1, int(os.getenv("IMAGE_MAX_INFLIGHT", "20"))) |
| 133 | _OVERLOAD_STATUS_CODE = int(os.getenv("EMBEDDING_OVERLOAD_STATUS_CODE", "503")) | 133 | _OVERLOAD_STATUS_CODE = int(os.getenv("EMBEDDING_OVERLOAD_STATUS_CODE", "503")) |
| 134 | _LOG_PREVIEW_COUNT = max(1, int(os.getenv("EMBEDDING_LOG_PREVIEW_COUNT", "3"))) | 134 | _LOG_PREVIEW_COUNT = max(1, int(os.getenv("EMBEDDING_LOG_PREVIEW_COUNT", "3"))) |
| 135 | _LOG_TEXT_PREVIEW_CHARS = max(32, int(os.getenv("EMBEDDING_LOG_TEXT_PREVIEW_CHARS", "120"))) | 135 | _LOG_TEXT_PREVIEW_CHARS = max(32, int(os.getenv("EMBEDDING_LOG_TEXT_PREVIEW_CHARS", "120"))) |