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

  1. Capture the repr from the error message to identify the actual type, then check which provider produced it
  2. Upgrade litellm — provider-specific tool_call normalization is actively patched
  3. Pre-normalize tool_calls before they reach the callback (map your shape to {id, type:'function', function:{name, arguments}})
  4. 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

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


AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15). Data as JSON: /api/errors/e5325b3acce4b294. Report an issue: GitHub.