BerriAI/litellm · error · TypeError
Cannot normalize tool_call of type
Error message
Cannot normalize tool_call of type {type(tc).__name__}: {tc!r} What it means
To evaluate tool calls, Rubrik normalizes each item to ChatCompletionMessageToolCall, accepting either a dict containing 'function' (with name/arguments) or an object exposing .id and .function attributes; every other shape — a bare string, an Anthropic-style content block, a partial streaming chunk, a dataclass without those fields — hits the final raise as TypeError with the offending type name and repr.
Solutions
- Log the repr from the exception to identify the concrete type being passed
- Update litellm — normalization coverage for provider-specific tool-call shapes improves across versions
- As a workaround, exclude such models from the rubrik response-moderation (after_completion) mode until supported
- Post-process the response so tool calls are plain OpenAI dicts ({'id','type','function':{'name','arguments'}}) before guardrails see them
Example fix
# before (guardrail receives an Anthropic-style content block)
# TypeError: Cannot normalize tool_call of type ContentBlock: ...
# after (convert to OpenAI shape before the response reaches guardrails)
tool_calls = [
{'id': tc.id, 'type': 'function', 'function': {'name': tc.name, 'arguments': tc.input}}
for tc in response.content if getattr(tc, 'type', '') == 'tool_use'
] Defensive patterns
Strategy: type-guard
Validate before calling
def is_normalizable_tool_call(tc: object) -> bool:
if isinstance(tc, dict):
return isinstance(tc.get('function'), (dict,)) or 'function' in tc
return hasattr(tc, 'id') and hasattr(tc, 'function')
if not all(is_normalizable_tool_call(tc) for tc in tool_calls):
raise TypeError('tool_calls contain a shape the rubrik guardrail cannot normalize') Type guard
from typing import Any
def to_openai_tool_call(tc: Any) -> dict | None:
if isinstance(tc, dict) and 'function' in tc:
return tc
if hasattr(tc, 'id') and hasattr(tc, 'function'):
return {'id': tc.id or '', 'type': getattr(tc, 'type', 'function'), 'function': tc.function}
return None # caller decides: convert or skip moderation for this call Try / catch
try:
await rubrik.async_moderation(...)
except TypeError as e:
if 'Cannot normalize tool_call' in str(e):
logger.warning('skipping rubrik tool-call moderation for unsupported shape: %s', e)
else:
raise Prevention
- Normalize provider tool calls to OpenAI dicts before guardrail processing
- Pin a recent litellm version — normalization coverage expands over releases
- Log the offending repr once to identify which provider emits the unsupported shape
When it happens
Trigger: A provider or gateway returns tool calls in a non-OpenAI shape that reaches the Rubrik response-moderation path: string fragments of arguments, content-block objects, or custom objects lacking id/function; a router/custom post-processor mutating the response into an unusual structure before guardrails run.
Common situations: Using tools=... with the rubrik after_completion guardrail on providers whose tool-call representations differ from OpenAI's; middleware converting responses; litellm versions before broader normalization coverage.
Related errors
- returned non-dict JSON ( ); expected OpenAI chat completion…
- {blocked.explanation}
- {refusal}
- Rubrik webhook URL not configured. Set RUBRIK_WEBHOOK_URL…
- api_base is required for Generic Guardrail API. Set…
AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18).
Data as JSON: /api/errors/e5325b3acce4b294.
Report an issue: GitHub.
Appendix: 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 77b7c6c40c)