BerriAI/litellm · error · ValueError
Unsupported tool call {tool_call} must contain a function ke
Error message
Unsupported tool call {tool_call} must contain a function key What it means
Each entry in an assistant message's tool_calls list must contain a 'function' key ({"function": {"name", "arguments"}}). The counter found a tool_call dict without it, so there are no arguments to count; the malformed tool_call is echoed in the error.
Source
Thrown at litellm/litellm_core_utils/token_counter.py:423
value: Any,
message: Mapping[str, Any],
count_function: TokenCounterFunction,
) -> int:
"""
Count tokens contributed by an assistant message's tool/function call payload.
Handles both the modern `tool_calls` list and the legacy OpenAI
`function_call` dict. Only the `arguments` string is counted (matching the
existing tool_calls behavior); names are accounted for elsewhere via the
tool/function definitions and `tool_choice`.
"""
if key == "tool_calls":
if not isinstance(value, list):
raise ValueError(f"Unsupported type {type(value)} for key tool_calls in message {message}")
total = 0
for tool_call in value:
if "function" not in tool_call:
raise ValueError(f"Unsupported tool call {tool_call} must contain a function key")
function_arguments = tool_call["function"].get("arguments", "")
total += count_function(str(function_arguments))
return total
if key == "function_call":
if not isinstance(value, Mapping):
raise ValueError(f"Unsupported type {type(value)} for key function_call in message {message}")
return count_function(str(value.get("arguments", "")))
raise ValueError(f"Unexpected key {key!r}; expected 'tool_calls' or 'function_call'")
def _count_messages(
params: _MessageCountParams,
messages: list[AllMessageValues],
use_default_image_token_count: bool,
default_token_count: int | None,
) -> int:
"""
Count the number of tokens in a list of messages.View on GitHub (pinned to 6c2dcb801b)
Solutions
- Rebuild each tool_call as {"id", "type": "function", "function": {"name", "arguments"}} before passing messages to token_counter.
- If the entry came from an unfinished stream delta, filter out entries lacking 'function' before persisting.
Example fix
# before
tool_call = {"id": "call_1", "type": "function", "name": "get_weather", "arguments": "{}"}
# after
tool_call = {"id": "call_1", "type": "function",
"function": {"name": "get_weather", "arguments": "{}"}} Defensive patterns
Strategy: type-guard
Validate before calling
clean = [tc for tc in (msg.get("tool_calls") or []) if isinstance(tc, dict) and "function" in tc]
msg["tool_calls"] = clean
n = litellm.token_counter(model=m, messages=msgs) Type guard
def is_complete_tool_call(tc) -> bool:
return isinstance(tc, dict) and isinstance(tc.get("function"), dict) and "name" in tc["function"] Prevention
- When assembling tool_calls from streaming deltas, persist only after the stream's finish_reason arrives.
- Validate at write time (DB/queue), not at count time.
When it happens
Trigger: tool_calls entries like {"id":"1","type":"function"} (function omitted); entries built from an incomplete streaming delta that never finished; agent frameworks writing name/arguments at the top level instead of nested under 'function'.
Common situations: Reconstructing tool_calls from accumulated streaming deltas and persisting an unfinished final delta; saving LLM output as tool_calls without schema validation; framework versions that flattened the function object.
Related errors
- Unsupported type {type(value)} for key tool_calls in message
- Unknown Anthropic content type: '{content_type}'
- Invalid template message type: {type(template_message)}
- WebSearchInterception: missing follow-up messages
- text and messages cannot both be set
AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15).
Data as JSON: /api/errors/6284f6b8065923ae.
Report an issue: GitHub.