{"record":{"id":"0dd34a3107edd9c6","repo":"BerriAI/litellm","slug":"unable-to-convert-openai-tool-calls-message-to-g","errorCode":null,"errorMessage":"Unable to convert openai tool calls={message} to gemini tool calls. Received error={e}","messagePattern":"Unable to convert openai tool calls=(.+?) to gemini tool calls\\. Received error=(.+?)","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"litellm/litellm_core_utils/prompt_templates/factory.py","lineNumber":1360,"sourceCode":"                )\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()\n\n                if thought_signature:\n                    part_dict_function[\"thoughtSignature\"] = thought_signature\n\n                _parts_list.append(part_dict_function)\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 - {message}\"\n                )\n        return _parts_list\n    except Exception as e:\n        raise Exception(f\"Unable to convert openai tool calls={message} to gemini tool calls. Received error={e}\")\n\n\ndef convert_to_gemini_tool_call_result(\n    message: ChatCompletionToolMessage | ChatCompletionFunctionMessage,\n    last_message_with_tool_calls: dict | None,\n    forward_function_call_id: bool = False,\n) -> VertexPartType | list[VertexPartType]:\n    \"\"\"\n    OpenAI message with a tool result looks like:\n    {\n        \"tool_call_id\": \"tool_1\",\n        \"role\": \"tool\",\n        \"content\": \"function result goes here\",\n    },\n\n    # NOTE: Function messages have been deprecated\n    OpenAI message with a function call result looks like:\n    {","sourceCodeStart":1342,"sourceCodeEnd":1378,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/litellm_core_utils/prompt_templates/factory.py#L1342-L1378","documentation":"Catch-all wrapper around convert_to_gemini_tool_call: any exception raised while converting your OpenAI tool calls to Gemini format (including the 'function_call missing' errors) is re-raised as this Exception with the original message and error embedded. It signals that the assistant tool-call portion of your messages is not convertible to Vertex/Gemini's functionCall part shape.","triggerScenarios":"Any malformed assistant tool_calls/function_call entry (missing function.name, wrong types) sent to gemini/* or vertex_ai/* models; unexpected dict shapes after JSON (de)serialization of histories.","commonSituations":"Agent loops replaying stored or hand-built assistant messages to Gemini; cross-provider history migration; upstream models emitting non-standard tool call fields that got persisted.","solutions":["Read the trailing 'Received error=' clause — it contains the specific converter failure (usually function_call missing)","Repair the tool call entries so each has type 'function' and a function dict with a non-empty name and JSON-string arguments","Drop the corrupted assistant tool-call turn and regenerate it if it cannot be repaired"],"exampleFix":"# before\nassistant_msg = {\"role\": \"assistant\", \"tool_calls\": json.loads(db_row.tool_calls)}  # rows may be malformed\nlitellm.completion(model=\"gemini/gemini-1.5-pro\", messages=[*history, assistant_msg])\n\n# after\nassistant_msg = {\"role\": \"assistant\", \"tool_calls\": json.loads(db_row.tool_calls)}\nassistant_msg[\"tool_calls\"] = [\n    tc for tc in assistant_msg[\"tool_calls\"]\n    if isinstance(tc.get(\"function\"), dict) and tc[\"function\"].get(\"name\")\n]\nassert assistant_msg[\"tool_calls\"], \"no valid tool calls to replay\"\nlitellm.completion(model=\"gemini/gemini-1.5-pro\", messages=[*history, assistant_msg])","handlingStrategy":"try-catch","validationCode":"def gemini_replayable(messages) -> bool:\n    for m in messages:\n        if m.get(\"role\") == \"assistant\":\n            for tc in m.get(\"tool_calls\") or []:\n                fn = tc.get(\"function\") if isinstance(tc, dict) else None\n                if not (isinstance(fn, dict) and fn.get(\"name\")):\n                    return False\n            fc = m.get(\"function_call\")\n            if fc is not None and not (isinstance(fc, dict) and fc.get(\"name\")):\n                return False\n    return True","typeGuard":null,"tryCatchPattern":"try:\n    resp = litellm.completion(model=\"gemini/gemini-2.0-flash\", messages=messages, tools=tools)\nexcept Exception as e:\n    if \"Unable to convert openai tool calls\" in str(e):\n        messages = [m for m in messages if message_tool_calls_valid(m)]  # drop/repair bad turns\n        resp = litellm.completion(model=\"gemini/gemini-2.0-flash\", messages=messages, tools=tools)\n    else:\n        raise","preventionTips":["Sanitize stored tool-call history before replaying to Gemini","Log the embedded 'Received error=' clause to find the exact bad entry","Regenerate corrupted assistant turns instead of forwarding them"],"tags":["gemini","vertex","tool-calling","conversion"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}