Skip to content

fix(anthropic): strip undocumented keys from metadata before sending to API#24661

Merged
yuneng-berri merged 1 commit intoBerriAI:mainfrom
Sameerlite:litellm_filter-metadata-user-id
Mar 27, 2026
Merged

fix(anthropic): strip undocumented keys from metadata before sending to API#24661
yuneng-berri merged 1 commit intoBerriAI:mainfrom
Sameerlite:litellm_filter-metadata-user-id

Conversation

@Sameerlite
Copy link
Copy Markdown
Collaborator

@Sameerlite Sameerlite commented Mar 27, 2026

Summary

The Anthropic Messages API only supports user_id as a field inside the metadata object (per official documentation). Passing any other key causes the API to return an error. The same constraint applies to Vertex AI Anthropic and Azure AI Anthropic, which share the same API contract.

This PR adds a final filter in AnthropicConfig.transform_request() that runs after all metadata sources are merged, ensuring only {"user_id": <value>} is forwarded — or metadata is dropped entirely if no user_id is present.

Why this is needed:

  • The Anthropic API throws an error if any key other than user_id is present in metadata
  • The official Anthropic documentation states only user_id is a valid field
  • Previously, if metadata entered optional_params via any path other than the litellm-params handler (e.g., direct Anthropic param pass-through), extra keys would be forwarded unfiltered

Providers covered: Anthropic, Vertex AI Anthropic, Azure AI Anthropic — all three inherit from AnthropicConfig, so the single fix covers all of them.

image image

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 27, 2026

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

Project Deployment Actions Updated (UTC)
litellm Ready Ready Preview, Comment Mar 27, 2026 3:14pm

Request Review

@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq bot commented Mar 27, 2026

Merging this PR will not alter performance

✅ 16 untouched benchmarks


Comparing Sameerlite:litellm_filter-metadata-user-id (38e8003) with main (88ed4f9)

Open in CodSpeed

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 27, 2026

Greptile Summary

This PR adds a post-merge metadata sanitization filter in AnthropicConfig.transform_request() that ensures only {"user_id": <value>} is forwarded to the Anthropic Messages API — or metadata is dropped entirely when no user_id is present — preventing API errors caused by undocumented keys. The fix is inherited by VertexAIAnthropicConfig and AzureAnthropicConfig at no extra cost.\n\n- Core fix (transformation.py lines 1424–1432): after all metadata sources are merged into optional_params, any key other than user_id is stripped; a missing user_id causes the entire metadata field to be removed.\n- Test coverage (test_anthropic_completion.py): six new mock-only tests verify the filter behaviour for plain Anthropic, Vertex AI Anthropic, and Azure AI Anthropic configs.\n- Open concerns from previous review threads (not repeated here): the new filter does not call _valid_user_id() before forwarding the value, so an email/phone passed directly via optional_params[\"metadata\"] bypasses the existing validation; and extra keys are dropped silently without a verbose_logger.warning() call that other similar paths in the file emit.

Confidence Score: 4/5

Safe to merge for the stated fix; two pre-existing concerns flagged in earlier review threads (missing _valid_user_id call and silent key-drop without a log warning) remain unresolved.

The filter logic is correct for the primary bug being fixed and is well-covered by the new mock tests. Score is 4 rather than 5 because the previously flagged P1 concern — that a user_id arriving via the direct optional_params path skips _valid_user_id() validation and will cause an Anthropic API error for email/phone values — has not been addressed in this revision.

litellm/llms/anthropic/chat/transformation.py lines 1428–1430: the new filter should call _valid_user_id(_user_id) before forwarding, consistent with the two other paths that populate metadata.user_id.

Important Files Changed

Filename Overview
litellm/llms/anthropic/chat/transformation.py Adds a post-merge metadata filter that strips all keys except user_id before sending to the Anthropic API; the filter does not call _valid_user_id() (already flagged in previous threads), meaning invalid values like emails can still be forwarded via the direct optional_params path.
tests/llm_translation/test_anthropic_completion.py Six new mock-only unit tests covering the new filter, inheritance through Vertex/Azure configs, and edge cases; one test (test_metadata_user_id_from_litellm_params_strips_extras) tests pre-existing extraction logic rather than the newly added filter.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[transform_request called] --> B{litellm_params.metadata has user_id?}
    B -- Yes and valid --> C[optional_params.metadata set to user_id only]
    B -- No or invalid --> D[optional_params.metadata unchanged]
    C --> E{metadata in optional_params?}
    D --> E
    E -- No --> F[skip filter]
    E -- Yes --> G{user_id key present in metadata?}
    G -- Yes --> H[NEW: strip all other keys, keep only user_id]
    G -- No --> I[NEW: pop metadata entirely]
    H --> J[pop is_vertex_request]
    I --> J
    F --> J
    J --> K[return final data dict]
Loading

Reviews (2): Last reviewed commit: "fix(anthropic): strip undocumented keys ..." | Re-trigger Greptile

Comment on lines +1428 to +1432
_user_id = optional_params["metadata"].get("user_id")
if _user_id is not None:
optional_params["metadata"] = {"user_id": _user_id}
else:
optional_params.pop("metadata")
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 Missing _valid_user_id validation creates inconsistency

The two existing paths that populate metadata.user_id both call _valid_user_id() to reject emails and phone numbers (Anthropic rejects those values — see the comment at line 1032: "anthropic fails on emails"). This new filter skips that check entirely.

If a caller passes metadata={"user_id": "[email protected]"} directly in optional_params, the new code will forward {"user_id": "[email protected]"} to Anthropic and receive an API error, while the same value arriving via the user parameter or litellm_params.metadata would be silently dropped.

Suggested change
_user_id = optional_params["metadata"].get("user_id")
if _user_id is not None:
optional_params["metadata"] = {"user_id": _user_id}
else:
optional_params.pop("metadata")
_user_id = optional_params["metadata"].get("user_id")
if _user_id is not None and _valid_user_id(str(_user_id)):
optional_params["metadata"] = {"user_id": _user_id}
else:
optional_params.pop("metadata")

Comment on lines +1424 to +1432
## Ensure metadata only contains user_id (only documented field in Anthropic Messages API)
if "metadata" in optional_params and isinstance(
optional_params["metadata"], dict
):
_user_id = optional_params["metadata"].get("user_id")
if _user_id is not None:
optional_params["metadata"] = {"user_id": _user_id}
else:
optional_params.pop("metadata")
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 Silent key stripping without a log warning

Extra metadata keys are dropped silently. A user who passes metadata={"user_id": "x", "trace_id": "y"} will see trace_id disappear with no indication of why. Other spots in this file already use litellm.verbose_logger.warning() for similar "we modified your input" situations (e.g. line 1366). Adding a warning here would make it much easier to debug.

Suggested change
## Ensure metadata only contains user_id (only documented field in Anthropic Messages API)
if "metadata" in optional_params and isinstance(
optional_params["metadata"], dict
):
_user_id = optional_params["metadata"].get("user_id")
if _user_id is not None:
optional_params["metadata"] = {"user_id": _user_id}
else:
optional_params.pop("metadata")
## Ensure metadata only contains user_id (only documented field in Anthropic Messages API)
if "metadata" in optional_params and isinstance(
optional_params["metadata"], dict
):
_user_id = optional_params["metadata"].get("user_id")
_extra_keys = [k for k in optional_params["metadata"] if k != "user_id"]
if _extra_keys:
litellm.verbose_logger.warning(
"Anthropic metadata only supports 'user_id'. "
"Dropping unsupported keys: %s",
_extra_keys,
)
if _user_id is not None:
optional_params["metadata"] = {"user_id": _user_id}
else:
optional_params.pop("metadata")

@yuneng-berri yuneng-berri merged commit 53ac4c5 into BerriAI:main Mar 27, 2026
40 of 41 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.

2 participants