"""BGE Embedding Service Client""" import requests import logging from typing import List logger = logging.getLogger(__name__) # BGE-small-zh-v1.5: 512 tokens, roughly ~1500 Chinese chars # Conservative limit: 2000 chars (most text under this is safe) MAX_TEXT_CHARS = 2000 class EmbeddingService: """Client for BGE-small-zh-v1.5 embedding service.""" def __init__(self, base_url: str = "http://127.0.0.1:8080", timeout: int = 30): self.base_url = base_url.rstrip("/") self.timeout = timeout def embed(self, text: str) -> List[float]: """Get embedding for a single text. Text is truncated to MAX_TEXT_CHARS if too long. """ original_len = len(text) if len(text) > MAX_TEXT_CHARS: logger.warning(f"Text too long ({original_len} chars), truncating to {MAX_TEXT_CHARS}") text = text[:MAX_TEXT_CHARS] try: resp = requests.post( f"{self.base_url}/embed", json={"text": text}, timeout=self.timeout, ) resp.raise_for_status() data = resp.json() return data["embedding"] except requests.exceptions.ConnectionError: msg = "Embedding service unavailable (connection refused)" logger.error(msg) raise RuntimeError(msg) except requests.exceptions.Timeout: msg = f"Embedding request timeout ({self.timeout}s). Text may be too long ({original_len} chars, max {MAX_TEXT_CHARS})" logger.error(msg) raise RuntimeError(msg) except requests.exceptions.HTTPError as e: status = e.response.status_code if e.response is not None else "unknown" msg = f"Embedding service error (HTTP {status}): {e}" logger.error(msg) raise RuntimeError(msg) except Exception as e: msg = f"Embedding failed: {e}" logger.error(msg) raise RuntimeError(msg) def embed_batch(self, texts: List[str]) -> List[List[float]]: """Get embeddings for multiple texts.""" return [self.embed(t) for t in texts] def health_check(self) -> bool: """Check if embedding service is alive.""" try: resp = requests.get(f"{self.base_url}/health", timeout=5) return resp.status_code == 200 except Exception: return False