# 搜索API对接指南-07-微服务接口（Embedding-Reranker-Translation）

本篇覆盖向量服务（Embedding）、重排服务（Reranker）、翻译服务（Translation）以及 Indexer 服务内的内容理解字段生成（原文第 7 章）。

## 7. 微服务接口（向量、重排、翻译）

以下三个微服务独立部署，**外部系统可直接调用**。它们被搜索后端（6002）和索引服务（6004）内部使用，也可供其他业务系统直接对接。

| 服务 | 默认端口 | Base URL | 说明 |
|------|----------|----------|------|
| 向量服务（文本） | 6005 | `http://localhost:6005` | 文本向量化，用于 query/doc 语义检索 |
| 向量服务（图片 / 多模态 CN-CLIP） | 6008 | `http://localhost:6008` | 图片向量 `/embed/image`；同空间文本向量 `/embed/clip_text`（以文搜图等） |
| 翻译服务 | 6006 | `http://localhost:6006` | 多语言翻译（云端与本地模型统一入口） |
| 重排服务 | 6007 | `http://localhost:6007` | 对检索结果进行二次排序 |

生产环境请将 `localhost` 替换为实际服务地址。
服务管理入口与完整启停规则见：`docs/Usage-Guide.md` -> `服务管理总览`。

### 7.1 向量服务（Embedding）

- **Base URL**:
  - 文本：`http://localhost:6005`（可通过 `EMBEDDING_TEXT_SERVICE_URL` 覆盖）
  - 图片：`http://localhost:6008`（可通过 `EMBEDDING_IMAGE_SERVICE_URL` 覆盖）
- **启动**:
  - 文本：`./scripts/start_embedding_text_service.sh`
  - 图片：`./scripts/start_embedding_image_service.sh`
- **依赖**:
  - 文本向量后端默认走 TEI（`http://127.0.0.1:8080`）
  - 图片向量依赖 `cnclip`（`grpc://127.0.0.1:51000`）
  - TEI 默认使用 GPU（`TEI_DEVICE=cuda`）；当配置为 GPU 且不可用时会启动失败（不会自动降级到 CPU）
  - cnclip 默认使用 `cuda`；若配置为 `cuda` 但 GPU 不可用会启动失败（不会自动降级到 `cpu`）
  - 当前单机部署建议保持单实例，通过**文本/图片拆分 + 独立限流**隔离压力

补充说明：

- 文本和图片现在已经拆成**不同进程 / 不同端口**，避免图片下载与编码波动影响文本向量化。
- 服务端对 text / image 有**独立 admission control**：
  - `TEXT_MAX_INFLIGHT`
  - `IMAGE_MAX_INFLIGHT`
- 当超过处理能力时，服务会直接返回过载错误，而不是无限排队。
- 文本与图片服务均支持 `priority` query 参数（图片不做队列插队，仅 admission 规则与文本一致）：
  - `priority=0`（默认）：适合离线索引，仍分别受 `TEXT_MAX_INFLIGHT` / `IMAGE_MAX_INFLIGHT` admission control 约束。
  - `priority>0`（建议在线请求用 `1`）：不会因 admission control 被拒绝，但仍会占用对应 text/image 的 inflight。
  - 文本服务端会优先处理高优先级文本请求；图片端不实现插队，顺序按请求到达处理即可。
- `GET /health` 会返回各自的 `limits`、`stats`、`cache_enabled` 等状态；`GET /ready` 用于就绪探针。

#### 7.1.1 `POST /embed/text` — 文本向量化

将文本列表转为 1024 维向量，用于语义搜索、文档索引等。

**完整 curl 示例**:

```bash
curl -X POST "http://localhost:6005/embed/text?normalize=true&priority=1" \
  -H "Content-Type: application/json" \
  -d '["芭比娃娃 儿童玩具", "纯棉T恤 短袖"]'
```

**响应**（JSON 数组，与输入一一对应）:

```json
[[0.01, -0.02, ...], [0.03, 0.01, ...], ...]
```

说明：
- 在线 query / 实时请求：显式传 `priority=1`
- 离线索引 / 批量回填：保持默认 `priority=0` 即可
- 离线索引建议直接传数组，**单次尽量聚合到 24 条文本**。
- 当前 TEI 客户端按 `TEI_MAX_CLIENT_BATCH_SIZE=24` 调优；超过该值会继续拆批，但推荐调用方主动分批。

#### 7.1.2 `POST /embed/image` — 图片向量化

将图片 URL 或路径转为向量，用于以图搜图。

前置条件：`cnclip` 服务已启动（默认端口 `51000`）。若未启动，图片 embedding 服务启动会失败或请求返回错误。

**完整 curl 示例**:

```bash
curl -X POST "http://localhost:6008/embed/image?normalize=true&priority=1" \
  -H "Content-Type: application/json" \
  -d '["https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg"]'
```

**响应**（JSON 数组，与输入一一对应）:

```json
[[0.01, -0.02, ...], [0.03, 0.01, ...], ...]
```

在线以图搜图等实时场景可传 `priority=1`；离线索引回填保持默认 `priority=0`。
离线图片向量化建议**每次聚合 `8` 张图片 URL** 后请求，避免长期逐张调用。

#### 7.1.3 `POST /embed/clip_text` — CN-CLIP 文本多模态向量（与图片同空间）

将**自然语言短语**编码为向量，与 `POST /embed/image` 输出的图向量**处于同一向量空间**（Chinese-CLIP 文本塔 / 图塔），用于 **以文搜图**、与 ES `image_embedding` 对齐的 KNN 等。默认配置为 **ViT-H-14**，向量长度 **1024**（与 `mappings/search_products.json` 中 `image_embedding.vector.dims` 一致）；若改为 ViT-L-14 则为 768 维，须同步索引映射与全量重索引。

**curl 示例**:

```bash
curl -X POST "http://localhost:6008/embed/clip_text?normalize=true&priority=1" \
  -H "Content-Type: application/json" \
  -d '["纯棉短袖", "street tee"]'
```

**响应**（JSON 数组，与输入一一对应）:

```json
[[0.01, -0.02, ...], [0.03, 0.01, ...], ...]
```

说明：与 `/embed/image` 共用图片侧限流与 `IMAGE_MAX_INFLIGHT`；Redis 缓存键 namespace 为 `clip_text`，与 TEI 文本缓存区分。
调用建议：离线批量构建图文检索索引时，建议**每次聚合 `24` 条短文本**。

#### 7.1.4 `GET /health` — 健康检查

```bash
curl "http://localhost:6005/health"
curl "http://localhost:6008/health"
```

返回中会包含：

- `service_kind`：`text` / `image` / `all`
- `cache_enabled`：text/image/clip_text Redis 缓存是否可用
- `limits`：当前 inflight limit、active、rejected_total 等
- `stats`：request_total、cache_hits、cache_misses、avg_latency_ms 等

#### 7.1.5 `GET /ready` — 就绪检查

```bash
curl "http://localhost:6005/ready"
curl "http://localhost:6008/ready"
```

#### 7.1.6 缓存与限流说明

- 文本与图片都会先查 Redis 向量缓存。
- Redis 中 value 仍是 **BF16 bytes**，读取后恢复成 `float32` 返回。
- cache key 已区分 `normalize=true/false`，避免不同归一化策略命中同一条缓存。
- 当服务端发现请求是 **full-cache-hit** 时，会直接返回，不占用模型并发槽位。
- 当服务端发现超过 `TEXT_MAX_INFLIGHT` / `IMAGE_MAX_INFLIGHT` 时，会直接拒绝，而不是无限排队。
- 其中 `POST /embed/text` 的 `priority=0` 会按上面的 inflight 规则直接拒绝；`priority>0` 不会被 admission 拒绝，但仍计入 inflight，并在服务端排队时优先于 `priority=0` 请求。
- `POST /embed/image` 的 `priority=0` 受 `IMAGE_MAX_INFLIGHT` 约束；`priority>0` 不会被 admission 拒绝，但仍计入 inflight（无插队）。
- `POST /embed/clip_text` 与 `/embed/image` 共用同一后端与 `IMAGE_MAX_INFLIGHT`（计入图片侧并发）。

#### 7.1.7 TEI 统一调优建议（主服务）

使用单套主服务即可同时兼顾：
- 在线 query 向量化（低延迟，常见 `batch=1~4`）
- 索引构建向量化（高吞吐，常见 `batch=24`）

统一启动（主链路）：

```bash
./scripts/start_tei_service.sh
./scripts/service_ctl.sh restart embedding
```

默认端口：
- TEI: `http://127.0.0.1:8080`
- 文本向量服务（`/embed/text`）: `http://127.0.0.1:6005`
- 图片向量服务（`/embed/image`、`/embed/clip_text`）: `http://127.0.0.1:6008`

当前主 TEI 启动默认值（已按 T4/短文本场景调优）：
- `TEI_MAX_BATCH_TOKENS=4096`
- `TEI_MAX_CLIENT_BATCH_SIZE=24`
- `TEI_DTYPE=float16`

### 7.2 重排服务（Reranker）

- **Base URL**: `http://localhost:6007`（可通过 `RERANKER_SERVICE_URL` 覆盖）
- **启动**: `./scripts/start_reranker.sh`

说明：默认后端为 `qwen3_vllm`（`Qwen/Qwen3-Reranker-0.6B`），需要可用 GPU 显存。

补充：若切换到 `jina_reranker_v3`，在当前 `Tesla T4` 上建议使用：

- `dtype: float16`
- `batch_size: 64`
- `max_doc_length: 160`
- `max_query_length: 64`
- `sort_by_doc_length: true`

原因：`jina_reranker_v3` 的 `auto` 在当前机器上会落到 `bfloat16`，性能明显差于 `float16`；而它的 listwise 架构在 T4 上对上下文长度更敏感，过大的 batch 会显著拉长延迟。

补充：`docs` 的请求大小与模型推理 `batch size` 解耦。即使一次传入 1000 条文档，服务端也会按 `services.rerank.backends.qwen3_vllm.infer_batch_size` 自动拆分。

#### 7.2.1 `POST /rerank` — 结果重排

根据 query 与 doc 的相关性对文档列表重新打分排序。

**请求体**:
```json
{
  "query": "玩具 芭比",
  "docs": [
    "12PCS 6 Types of Dolls with Bottles",
    "纯棉T恤 短袖 夏季"
  ],
  "normalize": true
}
```

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `query` | string | Y | 搜索查询 |
| `docs` | array[string] | Y | 待重排的文档列表（单次最多由服务端配置限制） |
| `normalize` | boolean | N | 是否对分数做 sigmoid 归一化，默认 true |

**响应**:
```json
{
  "scores": [0.92, 0.15],
  "meta": {
    "service_elapsed_ms": 45.2,
    "input_docs": 2,
    "unique_docs": 2
  }
}
```

**完整 curl 示例**:
```bash
curl -X POST "http://localhost:6007/rerank" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "玩具 芭比",
    "docs": ["12PCS 6 Types of Dolls with Bottles", "纯棉T恤 短袖"],
    "top_n":386,
    "normalize": true
  }'
```

#### 7.2.2 `GET /health` — 健康检查

```bash
curl "http://localhost:6007/health"
```

### 7.3 翻译服务（Translation）

- **Base URL**: `http://localhost:6006`（以 `config/config.yaml -> services.translation.service_url` 为准）
- **启动**: `./scripts/start_translator.sh`

#### 7.3.1 `POST /translate` — 文本翻译

支持 translator service 内所有已启用 capability，适用于商品名称、描述、query 等电商场景。当前可配置能力包括 `qwen-mt`、`llm`、`deepl` 以及本地模型 `nllb-200-distilled-600m`、`opus-mt-zh-en`、`opus-mt-en-zh`。

**请求体**（支持单条字符串或字符串列表）:
```json
{
  "text": "商品名称",
  "target_lang": "en",
  "source_lang": "zh",
  "model": "qwen-mt",
  "scene": "sku_name"
}
```

也支持批量列表形式:
```json
{
  "text": ["商品名称1", "商品名称2"],
  "target_lang": "en",
  "source_lang": "zh",
  "model": "qwen-mt",
  "scene": "sku_name"
}
```

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `text` | string \| string[] | Y | 待翻译文本，既支持单条字符串，也支持字符串列表（批量翻译） |
| `target_lang` | string | Y | 目标语言：`zh`、`en`、`ru` 等 |
| `source_lang` | string | N | 源语言。云端模型可不传；`nllb-200-distilled-600m` 建议显式传入 |
| `model` | string | N | 已启用 capability 名称，如 `qwen-mt`、`llm`、`deepl`、`nllb-200-distilled-600m`、`opus-mt-zh-en`、`opus-mt-en-zh` |
| `scene` | string | N | 翻译场景参数，与 `model` 配套使用；当前标准值为 `sku_name`、`ecommerce_search_query`、`sku_attribute`、`general` |

说明：
- 外部接口不接受 `prompt`；LLM prompt 由服务端按 `scene` 自动生成。
- 传入未定义的 `scene` 或未启用的 `model` 会返回 `400`。

**SKU 名称场景选型建议**:
- 批量 SKU 名称翻译，优先考虑本地大吞吐方案时，可使用 `"model": "nllb-200-distilled-600m"`（该模型"scene":参数无效）。
- 如果目标是更高质量，且可以接受更慢速度与额外 LLM API 费用，可使用 `"model": "llm"` + `"scene": "sku_name"`。
- 如果是en-zh互译、期待更高的速度，可以考虑`opus-mt-zh-en` / `opus-mt-en-zh`。（质量未详细评测，一些文章说比blib-200-600m更好，但是我看了些case感觉要差不少）

**实时翻译选型建议**:
- 在线 query 翻译如果只是 `en/zh` 互译，极致要求耗时使用 `opus-mt-zh-en / opus-mt-en-zh`，`nllb-200-distilled-600m`支持多语言，效果略好一点，但是耗时长很多（120-190ms左右）
- 如果涉及其他语言，或对质量要求高于本地轻量模型，优先考虑 `deepl`。

**Batch Size / 调用方式建议**:
- 本接口支持 `text: string[]`；离线或批量索引翻译时，应尽量合并请求，让底层 backend 发挥批处理能力。
- 对商品标题、标题片段、属性值等短文本，调用方建议**按 `8~16` 条/批**聚合后再调用；这比大量 `batch=1` 并发更能发挥 GPU 吞吐。
- `nllb-200-distilled-600m` 在当前 `Tesla T4` 压测中，推荐配置是 `batch_size=16`、`max_new_tokens=64`、`attn_implementation=sdpa`；继续升到 `batch_size=32` 虽可能提高吞吐，但 tail latency 会明显变差。
- 在线 query 场景可直接把“单条请求”理解为 `batch_size=1`；更关注 request latency，而不是离线吞吐。
- `opus-mt-zh-en` / `opus-mt-en-zh` 当前生产配置也是 `batch_size=16`，适合作为中英互译的低延迟本地默认值；若走在线单条调用，同样按 `batch_size=1` 理解即可。
- `llm`、`qwen-mt`、`deepl` 也支持列表调用；如果是离线索引链路，仍建议优先合并成批，以减少 HTTP 往返和缓存探测开销。

**响应**:
```json
{
  "text": "商品名称",
  "target_lang": "en",
  "source_lang": "zh",
  "translated_text": "Product name",
  "status": "success",
  "model": "qwen-mt",
  "scene": "sku_name"
}
```

当请求为列表形式时，`text` 与 `translated_text` 均为等长数组:
```json
{
  "text": ["商品名称1", "商品名称2"],
  "target_lang": "en",
  "source_lang": "zh",
  "translated_text": ["Product name 1", "Product name 2"],
  "status": "success",
  "model": "qwen-mt",
  "scene": "sku_name"
}
```

> **失败语义（批量）**：当 `text` 为列表时，如果其中某条翻译失败，对应位置返回 `null`（即 `translated_text[i] = null`），并保持数组长度与顺序不变；接口整体仍返回 `status="success"`，用于避免“部分失败”导致整批请求失败。

> **实现提示（可忽略）**：服务端会尽可能使用底层 backend 的批量能力（若支持），否则自动拆分逐条翻译；无论采用哪种方式，上述批量契约保持一致。

**完整 curl 示例**:

中文 → 英文:
```bash
curl -X POST "http://localhost:6006/translate" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "商品名称",
    "target_lang": "en",
    "source_lang": "zh"
  }'
```

俄文 → 英文:
```bash
curl -X POST "http://localhost:6006/translate" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Название товара",
    "target_lang": "en",
    "source_lang": "ru"
  }'
```

使用 DeepL 模型:
```bash
curl -X POST "http://localhost:6006/translate" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "商品名称",
    "target_lang": "en",
    "source_lang": "zh",
    "model": "deepl"
  }'
```

使用本地 OPUS 模型（中文 → 英文）:
```bash
curl -X POST "http://localhost:6006/translate" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "蓝牙耳机",
    "target_lang": "en",
    "source_lang": "zh",
    "model": "opus-mt-zh-en",
    "scene": "sku_name"
  }'
```

使用本地 NLLB 做 SKU 名称批量翻译:
```bash
curl -X POST "http://localhost:6006/translate" \
  -H "Content-Type: application/json" \
  -d '{
    "text": ["商品名称1", "商品名称2", "商品名称3"],
    "target_lang": "en",
    "source_lang": "zh",
    "model": "nllb-200-distilled-600m",
    "scene": "sku_name"
  }'
```

使用 LLM 做高质量 SKU 名称翻译:
```bash
curl -X POST "http://localhost:6006/translate" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "男士偏光飞行员太阳镜",
    "target_lang": "en",
    "source_lang": "zh",
    "model": "llm",
    "scene": "sku_name"
  }'
```

#### 7.3.2 `GET /health` — 健康检查

```bash
curl "http://localhost:6006/health"
```

典型响应：
```json
{
  "status": "healthy",
  "service": "translation",
  "default_model": "llm",
  "default_scene": "general",
  "available_models": ["qwen-mt", "llm", "opus-mt-zh-en"],
  "enabled_capabilities": ["qwen-mt", "llm", "opus-mt-zh-en"],
  "loaded_models": ["llm"]
}
```

### 7.4 内容理解字段生成（Indexer 服务内）

内容理解字段生成接口部署在 **Indexer 服务**（默认端口 6004）内，与「翻译、向量化」等独立端口微服务并列，供采用**微服务组合**方式的 indexer 调用。

- **Base URL**: Indexer 服务地址，如 `http://localhost:6004`
- **路径**: `POST /indexer/enrich-content`
- **说明**: 根据商品标题批量生成 `qanchors`、`enriched_attributes`、`enriched_tags`、`enriched_taxonomy_attributes`，用于拼装 ES 文档。支持通过 `enrichment_scopes` 选择执行 `generic` / `category_taxonomy`，并通过 `category_taxonomy_profile` 选择对应大类的 taxonomy prompt/profile；默认执行 `generic + category_taxonomy(apparel)`。当前支持的 taxonomy profile 包括 `apparel`、`3c`、`bags`、`pet_supplies`、`electronics`、`outdoor`、`home_appliances`、`home_living`、`wigs`、`beauty`、`accessories`、`toys`、`shoes`、`sports`、`others`。所有 profile 的 taxonomy 输出都统一返回 `zh` + `en`，`category_taxonomy_profile` 只决定字段集合。内部使用大模型（需配置 `DASHSCOPE_API_KEY`），支持按单条进行 Redis 缓存；单次最多 50 条，但推荐常态请求控制在 `20` 条左右**。内部大模型处理批次按 `20` 条拆分。

请求/响应格式、示例及错误码见 [-05-索引接口（Indexer）](./搜索API对接指南-05-索引接口（Indexer）.md#58-内容理解字段生成接口)。

---
