How to parse tool calls from OpenAI, Anthropic and Google

How to parse tool calls from OpenAI, Anthropic and Google

One problem, three dialectslink

OpenAI, Anthropic and Google express the same intent: “I want to run this function with these arguments.” The wrapper changes. OpenAI returns a function_call item, Anthropic a tool_use block and Google a functionCall part.

A harness should not spread those three shapes throughout the product. Translate them at the provider boundary into one common command for the executor. But normalising does not mean erasing: the provider's native call identifier must survive so the result returns to the right request.

OpenAI Responses

function_call

Arguments arrive as a JSON string. call_id links the function output.

Anthropic Messages

tool_use

The input is already an object. The result references tool_use_id.

Google GenerateContent

functionCall

Arguments are normally an object and id is optional, but it must be returned when present.

The JSON returned by each APIlink

These examples reduce real captures to the fields involved in a call. Opaque identifiers have been replaced with readable names. OpenAI and Google were captured with a forced minimal request; the Anthropic example comes from an earlier recorded fixture and was checked against the official documentation because the capture credential had expired.

OpenAI
{
  "type": "function_call",
  "id": "fc_openai_1",
  "call_id": "call_openai_1",
  "name": "lookup_demo_value",
  "arguments": "{\"key\":\"height\"}",
  "status": "completed"
}
Anthropic
{
  "type": "tool_use",
  "id": "toolu_anthropic_1",
  "name": "lookup_demo_value",
  "input": {
    "key": "height"
  }
}
Google
{
  "functionCall": {
    "name": "lookup_demo_value",
    "args": {
      "key": "height"
    },
    "id": "call_google_1"
  }
}
ConceptOpenAIAnthropicGoogle
NamenamenamefunctionCall.name
Argumentsarguments, JSON stringinput, objectargs, object or tolerated string
Correlationcall_ididtool_use_idoptional id in call and response
Resultfunction_call_outputtool_resultfunctionResponse

A common shape that keeps contextlink

The code that executes a function only needs name and args. The code that builds the next turn also needs the provider and its native identifier. Separating those concepts prevents an OpenAI detail from reaching the handler, while also avoiding a name-only response when two calls use the same function.

{
  "execution": {
    "name": "lookup_demo_value",
    "args": {
      "key": "height"
    }
  },
  "correlation": {
    "provider": "openai | anthropic | google",
    "callId": "native call identifier"
  }
}
Practical rule:

normalise the data your code consumes; preserve the metadata the protocol consumes.

Parse the stream, not packetslink

With streaming, a network chunk is not the same thing as an SSE event or a complete JSON document. The network may split halfway through "arguments", combine five events in one chunk or leave the final event without a blank terminating line.

  1. Accumulate text in a buffer.
  2. Extract only complete SSE events and keep the remainder.
  3. Interpret the event type and aggregate deltas by index or identifier.
  4. When the stream closes, process the remainder once more if it forms a valid event.

OpenAI may split item creation and argument deltas; Anthropic streams input_json_delta; Google often sends a complete function part. The parser should hide that difference and produce the same result for every byte partition.

Multiple calls and broken datalink

Order

Keep every call

One turn may request several tools. Preserve provider order and return one result per call.

Identity

The name is not enough

Two calls to lookup_demo_value can have different inputs. Correlate them by id, not name alone.

Safe failure

Do not invent a call

Ignore a truncated event or invalid outer JSON. Convert an explicit provider error into a controlled error.

Invalid arguments can be normalised to an empty object to keep the parser stable, but that is not validation. Before running an action with effects, the harness still has to enforce JSON Schema, permissions and business rules.

The testing contractlink

Provider fixtures

One call, no calls and multiple calls with concrete ids and arguments.

Native continuation

Check call_id, tool_use_id and Google's optional id.

Property-based

Generate arbitrary stream partitions and demand the same result as a one-chunk replay.

Add regressions for truncated JSON, explicit errors and unexpected argument shapes. These tests do not prove that the model will select the right tool; they prove that the harness will interpret whatever it receives deterministically.

Official sources

Learn to build a complete agentlink

This article belongs to a series about developing an agent or harness from end to end. Gymnasia is the practical example, but provider parsers, the common shape and fragmentation tests transfer to other products.

See the complete index of the Gymnasia agent series.

Continue reading

Last posts -->

Have you seen these projects?

Gymnasia

Gymnasia Gymnasia
Expo
React Native
TypeScript
OpenAI
Anthropic

Fitness app with two agents that run entirely on the device, with no backend, so the user's data never leaves the phone. A BYOK conversational coach with local tools and a remote system prompt with offline fallback, plus a food estimator that pulls macronutrients out of a photo of the plate, with barcode scanning against OpenFoodFacts.

LangGraph Deep Researcher

LangGraph Deep Researcher LangGraph Deep Researcher
Python
LangGraph
FastAPI
React
TypeScript
Docker

Multi-agent research system built with LangGraph. A supervisor breaks your question down into topics and launches search sub-agents in parallel; each one compresses its findings before handing them to a writer agent that produces the final sourced markdown report. Live streaming over WebSockets, a configurable model per role and bring-your-own API keys that are never persisted server-side.

Tau

Tau Tau
Python
LangChain

Multi-agent tutoring system for secondary school students, with one agent per subject and course material written and validated by a team of teachers. It was used with real students at a private school in Spain and at a secondary school in Colombia.

View all projects -->
>_ Available for projects

Do you have an AI project?

Let's talk.

maximofn@gmail.com

Machine Learning and AI specialist. I develop solutions with generative AI, intelligent agents and custom models.

Do you want to watch any talk?

Last talks -->

Do you want to improve with these tips?

Last tips -->

Use this locally

Hugging Face spaces allow us to run models with very simple demos, but what if the demo breaks? Or if the user deletes it? That's why I've created docker containers with some interesting spaces, to be able to use them locally, whatever happens. In fact, if you click on any project view button, it may take you to a space that doesn't work.

Flow edit

Flow edit Flow edit

FLUX.1-RealismLora

FLUX.1-RealismLora FLUX.1-RealismLora
View all containers -->
>_ Available for projects

Do you have an AI project?

Let's talk.

maximofn@gmail.com

Machine Learning and AI specialist. I develop solutions with generative AI, intelligent agents and custom models.

Do you want to train your model with these datasets?

short-jokes-dataset

HuggingFace

Dataset with jokes in English

Use: Fine-tuning text generation models for humor

231K rows 2 columns 45 MB
View on HuggingFace →

opus100

HuggingFace

Dataset with translations from English to Spanish

Use: Training English-Spanish translation models

1M rows 2 columns 210 MB
View on HuggingFace →

netflix_titles

HuggingFace

Dataset with Netflix movies and series

Use: Netflix catalog analysis and recommendation systems

8.8K rows 12 columns 3.5 MB
View on HuggingFace →
View more datasets -->