Skip to content

fix: sanitize empty text content blocks in /v1/messages endpoint#22933

Merged
krrishdholakia merged 1 commit intoBerriAI:litellm_oss_staging_03_06_2026from
shaeberling:fix/sanitize-empty-text-blocks-v1-messages
Mar 7, 2026
Merged

fix: sanitize empty text content blocks in /v1/messages endpoint#22933
krrishdholakia merged 1 commit intoBerriAI:litellm_oss_staging_03_06_2026from
shaeberling:fix/sanitize-empty-text-blocks-v1-messages

Conversation

@shaeberling
Copy link

Summary

  • Sanitize empty text content blocks in the /v1/messages endpoint before forwarding to providers
  • Fixes 400 errors ("text content blocks must be non-empty") in multi-turn tool-use conversations

Problem

The Anthropic API returns assistant messages with empty text blocks alongside tool_use blocks:

{"type": "text", "text": ""},
{"type": "tool_use", "id": "toolu_xxx", "name": "Bash", "input": {...}}

While the API returns these, it rejects them when sent back in subsequent requests. The /anthropic passthrough handles this transparently, but the /v1/messages native path (used for Anthropic direct and Bedrock) passes messages through without sanitization.

The /v1/chat/completions path already handles this via process_empty_text_blocks() in factory.py, but the /v1/messages path was missing equivalent sanitization. modify_params=True does not help as it targets a different code path.

Fix

Add _sanitize_anthropic_messages() at the entry point of anthropic_messages() in the /v1/messages handler, before any routing. This strips empty/whitespace-only text blocks from message content arrays while preserving non-empty text and other block types (tool_use, tool_result, etc.). Does not leave content arrays empty to avoid a different validation error.

Testing

  • 7 unit tests covering: empty text + tool_use, whitespace-only, non-empty preservation, single empty block (no modification), string content, and user messages
  • Verified fix on production deployment with Claude Code / Agent SDK multi-turn tool-use conversations routed through /v1/messages to both Anthropic direct and Bedrock Converse

Fixes #22930

@vercel
Copy link

vercel bot commented Mar 5, 2026

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

Project Deployment Actions Updated (UTC)
litellm Ready Ready Preview, Comment Mar 5, 2026 11:16pm

Request Review

@CLAassistant
Copy link

CLAassistant commented Mar 5, 2026

CLA assistant check
All committers have signed the CLA.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 5, 2026

Greptile Summary

This PR adds _sanitize_anthropic_messages() to the /v1/messages handler to strip empty and whitespace-only text content blocks from messages before they are forwarded to providers. This fixes 400 errors ("text content blocks must be non-empty") that occur in multi-turn tool-use conversations where the Anthropic API returns—but won't accept back—empty text blocks alongside tool_use blocks.

Key implementation details:

  • _sanitize_anthropic_messages() is inserted at the entry point of anthropic_messages(), before any routing or hook execution, ensuring all provider paths (Anthropic direct, Bedrock, etc.) benefit.
  • Handles None text values safely via (block.get("text") or "").strip().
  • Creates shallow copies of affected message dicts to avoid mutating caller's original message objects.
  • When all content blocks in a message are empty text, falls back to DEFAULT_ASSISTANT_CONTINUE_MESSAGE, preventing a different validation error from an empty content array.
  • Comprehensive test coverage with 8 unit tests covering empty text + tool_use, whitespace-only text, non-empty preservation, all-empty fallback, string content, user messages, and None text values.

Confidence Score: 5/5

  • PR is safe to merge. The fix is targeted, well-tested, and handles all identified edge cases correctly.
  • All logic is correct. The _sanitize_anthropic_messages() function properly filters empty/whitespace-only text blocks while preserving non-empty content and other block types (tool_use, tool_result, etc.). Defensive guards against None text values work correctly. When all blocks are empty, the function correctly falls back to DEFAULT_ASSISTANT_CONTINUE_MESSAGE to prevent validation errors. Tests are comprehensive and properly mocked with no real network calls. Both changed files are clean and the implementation is consistent with existing codebase patterns.
  • No files require special attention

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["Client sends /v1/messages request\n(multi-turn tool-use conversation)"] --> B["anthropic_messages()"]
    B --> C["_sanitize_anthropic_messages(messages)"]
    C --> D{"For each message:\ncontent is a list?"}
    D -- No --> E["Pass through unchanged"]
    D -- Yes --> F["Filter: remove blocks where\ntype=text AND text is empty/whitespace"]
    F --> G{"Any blocks\nremoved?"}
    G -- No --> E
    G -- Yes --> H{"filtered list\nnon-empty?"}
    H -- Yes --> I["Replace message with shallow copy\n{**message, content: filtered}"]
    H -- No --> J["Replace message with\ncontinuation message\n(DEFAULT_ASSISTANT_CONTINUE_MESSAGE)"]
    I --> K["Return sanitized messages list"]
    J --> K
    E --> K
    K --> L["_execute_pre_request_hooks()"]
    L --> M["Route to provider\n(Anthropic direct / Bedrock / etc.)"]
Loading

Last reviewed commit: e8e2a27

@shaeberling shaeberling force-pushed the fix/sanitize-empty-text-blocks-v1-messages branch from 60adc81 to 7fe6a21 Compare March 5, 2026 23:02
The `/v1/messages` endpoint does not sanitize empty text content blocks
from messages, causing 400 errors ("text content blocks must be
non-empty") in multi-turn tool-use conversations.

Claude's API returns assistant messages with empty text blocks alongside
`tool_use` blocks (e.g., `{"type": "text", "text": ""}`). While the API
returns these, it rejects them when sent back in subsequent requests.

The `/v1/chat/completions` path already handles this via
`process_empty_text_blocks()` in `factory.py`, but the `/v1/messages`
path was missing equivalent sanitization.

Fix: Add `_sanitize_anthropic_messages()` at the entry point of the
`/v1/messages` handler, before any routing. This strips empty/whitespace
text blocks from message content arrays while preserving non-empty text
and other block types (`tool_use`, `tool_result`, etc.). The function
does not leave content arrays empty to avoid a different validation error.

This affects all providers routed through `/v1/messages` (Anthropic
direct, Bedrock Converse, Azure, etc.) and is particularly important for
Claude Code / Agent SDK conversations which frequently produce these
patterns.

Fixes BerriAI#22930
@shaeberling shaeberling force-pushed the fix/sanitize-empty-text-blocks-v1-messages branch from ad47c19 to e8e2a27 Compare March 5, 2026 23:14
dsteeley added a commit to dsteeley/litellm that referenced this pull request Mar 6, 2026
…s fallback

Strict OpenAI-compatible models (e.g. Kimi-K2.5, gpt-oss-120b on Azure AI)
reject messages whose content array contains {"type": "text", "text": ""}.
These empty parts are produced by the Responses API -> chat/completions
transformation when an assistant turn contains a tool call alongside an
empty text block.

Add _strip_empty_text_content_parts() to handler.py and call it in both
the sync (response_api_handler) and async (async_response_api_handler)
paths before litellm.completion() / litellm.acompletion(). The helper is
a no-op when no empty parts are present (returns the original dict).

Related to PR BerriAI#22933 which fixes the same class of bug in the /v1/messages
path.
@krrishdholakia krrishdholakia changed the base branch from main to litellm_oss_staging_03_06_2026 March 7, 2026 01:52
@krrishdholakia krrishdholakia merged commit a4fe061 into BerriAI:litellm_oss_staging_03_06_2026 Mar 7, 2026
28 of 38 checks passed
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

3 participants