Inside RapidNative's Multi-LLM Router for React Native Code
By Rishav
9th Aug 2026
Last updated: 9th Aug 2026
There's a comforting myth in AI tooling that somewhere out there is a single "best model" — pick it, prompt it, ship it. The reality of building RapidNative, an AI mobile app builder that turns plain-English prompts into production-ready React Native and Expo apps, is the exact opposite. Different jobs in the same request want fundamentally different models. Writing a screen wants a reasoning-heavy coder. Reading a screenshot wants a multimodal vision model. Classifying a sentence as "frustrated" or "praise" wants an 8B model that costs pennies. Drafting a personalized outreach email wants Claude Sonnet's prose polish. Ship them all through one model and you overpay for the small tasks and underpower the hard ones.
So RapidNative doesn't. It runs a multi-LLM router: a database-backed registry called ai_agents, four task-specific ModelPurpose types, and six provider integrations that any of them can resolve to. Model selection is data, not code. Swapping the model behind React Native code generation is a row update, not a redeploy. This post walks through how that router is actually built, why each design decision exists, and what it takes to keep multi-LLM code generation cost-effective and fast at production scale.
A generated React Native screen running side-by-side with the code that produced it — the output of RapidNative's multi-LLM pipeline.
Why one LLM isn't enough for React Native code generation
React Native code generation is not one problem. Inside a single user turn — "make me a fitness tracker with a leaderboard" — the system needs to do at least four qualitatively different things:
- Understand the intent — parse the prompt (or a sketch, screenshot, or PRD) into a concrete plan of screens, state, and data.
- Gather context — read the current project's files, find reusable components, look up matching skills, inspect the Supabase schema.
- Generate code — write the actual
.tsxscreens, edit existing files, and create new SQL migrations. - Loop with tools — call
write_file,db_migration_new,edit_file, and read the results back until the change is complete.
Each of these has a different sweet spot for cost, latency, and reasoning depth. Context gathering runs many small tool-use loops, so a fast, cheap model with solid tool-calling behavior is ideal. Final code generation runs once per turn but requires deep reasoning about component structure, prop drilling, and platform quirks. Vision tasks (turning a screenshot into a screen) need multimodal ability at all. And the system also runs adjacent tasks — sentiment analysis, upsell email drafting, structured API planning — that don't need a code-tuned model at all.
Force everything through one model and you make the same trade-off every AI startup regrets in month six: either you pay top-tier prices for tasks that don't need it, or you starve the hard tasks of the reasoning they need. RapidNative's answer is to route.
The four model purposes
At the core of RapidNative's multi-LLM code generation architecture is a tiny type:
export type ModelPurpose = 'MAIN_GENERATION' | 'VISION' | 'CONTEXT_GATHERING' | 'AGENT';
That lives in src/modules/api/services/ai/llm/types.ts. Four values, four sockets that any provider can plug into. Every call site in the codebase that needs a model asks by purpose, not by name:
const model = await getAIModelAsync('MAIN_GENERATION');
The resolver reads the current admin configuration and returns whichever provider + model ID is bound to that slot. Nothing at the call site knows or cares whether that resolves to a DeepSeek reasoning model, a Claude Sonnet, or an OpenRouter-fronted Qwen. That indirection is the whole trick.
Here's how the four purposes map to real work:
| Purpose | What it's for | Why it exists as a separate slot |
|---|---|---|
| MAIN_GENERATION | Final .tsx code generation, screen creation, SQL migrations | Needs the highest-quality reasoning; runs once per turn; worth the cost |
| VISION | Reading screenshots, sketches, and images into component descriptions | Requires multimodal ability; only ~20% of turns need it |
| CONTEXT_GATHERING | Reading files, searching skills, inspecting schema, calling tools | Fires many small calls; latency + cost dominate; quality bar is lower |
| AGENT | The support chat and other conversational agents outside the code path | Needs tool use and instruction following, not code-writing skill |
The two-step pipeline in src/app/api/user/ai/generate-v2/route.ts shows the split most cleanly. First it calls getAIModelAsync('CONTEXT_GATHERING') with a bundle of read-only tools — list_skills, search_skills, read_skills, read_file — and lets a fast, cheap model wander through the project to figure out what's relevant. Then it hands the gathered context to getAIModelAsync('MAIN_GENERATION') with no tools and one job: write good code. That split alone can cut per-turn cost dramatically without touching output quality, because the model doing the "librarian" work is often 10x cheaper per million tokens than the model doing the "author" work.
Four ModelPurpose slots, six providers underneath — the routing surface that every generation call flows through.
Inside the ai_agents registry
The router's other half is a Postgres table, defined in supabase/migrations/20260623120000_ai_agents.sql. Each row is what RapidNative calls an "agent" — not in the autonomous-agent sense, but in the sense of a named, priced, provider-bound model configuration. The columns tell the story:
name— a stable identifier the frontend can pass (e.g.'slow','fast', or something custom)model— the model ID with an optional provider prefix, like'deepseek/deepseek-v4-pro'vision_model— an optional second model for multimodal work, ornullto fall back to the globalVISIONconfigprovider— the SDK adapter to use, defaulting to'openrouter'provider_order— sub-provider routing hints for OpenRouter (e.g.['alibaba', 'deepinfra']) so you can prefer certain inference backendsinput_credits_per_million,output_credits_per_million,cached_input_credits_per_million— per-agent pricing used for RapidNative credit deductiondiscount_percent— a promotional discount applied at billing timeis_active,is_default— visibility flags
The seed migration ships with one agent named 'slow' mapped to deepseek/deepseek-v4-pro on OpenRouter, with the alibaba and deepinfra sub-providers preferred. That agent is marked is_default = true, and every RapidNative user starts requests against it unless they've picked something else.
The important architectural claim is that this is a registry, not a hardcode. Adding a new model to production is: insert a row, mark it active, optionally mark it default, done. The next request the app serves picks it up (subject to a five-minute cache — more on that below). There is no code change, no deploy, no client update. Model selection is treated as operational data — the same shape as feature flags, pricing tiers, or A/B assignments.
That property matters a lot when new frontier models drop every few weeks. A router that requires a code deploy to try Kimi K2.5 or GLM 5.1 will fall behind. A router that requires a row insert will not.
How each task picks its model
Now the concrete part. Here's what routing looks like across the actual code paths that run when you use RapidNative:
Main code generation
src/app/api/user/ai/generate-v3/route.ts is the workhorse — the route that fires when you type a prompt in the RapidNative editor. Its selection logic is a decision tree, roughly:
- If the request has an image attachment and the resolved agent defines a
vision_model→ use OpenRouter with that vision model - If the request has an image and no agent-level vision model → use OpenRouter with the global
getModelConfigForPurpose('VISION') - If it's text and the agent has a model → use DeepSeek's native SDK with the agent's text model (stripping the
deepseek/prefix that gets stored) - If no agent resolves at all → use DeepSeek native with the
DEEPSEEK_MODELenv fallback, currentlydeepseek-v4-pro
The generation call itself is a streamText from Vercel's AI SDK v6, with a bundle of tools (write_file, edit_file, db_migration_new, ask_question, and more from createFsTools, createDbTools, createSkillTools, createQuestionTools, createActionTools, createWebTools), a stopWhen condition that halts at 40 steps or on an ask_question call, and DeepSeek's reasoning mode enabled through providerOptions:
providerOptions: { deepseek: { thinking: { type: 'enabled' } } }
That thinking: enabled flag is what turns DeepSeek's cheap-per-token pricing into competitive quality on hard reasoning problems — the model produces a hidden reasoning trace before its answer, at the cost of extra output tokens but with a large jump in code correctness.
Vision — screenshots and sketches into components
When you drop a screenshot into RapidNative or draw on the whiteboard, the request carries an image attachment. The router notices, checks whether the current agent has a vision_model defined, and if it does, routes to OpenRouter with that model. If not, it falls back to the global VISION purpose config. This is why every agent row has a distinct vision slot: the ideal text-code model rarely has the ideal vision behavior, and vice versa.
Sentiment analysis — cheap, small, fast
Not every LLM call in the product is about code. src/modules/api/services/ai/SentimentService.ts runs a lightweight classifier on user messages to detect frustration, praise, or neutral instruction. The model? meta-llama/llama-3.1-8b-instruct on OpenRouter, priced around $0.055 per million tokens in and out. It's disabled entirely in dev to keep local iteration free. This is the archetype of a call that would be absurd to route through a frontier model — the accuracy ceiling on this task is close to saturated by any half-decent instruct model, so you optimize for cost.
Structured planning — Claude Haiku for schemas
src/app/api/tools/app-api-planner/route.ts generates API schemas — endpoints, methods, parameters — for a given app description. It uses anthropic/claude-haiku-4.5 via OpenRouter, invoked through generateObject from the AI SDK with a Zod schema. Haiku's combination of speed, low cost, and strong structured-output compliance makes it the right tool for producing JSON-shaped outputs that must validate cleanly.
Outreach drafting — Claude Sonnet for prose
src/app/api/admin/growth/upsell/route.ts drafts personalized outreach for high-intent users. Model: anthropic/claude-sonnet-4. Sonnet is meaningfully better at natural-sounding English than any cheap code-tuned model, and the volume of outreach drafts is low enough that per-token cost doesn't drive the total. Also, importantly: this route does not send project content to the model — only engagement signals — so it's outside the code generation flow entirely.
The coding agent — same registry, offline
RapidNative also ships an in-repo harness — npm run agent:run "Create a fitness tracker app" — that runs the same coding agent from a Node script against a local filesystem instead of the production database. The important design fact is that the harness reads its model from the same ai_agents table the production route does, not a hardcoded ID. This guarantees that when engineers debug or verify the agent locally, they're driving the exact same model that ships. The only thing that swaps is the storage adapter — FsFileStorage for MemoryFileStorage. That single discipline is why bugs found in the harness are always real production bugs, not testing artifacts.
The coding agent's tool-use loop mid-flight — every call is bound to a model resolved from the same registry, whether it runs in the browser or the harness.
The economics: why routing cuts credit cost
The router isn't just a purity argument — it changes the unit economics of every request. RapidNative charges users in credits, and credits are computed from actual token counts at the per-agent rates stored in the ai_agents row:
credits = (inputTokens × input_credits_per_million / 1M)
+ (outputTokens × output_credits_per_million / 1M)
+ (cachedInputTokens × cached_input_credits_per_million / 1M)
Because the cached-input rate is broken out separately, prompt caching translates directly into user-visible savings. RapidNative deliberately keeps its system prompt static across every request — a long, careful FULL_SYSTEM_PROMPT that never varies per turn — because DeepSeek supports byte-level prefix caching, and Anthropic supports explicit cache-control markers. Both mean the model provider only bills you full price for the first cache miss, and pennies per million for every hit after that. On a typical multi-turn session, this alone can knock 30–50% off the token cost without changing anything the user sees.
Routing multiplies the effect. Send the librarian work through a $0.30/M model and the author work through a $3/M model, and the blended per-turn cost drops well below what a single-model architecture could achieve at the same quality. Add a discount_percent at the agent level (RapidNative uses this for promo periods), and the credit charge to the user drops proportionally without touching the underlying provider bill.
Fallbacks, defaults, and the 5-minute cache
Routing in production has to survive misconfiguration and downtime. RapidNative's resolver — getAgentOrDefault(name) in src/modules/saas/api/services/AgentConfigService.ts — implements a strict fallback chain:
- Try the named agent from the request. If it exists and is active, use it.
- Otherwise, return the agent flagged
is_default = true. - If no default is flagged, look up the hardcoded name
'slow'. - If
'slow'doesn't exist, pick the first active agent in the table. - Only return
nullif there are no active agents at all — a state that would mean the product is completely misconfigured.
The whole chain is wrapped in Next.js's unstable_cache with a five-minute TTL and a revalidateTag hook. So an admin flipping the default agent in the panel takes at most five minutes to propagate to every serverless region, without any deploy, without any restart. In practice that's fast enough for A/B tests and slow enough that the database isn't hit on every request. It's also the exact right window for spotting a bad rollout: if a newly-flipped default model causes errors, the fix is a row update that clears within minutes.
Six providers under one interface
The bottom of the router — src/modules/api/services/ai/llm/index.ts — wraps six SDKs so the caller only ever gets back a LanguageModelV2:
- DeepSeek native via
@ai-sdk/deepseek— used for the main code generation path, with reasoning enabled - OpenRouter via
@openrouter/ai-sdk-provider— the swiss-army-knife route to Anthropic, Google, Meta, xAI, and open-weights models like Kimi and Qwen - AWS Bedrock via
@ai-sdk/amazon-bedrock— for enterprise deployments that need in-region inference - Google Vertex AI via
@ai-sdk/google-vertex— Gemini-family models - Azure via
@ai-sdk/azure— both OpenAI and Anthropic models with Azure's compliance posture (the code even sniffs the model ID to instantiate the right sub-client) - Anthropic direct via
@ai-sdk/anthropicand@anthropic-ai/sdk— Claude with full prompt-caching support
Every one of these is a factory that produces the same interface. Whether the current MAIN_GENERATION resolves to DeepSeek V4 Pro, Claude Sonnet 4, or a Gemini 2.5, the code downstream in generate-v3 doesn't branch — it just calls streamText.
People Also Ask
Why not just use one big model like Claude Opus for everything?
Cost and latency. A frontier model priced at $15 per million output tokens is overkill for sentiment classification or file-listing tool calls, and it's slower than smaller models on tasks where reasoning depth isn't the bottleneck. RapidNative's router lets each task pick a model sized for that task, which cuts blended cost dramatically without lowering the quality bar on the calls that actually need reasoning.
How does RapidNative handle a new model release?
An admin inserts a new row into the ai_agents table with the new model ID, provider, and pricing, then flips is_default = true when they're ready to roll it out. The five-minute cache TTL means the switch propagates across every region within minutes. No code change, no deploy, no client update — the router treats models as data.
What happens if a provider goes down?
The named-agent resolver falls back through a chain: named → default → the hardcoded 'slow' name → first active agent. For provider-level outages, OpenRouter's provider_order field lets a single logical model route to multiple sub-providers (e.g. ['alibaba', 'deepinfra']), so a Kimi or Qwen call automatically fails over between inference backends without changing the model ID.
The design principle underneath all of this
If there's one takeaway that matters more than any specific model choice, it's this: treat model selection as configuration, not code. Names, pricing, provider routing hints, and default flags all live in a table you can edit from an admin panel. The application code asks for a model by purpose and gets back an interface. That decoupling is what lets RapidNative move as fast as the frontier does — swap DeepSeek for Kimi K2.5, or add GLM 5.1 alongside, or try Claude Sonnet 4.5 on 10% of traffic, all without touching the generation code.
Multi-LLM code generation isn't a matter of "picking the best model." It's a matter of picking the right model for the right slice of work, and making that choice cheap to change. RapidNative does both — which is why the same prompt that produces a working React Native app today can quietly get better every time a new model ships, without a single line of generate-v3 changing.
If you want to see the output of that router in action, start building a mobile app for free — 20 credits, no card, real Expo project export at the end.
Ready to build your app?
Turn your idea into a production-ready React Native app in minutes.
Free tools to get you started
Free AI PRD Generator
Generate a professional product requirements document in seconds. Describe your product idea and get a complete, structured PRD instantly.
Try it freeFree AI App Name Generator
Generate unique, brandable app name ideas with AI. Get creative name suggestions with taglines, brand colors, and monogram previews.
Try it freeFree AI App Icon Generator
Generate beautiful, professional app icons with AI. Describe your app and get multiple icon variations in different styles, ready for App Store and Google Play.
Try it freeFrequently asked questions
What is RapidNative?
RapidNative is an AI-powered mobile app builder. Describe the app you want in plain English and RapidNative generates real, production-ready React Native screens you can preview, edit, and publish to the App Store or Google Play.
Can I export the code?
Yes. RapidNative generates clean React Native and Expo code that you can export at any time. No lock-in, no proprietary format. Hand it to your developers or keep building inside RapidNative.
Is RapidNative free to use?
Yes. You can build apps on the free plan with no credit card required. Paid plans unlock unlimited AI generations, code export, and direct publishing to the App Store and Google Play.
Do I need to know how to code?
No. Most users build apps by describing what they want in plain English. Developers can drop into the code whenever they want more control, but coding is optional.
How long does it take to build an app?
Most users have a working first screen in under a minute. A full MVP usually takes a few hours instead of the weeks or months traditional development requires.