Files
hermes-agent/agent/thinking_timeout_guidance.py
teknium1 23036e20a6 fix(ux): plain-language, actionable user-facing messages (core)
Squashed integration of the user-facing message audit for this surface set.
Full per-finding receipts: /tmp/ux-audit/lanes/*-receipt.md (campaign artifacts).
2026-09-15 04:12:13 -07:00

52 lines
2.5 KiB
Python

"""Thinking-timeout detection and user-facing guidance for reasoning models.
A known reasoning model hitting a transport-layer error before the first content token was
almost certainly idle-killed mid-think by the upstream proxy — not a context overflow — so
the generic stream-drop guidance in conversation_loop is wrong for that case.
"""
from __future__ import annotations
from typing import Optional
# Transport-layer failure signatures: the classifier's server-disconnect set plus the OS-level
# ``broken pipe`` / ``errno 32`` the upstream kill surfaces through the OpenAI SDK wrapper.
_THINKING_TIMEOUT_SUBSTRINGS: tuple[str, ...] = (
"broken pipe", "errno 32", "remote protocol", "connection reset", "connection lost",
"peer closed", "server disconnected",
)
def is_thinking_timeout(classified: object, model: str, error_msg: str) -> bool:
"""True when a reasoning model's thinking phase hit a transport kill.
All must hold: ``classified.reason`` is the ``timeout`` FailoverReason (duck-typed via
``.value`` to avoid importing error_classifier), ``model`` is in the reasoning allowlist,
and ``error_msg`` carries a transport-kill substring. The caller gates on the error having
no HTTP ``status_code``.
"""
from agent.reasoning_timeouts import get_reasoning_stale_timeout_floor
if getattr(getattr(classified, "reason", None), "value", None) != "timeout":
return False
if get_reasoning_stale_timeout_floor(model) is None:
return False
return any(p in (error_msg or "").lower() for p in _THINKING_TIMEOUT_SUBSTRINGS)
def build_thinking_timeout_guidance(provider: str, model: str, model_label: Optional[str] = None) -> str:
"""User-facing guidance appended to the final response: easiest fix first (``/reasoning
low``), the config knob last. ``model`` is used verbatim in the config path so it is
copy-pasteable; ``model_label`` is the optional prose name."""
from hermes_constants import display_hermes_home
label = model_label or model
return (
f"{label} was thinking for so long that the connection timed out before it wrote anything "
"(common for reasoning models behind cloud gateways such as NVIDIA NIM, OpenAI, Anthropic, "
"DeepSeek). Easiest fixes: `/reasoning low`, or switch to a faster model with /model. "
f"Advanced: set `providers.{provider}.models.{model}.stale_timeout_seconds: 900` in "
f"`{display_hermes_home()}/config.yaml` to allow a longer wait."
)