From f9da57c9e066ce1d416888621667f61565219e54 Mon Sep 17 00:00:00 2001 From: Bruno Charest Date: Mon, 14 Sep 2026 23:21:21 -0400 Subject: [PATCH] fix(agent): remonter le corps de reponse des erreurs HTTP LLM 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. --- app/services/llm_client.py | 21 ++++++++++++++++++++- app/services/llm_config.py | 8 +++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/app/services/llm_client.py b/app/services/llm_client.py index e6f2a79..1346400 100644 --- a/app/services/llm_client.py +++ b/app/services/llm_client.py @@ -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 "" diff --git a/app/services/llm_config.py b/app/services/llm_config.py index ce0f0a5..cbba77a 100644 --- a/app/services/llm_config.py +++ b/app/services/llm_config.py @@ -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] = []