Skip to content

fix: prevent false StreamIdleTimeout when ToolCallTextFilter suppresses SSE events#720

Merged
Aaronontheweb merged 2 commits into
devfrom
fix/streaming-watchdog-keepalive
Apr 22, 2026
Merged

fix: prevent false StreamIdleTimeout when ToolCallTextFilter suppresses SSE events#720
Aaronontheweb merged 2 commits into
devfrom
fix/streaming-watchdog-keepalive

Conversation

@Aaronontheweb

Copy link
Copy Markdown
Collaborator

Summary

Fixes #717

  • When the ToolCallTextFilter detects <tool_call> XML in streaming text, it suppresses all subsequent SSE updates via continue, bypassing yield return. This creates a watchdog blackout — no LlmResponseDeltaReceived messages reach the session actor, so ProcessingWatchdog.Refresh() is never called and the StreamIdleTimeout (default 120s) fires even though the GPU is actively generating tokens.
  • The fix yields a content-free keepalive ChatResponseUpdate when text is suppressed, and sends a keepalive LlmResponseDeltaReceived with empty TextContent when no content is dispatched for an SSE event. The actor's delta handler refreshes the watchdog unconditionally before the content-type switch, so empty TextContent resets the timer without emitting visible output.

Changes

File Change
OpenAiCompatibleChatClient.cs Yield keepalive ChatResponseUpdate when ToolCallTextFilter suppresses text
SessionLlmInvoker.cs Add dispatched flag; send keepalive delta when no content dispatched
OpenAiCompatibleChatClientTests.cs Two new tests verifying keepalive behavior during suppression

Test plan

  • StreamingYieldsKeepaliveUpdates_WhenToolCallFilterSuppressesText — verifies keepalive updates are yielded when filter suppresses text
  • StreamingKeepalive_ToolCallStillExtracted_AfterSuppression — verifies tool call extraction still works with keepalives
  • All 40 existing OpenAiCompatibleChatClientTests pass
  • Both existing LlmSessionWatchdogTests pass (watchdog integration)

…es SSE events (#717)

When the ToolCallTextFilter detects <tool_call> XML in streaming text,
it suppresses all subsequent SSE updates via `continue`, bypassing the
`yield return`. This creates a watchdog blackout — no
LlmResponseDeltaReceived messages reach the session actor, so
ProcessingWatchdog.Refresh() is never called and the StreamIdleTimeout
(default 120s) fires even though the GPU is actively generating tokens.

Fix: yield a content-free keepalive ChatResponseUpdate when text is
suppressed, and send a keepalive LlmResponseDeltaReceived with empty
TextContent when no content is dispatched for an SSE event. The actor's
delta handler refreshes the watchdog unconditionally before the
content-type switch, so empty TextContent resets the timer without
emitting visible output.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ToolCallTextFilter suppresses streaming keepalives, causing false StreamIdleTimeout

1 participant