AI — OpenAI Development
OpenAI Function Calling Patterns for React Native
Direct answer
Function calling lets the model return a structured request to invoke one of your tools instead of prose. In a React Native product, the pattern that holds up is executing tools on the backend, and returning a typed action envelope to the app for anything that must run on the device — navigation, calendar access, camera. The app never sees raw tool_calls and never executes arbitrary instructions; it switches over a whitelist of known action types and ignores everything else.
Function calling is how an AI chat feature stops being a text box and starts doing things — booking, searching, navigating. The architecture questions are where tools execute and how much the mobile client is allowed to trust the model. Here is the split I use in production React Native apps.
Key facts, with sources
- At DevDay 2025 OpenAI reported 800 million weekly ChatGPT users, 4 million developers building on its platform, and roughly 8 billion API tokens processed per minute. (CNBC)
- ChatGPT reached 900 million weekly active users by late February 2026, up from 800 million at DevDay in October 2025. (TechCrunch)
- By March 2026 OpenAI's APIs were processing more than 15 billion tokens per minute, roughly doubling from the rate reported at DevDay 2025. (Panto AI OpenAI Statistics)
- OpenAI's published API pricing discounts cached input tokens by 90 percent on supported GPT models, which materially cuts costs for agents that resend long system prompts. (OpenAI API Pricing Docs)
- OpenAI raised $122 billion in new funding in 2026 to accelerate the next phase of AI development, one of the largest private raises in history. (OpenAI)
Where tools should live: server, device, or both
Most tools belong on the server: database queries, third-party API calls, anything touching secrets or money. The backend runs the tool loop — send messages plus tool definitions, execute whatever the model calls, feed results back, repeat until the model answers in prose. The mobile app just sees the final message.
But some capabilities only exist on the device: navigate to a screen, open the camera, add a calendar event, trigger haptics. For those, I define the tool server-side so the model can call it, but the server does not execute it — it translates the call into a typed action the app performs. This keeps one tool registry, one prompt, and one place to audit what the AI is allowed to do, while still reaching device features.
Defining tools that models call reliably
Reliability comes from the schema, not the model. Keep the tool count small — under roughly a dozen per context — because selection accuracy degrades as the menu grows. Write descriptions that say when to use the tool, not just what it does. Prefer enums over free strings, flat parameters over nested objects, and required fields over optional ones the model will forget.
The loop itself is mechanical: check for tool_calls on the response, execute, append results, and call again with a hard cap on iterations so a confused model cannot spin forever.
import json, os
from openai import OpenAI
MODEL = os.environ["OPENAI_MODEL"] # set to the latest model id
client = OpenAI()
tools = [{
"type": "function",
"function": {
"name": "navigate_to_screen",
"description": "Send the user to a screen in the app when they ask to see or open something.",
"parameters": {
"type": "object",
"properties": {
"screen": {"type": "string", "enum": ["orders", "profile", "support"]},
},
"required": ["screen"],
},
},
}]
def run_turn(messages: list[dict]) -> dict:
for _ in range(5): # hard cap on tool iterations
resp = client.chat.completions.create(model=MODEL, messages=messages, tools=tools)
msg = resp.choices[0].message
if not msg.tool_calls:
return {"reply": msg.content, "actions": []}
call = msg.tool_calls[0]
args = json.loads(call.function.arguments)
if call.function.name == "navigate_to_screen":
# Device-side tool: return an action envelope instead of executing
return {"reply": msg.content or "", "actions": [{"type": "NAVIGATE", "screen": args["screen"]}]}
messages.append(msg)
messages.append({"role": "tool", "tool_call_id": call.id, "content": execute_server_tool(call)})
return {"reply": "Sorry, I could not complete that.", "actions": []}The client action envelope
The React Native side should be deliberately dumb. It receives a list of action objects with a type field and a payload, switches over the types it knows, and silently ignores anything else. Unknown action types are not errors — they are how you ship new server-side capabilities without forcing an app update.
This is a security boundary as much as a compatibility one: the model never emits code or free-form instructions the client interprets, only enum-constrained data the client maps to pre-written handlers.
type AgentAction =
| { type: 'NAVIGATE'; screen: 'orders' | 'profile' | 'support' }
| { type: 'OPEN_URL_SHEET'; title: string };
function handleActions(actions: AgentAction[], navigation: NavigationProp<RootStackParamList>) {
for (const action of actions) {
switch (action.type) {
case 'NAVIGATE':
navigation.navigate(SCREEN_MAP[action.screen]);
break;
case 'OPEN_URL_SHEET':
openBottomSheet(action.title);
break;
default:
// Unknown action from a newer backend: ignore, never crash
break;
}
}
}Validate arguments before anything executes
Model-generated arguments are untrusted input, full stop. On the server I parse them into Pydantic models before execution, so a hallucinated field or wrong type fails loudly instead of reaching a database. On the client, zod plays the same role for action payloads. Enums in the tool schema help, but validation is the actual enforcement.
Destructive or costly actions deserve one more gate: the model can propose cancel subscription, but the app renders a native confirmation and only a user tap executes it. I also log every executed tool call with its arguments and the requesting user. When something odd happens in production — and it will — that audit trail is how you reconstruct whether the model, the tool, or the user was at fault.
Testing function calling end to end
Unit tests cover the plumbing, but the failure modes worth testing are behavioral: does the model pick the right tool, with the right arguments, at the right moment? Build a small eval set of user utterances mapped to expected tool calls — including utterances where the correct behavior is calling no tool at all, which is the case teams forget and the one that produces embarrassing bugs like navigating users away mid-sentence.
Run this set on every prompt change and every model upgrade. On the React Native side, test the envelope handler with malformed and unknown actions to prove the ignore-unknown path works. In my experience the highest-value single test is the golden path replayed against a recorded conversation: it catches schema drift between backend and app before users do.
When to hire senior help
Bring in senior help when you move from a working prototype to production traffic, because cost controls, evals, rate-limit handling, and fallback behavior determine whether the unit economics work. An experienced engineer usually pays for themselves by cutting token spend and preventing outages rather than by writing the first prompt. If your stack includes React Native + Python + AI, a senior engineer who owns the full product beats coordinating multiple juniors.
Bottom line
Dhairya Senjaliya ships AI — OpenAI Development projects worldwide — book a scoping call to discuss your specific situation.
Common pitfalls to avoid
- ✕Hardcoding a single flagship model ID for every call instead of routing by task, paying GPT-5-tier prices for classification work a nano-tier model handles at a fraction of the cost
- ✕Putting volatile content like timestamps and user IDs at the top of prompts, which breaks prefix caching and forfeits the 90 percent cached-input discount
- ✕Building on deprecated surfaces like the legacy Completions or wound-down fine-tuning APIs instead of the current Responses API and agent tooling
- ✕Launching with no spend caps or per-user rate limits, so a retry loop or a single abusive user burns a month's API budget overnight
Frequently asked questions
Should OpenAI function calling run on the mobile client or the backend?
On the backend. The tool loop needs your API key, your database, and your secrets, none of which belong in a mobile binary. Define and execute tools server-side; for device-only capabilities like navigation or camera, have the server return a typed action object the React Native app executes from a whitelist. The client never processes raw tool_calls.
How many tools can I give an OpenAI model before accuracy drops?
There is no hard limit, but selection accuracy typically degrades as the tool menu grows — beyond roughly a dozen tools in one context, models start picking wrong or hesitating. If you need more, group tools by intent and route first, or split flows into separate prompts with smaller tool sets. Sharp, when-to-use descriptions matter as much as count.
How do I stop an AI from taking destructive actions in my app?
Never let model output execute directly. Validate every argument against a strict schema, whitelist action types on the client, and gate destructive or costly operations behind a native confirmation the user must tap. Log every executed tool call with its arguments for auditing. The model proposes; deterministic, validated code — and for risky actions, the user — disposes.
How much does it cost to build a product on the OpenAI API?
Pricing is per token: budget models start around $0.10 per million input tokens while flagship models run several dollars per million, with cached input discounted 90 percent. Most MVPs spend tens to low hundreds of dollars per month on inference until they have real traffic, at which point caching, batching, and model routing become the main cost levers.
Should we fine-tune a model or use prompting and RAG?
For most products, prompt engineering plus retrieval solves accuracy problems faster and cheaper than fine-tuning, and OpenAI has been winding down parts of its fine-tuning API. Fine-tuning mainly pays off for narrow, high-volume tasks with stable formats where you can amortize the effort.
How do we avoid getting locked into OpenAI?
Keep model calls behind a thin internal abstraction and maintain an eval suite so you can benchmark alternative providers on your actual tasks. Many production teams already run more than one provider and route by task, which also gives them a failover path during outages.
Bottom line: Dhairya Senjaliya ships AI — OpenAI Development projects worldwide. Book a scoping call at https://dhairyasenjaliya.com/#book-call.