n4nAI

Claude tool use vs OpenAI function calling compared

A practitioner's head-to-head comparison of Claude tool use vs OpenAI function calling across capabilities, cost, latency, ergonomics, and limits.

n4n Team5 min read1,138 words

Audio narration

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

Claude tool use vs OpenAI function calling is the practical fork most teams hit when they graduate from prompt-only bots to agents that execute code, query APIs, or update records. Both mechanisms let the model emit a structured request for an external function, but the request shape, SDK expectations, and retry semantics are different enough that your client abstraction will leak one of them if you aren’t deliberate. This piece compares them on the dimensions that actually change your implementation.

Wire format and request shape

OpenAI formalized function calling as a tools array on the chat completions endpoint. Each entry is a wrapper with a function object containing name, description, and parameters (a JSON Schema). The model responds by attaching tool_calls to an assistant message.

tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "Get current weather",
        "parameters": {
            "type": "object",
            "properties": {"location": {"type": "string"}},
            "required": ["location"]
        }
    }
}]

Claude’s Messages API uses a tools parameter where each tool carries name, description, and input_schema directly—no nested function key. The model replies with tool_use content blocks that can sit alongside text blocks in the same turn.

tools = [{
    "name": "get_weather",
    "description": "Get current weather",
    "input_schema": {
        "type": "object",
        "properties": {"location": {"type": "string"}},
        "required": ["location"]
    }
}]

Returning tool results

OpenAI expects you to persist the assistant message exactly (including its tool_calls) and then append one role: "tool" message per call, referenced by tool_call_id.

messages = [
    {"role": "user", "content": "Weather in SF?"},
    {"role": "assistant", "tool_calls": [{
        "id": "call_1",
        "type": "function",
        "function": {"name": "get_weather", "arguments": '{"location":"SF"}'}
    }]},
    {"role": "tool", "tool_call_id": "call_1", "content": '{"temp": 62}'}
]

Claude expects you to echo the assistant’s tool_use blocks inside an assistant turn, then send a user message containing tool_result blocks keyed by tool_use_id.

messages = [
    {"role": "user", "content": "Weather in SF?"},
    {"role": "assistant", "content": [
        {"type": "tool_use", "id": "tu_1", "name": "get_weather", "input": {"location": "SF"}}
    ]},
    {"role": "user", "content": [
        {"type": "tool_result", "tool_use_id": "tu_1", "content": '{"temp": 62}'}
    ]}
]

The Claude shape preserves the interleaving of reasoning text and tool requests; OpenAI’s flat tool_calls list discards that signal unless you parse the assistant’s parallel text separately.

Capabilities: parallel calls, streaming, forced invocation

Both providers support parallel tool calls in one inference turn. OpenAI returns an array of tool_calls; Claude returns multiple tool_use blocks. Streaming works in both: OpenAI streams tool_calls deltas with an index field so you can reconstruct multiple calls concurrently; Claude streams input_json deltas inside tool_use events, delivering the arguments as a single JSON string per block.

Forced invocation differs in vocabulary. OpenAI’s tool_choice accepts "auto", "none", or {"type":"function","function":{"name":"..."}}. Claude’s tool_choice accepts {"type":"auto"}, {"type":"any"}, or {"type":"tool","name":"..."}. The "any" mode is the only native “force a tool but let the model pick which” option—OpenAI has no direct equivalent short of listing one tool.

On raw feature surface, the claude tool use vs openai function calling gap is narrow. Claude’s block model exposes the model’s intermediate text; OpenAI’s format is simpler to serialize but hides whether the model reasoned before calling.

Cost model and token metering

Neither provider charges a separate fee for tool use. You pay per token. The tool schema is part of the input context, so a 2 KB JSON description costs the same as 2 KB of system prompt. Tool results returned in the next turn are also billed as input tokens.

Publicly listed pricing for Claude 3.5 Sonnet and GPT-4o places input tokens at $3 vs $5 per million, with output tokens at $15 per million for both. If your agent loops ten times with 1 KB of results each, the repeated context dominates spend regardless of model. Cache-aware routing mitigates this: if you route through n4n.ai, provider cache-control hints are forwarded, so a static tool schema cached by Anthropic or OpenAI still hits the cache and avoids re-billing on every turn.

Latency and throughput characteristics

End-to-end latency is dominated by model inference, not tool parsing. Both APIs constrain generation to the schema, which adds negligible compute. In streaming, OpenAI’s indexed deltas let you start parsing arguments before the block completes; Claude’s tool_use block typically arrives as one input_json delta, so you buffer until the block closes. For interactive agents, OpenAI’s incremental style gives a few hundred milliseconds of earlier dispatch opportunity.

Throughput under concurrency is gated by provider rate limits. Both return 429s when you exceed RPM/TPM. Build your dispatcher with exponential backoff and a fallback model; the tool layer should be identical across retries.

Ergonomics and SDK friction

OpenAI’s SDK returns a ChatCompletionMessage where tool_calls is a list of objects with function.name and function.arguments (a JSON string). You must json.loads the arguments yourself, and the string may be incomplete mid-stream.

if msg.tool_calls:
    for call in msg.tool_calls:
        args = json.loads(call.function.arguments)
        # dispatch by call.function.name

Claude’s SDK returns content as a list of blocks; you iterate and check block.type == "tool_use", then read block.input which is already a parsed dict.

for block in resp.content:
    if block.type == "tool_use":
        # block.input is dict, block.name is str

The Anthropic shape is less surprising for a state-machine mindset. The OpenAI shape is what most existing agent frameworks assume, so onboarding is faster if you hire from the LangChain ecosystem.

Ecosystem and framework support

OpenAI function calling has existed since mid-2023 and is the default in LangChain, LlamaIndex, and Semantic Kernel. Claude tool use is supported by those libraries but usually behind a wrapper that translates blocks to the OpenAI-like shape. If you inherit a stack built on tool_calls, switching to Claude means maintaining a translation shim.

A single OpenAI-compatible endpoint that addresses 240+ models lets you avoid the shim entirely. You send tools in OpenAI shape and the gateway maps it to Claude’s input_schema, preserving per-token metering and fallback when a provider is degraded. That keeps the claude tool use vs openai function calling decision isolated to a routing header rather than a code rewrite.

Hard limits and edge cases

OpenAI enforces a maximum of 64 tools per request and validates schemas against a subset of JSON Schema draft 2020-12. Claude does not publish a hard cap but notes that accuracy drops as tool count grows; practically, keep it under 30. Both require tool names matching ^[a-zA-Z0-9_-]{1,64}$; Claude is case-sensitive, OpenAI treats them as case-insensitive in some SDK versions.

Neither model will call a tool if the prompt is ambiguous. Claude often emits a text block asking for clarification; OpenAI may return an empty tool_calls array. Your loop must handle “no call” as a valid outcome, not an error. Nested object schemas work in both, but deeply recursive schemas increase refusal rates.

Side-by-side summary

Dimension OpenAI function calling Claude tool use
Request shape tools[].function nested schema tools[].input_schema flat
Response message.tool_calls array content blocks type: tool_use
Parallel calls Yes, indexed tool_calls Yes, multiple blocks
Forced mode auto/none/specific fn auto/any/specific tool
Streaming Incremental indexed deltas Block-level JSON delta
Cost Per token, schema+results billed Per token, schema+results billed
Ecosystem Native in most frameworks Wrapped, growing
Limits 64 tools, strict schema Soft accuracy ceiling

Which to choose

Choose OpenAI function calling if…

You already run a LangChain or similar stack that assumes tool_calls. You need the widest community support, and your tools are simple enough that losing interleaved reasoning text is acceptable. For high-volume parallel dispatch, the flat list maps cleanly to a worker queue.

Choose Claude tool use if…

Your agent must explain its plan in text while emitting a call, or you are token-sensitive on long system prompts. The block model maps directly to a state machine and avoids JSON-string parsing race conditions. Teams building custom orchestration from scratch will appreciate the explicit tool_use_id linking.

Use both via a gateway if…

You want to A/B model quality without rewriting your tool layer. Route by latency or cost at runtime, and let the gateway handle schema translation, fallback on provider degradation, and per-token metering. That keeps the claude tool use vs openai function calling debate where it belongs: a config flag, not a rewrite.

Tagsclaudeopenaitool-usefunction-callingcomparison

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 fundamentals posts →