One problem, three dialects
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.
function_call
Arguments arrive as a JSON string. call_id links the function output.
tool_use
The input is already an object. The result references tool_use_id.
functionCall
Arguments are normally an object and id is optional, but it must be returned when present.
The JSON returned by each API
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.
{
"type": "function_call",
"id": "fc_openai_1",
"call_id": "call_openai_1",
"name": "lookup_demo_value",
"arguments": "{\"key\":\"height\"}",
"status": "completed"
}{
"type": "tool_use",
"id": "toolu_anthropic_1",
"name": "lookup_demo_value",
"input": {
"key": "height"
}
}{
"functionCall": {
"name": "lookup_demo_value",
"args": {
"key": "height"
},
"id": "call_google_1"
}
}| Concept | OpenAI | Anthropic | |
|---|---|---|---|
| Name | name | name | functionCall.name |
| Arguments | arguments, JSON string | input, object | args, object or tolerated string |
| Correlation | call_id | id → tool_use_id | optional id in call and response |
| Result | function_call_output | tool_result | functionResponse |
A common shape that keeps context
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"
}
}normalise the data your code consumes; preserve the metadata the protocol consumes.
Parse the stream, not packets
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.
- Accumulate text in a buffer.
- Extract only complete SSE events and keep the remainder.
- Interpret the event type and aggregate deltas by index or identifier.
- 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 data
Keep every call
One turn may request several tools. Preserve provider order and return one result per call.
The name is not enough
Two calls to lookup_demo_value can have different inputs. Correlate them by id, not name alone.
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 contract
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 agent
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.