Live AI provider/model switching without restart
Backend: - RuntimeAIConfig singleton: mutable in-memory overrides for provider/model - PATCH /admin/api/ai/config: change active provider/model on the fly - GET /admin/api/ai/config: read current runtime state - ai_vision.py: uses runtime_ai_config before falling back to settings - health/status endpoints reflect runtime overrides Frontend: - 'Définir comme actif' button in AI config section - Live feedback message on save
This commit is contained in:
+69
-3
@@ -17,6 +17,7 @@ from app.config import settings
|
||||
from app.database import get_db
|
||||
from app.dependencies.auth import require_scope
|
||||
from app.models.client import APIClient
|
||||
from app.services.runtime_config import runtime_ai_config
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -114,13 +115,14 @@ def _build_providers() -> list[ProviderInfo]:
|
||||
@router.get("/status", response_model=AIStatusResponse)
|
||||
async def get_ai_status():
|
||||
"""Retourne la configuration AI actuelle et les fournisseurs/modèles disponibles."""
|
||||
active_model = (
|
||||
settings.OPENROUTER_MODEL if settings.AI_PROVIDER == "openrouter"
|
||||
active_provider = runtime_ai_config.provider or settings.AI_PROVIDER
|
||||
active_model = runtime_ai_config.model or (
|
||||
settings.OPENROUTER_MODEL if active_provider == "openrouter"
|
||||
else settings.GEMINI_MODEL
|
||||
)
|
||||
return AIStatusResponse(
|
||||
ai_enabled=settings.AI_ENABLED,
|
||||
current_provider=settings.AI_PROVIDER,
|
||||
current_provider=active_provider,
|
||||
current_model=active_model,
|
||||
providers=_build_providers(),
|
||||
)
|
||||
@@ -252,3 +254,67 @@ async def _test_gemini(model: str, prompt: Optional[str], image_b64: Optional[st
|
||||
return {"success": True, "response": text[:500], "tokens": tokens}
|
||||
except Exception as e:
|
||||
return {"success": False, "error": str(e)[:500]}
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# GET /admin/api/ai/config — état actuel du runtime config
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
@router.get("/config")
|
||||
async def get_runtime_config():
|
||||
"""Retourne la configuration AI runtime actuelle (provider + modèle)."""
|
||||
return runtime_ai_config.to_dict()
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# PATCH /admin/api/ai/config — changer le provider/modèle live
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
class UpdateAIConfigRequest(BaseModel):
|
||||
provider: Optional[str] = Field(None, description="openrouter ou gemini")
|
||||
model: Optional[str] = Field(None, description="Nom du modèle à activer")
|
||||
reset: bool = Field(False, description="true = réinitialiser aux valeurs .env")
|
||||
|
||||
class UpdateAIConfigResponse(BaseModel):
|
||||
provider: str
|
||||
model: str
|
||||
is_overridden: bool
|
||||
message: str
|
||||
|
||||
|
||||
@router.patch("/config", response_model=UpdateAIConfigResponse)
|
||||
async def update_runtime_config(
|
||||
body: UpdateAIConfigRequest,
|
||||
_=Depends(require_scope("admin")),
|
||||
):
|
||||
"""Change le fournisseur et/ou le modèle AI actif sans redémarrer."""
|
||||
if body.reset:
|
||||
runtime_ai_config.clear()
|
||||
state = runtime_ai_config.to_dict()
|
||||
return UpdateAIConfigResponse(
|
||||
provider=state["provider"],
|
||||
model=state["model"],
|
||||
is_overridden=False,
|
||||
message="Configuration AI réinitialisée aux valeurs .env",
|
||||
)
|
||||
|
||||
if not body.provider and not body.model:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Spécifiez provider et/ou model, ou reset=true",
|
||||
)
|
||||
|
||||
runtime_ai_config.set(provider=body.provider, model=body.model)
|
||||
state = runtime_ai_config.to_dict()
|
||||
|
||||
logger.info("ai.runtime_config.updated", extra={
|
||||
"provider": state["provider"],
|
||||
"model": state["model"],
|
||||
})
|
||||
|
||||
return UpdateAIConfigResponse(
|
||||
provider=state["provider"],
|
||||
model=state["model"],
|
||||
is_overridden=True,
|
||||
message=f"Fournisseur changé à {state['provider']}, modèle {state['model']}",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user