fix(agent): remonter le corps de reponse des erreurs HTTP LLM
FlowDeck CI / lint (push) Successful in 1m1s
FlowDeck CI / test (push) Successful in 5m51s
FlowDeck CI / docker (push) Successful in 47s

Un 4xx (ex. 403 Mistral) affichait seulement 'Client error 403 Forbidden'. Le message d'erreur inclut desormais le corps renvoye par le fournisseur (modele non autorise, region bloquee, etc.) pour le test de connexion et la recuperation des modeles.
This commit is contained in:
2026-09-14 23:21:21 -04:00
parent 88e4ae1db8
commit f9da57c9e0
2 changed files with 27 additions and 2 deletions
+20 -1
View File
@@ -239,6 +239,24 @@ class LLMClient:
def _endpoint(self) -> str:
return f"{self.api_base.rstrip('/')}/chat/completions"
@staticmethod
def _http_error_detail(exc: httpx.HTTPStatusError) -> str:
"""Human-readable HTTP error including the provider's response body.
Providers return actionable JSON on 4xx (e.g. « model not allowed »,
« country not supported »); surfacing it makes the Settings test
debuggable instead of a bare « 403 Forbidden ».
"""
resp = exc.response
try:
body = (resp.text or "").strip()
except Exception: # noqa: BLE001 — body already consumed / undecodable
body = ""
if len(body) > 500:
body = body[:500] + "…"
base = f"{resp.status_code} {resp.reason_phrase} ({resp.url})"
return f"{base}: {body}" if body else base
async def _http_complete(self, messages, model, tools, *, _noticer: str = "") -> LLMResponse:
payload: dict = {
"model": model,
@@ -272,7 +290,8 @@ class LLMClient:
f"Le modèle « {model} » n'est plus disponible ({exc.response.status_code}). "
f"Réponse générée avec « {self.default_model} » à la place."
))
raise
# Surface the provider's own error body (403 « forbidden », 400 …).
raise RuntimeError(self._http_error_detail(exc)) from exc
response = self._parse_response(data, model)
response.notice = _noticer or ""
+7 -1
View File
@@ -347,7 +347,13 @@ async def fetch_provider_models(provider: str, *, api_key: str = "",
async with httpx.AsyncClient(timeout=timeout) as client:
resp = await client.get(f"{base_url}/models", headers=headers)
resp.raise_for_status()
if resp.status_code >= 400:
body = (resp.text or "").strip()
if len(body) > 500:
body = body[:500] + "…"
raise RuntimeError(
f"{resp.status_code} {resp.reason_phrase} ({resp.url}): {body}"
)
data = resp.json()
ids: list[str] = []