n4nAI

OpenAI function calling vs Claude tool use

Technical comparison of OpenAI function calling and Claude tool use across schema design, execution model, streaming, costs, and ecosystem — with a clear verdict by use case.

n4n Team8 min read1,657 words

Audio narration

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

OpenAI function calling vs Claude tool use comes down to two similar but meaningfully different approaches to structured model outputs. Both let you define JSON schemas the model must satisfy, both support parallel invocation, and both stream partial results. The differences show up in response formatting, tool-choice semantics, strict-mode enforcement, and how each handles prompt caching — details that cascade into your parsing logic, retry strategies, and cost profile.

Core architecture differences

OpenAI treats functions as a first-class message type. When the model decides to call a function, the assistant message contains a tool_calls array where each entry has an id, type: "function", and a function object with name and arguments (a JSON string). You respond with a tool role message containing the same tool_call_id and the result.

Claude uses content blocks. The assistant message’s content is an array of blocks; a tool invocation appears as a block with type: "tool_use", id, name, and input (a parsed JSON object, not a string). Your response sends a user message whose content array includes a tool_result block referencing that tool_use_id.

# OpenAI response shape (simplified)
{
  "role": "assistant",
  "tool_calls": [{
    "id": "call_abc123",
    "type": "function",
    "function": {
      "name": "get_weather",
      "arguments": '{"location": "San Francisco", "unit": "celsius"}'
    }
  }]
}

# Your tool response
{
  "role": "tool",
  "tool_call_id": "call_abc123",
  "content": "{\"temp\": 18, \"conditions\": \"foggy\"}"
}

# Claude response shape (simplified)
{
  "role": "assistant",
  "content": [{
    "type": "tool_use",
    "id": "toolu_abc123",
    "name": "get_weather",
    "input": {"location": "San Francisco", "unit": "celsius"}
  }]
}

# Your tool response
{
  "role": "user",
  "content": [{
    "type": "tool_result",
    "tool_use_id": "toolu_abc123",
    "content": "{\"temp\": 18, \"conditions\": \"foggy\"}",
    "is_error": false
  }]
}

This structural difference means your parsing code cannot be shared. OpenAI requires json.loads() on arguments; Claude gives you parsed objects directly. On the return path, OpenAI expects a stringified JSON in content; Claude accepts a string or an array of content blocks (text, image, etc.) for richer results.

Schema definition and validation

Both use JSON Schema (Draft 2020-12 subset) for parameter definitions. OpenAI adds a strict: true flag that enables constrained decoding — the model literally cannot emit invalid JSON or extra keys. When strict mode is on, OpenAI validates the schema upfront and rejects the request if it uses unsupported features (e.g., oneOf, anyOf, recursive refs). This is a compile-time guarantee: if the request succeeds, every function call will match the schema.

Claude has no strict mode. It relies on the model’s training to produce valid JSON. In practice, Sonnet 3.5 and Opus 3 are reliable, but you should still validate on your side. Claude does support tool_choice: "any" to force at least one tool call, and tool_choice: {"type": "tool", "name": "specific_tool"} to force a particular one — similar to OpenAI’s "required" and {"type": "function", "function": {"name": "..."}}.

// OpenAI strict schema example
{
  "name": "transfer_funds",
  "description": "Move money between accounts",
  "parameters": {
    "type": "object",
    "properties": {
      "from_account": {"type": "string", "pattern": "^ACC-\\d{8}$"},
      "to_account": {"type": "string", "pattern": "^ACC-\d{8}$"},
      "amount_cents": {"type": "integer", "minimum": 1}
    },
    "required": ["from_account", "to_account", "amount_cents"],
    "additionalProperties": false
  },
  "strict": true
}

If you need guaranteed schema adherence — financial transactions, database mutations, anything where a hallucinated key causes damage — OpenAI strict mode is the only zero-trust option. With Claude, you build a validation layer and retry on failure.

Execution model and control flow

Both providers let the model chain multiple tool calls in a single turn. OpenAI returns all tool_calls in one assistant message; you execute them (parallel or sequential) and return all results in one or more tool messages before the next model turn. Claude returns multiple tool_use blocks in the content array; same pattern.

The divergence appears when the model wants to speak and call tools in the same turn. OpenAI allows this: the assistant message can have content (text) and tool_calls simultaneously. Claude separates them — if there are tool_use blocks, the content array contains only those blocks; any textual response comes in a subsequent turn after tool results.

# OpenAI: text + tools in one message
{
  "role": "assistant",
  "content": "I'll check the weather for you.",
  "tool_calls": [...]
}

# Claude: separate turns
# Turn 1 - only tool_use blocks
{"role": "assistant", "content": [{"type": "tool_use", ...}]}
# Turn 2 - after tool_result, text response
{"role": "assistant", "content": [{"type": "text", "text": "It's 18°C and foggy."}]}

This affects your conversation loop. With OpenAI, you can display the model’s explanatory text immediately while tools run in the background. With Claude, you wait for the tool round-trip before any user-visible text appears. For latency-sensitive UIs, OpenAI’s model feels snappier.

Streaming and partial results

Both stream tool calls incrementally. OpenAI sends tool_calls chunks where function.arguments arrives as a growing string — you accumulate and parse when finish_reason hits "tool_calls". Claude streams tool_use blocks with partial_json that accumulates; the input field is only complete on the final chunk for that block.

# OpenAI streaming chunk (delta)
{
  "choices": [{
    "delta": {
      "tool_calls": [{
        "index": 0,
        "id": "call_abc123",
        "function": {
          "arguments": '{"location": "San Franc'
        }
      }]
    }
  }]
}

# Claude streaming chunk (delta)
{
  "type": "content_block_delta",
  "index": 0,
  "delta": {
    "type": "input_json_delta",
    "partial_json": '{"location": "San Franc'
  }
}

OpenAI’s string-based arguments mean you can’t safely parse until the chunk is complete. Claude’s partial_json is the same story — don’t parse early. However, Claude’s content_block_start event gives you the tool id and name upfront, before any arguments arrive. OpenAI only emits the id and name on the first delta chunk that includes them. Practically equivalent; handle both with a buffer-and-parse-on-finish pattern.

Error handling and retries

OpenAI returns HTTP 400 for schema violations in strict mode (request rejected before inference). For runtime errors — model emits invalid JSON despite strict mode, or arguments miss required fields — you get a normal completion with finish_reason: "tool_calls" but the parsed JSON fails validation. You must catch this, send a tool result with an error, and let the model retry.

Claude returns HTTP 400 for malformed requests. Runtime tool-use errors surface as normal completions; you validate input against your schema and return is_error: true in the tool_result. Both models will typically self-correct on the next turn if you feed back a clear error message.

# OpenAI error feedback
{
  "role": "tool",
  "tool_call_id": "call_abc123",
  "content": json.dumps({
    "error": "invalid_amount",
    "message": "amount_cents must be positive integer"
  })
}

# Claude error feedback
{
  "role": "user",
  "content": [{
    "type": "tool_result",
    "tool_use_id": "toolu_abc123",
    "content": json.dumps({
      "error": "invalid_amount",
      "message": "amount_cents must be positive integer"
    }),
    "is_error": true
  }]
}

Claude’s explicit is_error flag is cleaner — the model sees a structured error signal. OpenAI relies on you encoding errors in the content string. Neither provider has built-in retry logic; you implement the loop.

Cost and latency characteristics

Pricing differs by model, not by feature. OpenAI charges per token (input + output). Function calls consume output tokens for the arguments string and input tokens for the tool result you send back. Claude charges per token similarly. There is no per-call surcharge for either.

Latency: both add ~50-200ms overhead per tool round-trip versus plain text generation, dominated by model inference time. Parallel tool calls execute in a single model pass — the model emits all calls at once — so N parallel tools cost roughly the same latency as 1 tool (plus your execution time). OpenAI’s strict mode adds negligible latency; the constrained decoding runs during generation.

Prompt caching: Claude supports explicit cache_control: {"type": "ephemeral"} on system prompts, tool definitions, and message blocks. Cache hits reduce input token cost by ~90% and latency significantly. OpenAI has no public prompt caching API as of writing (though some enterprise tiers offer it). If your tool definitions are large and stable — dozens of functions with verbose descriptions — Claude’s caching can cut costs dramatically for high-volume workloads.

Ecosystem and tooling

OpenAI’s function calling has broader library support. The openai Python/TypeScript SDKs include tool_calls parsing helpers, Pydantic integration via instructor or openai-functions, and LangChain/LlamaIndex abstractions that default to OpenAI patterns. Most open-source eval frameworks (e.g., promptfoo, langsmith) assume OpenAI-style tool calls.

Claude’s SDKs (anthropic Python/TypeScript) are first-class but newer. instructor supports Claude tool use. LangChain and LlamaIndex have Claude integrations, but you’ll hit more edge cases — especially around streaming tool calls and multi-modal tool results. If you’re building on a framework, OpenAI is the path of least resistance. If you’re writing raw HTTP or a thin wrapper, both are straightforward.

n4n.ai normalizes both formats behind a single OpenAI-compatible endpoint, so you can swap models without rewriting your tool parsing layer — useful during evaluation phases.

Limits and quotas

Dimension OpenAI (GPT-4o) Claude (Sonnet 3.5)
Max tools per request 128 256
Max parallel calls per turn 128 (practical) 256 (practical)
Tool definition size No hard limit (context) No hard limit (context)
Strict mode schema complexity Limited (no refs, no oneOf) N/A
Context window 128k 200k
Output tokens per request 16k 8k (configurable to 64k)
Rate limits (tier 5) 10M tokens/min 5M tokens/min

OpenAI’s strict mode rejects schemas with $ref, oneOf, anyOf, allOf, not, if/then/else, or recursive types. You must inline everything. Claude accepts the full JSON Schema subset the model understands — which includes oneOf/anyOf in practice — but validation is on you.

Output token limits matter for tool-heavy workflows. If the model emits many parallel calls with large argument objects, you can hit the output cap before the model finishes. GPT-4o’s 16k output is more generous than Sonnet’s default 8k (though Sonnet can be configured higher via API parameter).

Comparison table

Capability OpenAI function calling Claude tool use
Response format tool_calls array in assistant message tool_use blocks in content array
Argument format JSON string (requires parsing) Parsed JSON object
Strict schema enforcement Yes (strict: true) No
Forced tool selection "required" or specific name "any" or specific name
Text + tools in same turn Yes No (separate turns)
Prompt caching Not publicly available Yes (cache_control)
Error signaling in tool result Encoded in content string Explicit is_error flag
Max parallel tools 128 256
Output token limit (default) 16k 8k
SDK maturity High High
Framework ecosystem Broadest Good, growing

Which to choose

Choose OpenAI function calling when:

  • You need guaranteed schema validity without application-level validation (financial writes, database migrations, API calls with strict contracts). Strict mode is a real architectural advantage here.
  • Your UI benefits from showing model reasoning text before tool results arrive. The combined text+tools turn lets you stream “Let me look that up…” while the tool executes.
  • You rely heavily on LangChain, LlamaIndex, Instructor, or eval frameworks that default to OpenAI patterns. The ecosystem friction is lower.
  • You need higher output token headroom for many parallel calls with large payloads (16k vs 8k default).

Choose Claude tool use when:

  • Your tool definitions are large and stable across requests. Prompt caching (cache_control) can slash input token costs by 90% on repeated calls — significant at scale.
  • You prefer parsed JSON objects over stringified arguments. It eliminates a class of parsing bugs and makes streaming deltas easier to inspect.
  • You want explicit error signaling (is_error) in the protocol rather than convention.
  • You need more than 128 tools defined or more than 128 parallel invocations (rare, but the ceiling is higher).
  • You’re building on a 200k context window and need to pack extensive tool descriptions + conversation history.

Use both (via a gateway) when:

  • You’re A/B testing model quality for agentic workflows. The schema translation layer is thin; swap the model, not your code.
  • Different tasks map to different strengths: strict-mode financial ops on GPT-4o, high-volume cached lookup agents on Sonnet.
  • You want fallback when one provider hits rate limits or degrades. A gateway that normalizes tool formats lets you fail over without rewriting your agent loop.

The technical differences are real but narrow. Most teams pick based on which model they already standardize on for reasoning quality — then adapt their tool layer to that provider’s conventions. If you’re starting fresh, prototype with both for 48 hours. The parsing code you write on day one will live for years; spend the time to feel the ergonomics.

Tagsopenaiclaudefunction-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 & tool use posts →