Source code for veupath_chatbot.integrations.vectorstore.bootstrap

"""Vectorstore startup/bootstrap helpers."""

import asyncio

from veupath_chatbot.integrations.vectorstore.collections import (
    EXAMPLE_PLANS_V1,
    WDK_RECORD_TYPES_V1,
    WDK_SEARCHES_V1,
)
from veupath_chatbot.integrations.vectorstore.qdrant_store import QdrantStore
from veupath_chatbot.platform.logging import get_logger

logger = get_logger(__name__)

_KNOWN_DIMS: dict[str, int] = {
    "text-embedding-3-small": 1536,
    "text-embedding-3-large": 3072,
}


def _known_embedding_dims(model: str) -> int | None:
    """Best-effort embedding dimension lookup for common OpenAI models.

    This avoids a network call at API startup while still letting us create
    empty collections before ingestion runs.

    :param model: OpenAI model name (e.g. text-embedding-3-small).
    :returns: Dimension count or None if unknown.
    """
    return _KNOWN_DIMS.get((model or "").strip())


[docs] async def get_embedding_dim(model: str) -> int: """Return embedding dimension for *model*, using cache then OpenAI fallback. Prefer this over calling ``embed_one()`` just to discover vector size. """ dim = _known_embedding_dims(model) if dim is not None: return dim from veupath_chatbot.integrations.embeddings.openai_embeddings import embed_one vec = await embed_one(text="dimension probe", model=model) dim = len(vec) # Cache for subsequent calls within the same process. _KNOWN_DIMS[(model or "").strip()] = dim return dim
[docs] async def ensure_rag_collections() -> None: """Ensure core Qdrant collections exist. This creates *empty* collections so that RAG lookups do not fail with 404 when ingestion has not been run yet. """ from veupath_chatbot.platform.config import get_settings settings = get_settings() if not settings.rag_enabled: return store = QdrantStore.from_settings() known = _known_embedding_dims(settings.embeddings_model) if known is not None: dim = known elif ( settings.openai_api_key or settings.embeddings_base_url or settings.ollama_base_url ): dim = await get_embedding_dim(settings.embeddings_model) else: logger.warning( "RAG enabled but cannot infer embedding dimension; " "set OPENAI_API_KEY, EMBEDDINGS_BASE_URL, or configure Ollama.", embeddings_model=settings.embeddings_model, ) return # Ensure the main discovery/example collections exist (even if empty). # # In Docker Compose, Qdrant may be "started" but not yet ready to accept # requests. Retry briefly to avoid flakey startup behavior. last_exc: Exception | None = None for attempt in range(8): try: await store.ensure_collection(name=WDK_RECORD_TYPES_V1, vector_size=dim) await store.ensure_collection(name=WDK_SEARCHES_V1, vector_size=dim) await store.ensure_collection(name=EXAMPLE_PLANS_V1, vector_size=dim) return except Exception as exc: # pragma: no cover last_exc = exc await asyncio.sleep(0.25 * (attempt + 1)) logger.warning( "Unable to ensure Qdrant collections after retries", error=str(last_exc) if last_exc else None, )