fix(bedrock): filter out custom field from tools to prevent 400 errors#22861
Conversation
Claude Code v2.1.69+ sends `custom: {defer_loading: true}` on tool
definitions. Anthropic's API accepts this field, but Bedrock rejects it
with "Extra inputs are not permitted", causing ~90% of requests to fail.
Strip the `custom` field from each tool in the request body before
sending to Bedrock, in both the Messages API and Chat API invoke paths.
Fixes BerriAI#22847
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR fixes a regression where Claude Code v2.1.69+ began sending a
Minor observations:
Confidence Score: 4/5
|
| Filename | Overview |
|---|---|
| litellm/llms/bedrock/common_utils.py | Adds remove_custom_field_from_tools() utility that strips the top-level custom key from each tool dict in-place; handles None, empty list, and non-list gracefully. |
| litellm/llms/bedrock/chat/invoke_transformations/anthropic_claude3_transformation.py | Calls remove_custom_field_from_tools on the assembled Anthropic request body after the model/stream/output_format pops; placement is correct and feature-detection still reads from optional_params (unaffected). |
| litellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py | Mirrors the chat-path change; remove_custom_field_from_tools is inserted as step 5a between the output_format conversion and the beta-header auto-injection; beta/feature detection reads from anthropic_messages_optional_request_params so it is unaffected by the strip. |
| tests/test_litellm/llws/bedrock/messages/invoke_transformations/test_anthropic_claude3_transformation.py | Adds four unit-test cases for remove_custom_field_from_tools covering the happy path, missing key, empty list, and None value; all are mock-only with no network calls. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Incoming request\nwith tools incl. custom field] --> B{Bedrock invoke path?}
B -- Chat API --> C[AmazonAnthropicClaudeConfig\n.transform_request]
B -- Messages API --> D[AmazonAnthropicClaudeMessagesConfig\n.transform_request]
C --> E[AnthropicConfig.transform_request\nbuilds _anthropic_request]
E --> F[pop model / stream / output_format]
F --> G[remove_custom_field_from_tools\n_anthropic_request]
D --> H[AnthropicMessagesConfig.transform_request\nbuilds anthropic_messages_request]
H --> I[pop stream / model]
I --> J[_remove_ttl_from_cache_control]
J --> K[_convert_output_format_to_inline_schema]
K --> L[remove_custom_field_from_tools\nanthropic_messages_request]
G --> M[Beta-header injection\nreads from optional_params — unaffected]
L --> N[Beta-header injection\nreads from optional_request_params — unaffected]
M --> O[Request sent to Bedrock\nno custom field → 200 OK]
N --> O
Last reviewed commit: dedc50d
| assert request4["tools"] is None | ||
|
|
||
| def test_remove_scope_from_cache_control(): |
There was a problem hiding this comment.
Missing blank line between top-level test functions
PEP 8 (and ruff's E302) requires two blank lines between top-level function definitions. There is only one blank line between the end of test_remove_custom_field_from_tools and the start of test_remove_scope_from_cache_control.
| assert request4["tools"] is None | |
| def test_remove_scope_from_cache_control(): | |
| assert request4["tools"] is None | |
| def test_remove_scope_from_cache_control(): |
| # Remove `custom` field from tools (Bedrock doesn't support it) | ||
| # Claude Code sends `custom: {defer_loading: true}` on tool definitions, | ||
| # which causes Bedrock to reject the request with "Extra inputs are not permitted" | ||
| # Ref: https://github.com/BerriAI/litellm/issues/22847 | ||
| remove_custom_field_from_tools(_anthropic_request) |
There was a problem hiding this comment.
No test coverage for the chat invoke path
The PR adds a test for remove_custom_field_from_tools in isolation and exercises it through the messages invoke transformation, but there is no test that exercises the chat invoke path (AmazonAnthropicClaudeConfig.transform_request) with a request that carries a custom field on tools.
Because the two transformation classes are separate, a future refactor could accidentally drop or mis-order this call without any test catching it. Consider adding a parallel test (analogous to the messages path) that calls AmazonAnthropicClaudeConfig.transform_request with a tool carrying custom and asserts it is absent in the returned dict.
…ors (BerriAI#22861) Claude Code v2.1.69+ sends `custom: {defer_loading: true}` on tool definitions. Anthropic's API accepts this field, but Bedrock rejects it with "Extra inputs are not permitted", causing ~90% of requests to fail. Strip the `custom` field from each tool in the request body before sending to Bedrock, in both the Messages API and Chat API invoke paths. Fixes BerriAI#22847 Co-authored-by: Ishaan Jaff <ishaanjaffer0324@gmail.com>
…ors (BerriAI#22861) Claude Code v2.1.69+ sends `custom: {defer_loading: true}` on tool definitions. Anthropic's API accepts this field, but Bedrock rejects it with "Extra inputs are not permitted", causing ~90% of requests to fail. Strip the `custom` field from each tool in the request body before sending to Bedrock, in both the Messages API and Chat API invoke paths. Fixes BerriAI#22847 Co-authored-by: Ishaan Jaff <ishaanjaffer0324@gmail.com>
Summary
Fixes #22847
Claude Code v2.1.69+ sends
custom: {defer_loading: true}on tool definitions in the Anthropic/v1/messagesrequest. Anthropic's API accepts this field, but Bedrock rejects it with:This causes ~90% of requests to fail when proxying Claude Code through LiteLLM to Bedrock.
Changes
remove_custom_field_from_tools()utility inlitellm/llms/bedrock/common_utils.pythat strips thecustomfield from each tool dict in the request bodylitellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py) — step 5a in the Bedrock-specific transformation pipeline, alongside existingoutput_format/stream/modelpop logiclitellm/llms/bedrock/chat/invoke_transformations/anthropic_claude3_transformation.py) — afteroutput_formatpop, before beta header injectionTest plan
test_remove_custom_field_from_toolscovering:customfield → field is removed, other fields preservedtoolskey → no errortools: None→ no errorpytest tests/test_litellm/llms/bedrock/messages/invoke_transformations/— 4/4 passed)