Source code for veupath_chatbot.transport.http.routers.models

"""Models endpoint — exposes available LLM models and their status."""

from fastapi import APIRouter
from pydantic import BaseModel, Field

from veupath_chatbot.ai.models.catalog import get_model_catalog
from veupath_chatbot.platform.config import get_settings
from veupath_chatbot.platform.types import ModelProvider, ReasoningEffort


[docs] class ModelCatalogEntryResponse(BaseModel): """A single model in the catalog — for API responses.""" id: str name: str provider: ModelProvider model: str description: str = "" supports_reasoning: bool = Field(default=False, alias="supportsReasoning") enabled: bool = True context_size: int = Field(default=0, alias="contextSize") default_reasoning_budget: int = Field(default=0, alias="defaultReasoningBudget") input_price: float = Field(default=0.0, alias="inputPrice") cached_input_price: float = Field(default=0.0, alias="cachedInputPrice") output_price: float = Field(default=0.0, alias="outputPrice") model_config = {"populate_by_name": True}
[docs] class ModelListResponse(BaseModel): """Response for the /models endpoint.""" models: list[ModelCatalogEntryResponse] default: str default_reasoning_effort: ReasoningEffort = Field(alias="defaultReasoningEffort") model_config = {"populate_by_name": True}
router = APIRouter(prefix="/api/v1", tags=["models"]) def _provider_enabled(provider: ModelProvider) -> bool: """Check whether a model provider has its API key configured. :param provider: Model provider. :returns: True if the provider is enabled, False otherwise. """ settings = get_settings() key_map: dict[ModelProvider, str] = { "openai": settings.openai_api_key, "anthropic": settings.anthropic_api_key, "google": settings.gemini_api_key, "ollama": settings.ollama_base_url, } return bool(key_map.get(provider, ""))
[docs] @router.get("/models") async def list_models() -> ModelListResponse: """Return available models grouped by provider. Models whose provider has no API key are returned with ``enabled: false`` so the frontend can render them as disabled in the picker. """ settings = get_settings() is_mock = settings.chat_provider.strip().lower() == "mock" models: list[ModelCatalogEntryResponse] = [ ModelCatalogEntryResponse( id=m.id, name=m.name, provider=m.provider, model=m.model, supportsReasoning=m.supports_reasoning, enabled=_provider_enabled(m.provider), contextSize=m.context_size, defaultReasoningBudget=m.default_reasoning_budget, description=m.description, inputPrice=m.input_price, cachedInputPrice=m.cached_input_price, outputPrice=m.output_price, ) for m in get_model_catalog() if is_mock or m.provider != "mock" ] return ModelListResponse( models=models, default=settings.default_model_id, defaultReasoningEffort=settings.default_reasoning_effort, )