Files
rag-ingestor/app/ingest/embedder.py

57 lines
1.6 KiB
Python

import asyncio
import logging
from functools import lru_cache
from ollama import AsyncClient
from app.config import get_settings
logger = logging.getLogger(__name__)
_BACKOFF_SECONDS: tuple[int, ...] = (1, 2, 4)
class EmbeddingError(Exception):
pass
@lru_cache(maxsize=1)
def _client() -> AsyncClient:
return AsyncClient(host=get_settings().ollama_url)
async def embed_texts(texts: list[str], model: str) -> list[list[float]]:
"""Embed each text via Ollama. Retries individual calls 3x with backoff."""
vectors: list[list[float]] = []
for text in texts:
vec = await _embed_one(text, model)
vectors.append(vec)
return vectors
async def _embed_one(text: str, model: str) -> list[float]:
last_err: Exception | None = None
client = _client()
for attempt in range(len(_BACKOFF_SECONDS) + 1):
try:
response = await client.embeddings(model=model, prompt=text)
return list(response["embedding"])
except Exception as exc:
last_err = exc
if attempt < len(_BACKOFF_SECONDS):
wait = _BACKOFF_SECONDS[attempt]
logger.warning(
"ollama embed retry",
extra={"event": "embed_retry", "attempt": attempt + 1, "wait_s": wait, "error": str(exc)},
)
await asyncio.sleep(wait)
raise EmbeddingError("embed failed after retries") from last_err
async def embedding_dimension(model: str) -> int:
"""Probe a single embedding to discover the model's vector dimension."""
vec = await _embed_one("dimension probe", model)
return len(vec)