BerriAI/litellm · error · Exception

Unable to convert openai tool calls={message} to gemini tool

Error message

Unable to convert openai tool calls={message} to gemini tool calls. Received error={e}

What it means

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.

Source

Thrown at litellm/litellm_core_utils/prompt_templates/factory.py:1360

                )
                if isinstance(provider_fields, dict):
                    thought_signature = provider_fields.get("thought_signature")

                # If no signature found and model is gemini-3, use dummy signature
                if not thought_signature and model and VertexGeminiConfig._is_gemini_3_or_newer(model):
                    thought_signature = _get_dummy_thought_signature()

                if thought_signature:
                    part_dict_function["thoughtSignature"] = thought_signature

                _parts_list.append(part_dict_function)
            else:  # don't silently drop params. Make it clear to user what's happening.
                raise Exception(
                    f"function_call missing. Received tool call with 'type': 'function'. No function call in argument - {message}"
                )
        return _parts_list
    except Exception as e:
        raise Exception(f"Unable to convert openai tool calls={message} to gemini tool calls. Received error={e}")


def convert_to_gemini_tool_call_result(
    message: ChatCompletionToolMessage | ChatCompletionFunctionMessage,
    last_message_with_tool_calls: dict | None,
    forward_function_call_id: bool = False,
) -> VertexPartType | list[VertexPartType]:
    """
    OpenAI message with a tool result looks like:
    {
        "tool_call_id": "tool_1",
        "role": "tool",
        "content": "function result goes here",
    },

    # NOTE: Function messages have been deprecated
    OpenAI message with a function call result looks like:
    {

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Read the trailing 'Received error=' clause — it contains the specific converter failure (usually function_call missing)
  2. Repair the tool call entries so each has type 'function' and a function dict with a non-empty name and JSON-string arguments
  3. Drop the corrupted assistant tool-call turn and regenerate it if it cannot be repaired

Example fix

# before
assistant_msg = {"role": "assistant", "tool_calls": json.loads(db_row.tool_calls)}  # rows may be malformed
litellm.completion(model="gemini/gemini-1.5-pro", messages=[*history, assistant_msg])

# after
assistant_msg = {"role": "assistant", "tool_calls": json.loads(db_row.tool_calls)}
assistant_msg["tool_calls"] = [
    tc for tc in assistant_msg["tool_calls"]
    if isinstance(tc.get("function"), dict) and tc["function"].get("name")
]
assert assistant_msg["tool_calls"], "no valid tool calls to replay"
litellm.completion(model="gemini/gemini-1.5-pro", messages=[*history, assistant_msg])
Defensive patterns

Strategy: try-catch

Validate before calling

def gemini_replayable(messages) -> bool:
    for m in messages:
        if m.get("role") == "assistant":
            for tc in m.get("tool_calls") or []:
                fn = tc.get("function") if isinstance(tc, dict) else None
                if not (isinstance(fn, dict) and fn.get("name")):
                    return False
            fc = m.get("function_call")
            if fc is not None and not (isinstance(fc, dict) and fc.get("name")):
                return False
    return True

Try / catch

try:
    resp = litellm.completion(model="gemini/gemini-2.0-flash", messages=messages, tools=tools)
except Exception as e:
    if "Unable to convert openai tool calls" in str(e):
        messages = [m for m in messages if message_tool_calls_valid(m)]  # drop/repair bad turns
        resp = litellm.completion(model="gemini/gemini-2.0-flash", messages=messages, tools=tools)
    else:
        raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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