litellm - ✅(Solved) Fix [Bug]: Guardrail pre_call leaks tool type into input_schema, breaking /v1/messages with Bedrock [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
BerriAI/litellm#24913Fetched 2026-04-08 02:23:47
View on GitHub
Comments
0
Participants
1
Timeline
2
Reactions
0
Author
Participants
Timeline (top)
cross-referenced ×1labeled ×1

Root Cause

In translate_anthropic_tools_to_openai() (litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py), the extra-params loop copies every unmapped key from the Anthropic tool into function.parameters:

mapped_tool_params = ["name", "input_schema", "description", "cache_control"]

for k, v in tool.items():
    if k not in mapped_tool_params:  # "type" is NOT excluded
        function_chunk.setdefault("parameters", {}).update({k: v})

Since "type" is not in mapped_tool_params, the tool-level type: "custom" gets written into parameters, overwriting input_schema.type from "object" to "custom".

Additionally, line 818 assigns input_schema by reference (no copy):

function_chunk["parameters"] = tool["input_schema"]  # shared reference

So the update({k: v}) on line 824 mutates the original input_schema dict. Due to the shallow copy in the guardrail handler (data.copy() at handler.py:83), this mutation propagates to the original request data.

Even though _map_tool_helper() (in chat/transformation.py) has a fix to normalize type: "object", the during_call guardrail hook can re-trigger the translation concurrently, re-corrupting the tools.

Fix Action

Fix

Two changes in translate_anthropic_tools_to_openai():

  1. Add "type" to mapped_tool_params so it's excluded from the extra-params loop
  2. Use copy.copy(tool["input_schema"]) instead of direct reference assignment to prevent mutation

PR fix notes

PR #24914: fix(anthropic): prevent tool type leaking into input_schema during guardrail translation

Description (problem / solution / changelog)

Summary

  • Add "type" to mapped_tool_params in translate_anthropic_tools_to_openai() to prevent tool-level type:"custom" from being written into function.parameters (overwrites input_schema.type)
  • Use copy.copy() on input_schema to prevent shallow-copy mutation of original request data
  • Add 2 regression tests

Context

When using /v1/messages with guardrails (pre_call mode) routing to Bedrock, the guardrail translation loop copies every unmapped key from the Anthropic tool into parameters. Since "type" was not excluded, type:"custom" overwrote input_schema.type from "object" to "custom", causing Anthropic to reject with: tools.0.custom.input_schema.type: Input should be 'object'

Fixes https://github.com/BerriAI/litellm/issues/24913

Test plan

  • New test: test_custom_tool_type_not_leaked_into_parameters
  • New test: test_input_schema_not_mutated_by_translation
  • All existing tests pass

Changed files

  • litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py (modified, +2/-2)
  • tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py (modified, +60/-0)

Code Example

tools.0.custom.input_schema.type: Input should be 'object'

---

mapped_tool_params = ["name", "input_schema", "description", "cache_control"]

for k, v in tool.items():
    if k not in mapped_tool_params:  # "type" is NOT excluded
        function_chunk.setdefault("parameters", {}).update({k: v})

---

function_chunk["parameters"] = tool["input_schema"]  # shared reference

---

tools.0.custom.input_schema.type: Input should be 'object'
RAW_BUFFERClick to expand / collapse

Bug Description

When using the /v1/messages (Anthropic Messages API) endpoint with guardrails configured (pre_call mode) and routing to Bedrock, the request fails with:

tools.0.custom.input_schema.type: Input should be 'object'

Root Cause

In translate_anthropic_tools_to_openai() (litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py), the extra-params loop copies every unmapped key from the Anthropic tool into function.parameters:

mapped_tool_params = ["name", "input_schema", "description", "cache_control"]

for k, v in tool.items():
    if k not in mapped_tool_params:  # "type" is NOT excluded
        function_chunk.setdefault("parameters", {}).update({k: v})

Since "type" is not in mapped_tool_params, the tool-level type: "custom" gets written into parameters, overwriting input_schema.type from "object" to "custom".

Additionally, line 818 assigns input_schema by reference (no copy):

function_chunk["parameters"] = tool["input_schema"]  # shared reference

So the update({k: v}) on line 824 mutates the original input_schema dict. Due to the shallow copy in the guardrail handler (data.copy() at handler.py:83), this mutation propagates to the original request data.

Even though _map_tool_helper() (in chat/transformation.py) has a fix to normalize type: "object", the during_call guardrail hook can re-trigger the translation concurrently, re-corrupting the tools.

Reproduction

  1. Configure LiteLLM proxy with:

    • Model: bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0
    • Guardrail: generic_guardrail_api with mode: [pre_call, post_call] and default_on: true
  2. Send a request via /v1/messages with custom tools that have type: "custom" and input_schema

  3. Observe: Guardrail passes, but the LLM call fails with 400:

    tools.0.custom.input_schema.type: Input should be 'object'
  4. Disable guardrails → request succeeds

Fix

Two changes in translate_anthropic_tools_to_openai():

  1. Add "type" to mapped_tool_params so it's excluded from the extra-params loop
  2. Use copy.copy(tool["input_schema"]) instead of direct reference assignment to prevent mutation

LiteLLM Version

Tested on v1.82.6-nightly. The bug exists in the latest main branch.

Environment

  • LiteLLM Proxy (Docker)
  • Model: Bedrock Claude Sonnet 4.5
  • Client: Claude Agent SDK v0.2.72 (sends requests via /v1/messages)
  • Guardrail: Pillar Security (generic_guardrail_api)

Related

  • Commit 45ba9e1f (preserve native tool format) partially addressed tool corruption but missed the type leak
  • Commit 78159212 (enforce type:'object') fixes _map_tool_helper but doesn't prevent the mutation at source

extent analysis

TL;DR

To fix the issue, update the translate_anthropic_tools_to_openai() function to exclude the "type" parameter from the extra-params loop and use a copy of the input_schema to prevent mutation.

Guidance

  • Add "type" to the mapped_tool_params list to prevent overwriting input_schema.type with "custom".
  • Use copy.copy(tool["input_schema"]) instead of direct reference assignment to prevent mutation of the original input_schema dict.
  • Verify the fix by sending a request via /v1/messages with custom tools that have type: "custom" and input_schema and checking that the LLM call succeeds.
  • Consider reviewing the guardrail handler to ensure that the shallow copy of the request data does not propagate mutations.

Example

mapped_tool_params = ["name", "input_schema", "description", "cache_control", "type"]

for k, v in tool.items():
    if k not in mapped_tool_params:  
        function_chunk.setdefault("parameters", {}).update({k: v})

function_chunk["parameters"] = copy.copy(tool["input_schema"])  # prevent mutation

Notes

The provided fix assumes that the issue is solely caused by the mutation of the input_schema dict and the overwriting of input_schema.type. However, the presence of concurrent guardrail hooks and the complexity of the system may introduce additional factors that need to be considered.

Recommendation

Apply the workaround by updating the translate_anthropic_tools_to_openai() function as described, as this directly addresses the identified root cause of the issue.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING