Quiet noisy Telegram gateway errors
This commit is contained in:
154
gateway/run.py
154
gateway/run.py
@@ -66,6 +66,144 @@ _PLATFORM_CONNECT_TIMEOUT_SECS_DEFAULT = 30.0
|
||||
_ADAPTER_DISCONNECT_TIMEOUT_SECS_DEFAULT = 5.0
|
||||
_TELEGRAM_COMMAND_MENTION_RE = re.compile(r"(?<![\w:/])/([A-Za-z0-9][A-Za-z0-9_-]*)")
|
||||
|
||||
_TELEGRAM_NOISY_STATUS_RE = re.compile(
|
||||
r"(" # transient/auxiliary status that should stay in logs, not Telegram chat
|
||||
r"auxiliary\s+.+\s+failed"
|
||||
r"|compression\s+summary\s+failed"
|
||||
r"|fallback\s+context\s+marker"
|
||||
r"|configured\s+compression\s+model\s+.+\s+failed"
|
||||
r"|no\s+auxiliary\s+llm\s+provider\s+configured"
|
||||
r"|auto-lowered\s+compression\s+threshold"
|
||||
r"|preflight\s+compression"
|
||||
r"|rate\s+limited\.\s+waiting\s+\d"
|
||||
r"|retrying\s+in\s+\d"
|
||||
r"|max\s+retries\s+\(\d+\).*(?:trying\s+fallback|exhausted|invalid\s+responses)"
|
||||
r"|stream\s+(?:drop|drop\s+mid\s+tool-call).+retry\s+\d"
|
||||
r"|stale\s+connections\s+from\s+a\s+previous\s+provider\s+issue"
|
||||
r")",
|
||||
re.IGNORECASE | re.DOTALL,
|
||||
)
|
||||
|
||||
_GATEWAY_PROVIDER_ERROR_RE = re.compile(
|
||||
r"(" # infrastructure/provider error preambles, not ordinary assistant prose
|
||||
r"api\s+(?:call\s+)?failed"
|
||||
r"|provider\s+authentication\s+failed"
|
||||
r"|non-retryable\s+error"
|
||||
r"|rate\s+limited\s+after\s+\d+\s+retries"
|
||||
r"|error\s+code\s*:"
|
||||
r"|\bhttp\s*\d{3}\b"
|
||||
r"|incorrect\s+api\s+key"
|
||||
r"|invalid\s+api\s+key"
|
||||
r")",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
_GATEWAY_PROVIDER_POLICY_RE = re.compile(
|
||||
r"(" # raw provider policy/safety bodies are noisy and may be sensitive
|
||||
r"cybersecurity\s+risk"
|
||||
r"|security\s+policy"
|
||||
r"|safety\s+policy"
|
||||
r"|policy\s+violation"
|
||||
r"|violat(?:e|es|ed|ion)"
|
||||
r"|blocked\s+(?:because|by|under)"
|
||||
r"|request\s+(?:was\s+)?(?:blocked|rejected)"
|
||||
r"|disallowed"
|
||||
r"|moderation"
|
||||
r")",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
_GATEWAY_AUTH_ERROR_RE = re.compile(
|
||||
r"(provider\s+authentication\s+failed|incorrect\s+api\s+key|invalid\s+api\s+key|\b401\b)",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
_GATEWAY_RATE_LIMIT_RE = re.compile(
|
||||
r"(rate\s+limit|rate-limited|\b429\b|quota|usage\s+limit)",
|
||||
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"),
|
||||
re.compile(r"\bxox[baprs]-[A-Za-z0-9\-]{20,}\b"),
|
||||
re.compile(r"\bhf_[A-Za-z0-9]{20,}\b"),
|
||||
re.compile(r"\bglpat-[A-Za-z0-9_\-]{20,}\b"),
|
||||
re.compile(r"(?i)\b(Bearer\s+)[A-Za-z0-9._\-]{20,}\b"),
|
||||
)
|
||||
|
||||
|
||||
def _gateway_platform_value(platform: Any) -> str:
|
||||
"""Return a normalized gateway platform value for enums or raw strings."""
|
||||
return str(getattr(platform, "value", platform) or "").strip().lower()
|
||||
|
||||
|
||||
def _redact_gateway_user_facing_secrets(text: str) -> str:
|
||||
"""Best-effort secret redaction before text can leave the gateway."""
|
||||
redacted = str(text or "")
|
||||
for pattern in _GATEWAY_SECRET_PATTERNS:
|
||||
redacted = pattern.sub(lambda m: (m.group(1) if m.lastindex else "") + "[REDACTED]", redacted)
|
||||
return redacted
|
||||
|
||||
|
||||
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):
|
||||
return (
|
||||
"⚠️ Provider authentication failed. Check the configured credentials; "
|
||||
"raw provider details are in the gateway logs."
|
||||
)
|
||||
if _GATEWAY_PROVIDER_POLICY_RE.search(text):
|
||||
return (
|
||||
"⚠️ The model provider rejected the request. I kept the raw provider "
|
||||
"error out of chat; check gateway logs for details or try rephrasing."
|
||||
)
|
||||
if _GATEWAY_RATE_LIMIT_RE.search(text):
|
||||
return "⏱️ The model provider is rate-limiting requests. Please wait a moment and try again."
|
||||
return (
|
||||
"⚠️ The model provider failed after retries. I kept raw provider details "
|
||||
"out of chat; check gateway logs for diagnostics."
|
||||
)
|
||||
|
||||
|
||||
def _looks_like_gateway_provider_error(text: str) -> bool:
|
||||
"""True when text is infrastructure/provider failure, not normal content."""
|
||||
return bool(_GATEWAY_PROVIDER_ERROR_RE.search(text))
|
||||
|
||||
|
||||
def _sanitize_gateway_final_response(platform: Any, text: str) -> str:
|
||||
"""Sanitize final gateway replies before sending them to high-noise chats.
|
||||
|
||||
Telegram is Bob's mobile inbox, so it should receive concise, safe provider
|
||||
failure categories instead of raw HTTP bodies, request IDs, or policy text.
|
||||
Other platforms keep the existing behaviour for now.
|
||||
"""
|
||||
if not text:
|
||||
return text
|
||||
if _gateway_platform_value(platform) != "telegram":
|
||||
return text
|
||||
|
||||
redacted = _redact_gateway_user_facing_secrets(str(text))
|
||||
if _looks_like_gateway_provider_error(redacted):
|
||||
return _gateway_provider_error_reply(redacted)
|
||||
return redacted
|
||||
|
||||
|
||||
def _prepare_gateway_status_message(platform: Any, event_type: str, message: str) -> Optional[str]:
|
||||
"""Filter/sanitize agent status callbacks before platform delivery."""
|
||||
text = str(message or "").strip()
|
||||
if not text:
|
||||
return None
|
||||
if _gateway_platform_value(platform) != "telegram":
|
||||
return text
|
||||
|
||||
text = _redact_gateway_user_facing_secrets(text)
|
||||
if _TELEGRAM_NOISY_STATUS_RE.search(text):
|
||||
return None
|
||||
if _looks_like_gateway_provider_error(text):
|
||||
return _gateway_provider_error_reply(text)
|
||||
return text
|
||||
|
||||
|
||||
def _telegramize_command_mentions(text: str, platform: Any) -> str:
|
||||
"""Rewrite slash-command mentions to Telegram-valid command names.
|
||||
@@ -8248,6 +8386,7 @@ class GatewayRunner:
|
||||
response = _normalize_empty_agent_response(
|
||||
agent_result, response, history_len=len(history),
|
||||
)
|
||||
response = _sanitize_gateway_final_response(source.platform, response)
|
||||
|
||||
# If the agent's session_id changed during compression, update
|
||||
# session_entry so transcript writes below go to the right session.
|
||||
@@ -15782,10 +15921,23 @@ class GatewayRunner:
|
||||
def _status_callback_sync(event_type: str, message: str) -> None:
|
||||
if not _status_adapter or not _run_still_current():
|
||||
return
|
||||
prepared_message = _prepare_gateway_status_message(
|
||||
source.platform,
|
||||
event_type,
|
||||
message,
|
||||
)
|
||||
if prepared_message is None:
|
||||
logger.debug(
|
||||
"status_callback suppressed for %s/%s: %s",
|
||||
source.platform.value if source.platform else "unknown",
|
||||
event_type,
|
||||
_redact_gateway_user_facing_secrets(str(message or ""))[:160],
|
||||
)
|
||||
return
|
||||
_fut = safe_schedule_threadsafe(
|
||||
_status_adapter.send(
|
||||
_status_chat_id,
|
||||
message,
|
||||
prepared_message,
|
||||
metadata=_status_thread_metadata,
|
||||
),
|
||||
_loop_for_step,
|
||||
|
||||
Reference in New Issue
Block a user