{"record":{"id":"eb3c8a8ae3bdca2f","repo":"BerriAI/litellm","slug":"unsupported-type-type-value-for-key-tool-calls","errorCode":null,"errorMessage":"Unsupported type {type(value)} for key tool_calls in message {message}","messagePattern":"Unsupported type (.+?) for key tool_calls in message (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"litellm/litellm_core_utils/token_counter.py","lineNumber":419,"sourceCode":"\n\ndef _count_function_call_tokens(\n    key: str,\n    value: Any,\n    message: Mapping[str, Any],\n    count_function: TokenCounterFunction,\n) -> int:\n    \"\"\"\n    Count tokens contributed by an assistant message's tool/function call payload.\n\n    Handles both the modern `tool_calls` list and the legacy OpenAI\n    `function_call` dict. Only the `arguments` string is counted (matching the\n    existing tool_calls behavior); names are accounted for elsewhere via the\n    tool/function definitions and `tool_choice`.\n    \"\"\"\n    if key == \"tool_calls\":\n        if not isinstance(value, list):\n            raise ValueError(f\"Unsupported type {type(value)} for key tool_calls in message {message}\")\n        total = 0\n        for tool_call in value:\n            if \"function\" not in tool_call:\n                raise ValueError(f\"Unsupported tool call {tool_call} must contain a function key\")\n            function_arguments = tool_call[\"function\"].get(\"arguments\", \"\")\n            total += count_function(str(function_arguments))\n        return total\n    if key == \"function_call\":\n        if not isinstance(value, Mapping):\n            raise ValueError(f\"Unsupported type {type(value)} for key function_call in message {message}\")\n        return count_function(str(value.get(\"arguments\", \"\")))\n    raise ValueError(f\"Unexpected key {key!r}; expected 'tool_calls' or 'function_call'\")\n\n\ndef _count_messages(\n    params: _MessageCountParams,\n    messages: list[AllMessageValues],\n    use_default_image_token_count: bool,","sourceCodeStart":401,"sourceCodeEnd":437,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/litellm_core_utils/token_counter.py#L401-L437","documentation":"While counting an assistant message's tool_calls payload, the value under the 'tool_calls' key is not a list. The OpenAI schema requires tool_calls to be a list of {id, type, function} objects; anything else (dict, string, None) cannot be iterated, so the counter raises with the offending type and the full message echoed.","triggerScenarios":"An assistant message with tool_calls as a dict instead of a list (e.g. {\"role\":\"assistant\",\"tool_calls\":{\"function\":...}}), tool_calls=null, or a message where tool_calls was serialized to a JSON string before reaching token_counter or cost calculation.","commonSituations":"Hand-constructed agent conversation histories; messages round-tripped through a queue/DB that mutated the shape; third-party frameworks emitting a single tool-call object instead of a list; None tool_calls on the final assistant message.","solutions":["Normalize to a list: always a list of tool-call objects (wrap a lone dict, drop None).","Fix the producer: validate tool_calls against the OpenAI schema before persisting messages."],"exampleFix":"# before\nmsg = {\"role\": \"assistant\", \"tool_calls\": {\"id\": \"1\", \"function\": {\"name\": \"get_weather\", \"arguments\": \"{}\"}}}\n\n# after\nmsg = {\"role\": \"assistant\", \"tool_calls\": [{\"id\": \"1\", \"type\": \"function\", \"function\": {\"name\": \"get_weather\", \"arguments\": \"{}\"}}]}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"def is_valid_tool_calls(value) -> bool:\n    return value is None or (isinstance(value, list) and all(\n        isinstance(tc, dict) and \"function\" in tc for tc in value))","tryCatchPattern":"try:\n    n = litellm.token_counter(model=m, messages=msgs)\nexcept ValueError as e:\n    if \"tool_calls\" in str(e):\n        msgs = normalize_tool_calls(msgs)  # wrap dict->list, drop None\n        n = litellm.token_counter(model=m, messages=msgs)\n    else:\n        raise","preventionTips":["Validate assistant tool_calls against the OpenAI schema before persisting to history.","Wrap lone tool-call dicts in a list at the point of construction."],"tags":["token-counter","tool-calls","validation","schema"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}