Commit 5bac964944e8cd54c86dbb9a8713476fba74c50c
1 parent
7214c2e7
文本 embedding 与图片 embedding 已拆分为两个独立进程 / 端口
Showing
16 changed files
with
229 additions
and
150 deletions
Show diff stats
docs/CNCLIP_SERVICE说明文档.md
| @@ -5,7 +5,7 @@ | @@ -5,7 +5,7 @@ | ||
| 5 | ## 1. 作用与边界 | 5 | ## 1. 作用与边界 |
| 6 | 6 | ||
| 7 | - `cnclip` 是独立 gRPC 服务(默认 `grpc://127.0.0.1:51000`)。 | 7 | - `cnclip` 是独立 gRPC 服务(默认 `grpc://127.0.0.1:51000`)。 |
| 8 | -- `embedding` 服务(6005)在 `USE_CLIP_AS_SERVICE=true` 时调用它完成 `/embed/image`。 | 8 | +- 图片 embedding 服务(默认 `6008`)在 `USE_CLIP_AS_SERVICE=true` 时调用它完成 `/embed/image`。 |
| 9 | - `cnclip` 不负责文本向量;文本向量由 TEI(8080)负责。 | 9 | - `cnclip` 不负责文本向量;文本向量由 TEI(8080)负责。 |
| 10 | 10 | ||
| 11 | ## 2. 代码与脚本入口 | 11 | ## 2. 代码与脚本入口 |
| @@ -90,7 +90,7 @@ CNCLIP_MODEL_NAME=CN-CLIP/ViT-H-14 ./scripts/service_ctl.sh start cnclip | @@ -90,7 +90,7 @@ CNCLIP_MODEL_NAME=CN-CLIP/ViT-H-14 ./scripts/service_ctl.sh start cnclip | ||
| 90 | 90 | ||
| 91 | ```bash | 91 | ```bash |
| 92 | ./scripts/service_ctl.sh start cnclip | 92 | ./scripts/service_ctl.sh start cnclip |
| 93 | -# 或一次启动可选能力:./scripts/service_ctl.sh start embedding tei cnclip | 93 | +# 或一次启动可选能力:./scripts/service_ctl.sh start embedding embedding-image tei cnclip |
| 94 | ``` | 94 | ``` |
| 95 | 95 | ||
| 96 | ### 6.2 设备选择优先级 | 96 | ### 6.2 设备选择优先级 |
| @@ -162,9 +162,9 @@ GPU 模式下应出现 `clip_server` 相关 `python` 进程及显存占用。 | @@ -162,9 +162,9 @@ GPU 模式下应出现 `clip_server` 相关 `python` 进程及显存占用。 | ||
| 162 | ### 7.5 与 embedding 服务联调 | 162 | ### 7.5 与 embedding 服务联调 |
| 163 | 163 | ||
| 164 | ```bash | 164 | ```bash |
| 165 | -./scripts/start_embedding_service.sh | 165 | +./scripts/start_embedding_image_service.sh |
| 166 | 166 | ||
| 167 | -curl -sS -X POST "http://127.0.0.1:6005/embed/image" \ | 167 | +curl -sS -X POST "http://127.0.0.1:6008/embed/image" \ |
| 168 | -H "Content-Type: application/json" \ | 168 | -H "Content-Type: application/json" \ |
| 169 | -d '["https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg"]' | 169 | -d '["https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg"]' |
| 170 | ``` | 170 | ``` |
docs/DEVELOPER_GUIDE.md
| @@ -92,7 +92,8 @@ MySQL (店匠 SPU/SKU) | @@ -92,7 +92,8 @@ MySQL (店匠 SPU/SKU) | ||
| 92 | | backend | 6002 | 搜索 API(含 admin) | ✓ | | 92 | | backend | 6002 | 搜索 API(含 admin) | ✓ | |
| 93 | | indexer | 6004 | 索引 API(reindex/build-docs 等) | ✓ | | 93 | | indexer | 6004 | 索引 API(reindex/build-docs 等) | ✓ | |
| 94 | | frontend | 6003 | 调试 UI | ✓ | | 94 | | frontend | 6003 | 调试 UI | ✓ | |
| 95 | -| embedding | 6005 | 向量服务(文本/图片) | 可选 | | 95 | +| embedding | 6005 | 文本向量服务 | 可选 | |
| 96 | +| embedding-image | 6008 | 图片向量服务 | 可选 | | ||
| 96 | | translator | 6006 | 翻译服务(`POST /translate` 支持单条或批量 list;批量失败用 `null` 占位) | 可选 | | 97 | | translator | 6006 | 翻译服务(`POST /translate` 支持单条或批量 list;批量失败用 `null` 占位) | 可选 | |
| 97 | | reranker | 6007 | 重排服务 | 可选 | | 98 | | reranker | 6007 | 重排服务 | 可选 | |
| 98 | 99 | ||
| @@ -156,8 +157,8 @@ docs/ # 文档(含本指南) | @@ -156,8 +157,8 @@ docs/ # 文档(含本指南) | ||
| 156 | 157 | ||
| 157 | ### 4.6 embeddings | 158 | ### 4.6 embeddings |
| 158 | 159 | ||
| 159 | -- **职责**:提供向量服务(FastAPI):`POST /embed/text`、`POST /embed/image`;服务内按配置加载文本后端(如 Qwen3-Embedding-0.6B)与图像后端(如 clip-as-service 或本地 CN-CLIP),实现协议即可插拔。 | ||
| 160 | -- **原则**:图片后端实现 `embeddings/protocols.ImageEncoderProtocol`;文本后端与参数统一从 `config/config.yaml -> services.embedding.backend(s)` 读取。 | 160 | +- **职责**:提供向量服务(FastAPI):文本服务默认监听 `6005`,图片服务默认监听 `6008`;对外暴露 `POST /embed/text`、`POST /embed/image`、`GET /health`、`GET /ready`;服务内按配置加载文本后端(如 Qwen3-Embedding-0.6B)与图像后端(如 clip-as-service 或本地 CN-CLIP),实现协议即可插拔。 |
| 161 | +- **原则**:图片后端实现 `embeddings/protocols.ImageEncoderProtocol`;文本后端与参数统一从 `config/config.yaml -> services.embedding.backend(s)` 读取;文本与图片流量应通过独立进程和独立 inflight limit 做隔离。 | ||
| 161 | - **详见**:`embeddings/README.md`、本指南 §7.5–§7.6。 | 162 | - **详见**:`embeddings/README.md`、本指南 §7.5–§7.6。 |
| 162 | 163 | ||
| 163 | ### 4.7 reranker | 164 | ### 4.7 reranker |
docs/QUICKSTART.md
| @@ -57,7 +57,8 @@ source activate.sh | @@ -57,7 +57,8 @@ source activate.sh | ||
| 57 | | backend | 6002 | ✓ | 搜索 API(`/search/*`)+ 管理接口(`/admin/*`) | | 57 | | backend | 6002 | ✓ | 搜索 API(`/search/*`)+ 管理接口(`/admin/*`) | |
| 58 | | indexer | 6004 | ✓ | 索引 API(`/indexer/*`) | | 58 | | indexer | 6004 | ✓ | 索引 API(`/indexer/*`) | |
| 59 | | frontend | 6003 | ✓ | 调试 UI | | 59 | | frontend | 6003 | ✓ | 调试 UI | |
| 60 | -| embedding | 6005 | - | 向量服务(`/embed/text`, `/embed/image`) | | 60 | +| embedding | 6005 | - | 文本向量服务(`/embed/text`) | |
| 61 | +| embedding-image | 6008 | - | 图片向量服务(`/embed/image`) | | ||
| 61 | | translator | 6006 | - | 翻译服务(`/translate`) | | 62 | | translator | 6006 | - | 翻译服务(`/translate`) | |
| 62 | | reranker | 6007 | - | 重排服务(`/rerank`) | | 63 | | reranker | 6007 | - | 重排服务(`/rerank`) | |
| 63 | 64 | ||
| @@ -135,21 +136,22 @@ curl -X POST http://localhost:6004/indexer/build-docs \ | @@ -135,21 +136,22 @@ curl -X POST http://localhost:6004/indexer/build-docs \ | ||
| 135 | 136 | ||
| 136 | API 文档:`http://localhost:6004/docs` | 137 | API 文档:`http://localhost:6004/docs` |
| 137 | 138 | ||
| 138 | -#### Embedding 服务(6005) | 139 | +#### Embedding 服务(文本 6005 / 图片 6008) |
| 139 | 140 | ||
| 140 | ```bash | 141 | ```bash |
| 141 | # TEI(文本向量后端,默认) | 142 | # TEI(文本向量后端,默认) |
| 142 | # GPU(需 nvidia-container-toolkit) | 143 | # GPU(需 nvidia-container-toolkit) |
| 143 | TEI_DEVICE=cuda ./scripts/start_tei_service.sh | 144 | TEI_DEVICE=cuda ./scripts/start_tei_service.sh |
| 144 | 145 | ||
| 145 | -# Embedding API(会校验 TEI /health) | ||
| 146 | -./scripts/start_embedding_service.sh | 146 | +# Embedding API(text 会校验 TEI /health) |
| 147 | +./scripts/start_embedding_text_service.sh | ||
| 148 | +./scripts/start_embedding_image_service.sh | ||
| 147 | 149 | ||
| 148 | curl -X POST http://localhost:6005/embed/text \ | 150 | curl -X POST http://localhost:6005/embed/text \ |
| 149 | -H "Content-Type: application/json" \ | 151 | -H "Content-Type: application/json" \ |
| 150 | -d '["衣服", "Bohemian Maxi Dress"]' | 152 | -d '["衣服", "Bohemian Maxi Dress"]' |
| 151 | 153 | ||
| 152 | -curl -X POST http://localhost:6005/embed/image \ | 154 | +curl -X POST http://localhost:6008/embed/image \ |
| 153 | -H "Content-Type: application/json" \ | 155 | -H "Content-Type: application/json" \ |
| 154 | -d '["https://example.com/img.jpg"]' | 156 | -d '["https://example.com/img.jpg"]' |
| 155 | ``` | 157 | ``` |
| @@ -157,7 +159,7 @@ curl -X POST http://localhost:6005/embed/image \ | @@ -157,7 +159,7 @@ curl -X POST http://localhost:6005/embed/image \ | ||
| 157 | 说明: | 159 | 说明: |
| 158 | - TEI 默认镜像按 `TEI_VERSION` 组装:`cuda-<version>`(默认 `1.9`)。 | 160 | - TEI 默认镜像按 `TEI_VERSION` 组装:`cuda-<version>`(默认 `1.9`)。 |
| 159 | - `TEI_DEVICE=cuda` 时会严格校验 Docker GPU runtime;未配置会直接报错退出。 | 161 | - `TEI_DEVICE=cuda` 时会严格校验 Docker GPU runtime;未配置会直接报错退出。 |
| 160 | -- `/embed/image` 依赖 `cnclip`(`grpc://127.0.0.1:51000`),未启动时 embedding 服务会启动失败。 | 162 | +- `/embed/image` 依赖 `cnclip`(`grpc://127.0.0.1:51000`),未启动时图片 embedding 服务会启动失败。 |
| 161 | 163 | ||
| 162 | #### Translator 服务(6006) | 164 | #### Translator 服务(6006) |
| 163 | 165 | ||
| @@ -530,6 +532,7 @@ curl http://localhost:6002/health | @@ -530,6 +532,7 @@ curl http://localhost:6002/health | ||
| 530 | curl http://localhost:6002/admin/health | 532 | curl http://localhost:6002/admin/health |
| 531 | curl http://localhost:6004/health | 533 | curl http://localhost:6004/health |
| 532 | curl http://localhost:6005/health | 534 | curl http://localhost:6005/health |
| 535 | +curl http://localhost:6008/health | ||
| 533 | curl http://localhost:6006/health | 536 | curl http://localhost:6006/health |
| 534 | curl http://localhost:6007/health | 537 | curl http://localhost:6007/health |
| 535 | ``` | 538 | ``` |
docs/TEI_SERVICE说明文档.md
| @@ -5,7 +5,7 @@ | @@ -5,7 +5,7 @@ | ||
| 5 | ## 1. 作用与边界 | 5 | ## 1. 作用与边界 |
| 6 | 6 | ||
| 7 | - TEI 提供文本向量 HTTP 服务(默认 `http://127.0.0.1:8080`)。 | 7 | - TEI 提供文本向量 HTTP 服务(默认 `http://127.0.0.1:8080`)。 |
| 8 | -- 本项目中 `embedding` 服务(6005)默认把文本向量请求转发到 TEI。 | 8 | +- 本项目中文本 embedding 服务(默认 `6005`)把文本向量请求转发到 TEI。 |
| 9 | - TEI 仅负责文本向量,不负责图片向量(图片向量由 `cnclip` 提供)。 | 9 | - TEI 仅负责文本向量,不负责图片向量(图片向量由 `cnclip` 提供)。 |
| 10 | 10 | ||
| 11 | ## 2. 代码与脚本入口 | 11 | ## 2. 代码与脚本入口 |
| @@ -13,7 +13,7 @@ | @@ -13,7 +13,7 @@ | ||
| 13 | - 启动脚本:`scripts/start_tei_service.sh` | 13 | - 启动脚本:`scripts/start_tei_service.sh` |
| 14 | - 停止脚本:`scripts/stop_tei_service.sh` | 14 | - 停止脚本:`scripts/stop_tei_service.sh` |
| 15 | - 统一编排:`scripts/service_ctl.sh` | 15 | - 统一编排:`scripts/service_ctl.sh` |
| 16 | -- embedding 服务启动脚本(会校验 TEI 健康):`scripts/start_embedding_service.sh` | 16 | +- 文本 embedding 服务启动脚本(会校验 TEI 健康):`scripts/start_embedding_text_service.sh` |
| 17 | 17 | ||
| 18 | ## 3. 前置条件 | 18 | ## 3. 前置条件 |
| 19 | 19 | ||
| @@ -117,7 +117,7 @@ curl -sS http://127.0.0.1:8080/embed \ | @@ -117,7 +117,7 @@ curl -sS http://127.0.0.1:8080/embed \ | ||
| 117 | ### 6.3 与 embedding 服务联调 | 117 | ### 6.3 与 embedding 服务联调 |
| 118 | 118 | ||
| 119 | ```bash | 119 | ```bash |
| 120 | -./scripts/start_embedding_service.sh | 120 | +./scripts/start_embedding_text_service.sh |
| 121 | 121 | ||
| 122 | curl -sS -X POST "http://127.0.0.1:6005/embed/text" \ | 122 | curl -sS -X POST "http://127.0.0.1:6005/embed/text" \ |
| 123 | -H "Content-Type: application/json" \ | 123 | -H "Content-Type: application/json" \ |
| @@ -152,7 +152,7 @@ curl -sS -X POST "http://127.0.0.1:6005/embed/text" \ | @@ -152,7 +152,7 @@ curl -sS -X POST "http://127.0.0.1:6005/embed/text" \ | ||
| 152 | 启动全套(含 TEI): | 152 | 启动全套(含 TEI): |
| 153 | 153 | ||
| 154 | ```bash | 154 | ```bash |
| 155 | -TEI_DEVICE=cuda ./scripts/service_ctl.sh start tei cnclip embedding translator reranker | 155 | +TEI_DEVICE=cuda ./scripts/service_ctl.sh start tei cnclip embedding embedding-image translator reranker |
| 156 | ``` | 156 | ``` |
| 157 | 157 | ||
| 158 | 仅启动 TEI: | 158 | 仅启动 TEI: |
docs/Usage-Guide.md
| @@ -335,7 +335,8 @@ python -m http.server 6003 | @@ -335,7 +335,8 @@ python -m http.server 6003 | ||
| 335 | | Backend API | 6002 | http://localhost:6002 | | 335 | | Backend API | 6002 | http://localhost:6002 | |
| 336 | | Indexer API | 6004 | http://localhost:6004 | | 336 | | Indexer API | 6004 | http://localhost:6004 | |
| 337 | | Frontend Web | 6003 | http://localhost:6003 | | 337 | | Frontend Web | 6003 | http://localhost:6003 | |
| 338 | -| Embedding (optional) | 6005 | http://localhost:6005 | | 338 | +| Embedding Text (optional) | 6005 | http://localhost:6005 | |
| 339 | +| Embedding Image (optional) | 6008 | http://localhost:6008 | | ||
| 339 | | TEI (optional) | 8080 | http://localhost:8080 | | 340 | | TEI (optional) | 8080 | http://localhost:8080 | |
| 340 | | Translation (optional) | 6006 | http://localhost:6006 | | 341 | | Translation (optional) | 6006 | http://localhost:6006 | |
| 341 | | Reranker (optional) | 6007 | http://localhost:6007 | | 342 | | Reranker (optional) | 6007 | http://localhost:6007 | |
| @@ -380,7 +381,8 @@ INDEXER_PORT=6004 | @@ -380,7 +381,8 @@ INDEXER_PORT=6004 | ||
| 380 | 381 | ||
| 381 | # Optional service ports | 382 | # Optional service ports |
| 382 | FRONTEND_PORT=6003 | 383 | FRONTEND_PORT=6003 |
| 383 | -EMBEDDING_PORT=6005 | 384 | +EMBEDDING_TEXT_PORT=6005 |
| 385 | +EMBEDDING_IMAGE_PORT=6008 | ||
| 384 | TEI_PORT=8080 | 386 | TEI_PORT=8080 |
| 385 | CNCLIP_PORT=51000 | 387 | CNCLIP_PORT=51000 |
| 386 | TRANSLATION_PORT=6006 | 388 | TRANSLATION_PORT=6006 |
docs/向量化模块和API说明文档.md
| @@ -4,12 +4,36 @@ | @@ -4,12 +4,36 @@ | ||
| 4 | 4 | ||
| 5 | ## 服务接口 | 5 | ## 服务接口 |
| 6 | 6 | ||
| 7 | -- `POST /embed/text`:文本向量,入参 `["text1", "text2"]`,出参 `[[...], [...]]` | ||
| 8 | -- `POST /embed/image`:图片向量,入参 `["url1", "url2"]`,出参 `[[...], [...]]` | 7 | +- 文本服务:`POST http://localhost:6005/embed/text` |
| 8 | +- 图片服务:`POST http://localhost:6008/embed/image` | ||
| 9 | +- 健康检查:`GET /health` | ||
| 10 | +- 就绪检查:`GET /ready` | ||
| 11 | + | ||
| 12 | +## 当前架构 | ||
| 13 | + | ||
| 14 | +- 文本 embedding 与图片 embedding 已拆分为两个独立进程 / 端口: | ||
| 15 | + - text: `6005` | ||
| 16 | + - image: `6008` | ||
| 17 | +- 两侧有独立并发控制: | ||
| 18 | + - `TEXT_MAX_INFLIGHT` | ||
| 19 | + - `IMAGE_MAX_INFLIGHT` | ||
| 20 | +- 两侧都接入 Redis 向量缓存,value 统一使用 BF16 bytes 存储。 | ||
| 21 | + | ||
| 22 | +## 缓存 | ||
| 23 | + | ||
| 24 | +- 当前是双层缓存: | ||
| 25 | + - 调用侧 client 先查 Redis | ||
| 26 | + - 服务侧收到请求后再查 Redis | ||
| 27 | +- 当前主 key 规则: | ||
| 28 | + - 文本:`embedding:embed:norm{0|1}:{text}` | ||
| 29 | + - 图片:`embedding:image:embed:norm{0|1}:{url_or_path}` | ||
| 30 | +- full-cache-hit 时,服务会直接返回,不占用模型 lane。 | ||
| 9 | 31 | ||
| 10 | ## 配置 | 32 | ## 配置 |
| 11 | 33 | ||
| 12 | - Provider/URL:`config/config.yaml` 的 `services.embedding` | 34 | - Provider/URL:`config/config.yaml` 的 `services.embedding` |
| 35 | +- 文本服务 URL:`services.embedding.providers.http.text_base_url` | ||
| 36 | +- 图片服务 URL:`services.embedding.providers.http.image_base_url` | ||
| 13 | - 文本模型:`embeddings/config.py` 的 `TEXT_MODEL_ID`(默认 `Qwen/Qwen3-Embedding-0.6B`) | 37 | - 文本模型:`embeddings/config.py` 的 `TEXT_MODEL_ID`(默认 `Qwen/Qwen3-Embedding-0.6B`) |
| 14 | - 运行参数:`TEXT_DEVICE`、`TEXT_BATCH_SIZE`、`TEXT_NORMALIZE_EMBEDDINGS` | 38 | - 运行参数:`TEXT_DEVICE`、`TEXT_BATCH_SIZE`、`TEXT_NORMALIZE_EMBEDDINGS` |
| 15 | 39 |
docs/工作总结-微服务性能优化与架构.md
| @@ -16,12 +16,12 @@ | @@ -16,12 +16,12 @@ | ||
| 16 | | **vLLM** | 高吞吐推理框架,更适合生成式与重排混合场景 | 纯 embedding 场景通常不作为首选 | | 16 | | **vLLM** | 高吞吐推理框架,更适合生成式与重排混合场景 | 纯 embedding 场景通常不作为首选 | |
| 17 | | **TEI** | HuggingFace 官方 embedding 专用推理服务,Docker 部署 | **当前最优选型** | | 17 | | **TEI** | HuggingFace 官方 embedding 专用推理服务,Docker 部署 | **当前最优选型** | |
| 18 | 18 | ||
| 19 | -**当前方案**:以 **TEI** 为文本向量后端,模型为 `Qwen/Qwen3-Embedding-0.6B`,embedding 服务(端口 **6005**)将 `POST /embed/text` 请求转发至 TEI(默认端口 **8080**)。 | 19 | +**当前方案**:以 **TEI** 为文本向量后端,模型为 `Qwen/Qwen3-Embedding-0.6B`;文本 embedding 服务(端口 **6005**)将 `POST /embed/text` 请求转发至 TEI(默认端口 **8080**)。 |
| 20 | 20 | ||
| 21 | **具体配置与脚本**: | 21 | **具体配置与脚本**: |
| 22 | - **配置**:`config/config.yaml` → `services.embedding.backend: "tei"`,`services.embedding.backends.tei.base_url: "http://127.0.0.1:8080"`、`model_id: "Qwen/Qwen3-Embedding-0.6B"`、`timeout_sec: 20`。 | 22 | - **配置**:`config/config.yaml` → `services.embedding.backend: "tei"`,`services.embedding.backends.tei.base_url: "http://127.0.0.1:8080"`、`model_id: "Qwen/Qwen3-Embedding-0.6B"`、`timeout_sec: 20`。 |
| 23 | - **启动**:`scripts/start_tei_service.sh`(Docker 容器);环境变量:`TEI_DEVICE=cuda`(默认)、`TEI_PORT=8080`、`TEI_MODEL_ID=Qwen/Qwen3-Embedding-0.6B`、`TEI_VERSION=1.9`、`TEI_MAX_BATCH_TOKENS=4096`、`TEI_MAX_CLIENT_BATCH_SIZE=24`、`TEI_DTYPE=float16`;T4 自动选镜像 `ghcr.io/huggingface/text-embeddings-inference:turing-1.9`。 | 23 | - **启动**:`scripts/start_tei_service.sh`(Docker 容器);环境变量:`TEI_DEVICE=cuda`(默认)、`TEI_PORT=8080`、`TEI_MODEL_ID=Qwen/Qwen3-Embedding-0.6B`、`TEI_VERSION=1.9`、`TEI_MAX_BATCH_TOKENS=4096`、`TEI_MAX_CLIENT_BATCH_SIZE=24`、`TEI_DTYPE=float16`;T4 自动选镜像 `ghcr.io/huggingface/text-embeddings-inference:turing-1.9`。 |
| 24 | -- **编排**:`./scripts/service_ctl.sh start tei` 或 `start embedding`(embedding 会校验 TEI `/health` 后再就绪)。 | 24 | +- **编排**:`./scripts/service_ctl.sh start tei` 或 `start embedding`(text embedding 会校验 TEI `/health` 后再就绪)。 |
| 25 | 25 | ||
| 26 | **工程化收益**: | 26 | **工程化收益**: |
| 27 | - **独立服务**:TEI 以 Docker 容器运行,与主程序 `.venv` 解耦;embedding 使用 `.venv-embedding`,便于独立扩缩容与升级。 | 27 | - **独立服务**:TEI 以 Docker 容器运行,与主程序 `.venv` 解耦;embedding 使用 `.venv-embedding`,便于独立扩缩容与升级。 |
| @@ -71,7 +71,7 @@ instruction: "Given a shopping query, rank product titles by relevance" | @@ -71,7 +71,7 @@ instruction: "Given a shopping query, rank product titles by relevance" | ||
| 71 | 71 | ||
| 72 | **具体内容**: | 72 | **具体内容**: |
| 73 | - **端口**:clip-as-service 默认 **51000**(`CNCLIP_PORT`);文本走 TEI(8080),图片走 clip-as-service。 | 73 | - **端口**:clip-as-service 默认 **51000**(`CNCLIP_PORT`);文本走 TEI(8080),图片走 clip-as-service。 |
| 74 | -- **API**:embedding 服务(6005)统一暴露 `POST /embed/text` 与 `POST /embed/image`;图片请求由 `embeddings/server.py` 按配置调用实现 `ImageEncoderProtocol` 的后端(clip-as-service 或本地 CN-CLIP)。 | 74 | +- **API**:文本 embedding 服务默认暴露 `POST /embed/text`(6005),图片 embedding 服务默认暴露 `POST /embed/image`(6008);图片请求由 `embeddings/server.py` 按配置调用实现 `ImageEncoderProtocol` 的后端(clip-as-service 或本地 CN-CLIP)。 |
| 75 | - **环境与启停**:CN-CLIP 使用独立虚拟环境 `.venv-cnclip`;启动 `scripts/start_cnclip_service.sh`,或 `./scripts/service_ctl.sh start cnclip`;设备可通过 `CNCLIP_DEVICE=cuda`(默认)或 `cpu` 指定。 | 75 | - **环境与启停**:CN-CLIP 使用独立虚拟环境 `.venv-cnclip`;启动 `scripts/start_cnclip_service.sh`,或 `./scripts/service_ctl.sh start cnclip`;设备可通过 `CNCLIP_DEVICE=cuda`(默认)或 `cpu` 指定。 |
| 76 | - **配置**:图片后端在 `config/config.yaml` 的 `services.embedding` 下配置(若存在 image 相关 backend);clip-as-service 默认模型由 `embeddings/config.py` 的 `CLIP_AS_SERVICE_MODEL_NAME` 控制,flow 配置在 `third-party/clip-as-service/server/torch-flow-temp.yml`。 | 76 | - **配置**:图片后端在 `config/config.yaml` 的 `services.embedding` 下配置(若存在 image 相关 backend);clip-as-service 默认模型由 `embeddings/config.py` 的 `CLIP_AS_SERVICE_MODEL_NAME` 控制,flow 配置在 `third-party/clip-as-service/server/torch-flow-temp.yml`。 |
| 77 | 77 | ||
| @@ -127,8 +127,8 @@ instruction: "Given a shopping query, rank product titles by relevance" | @@ -127,8 +127,8 @@ instruction: "Given a shopping query, rank product titles by relevance" | ||
| 127 | - **脚本**:`scripts/service_ctl.sh` 统一负责各服务的生命周期与监控;依赖 `scripts/lib/load_env.sh` 与项目根目录 `.env`。 | 127 | - **脚本**:`scripts/service_ctl.sh` 统一负责各服务的生命周期与监控;依赖 `scripts/lib/load_env.sh` 与项目根目录 `.env`。 |
| 128 | - **服务与端口**: | 128 | - **服务与端口**: |
| 129 | - 核心:backend **6002**、indexer **6004**、frontend **6003**。 | 129 | - 核心:backend **6002**、indexer **6004**、frontend **6003**。 |
| 130 | - - 可选:embedding **6005**、translator **6006**、reranker **6007**、tei **8080**、cnclip **51000**。 | ||
| 131 | - - 端口可由环境变量覆盖:`API_PORT`、`INDEXER_PORT`、`FRONTEND_PORT`、`EMBEDDING_PORT`、`TRANSLATION_PORT`、`RERANKER_PORT`、`TEI_PORT`、`CNCLIP_PORT`。 | 130 | + - 可选:embedding(text) **6005**、embedding-image **6008**、translator **6006**、reranker **6007**、tei **8080**、cnclip **51000**。 |
| 131 | + - 端口可由环境变量覆盖:`API_PORT`、`INDEXER_PORT`、`FRONTEND_PORT`、`EMBEDDING_TEXT_PORT`、`EMBEDDING_IMAGE_PORT`、`TRANSLATION_PORT`、`RERANKER_PORT`、`TEI_PORT`、`CNCLIP_PORT`。 | ||
| 132 | - **命令**: | 132 | - **命令**: |
| 133 | - `./scripts/service_ctl.sh start [service...]` 或 `start all`(all = tei cnclip embedding translator reranker backend indexer frontend,按依赖顺序);`stop`、`restart` 同参数;`status` 默认列出所有服务。 | 133 | - `./scripts/service_ctl.sh start [service...]` 或 `start all`(all = tei cnclip embedding translator reranker backend indexer frontend,按依赖顺序);`stop`、`restart` 同参数;`status` 默认列出所有服务。 |
| 134 | - 启动时:backend/indexer/frontend/embedding/translator/reranker 会写 pid 到 `logs/<service>.pid`,并执行 `wait_for_health`(GET `http://127.0.0.1:<port>/health`);reranker 健康重试 90 次,其余 30 次;TEI 校验 Docker 容器存在且 `/health` 成功;cnclip 无 HTTP 健康则仅校验进程/端口。 | 134 | - 启动时:backend/indexer/frontend/embedding/translator/reranker 会写 pid 到 `logs/<service>.pid`,并执行 `wait_for_health`(GET `http://127.0.0.1:<port>/health`);reranker 健康重试 90 次,其余 30 次;TEI 校验 Docker 容器存在且 `/health` 成功;cnclip 无 HTTP 健康则仅校验进程/端口。 |
docs/搜索API对接指南.md
| @@ -157,8 +157,8 @@ curl -X POST "http://43.166.252.75:6002/search/" \ | @@ -157,8 +157,8 @@ curl -X POST "http://43.166.252.75:6002/search/" \ | ||
| 157 | 157 | ||
| 158 | | 服务 | 端口 | 接口 | 说明 | | 158 | | 服务 | 端口 | 接口 | 说明 | |
| 159 | |------|------|------|------| | 159 | |------|------|------|------| |
| 160 | -| 向量服务 | 6005 | `POST /embed/text` | 文本向量化 | | ||
| 161 | -| 向量服务 | 6005 | `POST /embed/image` | 图片向量化 | | 160 | +| 向量服务(文本) | 6005 | `POST /embed/text` | 文本向量化 | |
| 161 | +| 向量服务(图片) | 6008 | `POST /embed/image` | 图片向量化 | | ||
| 162 | | 翻译服务 | 6006 | `POST /translate` | 文本翻译(支持 qwen-mt / llm / deepl / 本地模型) | | 162 | | 翻译服务 | 6006 | `POST /translate` | 文本翻译(支持 qwen-mt / llm / deepl / 本地模型) | |
| 163 | | 重排服务 | 6007 | `POST /rerank` | 检索结果重排 | | 163 | | 重排服务 | 6007 | `POST /rerank` | 检索结果重排 | |
| 164 | | 内容理解(Indexer 内) | 6004 | `POST /indexer/enrich-content` | 根据商品标题生成 qanchors、tags 等,供 indexer 微服务组合方式使用 | | 164 | | 内容理解(Indexer 内) | 6004 | `POST /indexer/enrich-content` | 根据商品标题生成 qanchors、tags 等,供 indexer 微服务组合方式使用 | |
| @@ -1691,7 +1691,8 @@ curl -X POST "http://localhost:6004/indexer/enrich-content" \ | @@ -1691,7 +1691,8 @@ curl -X POST "http://localhost:6004/indexer/enrich-content" \ | ||
| 1691 | 1691 | ||
| 1692 | | 服务 | 默认端口 | Base URL | 说明 | | 1692 | | 服务 | 默认端口 | Base URL | 说明 | |
| 1693 | |------|----------|----------|------| | 1693 | |------|----------|----------|------| |
| 1694 | -| 向量服务 | 6005 | `http://localhost:6005` | 文本/图片向量化,用于语义搜索与以图搜图 | | 1694 | +| 向量服务(文本) | 6005 | `http://localhost:6005` | 文本向量化,用于 query/doc 语义检索 | |
| 1695 | +| 向量服务(图片) | 6008 | `http://localhost:6008` | 图片向量化,用于以图搜图 | | ||
| 1695 | | 翻译服务 | 6006 | `http://localhost:6006` | 多语言翻译(云端与本地模型统一入口) | | 1696 | | 翻译服务 | 6006 | `http://localhost:6006` | 多语言翻译(云端与本地模型统一入口) | |
| 1696 | | 重排服务 | 6007 | `http://localhost:6007` | 对检索结果进行二次排序 | | 1697 | | 重排服务 | 6007 | `http://localhost:6007` | 对检索结果进行二次排序 | |
| 1697 | 1698 | ||
| @@ -1700,13 +1701,28 @@ curl -X POST "http://localhost:6004/indexer/enrich-content" \ | @@ -1700,13 +1701,28 @@ curl -X POST "http://localhost:6004/indexer/enrich-content" \ | ||
| 1700 | 1701 | ||
| 1701 | ### 7.1 向量服务(Embedding) | 1702 | ### 7.1 向量服务(Embedding) |
| 1702 | 1703 | ||
| 1703 | -- **Base URL**: `http://localhost:6005`(可通过 `EMBEDDING_SERVICE_URL` 覆盖) | ||
| 1704 | -- **启动**: `./scripts/start_embedding_service.sh` | 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 | + - 兼容入口:`./scripts/start_embedding_service.sh text|image|all` | ||
| 1705 | - **依赖**: | 1711 | - **依赖**: |
| 1706 | - 文本向量后端默认走 TEI(`http://127.0.0.1:8080`) | 1712 | - 文本向量后端默认走 TEI(`http://127.0.0.1:8080`) |
| 1707 | - 图片向量依赖 `cnclip`(`grpc://127.0.0.1:51000`) | 1713 | - 图片向量依赖 `cnclip`(`grpc://127.0.0.1:51000`) |
| 1708 | - TEI 默认使用 GPU(`TEI_DEVICE=cuda`);当配置为 GPU 且不可用时会启动失败(不会自动降级到 CPU) | 1714 | - TEI 默认使用 GPU(`TEI_DEVICE=cuda`);当配置为 GPU 且不可用时会启动失败(不会自动降级到 CPU) |
| 1709 | - cnclip 默认使用 `cuda`;若配置为 `cuda` 但 GPU 不可用会启动失败(不会自动降级到 `cpu`) | 1715 | - cnclip 默认使用 `cuda`;若配置为 `cuda` 但 GPU 不可用会启动失败(不会自动降级到 `cpu`) |
| 1716 | + - 当前单机部署建议保持单实例,通过**文本/图片拆分 + 独立限流**隔离压力 | ||
| 1717 | + | ||
| 1718 | +补充说明: | ||
| 1719 | + | ||
| 1720 | +- 文本和图片现在已经拆成**不同进程 / 不同端口**,避免图片下载与编码波动影响文本向量化。 | ||
| 1721 | +- 服务端对 text / image 有**独立 admission control**: | ||
| 1722 | + - `TEXT_MAX_INFLIGHT` | ||
| 1723 | + - `IMAGE_MAX_INFLIGHT` | ||
| 1724 | +- 当超过处理能力时,服务会直接返回过载错误,而不是无限排队。 | ||
| 1725 | +- `GET /health` 会返回各自的 `limits`、`stats`、`cache_enabled` 等状态;`GET /ready` 用于就绪探针。 | ||
| 1710 | 1726 | ||
| 1711 | #### 7.1.1 `POST /embed/text` — 文本向量化 | 1727 | #### 7.1.1 `POST /embed/text` — 文本向量化 |
| 1712 | 1728 | ||
| @@ -1724,7 +1740,7 @@ curl -X POST "http://localhost:6004/indexer/enrich-content" \ | @@ -1724,7 +1740,7 @@ curl -X POST "http://localhost:6004/indexer/enrich-content" \ | ||
| 1724 | 1740 | ||
| 1725 | **完整 curl 示例**: | 1741 | **完整 curl 示例**: |
| 1726 | ```bash | 1742 | ```bash |
| 1727 | -curl -X POST "http://localhost:6005/embed/text" \ | 1743 | +curl -X POST "http://localhost:6005/embed/text?normalize=true" \ |
| 1728 | -H "Content-Type: application/json" \ | 1744 | -H "Content-Type: application/json" \ |
| 1729 | -d '["芭比娃娃 儿童玩具", "纯棉T恤 短袖"]' | 1745 | -d '["芭比娃娃 儿童玩具", "纯棉T恤 短袖"]' |
| 1730 | ``` | 1746 | ``` |
| @@ -1733,7 +1749,7 @@ curl -X POST "http://localhost:6005/embed/text" \ | @@ -1733,7 +1749,7 @@ curl -X POST "http://localhost:6005/embed/text" \ | ||
| 1733 | 1749 | ||
| 1734 | 将图片 URL 或路径转为向量,用于以图搜图。 | 1750 | 将图片 URL 或路径转为向量,用于以图搜图。 |
| 1735 | 1751 | ||
| 1736 | -前置条件:`cnclip` 服务已启动(默认端口 `51000`)。若未启动,`/embed/image` 会返回 500。 | 1752 | +前置条件:`cnclip` 服务已启动(默认端口 `51000`)。若未启动,图片 embedding 服务启动会失败或请求返回错误。 |
| 1737 | 1753 | ||
| 1738 | **请求体**(JSON 数组): | 1754 | **请求体**(JSON 数组): |
| 1739 | ```json | 1755 | ```json |
| @@ -1747,7 +1763,7 @@ curl -X POST "http://localhost:6005/embed/text" \ | @@ -1747,7 +1763,7 @@ curl -X POST "http://localhost:6005/embed/text" \ | ||
| 1747 | 1763 | ||
| 1748 | **完整 curl 示例**: | 1764 | **完整 curl 示例**: |
| 1749 | ```bash | 1765 | ```bash |
| 1750 | -curl -X POST "http://localhost:6005/embed/image" \ | 1766 | +curl -X POST "http://localhost:6008/embed/image?normalize=true" \ |
| 1751 | -H "Content-Type: application/json" \ | 1767 | -H "Content-Type: application/json" \ |
| 1752 | -d '["https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg"]' | 1768 | -d '["https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg"]' |
| 1753 | ``` | 1769 | ``` |
| @@ -1756,9 +1772,32 @@ curl -X POST "http://localhost:6005/embed/image" \ | @@ -1756,9 +1772,32 @@ curl -X POST "http://localhost:6005/embed/image" \ | ||
| 1756 | 1772 | ||
| 1757 | ```bash | 1773 | ```bash |
| 1758 | curl "http://localhost:6005/health" | 1774 | curl "http://localhost:6005/health" |
| 1775 | +curl "http://localhost:6008/health" | ||
| 1776 | +``` | ||
| 1777 | + | ||
| 1778 | +返回中会包含: | ||
| 1779 | + | ||
| 1780 | +- `service_kind`:`text` / `image` / `all` | ||
| 1781 | +- `cache_enabled`:text/image Redis 缓存是否可用 | ||
| 1782 | +- `limits`:当前 inflight limit、active、rejected_total 等 | ||
| 1783 | +- `stats`:request_total、cache_hits、cache_misses、avg_latency_ms 等 | ||
| 1784 | + | ||
| 1785 | +#### 7.1.4 `GET /ready` — 就绪检查 | ||
| 1786 | + | ||
| 1787 | +```bash | ||
| 1788 | +curl "http://localhost:6005/ready" | ||
| 1789 | +curl "http://localhost:6008/ready" | ||
| 1759 | ``` | 1790 | ``` |
| 1760 | 1791 | ||
| 1761 | -#### 7.1.4 TEI 统一调优建议(主服务) | 1792 | +#### 7.1.5 缓存与限流说明 |
| 1793 | + | ||
| 1794 | +- 文本与图片都会先查 Redis 向量缓存。 | ||
| 1795 | +- Redis 中 value 仍是 **BF16 bytes**,读取后恢复成 `float32` 返回。 | ||
| 1796 | +- cache key 已区分 `normalize=true/false`,避免不同归一化策略命中同一条缓存。 | ||
| 1797 | +- 当服务端发现请求是 **full-cache-hit** 时,会直接返回,不占用模型并发槽位。 | ||
| 1798 | +- 当服务端发现超过 `TEXT_MAX_INFLIGHT` / `IMAGE_MAX_INFLIGHT` 时,会直接拒绝,而不是无限排队。 | ||
| 1799 | + | ||
| 1800 | +#### 7.1.6 TEI 统一调优建议(主服务) | ||
| 1762 | 1801 | ||
| 1763 | 使用单套主服务即可同时兼顾: | 1802 | 使用单套主服务即可同时兼顾: |
| 1764 | - 在线 query 向量化(低延迟,常见 `batch=1~4`) | 1803 | - 在线 query 向量化(低延迟,常见 `batch=1~4`) |
| @@ -1773,7 +1812,8 @@ curl "http://localhost:6005/health" | @@ -1773,7 +1812,8 @@ curl "http://localhost:6005/health" | ||
| 1773 | 1812 | ||
| 1774 | 默认端口: | 1813 | 默认端口: |
| 1775 | - TEI: `http://127.0.0.1:8080` | 1814 | - TEI: `http://127.0.0.1:8080` |
| 1776 | -- 向量服务(`/embed/text`): `http://127.0.0.1:6005` | 1815 | +- 文本向量服务(`/embed/text`): `http://127.0.0.1:6005` |
| 1816 | +- 图片向量服务(`/embed/image`): `http://127.0.0.1:6008` | ||
| 1777 | 1817 | ||
| 1778 | 当前主 TEI 启动默认值(已按 T4/短文本场景调优): | 1818 | 当前主 TEI 启动默认值(已按 T4/短文本场景调优): |
| 1779 | - `TEI_MAX_BATCH_TOKENS=4096` | 1819 | - `TEI_MAX_BATCH_TOKENS=4096` |
docs/缓存与Redis使用说明.md
| @@ -19,7 +19,7 @@ | @@ -19,7 +19,7 @@ | ||
| 19 | 19 | ||
| 20 | | 模块 / 场景 | Key 模板 | Value 内容示例 | 过期策略 | 备注 | | 20 | | 模块 / 场景 | Key 模板 | Value 内容示例 | 过期策略 | 备注 | |
| 21 | |------------|----------|----------------|----------|------| | 21 | |------------|----------|----------------|----------|------| |
| 22 | -| 向量缓存(text/image embedding) | `{EMBEDDING_CACHE_PREFIX}:{query_or_url}` / `{EMBEDDING_CACHE_PREFIX}:image:{url_or_path}` | **BF16 bytes**(每维 2 字节大端存储),读取后恢复为 `np.float32` | TTL=`REDIS_CONFIG["cache_expire_days"]` 天;访问时滑动过期 | 见 `embeddings/text_encoder.py`(文本)与 `embeddings/image_encoder.py`(图片);前缀由 `REDIS_CONFIG["embedding_cache_prefix"]` 控制 | | 22 | +| 向量缓存(text/image embedding) | 文本:`{EMBEDDING_CACHE_PREFIX}:embed:norm{0|1}:{text}`;图片:`{EMBEDDING_CACHE_PREFIX}:image:embed:norm{0|1}:{url_or_path}` | **BF16 bytes**(每维 2 字节大端存储),读取后恢复为 `np.float32` | TTL=`REDIS_CONFIG["cache_expire_days"]` 天;访问时滑动过期 | 见 `embeddings/text_encoder.py`、`embeddings/image_encoder.py`、`embeddings/server.py`;前缀由 `REDIS_CONFIG["embedding_cache_prefix"]` 控制 | |
| 23 | | 翻译结果缓存(translator service) | `trans:{model}:{target_lang}:{source_text[:4]}{sha256(source_text)}` | 机翻后的单条字符串 | TTL=`services.translation.cache.ttl_seconds` 秒;可配置滑动过期 | 见 `translation/service.py` + `config/config.yaml` | | 23 | | 翻译结果缓存(translator service) | `trans:{model}:{target_lang}:{source_text[:4]}{sha256(source_text)}` | 机翻后的单条字符串 | TTL=`services.translation.cache.ttl_seconds` 秒;可配置滑动过期 | 见 `translation/service.py` + `config/config.yaml` | |
| 24 | | 商品内容理解缓存(anchors / 语义属性 / tags) | `{ANCHOR_CACHE_PREFIX}:{tenant_or_global}:{target_lang}:{md5(title)}` | `json.dumps(dict)`,包含 id/title/category/tags/anchor_text 等 | TTL=`ANCHOR_CACHE_EXPIRE_DAYS` 天 | 见 `indexer/product_enrich.py` | | 24 | | 商品内容理解缓存(anchors / 语义属性 / tags) | `{ANCHOR_CACHE_PREFIX}:{tenant_or_global}:{target_lang}:{md5(title)}` | `json.dumps(dict)`,包含 id/title/category/tags/anchor_text 等 | TTL=`ANCHOR_CACHE_EXPIRE_DAYS` 天 | 见 `indexer/product_enrich.py` | |
| 25 | 25 | ||
| @@ -27,23 +27,49 @@ | @@ -27,23 +27,49 @@ | ||
| 27 | 27 | ||
| 28 | --- | 28 | --- |
| 29 | 29 | ||
| 30 | -## 2. 文本向量缓存(embeddings/text_encoder.py) | 30 | +## 2. 向量缓存(embeddings/text_encoder.py / embeddings/image_encoder.py / embeddings/server.py) |
| 31 | 31 | ||
| 32 | -- **代码位置**:`embeddings/text_encoder.py` 中 `TextEmbeddingEncoder` | ||
| 33 | -- **用途**:缓存调用向量服务(6005)的文本向量结果,避免重复计算。 | 32 | +- **代码位置**: |
| 33 | + - 调用侧:`embeddings/text_encoder.py`、`embeddings/image_encoder.py` | ||
| 34 | + - 服务侧:`embeddings/server.py` | ||
| 35 | + - 公共 Redis/BF16 编解码:`embeddings/redis_embedding_cache.py`、`embeddings/bf16.py` | ||
| 36 | +- **用途**:缓存文本/图片向量结果,避免重复推理、重复下载图片、重复占用模型并发槽位。 | ||
| 37 | + | ||
| 38 | +### 2.0 当前缓存链路总览 | ||
| 39 | + | ||
| 40 | +- 现在是**双层缓存**: | ||
| 41 | + - **调用侧缓存**:`TextEmbeddingEncoder` / `CLIPImageEncoder` 在发 HTTP 请求前先查 Redis; | ||
| 42 | + - **服务侧缓存**:`/embed/text` / `/embed/image` 在进入后端推理前再查 Redis。 | ||
| 43 | +- 这次改动里,**BF16 存储格式本身没有变化**: | ||
| 44 | + - 写入:`float32 -> BF16 -> bytes -> Redis` | ||
| 45 | + - 读取:`Redis bytes -> BF16 -> float32(np.float32)` | ||
| 46 | +- 这次新增的核心变化是: | ||
| 47 | + - **缓存 key 加入 normalize 维度**,避免 `normalize=true` 与 `normalize=false` 命中同一条缓存; | ||
| 48 | + - **服务端也启用同一套 Redis 向量缓存**,而不是只有 client 侧缓存; | ||
| 49 | + - **拆分 text / image 服务** 后,图片压力不会再拖慢文本服务; | ||
| 50 | + - **服务侧 full-cache-hit 可直接返回**,不会再进入模型限流槽位。 | ||
| 34 | 51 | ||
| 35 | ### 2.1 Key 设计 | 52 | ### 2.1 Key 设计 |
| 36 | 53 | ||
| 37 | -- 函数:`_get_cache_key(query: str, normalize_embeddings: bool) -> str` | 54 | +- 统一 helper:`embeddings/cache_keys.py` |
| 55 | +- 文本主 key:`build_text_cache_key(text, normalize=...)` | ||
| 56 | +- 图片主 key:`build_image_cache_key(url, normalize=...)` | ||
| 38 | - 模板: | 57 | - 模板: |
| 39 | 58 | ||
| 40 | ```text | 59 | ```text |
| 41 | -{EMBEDDING_CACHE_PREFIX}:{query} | 60 | +文本: {EMBEDDING_CACHE_PREFIX}:embed:norm{0|1}:{text} |
| 61 | +图片: {EMBEDDING_CACHE_PREFIX}:image:embed:norm{0|1}:{url_or_path} | ||
| 42 | ``` | 62 | ``` |
| 43 | 63 | ||
| 44 | - 字段说明: | 64 | - 字段说明: |
| 45 | - `EMBEDDING_CACHE_PREFIX`:来自 `REDIS_CONFIG["embedding_cache_prefix"]`,默认值为 `"embedding"`,可通过环境变量 `REDIS_EMBEDDING_CACHE_PREFIX` 覆盖; | 65 | - `EMBEDDING_CACHE_PREFIX`:来自 `REDIS_CONFIG["embedding_cache_prefix"]`,默认值为 `"embedding"`,可通过环境变量 `REDIS_EMBEDDING_CACHE_PREFIX` 覆盖; |
| 46 | - - `query`:原始文本(未做哈希),注意长度特别长的 query 会直接出现在 key 中。 | 66 | + - `norm1` / `norm0`:分别表示 `normalize=true` / `normalize=false`; |
| 67 | + - `text` / `url_or_path`:当前仍直接使用规范化后的原始输入,不做哈希。 | ||
| 68 | + | ||
| 69 | +补充说明: | ||
| 70 | + | ||
| 71 | +- 本次把 raw key 格式统一成 `embed:norm{0|1}:...`,比以 `norm:` 开头更清晰,也更接近历史命名习惯。 | ||
| 72 | +- 当前实现**不再兼容历史 key 协议**,只保留这一套主 key 规则,以降低运行时复杂度和歧义。 | ||
| 47 | 73 | ||
| 48 | ### 2.2 Value 与类型 | 74 | ### 2.2 Value 与类型 |
| 49 | 75 | ||
| @@ -68,6 +94,23 @@ | @@ -68,6 +94,23 @@ | ||
| 68 | - 直接丢弃该缓存(并尝试 `delete` key); | 94 | - 直接丢弃该缓存(并尝试 `delete` key); |
| 69 | - 回退为重新调用向量服务。 | 95 | - 回退为重新调用向量服务。 |
| 70 | 96 | ||
| 97 | +### 2.5 服务拆分与缓存/限流关系 | ||
| 98 | + | ||
| 99 | +- 当前默认部署形态: | ||
| 100 | + - 文本 embedding 服务:`6005` | ||
| 101 | + - 图片 embedding 服务:`6008` | ||
| 102 | +- 调用方配置: | ||
| 103 | + - `EMBEDDING_TEXT_SERVICE_URL` | ||
| 104 | + - `EMBEDDING_IMAGE_SERVICE_URL` | ||
| 105 | +- 服务端配置: | ||
| 106 | + - `TEXT_MAX_INFLIGHT` | ||
| 107 | + - `IMAGE_MAX_INFLIGHT` | ||
| 108 | + - `EMBEDDING_SERVICE_KIND=all|text|image` | ||
| 109 | +- 关键行为: | ||
| 110 | + - **full-cache-hit** 请求在服务端会直接返回,不占用 `TEXT_MAX_INFLIGHT` / `IMAGE_MAX_INFLIGHT`; | ||
| 111 | + - **cache miss** 才会进入对应模型 lane; | ||
| 112 | + - 因此高频重复图片请求不会再因为严格的图片限流而大量阻塞。 | ||
| 113 | + | ||
| 71 | --- | 114 | --- |
| 72 | 115 | ||
| 73 | ## 3. 翻译结果缓存(translation/service.py) | 116 | ## 3. 翻译结果缓存(translation/service.py) |
embeddings/README.md
| @@ -16,7 +16,7 @@ | @@ -16,7 +16,7 @@ | ||
| 16 | - **统一配置**:`config.py` | 16 | - **统一配置**:`config.py` |
| 17 | - **接口契约**:`protocols.ImageEncoderProtocol`(图片编码统一为 `encode_image_urls(urls, batch_size, normalize_embeddings)`,本地 CN-CLIP 与 clip-as-service 均实现该接口) | 17 | - **接口契约**:`protocols.ImageEncoderProtocol`(图片编码统一为 `encode_image_urls(urls, batch_size, normalize_embeddings)`,本地 CN-CLIP 与 clip-as-service 均实现该接口) |
| 18 | 18 | ||
| 19 | -说明:历史上的云端 embedding 试验实现(DashScope)已从主仓库移除,当前仅维护 6005 这条统一向量服务链路。 | 19 | +说明:历史上的云端 embedding 试验实现(DashScope)已从主仓库移除。当前默认部署为**文本服务 6005** 与**图片服务 6008** 两条链路,也兼容 `all` 模式单进程启动。 |
| 20 | 20 | ||
| 21 | ### 文本向量后端(默认) | 21 | ### 文本向量后端(默认) |
| 22 | 22 | ||
| @@ -27,6 +27,15 @@ | @@ -27,6 +27,15 @@ | ||
| 27 | 27 | ||
| 28 | ### 服务接口 | 28 | ### 服务接口 |
| 29 | 29 | ||
| 30 | +- 文本服务(默认 `6005`) | ||
| 31 | + - `POST /embed/text` | ||
| 32 | + - `GET /health` | ||
| 33 | + - `GET /ready` | ||
| 34 | +- 图片服务(默认 `6008`) | ||
| 35 | + - `POST /embed/image` | ||
| 36 | + - `GET /health` | ||
| 37 | + - `GET /ready` | ||
| 38 | + | ||
| 30 | - `POST /embed/text` | 39 | - `POST /embed/text` |
| 31 | - 入参:`["文本1", "文本2", ...]` | 40 | - 入参:`["文本1", "文本2", ...]` |
| 32 | - 可选 query 参数:`normalize=true|false`(不传则使用服务端默认) | 41 | - 可选 query 参数:`normalize=true|false`(不传则使用服务端默认) |
| @@ -37,6 +46,28 @@ | @@ -37,6 +46,28 @@ | ||
| 37 | - 可选 query 参数:`normalize=true|false`(不传则使用服务端默认) | 46 | - 可选 query 参数:`normalize=true|false`(不传则使用服务端默认) |
| 38 | - 出参:`[[...], [...], ...]`(与输入按 index 对齐,失败直接报错) | 47 | - 出参:`[[...], [...], ...]`(与输入按 index 对齐,失败直接报错) |
| 39 | 48 | ||
| 49 | +### Redis 向量缓存 | ||
| 50 | + | ||
| 51 | +- Value 格式没有变化,仍然是 **BF16 bytes**: | ||
| 52 | + - 写入:`float32 -> BF16 -> bytes` | ||
| 53 | + - 读取:`bytes -> BF16 -> float32` | ||
| 54 | +- 现在是**双层缓存**: | ||
| 55 | + - client 侧:`text_encoder.py` / `image_encoder.py` | ||
| 56 | + - service 侧:`server.py` | ||
| 57 | +- 当前主 key 格式: | ||
| 58 | + - 文本:`embedding:embed:norm{0|1}:{text}` | ||
| 59 | + - 图片:`embedding:image:embed:norm{0|1}:{url_or_path}` | ||
| 60 | +- 当前实现不再兼容历史 key 规则,只保留这一套格式,减少代码路径和缓存歧义。 | ||
| 61 | + | ||
| 62 | +### 压力隔离与拒绝策略 | ||
| 63 | + | ||
| 64 | +- 文本与图片各自有独立 admission control: | ||
| 65 | + - `TEXT_MAX_INFLIGHT` | ||
| 66 | + - `IMAGE_MAX_INFLIGHT` | ||
| 67 | +- 图片服务可以配置得比文本更严格。 | ||
| 68 | +- 请求若是 full-cache-hit,会在服务端直接返回,不占用模型并发槽位。 | ||
| 69 | +- 超过处理能力时直接拒绝,比无限排队更稳定。 | ||
| 70 | + | ||
| 40 | ### 图片向量:clip-as-service(推荐) | 71 | ### 图片向量:clip-as-service(推荐) |
| 41 | 72 | ||
| 42 | 默认使用 `third-party/clip-as-service` 的 Jina CLIP 服务生成图片向量。 | 73 | 默认使用 `third-party/clip-as-service` 的 Jina CLIP 服务生成图片向量。 |
| @@ -63,7 +94,7 @@ | @@ -63,7 +94,7 @@ | ||
| 63 | 94 | ||
| 64 | ### 启动服务 | 95 | ### 启动服务 |
| 65 | 96 | ||
| 66 | -使用仓库脚本启动(默认端口 6005): | 97 | +使用仓库脚本启动: |
| 67 | 98 | ||
| 68 | ```bash | 99 | ```bash |
| 69 | # GPU(需 nvidia-container-toolkit) | 100 | # GPU(需 nvidia-container-toolkit) |
| @@ -72,16 +103,27 @@ TEI_DEVICE=cuda ./scripts/start_tei_service.sh | @@ -72,16 +103,27 @@ TEI_DEVICE=cuda ./scripts/start_tei_service.sh | ||
| 72 | # CPU | 103 | # CPU |
| 73 | TEI_DEVICE=cpu ./scripts/start_tei_service.sh | 104 | TEI_DEVICE=cpu ./scripts/start_tei_service.sh |
| 74 | 105 | ||
| 75 | -./scripts/start_embedding_service.sh | 106 | +./scripts/start_embedding_text_service.sh |
| 107 | +./scripts/start_embedding_image_service.sh | ||
| 108 | + | ||
| 109 | +# 或兼容入口 | ||
| 110 | +./scripts/start_embedding_service.sh text | ||
| 111 | +./scripts/start_embedding_service.sh image | ||
| 76 | ``` | 112 | ``` |
| 77 | 113 | ||
| 78 | ### 修改配置 | 114 | ### 修改配置 |
| 79 | 115 | ||
| 80 | 编辑 `embeddings/config.py`: | 116 | 编辑 `embeddings/config.py`: |
| 81 | 117 | ||
| 82 | -- `PORT`: 服务端口(默认 6005) | 118 | +- `PORT`: combined 模式默认端口(默认 6005) |
| 83 | - `TEXT_MODEL_ID`, `TEXT_DEVICE`, `TEXT_BATCH_SIZE`, `TEXT_NORMALIZE_EMBEDDINGS` | 119 | - `TEXT_MODEL_ID`, `TEXT_DEVICE`, `TEXT_BATCH_SIZE`, `TEXT_NORMALIZE_EMBEDDINGS` |
| 84 | - `IMAGE_NORMALIZE_EMBEDDINGS`(默认 true) | 120 | - `IMAGE_NORMALIZE_EMBEDDINGS`(默认 true) |
| 85 | - `USE_CLIP_AS_SERVICE`, `CLIP_AS_SERVICE_SERVER`, `CLIP_AS_SERVICE_MODEL_NAME`:图片向量(clip-as-service) | 121 | - `USE_CLIP_AS_SERVICE`, `CLIP_AS_SERVICE_SERVER`, `CLIP_AS_SERVICE_MODEL_NAME`:图片向量(clip-as-service) |
| 86 | - `IMAGE_MODEL_NAME`, `IMAGE_DEVICE`:本地 CN-CLIP(当 `USE_CLIP_AS_SERVICE=false` 时) | 122 | - `IMAGE_MODEL_NAME`, `IMAGE_DEVICE`:本地 CN-CLIP(当 `USE_CLIP_AS_SERVICE=false` 时) |
| 87 | - TEI 相关:`TEI_DEVICE`、`TEI_VERSION`、`TEI_MAX_BATCH_TOKENS`、`TEI_MAX_CLIENT_BATCH_SIZE`、`TEI_HEALTH_TIMEOUT_SEC` | 123 | - TEI 相关:`TEI_DEVICE`、`TEI_VERSION`、`TEI_MAX_BATCH_TOKENS`、`TEI_MAX_CLIENT_BATCH_SIZE`、`TEI_HEALTH_TIMEOUT_SEC` |
| 124 | +- 分流/限流相关: | ||
| 125 | + - `EMBEDDING_SERVICE_KIND=all|text|image` | ||
| 126 | + - `EMBEDDING_TEXT_PORT` | ||
| 127 | + - `EMBEDDING_IMAGE_PORT` | ||
| 128 | + - `TEXT_MAX_INFLIGHT` | ||
| 129 | + - `IMAGE_MAX_INFLIGHT` |
embeddings/cache_keys.py
| 1 | -"""Shared cache key helpers for embedding inputs.""" | 1 | +"""Shared cache key helpers for embedding inputs. |
| 2 | + | ||
| 3 | +Current canonical raw-key format: | ||
| 4 | +- text: ``embed:norm1:<text>`` / ``embed:norm0:<text>`` | ||
| 5 | +- image: ``embed:norm1:<url>`` / ``embed:norm0:<url>`` | ||
| 6 | + | ||
| 7 | +`RedisEmbeddingCache` adds the configured key prefix and optional namespace on top. | ||
| 8 | +""" | ||
| 2 | 9 | ||
| 3 | from __future__ import annotations | 10 | from __future__ import annotations |
| 4 | 11 | ||
| 5 | 12 | ||
| 6 | def build_text_cache_key(text: str, *, normalize: bool) -> str: | 13 | def build_text_cache_key(text: str, *, normalize: bool) -> str: |
| 7 | normalized_text = str(text or "").strip() | 14 | normalized_text = str(text or "").strip() |
| 8 | - return f"norm:{1 if normalize else 0}:text:{normalized_text}" | 15 | + return f"embed:norm{1 if normalize else 0}:{normalized_text}" |
| 9 | 16 | ||
| 10 | 17 | ||
| 11 | def build_image_cache_key(url: str, *, normalize: bool) -> str: | 18 | def build_image_cache_key(url: str, *, normalize: bool) -> str: |
| 12 | normalized_url = str(url or "").strip() | 19 | normalized_url = str(url or "").strip() |
| 13 | - return f"norm:{1 if normalize else 0}:image:{normalized_url}" | 20 | + return f"embed:norm{1 if normalize else 0}:{normalized_url}" |
embeddings/image_encoder.py
| @@ -127,10 +127,10 @@ class CLIPImageEncoder: | @@ -127,10 +127,10 @@ class CLIPImageEncoder: | ||
| 127 | cached = self.cache.get(cache_key) | 127 | cached = self.cache.get(cache_key) |
| 128 | if cached is not None: | 128 | if cached is not None: |
| 129 | results.append(cached) | 129 | results.append(cached) |
| 130 | - else: | ||
| 131 | - results.append(np.array([], dtype=np.float32)) # placeholder | ||
| 132 | - pending_positions.append(pos) | ||
| 133 | - pending_urls.append(url) | 130 | + continue |
| 131 | + results.append(np.array([], dtype=np.float32)) # placeholder | ||
| 132 | + pending_positions.append(pos) | ||
| 133 | + pending_urls.append(url) | ||
| 134 | 134 | ||
| 135 | for i in range(0, len(pending_urls), batch_size): | 135 | for i in range(0, len(pending_urls), batch_size): |
| 136 | batch_urls = pending_urls[i : i + batch_size] | 136 | batch_urls = pending_urls[i : i + batch_size] |
embeddings/text_encoder.py
| @@ -164,12 +164,14 @@ class TextEmbeddingEncoder: | @@ -164,12 +164,14 @@ class TextEmbeddingEncoder: | ||
| 164 | normalize_embeddings: bool, | 164 | normalize_embeddings: bool, |
| 165 | ) -> Optional[np.ndarray]: | 165 | ) -> Optional[np.ndarray]: |
| 166 | """Get embedding from cache if exists (with sliding expiration).""" | 166 | """Get embedding from cache if exists (with sliding expiration).""" |
| 167 | - embedding = self.cache.get(build_text_cache_key(query, normalize=normalize_embeddings)) | 167 | + cache_key = build_text_cache_key(query, normalize=normalize_embeddings) |
| 168 | + embedding = self.cache.get(cache_key) | ||
| 168 | if embedding is not None: | 169 | if embedding is not None: |
| 169 | logger.debug( | 170 | logger.debug( |
| 170 | - "Cache hit for text embedding | normalize=%s query=%s", | 171 | + "Cache hit for text embedding | normalize=%s query=%s key=%s", |
| 171 | normalize_embeddings, | 172 | normalize_embeddings, |
| 172 | query, | 173 | query, |
| 174 | + cache_key, | ||
| 173 | ) | 175 | ) |
| 174 | return embedding | 176 | return embedding |
| 175 | 177 |
scripts/trace_indexer_calls.sh
| @@ -14,7 +14,8 @@ echo "索引服务调用方排查" | @@ -14,7 +14,8 @@ echo "索引服务调用方排查" | ||
| 14 | echo "==========================================" | 14 | echo "==========================================" |
| 15 | 15 | ||
| 16 | INDEXER_PORT="${INDEXER_PORT:-6004}" | 16 | INDEXER_PORT="${INDEXER_PORT:-6004}" |
| 17 | -EMBEDDING_PORT="${EMBEDDING_PORT:-6005}" | 17 | +EMBEDDING_TEXT_PORT="${EMBEDDING_TEXT_PORT:-${EMBEDDING_PORT:-6005}}" |
| 18 | +EMBEDDING_IMAGE_PORT="${EMBEDDING_IMAGE_PORT:-6008}" | ||
| 18 | 19 | ||
| 19 | echo "" | 20 | echo "" |
| 20 | echo "1. 监听端口 6004 的进程(Indexer 服务)" | 21 | echo "1. 监听端口 6004 的进程(Indexer 服务)" |
| @@ -37,10 +38,10 @@ else | @@ -37,10 +38,10 @@ else | ||
| 37 | fi | 38 | fi |
| 38 | 39 | ||
| 39 | echo "" | 40 | echo "" |
| 40 | -echo "3. 连接到 6005 的客户端(Indexer 会调用 Embedding 服务)" | 41 | +echo "3. 连接到 Embedding 服务的客户端" |
| 41 | echo "------------------------------------------" | 42 | echo "------------------------------------------" |
| 42 | if command -v ss >/dev/null 2>&1; then | 43 | if command -v ss >/dev/null 2>&1; then |
| 43 | - ss -tnp 2>/dev/null | grep ":${EMBEDDING_PORT}" || echo " (当前无活跃连接)" | 44 | + ss -tnp 2>/dev/null | grep -E ":${EMBEDDING_TEXT_PORT}|:${EMBEDDING_IMAGE_PORT}" || echo " (当前无活跃连接)" |
| 44 | fi | 45 | fi |
| 45 | 46 | ||
| 46 | echo "" | 47 | echo "" |
| @@ -63,12 +64,13 @@ echo " 全量: curl -X POST http://localhost:${INDEXER_PORT}/indexer/reindex | @@ -63,12 +64,13 @@ echo " 全量: curl -X POST http://localhost:${INDEXER_PORT}/indexer/reindex | ||
| 63 | echo " 增量: curl -X POST http://localhost:${INDEXER_PORT}/indexer/index -d '{\"tenant_id\":\"170\",\"spu_ids\":[\"123\"]}'" | 64 | echo " 增量: curl -X POST http://localhost:${INDEXER_PORT}/indexer/index -d '{\"tenant_id\":\"170\",\"spu_ids\":[\"123\"]}'" |
| 64 | echo "" | 65 | echo "" |
| 65 | echo " - Indexer 内部会调用:" | 66 | echo " - Indexer 内部会调用:" |
| 66 | -echo " - Embedding 服务 (${EMBEDDING_PORT}): POST /embed/text" | 67 | +echo " - Text Embedding 服务 (${EMBEDDING_TEXT_PORT}): POST /embed/text" |
| 68 | +echo " - Image Embedding 服务 (${EMBEDDING_IMAGE_PORT}): POST /embed/image" | ||
| 67 | echo " - Qwen API: dashscope.aliyuncs.com (翻译、LLM 分析)" | 69 | echo " - Qwen API: dashscope.aliyuncs.com (翻译、LLM 分析)" |
| 68 | echo " - MySQL: 商品数据" | 70 | echo " - MySQL: 商品数据" |
| 69 | echo " - Elasticsearch: 写入索引" | 71 | echo " - Elasticsearch: 写入索引" |
| 70 | echo "" | 72 | echo "" |
| 71 | echo "6. 实时监控连接(按 Ctrl+C 停止)" | 73 | echo "6. 实时监控连接(按 Ctrl+C 停止)" |
| 72 | echo "------------------------------------------" | 74 | echo "------------------------------------------" |
| 73 | -echo " 运行: watch -n 2 'ss -tnp | grep -E \":${INDEXER_PORT}|:${EMBEDDING_PORT}\"'" | 75 | +echo " 运行: watch -n 2 'ss -tnp | grep -E \":${INDEXER_PORT}|:${EMBEDDING_TEXT_PORT}|:${EMBEDDING_IMAGE_PORT}\"'" |
| 74 | echo "" | 76 | echo "" |
utils/__init__.py
| @@ -2,7 +2,7 @@ | @@ -2,7 +2,7 @@ | ||
| 2 | 2 | ||
| 3 | from .db_connector import create_db_connection, get_connection_from_config, test_connection | 3 | from .db_connector import create_db_connection, get_connection_from_config, test_connection |
| 4 | from .es_client import ESClient, get_es_client_from_env | 4 | from .es_client import ESClient, get_es_client_from_env |
| 5 | -from .cache import EmbeddingCache, DictCache | 5 | +from .cache import DictCache |
| 6 | 6 | ||
| 7 | __all__ = [ | 7 | __all__ = [ |
| 8 | 'create_db_connection', | 8 | 'create_db_connection', |
| @@ -10,6 +10,5 @@ __all__ = [ | @@ -10,6 +10,5 @@ __all__ = [ | ||
| 10 | 'test_connection', | 10 | 'test_connection', |
| 11 | 'ESClient', | 11 | 'ESClient', |
| 12 | 'get_es_client_from_env', | 12 | 'get_es_client_from_env', |
| 13 | - 'EmbeddingCache', | ||
| 14 | 'DictCache', | 13 | 'DictCache', |
| 15 | ] | 14 | ] |
utils/cache.py
| 1 | -""" | ||
| 2 | -Cache utility for storing embedding results. | ||
| 3 | -""" | 1 | +"""Small file-backed cache helpers.""" |
| 4 | 2 | ||
| 5 | import json | 3 | import json |
| 6 | -import hashlib | ||
| 7 | -import pickle | ||
| 8 | from pathlib import Path | 4 | from pathlib import Path |
| 9 | from typing import Any, Optional | 5 | from typing import Any, Optional |
| 10 | -import numpy as np | ||
| 11 | - | ||
| 12 | - | ||
| 13 | -class EmbeddingCache: | ||
| 14 | - """ | ||
| 15 | - Simple file-based cache for embeddings. | ||
| 16 | - | ||
| 17 | - Uses MD5 hash of input text/URL as cache key. | ||
| 18 | - """ | ||
| 19 | - | ||
| 20 | - def __init__(self, cache_dir: str = ".cache/embeddings"): | ||
| 21 | - self.cache_dir = Path(cache_dir) | ||
| 22 | - self.cache_dir.mkdir(parents=True, exist_ok=True) | ||
| 23 | - | ||
| 24 | - def _get_cache_key(self, input_str: str) -> str: | ||
| 25 | - """Generate cache key from input string.""" | ||
| 26 | - return hashlib.md5(input_str.encode('utf-8')).hexdigest() | ||
| 27 | - | ||
| 28 | - def get(self, input_str: str) -> Optional[np.ndarray]: | ||
| 29 | - """ | ||
| 30 | - Get cached embedding. | ||
| 31 | - | ||
| 32 | - Args: | ||
| 33 | - input_str: Input text or URL | ||
| 34 | - | ||
| 35 | - Returns: | ||
| 36 | - Cached embedding or None if not found | ||
| 37 | - """ | ||
| 38 | - cache_key = self._get_cache_key(input_str) | ||
| 39 | - cache_file = self.cache_dir / f"{cache_key}.npy" | ||
| 40 | - | ||
| 41 | - if cache_file.exists(): | ||
| 42 | - try: | ||
| 43 | - return np.load(cache_file) | ||
| 44 | - except Exception as e: | ||
| 45 | - print(f"Failed to load cache for {input_str}: {e}") | ||
| 46 | - return None | ||
| 47 | - return None | ||
| 48 | - | ||
| 49 | - def set(self, input_str: str, embedding: np.ndarray) -> bool: | ||
| 50 | - """ | ||
| 51 | - Store embedding in cache. | ||
| 52 | - | ||
| 53 | - Args: | ||
| 54 | - input_str: Input text or URL | ||
| 55 | - embedding: Embedding vector | ||
| 56 | - | ||
| 57 | - Returns: | ||
| 58 | - True if successful | ||
| 59 | - """ | ||
| 60 | - cache_key = self._get_cache_key(input_str) | ||
| 61 | - cache_file = self.cache_dir / f"{cache_key}.npy" | ||
| 62 | - | ||
| 63 | - try: | ||
| 64 | - np.save(cache_file, embedding) | ||
| 65 | - return True | ||
| 66 | - except Exception as e: | ||
| 67 | - print(f"Failed to cache embedding for {input_str}: {e}") | ||
| 68 | - return False | ||
| 69 | - | ||
| 70 | - def exists(self, input_str: str) -> bool: | ||
| 71 | - """Check if embedding is cached.""" | ||
| 72 | - cache_key = self._get_cache_key(input_str) | ||
| 73 | - cache_file = self.cache_dir / f"{cache_key}.npy" | ||
| 74 | - return cache_file.exists() | ||
| 75 | - | ||
| 76 | - def clear(self) -> int: | ||
| 77 | - """ | ||
| 78 | - Clear all cached embeddings. | ||
| 79 | - | ||
| 80 | - Returns: | ||
| 81 | - Number of files deleted | ||
| 82 | - """ | ||
| 83 | - count = 0 | ||
| 84 | - for cache_file in self.cache_dir.glob("*.npy"): | ||
| 85 | - cache_file.unlink() | ||
| 86 | - count += 1 | ||
| 87 | - return count | ||
| 88 | - | ||
| 89 | - def size(self) -> int: | ||
| 90 | - """Get number of cached embeddings.""" | ||
| 91 | - return len(list(self.cache_dir.glob("*.npy"))) | ||
| 92 | 6 | ||
| 93 | 7 | ||
| 94 | class DictCache: | 8 | class DictCache: |