Papi AI.
An AI dating wingman on the App Store for iPhone. A React Native app talks to a FastAPI backend that streams replies over SSE, reads screenshots, transcribes voice, and keeps per-match memory, running on hosted Groq models behind fallback chains.
The Goal
Papi started as a single-file dating-app helper and grew into two layers that ship together: a mobile app people open on their phone, and a FastAPI backend on Vercel that calls hosted models on Groq.
The interesting engineering isn't "Papi answers Tinder messages." It's keeping a consumer app working while the hosted models underneath it change. Every release coordinates a mobile and a backend update, and the backend has to keep answering when the provider retires the model it was calling.
Two Layers and a Fallback Plan
papi-ai-app
React Native on Expo SDK 54. Live on the App Store since 2026-05-15. Voice mode with 30+ HD voices, screenshot replies, profile openers, per-match memory, and a translator for 20+ languages.
papi-ai-web
FastAPI on Vercel with SSE streaming, vision, whisper-large-v3 voice transcription, and per-match memory. Version-tagged rollouts; the health endpoint reports the version, so a stale deployment shows up immediately.
fallback chains
Chat and vision each walk an ordered chain of hosted Groq models. A retired model is skipped, a rate limit falls through to the next model, and the chains are environment variables, so a model swap is a config change.
Architecture
The app calls one FastAPI backend on Vercel. For each request the backend uses the first model in the chain that is still available and not rate-limited, so the mobile app never needs to know which model answered.
[ User on iPhone ] │ React Native (Expo) │ HTTPS / SSE / multipart ▼ Vercel Functions FastAPI (papi-ai-web) │ ┌─────────────────┬────────┴────────┬─────────────────┐ ▼ ▼ ▼ ▼ Groq Groq Whisper-V3 Memory chat chain vision chain voice transcribe per match
When the Models Went Away
In August 2026 the hosted provider retired the vision models and then the text models the app depended on, and image requests started failing with "model does not exist". Swapping in one new model name would only have reset the clock.
- Fallback chains. Chat and vision each walk an ordered list of models instead of calling one.
- Skip what's gone. A model the API reports as retired is dropped for the rest of the process, so the next request goes straight to a working one.
- Fail over on rate limits. Each model has its own token bucket, so a 429 falls through to the next model instead of surfacing an error.
- Config, not code. The model lists are environment variables, so the next retirement is a settings change.
The Hard Bugs
iPhone HEIC + Vercel's 4.5 MB body cap
iPhones photograph in HEIC. Vercel Functions cap multipart bodies at 4.5 MB. A modern iPhone HEIC plus form metadata = 5–8 MB easily. The mobile client now handles both the format flip (HEIC → JPEG re-encode) and a 1600 px resize before upload — and the error strings on the server differentiate which layer failed so the next bug doesn't take an hour to bisect.
Whisper Turbo's English-only failure mode
Initial voice transcription used whisper-large-v3-turbo. Speed was great; multilingual quality was abysmal — non-English voice notes came back as confident-sounding nonsense. Switched to whisper-large-v3 with explicit language hint + a domain-specific prompt bias. Real bug, real fix, permanent note.
Reanimated 4 + missing Worklets dep
Upgrading to Reanimated 4 silently requires react-native-worklets as a peer dep. Missing it surfaces as a misleading "unable to resolve the native app" in Expo Go that doesn't mention worklets. The fix is one line in package.json; the diagnostic is the cost.
All Service Leads →
Roofing-lead marketplace with live Stripe billing, Twilio SMS and Resend email, plus a write-up of a real production incident.