Skip to content

fix(bedrock): preserve stream param and decode SSE for bedrock mantle streaming - #32141

Merged
mateo-berri merged 2 commits into
litellm_internal_stagingfrom
litellm_lit4190_mantle_stream
Jul 7, 2026
Merged

fix(bedrock): preserve stream param and decode SSE for bedrock mantle streaming#32141
mateo-berri merged 2 commits into
litellm_internal_stagingfrom
litellm_lit4190_mantle_stream

Conversation

@mateo-berri

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

Copy link
Copy Markdown
Contributor

Relevant issues

Resolves LIT-4330
Fixes #31845

Linear ticket

Resolves LIT-4190

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

Verified end to end against the real Bedrock Mantle us-east-1 endpoint with zero mocks: the litellm proxy was run from source in a fresh venv, once detached at the base commit 4bd579c and once at the PR head be402cc, with the same config, same venv, and same port (61632) for both runs. The config points mantle-claude at bedrock/mantle/anthropic.claude-opus-4-7 via aws_profile_name, so requests are SigV4-signed with real AWS credentials and cost real money

model_list:
  - model_name: mantle-claude
    litellm_params:
      model: bedrock/mantle/anthropic.claude-opus-4-7
      aws_profile_name: litellm-dev
      aws_region_name: us-east-1

general_settings:
  master_key: sk-lit4190-qa

Before (proxy at base commit 4bd579c)

Non-streaming control, proving config, auth, and model access are all fine

curl -sS --max-time 120 http://localhost:61632/v1/chat/completions \
  -H "Authorization: Bearer sk-lit4190-qa" -H "Content-Type: application/json" \
  -d '{"model":"mantle-claude","messages":[{"role":"user","content":"Reply with exactly the word pong"}],"max_tokens":32}' \
  -w '\nHTTP %{http_code}\n'
{"id":"chatcmpl-736270b8-e9a6-4fba-8726-08893bedaaae","created":1783192888,"model":"mantle-claude","object":"chat.completion","choices":[{"finish_reason":"stop","index":0,"message":{"content":"pong","role":"assistant","provider_specific_fields":{"citations":null,"thinking_blocks":null}}}],"usage":{"completion_tokens":6,"prompt_tokens":22,"total_tokens":28,...}}
HTTP 200

Streaming on /v1/chat/completions fails with HTTP 500 ChecksumMismatch

curl -sS -N --max-time 120 http://localhost:61632/v1/chat/completions \
  -H "Authorization: Bearer sk-lit4190-qa" -H "Content-Type: application/json" \
  -d '{"model":"mantle-claude","messages":[{"role":"user","content":"Reply with exactly the word pong"}],"max_tokens":32,"stream":true}' \
  -w '\nHTTP %{http_code}\n'
{"error":{"message":"litellm.APIConnectionError: Checksum mismatch: expected 0x3a22636c, calculated 0x85428ad2\nTraceback (most recent call last):\n  File \".../litellm/llms/bedrock/chat/invoke_handler.py\", line 1631, in aiter_bytes\n    for event in event_stream_buffer:\n... (traceback elided) ...\n  File \".../botocore/eventstream.py\", line 289, in _validate_checksum\n    raise ChecksumMismatch(checksum, computed_checksum)\nbotocore.eventstream.ChecksumMismatch: Checksum mismatch: expected 0x3a22636c, calculated 0x85428ad2\n","type":null,"param":null,"code":"500"}}
HTTP 500

Streaming on /v1/messages fails the same way

curl -sS -N --max-time 120 http://localhost:61632/v1/messages \
  -H "Authorization: Bearer sk-lit4190-qa" -H "Content-Type: application/json" \
  -d '{"model":"mantle-claude","max_tokens":32,"stream":true,"messages":[{"role":"user","content":"Reply with exactly the word pong"}]}' \
  -w '\nHTTP %{http_code}\n'
{"error":{"message":"Checksum mismatch: expected 0x3a22636c, calculated 0x85428ad2\n\nTraceback (most recent call last):\n... (traceback elided) ...\n  File \".../litellm/llms/bedrock/chat/invoke_handler.py\", line 1631, in aiter_bytes\n    for event in event_stream_buffer:\n... (traceback elided) ...\nbotocore.eventstream.ChecksumMismatch: Checksum mismatch: expected 0x3a22636c, calculated 0x85428ad2\n","type":"None","param":"None","code":"500"}}
HTTP 500

After (proxy at PR head be402cc, same port, same venv, same commands)

Streaming on /v1/chat/completions now returns real SSE chunks ending in [DONE]

curl -sS -N --max-time 120 http://localhost:61632/v1/chat/completions \
  -H "Authorization: Bearer sk-lit4190-qa" -H "Content-Type: application/json" \
  -d '{"model":"mantle-claude","messages":[{"role":"user","content":"Reply with exactly the word pong"}],"max_tokens":32,"stream":true}' \
  -w '\nHTTP %{http_code}\n'
data: {"id":"chatcmpl-cc7c985e-9237-4668-b30a-9fb483b12f71","object":"chat.completion.chunk","created":1783192948,"model":"mantle-claude","choices":[{"index":0,"delta":{"role":"assistant","content":"p"}}]}

data: {"id":"chatcmpl-cc7c985e-9237-4668-b30a-9fb483b12f71","object":"chat.completion.chunk","created":1783192948,"model":"mantle-claude","choices":[{"index":0,"delta":{"content":"ong"}}]}

data: {"id":"chatcmpl-cc7c985e-9237-4668-b30a-9fb483b12f71","object":"chat.completion.chunk","created":1783192948,"model":"mantle-claude","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data: [DONE]

HTTP 200

Streaming on /v1/messages now returns native Anthropic SSE events from message_start through message_stop

curl -sS -N --max-time 120 http://localhost:61632/v1/messages \
  -H "Authorization: Bearer sk-lit4190-qa" -H "Content-Type: application/json" \
  -d '{"model":"mantle-claude","max_tokens":32,"stream":true,"messages":[{"role":"user","content":"Reply with exactly the word pong"}]}' \
  -w '\nHTTP %{http_code}\n'
event: message_start
data: {"type":"message_start","message":{"model":"claude-opus-4-7","id":"msg_bdrk_i7gpetjef4prkikdt6d4pmkhbasgesup4cminbfbtal4lby52dya","type":"message","role":"assistant","content":[],"stop_reason":null,"stop_sequence":null,"stop_details":null,"usage":{"input_tokens":22,...}}}

event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"p"}}

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"ong"}}

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

event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null,"stop_details":null},"usage":{"input_tokens":22,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":6,"output_tokens_details":{"thinking_tokens":0}}}

event: message_stop
data: {"type":"message_stop"}

HTTP 200

Non-streaming control still works after the fix

curl -sS --max-time 120 http://localhost:61632/v1/chat/completions \
  -H "Authorization: Bearer sk-lit4190-qa" -H "Content-Type: application/json" \
  -d '{"model":"mantle-claude","messages":[{"role":"user","content":"Reply with exactly the word pong"}],"max_tokens":32}' \
  -w '\nHTTP %{http_code}\n'
{"id":"chatcmpl-85ad45f3-d203-46ad-9d19-3587f8c4c500","created":1783192963,"model":"mantle-claude","object":"chat.completion","choices":[{"finish_reason":"stop","index":0,"message":{"content":"pong","role":"assistant","provider_specific_fields":{"citations":null,"thinking_blocks":null}}}],"usage":{"completion_tokens":6,"prompt_tokens":22,"total_tokens":28,...}}
HTTP 200

Type

Bug Fix

Changes

Streaming requests to bedrock/mantle/... models failed with an HTTP 500 botocore.eventstream.ChecksumMismatch error on both the /v1/chat/completions and /v1/messages surfaces, while non-streaming requests worked fine. Two problems compounded

First, the outbound request body never carried stream. The mantle transforms build the body via the shared Bedrock Invoke Anthropic base, which strips both model and stream because Bedrock Invoke puts the model in the URL and streams via the dedicated invoke-with-response-stream endpoint. The mantle configs restored model but not stream, so the bedrock-mantle endpoint (which speaks the native Anthropic Messages API and needs "stream": true in the body) returned a plain JSON response instead of SSE. Both mantle transforms (chat sync/async and the /v1/messages transform) now restore stream into the body when the caller streams, before SigV4 signing so the signature stays valid

Second, even with stream restored, the response was decoded with botocore's binary AWS event-stream parser, which crashes with ChecksumMismatch when fed the text/event-stream bytes that the mantle endpoint actually returns. The chat config now opts out of the Bedrock custom stream wrapper and reuses the existing Anthropic SSE iterator (litellm/llms/anthropic/chat/handler.py's ModelResponseIterator), and the /v1/messages config reuses the native AnthropicMessagesConfig streaming iterator that passes the SSE bytes through, instead of the AmazonAnthropicClaudeMessagesStreamDecoder binary path

Regression tests cover stream being present in the transformed body for both surfaces (sync and async), its absence when not streaming, the SSE iterator selection for the chat path, and full mocked-SSE streaming flows for completion, acompletion, and anthropic_messages that fail with the old binary decoder

Summary by CodeRabbit

  • New Features

    • Improved streaming support for Mantle chat and message-based requests.
    • Streaming responses are now handled more consistently across sync and async usage.
  • Bug Fixes

    • Fixed request payload handling so streaming is only included when enabled.
    • Improved SSE decoding so streamed output is delivered as expected, including final completion status.

Note

Medium Risk
Scoped to mantle Bedrock routing and streaming paths with regression tests; wrong decoder or body fields could still break mantle-only streaming without affecting standard Bedrock Invoke.

Overview
Fixes streaming for bedrock/mantle/... on chat completions and Anthropic /messages, which previously failed with botocore.eventstream.ChecksumMismatch because the client treated mantle responses like Bedrock Invoke binary event streams.

Request bodies now re-add stream: true (along with model) when the caller streams, via shared _restore_mantle_body_fields on the chat config and the same pattern on AmazonMantleMessagesConfig, since the Invoke base strips both fields.

Response handling switches mantle chat streaming to Anthropic ModelResponseIterator (has_custom_stream_wrapperFalse) and mantle messages streaming to AnthropicMessagesConfig’s async SSE pass-through instead of the Bedrock binary stream decoder.

Tests cover body shaping, iterator choice, and end-to-end mocked SSE for completion, acompletion, and anthropic_messages.

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

@mateo-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

@greptile-apps

greptile-apps Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes two compounding bugs that caused streaming requests to bedrock/mantle/... models to fail with a ChecksumMismatch error. The body transformation now restores stream: true (in addition to model) before SigV4 signing, and the streaming response is decoded with the Anthropic SSE iterator rather than the botocore binary event-stream parser.

  • litellm/llms/bedrock/chat/mantle/transformation.py_restore_mantle_body_fields now includes stream when streaming; has_custom_stream_wrapper returns False and get_model_response_iterator delegates to Anthropic's SSE iterator.
  • litellm/llms/bedrock/messages/mantle_transformation.py — same stream restoration for the /messages surface; get_async_streaming_response_iterator is overridden to bypass the Bedrock binary decoder and use the Anthropic SSE pass-through path.
  • tests/test_litellm/llms/bedrock/test_mantle.py — comprehensive mock-only tests for both surfaces covering stream body injection, omission when not streaming, iterator selection, and full mocked-SSE end-to-end flows.

Confidence Score: 4/5

Safe to merge — both changes are narrowly scoped to the mantle routing path and do not touch any shared Bedrock or Anthropic code paths.

The fix is correct and well-tested with mock-only unit tests covering sync/async and both surfaces. The one minor concern is in mantle_transformation.py: AnthropicMessagesConfig.get_async_streaming_response_iterator is called with self passed as a keyword argument rather than positionally. Because AmazonMantleMessagesConfig does not inherit from AnthropicMessagesConfig, any future edit to that method that accesses self would silently dispatch through the wrong MRO. The method currently does not use self, so this has no impact today, but it introduces a latent fragility.

litellm/llms/bedrock/messages/mantle_transformation.py — the get_async_streaming_response_iterator override uses an unconventional calling pattern worth cleaning up.

Important Files Changed

Filename Overview
litellm/llms/bedrock/chat/mantle/transformation.py Adds stream restoration in body, opts out of the binary Bedrock stream wrapper, and delegates to Anthropic's SSE iterator — all logically correct.
litellm/llms/bedrock/messages/mantle_transformation.py Correctly restores stream in the request body and delegates streaming to AnthropicMessagesConfig; uses an unconventional self=self keyword-argument pattern that is currently harmless but fragile if the target method ever starts using self.
tests/test_litellm/llms/bedrock/test_mantle.py New mock-only tests cover all surfaces (sync/async chat, messages API) for both the stream body injection and the SSE decode path; no real network calls and no weakening of existing assertions.

Reviews (1): Last reviewed commit: "fix(bedrock): preserve stream param and ..." | Re-trigger Greptile

Comment on lines +106 to +119
def get_async_streaming_response_iterator(
self,
model: str,
httpx_response: httpx.Response,
request_body: dict,
litellm_logging_obj: LiteLLMLoggingObj,
) -> AsyncIterator:
return AnthropicMessagesConfig.get_async_streaming_response_iterator(
self=self,
model=model,
httpx_response=httpx_response,
request_body=request_body,
litellm_logging_obj=litellm_logging_obj,
)

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.

P2 Calling AnthropicMessagesConfig.get_async_streaming_response_iterator(self=self, ...) with self passed as a keyword argument is unconventional. More importantly, because AmazonMantleMessagesConfig does not inherit from AnthropicMessagesConfig, any future change to that method that calls self.some_attr will dispatch through AmazonMantleMessagesConfig's MRO rather than AnthropicMessagesConfig's — silently producing unexpected behavior. The standard way to call an unbound method from a non-parent class is to pass the instance positionally.

Suggested change
def get_async_streaming_response_iterator(
self,
model: str,
httpx_response: httpx.Response,
request_body: dict,
litellm_logging_obj: LiteLLMLoggingObj,
) -> AsyncIterator:
return AnthropicMessagesConfig.get_async_streaming_response_iterator(
self=self,
model=model,
httpx_response=httpx_response,
request_body=request_body,
litellm_logging_obj=litellm_logging_obj,
)
def get_async_streaming_response_iterator(
self,
model: str,
httpx_response: httpx.Response,
request_body: dict,
litellm_logging_obj: LiteLLMLoggingObj,
) -> AsyncIterator:
return AnthropicMessagesConfig.get_async_streaming_response_iterator(
self,
model=model,
httpx_response=httpx_response,
request_body=request_body,
litellm_logging_obj=litellm_logging_obj,
)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@greptile-apps

greptile-apps Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes two compounding bugs that caused HTTP 500 ChecksumMismatch errors on all streaming requests to bedrock/mantle/… models, while non-streaming requests worked fine.

  • stream not preserved in body: Both mantle transforms inherit from a base that pops model and stream from the request (Bedrock Invoke convention). The mantle endpoint speaks the native Anthropic Messages API and needs both. The fix adds _restore_mantle_body_fields to re-inject them after the base transform runs, before SigV4 signing.
  • Wrong stream decoder: Even with stream restored, the response was fed to botocore's binary AWS event-stream parser. The chat config now returns has_custom_stream_wrapper = False and delegates to ModelResponseIterator (Anthropic SSE); the messages config overrides get_async_streaming_response_iterator to use AnthropicMessagesConfig's SSE iterator instead of AmazonAnthropicClaudeMessagesStreamDecoder.

Confidence Score: 4/5

Safe to merge — the change is narrowly scoped to the mantle routing path, non-mantle Bedrock callers are unaffected, and the new tests exercise both the body-transform and SSE-decode paths end-to-end with mocked HTTP.

The two-part fix is logically correct and well-tested. The only roughness is in mantle_transformation.py, where AnthropicMessagesConfig.get_async_streaming_response_iterator(self=self, ...) is an unbound-call pattern that works today because the method does not use instance state, but is opaque about its intent to skip a parent override and could silently misbehave if that method ever starts accessing self.

litellm/llms/bedrock/messages/mantle_transformation.py — the unbound MRO-bypass call warrants a second look.

Important Files Changed

Filename Overview
litellm/llms/bedrock/chat/mantle/transformation.py Restores stream in the request body via _restore_mantle_body_fields and opts out of the Bedrock binary event-stream wrapper by returning False from has_custom_stream_wrapper and delegating to the Anthropic SSE ModelResponseIterator.
litellm/llms/bedrock/messages/mantle_transformation.py Restores stream in the messages request body and bypasses the binary AmazonAnthropicClaudeMessagesStreamDecoder by calling AnthropicMessagesConfig.get_async_streaming_response_iterator directly via an unbound call; the unbound pattern works but is stylistically unusual.
tests/test_litellm/llms/bedrock/test_mantle.py Adds new mock-based tests for stream body preservation (sync/async, chat and messages paths) and end-to-end SSE decoding for completion, acompletion, and anthropic_messages; no real network calls, consistent with CI requirements.

Reviews (2): Last reviewed commit: "fix(bedrock): preserve stream param and ..." | Re-trigger Greptile

Comment on lines +106 to +119
def get_async_streaming_response_iterator(
self,
model: str,
httpx_response: httpx.Response,
request_body: dict,
litellm_logging_obj: LiteLLMLoggingObj,
) -> AsyncIterator:
return AnthropicMessagesConfig.get_async_streaming_response_iterator(
self=self,
model=model,
httpx_response=httpx_response,
request_body=request_body,
litellm_logging_obj=litellm_logging_obj,
)

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.

P2 The unbound call AnthropicMessagesConfig.get_async_streaming_response_iterator(self=self, ...) works but is unusual — it silently skips the AmazonAnthropicClaudeMessagesConfig MRO level without stating intent. The idiomatic Python equivalent is super(AmazonAnthropicClaudeMessagesConfig, self).get_async_streaming_response_iterator(...), which makes the MRO-bypass explicit and will continue to work correctly if the class hierarchy is refactored.

Suggested change
def get_async_streaming_response_iterator(
self,
model: str,
httpx_response: httpx.Response,
request_body: dict,
litellm_logging_obj: LiteLLMLoggingObj,
) -> AsyncIterator:
return AnthropicMessagesConfig.get_async_streaming_response_iterator(
self=self,
model=model,
httpx_response=httpx_response,
request_body=request_body,
litellm_logging_obj=litellm_logging_obj,
)
def get_async_streaming_response_iterator(
self,
model: str,
httpx_response: httpx.Response,
request_body: dict,
litellm_logging_obj: LiteLLMLoggingObj,
) -> AsyncIterator:
return super(AmazonAnthropicClaudeMessagesConfig, self).get_async_streaming_response_iterator(
model=model,
httpx_response=httpx_response,
request_body=request_body,
litellm_logging_obj=litellm_logging_obj,
)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

AmazonMantleMessagesConfig actually inherit AnthropicMessagesConfig (w/ AmazonAnthropicClaudeMessagesConfig), and passing self keyword vs positionally never changes dispatch, so your concern is factually wrong. But ig it's more conventional to do self so I'll take the suggestion

@codecov

codecov Bot commented Jul 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@mateo-berri

Copy link
Copy Markdown
Contributor Author

@CodeRabbit review

@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.

@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 be402cc. Configure here.

@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Bedrock Mantle chat and messages transformation code now centralizes request body restoration (model and conditional stream flag) via a shared helper, and Mantle messages streaming delegates to AnthropicMessagesConfig's async streaming iterator for native SSE handling instead of AWS binary event-stream parsing. Tests were expanded accordingly.

Changes

Mantle Streaming Fix

Layer / File(s) Summary
Chat transform request body restoration
litellm/llms/bedrock/chat/mantle/transformation.py
Introduces _restore_mantle_body_fields static method used by both transform_request and async_transform_request to set model and conditionally inject stream; adds has_custom_stream_wrapper property and updates get_model_response_iterator typing to accept sync/async iterators, returning a ModelResponseIterator.
Anthropic messages transform and streaming iterator delegation
litellm/llms/bedrock/messages/mantle_transformation.py
transform_anthropic_messages_request now injects stream: True into the request body when requested; adds an override of get_async_streaming_response_iterator that delegates to AnthropicMessagesConfig for native SSE decoding.
Mantle streaming and SSE tests
tests/test_litellm/llms/bedrock/test_mantle.py
Adds tests for stream flag presence/omission in transform methods, verifies get_model_response_iterator return type, and adds SSE fixtures/helpers plus sync/async completion and anthropic_messages streaming tests validating request bodies and decoded output.

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

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant AmazonMantleMessagesConfig
  participant AnthropicMessagesConfig
  participant BedrockAPI
  Client->>AmazonMantleMessagesConfig: transform_anthropic_messages_request(stream=True)
  AmazonMantleMessagesConfig-->>Client: request body with stream=True
  Client->>BedrockAPI: POST request
  BedrockAPI-->>Client: httpx_response (native SSE)
  Client->>AmazonMantleMessagesConfig: get_async_streaming_response_iterator(httpx_response)
  AmazonMantleMessagesConfig->>AnthropicMessagesConfig: get_async_streaming_response_iterator(model, httpx_response, request_body)
  AnthropicMessagesConfig-->>Client: AsyncIterator of decoded SSE chunks
Loading

Related issues: #31845 (bedrock-mantle /v1/messages streaming ChecksumMismatch crash)

Suggested labels: bug, bedrock, streaming

Suggested reviewers: krrishdholakia, ishaan-jaff

🐰 A mantle wrapped in SSE,
No more checksum crash for me,
Streams now flow the Anthropic way,
Tests confirm "pong" comes out to play,
Hopping through each chunk with glee!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main fix: preserving stream params and switching Mantle streaming to SSE decoding.
Description check ✅ Passed The PR description includes all required sections and enough detail, including issues, ticket, checklist, proof, type, and changes.
Linked Issues check ✅ Passed The changes fix Mantle /v1/messages streaming by preserving stream and using SSE-compatible iteration, while leaving non-streaming behavior intact.
Out of Scope Changes check ✅ Passed All code changes are tied to Mantle streaming behavior and regression tests; no unrelated scope was introduced.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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_lit4190_mantle_stream

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.

🧹 Nitpick comments (2)
tests/test_litellm/llms/bedrock/test_mantle.py (1)

496-522: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Monkeypatching HTTPHandler.post/AsyncHTTPHandler.post class attributes.

These three new tests patch the post method directly on the HTTPHandler/AsyncHTTPHandler classes rather than injecting a mocked/custom client instance into litellm.completion/acompletion/anthropic_messages (which accept a client= param in most call paths). As per coding guidelines, **/*.{py,pyi}: "Prefer dependency injection over monkeypatching class attributes in tests; pass mocked dependencies into classes instead."

#!/bin/bash
# Verify whether completion/acompletion/anthropic_messages accept an injectable client param
# for the bedrock provider path, to confirm DI is a viable alternative here.
rg -n -A5 'def completion\(' litellm/llms/anthropic/chat/handler.py
rg -n 'client=' tests/test_litellm/llms/bedrock/test_mantle.py

Also applies to: 524-557, 559-592

🤖 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 `@tests/test_litellm/llms/bedrock/test_mantle.py` around lines 496 - 522, The
new Mantle streaming tests are monkeypatching HTTPHandler.post and
AsyncHTTPHandler.post directly, which should be replaced with dependency
injection. Update test_mantle.py so
test_mantle_completion_streaming_sends_stream_and_decodes_sse and the related
async/message tests pass a mocked client or transport into litellm.completion,
litellm.acompletion, or anthropic_messages instead of patching class attributes.
Use the existing client= injection path where available, and keep the
request/assertion logic the same.

Source: Coding guidelines

litellm/llms/bedrock/chat/mantle/transformation.py (1)

128-149: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Extract the shared Mantle request-restoration helper. _restore_mantle_body_fields repeats the same stream/model restoration logic used in litellm/llms/bedrock/messages/mantle_transformation.py; a small helper would keep the chat and messages transforms aligned.

🤖 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/bedrock/chat/mantle/transformation.py` around lines 128 - 149,
The Mantle request-restoration logic in _restore_mantle_body_fields is
duplicated from the messages transform and should be centralized. Extract the
shared stream/model restoration into a common helper used by both Mantle
transformation classes, then have _restore_mantle_body_fields delegate to it so
chat and messages stay aligned. Keep the existing behavior in
get_model_response_iterator and the Mantle transformation flow unchanged.
🤖 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.

Nitpick comments:
In `@litellm/llms/bedrock/chat/mantle/transformation.py`:
- Around line 128-149: The Mantle request-restoration logic in
_restore_mantle_body_fields is duplicated from the messages transform and should
be centralized. Extract the shared stream/model restoration into a common helper
used by both Mantle transformation classes, then have
_restore_mantle_body_fields delegate to it so chat and messages stay aligned.
Keep the existing behavior in get_model_response_iterator and the Mantle
transformation flow unchanged.

In `@tests/test_litellm/llms/bedrock/test_mantle.py`:
- Around line 496-522: The new Mantle streaming tests are monkeypatching
HTTPHandler.post and AsyncHTTPHandler.post directly, which should be replaced
with dependency injection. Update test_mantle.py so
test_mantle_completion_streaming_sends_stream_and_decodes_sse and the related
async/message tests pass a mocked client or transport into litellm.completion,
litellm.acompletion, or anthropic_messages instead of patching class attributes.
Use the existing client= injection path where available, and keep the
request/assertion logic the same.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ea251a21-ad4b-4335-bcb7-8ebcb19f301c

📥 Commits

Reviewing files that changed from the base of the PR and between 07b9ea8 and be402cc.

📒 Files selected for processing (3)
  • litellm/llms/bedrock/chat/mantle/transformation.py
  • litellm/llms/bedrock/messages/mantle_transformation.py
  • tests/test_litellm/llms/bedrock/test_mantle.py

@mateo-berri
mateo-berri requested a review from yucheng-berri July 4, 2026 20:48
@lzy7071

lzy7071 commented Jul 7, 2026

Copy link
Copy Markdown

This is a fairly important patch, at least for my use case, because the only way to use fable-5 on bedrock is via bedrock/mantle/anthropic.claude-fable-5 as far as I know. Without the patch, I cannot use fable-5 on claude-cli.

@mateo-berri
mateo-berri merged commit 90440d7 into litellm_internal_staging Jul 7, 2026
123 checks passed
@mateo-berri
mateo-berri deleted the litellm_lit4190_mantle_stream branch July 7, 2026 17:15
@mateo-berri

Copy link
Copy Markdown
Contributor Author

This is a fairly important patch, at least for my use case, because the only way to use fable-5 on bedrock is via bedrock/mantle/anthropic.claude-fable-5 as far as I know. Without the patch, I cannot use fable-5 on claude-cli.

@lzy7071 thanks for letting us know. Merged. Thanks for the reminder!

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.

[Bug]: bedrock-mantle /v1/messages streaming raises botocore ChecksumMismatch (native Anthropic SSE decoded as AWS binary event-stream)

3 participants