Spaces:
Build error
Build error
File size: 6,287 Bytes
ed52286 2d76892 ed52286 2d76892 ed52286 2d76892 ed52286 d03b796 ed52286 2d76892 ed52286 2d76892 ed52286 d03b796 d82da85 ed52286 d82da85 ed52286 2d76892 193eb98 2d76892 193eb98 2d76892 b986b08 ed52286 2d76892 193eb98 ed52286 9097545 ed52286 d82da85 ed52286 d82da85 ed52286 9097545 ed52286 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | """
Endpoints de gestion des providers et modèles IA (R10 — préfixe /api/v1/).
GET /api/v1/providers → providers détectés (disponibles ou non)
GET /api/v1/providers/{provider_type}/models → modèles d'un provider
POST /api/v1/models/refresh → liste agrégée de tous les modèles
PUT /api/v1/corpora/{id}/model → associe un modèle à un corpus
GET /api/v1/corpora/{id}/model → modèle actif d'un corpus
Les clés API vivent exclusivement dans les secrets HuggingFace (variables
d'environnement). Le backend détecte automatiquement quels providers sont
disponibles au démarrage. L'interface ne demande jamais de clé (R06).
"""
# 1. stdlib
import logging
from datetime import datetime, timezone
# 2. third-party
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, ConfigDict, Field
from sqlalchemy.ext.asyncio import AsyncSession
# 3. local
from app.models.corpus import CorpusModel
from app.models.database import get_db
from app.models.model_config_db import ModelConfigDB
from app.schemas.model_config import ProviderType
logger = logging.getLogger(__name__)
router = APIRouter(tags=["models"])
# ── Schémas ───────────────────────────────────────────────────────────────────
class ProviderInfo(BaseModel):
"""Informations sur un provider IA détecté au démarrage."""
provider_type: str
display_name: str
available: bool
model_count: int
class ModelSelectRequest(BaseModel):
model_id: str = Field(..., min_length=1, max_length=256)
provider_type: str = Field(..., min_length=1, max_length=64)
display_name: str = Field("", max_length=256)
supports_vision: bool = Field(True)
class ModelConfigResponse(BaseModel):
model_config = ConfigDict(from_attributes=True)
corpus_id: str
provider_type: str
selected_model_id: str
selected_model_display_name: str
supports_vision: bool
updated_at: datetime
class ModelsRefreshResponse(BaseModel):
models: list[dict]
count: int
refreshed_at: datetime
# ── Endpoints ─────────────────────────────────────────────────────────────────
@router.get("/providers", response_model=list[ProviderInfo])
async def list_providers() -> list[dict]:
"""Liste tous les providers IA avec leur disponibilité.
Un provider est disponible si la variable d'environnement correspondante
est présente dans les secrets HuggingFace. Aucune clé n'est exposée.
"""
from app.services.ai.model_registry import get_available_providers
return get_available_providers()
@router.get("/providers/{provider_type}/models", response_model=list[dict])
async def get_provider_models(provider_type: str) -> list[dict]:
"""Liste les modèles disponibles pour un provider spécifique."""
try:
ptype = ProviderType(provider_type)
except ValueError:
raise HTTPException(
status_code=404,
detail=f"Provider inconnu : {provider_type}. "
f"Valeurs acceptées : {[p.value for p in ProviderType]}",
)
from app.services.ai.model_registry import list_models_for_provider
try:
models = list_models_for_provider(ptype)
except RuntimeError as exc:
raise HTTPException(status_code=503, detail=str(exc))
except Exception as exc:
logger.warning("Erreur listing models", extra={"provider": provider_type, "error": str(exc)})
raise HTTPException(status_code=502, detail="Erreur temporaire du provider IA. Réessayez ultérieurement.")
return [m.model_dump() for m in models]
@router.post("/models/refresh", response_model=ModelsRefreshResponse)
async def refresh_models() -> ModelsRefreshResponse:
"""Force la mise à jour de la liste agrégée de tous les modèles disponibles."""
from app.services.ai.model_registry import list_all_models
models = list_all_models()
return ModelsRefreshResponse(
models=[m.model_dump() for m in models],
count=len(models),
refreshed_at=datetime.now(timezone.utc),
)
@router.put("/corpora/{corpus_id}/model", response_model=ModelConfigResponse)
async def set_corpus_model(
corpus_id: str,
body: ModelSelectRequest,
db: AsyncSession = Depends(get_db),
) -> ModelConfigResponse:
"""Associe un modèle IA à un corpus. Crée ou met à jour la configuration."""
corpus = await db.get(CorpusModel, corpus_id)
if corpus is None:
raise HTTPException(status_code=404, detail="Corpus introuvable")
display_name = body.display_name or body.model_id
config = await db.get(ModelConfigDB, corpus_id)
if config is None:
config = ModelConfigDB(
corpus_id=corpus_id,
provider_type=body.provider_type,
selected_model_id=body.model_id,
selected_model_display_name=display_name,
supports_vision=body.supports_vision,
updated_at=datetime.now(timezone.utc),
)
db.add(config)
else:
config.provider_type = body.provider_type
config.selected_model_id = body.model_id
config.selected_model_display_name = display_name
config.supports_vision = body.supports_vision
config.updated_at = datetime.now(timezone.utc)
await db.commit()
await db.refresh(config)
return config
@router.get("/corpora/{corpus_id}/model", response_model=ModelConfigResponse)
async def get_corpus_model(
corpus_id: str, db: AsyncSession = Depends(get_db)
) -> ModelConfigResponse:
"""Retourne la configuration du modèle IA actif pour un corpus."""
corpus = await db.get(CorpusModel, corpus_id)
if corpus is None:
raise HTTPException(status_code=404, detail="Corpus introuvable")
config = await db.get(ModelConfigDB, corpus_id)
if config is None:
raise HTTPException(
status_code=404,
detail="Aucun modèle configuré pour ce corpus",
)
return config
|