BerriAI/litellm · error · TypeError
Cannot normalize tool_call of type {type(tc).__name__}: {tc!
Error message
Cannot normalize tool_call of type {type(tc).__name__}: {tc!r} What it means
Rubrik's normalization converts a provider tool_call into ChatCompletionMessageToolCall. It handles dict-shaped tool calls ({id, function:{name,arguments}}) and object-shaped ones (has .id and .function attributes); anything else — a string, a list, an object without those attrs — hits the final raise TypeError with the offending value repr'd in the message.
Source
Thrown at litellm/integrations/rubrik.py:441
if isinstance(tc, ChatCompletionMessageToolCall):
return tc
if isinstance(tc, dict):
func: Final = tc.get("function") or _EMPTY_MAPPING
return ChatCompletionMessageToolCall(
id=tc.get("id", ""),
type=tc.get("type", "function"),
function=Function(
name=func.get("name", ""),
arguments=func.get("arguments", ""),
),
)
if hasattr(tc, "id") and hasattr(tc, "function"):
return ChatCompletionMessageToolCall(
id=tc.id or "",
type=getattr(tc, "type", None) or "function",
function=tc.function,
)
raise TypeError(f"Cannot normalize tool_call of type {type(tc).__name__}: {tc!r}")
@staticmethod
def _join_texts(texts: Sequence[str] | None) -> str:
"""Join response text segments into the single content string the
webhook evaluates. Empty when there is no assistant text."""
if not texts:
return ""
return "\n".join(t for t in texts if t)
@staticmethod
def _build_response_moderation_payload(
tool_calls: Sequence[ChatCompletionMessageToolCall],
content: str,
request_id: str | None,
) -> Mapping[str, object]:
"""Build an OpenAI ChatCompletion-format dict (assistant text + tool
calls) for the after_completion webhook.
View on GitHub (pinned to 6c2dcb801b)
Solutions
- Capture the repr from the error message to identify the actual type, then check which provider produced it
- Upgrade litellm — provider-specific tool_call normalization is actively patched
- Pre-normalize tool_calls before they reach the callback (map your shape to {id, type:'function', function:{name, arguments}})
- If it comes from a test fixture, fix the fixture to use a dict or ChatCompletionMessageToolCall
Example fix
# before (fixture / provider emits a bare string)
tool_calls = ["get_weather"] # TypeError: Cannot normalize tool_call of type str
# after
tool_calls = [{
"id": "call_1",
"type": "function",
"function": {"name": "get_weather", "arguments": "{\"city\": \"SF\"}"},
}] Defensive patterns
Strategy: type-guard
Validate before calling
def is_normalizable_tool_call(tc) -> bool:
if isinstance(tc, dict):
return isinstance(tc.get("function"), (dict, type(None)))
return hasattr(tc, "id") and hasattr(tc, "function")
tool_calls = [tc for tc in raw_tool_calls if is_normalizable_tool_call(tc)] Type guard
from typing import Any, TypeGuard
def is_dict_tool_call(tc: Any) -> TypeGuard[dict]:
return (
isinstance(tc, dict)
and isinstance(tc.get("function", {}), dict)
and "name" in tc.get("function", {})
) Try / catch
try:
normalized = RubrikSecurityLogger._normalize_tool_call(tc)
except TypeError as e:
if "Cannot normalize tool_call" in str(e):
logger.warning("skipping unshaped tool_call: %r", tc)
normalized = None
else:
raise Prevention
- Filter tool_calls through a shape guard before they reach third-party callbacks
- Keep litellm current when routing new/exotic providers — normalization gaps are patched quickly
- In tests, generate tool calls from the provider SDK's model classes instead of hand-built strings
When it happens
Trigger: A model/provider returns tool_calls entries in an unexpected schema: a bare string function name, a partially-initialized object, or a new provider whose tool call is a pydantic model with different field names; also fabricated fixtures in tests that use the wrong shape.
Common situations: Routing a non-OpenAI-compatible provider through the Rubrik moderation callback; LiteLLM version lag where a newly added provider's tool-call class is not yet normalized; mutating/serializing tool call objects (e.g. after a mock) so attribute access fails.
Related errors
- Setting user/encoding format is not supported by {custom_llm
- Missing expected key in embedding response: {e}
- tool call not supported: {tool_call}
- Chat provider: Invalid function argument delta {parsed_chunk
- Invalid completion response: no message found in choice
AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15).
Data as JSON: /api/errors/e5325b3acce4b294.
Report an issue: GitHub.