"""火山方舟 Doubao-embedding-vision 文本向量化客户端(scope-link 用)。 契约见《数据接口与来源/embedding接口.md》: - Endpoint: POST .../api/v3/embeddings/multimodal(vision 版必须走 multimodal) - model = 预置推理接入点 ep-id - 输入 input=[{"type":"text","text":...}],输出 data.embedding = 2048 维 - 单条调用(一次请求一个向量),批量靠并发多次请求。 密钥/ep 全部从 .env 读,不硬编码。 """ from __future__ import annotations import time from dataclasses import dataclass from pathlib import Path import httpx from core.config import env_value, load_env_file _DEFAULT_URL = "https://ark.cn-beijing.volces.com/api/v3/embeddings/multimodal" @dataclass class ArkEmbedConfig: api_key: str endpoint: str # ep-id,作为 model 字段传入 url: str = _DEFAULT_URL dim: int = 2048 timeout: int = 30 @classmethod def from_env(cls, env_file: str | Path = ".env") -> "ArkEmbedConfig": fe = load_env_file(env_file) return cls( api_key=env_value("ARK_API_KEY", fe, required=True), endpoint=env_value("ARK_EMBEDDING_EP", fe, required=True), url=env_value("ARK_EMBEDDING_URL", fe, _DEFAULT_URL), dim=int(env_value("ARK_EMBEDDING_DIM", fe, "2048")), ) def embed_text(text: str, cfg: ArkEmbedConfig, retries: int = 4) -> list[float]: """文本 → 2048 维向量。失败指数退避重试,最终仍失败则抛出。""" payload = {"model": cfg.endpoint, "input": [{"type": "text", "text": text}]} headers = { "Authorization": f"Bearer {cfg.api_key}", "Content-Type": "application/json", } last_err: Exception | None = None for attempt in range(retries): try: resp = httpx.post(cfg.url, json=payload, headers=headers, timeout=cfg.timeout) resp.raise_for_status() return resp.json()["data"]["embedding"] except Exception as err: # noqa: BLE001 — 网络/限流统一退避重试 last_err = err time.sleep(1.0 * (attempt + 1)) raise RuntimeError(f"ark embed failed for {text!r}: {last_err}")