Skip to content

fix: strip empty text content blocks in /v1/messages endpoint - #23097

Merged
1 commit merged into
BerriAI:mainfrom
MaxwellCalkin:fix/sanitize-empty-text-blocks-v1-messages
Mar 10, 2026
Merged

fix: strip empty text content blocks in /v1/messages endpoint#23097
1 commit merged into
BerriAI:mainfrom
MaxwellCalkin:fix/sanitize-empty-text-blocks-v1-messages

Conversation

@MaxwellCalkin

Copy link
Copy Markdown
Contributor

Summary

Fixes #22930

When routing Anthropic SDK requests through LiteLLM's /v1/messages endpoint, multi-turn tool-use conversations fail because Claude's API returns assistant messages with empty text blocks ({"type": "text", "text": ""}) alongside tool_use blocks. These empty blocks are forwarded as-is and rejected when sent back with:

400: {"type":"error","error":{"type":"invalid_request_error","message":"messages: text content blocks must be non-empty"}}

Sanitization already exists for other code paths:

But none of these fixes cover the /v1/messages native path, which passes messages through async_anthropic_messages_handlertransform_anthropic_messages_request without any sanitization.

Changes

  • Added _sanitize_anthropic_messages_empty_text_blocks() helper in litellm/llms/custom_httpx/llm_http_handler.py that strips empty/whitespace-only text blocks from Anthropic-format message content arrays before forwarding
  • Called it in async_anthropic_messages_handler() right before transform_anthropic_messages_request(), so all providers using the native /v1/messages path benefit (Anthropic direct, Bedrock invoke, Azure AI, Vertex AI)
  • If stripping would leave an empty content array (all blocks were empty text), replaces with a single {"type": "text", "text": "..."} placeholder

Test plan

  • Added tests/test_litellm/llms/anthropic/test_v1_messages_empty_text_sanitization.py with 10 unit tests covering:
    • Empty text block alongside tool_use is stripped
    • Non-empty text blocks are preserved
    • Whitespace-only text blocks are stripped
    • All-empty content replaced with placeholder (not empty array)
    • String content messages pass through unchanged
    • Messages without content key pass through unchanged
    • User message content lists are also sanitized
    • tool_result blocks are not affected
    • Multi-message end-to-end scenario
    • Original messages are not mutated

Claude's API returns assistant messages with empty text blocks
({"type": "text", "text": ""}) alongside tool_use blocks during
multi-turn tool-use conversations. These blocks are rejected when
sent back to the API with "text content blocks must be non-empty".

Sanitization already exists for other code paths (/v1/chat/completions
for both Anthropic and Bedrock), but NOT for the /v1/messages native
path. This adds the same treatment by stripping empty text blocks
from messages in async_anthropic_messages_handler before they are
forwarded to the provider.

Fixes BerriAI#22930
@vercel

vercel Bot commented Mar 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
litellm Error Error Mar 8, 2026 7:36am

Request Review

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

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

@greptile-apps

greptile-apps Bot commented Mar 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a 400 invalid_request_error that occurred in multi-turn tool-use conversations routed through LiteLLM's /v1/messages native path. The issue was caused by empty text blocks ({"type": "text", "text": ""}) returned by Claude being forwarded back unchanged, which Claude's API rejects.

The fix adds a module-level _sanitize_anthropic_messages_empty_text_blocks() helper function in llm_http_handler.py and calls it in async_anthropic_messages_handler() before transform_anthropic_messages_request(). This ensures all providers using the native /v1/messages path (Anthropic, Bedrock, Azure AI, Vertex AI) benefit.

Key strengths:

  • The sanitizer correctly avoids mutating the original message list (uses message.copy() only when changes are needed).
  • The fallback to {"type": "text", "text": "..."} when all blocks are empty prevents sending an empty content array (which would also be rejected).
  • The always-on nature is appropriate: the previous behavior unconditionally caused 400 errors, so there is no regression risk.
  • Comprehensive unit tests (10 tests) cover all documented branches and scenarios without making real network calls.
  • A minor style issue in the test file: unused import pytest.

Confidence Score: 5/5

  • Safe to merge; targeted bug fix with comprehensive unit tests and no backwards-incompatible risk.
  • The change is a straightforward, well-tested bug fix. The implementation avoids mutation, includes a sensible fallback for edge cases, and the always-on behavior is appropriate given the prior behavior unconditionally caused errors. The 10 unit tests provide good coverage of the sanitization logic. Only a trivial unused import remains to clean up.
  • No files require special attention; remove the unused pytest import as a minor cleanup.

Sequence Diagram

sequenceDiagram
    participant Client as Anthropic SDK Client
    participant LiteLLM as LiteLLM /v1/messages
    participant Sanitizer as _sanitize_anthropic_messages_empty_text_blocks()
    participant Transform as transform_anthropic_messages_request()
    participant API as Upstream Provider (Anthropic / Bedrock / Vertex / Azure)

    Client->>LiteLLM: POST /v1/messages (messages with empty text blocks)
    Note over LiteLLM: async_anthropic_messages_handler()
    LiteLLM->>Sanitizer: messages (may contain {"type":"text","text":""})
    Sanitizer-->>LiteLLM: sanitized messages (empty text blocks stripped)
    LiteLLM->>Transform: sanitized messages
    Transform-->>LiteLLM: request_body
    LiteLLM->>API: POST (clean request body, no empty text blocks)
    API-->>LiteLLM: 200 OK (no 400 invalid_request_error)
    LiteLLM-->>Client: response
Loading

Last reviewed commit: 61e7ebb

Ref: https://github.com/BerriAI/litellm/issues/22930
"""

import pytest

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.

pytest is imported but never used. All test assertions use plain assert statements—no pytest.raises(), pytest.mark, or other pytest APIs are referenced. This unused import should be removed to prevent linting warnings.

Suggested change
import pytest

@MaxwellCalkin

Copy link
Copy Markdown
Contributor Author

I have read the CLA Document and I hereby sign the CLA

@ghost
ghost merged commit 2c738cc into BerriAI:main Mar 10, 2026
30 of 38 checks passed
fzowl pushed a commit to fzowl/litellm that referenced this pull request Jun 24, 2026
…I#23097)

Claude's API returns assistant messages with empty text blocks
({"type": "text", "text": ""}) alongside tool_use blocks during
multi-turn tool-use conversations. These blocks are rejected when
sent back to the API with "text content blocks must be non-empty".

Sanitization already exists for other code paths (/v1/chat/completions
for both Anthropic and Bedrock), but NOT for the /v1/messages native
path. This adds the same treatment by stripping empty text blocks
from messages in async_anthropic_messages_handler before they are
forwarded to the provider.

Fixes BerriAI#22930
fzowl pushed a commit to fzowl/litellm that referenced this pull request Jun 24, 2026
fzowl pushed a commit to fzowl/litellm that referenced this pull request Jun 24, 2026
This pull request was closed.
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]: /v1/messages endpoint does not sanitize empty text content blocks

2 participants