Skip to content

fix(bedrock): emit SSE error event when invoke Messages stream ends without message_stop - #32159

Merged
mateo-berri merged 3 commits into
litellm_internal_stagingfrom
litellm_bedrock_stream_truncation_error
Jul 5, 2026
Merged

fix(bedrock): emit SSE error event when invoke Messages stream ends without message_stop#32159
mateo-berri merged 3 commits into
litellm_internal_stagingfrom
litellm_bedrock_stream_truncation_error

Conversation

@mateo-berri

@mateo-berri mateo-berri commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Relevant issues

Linear ticket

Resolves LIT-3724 (issue 1: silent end-of-iterator)

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have requested a Greptile review by commenting @greptileai and received a Confidence Score of at least 4/5 before requesting a maintainer review

Screenshots / Proof of Fix

Bedrock cannot be forced to truncate on demand (the customer's truncation was intermittent, ultimately caused by an ALB buffering timeout between their gateway and Bedrock), so the truncation is reproduced end to end with a local mock Bedrock endpoint that streams valid application/vnd.amazon.eventstream frames for a tool_use block and then closes the connection mid input_json_delta, exactly like the customer's evidence logs. The proxy config points aws_bedrock_runtime_endpoint at it:

model_list:
  - model_name: bedrock-claude-truncating
    litellm_params:
      model: bedrock/invoke/us.anthropic.claude-sonnet-4-5-20250929-v1:0
      aws_bedrock_runtime_endpoint: http://127.0.0.1:9200
      aws_region_name: us-east-1
      aws_access_key_id: mock
      aws_secret_access_key: mock
  - model_name: bedrock-claude-real
    litellm_params:
      model: bedrock/invoke/us.anthropic.claude-opus-4-1-20250805-v1:0
      aws_region_name: us-east-1
      aws_profile_name: litellm-dev

Truncating stream, before this fix (proxy on litellm_internal_staging): the stream just stops after the partial tool JSON, HTTP 200, no terminal event

$ curl -sN http://127.0.0.1:4003/v1/messages -H "Authorization: Bearer sk-1234" -H "Content-Type: application/json" \
    -d '{"model": "bedrock-claude-truncating", "max_tokens": 1024, "stream": true, "tools": [{"name": "write", "description": "write a file", "input_schema": {"type": "object", "properties": {"filePath": {"type": "string"}}}}], "messages": [{"role": "user", "content": "write the QUAL doc"}]}' | tail -4

event: content_block_delta
data: {"type": "content_block_delta", "index": 0, "delta": {"type": "input_json_delta", "partial_json": "AL"}}

Same request, after this fix (proxy on this branch): the client now receives an Anthropic-protocol error event instead of a silent close

$ curl -sN http://127.0.0.1:4001/v1/messages ... (same request) | tail -7

event: content_block_delta
data: {"type": "content_block_delta", "index": 0, "delta": {"type": "input_json_delta", "partial_json": "AL"}}

event: error
data: {"type": "error", "error": {"type": "api_error", "message": "Provider stream ended before emitting a message_stop event; the response is incomplete and any partial content (e.g. tool_use input JSON) may be truncated."}}

Real Bedrock (live API, us.anthropic.claude-opus-4-1-20250805-v1:0), proving healthy streams are untouched: full tool call streams to completion and ends with message_stop, no error event anywhere in the stream

$ curl -sN http://127.0.0.1:4001/v1/messages -H "Authorization: Bearer sk-1234" -H "Content-Type: application/json" \
    -d '{"model": "bedrock-claude-real", "max_tokens": 200, "stream": true, "tools": [{"name": "write", "description": "write a file", "input_schema": {"type": "object", "properties": {"filePath": {"type": "string"}}}}], "messages": [{"role": "user", "content": "Use the write tool to create /docs/hello.md"}]}' | tail -8

event: content_block_stop
data: {"type": "content_block_stop", "index": 1}

event: message_delta
data: {"type": "message_delta", "delta": {"stop_reason": "tool_use", "stop_sequence": null}, "usage": {"output_tokens": 72, "input_tokens": 376, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0}}

event: message_stop
data: {"type": "message_stop", "usage": {"input_tokens": 376, "output_tokens": 72}}

QA rerun (e2e, real Bedrock)

Independent end-to-end rerun of both scenarios on a live proxy started from a fresh venv (pip install -e ".[proxy]"), with the before leg on the base branch at 26c0c93dece5182921e11387253a39dd8086db6e and the after leg on this PR's head at fe243dfe27639913382cc813892b56a27d467345, same commands both times. Every call below streams real Opus 4.1 tokens from the real Bedrock API. For the truncation scenario the proxy's aws_bedrock_runtime_endpoint points at a small local relay that re-signs the incoming request with SigV4 against the real bedrock-runtime.us-east-1.amazonaws.com, streams the real response bytes back to the proxy, and hard-closes the connection after ~12KB of eventstream bytes, which lands mid input_json_delta of a large write tool call; the tokens are real Bedrock output and only the connection cut is synthetic, reproducing the customer's middlebox failure. The healthy scenario hits Bedrock directly with no endpoint override

Request body used for all four calls (req_healthy.json is identical except "model": "bedrock-healthy"):

{
  "model": "bedrock-truncating",
  "max_tokens": 3000,
  "stream": true,
  "tools": [
    {
      "name": "write",
      "description": "Write a document to a path on disk",
      "input_schema": {
        "type": "object",
        "properties": {
          "path": {"type": "string"},
          "content": {"type": "string"}
        },
        "required": ["path", "content"]
      }
    }
  ],
  "messages": [
    {
      "role": "user",
      "content": "Use the write tool to save a detailed 1500-word QA runbook about testing streaming LLM proxies to /builder/docs/QUALITY.md. Call the tool exactly once, putting the entire document in the content field."
    }
  ]
}

Truncating stream, before (base 26c0c93dec): HTTP 200, the stream just stops mid input_json_delta, and grep over the full 131-line capture finds 0 event: message_stop and 0 event: error

$ curl -sN -w '\n[curl http_code=%{http_code}]\n' http://localhost:64769/v1/messages \
    -H 'Authorization: Bearer sk-1234' -H 'content-type: application/json' \
    -d @req_truncating.json | tail -8

event: content_block_delta
data: {"type": "content_block_delta", "index": 1, "delta": {"type": "input_json_delta", "partial_json": "e testing of"}}

event: content_block_delta
data: {"type": "content_block_delta", "index": 1, "delta": {"type": "input_json_delta", "partial_json": " s"}}


[curl http_code=200]

Relay log for that call, confirming the upstream was the real Bedrock API and the cut happened mid-stream:

[relay] POST /model/us.anthropic.claude-opus-4-1-20250805-v1:0/invoke-with-response-stream body=538B
[relay] upstream status=200
[relay] HARD CUT after 12328 bytes of real Bedrock eventstream

Truncating stream, after (head fe243dfe27): the identical request now ends with the Anthropic-protocol error event

$ curl -sN -w '\n[curl http_code=%{http_code}]\n' http://localhost:64769/v1/messages \
    -H 'Authorization: Bearer sk-1234' -H 'content-type: application/json' \
    -d @req_truncating.json | tail -8

event: content_block_delta
data: {"type": "content_block_delta", "index": 1, "delta": {"type": "input_json_delta", "partial_json": "rve as"}}

event: error
data: {"type": "error", "error": {"type": "api_error", "message": "Provider stream ended before emitting a message_stop event; the response is incomplete and any partial content (e.g. tool_use input JSON) may be truncated."}}


[curl http_code=200]

Healthy stream direct to real Bedrock, before (base 26c0c93dec): completes a 2068-output-token tool call and ends with message_stop; grep over the full 4100-line capture finds 1 event: message_stop and 0 event: error

$ curl -sN -w '\n[curl http_code=%{http_code}]\n' http://localhost:64769/v1/messages \
    -H 'Authorization: Bearer sk-1234' -H 'content-type: application/json' \
    -d @req_healthy.json | tail -8

event: message_delta
data: {"type": "message_delta", "delta": {"stop_reason": "tool_use", "stop_sequence": null}, "usage": {"output_tokens": 2068, "input_tokens": 438, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0}}

event: message_stop
data: {"type": "message_stop", "usage": {"input_tokens": 438, "output_tokens": 2068}}


[curl http_code=200]

Healthy stream, after (head fe243dfe27): unchanged behavior; grep over the full 4742-line capture finds 1 event: message_stop and 0 event: error

$ curl -sN -w '\n[curl http_code=%{http_code}]\n' http://localhost:64769/v1/messages \
    -H 'Authorization: Bearer sk-1234' -H 'content-type: application/json' \
    -d @req_healthy.json | tail -8

event: message_delta
data: {"type": "message_delta", "delta": {"stop_reason": "tool_use", "stop_sequence": null}, "usage": {"output_tokens": 2025, "input_tokens": 438, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0}}

event: message_stop
data: {"type": "message_stop", "usage": {"input_tokens": 438, "output_tokens": 2025}}


[curl http_code=200]

Type

🐛 Bug Fix

Changes

When a Bedrock invoke /v1/messages stream ends without a message_stop event (Bedrock or a middlebox drops the connection mid tool call), BaseAnthropicMessagesStreamingIterator.async_sse_wrapper used to pass through whatever events had arrived and close the downstream SSE as a clean HTTP 200. Strict clients (Anthropic SDK, Claude Code) then crash parsing unterminated tool_use input JSON, and operators cannot tell the failure from a success

async_sse_wrapper now tracks whether a terminal event was seen; if the upstream iterator is exhausted without one, it appends an Anthropic-protocol error SSE event ({"type": "error", "error": {"type": "api_error", ...}}) so clients surface the truncation instead of treating the response as complete. Complete streams are unaffected. Both message_stop and provider-sent error events count as terminal, so a stream the provider already ended with an error event does not get a second synthetic error appended. For raw-bytes chunks the detection matches only exact event: message_stop / event: error SSE lines rather than a substring search, so payload text that merely mentions message_stop cannot suppress the truncation error. The synthetic error event is deliberately excluded from the chunks handed to the logging pipeline because response reconstruction raises on {"type": "error"} events, which would drop the spend log for the tokens that did stream

Regression tests cover the truncated tool_use stream (the exact customer scenario), the complete stream, the empty stream, raw-bytes passthrough chunks, the payload-mention false positive, provider-error-terminated streams (dict and bytes), and the logging exclusion, both at the BaseAnthropicMessagesStreamingIterator level and through AmazonAnthropicClaudeMessagesConfig.bedrock_sse_wrapper


Note

Low Risk
Narrow streaming guardrail with clear success criteria; only affects streams missing message_stop, with broad test coverage and no auth or request-path changes.

Overview
Fixes LIT-3724: when a Bedrock (or proxy) /v1/messages stream stops early—e.g. mid tool_use / partial input_json_delta—clients used to get HTTP 200 with no terminal event, so Anthropic SDK / Claude Code could treat the response as complete and fail on broken tool JSON.

BaseAnthropicMessagesStreamingIterator.async_sse_wrapper now tracks whether any chunk is a message_stop (dict type or raw bytes containing message_stop). If the upstream iterator finishes without one, it appends an Anthropic-style SSE error event (api_error with a fixed incomplete-stream message). Normal streams that end with message_stop are unchanged.

Regression tests cover truncated tool-use, complete streams, empty streams, and byte passthrough at the base iterator and via bedrock_sse_wrapper.

Reviewed by Cursor Bugbot for commit 478b837. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • Bug Fixes
    • Improved streaming termination handling: if a streamed response ends without the expected final stop signal, the system now appends a clear SSE error event describing the incomplete stream.
    • Truncated tool-use or mid-stream content now surfaces an error payload rather than silently completing.
    • Complete streams continue to end normally without any added error SSE event.
  • Tests
    • Added/expanded tests covering incomplete vs complete streaming, including empty and byte-encoded streams, plus regression coverage for Bedrock Anthropic unified streaming behavior.

@mateo-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

@codecov

codecov Bot commented Jul 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.45455% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...mental_pass_through/messages/streaming_iterator.py 95.45% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@greptile-apps

greptile-apps Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a silent failure in Bedrock invoke /v1/messages streaming: when Bedrock (or a middlebox like an ALB) drops the connection before emitting a message_stop event, the proxy previously forwarded an HTTP 200 with no terminal SSE frame, causing strict clients (Anthropic SDK, Claude Code) to crash on unterminated tool-call JSON. The fix detects missing terminal events and appends a well-formed Anthropic-protocol error SSE event so consumers surface the truncation correctly.

  • BaseAnthropicMessagesStreamingIterator.async_sse_wrapper now tracks saw_terminal_event (set by message_stop or error frame detection) and yields a synthetic api_error SSE event when the upstream iterator exhausts without one; complete streams are unaffected.
  • Helper functions (_is_message_stop_chunk, _is_provider_error_chunk) use exact SSE-line matching (event: message_stop / event: error via splitlines()) rather than a raw substring search, preventing false positives from payload text that mentions message_stop.
  • The synthetic error event is intentionally excluded from collected_chunks to avoid breaking the billing/logging pipeline (which raises on {"type": "error"} events); this behavior is locked in by test_async_sse_wrapper_excludes_synthetic_error_event_from_logged_chunks.

Confidence Score: 5/5

Safe to merge: the change is narrowly scoped to the SSE wrapper's post-stream finalization path, existing complete streams are fully unaffected, and the false-positive/double-error edge cases are explicitly covered by regression tests.

The guard is a late-stage yield appended only when the upstream iterator exhausts without a terminal frame; it does not touch the hot path for normal streams. Terminal-event detection uses exact SSE-line matching rather than substring search, closing the previously-reported false-positive path. The intentional exclusion of the synthetic event from collected_chunks (to protect billing) is documented, tested, and explained. No auth, routing, or schema changes are present.

No files require special attention.

Important Files Changed

Filename Overview
litellm/llms/anthropic/experimental_pass_through/messages/streaming_iterator.py Adds terminal-event detection and synthetic error SSE emission when streams end without message_stop; helper functions are clean and use precise line-level matching to prevent false positives.
tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_streaming_iterator.py New test file with comprehensive mock-only coverage: truncated/complete/empty streams, bytes passthrough, false-positive substring regression, double-error prevention, and logging exclusion of synthetic events.
tests/test_litellm/llms/bedrock/messages/invoke_transformations/test_anthropic_claude3_transformation.py Adds two integration-level regression tests through bedrock_sse_wrapper for truncated and complete streams; follows the pre-existing pattern in this file.

Reviews (3): Last reviewed commit: "test(bedrock): lock in that the syntheti..." | Re-trigger Greptile

Comment thread litellm/llms/anthropic/experimental_pass_through/messages/streaming_iterator.py Outdated
Comment thread litellm/llms/anthropic/experimental_pass_through/messages/streaming_iterator.py Outdated
@greptile-apps

greptile-apps Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a silent stream truncation bug in the Bedrock invoke /v1/messages path: when Bedrock (or a middlebox) drops the connection before emitting a message_stop event, the iterator now appends an Anthropic-protocol error SSE event so strict clients surface the failure instead of crashing on unterminated tool-call JSON. The change is contained to async_sse_wrapper in the shared base class and is accompanied by solid regression tests.

  • async_sse_wrapper tracks a saw_message_stop flag and injects an event: error SSE sentinel after the stream exhausts without one; complete streams are untouched.
  • Two new helper functions (_is_message_stop_chunk, _incomplete_stream_error_sse_event) centralise the detection and error-payload logic.
  • Regression tests cover truncated tool-use, complete, and empty streams at both the BaseAnthropicMessagesStreamingIterator level and through AmazonAnthropicClaudeMessagesConfig.bedrock_sse_wrapper.

Confidence Score: 3/5

The core fix is sound for the Bedrock dict-chunk path, but the bytes detection path has a correctness gap that could silently suppress the error event on the Anthropic direct-streaming path.

The bytes-path _is_message_stop_chunk uses a raw substring match that means any SSE data payload containing the literal text 'message_stop' as content would be treated as a terminal event, setting saw_message_stop=True and causing the injected error sentinel to be skipped for a genuinely truncated stream. The Bedrock dict path is unaffected. The missing collected_chunks.append before logging is a secondary concern affecting observability only.

streaming_iterator.py — specifically the _is_message_stop_chunk bytes branch and the missing append of the injected error event into collected_chunks.

Important Files Changed

Filename Overview
litellm/llms/anthropic/experimental_pass_through/messages/streaming_iterator.py Adds message_stop detection and injects an Anthropic-protocol error SSE event on truncated streams; bytes detection uses a broad substring match that can false-positive on content containing "message_stop", and the injected error event is not appended to collected_chunks before logging.
tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_streaming_iterator.py New test file with good coverage: truncated tool_use, complete stream, empty stream, raw-bytes passthrough, and unit tests for the two new helper functions. All mock-only, no network calls.
tests/test_litellm/llms/bedrock/messages/invoke_transformations/test_anthropic_claude3_transformation.py Adds two regression tests for the bedrock_sse_wrapper: truncated mid-tool-use stream and complete stream ending with message_stop; both are dict-based mocks with no real network calls.

Reviews (2): Last reviewed commit: "fix(bedrock): emit SSE error event when ..." | Re-trigger Greptile

Comment thread litellm/llms/anthropic/experimental_pass_through/messages/streaming_iterator.py Outdated
Comment thread litellm/llms/anthropic/experimental_pass_through/messages/streaming_iterator.py Outdated

@yucheng-berri yucheng-berri left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gretile comments legit?

@mateo-berri

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@mateo-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Autofix Details

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Substring marks stop falsely
    • Replaced the loose b"message_stop" in chunk check with a line-level match on the SSE event: message_stop header so payload substrings (e.g. inside a partial_json delta) no longer falsely mark the stream as complete.
  • ✅ Fixed: Provider error not terminal
    • Introduced _is_terminal_stream_chunk (message_stop or provider error event, for both dict and bytes chunks) and used it in the wrapper so an upstream error terminates the stream without appending a second synthetic incomplete-stream error.

You can send follow-ups to the cloud agent here.

Comment thread litellm/llms/anthropic/experimental_pass_through/messages/streaming_iterator.py Outdated
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds incomplete-stream detection for Anthropic streaming SSE output, emits a synthetic event: error when no terminal event is seen, and expands iterator and Bedrock Claude3 tests for truncated, complete, empty, byte-framed, and provider-error streams.

Changes

Incomplete Stream Detection and Error Event Emission

Layer / File(s) Summary
Helper functions for message_stop detection and error event generation
litellm/llms/anthropic/experimental_pass_through/messages/streaming_iterator.py
Adds INCOMPLETE_STREAM_ERROR_MESSAGE, terminal-chunk detection helpers, and a helper that builds the synthetic SSE error payload.
async_sse_wrapper tracks message_stop and yields error event
litellm/llms/anthropic/experimental_pass_through/messages/streaming_iterator.py
Tracks whether a terminal event was observed while streaming and appends an SSE error event when the stream ends without one.
Anthropic streaming iterator test suite
tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_streaming_iterator.py
Adds coverage for truncated, complete, empty, byte-framed, provider-error, and logging scenarios, plus direct checks for the helper functions and synthetic error payload.
Bedrock Claude3 SSE wrapper regression tests
tests/test_litellm/llms/bedrock/messages/invoke_transformations/test_anthropic_claude3_transformation.py
Adds async regression tests for truncated mid-tool_use and normal message_stop completion behavior.

Estimated code review effort: 3 (Moderate) | ~25 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title is concise and accurately summarizes the main Bedrock streaming fix.
Description check ✅ Passed The description mostly matches the template and includes the ticket, checklist, proof, type, and changes sections.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch litellm_bedrock_stream_truncation_error

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@litellm/llms/anthropic/experimental_pass_through/messages/streaming_iterator.py`:
- Around line 21-26: The chunk detection in _is_message_stop_chunk is too narrow
and misses raw ModelResponseStream objects coming from
_promote_message_stop_usage, which then causes async_sse_wrapper to treat a
completed stream as incomplete. Update _is_message_stop_chunk and the related
_convert_chunk_to_sse_format path in streaming_iterator.py to recognize and
handle ModelResponseStream chunks as a stop signal, converting them into the
proper SSE stop format instead of passing them through unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1bd4037d-196a-444a-8e69-70ce609effc3

📥 Commits

Reviewing files that changed from the base of the PR and between 7a6a070 and 478b837.

📒 Files selected for processing (3)
  • litellm/llms/anthropic/experimental_pass_through/messages/streaming_iterator.py
  • tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_streaming_iterator.py
  • tests/test_litellm/llms/bedrock/messages/invoke_transformations/test_anthropic_claude3_transformation.py

Comment on lines +21 to +26
def _is_message_stop_chunk(chunk: object) -> bool:
if isinstance(chunk, dict):
return chunk.get("type") == "message_stop"
if isinstance(chunk, (bytes, bytearray)):
return b"message_stop" in chunk
return False

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether GenericStreamingChunk/ModelResponseStream instances are ever fed into async_sse_wrapper directly
rg -n 'async_sse_wrapper\(' -A5 --type=py
rg -nP 'class\s+(GenericStreamingChunk|ModelResponseStream)\b' --type=py -A5

Repository: BerriAI/litellm

Length of output: 153


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the relevant file and find the surrounding symbols.
ast-grep outline litellm/llms/anthropic/experimental_pass_through/messages/streaming_iterator.py --view expanded

# Inspect the exact implementation around the helper and wrapper.
nl -ba litellm/llms/anthropic/experimental_pass_through/messages/streaming_iterator.py | sed -n '1,260p'

# Find the type definitions and all direct call sites/usages.
rg -n --hidden --glob '*.py' '\b(GenericStreamingChunk|ModelResponseStream)\b|async_sse_wrapper\(' litellm

Repository: BerriAI/litellm

Length of output: 822


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the implementation around the helper, converter, and wrapper.
sed -n '1,220p' litellm/llms/anthropic/experimental_pass_through/messages/streaming_iterator.py

# Find the concrete definitions/usages of the hinted stream types.
rg -n --hidden --glob '*.py' 'class\s+(GenericStreamingChunk|ModelResponseStream)\b|GenericStreamingChunk|ModelResponseStream|async_sse_wrapper\(' litellm

Repository: BerriAI/litellm

Length of output: 50371


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the call site and the upstream iterator that feeds async_sse_wrapper.
rg -n -A20 -B20 'async_sse_wrapper\(' litellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py

# Inspect the chunk parser / patched stream path for the actual yielded runtime types.
rg -n -A25 -B10 'def _chunk_parser|def _patch|yield .*chunk|return .*chunk|async def .*stream' \
  litellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py

# Narrowly inspect the stream type definitions.
sed -n '270,330p' litellm/types/utils.py
sed -n '1760,1835p' litellm/types/utils.py

Repository: BerriAI/litellm

Length of output: 13913


Handle raw ModelResponseStream chunks here. async_sse_wrapper can receive non-dict chunks from _promote_message_stop_usage, but _is_message_stop_chunk only checks dict/bytes. A stream ending on a ModelResponseStream will still trigger the incomplete-stream error, and _convert_chunk_to_sse_format will pass that object through unchanged.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@litellm/llms/anthropic/experimental_pass_through/messages/streaming_iterator.py`
around lines 21 - 26, The chunk detection in _is_message_stop_chunk is too
narrow and misses raw ModelResponseStream objects coming from
_promote_message_stop_usage, which then causes async_sse_wrapper to treat a
completed stream as incomplete. Update _is_message_stop_chunk and the related
_convert_chunk_to_sse_format path in streaming_iterator.py to recognize and
handle ModelResponseStream chunks as a stop signal, converting them into the
proper SSE stop format instead of passing them through unchanged.

…ves and double errors

The bytes branch of _is_message_stop_chunk used a plain substring match,
so a content_block_delta whose partial_json contained the literal text
message_stop would look like a real terminal event and suppress the
synthetic incomplete-stream error. Match the SSE event header line
instead.

Also treat a provider-emitted error event as terminal so a stream that
ends with an upstream error is not followed by a second, contradictory
synthetic incomplete-stream error.
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ mateo-berri
❌ cursoragent
You have signed the CLA already but the status is still pending? Let us recheck it.

@mateo-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

@mateo-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit fe243df. Configure here.

@mateo-berri

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@mateo-berri
mateo-berri merged commit 7e43b3f into litellm_internal_staging Jul 5, 2026
128 checks passed
@mateo-berri
mateo-berri deleted the litellm_bedrock_stream_truncation_error branch July 5, 2026 00:49
EkkoG pushed a commit to EkkoG/litellm that referenced this pull request Jul 7, 2026
…ithout message_stop (BerriAI#32159)

* fix(bedrock): emit SSE error event when invoke Messages stream ends without message_stop

* fix(bedrock): tighten stream-terminal detection to avoid false positives and double errors

The bytes branch of _is_message_stop_chunk used a plain substring match,
so a content_block_delta whose partial_json contained the literal text
message_stop would look like a real terminal event and suppress the
synthetic incomplete-stream error. Match the SSE event header line
instead.

Also treat a provider-emitted error event as terminal so a stream that
ends with an upstream error is not followed by a second, contradictory
synthetic incomplete-stream error.

* test(bedrock): lock in that the synthetic truncation error event is excluded from logged chunks

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
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.

4 participants