Function Calling Across AI Providers: A Practical 2026 Guide
Build portable tool calling across OpenAI-compatible, Anthropic, and Gemini-style APIs with schemas, adapters, validation, and retries.

Function Calling Across AI Providers: A Practical 2026 Guide#
Function calling lets a language model request an application operation such as searching a catalog, looking up an order, or creating a ticket. The model does not execute the function. It returns a structured proposal, and your application validates and runs it. The difficulty begins when one product supports OpenAI, Anthropic, and Gemini models: their tool schemas and response envelopes differ.
What is cross-provider function calling?#
Cross-provider function calling is an adapter layer around a common internal tool contract. Your application defines tools once, translates that definition for each provider, normalizes the returned call, executes only approved operations, and sends the result back in the provider's expected format. A multi-model gateway such as Crazyrouter can reduce endpoint changes because its OpenAI-compatible route supports common tool-calling patterns.
| Capability | OpenAI-compatible | Anthropic Messages | Gemini native | Portable design |
|---|---|---|---|---|
| Tool definition | tools | tools | functionDeclarations | Internal JSON schema |
| Call identifier | Provider ID | Tool-use ID | Function call part | Normalized call ID |
| Result message | Tool message | Tool result block | Function response | Adapter-generated result |
| Main risk | Invalid arguments | Wrong block ordering | Part ordering | Server-side validation |
Design the internal contract first#
Keep the internal representation boring and explicit:
tool = {
"name": "lookup_order",
"description": "Read an order status for the authenticated customer",
"parameters": {
"type": "object",
"properties": {"order_id": {"type": "string"}},
"required": ["order_id"],
"additionalProperties": False,
},
}
The adapter can transform this into provider-specific syntax. Normalize model output into name, arguments, call_id, and provider. Parse arguments as JSON and reject malformed or extra fields before execution.
How to use function calling with an OpenAI-compatible endpoint#
import json, os
from openai import OpenAI
client = OpenAI(api_key=os.environ["CRAZYROUTER_API_KEY"],
base_url="https://crazyrouter.com/v1")
tools = [{"type": "function", "function": tool}]
response = client.chat.completions.create(
model="gpt-5-mini",
messages=[{"role": "user", "content": "Where is order A-1024?"}],
tools=tools,
tool_choice="auto",
)
call = response.choices[0].message.tool_calls[0]
args = json.loads(call.function.arguments)
The application should authorize the call using the authenticated user, not the model's text. Execute the operation, then append a tool result and ask the model to formulate the final answer.
const result = await lookupOrder({ order_id: args.order_id, userId });
messages.push({ role: "tool", tool_call_id: call.id,
content: JSON.stringify(result) });
Reliability and safety patterns#
Use an allowlist of callable functions. Add timeouts and idempotency keys to side-effecting operations. Limit the number of tool turns to prevent loops. Store a hash of the schema with each request so you can reproduce behavior after a tool changes. Return concise, typed errors rather than stack traces or secrets.
Normalize provider differences in one adapter#
Keep provider-specific code at the edge. An adapter should translate the internal tool list, extract calls, and format results; it should not contain business authorization. This separation makes unit tests straightforward: feed the adapter recorded provider payloads and assert the same normalized call is produced. Contract tests should cover empty tool calls, multiple calls, malformed JSON, unknown tools, and a model response that mixes text with a call.
For auditability, persist a request ID, tenant ID, model, tool name, schema version, validation result, and execution result. Avoid storing raw arguments when they contain personal data. A redacted audit record is more useful than an unsearchable dump of sensitive prompts.
For parallel calls, execute only independent read operations concurrently. Serialize writes. If a tool times out, return a retryable status and let the orchestration layer decide whether to retry. Never blindly retry a payment or deletion.
Pricing: official APIs versus a unified gateway#
| Cost dimension | Official provider directly | Crazyrouter approach |
|---|---|---|
| Model access | Provider's token rates | Usage-based model rates |
| Integration work | Separate adapters and keys | One compatible entry point for many models |
| Failover | Build yourself | Available through gateway routing policies |
| Fixed monthly fee | Provider-dependent | No monthly fee or minimum consumption in the documented plan |
Check current rates on Crazyrouter pricing. Your real cost also includes tool execution, retries, logging, and engineering time.
FAQ#
Does function calling execute code inside the model?#
No. The model proposes a structured call; your application validates authorization and executes it.
Can one tool schema work with every provider?#
The semantic schema can be shared, but the request and response envelope usually needs an adapter.
How should function arguments be validated?#
Parse JSON, validate against a strict schema, reject unknown fields, and apply business authorization before execution.
What is the safest first tool?#
Start with an authenticated, read-only lookup. Add writes only after confirmation, idempotency, audit logging, and rollback are tested.
Can Crazyrouter route function-calling requests?#
Its OpenAI-compatible API supports common chat and tool-calling workflows; verify the selected model's current capability in the documentation.
Summary#
Portable function calling is an application architecture problem, not a prompt trick. Define tools internally, adapt envelopes, validate every argument, authorize outside the model, and instrument each turn. A unified endpoint such as Crazyrouter gives developers a practical way to test several models without rewriting the entire tool loop.



