diff --git a/agent/chat_completion_nonstream.py b/agent/chat_completion_nonstream.py index 96d3c4a1d3..8d12aff8db 100644 --- a/agent/chat_completion_nonstream.py +++ b/agent/chat_completion_nonstream.py @@ -160,12 +160,11 @@ class _NonStreamRequest: elapsed=elapsed) # One neutral notice per silence; repeating it every heartbeat made # healthy long calls read as provider trouble (#92550). - near = watchdog is not None and watchdog[1] <= wn.NEAR_DEADLINE_SECS if not self.wait_notice.should_emit(phase, watchdog): self.agent._touch_activity(f"waiting for provider response ({int(silence)}s, {phase})") return self.agent._emit_wait_notice(wn.wait_notice_text( - self.api_kwargs.get('model', 'the provider'), silence, phase, watchdog, near=near)) + self.api_kwargs.get('model', 'the provider'), silence, phase, watchdog)) self.wait_notice_started_ts = self.call_start + elapsed except Exception: h.logger.debug("wait-notice construction failed", exc_info=True) diff --git a/agent/chat_completion_stream_monitor.py b/agent/chat_completion_stream_monitor.py index 145e412bcd..fa129b5e97 100644 --- a/agent/chat_completion_stream_monitor.py +++ b/agent/chat_completion_stream_monitor.py @@ -10,7 +10,7 @@ from agent.model_metadata import is_local_endpoint class StreamingWaitMonitor: def _poll_local_load_notice(self, now: float) -> bool: """Managed local server: surface a cold model's weight-load progress - instead of the 60s "provider may be slow" copy. Polled ~1s only while no + instead of the 60s neutral "waiting on " notice. Polled ~1s only while no REAL chunk arrived for 2s+ (never during healthy token flow); in-memory, no network. True while loading = heartbeat liveness, skip the rest of this iteration (the stale detector's local floor dwarfs any load).""" @@ -46,13 +46,12 @@ class StreamingWaitMonitor: watchdog = ("stream stale", stale - waiting_secs) if stale is not None and stale != float("inf") else None diag = getattr(getattr(self, "clients", None), "diag", None) phase = "post_chunk" if isinstance(diag, dict) and diag.get("first_chunk_at") else "first_chunk" - near = watchdog is not None and watchdog[1] <= wn.NEAR_DEADLINE_SECS if not self._mon.wait_notice.should_emit(phase, watchdog): self.agent._touch_activity(f"waiting for stream response ({waiting_secs}s, {phase})") return self._mon.wait_notice_started_ts = self._mon.last_heartbeat self.agent._emit_wait_notice(wn.wait_notice_text( - self.api_kwargs.get('model', 'the provider'), waiting_secs, phase, watchdog, near=near)) + self.api_kwargs.get('model', 'the provider'), waiting_secs, phase, watchdog)) else: # Chunks are flowing — keep the tracker fresh, leave the display alone. self.agent._touch_activity(f"waiting for stream response ({waiting_secs}s, no chunks yet)") diff --git a/agent/chat_completion_wait_notice.py b/agent/chat_completion_wait_notice.py index 3c51ffefcc..20b47b1f29 100644 --- a/agent/chat_completion_wait_notice.py +++ b/agent/chat_completion_wait_notice.py @@ -13,6 +13,11 @@ from typing import Optional NEAR_DEADLINE_SECS = 15.0 + +def _near_deadline(watchdog: Optional[tuple[str, float]]) -> bool: + return watchdog is not None and watchdog[1] <= NEAR_DEADLINE_SECS + + _PHASE_TEXT = { # Codex Responses (non-stream request path) "first_event": "{n}s waiting for the first provider event", @@ -25,9 +30,9 @@ _PHASE_TEXT = { def wait_notice_text(model: str, silence_secs: float, phase: str, - watchdog: Optional[tuple[str, float]] = None, *, near: bool = False) -> str: + watchdog: Optional[tuple[str, float]] = None) -> str: """One neutral status line. ``watchdog`` is ``(label, seconds_until_it_fires)``.""" - lead = "still waiting on" if near else "waiting on" + lead = "still waiting on" if _near_deadline(watchdog) else "waiting on" text = f"⏳ {lead} {model} — " + _PHASE_TEXT[phase].format(n=int(silence_secs)) if watchdog is not None: label, remaining = watchdog @@ -79,7 +84,7 @@ class WaitNoticeState: def should_emit(self, phase: str, watchdog: Optional[tuple[str, float]]) -> bool: label = watchdog[0] if watchdog is not None else None - near = watchdog is not None and watchdog[1] <= NEAR_DEADLINE_SECS + near = _near_deadline(watchdog) emit = self.phase != phase or self.watchdog_label != label or (near and not self.near_shown) self.phase, self.watchdog_label = phase, label if near: diff --git a/tests/hermes_cli/test_load_progress.py b/tests/hermes_cli/test_load_progress.py index 3da34a8a44..ed861c79e5 100644 --- a/tests/hermes_cli/test_load_progress.py +++ b/tests/hermes_cli/test_load_progress.py @@ -2,7 +2,7 @@ The 40-second problem: a cold local model streams 16-21 GB of weights before the first token, and the chat rendered that as the generic -"provider may be slow or overloaded" stall warning. llama-server's child +"waiting on " long-wait notice. llama-server's child emits real per-tensor progress which the router relays over /models/sse ONLY — these tests pin the consumer that turns that stream into the status route's `loading` field and the chat's load notice."""