fix(gateway): surface actionable message for local model server connection errors

This commit is contained in:
Christopher
2026-08-15 07:27:43 +02:00
committed by Teknium
parent ae7b5c8eab
commit 5cfa2b2f0f
2 changed files with 87 additions and 1 deletions
+38 -1
View File
@@ -407,6 +407,25 @@ _GATEWAY_RATE_LIMIT_RE = re.compile(
re.IGNORECASE,
)
_GATEWAY_CONNECTION_ERROR_RE = re.compile(
r"("
r"(?:\w+\.)?(?:api\s*)?connection\s*(?:error|timeout)"
r"|(?:\w+\.)?connect\s*(?:error|timeout)"
r"|connection\s+refused"
r"|connection\s+reset"
r"|connection\s+aborted"
r"|actively\s+refused"
r"|winerror\s+10061"
r"|errno\s+111"
r"|no\s+route\s+to\s+host"
r"|network\s+is\s+unreachable"
r"|cannot\s+connect"
r"|failed\s+to\s+establish"
r"|could\s+not\s+connect"
r")",
re.IGNORECASE,
)
_GATEWAY_SECRET_PATTERNS = (
re.compile(r"\bsk-[A-Za-z0-9][A-Za-z0-9_\-]{12,}\b"),
re.compile(r"\bgh[pousr]_[A-Za-z0-9_]{20,}\b"),
@@ -665,7 +684,6 @@ def _format_exec_approval_fallback(
+ ", ".join(choices[:-1]) + f", or {choices[-1]}."
)
def _gateway_provider_error_reply(text: str) -> str:
"""Map raw provider/API errors to a short user-safe Telegram reply."""
if _GATEWAY_AUTH_ERROR_RE.search(text):
@@ -680,6 +698,11 @@ def _gateway_provider_error_reply(text: str) -> str:
)
if _GATEWAY_RATE_LIMIT_RE.search(text):
return "⏱️ The model provider is rate-limiting requests. Please wait a moment and try again."
if _GATEWAY_CONNECTION_ERROR_RE.search(text):
return (
"⚠️ The model server is not responding — it looks like the configured "
"model endpoint is not running or is unreachable."
)
return (
"⚠️ The model provider failed after retries. I kept raw provider details "
"out of chat; check gateway logs for diagnostics."
@@ -696,6 +719,20 @@ _GATEWAY_PROVIDER_ERROR_SHAPE_RE = re.compile(
r"|http\s*\d{3}\b"
r"|incorrect\s+api\s+key"
r"|invalid\s+api\s+key"
r"|(?:\w+\.)?(?:api\s*)?connection\s*(?:error|timeout)"
r"|(?:\w+\.)?connect\s*(?:error|timeout)"
r"|connection\s+refused"
r"|connection\s+reset"
r"|connection\s+aborted"
r"|actively\s+refused"
r"|winerror\s+10061"
r"|errno\s+111"
r"|no\s+route\s+to\s+host"
r"|network\s+is\s+unreachable"
r"|cannot\s+connect"
r"|failed\s+to\s+establish"
r"|could\s+not\s+connect"
r"|all\s+connection\s+attempts\s+failed"
r")",
re.IGNORECASE,
)
@@ -0,0 +1,49 @@
"""Regression tests for #86570: gateway provider error connection messaging."""
import pytest
from gateway.run import (
_GATEWAY_CONNECTION_ERROR_RE,
_gateway_provider_error_reply,
_looks_like_gateway_provider_error,
)
class TestGatewayConnectionErrorReply:
def test_connection_error_strings_produce_specific_reply(self):
samples = [
"openai.APIConnectionError",
"httpx.ConnectError: connection refused",
"ConnectionError: [WinError 10061] No connection could be made",
"Errno 111 Connection refused",
"All connection attempts failed: Connection refused",
"cannot connect to http://127.0.0.1:8033/v1",
"failed to establish a new connection",
]
for text in samples:
assert _looks_like_gateway_provider_error(text), text
reply = _gateway_provider_error_reply(text)
assert "not responding" in reply.lower(), text
assert "not running or is unreachable" in reply, text
def test_other_errors_keep_generic_reply(self):
for text in (
"RuntimeError: model returned empty content",
"Exception: unknown provider",
"HTTP 500 internal server error",
):
if _looks_like_gateway_provider_error(text):
reply = _gateway_provider_error_reply(text)
assert "not running or is unreachable" not in reply, text
def test_connection_regex_does_not_match_non_connection_error(self):
assert not _GATEWAY_CONNECTION_ERROR_RE.search("Rate limited after 3 retries")
assert not _GATEWAY_CONNECTION_ERROR_RE.search("Provider authentication failed")
def test_auth_and_rate_limit_preserved(self):
assert "authentication" in _gateway_provider_error_reply(
"provider authentication failed"
).lower()
assert "rate-limiting" in _gateway_provider_error_reply(
"rate limited after 3 retries"
).lower()