"""LLM routing configuration API.""" from __future__ import annotations from fastapi import APIRouter, Depends, HTTPException from app.core.deps import require_admin, require_readonly from app.models.user import User from app.schemas import LlmRoutingConfigIn, LlmRoutingConfigOut, ProviderModelsOut from app.services.model_catalog import catalog_by_tier, get_entry from app.services.model_config import ( LlmRoutingConfig, available_backends, get_routing_config, save_routing_config, ) from app.services.model_discovery import list_all_provider_models router = APIRouter(prefix="/api/llm", tags=["llm"]) ROUTING_STRATEGY = ( "Local Ollama first for triage and reasoning. On escalation, cloud providers are tried " "in order: Gemini API → DeepSeek → OpenRouter. Within each cost tier (economy, premium), " "each provider is attempted before moving to the next tier." ) _MODEL_ID_FIELDS = ( "local_model_id", "economy_gemini_id", "economy_deepseek_id", "economy_openrouter_id", "premium_gemini_id", "premium_deepseek_id", "premium_openrouter_id", ) @router.get("/routing", response_model=LlmRoutingConfigOut) async def get_llm_routing(_: User = Depends(require_readonly)): cfg = await get_routing_config() return LlmRoutingConfigOut( config=cfg.to_dict(), catalog=catalog_by_tier(), available_backends=available_backends(), strategy=ROUTING_STRATEGY, ) @router.get("/models/available", response_model=ProviderModelsOut) async def get_available_models(_: User = Depends(require_readonly)): """Live model lists from Ollama, Gemini, DeepSeek, and OpenRouter APIs.""" data = await list_all_provider_models() return ProviderModelsOut(providers=data["providers"], routing_order=data["routing_order"]) @router.put("/routing", response_model=LlmRoutingConfigOut) async def update_llm_routing(body: LlmRoutingConfigIn, _: User = Depends(require_admin)): current = await get_routing_config() data = body.model_dump(exclude_unset=True) for field in ( *_MODEL_ID_FIELDS, "auto_escalate", "max_tier", ): if field in data and data[field] is not None: setattr(current, field, data[field]) if body.cloud_provider_order is not None: current.cloud_provider_order = body.cloud_provider_order for fid in _MODEL_ID_FIELDS: val = getattr(current, fid) if val and not get_entry(val): raise HTTPException(status_code=400, detail=f"Unknown model id: {val}") if current.max_tier not in ("local", "economy", "premium"): raise HTTPException(status_code=400, detail="max_tier must be local, economy, or premium") await save_routing_config(current) return LlmRoutingConfigOut( config=current.to_dict(), catalog=catalog_by_tier(), available_backends=available_backends(), strategy=ROUTING_STRATEGY, )