A production-grade FinTech RAG chatbot that answers credit card questions for both the US and India using live web data. When its cache goes stale, a search agent fetches fresh card data from the web, extracts it into a structured schema with an LLM, and re-indexes it for hybrid retrieval — all on free-tier services.
[User query]
│
▼
[Local regex router] intent + region + card type — 0 API calls
│
▼
[Web search agent] only for card questions, and only if this
│ (region, card type) slice is > 24h old:
│ Tavily search → LLM extraction → Supabase upsert → FAISS rebuild
▼
[Hybrid retrieval] SQL filters (Supabase) + semantic search (FAISS)
│
▼
[Streaming generation] multi-provider LLM chain with automatic failover:
│ Groq llama-3.3-70b → Cerebras gpt-oss-120b → Gemini flash-lite
▼
[React frontend] SSE token stream, markdown tables, region selector
Key properties
- Quota-proof — every LLM call walks a fallback chain of free providers; rate-limited providers are skipped automatically. Repeated queries are served from a response cache at zero cost, and card embeddings are cached by content hash so re-indexing only pays for what changed.
- Multi-region — US cards (USD, APR, signup bonuses) and Indian cards (INR, joining fees, fuel surcharge waivers, milestone benefits, LTF) are first-class citizens.
- Self-updating — no scheduled scrapers; user queries trigger fresh searches when data is stale (24h TTL per region and card type, with a 6h backoff when a search finds nothing).
- Never crashes the chat — search, extraction, and storage failures all degrade gracefully to cached data.
| Layer | Technology |
|---|---|
| Backend | FastAPI + uvicorn, SSE streaming, Python 3.12+ |
| LLMs | Groq / Cerebras / Gemini via OpenAI-compatible endpoints (app/llm.py) |
| Web search | Tavily API |
| Relational DB | Supabase PostgreSQL |
| Vector search | FAISS (local) + gemini-embedding-001 (3072-dim) |
| Frontend | React 19, Vite, Tailwind v4, react-markdown |
cd backend
python -m venv venv
venv\Scripts\activate # Windows (source venv/bin/activate on Unix)
pip install -r requirements.txt # add -r requirements-dev.txt for tests/scraper
copy .env.example .env # then fill in your keys
python run.py --reloadGet free API keys: Groq · Cerebras · Gemini (required, used for embeddings) · Tavily · Supabase
First-time database setup: run backend/docs/schema.sql in the Supabase SQL Editor (or python -m scripts.migrate with SUPABASE_DB_PASSWORD set), then optionally seed US cards with python -m scripts.seed_data.
cd frontend
npm install
npm run dev # http://localhost:5173cd backend
pytest # unit tests, no API calls
RUN_INTEGRATION=1 pytest # adds live Tavily + LLM integration testsThe repo ships with a render.yaml blueprint:
- Push to GitHub, then in Render: New → Blueprint → select the repo.
- Fill in the secret env vars when prompted (
GROQ_API_KEY,GEMINI_API_KEY,TAVILY_API_KEY,SUPABASE_URL,SUPABASE_ANON_KEY, …). - After the first deploy, note the service URL (e.g.
https://cardai-api.onrender.com).
Railway works too — a backend/Procfile is included; set the same env vars.
- Import the repo, set the project root to
frontend/. - Set one env var:
VITE_API_URL=https://<your-backend-url>(no trailing slash). - Deploy, then add the resulting frontend URL to the backend's
ALLOWED_ORIGINSenv var (comma-separated) and redeploy the backend.
| Variable | Purpose |
|---|---|
ALLOWED_ORIGINS |
Comma-separated frontend origins for CORS |
ADMIN_API_KEY |
When set, /api/ingest and /api/cache/clear require the X-Admin-Key header |
GENERATION_CHAIN / EXTRACTION_CHAIN |
Override the LLM fallback order (provider:model,…) |
CARD_CACHE_TTL_HOURS |
Regional search cache TTL (default 24) |
| Endpoint | Description |
|---|---|
GET /health |
Liveness + LLM provider status |
POST /api/chat |
SSE chat — {messages, session_id, region: "US"|"IN"|"BOTH"} |
GET /api/cache/stats |
Response-cache stats |
POST /api/cache/clear |
Clear response cache (admin) |
POST /api/ingest |
Legacy static scraper ingest (admin, dev-only) |