from fastapi import APIRouter, Body, HTTPException from app.config import get_settings from app.llm.improved_gemini_client import ImprovedGeminiClient from app.llm.improved_ollama_client import ImprovedOllamaClient from app.llm.improved_vllm_client import ImprovedVllmClient from app.models.default_personas import get_default_personas from app.core.bootstrap import chat_orchestrator, llm, current_provider, available_providers from pydantic import BaseModel import os import logging logger = logging.getLogger(__name__) router = APIRouter() def create_llm_client(provider: str = None): global current_provider if provider is None: provider = current_provider if provider == "gemini": try: return ImprovedGeminiClient(model_name=os.getenv("GEMINI_MODEL")) except ValueError as e: logger.warning(f"Gemini API key not found, falling back to Ollama: {e}") return ImprovedOllamaClient(model_name="llama3.2:1b") elif provider == "ollama": return ImprovedOllamaClient(model_name="llama3.2:1b") elif provider == "vllm": settings = get_settings() if not settings.llm.vllm.api_url: raise ValueError("No vLLM endpoint configured. Set llm.vllm.api_url in your config.") return ImprovedVllmClient( api_url=settings.llm.vllm.api_url, api_key=settings.llm.vllm.api_key, ) else: raise ValueError(f"Unknown provider: {provider}") # NOTE: Personas and `llm` are already created and registered by # app/core/bootstrap.py with the correct ResilientLLMClient wrapping # (primary vLLM + OpenAI fallback). Re-registering them here at module # import time replaces them with a raw vLLM client that lacks the # fallback wrapper AND lacks the api_username/api_key wiring, breaking # both auth and failover. Bootstrap already handles initial setup. class ProviderSwitch(BaseModel): provider: str @router.get("/current-provider") async def get_current_provider(): return { "current_provider": current_provider, "available_providers": available_providers, "model_info": { "name": llm.model_name if hasattr(llm, 'model_name') else "gemini-2.0-flash", "provider": current_provider } } @router.post("/switch-provider") async def switch_provider(provider_data: ProviderSwitch): global current_provider, llm if provider_data.provider not in available_providers: raise HTTPException(status_code=400, detail=f"Unknown provider: {provider_data.provider}. Available: {available_providers}") try: from app.core.bootstrap import ( create_orchestrator_llm, create_persona_llm, ) current_provider = provider_data.provider new_llm = create_orchestrator_llm() llm = new_llm chat_orchestrator.llm_client = new_llm persona_llm = create_persona_llm() new_personas = get_default_personas(persona_llm) chat_orchestrator.personas.clear() for persona in new_personas: chat_orchestrator.register_persona(persona) return { "message": f"Successfully switched to {current_provider}", "current_provider": current_provider, "model_info": { "name": new_llm.model_name if hasattr(new_llm, 'model_name') else "gemini-2.0-flash", "provider": current_provider } } except Exception as e: raise HTTPException(status_code=500, detail=f"Failed to switch to {provider_data.provider}: {str(e)}") @router.post("/switch-model") async def switch_model(model_name: str = Body(...)): if "gemini" in model_name.lower(): return await switch_provider(ProviderSwitch(provider="gemini")) else: return await switch_provider(ProviderSwitch(provider="ollama")) @router.get("/current-model") async def get_current_model(): model_name = llm.model_name if hasattr(llm, 'model_name') else "gemini-2.0-flash" return { "model": model_name, "provider": current_provider }