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] = []