The Same Tool, Three Schemas: How Claude, OpenAI, and Gemini Each Define Function Calling

Rooftop of a data center building, representing cloud AI infrastructure

Claude, OpenAI, and Gemini all let a model call a function you define, and all three describe that function with a small JSON object — but the object is not the same shape in any two of them. Copy a tool definition from one provider’s docs into another provider’s SDK and it will usually fail validation, not because the idea is different, but because the field names, the nesting, and what counts as required all differ. Below is the same tool written three times, straight from each provider’s own reference, with the differences that actually bite laid out in one table.

The same tool, three ways to write it down

Take a simple tool that looks up the weather for a city. Anthropic’s Messages API wraps the schema in a field called input_schema:

{
  "name": "get_weather",
  "description": "Get the current weather for a given location.",
  "input_schema": {
    "type": "object",
    "properties": {
      "location": {
        "type": "string",
        "description": "City and state, e.g. San Francisco, CA"
      }
    },
    "required": ["location"]
  }
}

OpenAI’s function-calling format nests the same information one level deeper, inside a function object, and tags the whole thing with "type": "function":

{
  "type": "function",
  "function": {
    "name": "get_weather",
    "description": "Get the current weather for a given location.",
    "parameters": {
      "type": "object",
      "properties": {
        "location": {
          "type": "string",
          "description": "City and state, e.g. San Francisco, CA"
        }
      },
      "required": ["location"]
    }
  }
}

Gemini’s function declaration drops the outer wrapper entirely and calls the argument object parameters, using the same type / properties / required shape as the other two but documented as a constrained subset of the OpenAPI Schema object rather than full JSON Schema:

{
  "name": "get_weather",
  "description": "Get the current weather for a given location.",
  "parameters": {
    "type": "object",
    "properties": {
      "location": {
        "type": "string",
        "description": "City and state, e.g. San Francisco, CA"
      }
    },
    "required": ["location"]
  }
}

The inner object describing location is identical in all three. That is the trap: the part people copy-paste (the properties block) is portable, and the part that is not (the wrapper key, and what the wrapper is called) is exactly the part that is easy to overlook when adapting code from one provider’s example to another’s SDK.

What actually differs, side by side

Anthropic ClaudeOpenAIGemini
Top-level wrapperNone — the tool object is passed as-is{"type": "function", "function": {...}}None — the function declaration is passed as-is
Field holding the schemainput_schemaparameters (inside function)parameters
Schema formatJSON Schema-style (type/properties/required); the overview doesn’t commit to full JSON Schema supportJSON Schema, with an optional strict flag for guaranteed conformanceA documented subset of the OpenAPI 3.0 Schema object, not JSON Schema
Guaranteed schema conformancestrict: true on the tool definitionstrict: true alongside additionalProperties: false for strict modeNot offered as a named flag in the function-calling guide
Turning off parallel tool callstool_choice: {"type": "auto", "disable_parallel_tool_use": true} asks for at most one tool call per turnDocumented separately per model version; not a single universal flag in the schema itselfDocumented separately per model version; not part of the function declaration

The strict-mode row is worth sitting with: OpenAI’s strict mode is described alongside additionalProperties: false, which means every object in the schema needs that field set explicitly or strict validation can reject the call — a requirement that doesn’t exist at all in Anthropic’s or Gemini’s plain (non-strict) schema format, so a schema that works fine against one provider’s default mode can fail outright the moment you turn on another provider’s strict mode without adjusting it.

Where it gets harder than a flat object

All three examples above use a single string property, which is where the formats look almost interchangeable. The gap widens with anything more complex than that:

  • Enums work the same way syntactically ("enum": [...] on a string property) across all three, so this is one of the safer parts of a schema to share verbatim.
  • Nested objects and arrays of objects are standard JSON Schema in Anthropic’s and OpenAI’s formats, but Gemini documents its schema as a constrained OpenAPI subset, which historically has not tracked every JSON Schema keyword — check the current function-calling reference for the exact list of supported keywords before assuming a deeply nested schema will validate unchanged.
  • Optional vs. required fields are marked with a required array in all three, but what happens when a field is omitted differs: strict modes expect every property to be listed somewhere (as required or explicitly nullable), while non-strict modes are more forgiving about a model simply not filling in an optional field.

A short portability checklist

If the same tool needs to run against more than one of these APIs — common once a project adds a model router or compares providers on cost, as covered in what an AI model router is — keep a provider-agnostic version of each tool’s properties and required block, and generate the three wrapper shapes from it rather than hand-maintaining three near-identical JSON files that quietly drift apart:

  • Store name, description, and the inner schema object once, and wrap it per provider at request time instead of duplicating the whole definition.
  • If you turn on strict validation for one provider, audit the schema for additionalProperties and fully-specified required fields specifically for that provider; don’t assume the same schema is strict-safe everywhere.
  • Test the exact error a malformed schema produces on each provider once, up front — a missing wrapper key tends to fail with a generic 400 rather than a message that names the field, which is faster to recognize the second time.
  • Re-check the provider’s current reference before shipping a schema with nested objects or arrays; schema support is one of the areas most likely to have changed since you last read the docs.

Why this is worth getting right before it matters

A tool schema is the contract a model is reasoning against when it decides whether, and how, to call your function. Get the wrapper wrong and the request fails before the model even sees it; get the inner schema subtly wrong for one provider’s strict mode and the failure shows up later, as a rejected call in production rather than an error in testing. For the broader question of why models sometimes call tools in parallel and what that changes about error handling, see how multiple AI agents collaborate on complex tasks, and for a look at how far ahead some providers now let a model plan before calling anything at all, see interleaved thinking in Claude’s API. If the tool-calling decision is part of a larger comparison that includes cost, rate limits and pricing tiers across major AI APIs covers the other half of that trade-off, and what AI reasoning models change about output is useful background for why a model picks one tool call over several.

A worked example: adding a second property changes more than the properties block

Go one step past the single-string example and the three formats stop looking like minor variations on each other. Add a second, optional property — a unit for the temperature — and the question of what “optional” means has to be answered the same way in the schema and in the code that reads the model’s response:

"properties": {
  "location": { "type": "string", "description": "City and state" },
  "unit": {
    "type": "string",
    "enum": ["celsius", "fahrenheit"],
    "description": "Temperature unit to return"
  }
},
"required": ["location"]

This inner block is the part that is genuinely portable: the enum keyword, the nesting, and the required array all mean the same thing whether it ends up inside Anthropic’s input_schema, OpenAI’s parameters, or Gemini’s parameters. What changes per provider is what happens on the calling side once unit is left out: non-strict modes simply omit the key from the model’s generated arguments, so code that reads the response needs a default rather than assuming the key exists, while OpenAI’s strict mode pushes toward declaring every property as required and modelling “the caller skipped this” with a nullable type instead of an absent key. Porting a schema between providers without checking which convention the target expects is a common source of a KeyError or undefined that only shows up once real traffic starts omitting the optional field.

The same caution applies to arrays of objects — a list of line items, say, instead of a single location. The shape ("type": "array", "items": {"type": "object", "properties": {...}}) is standard enough to expect it to work everywhere, but because Gemini’s parameters are documented as a constrained subset of the OpenAPI Schema object rather than full JSON Schema, a keyword that is valid JSON Schema (certain conditional or combinator keywords, for instance) is not guaranteed to be supported just because it validates against a general-purpose JSON Schema linter. The only reliable check is running the exact schema against the exact provider you intend to call, not against a generic validator.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top