Function calling gives your application a structured proposal to run a tool. For a custom client-side function, your code decides whether to execute it and returns the result. The important production boundary is that decision, not whether the generated arguments look like valid JSON.

This guide maps the protocols and supplies a small application-boundary example. Provider-hosted tools have different execution responsibilities; do not treat every tool as a local Python callback.

Keep each provider’s call and result pair intact

API surfaceModel-side requestApplication result correspondence
OpenAI Responses custom functionfunction_call item with name, arguments, and call IDfunction_call_output carrying the same call_id
Claude Messages client tooltool_use block with ID, name, and inputtool_result referencing tool_use_id
Gemini Interactions functionfunction_call stepfunction_result referencing call_id

Sources: OpenAI function calling, Claude tool-call handling, and Gemini function calling. The Gemini row refers to the Interactions examples in the current documentation; do not paste those envelopes into a different API surface.

Claude’s client-tool results belong immediately after the corresponding tool-use turn, with result blocks before any additional text in that user message. Preserve all required identifiers and typed items when adapting a conversation between providers.

Validate three different things

Shape: Does the tool name exist, and do the arguments have the expected fields and types? Reject unexpected fields. Be careful with language-specific type behavior, such as Python treating Boolean values as integers.

Authority: May the authenticated requester perform this action on this resource? Derive identity from the application session. A model-supplied tenant, user ID, or claim of approval is data, not proof of permission.

Intent and effect: Is the requested operation within the user’s task, and what will actually change? For side effects, bind confirmation to the concrete action, use an idempotency key, and reconcile uncertain results before retrying.

Keep these checks separate in logs. A schema failure, an authorization denial, and a failed downstream service require different fixes.

Run a read-only boundary example

Download tool_boundary.py. It exposes one synthetic record lookup and accepts only record_id in the generated arguments. The tenant is supplied by trusted application code.

python3 tool_boundary.py

The execution record shows twelve deterministic cases passing. Two valid reads succeed; requests including a cross-tenant record, forged tenant argument, unknown tool, wrong type, and malformed JSON are rejected. A shared error is used for unavailable and unauthorized records.

The fixture demonstrates application behavior under specific inputs. It contains no live model request, so it does not measure a provider’s tool-selection accuracy or resistance to prompt injection.

Bound the loop, including failures

Set limits before starting the run: total requests, tool calls, elapsed time, and allowed spend. Count unsuccessful attempts. Validate an entire parallel batch before dispatch so a duplicate identifier or excessive batch cannot partially execute.

The Responses tutorial includes a bounded example with mock tests. It returns each tool result to the matching call and stops on incomplete responses, missing final text, and exhausted budgets.

For a write-capable tool, retry policy needs more care than a read-only example. After a network timeout, query the operation status using the same application request identifier. Do not create a fresh operation simply because the model asked again.

FailureApplication response
Unknown functionReject; never resolve arbitrary names through eval or dynamic imports
Invalid argumentsReturn a bounded structured error without secrets
Access deniedStop that action; do not try a more privileged identity
Downstream timeoutReconcile state before retrying a side effect
Repeated unsuccessful callsStop at the budget and explain the unresolved task
Tool output contains instructionsKeep it as untrusted data; do not promote it into system guidance

Parallelism has a workload-dependent benefit

If independent tools take three and five seconds, their sequential waiting time is eight seconds; perfect parallel scheduling reduces that component to five seconds. Model time, queueing, validation, and aggregation still add latency. Dependencies or rate limits can remove the advantage.

Measure the complete workflow with the same task and success criteria. A published acceleration result for one execution system does not justify a universal multiplier for all function-calling applications.

Revision note

Updated October 20, 2026. Removed unsupported universal speed and token-overhead claims. Replaced mixed provider assumptions with a named-API mapping, explicit authorization boundaries, failure handling, and an executed offline fixture.

Primary sources

Checked October 20, 2026: responses functions; claude tools; gemini functions.