Mapping Tool and Function Schemas Between APIs
10 min read · updated August 11, 2026
A tool definition is a name, a description and a JSON Schema. All four major shapes carry exactly those three things, and all four disagree about what to call the schema and how deep to bury it. The renames are mechanical; the schema dialect underneath them is not.
The same tool, four nesting levels
Here is one tool — look up the weather for a city — written out for each API. Read them for the nesting depth, which is the part that trips adapters.
OpenAI Chat Completions wraps the definition in a typed envelope:
"tools": [
{ "type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a city.",
"parameters": {
"type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"]
}
} }
]OpenAI’s Responses API keeps the type discriminator and flattens the envelope away:
"tools": [
{ "type": "function",
"name": "get_weather",
"description": "Get the current weather for a city.",
"parameters": {
"type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"]
} }
]Anthropic drops the discriminator entirely for custom tools — a bare object is a function tool — and renames the schema field:
"tools": [
{ "name": "get_weather",
"description": "Get the current weather for a city.",
"input_schema": {
"type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"]
} }
]Gemini adds a level rather than removing one. The top-level tools array holds objects, each of which holds an array of declarations:
"tools": [
{ "functionDeclarations": [
{ "name": "get_weather",
"description": "Get the current weather for a city.",
"parameters": {
"type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"]
} }
] }
]Same three pieces of information, four arrangements. The Gemini shape is the one that catches people, because tools is an array on every API and on this one its elements are not tools.
Which field name changes where
- The schema field.
parameterson both OpenAI shapes and on Gemini;input_schemaon Anthropic. This single rename is the most common cause of a translated tool being silently ignored, because a definition with an unrecognised extra key and no recognised schema key can be accepted as a tool that takes no arguments. - The envelope. Present and nested on Chat Completions, present and flat on Responses, absent on Anthropic, and replaced by a grouping array on Gemini.
- The deprecated ancestor. OpenAI’s older
functionsparameter and its companionfunction_callare deprecated in favour oftoolsandtool_choice. If you are mapping from a codebase old enough to use them, you are doing two migrations at once.
Forcing a tool call
The parameter that says “you must call a tool” is a small enumeration on each API and the enumerations do not use the same words.
OpenAI tool_choice: "none" | "auto" | "required"
| { "type": "function",
"function": { "name": "get_weather" } }
Anthropic tool_choice: { "type": "auto" }
| { "type": "any" }
| { "type": "none" }
| { "type": "tool", "name": "get_weather" }
Gemini toolConfig.functionCallingConfig.mode: "AUTO" | "ANY" | "NONE"
toolConfig.functionCallingConfig.allowedFunctionNames: [...]The concept that OpenAI spells required is spelled any by Anthropic and ANY by Gemini — three words for “some tool, your choice which”. Pinning a specific tool is an object on the first two and a list on the third, which means Gemini can express “one of these three” and the others cannot. That is a rare case of the mapping being lossy in the direction people do not expect. There is a parallel-call switch too, and it is inverted between providers: OpenAI has parallel_tool_calls, defaulting on, while Anthropic expresses the same preference as disable_parallel_tool_use inside tool_choice. Negating a boolean is easy to get wrong once and never notice.
The JSON Schema itself is not portable
Underneath all the renaming sits a JSON Schema, and this is where the mapping stops being mechanical. Each provider validates the schema against its own accepted subset, and the subsets differ.
Gemini’s parameters is documented as an OpenAPI 3.0 Schema subset rather than full JSON Schema, and the accepted keyword set is narrower than what a hand-written schema usually contains. Numeric and string constraints, complex composition keywords and recursive references are the usual casualties across providers generally: a schema that a validator in your own codebase accepts happily can be rejected by a provider that only implements part of the vocabulary.
The important thing about this failure is that it is not uniform. The same tool definition can be accepted by one provider, rejected with a schema error by a second, and accepted-but-ignored by a third, which is the worst of the three outcomes because nothing tells you. If you are building an adapter, validate the schema against the narrowest target you support at definition time rather than at call time, so the failure lands in your own tests instead of in a user’s conversation. The authoritative list of what each provider accepts is the provider’s own function-calling reference — for the current Gemini subset, Google’s function calling documentation is the primary source.
Strict mode is a contract, not a flag
Both OpenAI and Anthropic expose a strict boolean on a tool definition that guarantees the arguments the model produces will validate against your schema. It is spelled the same on both, which makes it look like the easiest field on this page to map. It is not, because turning it on imposes requirements on the schema itself.
A strict schema must set additionalProperties to false and must list its properties in required. A schema written without those constraints is a valid tool definition with strict off and a 400 with strict on. So the flag does not map field-to-field: turning it on during a migration means rewriting every schema that has an optional property, usually by making the property required and allowing null. An adapter that copies strict: true across without checking the schema shape produces requests that fail at definition time on the target — which is at least loud, and preferable to the alternative of dropping the flag and quietly losing the guarantee your parser was relying on.
For the mechanism underneath all of this — what a tool call actually is and why the model never executes anything — see tool calling explained. What comes back on the wire when a tool call streams is a different shape again on each provider, covered in the streaming event mapping.