n4nAI

How Gemini function calling differs from OpenAI's

A head-to-head technical comparison of Gemini and OpenAI function calling — schema differences, parallel calls, streaming behavior, tool choice modes, and when to use each.

n4n Team5 min read1,103 words

Audio narration

Coming soon — every post will get a voice note here.

If you’re building an agent or structured-output pipeline, the choice between Gemini function calling vs OpenAI function calling isn’t academic — it shapes your schema design, error handling, and latency profile. Both providers now support parallel invocation, streaming tool calls, and forced tool selection, but the implementation details diverge in ways that bite you in production. This breakdown covers the concrete differences so you can pick without guessing.

Schema definition and validation

OpenAI uses standard JSON Schema (Draft 2020-12 subset) for function parameters. Gemini uses its own FunctionDeclaration schema, which looks similar but enforces a stricter type system and lacks a few JSON Schema keywords.

OpenAI function definition:

{
  "name": "search_products",
  "description": "Search the product catalog",
  "parameters": {
    "type": "object",
    "properties": {
      "query": { "type": "string" },
      "filters": {
        "type": "object",
        "properties": {
          "category": { "type": "string", "enum": ["electronics", "apparel", "home"] },
          "price_max": { "type": "number" }
        },
        "additionalProperties": false
      },
      "limit": { "type": "integer", "minimum": 1, "maximum": 50 }
    },
    "required": ["query"],
    "additionalProperties": false
  }
}

Gemini function declaration:

{
  "name": "search_products",
  "description": "Search the product catalog",
  "parameters": {
    "type": "OBJECT",
    "properties": {
      "query": { "type": "STRING" },
      "filters": {
        "type": "OBJECT",
        "properties": {
          "category": { "type": "STRING", "enum": ["electronics", "apparel", "home"] },
          "price_max": { "type": "NUMBER" }
        }
      },
      "limit": { "type": "INTEGER" }
    },
    "required": ["query"]
  }
}

Key differences:

  • Gemini types are uppercase (STRING, NUMBER, INTEGER, BOOLEAN, ARRAY, OBJECT) not lowercase
  • No additionalProperties keyword in Gemini — extra keys are silently ignored at validation time
  • No minimum/maximum/pattern/format constraints in Gemini; you validate in your handler
  • Gemini requires required array but doesn’t enforce it as strictly during model generation

If you share schemas across providers, write a canonical JSON Schema and transform at the boundary. Don’t hand-maintain both.

Parallel and composite calls

Both providers now return multiple tool calls in a single response. The wire format differs.

OpenAI response (non-streaming):

{
  "choices": [{
    "message": {
      "role": "assistant",
      "tool_calls": [
        { "id": "call_abc123", "type": "function", "function": { "name": "search_products", "arguments": "{\"query\": \"wireless headphones\"}" }},
        { "id": "call_def456", "type": "function", "function": { "name": "get_inventory", "arguments": "{\"sku\": \"WH-1000XM5\"}" }}
      ]
    }
  }]
}

Gemini response (non-streaming):

{
  "candidates": [{
    "content": {
      "parts": [
        { "functionCall": { "name": "search_products", "args": { "query": "wireless headphones" }}},
        { "functionCall": { "name": "get_inventory", "args": { "sku": "WH-1000XM5" }}}
      ]
    }
  }]
}

OpenAI includes a tool_call_id you must echo back in the tool result message. Gemini uses the function name as the implicit key — you return a functionResponse part with the same name. This matters for your message builder: OpenAI requires a 1:1 mapping between tool_calls and tool role messages; Gemini matches by name and order.

Both support parallel execution in your handler. The latency win is real — two 200ms API calls in parallel beat 400ms sequential every time.

Streaming and partial results

Streaming tool calls arrive incrementally. OpenAI streams tool_calls as an array where each entry accumulates function.arguments as a JSON string fragment. Gemini streams functionCall parts where args arrives as a progressively complete JSON object.

OpenAI streaming chunk:

{
  "choices": [{
    "delta": {
      "tool_calls": [{
        "index": 0,
        "id": "call_abc123",
        "type": "function",
        "function": { "name": "search_products", "arguments": "{\"query\": \"wireless" }}
      }]
    }
  }]
}

Gemini streaming chunk:

{
  "candidates": [{
    "content": {
      "parts": [{ "functionCall": { "name": "search_products", "args": { "query": "wireless" }}}]
    }
  }]
}

Practical impact: with OpenAI you buffer string fragments and parse JSON only when finish_reason == "tool_calls". With Gemini you can validate args as a real object on each chunk — but beware that intermediate states may be invalid (missing required fields). Don’t execute until the final chunk.

Both providers let you stream the model’s text response after tool results are submitted. The pattern is identical: submit tool results, then stream the final answer.

Tool choice and forced invocation

OpenAI’s tool_choice parameter accepts "auto" (default), "none", "required", or a specific function object {"type": "function", "function": {"name": "my_func"}}. "required" forces some tool call but lets the model pick which.

Gemini uses tool_config.function_calling_config with mode: AUTO, ANY, NONE, and allowed_function_names to restrict the set.

// OpenAI: force a specific function
{ "tool_choice": { "type": "function", "function": { "name": "search_products" }} }

// Gemini: force any from allowed set
{
  "tool_config": {
    "function_calling_config": {
      "mode": "ANY",
      "allowed_function_names": ["search_products"]
    }
  }
}

Gemini’s ANY mode with a single allowed function is the closest equivalent to OpenAI’s forced function. OpenAI’s "required" (force any tool) maps to Gemini’s ANY without allowed_function_names.

One gotcha: OpenAI returns finish_reason: "tool_calls" when a tool is invoked. Gemini returns finishReason: "STOP" even when function calls are present — check parts for functionCall. Your routing logic must handle both.

Context handling and token accounting

OpenAI counts function definitions in the input token budget. The tools array adds ~200-500 tokens per function depending on schema complexity. Tool results (role: "tool" messages) also count against input tokens on the next turn.

Gemini counts function_declarations in the tools array similarly. However, Gemini’s functionResponse parts are not billed as input tokens on subsequent turns — only the initial declaration is. This can save 10-20% token spend on long agent loops with many tool rounds.

Both providers include tool call arguments and results in the context window. If you’re running multi-step agents, compress or summarize tool results before feeding them back. Neither provider does this automatically.

Provider-specific quirks and limits

Dimension OpenAI Gemini
Max parallel calls 128 (gpt-4o) 16 (gemini-1.5-pro), 8 (gemini-1.5-flash)
Max function schema depth Unlimited (practical ~10) 15 levels
Enum values per parameter 500 100
Function name length 64 chars 64 chars
Argument size limit 2 MB (gpt-4o) 1 MB
Streaming tool calls Yes (all models) Yes (1.5 Pro/Flash)
Structured output (JSON mode) response_format: {type: "json_object"} generation_config.response_mime_type: "application/json"
Vision + tools same request Yes (gpt-4o) Yes (1.5 Pro/Flash)
Audio + tools No No

Gemini’s lower parallel call limit matters for batch-heavy workloads (e.g., “fetch details for these 50 SKUs”). OpenAI’s higher ceiling lets you fan out more aggressively. On the flip side, Gemini Flash is significantly cheaper per token for high-volume tool-use workloads.

Both providers occasionally return malformed arguments (missing required fields, wrong types). Your handler must validate against the original schema before executing — don’t trust the model’s output blindly.

Comparison table

Capability OpenAI Gemini
Schema format JSON Schema (Draft 2020-12) FunctionDeclaration (proprietary)
Parallel calls Up to 128 Up to 16 (Pro), 8 (Flash)
Forced tool selection tool_choice: {function: {name}} mode: ANY, allowed_function_names: [...]
Force any tool tool_choice: "required" mode: ANY
Streaming tool calls String fragments (arguments) Object fragments (args)
Tool result token billing Counted every turn Only initial declaration counted
Enum limit 500 values 100 values
Max schema depth ~10 practical 15 enforced
JSON structured output response_format generation_config.response_mime_type
Vision + tools gpt-4o, gpt-4o-mini gemini-1.5-pro, gemini-1.5-flash
Typical latency (tool call) 150-300ms (gpt-4o) 200-400ms (1.5 Flash)
Cost per 1M input tokens $2.50 (gpt-4o), $0.15 (gpt-4o-mini) $1.25 (1.5 Pro), $0.075 (1.5 Flash)

Which to choose

Choose OpenAI when:

  • You need maximum parallel fan-out (dozens of simultaneous calls)
  • Your schemas rely on JSON Schema keywords (pattern, format, minimum, additionalProperties)
  • You’re already invested in the OpenAI SDK ecosystem and want minimal migration
  • You need the lowest latency on complex reasoning + tool use (gpt-4o edges out 1.5 Pro)
  • You require response_format: {type: "json_schema"} for strict structured output (Gemini’s response_mime_type is looser)

Choose Gemini when:

  • Cost per token is the primary driver — Flash is ~5x cheaper than gpt-4o-mini for tool-heavy workloads
  • You run long agent loops where tool result token billing adds up (Gemini doesn’t re-bill responses)
  • You need native multimodal (image/video/audio) input with tools in a single request
  • Your schemas are simple and you don’t need JSON Schema validation keywords
  • You’re building on Vertex AI and want unified billing, VPC-SC, and data residency controls

Hybrid approach (what we see in production): Route by task. Use gpt-4o-mini for high-fan-out, low-latency tool orchestration. Use Gemini 1.5 Flash for high-volume, cost-sensitive extraction and summarization pipelines where tool loops run 10+ turns. Keep a single abstraction layer in your code — normalize both providers to a common ToolCall / ToolResult interface — so you can swap or mix without rewriting business logic.

If you’re running a gateway that sits in front of both (like n4n.ai does), you can even implement automatic fallback: try the primary provider, and on rate limit or 5xx, retry the equivalent call on the secondary with the normalized schema. The schema translation is the only non-trivial part — everything else maps cleanly.

Tagsgeminifunction-callingopenaicomparison

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All function calling & tool use posts →