Claude Function Calling vs OpenAI Functions Compared
If you're choosing between Claude's tool use and OpenAI's function calling, the short answer is: they solve the same problem — letting a model call your code with structured arguments — but the request/response shapes, multi-step behavior, and parallel-call handling differ enough that a direct port between SDKs will break. This article walks through the concrete differences so you can pick the right one or migrate without surprises.
Both APIs let you define a set of callable "tools" or "functions" with a JSON Schema for arguments, send that schema alongside a user message, and get back a structured call the model wants to make instead of (or alongside) plain text. The model never executes anything — your backend runs the function and sends the result back in the next turn. That core loop is identical. Everything below is where they diverge.
Schema definition: similar JSON Schema, different field names
OpenAI's function definitions live under a tools array with type: "function" and a nested function object:
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather for a city",
"parameters": {
"type": "object",
"properties": {
"city": { "type": "string" }
},
"required": ["city"]
}
}
}
Claude flattens this — no nested function key, and parameters is renamed input_schema:
{
"name": "get_weather",
"description": "Get current weather for a city",
"input_schema": {
"type": "object",
"properties": {
"city": { "type": "string" }
},
"required": ["city"]
}
}
Functionally equivalent JSON Schema underneath, but enough structural difference that you need a thin adapter layer if you're supporting both providers from one codebase.
How the model signals a tool call
OpenAI returns finish_reason: "tool_calls" with a tool_calls array containing id, function.name, and function.arguments as a stringified JSON blob you have to parse yourself.
Claude returns stop_reason: "tool_use" with content blocks of type: "tool_use", each carrying id, name, and input as already-parsed JSON — no extra JSON.parse step. This is a small but real ergonomic win if you're building a generic tool dispatcher: one less failure mode where malformed stringified JSON crashes your parser.
Sending results back
OpenAI expects a tool role message referencing the tool_call_id:
{ "role": "tool", "tool_call_id": "call_abc", "content": "18°C, cloudy" }
Claude expects a user message containing a tool_result content block:
{
"role": "user",
"content": [
{ "type": "tool_result", "tool_use_id": "toolu_abc", "content": "18°C, cloudy" }
]
}
Claude has no dedicated tool role — results always travel inside a user turn. This matters if your conversation state machine assumes a distinct role per message type.
Parallel and sequential tool calls
Both models can request multiple tool calls in a single turn. OpenAI returns them as separate entries in tool_calls. Claude returns them as multiple tool_use content blocks in the same assistant message. In both cases you must return results for all requested calls before the model will continue — partial results stall the conversation.
Where behavior differs more noticeably: Claude tends to be more conservative about calling tools it's unsure about and more likely to ask a clarifying question first when a schema is ambiguous. OpenAI models, especially the gpt-4 family, have historically been more eager to call a function even on partial information. Neither behavior is strictly better — it depends on whether you want confirmation-seeking or decisive agents, and you can tune both with system prompt instructions.
Forcing a specific tool
OpenAI uses tool_choice: { type: "function", function: { name: "get_weather" } } to force a call. Claude uses tool_choice: { type: "tool", name: "get_weather" }. Both also support an "auto" mode (default) and a "none" mode that disables tool use entirely for that request. The semantics are nearly identical, just different key names again.
Streaming tool calls
Streaming is where the two diverge most. OpenAI streams tool_calls as incremental deltas where you concatenate arguments string fragments across chunks until you have valid JSON. Claude streams tool input as input_json_delta events inside a content_block_delta, and you accumulate the raw JSON string the same way, then parse once the block closes. The pattern is comparable but the event names and nesting are not interchangeable code.
If you're consuming Claude's API through SubToAPI, this complexity is already handled for you — streaming and tool use work through the same sub_live_... API key and the standard /v1/messages endpoint, so you get Claude's tool-calling behavior without hand-rolling the SSE parsing yourself:
curl https://api.subtoapi.app/v1/messages \
-H "Authorization: Bearer $SUBTOAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-5",
"max_tokens": 1024,
"tools": [{
"name": "get_weather",
"input_schema": {
"type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"]
}
}],
"messages": [{ "role": "user", "content": "Weather in Lisbon?" }]
}'
Picking one (or supporting both)
If you're starting fresh and only care about one provider, the choice usually comes down to model quality and cost for your specific task rather than the tool-calling mechanics — both APIs are capable enough for typical CRUD-style function calls, data lookups, and multi-step agents. If you're building a product that needs to support both providers, write an internal tool-call representation (name, id, input, result) and map it to each provider's shape at the edges rather than threading provider-specific types through your application logic.
If your app is already built against Claude and you want a hosted API layer with usage metadata, team seats, and key management on top, see pricing and the quickstart for how SubToAPI wraps Claude's /v1/messages endpoint, including tool use, behind a standard API key.
Questions
Does Claude support parallel function calls like OpenAI? Yes. Claude can return multiple tool_use blocks in one assistant turn, equivalent to OpenAI's tool_calls array. You must supply results for every requested call before continuing.
Is Claude's tool input easier to parse than OpenAI's? Slightly. Claude returns input as parsed JSON directly in the response, while OpenAI returns arguments as a JSON string you need to parse yourself.
Can I migrate a function-calling app from OpenAI to Claude easily? The JSON Schema for parameters is reusable, but you'll need to rename fields (parameters → input_schema), adjust how tool results are sent (no tool role in Claude), and update streaming delta handling.