UN-4137 [FIX] Stream LLM completions under the hood so long generations complete instead of timing out - #2294
Merged
Merged
Conversation
…ns complete instead of timing out and being replayed A non-streaming completion keeps the socket silent until the last token. On Anthropic, generations the console finishes in ~16 minutes never arrived: litellm.Timeout after 900 s (staging) and after 1800 s (production), then replayed up to 4x by the retry helper because Timeout is retryable, until the Celery time limit killed the task. - LLM.complete() streams (stream=True), collects the chunks and rebuilds the full response with litellm.stream_chunk_builder; callers unchanged. - collect_with_retry: retry only before the first content chunk; a drop after content started is raised immediately so a long generation is never replayed, and chunks from a failed attempt are discarded. - "Enable Streaming" checkbox on all LLM adapter forms, default on; adapters without a stored value stream too. Opt-out for endpoints that cannot stream. Read from raw adapter metadata, never sent to litellm. - Anthropic Timeout description now reflects per-chunk semantics. Verified locally on the reporting project: 12-minute Sonnet 4.6 generations (64k / 67k completion tokens) complete with no retry under the same 900 s / 3-retry settings that failed in staging. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Contributor
|
collect_with_retry created the stream outside its protected block, so an error raised by fn() itself escaped on the first attempt. litellm's streaming completion() sends the HTTP request when called, which is exactly where a 429/5xx/connection error surfaces, so those requests would have failed where the non-streaming path retried them. Create the stream inside the try block; a failure there is a failed request and retries like before. Tests cover fn() raising before returning an iterable, both retryable and not, and the same through LLM.complete() with a RateLimitError from completion(). Addresses Greptile finding on PR #2294. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The e2e rig mocks the LLM through litellm's mock_response and counts calls with litellm's fixed mock usage (10/20/30), which litellm reports only on the non-streaming path; the streaming mock token-counts the real prompt instead. A mocked completion never touches the network, so streaming buys nothing there. Bypass streaming when a mock response is injected, restoring the rig's contract. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Contributor
Unstract test resultsPer-group results
Critical paths
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



What
Jira: UN-4137 · Slack thread: https://zipstack.slack.com/archives/C08PX3XALEA/p1789985766495129?thread_ts=1789726991.952109&cid=C08PX3XALEA
LLM.complete()now streams under the hood (stream=True), collects the chunks and rebuilds the full response withlitellm.stream_chunk_builder. Callers are unchanged: same dict, same raw response (finish_reason, usage incl. cache tokens, provider request-id headers).collect_with_retryhelper inretry_utils: a failure before the first content chunk is retried exactly as a non-streaming call would be; a failure after content started is raised immediately so a long generation is never replayed. Chunks from a failed attempt are discarded, not duplicated.enable_streaming), default on. Adapters stored before the field existed also stream. Untick it only for an endpoint that cannot stream. The flag is read from the raw adapter metadata and never reaches litellm.Why
A Prompt Studio prompt on an Excel document (
claude-sonnet-4-6) "ran for an hour" and failed, while the same prompt completes in ~16 minutes in the Anthropic console. QA reproduced it with adapter timeouts of both 900 s and 1800 s.Cloud Logging (staging + production) shows the mechanism:
is_retryable_litellm_errortreatsTimeoutas retryable andmax_retriesdefaults to 3, so the identical 15–30 minute request was replayed up to 4× and then killed by the Celery time limit, with nothing to show for it.The console works because it streams. This PR makes the platform do the same.
How it was verified
Local stack on this branch, same project export, same adapter settings that failed in staging (Sonnet 4.6, timeout 900 s, 3 retries):
Backend test-connection against real providers on the branch: Anthropic and an OpenAI-compatible (
custom_openai/gpt-5) adapter both pass withstream=Trueconfirmed via a litellm spy. Bedrock could not be exercised locally (expired bearer token on the adapter).SDK1 unit tests: 33 new tests (
test_anthropic_stream_complete.py,test_llm_schema_streaming_flag.py,utils/test_collect_with_retry.py) drive litellm's real mock-streaming path sostream_chunk_builderruns for real. Full SDK1 suite: 574 passed; the 4 pre-existing asynctest_llm_compatfailures needpytest-asyncioin the venv and fail identically onmain.Staging regression checklist (all adapters, since streaming is now the default)
Retryline in executor logs)converse-stream; includes inference-profile ARN routing)stream_optionsor a zero-token usage line; the fix is to untick Enable Streaming on that adapter.Notes for reviewers
message_start, 429/529/5xx/connection errors), behaviour is unchanged.🤖 Generated with Claude Code