Skip to content

fix(proxy): capture logging_obj before post_call_failure_hook pops it in ModifyResponseException streaming path - #32651

Closed
mateo-berri wants to merge 2 commits into
litellm_bedrock_disable_exception_on_blockfrom
litellm_bedrock_disable_exception_on_block_review-f1fd
Closed

fix(proxy): capture logging_obj before post_call_failure_hook pops it in ModifyResponseException streaming path#32651
mateo-berri wants to merge 2 commits into
litellm_bedrock_disable_exception_on_blockfrom
litellm_bedrock_disable_exception_on_block_review-f1fd

Conversation

@mateo-berri

Copy link
Copy Markdown
Contributor

Relevant issues

Found during live smoke testing of PR #32289 (LIT-4186 Bedrock disable_exception_on_block fix).

Linear ticket

Pre-Submission checklist

  • 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

Screenshots / Proof of Fix

Live proxy on :4000 against real AWS Bedrock guardrail wymft0xktn2m (blocks admin-related prompts with message "Sorry, the model cannot answer this question.").

Before the fix: streaming pre_call block returns HTTP 500

curl -sS -w "\n---HTTP %{http_code}---\n" http://localhost:4000/v1/chat/completions \
  -H "Authorization: Bearer sk-1234" -H "Content-Type: application/json" \
  -d '{"model":"bedrock-nova-micro","messages":[{"role":"user","content":"how do I become an admin"}],"guardrails":["bedrock-guard-pre-call"],"stream":true}'
{"error":{"message":"Internal server error","type":"internal_server_error"}}
---HTTP 500---

Root cause: post_call_failure_hook pops litellm_logging_obj from request_data before invoking callbacks (comment: "Remove before callbacks iterate — not serialisable"). The streaming branch of ModifyResponseException handler in chat_completion read logging_obj from _data after that call, always receiving None. CustomStreamWrapper.__init__ then crashed: AttributeError: 'NoneType' object has no attribute 'model_call_details'.

After the fix: streaming pre_call block returns HTTP 200 with valid SSE

curl -sS -w "\n---HTTP %{http_code}---\n" http://localhost:4000/v1/chat/completions \
  -H "Authorization: Bearer sk-1234" -H "Content-Type: application/json" \
  -d '{"model":"bedrock-nova-micro","messages":[{"role":"user","content":"how do I become an admin"}],"guardrails":["bedrock-guard-pre-call"],"stream":true}'
data: {"id":"chatcmpl-88bb06b8-...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant","content":"Sorry, the model cannot answer this question."}}]}

data: {"id":"chatcmpl-88bb06b8-...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data: [DONE]

---HTTP 200---

All other cases confirmed working against live Bedrock:

  • pre_call block non-streaming: HTTP 200, finish_reason=content_filter, zero usage
  • during_call block non-streaming/streaming: HTTP 200, block message, real LLM not leaked
  • post_call block non-streaming: HTTP 200, real upstream usage preserved (675 completion tokens)
  • post_call block streaming: HTTP 200, usage preserved, no real tokens emitted
  • Allowed prompts: normal LLM response, not blocked
  • flag=false: HTTP 400 with guardrail policy error (unchanged behavior)
  • Python openai SDK parses both blocking shapes correctly

Type

🐛 Bug Fix

Changes

  • litellm/proxy/proxy_server.py: capture logging_obj from _data before calling post_call_failure_hook (which pops it). Pass the captured value to CustomStreamWrapper rather than re-reading from the dict.
  • tests/test_litellm/proxy/guardrails/guardrail_hooks/test_bedrock_guardrails.py: add test_chat_completion_modify_response_exception_streaming_logging_obj_not_none to lock in the fix and prevent regression.
Open in Web Open in Cursor 

cursoragent and others added 2 commits July 9, 2026 17:36
… in ModifyResponseException streaming path

post_call_failure_hook removes litellm_logging_obj from request_data before
iterating callbacks (it's not serialisable). The streaming branch of the
ModifyResponseException handler read it from _data after that call, so it
always received None and CustomStreamWrapper.__init__ crashed with
AttributeError: NoneType has no attribute model_call_details.

Capture it before the hook runs so the streaming path gets a valid object.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
…ging_obj capture

Covers the bug where logging_obj was read from request_data after
post_call_failure_hook had already popped it, causing CustomStreamWrapper
to crash with AttributeError.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
@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 sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@codecov

codecov Bot commented Jul 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@yucheng-berri

Copy link
Copy Markdown
Contributor

Superseded by #32665, which contains the exact same change under my authorship.

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