Function Calling with Claude vs GPT: Key Differences
Both Claude and GPT support structured function calling, but the mechanics, schema conventions, and response formats differ enough that porting code between them isn't a drop-in swap. If you're deciding which to build against, or maintaining both, the short answer is: Claude calls it "tool use," returns tool calls as content blocks inside a message, and expects results fed back as a specific tool_result content type. GPT calls it "function calling" (or the newer "tools" API), returns calls in a tool_calls array on the assistant message, and expects results as a role: "tool" message keyed by tool_call_id. The concepts map closely, but the JSON shapes and control flow are distinct enough to trip up anyone copy-pasting between SDKs.
This article compares the two side by side so you can pick the right mental model, or write an adapter layer if you support both.
How Claude Defines Tools
Claude's tool use API takes a tools array with a name, description, and input_schema (JSON Schema) for each tool:
{
"model": "claude-opus-4",
"tools": [
{
"name": "get_weather",
"description": "Get current weather for a location",
"input_schema": {
"type": "object",
"properties": {
"location": { "type": "string" },
"unit": { "type": "string", "enum": ["celsius", "fahrenheit"] }
},
"required": ["location"]
}
}
],
"messages": [{ "role": "user", "content": "What's the weather in Lisbon?" }]
}
When Claude decides to call a tool, it returns a message with stop_reason: "tool_use" and a content block of type tool_use containing an id, name, and input. You then send the result back as a user message containing a tool_result content block referencing that id:
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A...",
"content": "18°C, partly cloudy"
}
]
}
Claude can also emit multiple tool_use blocks in a single turn, meaning it can call more than one function before waiting for results, which is useful for parallel lookups.
How GPT Defines Functions
OpenAI's chat completions API uses a tools array too, but each entry wraps the schema under type: "function":
{
"model": "gpt-4o",
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather for a location",
"parameters": {
"type": "object",
"properties": {
"location": { "type": "string" },
"unit": { "type": "string", "enum": ["celsius", "fahrenheit"] }
},
"required": ["location"]
}
}
}
],
"messages": [{ "role": "user", "content": "What's the weather in Lisbon?" }]
}
GPT returns calls inside choices[0].message.tool_calls, an array of objects with id, type: "function", and function.arguments as a JSON string (not a parsed object — you must JSON.parse it yourself). You reply with a message of role: "tool", tool_call_id, and the result as content:
{
"role": "tool",
"tool_call_id": "call_abc123",
"content": "18°C, partly cloudy"
}
Key Differences That Actually Matter
Argument parsing. GPT gives you a JSON string for arguments that can occasionally be malformed on edge cases (truncated output, nested quotes). Claude's input field on tool_use blocks is already parsed JSON, so there's one less failure mode to guard against.
Multi-tool calls per turn. Claude naturally supports multiple tool_use blocks in one response. GPT also supports multiple tool_calls in one message, so both handle parallel calls — but the article-level nuance is in how forced tool choice works.
Forcing a specific tool. Claude uses tool_choice: {"type": "tool", "name": "get_weather"} to force a specific function call, or {"type": "any"} to force some tool use. GPT uses tool_choice: {"type": "function", "function": {"name": "get_weather"}}. Same idea, different nesting.
Streaming tool calls. Both APIs support streaming partial tool call arguments as tokens arrive, but the event shapes differ — Claude sends content_block_delta events with partial_json, GPT sends deltas on tool_calls[].function.arguments. If you're building a streaming UI that shows tool calls filling in live, expect to write separate parsers for each.
System prompts and tool descriptions. Claude tends to follow detailed tool descriptions and schema constraints (enums, required fields) more literally in practice, which matters if your tools have strict validation. Both models benefit from concrete descriptions and example values in the schema — vague descriptions produce vague or wrong calls on either platform.
A Practical Way to Handle Both
If you need to support Claude and GPT function calling behind one interface, normalize early: convert both response formats into a common { name, arguments, callId } shape right after the API call, and convert your tool results back into each provider's expected format right before sending. Don't try to make one schema serve both APIs directly — the wrapping (function.parameters vs input_schema) is different enough that a thin adapter is simpler than a shared schema hack.
If you're already calling Claude through SubToAPI, tool use works over a standard HTTPS endpoint with your sub_live_... key, so you get the same tool_use / tool_result flow described above without managing separate provider credentials. See the tools guide and messages reference for request and response shapes, or the quickstart to get a key running in a few minutes.
questions
Does Claude support parallel function calls like GPT? Yes. Claude can return multiple tool_use blocks in a single assistant turn, and you send back multiple tool_result blocks in one user message, similar to how GPT returns multiple entries in tool_calls.
Why are GPT's function arguments a string but Claude's are an object? It's an API design choice. OpenAI serializes function.arguments as a JSON string you must parse, while Claude's tool_use.input field is already a parsed JSON object in the response.
Can I force Claude or GPT to always call a tool instead of replying in text? Yes for both. Claude uses tool_choice: {"type": "any"} or a named tool; GPT uses tool_choice: {"type": "function", "function": {"name": "..."}} or "required" to force any tool call.