feat(agent): v4.10.1 - config LLM dans l'UI (sélecteur provider/modèle, config runtime, admin + test connexion)
- 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
This commit is contained in:
@@ -317,6 +317,98 @@ def test_router_tools_and_providers(client):
|
||||
prov = client.get("/api/agent/providers").json()
|
||||
assert prov["provider"] == "offline"
|
||||
assert prov["available"] is False
|
||||
assert "providers" in prov
|
||||
|
||||
|
||||
def test_router_providers_enriched(client):
|
||||
prov = client.get("/api/agent/providers").json()
|
||||
assert "providers" in prov
|
||||
ids = [p["id"] for p in prov["providers"]]
|
||||
for expected in ("openai", "anthropic", "google", "ollama", "offline"):
|
||||
assert expected in ids
|
||||
openai = next(p for p in prov["providers"] if p["id"] == "openai")
|
||||
assert openai["default_model"] == "gpt-4o"
|
||||
assert openai["models"]
|
||||
assert openai["requires_key"] is True
|
||||
# offline requires no key and exposes no models
|
||||
offline = next(p for p in prov["providers"] if p["id"] == "offline")
|
||||
assert offline["requires_key"] is False
|
||||
|
||||
|
||||
def test_router_patch_providers_updates_config(client):
|
||||
from app.auth.session import SessionManager
|
||||
from app.db import get_conn
|
||||
|
||||
with get_conn() as conn:
|
||||
admin = conn.execute("SELECT * FROM users WHERE login='admin'").fetchone()
|
||||
sess = SessionManager.create_session(dict(admin))
|
||||
|
||||
client.cookies.set("flowdeck_session", sess)
|
||||
r = client.patch("/api/agent/providers",
|
||||
json={"provider": "openai", "model": "gpt-4o-mini",
|
||||
"api_key": "sk-test", "api_base": ""})
|
||||
assert r.status_code == 200
|
||||
body = r.json()
|
||||
assert body["provider"] == "openai"
|
||||
assert body["model"] == "gpt-4o-mini"
|
||||
assert body["has_api_key"] is True
|
||||
|
||||
# persisted in llm_config (single row id=1)
|
||||
with get_conn() as conn:
|
||||
row = conn.execute("SELECT * FROM llm_config WHERE id=1").fetchone()
|
||||
assert row is not None
|
||||
assert row["provider"] == "openai"
|
||||
assert row["model"] == "gpt-4o-mini"
|
||||
assert row["api_key"] == "sk-test"
|
||||
|
||||
# GET /providers reflects the new config
|
||||
prov = client.get("/api/agent/providers").json()
|
||||
assert prov["provider"] == "openai"
|
||||
assert prov["model"] == "gpt-4o-mini"
|
||||
assert prov["has_api_key"] is True
|
||||
# a real provider with a key is marked available (no network call made)
|
||||
assert prov["available"] is True
|
||||
|
||||
|
||||
def test_router_patch_providers_requires_admin(client):
|
||||
from app.auth.session import SessionManager
|
||||
from app.db import get_conn
|
||||
|
||||
with get_conn() as conn:
|
||||
conn.execute("INSERT INTO users (login, full_name, is_admin) VALUES ('user_basic', 'Basic', 0)")
|
||||
user = conn.execute("SELECT * FROM users WHERE login='user_basic'").fetchone()
|
||||
conn.commit()
|
||||
sess = SessionManager.create_session(dict(user))
|
||||
client.cookies.set("flowdeck_session", sess)
|
||||
|
||||
r = client.patch("/api/agent/providers", json={"provider": "openai"})
|
||||
assert r.status_code == 403
|
||||
|
||||
|
||||
def test_router_providers_test_offline(client):
|
||||
r = client.post("/api/agent/providers/test", json={"provider": "offline"})
|
||||
assert r.status_code == 200
|
||||
assert r.json()["ok"] is True
|
||||
|
||||
|
||||
def test_router_providers_test_missing_key(client):
|
||||
# openai without a key must report failure (no mock fallback for the ping)
|
||||
r = client.post("/api/agent/providers/test",
|
||||
json={"provider": "openai", "api_key": ""})
|
||||
assert r.status_code == 200
|
||||
assert r.json()["ok"] is False
|
||||
assert "clé" in r.json()["error"].lower()
|
||||
|
||||
|
||||
def test_router_run_persists_provider_model(client):
|
||||
cid = _make_conversation(client)
|
||||
resp = client.post(f"/api/agent/conversations/{cid}/run",
|
||||
json={"message": "crée une collection Bilan",
|
||||
"provider": "openai", "model": "gpt-4o-mini"})
|
||||
assert resp.status_code == 200
|
||||
detail = client.get(f"/api/agent/conversations/{cid}").json()
|
||||
assert detail["conversation"]["provider"] == "openai"
|
||||
assert detail["conversation"]["model"] == "gpt-4o-mini"
|
||||
|
||||
|
||||
def _parse_sse(line: str) -> dict:
|
||||
|
||||
Reference in New Issue
Block a user