{"record":{"id":"2afb5e59e2485cd9","repo":"BerriAI/litellm","slug":"function-call-missing-received-tool-call-with-ty","errorCode":null,"errorMessage":"function_call missing. Received tool call with 'type': 'function'. No function call in argument - {tool}","messagePattern":"function_call missing\\. Received tool call with 'type': 'function'\\. No function call in argument - (.+?)","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"litellm/litellm_core_utils/prompt_templates/factory.py","lineNumber":1330,"sourceCode":"            VertexGeminiConfig,\n        )\n\n        if tool_calls is not None:\n            for idx, tool in enumerate(tool_calls):\n                if \"function\" in tool:\n                    gemini_function_call: VertexFunctionCall | None = _gemini_tool_call_invoke_helper(\n                        function_call_params=tool[\"function\"],\n                        tool_call_id=(tool.get(\"id\") if forward_function_call_id else None),\n                    )\n                    if gemini_function_call is not None:\n                        part_dict: VertexPartType = {\"function_call\": gemini_function_call}\n                        thought_signature = _get_thought_signature_from_tool(dict(tool), model=model)\n                        if thought_signature:\n                            part_dict[\"thoughtSignature\"] = thought_signature\n\n                        _parts_list.append(part_dict)\n                    else:  # don't silently drop params. Make it clear to user what's happening.\n                        raise Exception(\n                            f\"function_call missing. Received tool call with 'type': 'function'. No function call in argument - {tool}\"\n                        )\n        elif function_call is not None:\n            gemini_function_call = _gemini_tool_call_invoke_helper(function_call_params=function_call)\n            if gemini_function_call is not None:\n                part_dict_function: Final[VertexPartType] = {\"function_call\": gemini_function_call}\n\n                # Extract thought signature from function_call's provider_specific_fields\n                thought_signature = None\n                provider_fields: Final = (\n                    function_call.get(\"provider_specific_fields\") if isinstance(function_call, dict) else {}\n                )\n                if isinstance(provider_fields, dict):\n                    thought_signature = provider_fields.get(\"thought_signature\")\n\n                # If no signature found and model is gemini-3, use dummy signature\n                if not thought_signature and model and VertexGeminiConfig._is_gemini_3_or_newer(model):\n                    thought_signature = _get_dummy_thought_signature()","sourceCodeStart":1312,"sourceCodeEnd":1348,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/litellm_core_utils/prompt_templates/factory.py#L1312-L1348","documentation":"While converting an OpenAI tool_calls list to Gemini format, a tool entry had type 'function' but its `function` payload (name/arguments) was missing or unusable, so the helper returned None. LiteLLM deliberately raises instead of silently dropping the malformed tool call. The whole message is included so you can see which entry is bad.","triggerScenarios":"An assistant message with tool_calls=[{\"id\":..., \"type\":\"function\", \"function\": None}] or function lacking a 'name'; tool calls reconstructed from logs/DB where the function dict was dropped; calling Vertex/Gemini models after resuming an agent conversation with corrupted tool-call history.","commonSituations":"Replaying persisted conversations where JSON round-trips dropped nested function objects; hand-crafted assistant tool_calls in tests; upstream provider returned a degenerate tool call that was stored verbatim.","solutions":["Inspect the offending entry in the error message and fix its 'function' dict to include a valid 'name' (and 'arguments')","If you build tool_calls yourself, always include {\"type\":\"function\",\"function\":{\"name\":..., \"arguments\":\"{}\"}}","Sanitize stored histories: drop or repair tool_calls entries whose function/name is falsy before sending to Gemini"],"exampleFix":"# before\nhistory.append({\n    \"role\": \"assistant\",\n    \"tool_calls\": [{\"id\": \"call_1\", \"type\": \"function\", \"function\": {}}],\n})\nlitellm.completion(model=\"gemini/gemini-2.0-flash\", messages=history, tools=tools)\n\n# after\nhistory.append({\n    \"role\": \"assistant\",\n    \"tool_calls\": [{\"id\": \"call_1\", \"type\": \"function\",\n                     \"function\": {\"name\": \"get_weather\", \"arguments\": \"{\\\"city\\\": \\\"SF\\\"}\"}}],\n})","handlingStrategy":"validation","validationCode":"def tool_calls_are_well_formed(tool_calls) -> bool:\n    for tc in tool_calls or []:\n        fn = tc.get(\"function\") if isinstance(tc, dict) else None\n        if not isinstance(fn, dict) or not fn.get(\"name\"):\n            return False\n    return True\n\nfor m in messages:\n    if m.get(\"role\") == \"assistant\" and not tool_calls_are_well_formed(m.get(\"tool_calls\")):\n        raise ValueError(f\"malformed tool_calls in assistant message: {m}\")","typeGuard":"from typing import Any\n\ndef is_valid_tool_call(tc: Any) -> bool:\n    return (\n        isinstance(tc, dict)\n        and tc.get(\"type\") == \"function\"\n        and isinstance(tc.get(\"function\"), dict)\n        and bool(tc[\"function\"].get(\"name\"))\n    )","tryCatchPattern":null,"preventionTips":["Always include function.name and arguments when constructing assistant tool_calls","Validate persisted histories after JSON round-trips","Never replay tool calls whose function dict is empty"],"tags":["gemini","vertex","tool-calling","history"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}