fix: update to active nvidia embedding model, increase api timeouts, and add qdrant cloud indexing support
a3264f4 | """Nvidia NIM Embedding API wrapper with batching and retry logic.""" | |
| from __future__ import annotations | |
| import asyncio | |
| import httpx | |
| from loguru import logger | |
| from tenacity import retry, stop_after_attempt, wait_exponential | |
| from app.config import settings | |
| from app.utils.helpers import batch_list | |
| # Batch size for embedding requests | |
| _EMBED_BATCH_SIZE = 20 | |
| async def _embed_content_sync( | |
| texts: list[str], | |
| input_type: str, | |
| ) -> list[list[float]]: | |
| """Embed a batch of texts via Nvidia API using httpx. | |
| Args: | |
| texts: List of text strings to embed. | |
| input_type: Either 'passage' or 'query'. | |
| Returns: | |
| List of embedding vectors (each a list of floats). | |
| """ | |
| url = "https://integrate.api.nvidia.com/v1/embeddings" | |
| headers = { | |
| "Authorization": f"Bearer {settings.nvidia_api_key}", | |
| "Content-Type": "application/json", | |
| } | |
| payload = { | |
| "input": texts, | |
| "model": settings.embedding_model, | |
| "input_type": input_type, | |
| "encoding_format": "float", | |
| "truncate": "NONE", | |
| } | |
| async with httpx.AsyncClient(timeout=httpx.Timeout(120.0, connect=30.0)) as client: | |
| response = await client.post(url, headers=headers, json=payload) | |
| response.raise_for_status() | |
| data = response.json() | |
| return [item["embedding"] for item in data["data"]] | |
| async def embed_chunks(texts: list[str]) -> list[list[float]]: | |
| """Embed document chunks for indexing using Nvidia API. | |
| Batches texts into groups of 20 and embeds them with | |
| input_type='passage'. Includes exponential backoff retry. | |
| Args: | |
| texts: List of text chunks to embed. | |
| Returns: | |
| List of embedding vectors with dimension 2048. | |
| """ | |
| logger.info("Embedding {} chunks via Nvidia API", len(texts)) | |
| all_embeddings: list[list[float]] = [] | |
| batches = batch_list(texts, _EMBED_BATCH_SIZE) | |
| for i, batch in enumerate(batches): | |
| logger.debug("Embedding batch {}/{} ({} texts)", i + 1, len(batches), len(batch)) | |
| embeddings = await _embed_content_sync( | |
| batch, | |
| "passage", | |
| ) | |
| all_embeddings.extend(embeddings) | |
| logger.info( | |
| "Embedding complete: {} vectors of dimension {}", | |
| len(all_embeddings), | |
| len(all_embeddings[0]) if all_embeddings else 0, | |
| ) | |
| return all_embeddings | |
| async def embed_query(query: str) -> list[float]: | |
| """Embed a single query string for retrieval using Nvidia API. | |
| Uses input_type='query' for asymmetric search. | |
| Args: | |
| query: The user query text to embed. | |
| Returns: | |
| A single embedding vector with dimension 2048. | |
| """ | |
| logger.debug("Embedding query via Nvidia API: {}...", query[:80]) | |
| embeddings = await _embed_content_sync( | |
| [query], | |
| "query", | |
| ) | |
| return embeddings[0] | |