Commit af03fdef9ebc4cc70c1812737592501e8bde9655
1 parent
5bac9649
embedding模块代码整理
Showing
17 changed files
with
54 additions
and
99 deletions
Show diff stats
config/__init__.py
| ... | ... | @@ -26,7 +26,6 @@ from .services_config import ( |
| 26 | 26 | get_embedding_backend_config, |
| 27 | 27 | get_rerank_backend_config, |
| 28 | 28 | get_translation_base_url, |
| 29 | - get_embedding_base_url, | |
| 30 | 29 | get_embedding_text_base_url, |
| 31 | 30 | get_embedding_image_base_url, |
| 32 | 31 | get_rerank_service_url, |
| ... | ... | @@ -54,7 +53,6 @@ __all__ = [ |
| 54 | 53 | 'get_embedding_backend_config', |
| 55 | 54 | 'get_rerank_backend_config', |
| 56 | 55 | 'get_translation_base_url', |
| 57 | - 'get_embedding_base_url', | |
| 58 | 56 | 'get_embedding_text_base_url', |
| 59 | 57 | 'get_embedding_image_base_url', |
| 60 | 58 | 'get_rerank_service_url', | ... | ... |
config/config.yaml
| ... | ... | @@ -195,10 +195,8 @@ services: |
| 195 | 195 | use_cache: true |
| 196 | 196 | embedding: |
| 197 | 197 | provider: "http" # http |
| 198 | - base_url: "http://127.0.0.1:6005" | |
| 199 | 198 | providers: |
| 200 | 199 | http: |
| 201 | - base_url: "http://127.0.0.1:6005" | |
| 202 | 200 | text_base_url: "http://127.0.0.1:6005" |
| 203 | 201 | image_base_url: "http://127.0.0.1:6008" |
| 204 | 202 | # 服务内文本后端(embedding 进程启动时读取) | ... | ... |
config/env_config.py
| ... | ... | @@ -59,11 +59,12 @@ API_PORT = int(os.getenv('API_PORT', 6002)) |
| 59 | 59 | INDEXER_HOST = os.getenv('INDEXER_HOST', '0.0.0.0') |
| 60 | 60 | INDEXER_PORT = int(os.getenv('INDEXER_PORT', 6004)) |
| 61 | 61 | # Optional dependent services |
| 62 | +# EMBEDDING_HOST / EMBEDDING_PORT are only used by the optional combined embedding mode. | |
| 62 | 63 | EMBEDDING_HOST = os.getenv('EMBEDDING_HOST', '127.0.0.1') |
| 63 | 64 | EMBEDDING_PORT = int(os.getenv('EMBEDDING_PORT', 6005)) |
| 64 | -EMBEDDING_TEXT_HOST = os.getenv('EMBEDDING_TEXT_HOST', EMBEDDING_HOST) | |
| 65 | -EMBEDDING_TEXT_PORT = int(os.getenv('EMBEDDING_TEXT_PORT', EMBEDDING_PORT)) | |
| 66 | -EMBEDDING_IMAGE_HOST = os.getenv('EMBEDDING_IMAGE_HOST', EMBEDDING_HOST) | |
| 65 | +EMBEDDING_TEXT_HOST = os.getenv('EMBEDDING_TEXT_HOST', '127.0.0.1') | |
| 66 | +EMBEDDING_TEXT_PORT = int(os.getenv('EMBEDDING_TEXT_PORT', 6005)) | |
| 67 | +EMBEDDING_IMAGE_HOST = os.getenv('EMBEDDING_IMAGE_HOST', '127.0.0.1') | |
| 67 | 68 | EMBEDDING_IMAGE_PORT = int(os.getenv('EMBEDDING_IMAGE_PORT', 6008)) |
| 68 | 69 | TRANSLATION_HOST = os.getenv('TRANSLATION_HOST', '127.0.0.1') |
| 69 | 70 | TRANSLATION_PORT = int(os.getenv('TRANSLATION_PORT', 6006)) |
| ... | ... | @@ -77,7 +78,6 @@ if not API_BASE_URL: |
| 77 | 78 | INDEXER_BASE_URL = os.getenv('INDEXER_BASE_URL') or ( |
| 78 | 79 | f'http://localhost:{INDEXER_PORT}' if INDEXER_HOST == '0.0.0.0' else f'http://{INDEXER_HOST}:{INDEXER_PORT}' |
| 79 | 80 | ) |
| 80 | -EMBEDDING_SERVICE_URL = os.getenv('EMBEDDING_SERVICE_URL') or f'http://{EMBEDDING_HOST}:{EMBEDDING_PORT}' | |
| 81 | 81 | EMBEDDING_TEXT_SERVICE_URL = os.getenv('EMBEDDING_TEXT_SERVICE_URL') or ( |
| 82 | 82 | f'http://{EMBEDDING_TEXT_HOST}:{EMBEDDING_TEXT_PORT}' |
| 83 | 83 | ) | ... | ... |
config/services_config.py
| ... | ... | @@ -78,18 +78,20 @@ def _resolve_embedding() -> ServiceConfig: |
| 78 | 78 | if provider != "http": |
| 79 | 79 | raise ValueError(f"Unsupported embedding provider: {provider}") |
| 80 | 80 | |
| 81 | - env_url = os.getenv("EMBEDDING_SERVICE_URL") | |
| 82 | 81 | env_text_url = os.getenv("EMBEDDING_TEXT_SERVICE_URL") |
| 83 | 82 | env_image_url = os.getenv("EMBEDDING_IMAGE_SERVICE_URL") |
| 84 | - if (env_url or env_text_url or env_image_url) and provider == "http": | |
| 83 | + if provider == "http": | |
| 85 | 84 | providers = dict(providers) |
| 86 | - providers["http"] = dict(providers.get("http", {})) | |
| 87 | - if env_url: | |
| 88 | - providers["http"]["base_url"] = env_url.rstrip("/") | |
| 85 | + http_cfg = dict(providers.get("http", {})) | |
| 89 | 86 | if env_text_url: |
| 90 | - providers["http"]["text_base_url"] = env_text_url.rstrip("/") | |
| 87 | + http_cfg["text_base_url"] = env_text_url.rstrip("/") | |
| 91 | 88 | if env_image_url: |
| 92 | - providers["http"]["image_base_url"] = env_image_url.rstrip("/") | |
| 89 | + http_cfg["image_base_url"] = env_image_url.rstrip("/") | |
| 90 | + if not http_cfg.get("text_base_url"): | |
| 91 | + raise ValueError("services.embedding.providers.http.text_base_url is required") | |
| 92 | + if not http_cfg.get("image_base_url"): | |
| 93 | + raise ValueError("services.embedding.providers.http.image_base_url is required") | |
| 94 | + providers["http"] = http_cfg | |
| 93 | 95 | |
| 94 | 96 | return ServiceConfig(provider=provider, providers=providers) |
| 95 | 97 | |
| ... | ... | @@ -171,27 +173,9 @@ def get_translation_cache_config() -> Dict[str, Any]: |
| 171 | 173 | return get_translation_cache(get_translation_config()) |
| 172 | 174 | |
| 173 | 175 | |
| 174 | -def get_embedding_base_url() -> str: | |
| 175 | - provider_cfg = get_embedding_config().providers.get("http", {}) | |
| 176 | - base = ( | |
| 177 | - os.getenv("EMBEDDING_SERVICE_URL") | |
| 178 | - or provider_cfg.get("base_url") | |
| 179 | - or provider_cfg.get("text_base_url") | |
| 180 | - or provider_cfg.get("image_base_url") | |
| 181 | - ) | |
| 182 | - if not base: | |
| 183 | - raise ValueError("Embedding HTTP base_url is not configured") | |
| 184 | - return str(base).rstrip("/") | |
| 185 | - | |
| 186 | - | |
| 187 | 176 | def get_embedding_text_base_url() -> str: |
| 188 | 177 | provider_cfg = get_embedding_config().providers.get("http", {}) |
| 189 | - base = ( | |
| 190 | - os.getenv("EMBEDDING_TEXT_SERVICE_URL") | |
| 191 | - or provider_cfg.get("text_base_url") | |
| 192 | - or os.getenv("EMBEDDING_SERVICE_URL") | |
| 193 | - or provider_cfg.get("base_url") | |
| 194 | - ) | |
| 178 | + base = os.getenv("EMBEDDING_TEXT_SERVICE_URL") or provider_cfg.get("text_base_url") | |
| 195 | 179 | if not base: |
| 196 | 180 | raise ValueError("Embedding text HTTP base_url is not configured") |
| 197 | 181 | return str(base).rstrip("/") |
| ... | ... | @@ -199,12 +183,7 @@ def get_embedding_text_base_url() -> str: |
| 199 | 183 | |
| 200 | 184 | def get_embedding_image_base_url() -> str: |
| 201 | 185 | provider_cfg = get_embedding_config().providers.get("http", {}) |
| 202 | - base = ( | |
| 203 | - os.getenv("EMBEDDING_IMAGE_SERVICE_URL") | |
| 204 | - or provider_cfg.get("image_base_url") | |
| 205 | - or os.getenv("EMBEDDING_SERVICE_URL") | |
| 206 | - or provider_cfg.get("base_url") | |
| 207 | - ) | |
| 186 | + base = os.getenv("EMBEDDING_IMAGE_SERVICE_URL") or provider_cfg.get("image_base_url") | |
| 208 | 187 | if not base: |
| 209 | 188 | raise ValueError("Embedding image HTTP base_url is not configured") |
| 210 | 189 | return str(base).rstrip("/") | ... | ... |
docs/DEVELOPER_GUIDE.md
| ... | ... | @@ -293,7 +293,7 @@ services: |
| 293 | 293 | |
| 294 | 294 | ### 6.3 环境变量(常用) |
| 295 | 295 | |
| 296 | -- 能力 URL:`EMBEDDING_SERVICE_URL`、`RERANKER_SERVICE_URL` | |
| 296 | +- 能力 URL:`EMBEDDING_TEXT_SERVICE_URL`、`EMBEDDING_IMAGE_SERVICE_URL`、`RERANKER_SERVICE_URL` | |
| 297 | 297 | - 能力选择:`EMBEDDING_PROVIDER`、`EMBEDDING_BACKEND`、`RERANK_PROVIDER`、`RERANK_BACKEND` |
| 298 | 298 | - 翻译服务行为:统一查看 `config/config.yaml -> services.translation` |
| 299 | 299 | - 环境与索引:`ES_HOST`、`ES_INDEX_NAMESPACE`、`RUNTIME_ENV`、DB 与 Redis 等 |
| ... | ... | @@ -346,14 +346,14 @@ services: |
| 346 | 346 | |
| 347 | 347 | **重排后端协议(服务内)**:所有在 reranker 服务内加载的后端须实现 `score_with_meta(query, docs, normalize=True) -> (scores: List[float], meta: dict)`。返回的 `scores[i]` 与 `docs[i]` 一一对应;meta 至少含 `input_docs`、`usable_docs`、`elapsed_ms` 等。对外 HTTP 契约固定:`POST /rerank` 请求体 `{ "query": str, "docs": [str] }`,响应体 `{ "scores": [float], "meta": object }`;`GET /health` 返回 `status`、`model`、`backend` 等。 |
| 348 | 348 | |
| 349 | -**向量化后端协议(服务内)**:文本后端需支持 `encode(sentences: Union[str, List[str]], batch_size, device) -> ndarray | List[ndarray]`,单条与批量输入统一通过一个接口处理;图片后端实现 `embeddings/protocols.ImageEncoderProtocol`:`encode_image_urls(urls, batch_size) -> List[Optional[ndarray]]`,与 urls 等长。 | |
| 349 | +**向量化后端协议(服务内)**:文本后端需支持 `encode(sentences: Union[str, List[str]], batch_size, device) -> ndarray | List[ndarray]`,单条与批量输入统一通过一个接口处理;图片后端实现 `embeddings/protocols.ImageEncoderProtocol`:`encode_image_urls(urls, batch_size) -> List[ndarray]`,与 urls 等长,异常直接抛出。 | |
| 350 | 350 | |
| 351 | 351 | **配置速查**: |
| 352 | 352 | |
| 353 | 353 | | 层次 | 配置键 | 重排 | 向量化 | |
| 354 | 354 | |------|--------|------|--------| |
| 355 | 355 | | 调用方 | `services.<capability>.provider` | http | http | |
| 356 | -| 调用方 | `services.<capability>.providers.http.base_url` | 6007 | 6005 | | |
| 356 | +| 调用方 | `services.<capability>.providers.http.*_base_url` | 6007 | text=6005 / image=6008 | | |
| 357 | 357 | | 服务内 | `services.<capability>.backend` | qwen3_vllm / qwen3_transformers / bge / dashscope_rerank | tei / local_st | |
| 358 | 358 | | 服务内 | `services.<capability>.backends.<name>` | 模型名、batch、vLLM 参数 | 模型名、device 等 | |
| 359 | 359 | |
| ... | ... | @@ -370,7 +370,7 @@ services: |
| 370 | 370 | |
| 371 | 371 | - **单一路径**:Provider 和 backend 必须由 `config/config.yaml` 的 `services` 块显式指定;未知配置应直接报错。 |
| 372 | 372 | - **无兼容回退**:不保留“旧配置自动推导/兜底默认值”机制,避免静默行为偏差。 |
| 373 | -- **环境变量覆盖**:允许环境变量覆盖(如 `RERANKER_SERVICE_URL`、`RERANK_BACKEND`、`RERANK_DASHSCOPE_API_KEY_CN`/`RERANK_DASHSCOPE_API_KEY_US`、`RERANK_DASHSCOPE_ENDPOINT`、`EMBEDDING_SERVICE_URL`、`EMBEDDING_BACKEND`、`TEI_BASE_URL`),但覆盖后仍需满足合法性校验。 | |
| 373 | +- **环境变量覆盖**:允许环境变量覆盖(如 `RERANKER_SERVICE_URL`、`RERANK_BACKEND`、`RERANK_DASHSCOPE_API_KEY_CN`/`RERANK_DASHSCOPE_API_KEY_US`、`RERANK_DASHSCOPE_ENDPOINT`、`EMBEDDING_TEXT_SERVICE_URL`、`EMBEDDING_IMAGE_SERVICE_URL`、`EMBEDDING_BACKEND`、`TEI_BASE_URL`),但覆盖后仍需满足合法性校验。 | |
| 374 | 374 | |
| 375 | 375 | --- |
| 376 | 376 | ... | ... |
docs/QUICKSTART.md
| ... | ... | @@ -422,7 +422,7 @@ services: |
| 422 | 422 | provider: "http" |
| 423 | 423 | backend: "tei" |
| 424 | 424 | providers: |
| 425 | - http: { base_url: "http://127.0.0.1:6005" } | |
| 425 | + http: { text_base_url: "http://127.0.0.1:6005", image_base_url: "http://127.0.0.1:6008" } | |
| 426 | 426 | backends: |
| 427 | 427 | tei: { base_url: "http://127.0.0.1:8080", timeout_sec: 60, model_id: "Qwen/Qwen3-Embedding-0.6B" } |
| 428 | 428 | rerank: |
| ... | ... | @@ -434,7 +434,8 @@ services: |
| 434 | 434 | |
| 435 | 435 | 环境变量覆盖(优先级更高): |
| 436 | 436 | |
| 437 | -- `EMBEDDING_SERVICE_URL` | |
| 437 | +- `EMBEDDING_TEXT_SERVICE_URL` | |
| 438 | +- `EMBEDDING_IMAGE_SERVICE_URL` | |
| 438 | 439 | - `EMBEDDING_BACKEND` |
| 439 | 440 | - `TEI_BASE_URL` |
| 440 | 441 | - `RERANKER_SERVICE_URL` | ... | ... |
docs/搜索API对接指南.md
| ... | ... | @@ -1707,7 +1707,6 @@ curl -X POST "http://localhost:6004/indexer/enrich-content" \ |
| 1707 | 1707 | - **启动**: |
| 1708 | 1708 | - 文本:`./scripts/start_embedding_text_service.sh` |
| 1709 | 1709 | - 图片:`./scripts/start_embedding_image_service.sh` |
| 1710 | - - 兼容入口:`./scripts/start_embedding_service.sh text|image|all` | |
| 1711 | 1710 | - **依赖**: |
| 1712 | 1711 | - 文本向量后端默认走 TEI(`http://127.0.0.1:8080`) |
| 1713 | 1712 | - 图片向量依赖 `cnclip`(`grpc://127.0.0.1:51000`) | ... | ... |
embeddings/README.md
| ... | ... | @@ -16,7 +16,7 @@ |
| 16 | 16 | - **统一配置**:`config.py` |
| 17 | 17 | - **接口契约**:`protocols.ImageEncoderProtocol`(图片编码统一为 `encode_image_urls(urls, batch_size, normalize_embeddings)`,本地 CN-CLIP 与 clip-as-service 均实现该接口) |
| 18 | 18 | |
| 19 | -说明:历史上的云端 embedding 试验实现(DashScope)已从主仓库移除。当前默认部署为**文本服务 6005** 与**图片服务 6008** 两条链路,也兼容 `all` 模式单进程启动。 | |
| 19 | +说明:历史上的云端 embedding 试验实现(DashScope)已从主仓库移除。当前默认部署为**文本服务 6005** 与**图片服务 6008** 两条独立链路;`all` 模式仅作为单进程调试入口。 | |
| 20 | 20 | |
| 21 | 21 | ### 文本向量后端(默认) |
| 22 | 22 | |
| ... | ... | @@ -29,22 +29,16 @@ |
| 29 | 29 | |
| 30 | 30 | - 文本服务(默认 `6005`) |
| 31 | 31 | - `POST /embed/text` |
| 32 | - - `GET /health` | |
| 33 | - - `GET /ready` | |
| 32 | + - 请求体:`["文本1", "文本2", ...]` | |
| 33 | + - 可选 query 参数:`normalize=true|false` | |
| 34 | + - 返回:`[[...], [...], ...]` | |
| 35 | + - 健康接口:`GET /health`、`GET /ready` | |
| 34 | 36 | - 图片服务(默认 `6008`) |
| 35 | 37 | - `POST /embed/image` |
| 36 | - - `GET /health` | |
| 37 | - - `GET /ready` | |
| 38 | - | |
| 39 | -- `POST /embed/text` | |
| 40 | - - 入参:`["文本1", "文本2", ...]` | |
| 41 | - - 可选 query 参数:`normalize=true|false`(不传则使用服务端默认) | |
| 42 | - - 出参:`[[...], [...], ...]`(与输入按 index 对齐,失败直接报错) | |
| 43 | - | |
| 44 | -- `POST /embed/image` | |
| 45 | - - 入参:`["url或本地路径1", ...]` | |
| 46 | - - 可选 query 参数:`normalize=true|false`(不传则使用服务端默认) | |
| 47 | - - 出参:`[[...], [...], ...]`(与输入按 index 对齐,失败直接报错) | |
| 38 | + - 请求体:`["url或本地路径1", ...]` | |
| 39 | + - 可选 query 参数:`normalize=true|false` | |
| 40 | + - 返回:`[[...], [...], ...]` | |
| 41 | + - 健康接口:`GET /health`、`GET /ready` | |
| 48 | 42 | |
| 49 | 43 | ### Redis 向量缓存 |
| 50 | 44 | |
| ... | ... | @@ -105,17 +99,13 @@ TEI_DEVICE=cpu ./scripts/start_tei_service.sh |
| 105 | 99 | |
| 106 | 100 | ./scripts/start_embedding_text_service.sh |
| 107 | 101 | ./scripts/start_embedding_image_service.sh |
| 108 | - | |
| 109 | -# 或兼容入口 | |
| 110 | -./scripts/start_embedding_service.sh text | |
| 111 | -./scripts/start_embedding_service.sh image | |
| 112 | 102 | ``` |
| 113 | 103 | |
| 114 | 104 | ### 修改配置 |
| 115 | 105 | |
| 116 | 106 | 编辑 `embeddings/config.py`: |
| 117 | 107 | |
| 118 | -- `PORT`: combined 模式默认端口(默认 6005) | |
| 108 | +- `PORT`: `all` 模式单进程端口(默认 6005) | |
| 119 | 109 | - `TEXT_MODEL_ID`, `TEXT_DEVICE`, `TEXT_BATCH_SIZE`, `TEXT_NORMALIZE_EMBEDDINGS` |
| 120 | 110 | - `IMAGE_NORMALIZE_EMBEDDINGS`(默认 true) |
| 121 | 111 | - `USE_CLIP_AS_SERVICE`, `CLIP_AS_SERVICE_SERVER`, `CLIP_AS_SERVICE_MODEL_NAME`:图片向量(clip-as-service) | ... | ... |
embeddings/clip_as_service_encoder.py
| ... | ... | @@ -121,7 +121,9 @@ class ClipAsServiceImageEncoder: |
| 121 | 121 | out.append(vec) |
| 122 | 122 | return out |
| 123 | 123 | |
| 124 | - def encode_image_from_url(self, url: str, normalize_embeddings: bool = True) -> Optional[np.ndarray]: | |
| 125 | - """Encode a single image URL. Returns 1024-dim vector or None.""" | |
| 124 | + def encode_image_from_url(self, url: str, normalize_embeddings: bool = True) -> np.ndarray: | |
| 125 | + """Encode a single image URL and return one 1024-dim vector.""" | |
| 126 | 126 | results = self.encode_image_urls([url], batch_size=1, normalize_embeddings=normalize_embeddings) |
| 127 | - return results[0] if results else None | |
| 127 | + if not results: | |
| 128 | + raise RuntimeError("clip-as-service returned empty result for single image URL") | |
| 129 | + return results[0] | ... | ... |
embeddings/clip_model.py
| ... | ... | @@ -86,7 +86,7 @@ class ClipImageModel(object): |
| 86 | 86 | image_features /= image_features.norm(dim=-1, keepdim=True) |
| 87 | 87 | return image_features.cpu().numpy().astype("float32")[0] |
| 88 | 88 | |
| 89 | - def encode_image_from_url(self, url: str, normalize_embeddings: bool = True) -> Optional[np.ndarray]: | |
| 89 | + def encode_image_from_url(self, url: str, normalize_embeddings: bool = True) -> np.ndarray: | |
| 90 | 90 | image_data = self.download_image(url) |
| 91 | 91 | image = self.validate_image(image_data) |
| 92 | 92 | image = self.preprocess_image(image) |
| ... | ... | @@ -97,7 +97,7 @@ class ClipImageModel(object): |
| 97 | 97 | urls: List[str], |
| 98 | 98 | batch_size: Optional[int] = None, |
| 99 | 99 | normalize_embeddings: bool = True, |
| 100 | - ) -> List[Optional[np.ndarray]]: | |
| 100 | + ) -> List[np.ndarray]: | |
| 101 | 101 | """ |
| 102 | 102 | Encode a list of image URLs to vectors. Same interface as ClipAsServiceImageEncoder. |
| 103 | 103 | |
| ... | ... | @@ -106,7 +106,7 @@ class ClipImageModel(object): |
| 106 | 106 | batch_size: batch size for internal batching (default 8). |
| 107 | 107 | |
| 108 | 108 | Returns: |
| 109 | - List of vectors (or None for failed items), same length as urls. | |
| 109 | + List of vectors, same length as urls. | |
| 110 | 110 | """ |
| 111 | 111 | return self.encode_batch( |
| 112 | 112 | urls, |
| ... | ... | @@ -119,8 +119,8 @@ class ClipImageModel(object): |
| 119 | 119 | images: List[Union[str, Image.Image]], |
| 120 | 120 | batch_size: int = 8, |
| 121 | 121 | normalize_embeddings: bool = True, |
| 122 | - ) -> List[Optional[np.ndarray]]: | |
| 123 | - results: List[Optional[np.ndarray]] = [] | |
| 122 | + ) -> List[np.ndarray]: | |
| 123 | + results: List[np.ndarray] = [] | |
| 124 | 124 | for i in range(0, len(images), batch_size): |
| 125 | 125 | batch = images[i : i + batch_size] |
| 126 | 126 | for img in batch: |
| ... | ... | @@ -129,6 +129,5 @@ class ClipImageModel(object): |
| 129 | 129 | elif isinstance(img, Image.Image): |
| 130 | 130 | results.append(self.encode_image(img, normalize_embeddings=normalize_embeddings)) |
| 131 | 131 | else: |
| 132 | - results.append(None) | |
| 132 | + raise ValueError(f"Unsupported image input type: {type(img)!r}") | |
| 133 | 133 | return results |
| 134 | - | ... | ... |
embeddings/config.py
| ... | ... | @@ -18,7 +18,7 @@ class EmbeddingConfig(object): |
| 18 | 18 | |
| 19 | 19 | # Text backend defaults |
| 20 | 20 | TEXT_MODEL_ID = os.getenv("TEXT_MODEL_ID", "Qwen/Qwen3-Embedding-0.6B") |
| 21 | - # Backward-compatible alias for old naming in docs/scripts. | |
| 21 | + # Keep TEXT_MODEL_DIR as an alias so code can refer to one canonical text model value. | |
| 22 | 22 | TEXT_MODEL_DIR = TEXT_MODEL_ID |
| 23 | 23 | TEXT_DEVICE = os.getenv("TEXT_DEVICE", "cuda") # "cuda" or "cpu" |
| 24 | 24 | TEXT_BATCH_SIZE = int(os.getenv("TEXT_BATCH_SIZE", "32")) | ... | ... |
embeddings/image_encoder.py
| 1 | 1 | """Image embedding client for the local embedding HTTP service.""" |
| 2 | 2 | |
| 3 | -import os | |
| 4 | 3 | import logging |
| 5 | 4 | from typing import Any, List, Optional, Union |
| 6 | 5 | |
| ... | ... | @@ -24,12 +23,7 @@ class CLIPImageEncoder: |
| 24 | 23 | """ |
| 25 | 24 | |
| 26 | 25 | def __init__(self, service_url: Optional[str] = None): |
| 27 | - resolved_url = ( | |
| 28 | - service_url | |
| 29 | - or os.getenv("EMBEDDING_IMAGE_SERVICE_URL") | |
| 30 | - or os.getenv("EMBEDDING_SERVICE_URL") | |
| 31 | - or get_embedding_image_base_url() | |
| 32 | - ) | |
| 26 | + resolved_url = service_url or get_embedding_image_base_url() | |
| 33 | 27 | self.service_url = str(resolved_url).rstrip("/") |
| 34 | 28 | self.endpoint = f"{self.service_url}/embed/image" |
| 35 | 29 | # Reuse embedding cache prefix, but separate namespace for images to avoid collisions. | ... | ... |
embeddings/protocols.py
| ... | ... | @@ -18,11 +18,12 @@ class ImageEncoderProtocol(Protocol): |
| 18 | 18 | urls: List[str], |
| 19 | 19 | batch_size: Optional[int] = None, |
| 20 | 20 | normalize_embeddings: bool = True, |
| 21 | - ) -> List[Optional[np.ndarray]]: | |
| 21 | + ) -> List[np.ndarray]: | |
| 22 | 22 | """ |
| 23 | 23 | Encode a list of image URLs to vectors. |
| 24 | 24 | |
| 25 | 25 | Returns: |
| 26 | - List of vectors (or None for failed items), same length as urls. | |
| 26 | + List of vectors, same length as urls. Invalid inputs should raise instead | |
| 27 | + of returning partial None placeholders. | |
| 27 | 28 | """ |
| 28 | 29 | ... | ... | ... |
embeddings/text_encoder.py
| 1 | 1 | """Text embedding client for the local embedding HTTP service.""" |
| 2 | 2 | |
| 3 | 3 | import logging |
| 4 | -import os | |
| 5 | 4 | from datetime import timedelta |
| 6 | 5 | from typing import Any, List, Optional, Union |
| 7 | 6 | |
| ... | ... | @@ -24,12 +23,7 @@ class TextEmbeddingEncoder: |
| 24 | 23 | """ |
| 25 | 24 | |
| 26 | 25 | def __init__(self, service_url: Optional[str] = None): |
| 27 | - resolved_url = ( | |
| 28 | - service_url | |
| 29 | - or os.getenv("EMBEDDING_TEXT_SERVICE_URL") | |
| 30 | - or os.getenv("EMBEDDING_SERVICE_URL") | |
| 31 | - or get_embedding_text_base_url() | |
| 32 | - ) | |
| 26 | + resolved_url = service_url or get_embedding_text_base_url() | |
| 33 | 27 | self.service_url = str(resolved_url).rstrip("/") |
| 34 | 28 | self.endpoint = f"{self.service_url}/embed/text" |
| 35 | 29 | self.expire_time = timedelta(days=REDIS_CONFIG.get("cache_expire_days", 180)) | ... | ... |
scripts/service_ctl.sh
| ... | ... | @@ -30,7 +30,7 @@ get_port() { |
| 30 | 30 | backend) echo "${API_PORT:-6002}" ;; |
| 31 | 31 | indexer) echo "${INDEXER_PORT:-6004}" ;; |
| 32 | 32 | frontend) echo "${FRONTEND_PORT:-6003}" ;; |
| 33 | - embedding) echo "${EMBEDDING_TEXT_PORT:-${EMBEDDING_PORT:-6005}}" ;; | |
| 33 | + embedding) echo "${EMBEDDING_TEXT_PORT:-6005}" ;; | |
| 34 | 34 | embedding-image) echo "${EMBEDDING_IMAGE_PORT:-6008}" ;; |
| 35 | 35 | translator) echo "${TRANSLATION_PORT:-6006}" ;; |
| 36 | 36 | reranker) echo "${RERANKER_PORT:-6007}" ;; |
| ... | ... | @@ -593,7 +593,7 @@ start_one() { |
| 593 | 593 | return 1 |
| 594 | 594 | fi |
| 595 | 595 | ;; |
| 596 | - backend|indexer|frontend|embedding|translator|reranker) | |
| 596 | + backend|indexer|frontend|embedding|embedding-image|translator|reranker) | |
| 597 | 597 | echo "[start] ${service}" |
| 598 | 598 | nohup "${cmd}" >> "${lf}" 2>&1 & |
| 599 | 599 | local pid=$! | ... | ... |
scripts/start_embedding_service.sh
| ... | ... | @@ -60,7 +60,7 @@ fi |
| 60 | 60 | |
| 61 | 61 | EMBEDDING_SERVICE_HOST="${EMBEDDING_HOST:-${DEFAULT_EMBEDDING_SERVICE_HOST}}" |
| 62 | 62 | if [[ "${SERVICE_KIND}" == "text" ]]; then |
| 63 | - EMBEDDING_SERVICE_PORT="${EMBEDDING_TEXT_PORT:-${EMBEDDING_PORT:-${DEFAULT_EMBEDDING_SERVICE_PORT}}}" | |
| 63 | + EMBEDDING_SERVICE_PORT="${EMBEDDING_TEXT_PORT:-6005}" | |
| 64 | 64 | elif [[ "${SERVICE_KIND}" == "image" ]]; then |
| 65 | 65 | EMBEDDING_SERVICE_PORT="${EMBEDDING_IMAGE_PORT:-6008}" |
| 66 | 66 | else |
| ... | ... | @@ -147,7 +147,7 @@ if [[ "${SERVICE_KIND}" == "text" ]]; then |
| 147 | 147 | elif [[ "${SERVICE_KIND}" == "image" ]]; then |
| 148 | 148 | echo " - Clients can set EMBEDDING_IMAGE_SERVICE_URL=http://localhost:${EMBEDDING_SERVICE_PORT}" |
| 149 | 149 | else |
| 150 | - echo " - Clients can set EMBEDDING_SERVICE_URL=http://localhost:${EMBEDDING_SERVICE_PORT}" | |
| 150 | + echo " - All mode serves both /embed/text and /embed/image on port ${EMBEDDING_SERVICE_PORT}" | |
| 151 | 151 | fi |
| 152 | 152 | echo |
| 153 | 153 | ... | ... |
scripts/trace_indexer_calls.sh
| ... | ... | @@ -14,7 +14,7 @@ echo "索引服务调用方排查" |
| 14 | 14 | echo "==========================================" |
| 15 | 15 | |
| 16 | 16 | INDEXER_PORT="${INDEXER_PORT:-6004}" |
| 17 | -EMBEDDING_TEXT_PORT="${EMBEDDING_TEXT_PORT:-${EMBEDDING_PORT:-6005}}" | |
| 17 | +EMBEDDING_TEXT_PORT="${EMBEDDING_TEXT_PORT:-6005}" | |
| 18 | 18 | EMBEDDING_IMAGE_PORT="${EMBEDDING_IMAGE_PORT:-6008}" |
| 19 | 19 | |
| 20 | 20 | echo "" | ... | ... |