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

  1. Rebuild each tool_call as {"id", "type": "function", "function": {"name", "arguments"}} before passing messages to token_counter.
  2. 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 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


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