litellm - 💡(How to fix) Fix bug: Guardrail pre_call_hook crashes with KeyError: 'name' on /v1/messages endpoint when tools are present [1 comments, 2 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#23852Fetched 2026-04-08 00:48:46
View on GitHub
Comments
1
Participants
2
Timeline
4
Reactions
0
Timeline (top)
renamed ×2commented ×1labeled ×1

Error Message

File "/app/litellm/proxy/anthropic_endpoints/endpoints.py", line 53, in anthropic_response result = await base_llm_response_processor.base_process_llm_request(...)

File "/app/litellm/proxy/common_request_processing.py", line 837, in base_process_llm_request self.data, logging_obj = await self.common_processing_pre_call_logic(...)

File "/app/litellm/proxy/common_request_processing.py", line 695, in common_processing_pre_call_logic self.data = await proxy_logging_obj.pre_call_hook( user_api_key_dict=user_api_key_dict, data=self.data, call_type=route_type )

File "/app/litellm/proxy/utils.py", line 1351, in pre_call_hook result = await self._process_guardrail_callback(...)

File "/app/litellm/proxy/utils.py", line 1019, in _process_guardrail_callback response = await self._execute_guardrail_hook(...)

File "/app/litellm/proxy/utils.py", line 894, in _execute_guardrail_hook return await target.async_pre_call_hook(...)

File "/app/litellm/proxy/guardrails/guardrail_hooks/unified_guardrail/unified_guardrail.py", line 124, in async_pre_call_hook data = await endpoint_translation.process_input_messages(...)

File "/app/litellm/llms/anthropic/chat/guardrail_translation/handler.py", line 79, in process_input_messages LiteLLMAnthropicMessagesAdapter().translate_anthropic_to_openai( anthropic_message_request=cast(AnthropicMessagesRequest, data.copy()) )

File "/app/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py", line 910, in translate_anthropic_to_openai new_kwargs["tools"], tool_name_mapping = self.translate_anthropic_tools_to_openai( tools=cast(List[AllAnthropicToolsValues], regular_tools), model=new_kwargs.get("model"), )

File "/app/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py", line 736, in translate_anthropic_tools_to_openai original_name = tool["name"] ~~~~^^^^^^^^ KeyError: 'name'

Root Cause

In guardrail_translation/handler.py:79, process_input_messages() passes data.copy() (the full proxy-enriched request dict) to translate_anthropic_to_openai().

At this point in the proxy pipeline, data contains not just the original Anthropic request fields, but also proxy-internal fields added during preprocessing (e.g., litellm_call_id, proxy_server_request, litellm_logging_obj, applied_guardrails, etc.).

Inside translate_anthropic_to_openai() (line 167-170 of adapters/transformation.py):

model = kwargs.pop("model")
messages = kwargs.pop("messages")
request_body = AnthropicMessagesRequest(model=model, messages=messages, **kwargs)

The remaining **kwargs includes all proxy-internal fields. Since AnthropicMessagesRequest is a TypedDict(total=False), these extra fields are silently accepted. When the tools field from this enriched dict is later iterated in translate_anthropic_tools_to_openai(), the proxy-internal data interferes — some items in the tools iteration path lack the expected name key, causing the KeyError.

Fix Action

Workaround

Removing the guardrails: section from the LiteLLM config resolves the issue. However, this is not a viable long-term solution as it removes all content safety protections (prompt injection detection, credential leak prevention, harmful content filtering).

As an alternative, setting default_on: false on all guardrails and selectively enabling them only on /v1/chat/completions routes (where they work correctly) may preserve some protection, but leaves the /v1/messages endpoint unguarded.

Code Example

guardrails:
  - guardrail_name: "content-guard"
    litellm_params:
      guardrail: litellm_content_filter
      mode: "pre_call"
      default_on: true
      content_categories:
        harmful_violence:
          action: BLOCK
          severity: high

---

curl -s -X POST 'http://localhost:4000/v1/messages' \
  -H 'Content-Type: application/json' \
  -H 'x-litellm-api-key: sk-...' \
  -H 'anthropic-version: 2023-06-01' \
  -d '{
    "model": "claude-sonnet-4-6",
    "max_tokens": 100,
    "tools": [{"name": "get_weather", "description": "Get weather", "input_schema": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}}],
    "messages": [{"role": "user", "content": "What is the weather in London?"}]
  }'

---

File "/app/litellm/proxy/anthropic_endpoints/endpoints.py", line 53, in anthropic_response
    result = await base_llm_response_processor.base_process_llm_request(...)

File "/app/litellm/proxy/common_request_processing.py", line 837, in base_process_llm_request
    self.data, logging_obj = await self.common_processing_pre_call_logic(...)

File "/app/litellm/proxy/common_request_processing.py", line 695, in common_processing_pre_call_logic
    self.data = await proxy_logging_obj.pre_call_hook(
        user_api_key_dict=user_api_key_dict, data=self.data, call_type=route_type
    )

File "/app/litellm/proxy/utils.py", line 1351, in pre_call_hook
    result = await self._process_guardrail_callback(...)

File "/app/litellm/proxy/utils.py", line 1019, in _process_guardrail_callback
    response = await self._execute_guardrail_hook(...)

File "/app/litellm/proxy/utils.py", line 894, in _execute_guardrail_hook
    return await target.async_pre_call_hook(...)

File "/app/litellm/proxy/guardrails/guardrail_hooks/unified_guardrail/unified_guardrail.py", line 124, in async_pre_call_hook
    data = await endpoint_translation.process_input_messages(...)

File "/app/litellm/llms/anthropic/chat/guardrail_translation/handler.py", line 79, in process_input_messages
    LiteLLMAnthropicMessagesAdapter().translate_anthropic_to_openai(
        anthropic_message_request=cast(AnthropicMessagesRequest, data.copy())
    )

File "/app/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py", line 910, in translate_anthropic_to_openai
    new_kwargs["tools"], tool_name_mapping = self.translate_anthropic_tools_to_openai(
        tools=cast(List[AllAnthropicToolsValues], regular_tools),
        model=new_kwargs.get("model"),
    )

File "/app/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py", line 736, in translate_anthropic_tools_to_openai
    original_name = tool["name"]
                    ~~~~^^^^^^^^
KeyError: 'name'

---

model = kwargs.pop("model")
messages = kwargs.pop("messages")
request_body = AnthropicMessagesRequest(model=model, messages=messages, **kwargs)
RAW_BUFFERClick to expand / collapse

Bug Description

When guardrails are configured with default_on: true and a request to /v1/messages includes tools, the unified guardrail's async_pre_call_hook crashes with KeyError: 'name' before the request reaches the LLM provider. This affects all tool-bearing requests on the Anthropic /v1/messages passthrough endpoint.

Requests without tools, and all requests via /v1/chat/completions, work correctly with the same guardrail configuration.

LiteLLM Version

v1.82.0.patch5 (Docker image: ghcr.io/berriai/litellm:v1.82.0.patch5)

Steps to Reproduce

  1. Configure any guardrail with mode: "pre_call" and default_on: true:
guardrails:
  - guardrail_name: "content-guard"
    litellm_params:
      guardrail: litellm_content_filter
      mode: "pre_call"
      default_on: true
      content_categories:
        harmful_violence:
          action: BLOCK
          severity: high
  1. Send a /v1/messages request with tools:
curl -s -X POST 'http://localhost:4000/v1/messages' \
  -H 'Content-Type: application/json' \
  -H 'x-litellm-api-key: sk-...' \
  -H 'anthropic-version: 2023-06-01' \
  -d '{
    "model": "claude-sonnet-4-6",
    "max_tokens": 100,
    "tools": [{"name": "get_weather", "description": "Get weather", "input_schema": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}}],
    "messages": [{"role": "user", "content": "What is the weather in London?"}]
  }'
  1. Response: HTTP 500: {"error":{"message":"'name'","type":"None","param":"None","code":"500"}}

Expected Behavior

The guardrail should scan the message content and allow the request through to the LLM provider (or block it if content violates rules). Tool definitions should not cause a crash.

Actual Behavior

The request fails with 500: 'name' before reaching the LLM provider. The same request succeeds when:

  • Guardrails are removed from config
  • Tools are removed from the request
  • The request is sent via /v1/chat/completions instead

Full Traceback

File "/app/litellm/proxy/anthropic_endpoints/endpoints.py", line 53, in anthropic_response
    result = await base_llm_response_processor.base_process_llm_request(...)

File "/app/litellm/proxy/common_request_processing.py", line 837, in base_process_llm_request
    self.data, logging_obj = await self.common_processing_pre_call_logic(...)

File "/app/litellm/proxy/common_request_processing.py", line 695, in common_processing_pre_call_logic
    self.data = await proxy_logging_obj.pre_call_hook(
        user_api_key_dict=user_api_key_dict, data=self.data, call_type=route_type
    )

File "/app/litellm/proxy/utils.py", line 1351, in pre_call_hook
    result = await self._process_guardrail_callback(...)

File "/app/litellm/proxy/utils.py", line 1019, in _process_guardrail_callback
    response = await self._execute_guardrail_hook(...)

File "/app/litellm/proxy/utils.py", line 894, in _execute_guardrail_hook
    return await target.async_pre_call_hook(...)

File "/app/litellm/proxy/guardrails/guardrail_hooks/unified_guardrail/unified_guardrail.py", line 124, in async_pre_call_hook
    data = await endpoint_translation.process_input_messages(...)

File "/app/litellm/llms/anthropic/chat/guardrail_translation/handler.py", line 79, in process_input_messages
    LiteLLMAnthropicMessagesAdapter().translate_anthropic_to_openai(
        anthropic_message_request=cast(AnthropicMessagesRequest, data.copy())
    )

File "/app/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py", line 910, in translate_anthropic_to_openai
    new_kwargs["tools"], tool_name_mapping = self.translate_anthropic_tools_to_openai(
        tools=cast(List[AllAnthropicToolsValues], regular_tools),
        model=new_kwargs.get("model"),
    )

File "/app/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py", line 736, in translate_anthropic_tools_to_openai
    original_name = tool["name"]
                    ~~~~^^^^^^^^
KeyError: 'name'

Root Cause Analysis

In guardrail_translation/handler.py:79, process_input_messages() passes data.copy() (the full proxy-enriched request dict) to translate_anthropic_to_openai().

At this point in the proxy pipeline, data contains not just the original Anthropic request fields, but also proxy-internal fields added during preprocessing (e.g., litellm_call_id, proxy_server_request, litellm_logging_obj, applied_guardrails, etc.).

Inside translate_anthropic_to_openai() (line 167-170 of adapters/transformation.py):

model = kwargs.pop("model")
messages = kwargs.pop("messages")
request_body = AnthropicMessagesRequest(model=model, messages=messages, **kwargs)

The remaining **kwargs includes all proxy-internal fields. Since AnthropicMessagesRequest is a TypedDict(total=False), these extra fields are silently accepted. When the tools field from this enriched dict is later iterated in translate_anthropic_tools_to_openai(), the proxy-internal data interferes — some items in the tools iteration path lack the expected name key, causing the KeyError.

Environment

  • LiteLLM version: v1.82.0.patch5
  • Backend provider: vertex_ai (Anthropic partner models via Vertex AI)
  • Guardrail type: litellm_content_filter with pre_call mode
  • Endpoint: /v1/messages
  • Python: 3.13

Workaround

Removing the guardrails: section from the LiteLLM config resolves the issue. However, this is not a viable long-term solution as it removes all content safety protections (prompt injection detection, credential leak prevention, harmful content filtering).

As an alternative, setting default_on: false on all guardrails and selectively enabling them only on /v1/chat/completions routes (where they work correctly) may preserve some protection, but leaves the /v1/messages endpoint unguarded.

extent analysis

Fix Plan

To resolve the KeyError: 'name' issue in the unified guardrail's async_pre_call_hook, we need to ensure that only the original Anthropic request fields are passed to translate_anthropic_to_openai(). We can achieve this by filtering out proxy-internal fields before creating the AnthropicMessagesRequest object.

Here are the concrete steps:

  • Modify the process_input_messages() function in guardrail_translation/handler.py to filter out proxy-internal fields:
def process_input_messages(self, data):
    # Filter out proxy-internal fields
    original_request_fields = {
        "model": data.get("model"),
        "messages": data.get("messages"),
        "tools": data.get("tools"),
        # Add other original Anthropic request fields as needed
    }
    return LiteLLMAnthropicMessagesAdapter().translate_anthropic_to_openai(
        anthropic_message_request=cast(AnthropicMessagesRequest, original_request_fields)
    )
  • Alternatively, modify the translate_anthropic_to_openai() function in adapters/transformation.py to ignore proxy-internal fields when creating the AnthropicMessagesRequest object:
def translate_anthropic_to_openai(self, anthropic_message_request):
    # Ignore proxy-internal fields
    original_request_fields = {
        key: value for key, value in anthropic_message_request.items()
        if key in ["model", "messages", "tools"]  # Add other original Anthropic request fields as needed
    }
    request_body = AnthropicMessagesRequest(**original_request_fields)
    # ...

Verification

To verify that the fix worked, send a /v1/messages request with tools and check that the response does not contain a 500: 'name' error. You can use the same curl command provided in the issue description:

curl -s -X POST 'http://localhost:4000/v1/messages' \
  -H 'Content-Type: application/json' \
  -H 'x-litellm-api-key: sk-...' \
  -H 'anthropic-version: 2023-06-01' \
  -d '{
    "model": "claude-sonnet-4-6",
    "max_tokens": 100,
    "tools": [{"name": "get_weather", "description": "Get weather", "input_schema": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}}],
    "messages": [{"role": "user", "content": "What is the weather in London?"}]
  }'

Extra Tips

To prevent similar issues in the future, consider adding input validation and filtering to ensure that only expected fields are passed to sensitive functions. Additionally, review the

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

litellm - 💡(How to fix) Fix bug: Guardrail pre_call_hook crashes with KeyError: 'name' on /v1/messages endpoint when tools are present [1 comments, 2 participants]