Google’s official Gemini guide describes Function Calling as a four-step loop: declare a function, send it to the model, execute the function in your application, then send the result back for a final response. That same loop applies to OpenAI and Claude, but their JSON envelopes, call identifiers, schema controls, and history rules are not interchangeable. (ai.google.dev)
This week’s recommendation: start with one read-only tool, define an internal event contract, and keep the original provider response before adding a second model.
Function Calling does not let a model execute an arbitrary API. The model generates a structured request that matches a tool schema. Your application still decides whether to validate, authorize, execute, retry, reject, or log that request.
This guide is for:
- Backend developers building their first tool-enabled AI application.
- Platform engineers supporting OpenAI, Google Gemini, and Claude API in one product.
- Security and operations teams responsible for credentials, permissions, and high-risk actions.
The shared loop: model proposal versus application execution
Function Calling JSON API integrations usually follow this sequence:
-
Declare available tools.
You provide a tool name, description, input schema, and sometimes a tool-selection policy. -
Ask the model to respond with the available tools.
The model may return normal text, one tool call, or multiple tool calls. -
Validate and authorize the proposed call.
Your application checks the function name, JSON syntax, schema, user identity, resource ownership, and business rules. -
Execute the tool in your own environment.
The executor handles authentication, network access, timeouts, retries, idempotency, and result formatting. -
Return the result to the model.
The model uses the result to produce a user-facing answer or propose another tool call. -
Stop only when the model returns a final answer.
A tool call is an intermediate event, not proof that the task has completed.
This distinction answers a common question.
Does Function Calling directly execute an API?
No. The model can suggest a function name and arguments, but it does not automatically receive your API credentials or gain permission to call your infrastructure. The execution layer must map the proposed name to known code. An unknown function should be rejected rather than evaluated dynamically.
Google’s documentation states this responsibility directly: the model returns structured arguments, while the application executes the function and sends the result back. OpenAI’s API reference also warns that generated arguments must be validated before the function is called. (ai.google.dev)
A reliable internal event might look like this:
{
"provider": "openai",
"model": "provider-model-id",
"event_type": "tool_call",
"call_id": "provider-call-id",
"tool_name": "get_order_status",
"arguments": {
"order_id": "A-1042"
},
"raw_response": {}
}
This is an internal representation, not a universal provider request format. Store it after parsing, but retain the untouched response as well.
OpenAI, Gemini, and Claude: similar intent, different contracts
The three platforms share the same concept: the model receives tool definitions and can emit structured arguments. Their interfaces still differ in ways that affect production code.
OpenAI: tool calls are explicit response items or tool-call fields
In OpenAI’s current API documentation, a function tool includes a name, description, and parameters object expressed as JSON Schema. The API also exposes controls such as strict schema adherence, tool_choice, and parallel tool calls. The exact response shape depends on the API surface you use, so your adapter should not assume that a legacy chat message and a newer response item have identical fields. (platform.openai.com)
A simplified OpenAI-style tool declaration is:
{
"type": "function",
"name": "get_order_status",
"description": "Returns the current status of an order owned by the authenticated user.",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "The order identifier."
}
},
"required": ["order_id"],
"additionalProperties": false
},
"strict": true
}
The important implementation detail is that generated arguments may be represented as a JSON string in some response formats. Parse that string before validation. Do not pass it directly to business code.
OpenAI also supports parallel tool calls in relevant interfaces. That creates a second responsibility: your executor must decide whether calls can run concurrently. Two read-only lookups may be independent. Two writes against the same account may not be.
Google Gemini: function calls are content parts or interaction steps
Google Gemini exposes function declarations with a function name, description, and parameters schema. Depending on the API surface, the model may return a functionCall content part or a function_call interaction step. The result is then sent back as a function response or function result tied to the original call. (ai.google.dev)
A simplified Gemini-style declaration is:
{
"name": "get_order_status",
"description": "Returns the current status of an order owned by the authenticated user.",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "The order identifier."
}
},
"required": ["order_id"]
}
}
Gemini’s current documentation also describes sequential and parallel function calling. In streaming mode, arguments can arrive as partial deltas. Your code must aggregate the complete arguments before attempting JSON parsing or execution. Executing on the first partial fragment can create truncated identifiers or incomplete parameters. (ai.google.dev)
State handling matters too. In stateless operation, the client must preserve the user input, the model-generated function call, and the function result in the next request. If your history layer silently drops one of those parts, the model may repeat the call or produce a final answer without the tool result. (ai.google.dev)
Claude API: tool use is embedded in message content blocks
Claude API uses tools with an input_schema field. Claude’s response contains content blocks such as tool_use, including a tool name, an identifier, and an input object. Your application then returns a tool_result block with the matching tool_use_id. Anthropic’s documentation requires that the result immediately follows the associated tool-use message in the conversation structure. (docs.anthropic.com)
A simplified Claude-style declaration is:
{
"name": "get_order_status",
"description": "Returns the current status of an order owned by the authenticated user.",
"input_schema": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "The order identifier."
}
},
"required": ["order_id"]
}
}
This answers another common question.
Are the tool-calling JSON formats of the three models identical?
No. The business meaning can be shared, but the wire format cannot be assumed to be shared. OpenAI may expose a tool-call object or response item. Gemini may expose a function-call part or interaction step. Claude uses message content blocks with tool_use and tool_result.
Treat the provider interface as an external contract. Convert it at the boundary. Do not spread provider-specific conditionals throughout your business logic.
First responsibility: the model integration developer
The model integration developer owns the tool declaration. This person decides what the model is allowed to propose.
A good declaration contains four elements:
- A stable tool name.
- A description that explains when to use the tool and when not to use it.
- An input schema with required fields and meaningful descriptions.
- A clear execution policy, such as automatic use, user confirmation, or manual review.
Tool descriptions are operational instructions. “Updates a customer” is too vague. “Updates the shipping address for an order after the authenticated user confirms the order ID; it does not cancel orders or change payment details” gives the model and the approval layer a safer boundary.
Why does Function Calling need JSON Schema?
JSON Schema gives the model a machine-readable shape for its proposed arguments. It can distinguish a string from an integer, mark fields as required, constrain values with an enum, and reject unexpected fields when supported. It improves consistency, but it does not verify that the order belongs to the current user or that the requested action is permitted.
Schema validation answers: “Is this payload shaped correctly?”
Authorization answers: “May this actor perform this operation on this resource?”
You need both.
For a broader view of structured data contracts, see this guide to JSON Schema compatibility in AI systems. When your tool layer grows into a larger agent system, the discussion of AI coding agents and orchestration patterns is also relevant.
Second responsibility: the provider adapter developer
The adapter developer converts each platform’s response into an internal event without erasing information that may be needed later.
Your adapter should normalize at least:
- Provider name.
- Model identifier.
- API surface.
- Event type.
- Tool name.
- Call identifier.
- Parsed arguments.
- Streaming or complete-response status.
- Raw response payload.
- Finish or stop reason.
- Provider-specific metadata.
Do not normalize too aggressively. A single internal tool_call event is useful for routing, but it may hide provider-specific details such as streamed argument fragments, content-block ordering, thought-signature handling, or refusal states.
A safer pattern is:
provider response
|
v
provider parser
|
+--> internal tool_call event
|
+--> raw response archive
The raw response is valuable when a provider changes an API, when a test fails only on one model, or when support needs to reproduce a malformed call.
The adapter should also own provider-specific result serialization. Do not make the executor know whether a result must be placed in a Claude tool_result block, a Gemini function response, or an OpenAI tool message or output item.
Third responsibility: the tool executor
The executor performs the real work. It should never trust the model as an authority.
At minimum, the executor needs:
- An allowlist of callable tool names.
- Server-side credential injection.
- Input validation.
- User and tenant authorization.
- Network timeouts.
- Retry rules.
- Idempotency handling.
- Output size limits.
- Error normalization.
- Audit logging.
What should you do when the model generates invalid arguments?
Reject the call before execution. Record the validation error. Then choose one of three controlled paths:
- Ask the model to correct the arguments, if the error is recoverable.
- Ask the user for the missing value, if the value is ambiguous.
- End the run with a safe error, if the action is sensitive or repeated correction fails.
Never “repair” a dangerous argument silently. Converting an unknown account ID into a guessed account ID is not validation.
A tool result should be small and intentional. Return the fields needed for the next reasoning step. Avoid sending raw database records, access tokens, internal stack traces, or unrelated customer data back into the model context.
Long-term credentials must not be placed in prompts, tool descriptions, model-readable files, or conversation history. The executor should retrieve secrets from a server-side secret store and inject them only into the outbound API request.
Fourth responsibility: security and business approval
Security ownership cannot be delegated to the schema.
Classify tools before exposing them to a model:
- Read-only tools: search an order, inspect build status, retrieve documentation.
- Low-impact writes: create a draft, add a label, open a non-production ticket.
- High-impact writes: send email, issue a refund, deploy code, modify access.
- Destructive actions: delete data, revoke credentials, terminate infrastructure.
- Open-network tools: fetch arbitrary URLs, execute remote commands, or submit data to unknown destinations.
For read-only tools, automatic execution may be acceptable after identity and scope checks. For high-impact or destructive tools, require explicit confirmation or a separate approval service.
The approval service should evaluate:
- Who requested the action?
- Which tenant or account owns the resource?
- Is the action within the user’s role?
- Is the target environment production?
- Is the amount, scope, or data sensitivity above a threshold?
- Has the user confirmed the exact action?
A valid JSON payload can still be unauthorized. For example:
{
"user_id": "u-17",
"order_id": "A-1042",
"action": "cancel"
}
This may satisfy a schema. It does not prove that u-17 owns order A-1042, that cancellation is still allowed, or that the user actually intended cancellation.
For workflows that combine agent actions with coding or deployment, review your AI workflow rules and skill boundaries before adding write-capable tools.
Fifth responsibility: testing the complete call cycle
Testing only the final text is not enough. A tool-enabled application can produce a fluent answer while calling the wrong function, omitting a required parameter, or losing the tool result.
Use one read-only business scenario across OpenAI, Google Gemini, and Claude API. Keep the business tool identical. Record the provider, SDK version, model identifier, API surface, and test date separately for each run.
Your test suite should include:
- Missing required arguments.
- Wrong argument types.
- Unknown tool names.
- Additional unexpected properties.
- Multiple independent tool calls.
- Two calls that must not run in parallel.
- Tool timeout.
- Tool authentication failure.
- Tool returns an empty result.
- Tool returns an oversized result.
- Lost conversation history.
- Duplicate call identifiers.
- Model gives a final answer without using the required tool.
- Model proposes a second call after the first result.
- Streaming arguments that arrive in fragments.
- A tool result marked as an error.
Run the same test after changing the model, SDK, API version, or schema. “It worked last month” is not a compatibility guarantee.
A useful test assertion is not only:
final answer contains the order status
Also assert:
the approved tool was called
the call used the authenticated order scope
the arguments passed schema validation
the executor used the expected credential
the result was linked to the correct call identifier
the final answer did not claim success after a failed tool
Decision branches: direct SDK or internal adapter layer?
Use these conditions before choosing your architecture.
- If one model, one application, and a small read-only tool set meet your needs, choose the official SDK first. Keep the executor behind a local interface so you can replace it later.
- If two or more model providers must call the same business tools, choose an internal adapter layer. Normalize tool events, result routing, errors, and audit records at one boundary.
- If your tools are shared by several products, define an internal tool contract before adding provider-specific features. Version the contract and preserve raw provider payloads.
- If providers differ in streaming, parallel calls, history, or strict schema behavior, keep those capabilities as explicit flags. Do not pretend every provider supports the same guarantees.
- If a tool can send, delete, deploy, refund, or change permissions, add approval outside the model loop. A schema check alone is insufficient.
- If the workload depends on macOS commands, Xcode, Apple automation, or a persistent build environment, evaluate a remote Mac execution node separately from the model provider. The model API supplies reasoning; it does not supply the required operating system or project environment.
This branch-based approach prevents a common failure: building a “universal” tool format that works for the demo but loses important provider behavior in production.
A five-step implementation plan for this week
Step 1: Choose one read-only business action
Start with an action such as retrieving an order status, checking a build result, or reading a calendar entry. Avoid payments, deletion, production deployment, and arbitrary shell execution.
Write down the expected input and output before writing the tool description.
Step 2: Define the internal contract
Create an internal event with provider, model, call_id, tool_name, arguments, raw_response, and execution status.
Make the call identifier mandatory. It is the link between the model proposal, the executor log, and the returned result.
Step 3: Implement one provider parser
Use the official SDK and follow the provider’s own request and response structure. Do not copy a request format from another platform and rename fields.
Keep parsing, execution, and result serialization in separate modules.
Step 4: Add validation and authorization
Validate JSON syntax first. Validate the schema second. Validate identity, ownership, and business rules third.
Only then call the external API.
Step 5: Repeat the same scenario on the other providers
Record each provider’s model, SDK, interface date, response shape, and failure behavior. If the internal contract needs provider-specific fields, add them explicitly rather than hiding them in an untyped metadata blob.
Current setup versus a remote Mac execution node
For ordinary web APIs, a Linux or container-based executor may be enough. But a tool that must run Xcode builds, Apple automation, simulator workflows, or macOS-only commands has different requirements.
The current setup often has three weaknesses: local machines are difficult to keep online, CI containers may not include the required Apple toolchain, and shared developer laptops create permission and reproducibility problems. A cloud-only API design also cannot replace a real macOS environment when the tool must access Xcode projects, simulators, signing assets, or Apple-specific automation.
When the model layer is already separated from the executor, adding a Mac node is cleaner. The model proposes a typed action. Your approval service authorizes it. The Mac executor performs the operation and returns a bounded result.
If you need temporary macOS capacity for testing, build automation, or an isolated agent executor, Hashvps can be evaluated as a remote Mac resource rather than forcing your main backend to run on a developer workstation. It is not the best fit for every workload: long-term, stable, heavy usage may justify owning dedicated hardware, while tasks requiring physical devices or local peripherals still need a directly controlled environment. For short-lived experiments and Mac-specific execution, separating the AI control plane from the remote Mac worker usually gives you a clearer operational boundary.
Run Your Function-Calling Workflows on Hashvps
Deploy your API integrations on a reliable remote Mac built for development and automation.
Use Hashvps Mac rental to run tool-calling services, validation layers, and scheduled workflows remotely.