fix(streaming): rebuild correct client on stale stream for anthropic mode - #51866
IEatCodeDaily wants to merge 1 commit into
Conversation
…mode
When a streaming connection goes stale (no chunks within the stale
timeout), the cleanup path killed the connection and rebuilt the
primary client so the retry loop got a fresh pool. But the rebuild
unconditionally called _replace_primary_openai_client(), which
rebuilds the OpenAI SDK client (self.client).
In anthropic_messages mode the primary streaming client is
_anthropic_client (the Anthropic SDK client), NOT self.client. So for
non-OpenAI providers served via an Anthropic-compatible endpoint —
e.g. ZAI/GLM-5.x on api.z.ai/api/anthropic, MiniMax, DashScope — the
OpenAI rebuild failed ('OPENAI_API_KEY must be set') because there is
no OpenAI key, and the actually-stale _anthropic_client was never
rebuilt. Every retry then reused the dead Anthropic connection and
looped indefinitely, hammering the provider.
Observed in production: a 1.3k-message / ~420k-token ZAI/GLM session
recovered via the Z.AI Anthropic endpoint hit a stale stream (slow
prefill on a cold cache); Hermes retried for hours against a closed
client, never recovering and starving the gateway of capacity.
Fix: branch on api_mode in the stale-stream cleanup. For
anthropic_messages and bedrock_converse (both stream through
_anthropic_client), rebuild via _rebuild_anthropic_client(); for all
other modes, keep the legacy _replace_primary_openai_client() path.
The non-streaming stale path (interruptible_api_call) already branched
correctly — only the streaming path was broken.
Adds a regression test that drives the real
interruptible_streaming_api_call stale path with a hung stream worker
and asserts _rebuild_anthropic_client is invoked in anthropic mode and
_replace_primary_openai_client in chat_completions mode.
Duplicate of #28240 — same mechanism (branch on |
|
Closed in favor of #53926, now merged to main (commit a0b9663). This was a popular bug — 12 independent PRs fixed the same issue (#28161): the three stream-cleanup paths in the streaming code rebuilt the OpenAI primary client unconditionally, which on Anthropic-native sessions both failed (no OPENAI_API_KEY) and left the wedged stream open, causing the ~15-minute hang. We salvaged @EloquentBrush0x's #28240 (the earliest dedicated fix for this issue) onto current main, resolved the conflict against the newer request-client cleanup helper, repointed the bug-encoding regression test, and added coverage for the two reachable Anthropic cleanup sites. Verified live: stale stream torn down in <1s instead of ~900s, Anthropic client closed+rebuilt, OpenAI rebuild never called on the Anthropic path. Thank you for the fix — closing as a duplicate of the merged work. Credit to everyone who reported and fixed this. |
Problem
When a streaming connection goes stale (no chunks received within the stale timeout), the cleanup path in
interruptible_streaming_api_callkills the connection and rebuilds the primary client so the retry loop gets a fresh connection pool.The rebuild unconditionally called
_replace_primary_openai_client(), which rebuilds the OpenAI SDK client (self.client).In
anthropic_messagesmode the primary streaming client is_anthropic_client(the Anthropic SDK client), notself.client. So for non-OpenAI providers served via an Anthropic-compatible endpoint — e.g. ZAI/GLM-5.x onapi.z.ai/api/anthropic, MiniMax, DashScope — two things go wrong:OPENAI_API_KEY must be set) — there is no OpenAI key for these providers._anthropic_clientis never rebuilt → every retry reuses the dead Anthropic connection → infinite retry loop, hammering the provider.Observed in production
A ~1.3k-message / ~420k-token ZAI/GLM-5.2 session recovered via Z.AI's Anthropic endpoint hit a stale stream (slow prefill on a cold prefix cache). Hermes retried for hours against a closed client, never recovering and starving the gateway of capacity, with logs:
Fix
Branch on
api_modein the streaming stale-stream cleanup. Foranthropic_messagesandbedrock_converse(both stream through_anthropic_client), rebuild via_rebuild_anthropic_client(); for all other modes, keep the legacy_replace_primary_openai_client()path.The non-streaming stale path (
interruptible_api_call) already branched correctly onapi_mode— only the streaming path was broken. This change brings it in line.Testing
tests/run_agent/test_stale_stream_anthropic_client_rebuild.pydrives the realinterruptible_streaming_api_callstale path with a hung stream worker and asserts:anthropic_messagesmode →_rebuild_anthropic_clientis called,_replace_primary_openai_clientis notchat_completionsmode →_replace_primary_openai_clientis called (legacy behaviour preserved),_rebuild_anthropic_clientis nottest_streaming,test_stream_interrupt_retry,test_openai_client_lifecycle,test_create_openai_client_reuse): 49 passed, 0 failed.Scope
This PR contains only this bugfix + its regression test. One file changed in
agent/, one test added.