Under the hood

How PayWise actually works

A LangGraph agent that turns a plain-English purchase into an auditable, math-backed card recommendation — five deterministic steps, traced end to end.

High-level architecture
BrowserNext.js SPApaywise-frontendCloud Run · Next.js 14paywise-backendCloud Run · FastAPI + SSEQdrant Cloudvector search, payload-filteredNeon Postgresusers, cards, ownershipJina AIembeddings + rerankerOllama CloudQwen3 LLM (swappable)Google OAuthreal sign-in, JWT sessionLangfusetrace every node, per query
CI/CD — every push, not every laptop
git pushdeveloper machine
GitHubpath-filtered triggers
Cloud Buildbuild + push image
Artifact Registrykeep last 3 tags
Cloud Runnew revision live

Two path-filtered Cloud Build triggers watch this repo — a backend-only change never rebuilds the frontend image, and vice versa. Nothing reaches Cloud Run except through this pipeline.

Low-level: the 5-node LangGraph pipeline
extract_intent
LLM call
02
retrieve_rules
Vector search
03
rerank
Cross-encoder
04
calculate_math
Pure Python
05
generate_response
LLM call
01LLM call

extract_intent

Qwen3 reads your free-text query and pulls out a structured {merchant, amount} — the only place natural language enters the pipeline.

in
“₹300 movie tickets on BookMyShow”
out
{ "merchant": "bookmyshow", "amount": 300 }
Engineering decisions
Math stays deterministic
The ₹ number that actually matters never touches an LLM — it's regex + arithmetic over the rule text, so it's auditable and can't hallucinate.
Per-user isolation at the query level
Qdrant's payload filter restricts retrieval to your owned card_ids inside the search itself — not a post-filter in application code that could be forgotten.
Lighter tools, chosen on purpose
pypdf/python-docx over `unstructured` (tested against a real bank T&C PDF first), and a lightweight Langfuse @observe decorator instead of pulling in all of langchain for one CallbackHandler.
Evals over vibes
A 15-query golden dataset scores intent extraction, retrieval accuracy, and reward math independently — it already caught a real quota outage and a math-node edge case pre-launch.
📈
Every trace is public-API verifiable
Each of the 5 nodes above is a real Langfuse span in production — latency, cost, and I/O per step, for every query that's ever been asked.