- Panneau agent : selects provider/modèles connus persistés par conversation (colonnes agent_conversations.provider/model, migration idempotente) - GET /api/agent/providers enrichi (liste providers + modèles + requires_key) - PATCH /api/agent/providers (admin) : config runtime DB-backed (table llm_config) - POST /api/agent/providers/test : test connexion via LLMClient.ping() sans fallback mock - Settings > Admin > Agent & IA : provider, modèle, clé API, URL API, save + test - VERSION -> 4.10.1, CHANGELOG + ROADMAP mis à jour, 232 tests verts
82 lines
2.9 KiB
Python
82 lines
2.9 KiB
Python
"""FlowDeck — Runtime LLM configuration store (v4.10.1).
|
|
|
|
Precedence: a saved `llm_config` DB row (id=1) overrides the .env settings.
|
|
The row is created only once an admin saves the LLM settings from the UI;
|
|
until then the app falls back to `settings.llm_*` (default: offline mock).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from app.config import settings
|
|
from app.services.llm_client import PROVIDERS, PROVIDER_MODELS
|
|
|
|
__all__ = ["get_llm_config", "set_llm_config", "provider_info"]
|
|
|
|
|
|
def get_llm_config() -> dict:
|
|
"""Return the effective LLM config — DB overrides .env when present."""
|
|
cfg = {
|
|
"provider": settings.llm_provider or "offline",
|
|
"model": settings.llm_model or "gpt-4o",
|
|
"api_key": settings.llm_api_key or "",
|
|
"api_base": settings.llm_api_base or "",
|
|
}
|
|
try:
|
|
from app.db import get_conn
|
|
|
|
with get_conn() as conn:
|
|
row = conn.execute(
|
|
"SELECT provider, model, api_key, api_base FROM llm_config WHERE id=1"
|
|
).fetchone()
|
|
except Exception: # noqa: BLE001 — DB not ready yet → env defaults
|
|
return cfg
|
|
if row:
|
|
for key in ("provider", "model", "api_key", "api_base"):
|
|
if row[key]:
|
|
cfg[key] = row[key]
|
|
return cfg
|
|
|
|
|
|
def set_llm_config(*, provider: str | None = None, model: str | None = None,
|
|
api_key: str | None = None, api_base: str | None = None) -> dict:
|
|
"""Upsert the runtime LLM config row (id=1) and return the new effective config."""
|
|
from app.db import get_conn
|
|
|
|
cfg = get_llm_config()
|
|
if provider is not None:
|
|
cfg["provider"] = provider
|
|
if model is not None:
|
|
cfg["model"] = model
|
|
if api_key is not None:
|
|
cfg["api_key"] = api_key
|
|
if api_base is not None:
|
|
cfg["api_base"] = api_base
|
|
|
|
with get_conn() as conn:
|
|
conn.execute(
|
|
"""INSERT INTO llm_config (id, provider, model, api_key, api_base, updated_at)
|
|
VALUES (1, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
ON CONFLICT(id) DO UPDATE SET
|
|
provider=excluded.provider, model=excluded.model,
|
|
api_key=excluded.api_key, api_base=excluded.api_base,
|
|
updated_at=CURRENT_TIMESTAMP""",
|
|
(cfg["provider"], cfg["model"], cfg["api_key"], cfg["api_base"]),
|
|
)
|
|
conn.commit()
|
|
return cfg
|
|
|
|
|
|
def provider_info() -> list[dict]:
|
|
"""Providers list for the UI: known models + whether an API key is required."""
|
|
out: list[dict] = []
|
|
for name, (_base, default_model) in PROVIDERS.items():
|
|
models = list(PROVIDER_MODELS.get(name) or ())
|
|
if default_model and default_model not in models:
|
|
models.insert(0, default_model)
|
|
out.append({
|
|
"id": name,
|
|
"name": name.capitalize(),
|
|
"default_model": default_model,
|
|
"models": models,
|
|
"requires_key": name not in ("offline", "ollama"),
|
|
})
|
|
return out |