Inspiration
Ask anyone who's bought a home and they'll describe the same thing: a fog. You don't know what rate you'll get until you've handed over your life in PDFs. "Points," "DTI," "escrow" — a vocabulary designed to make you defer to someone else. And the loop is brutally slow: a question that has a real, computable answer ("can I afford a \$600k home at my income?") takes days, a credit pull, and three voicemails with a loan officer.
We kept coming back to one belief: the information to answer that question already exists — rate sheets, program guidelines, LLPA matrices, thousands of pages of mortgage content — it's just locked behind jargon and latency. If we could ground a language model in that corpus and wire it to a live pricing engine, we could turn a week-long, anxiety-soaked process into a conversation that answers in seconds. That's what we set out to build.
What it does
SRK CAPITAL AI is an AI mortgage advisor. You talk to it in plain English and it does the work a loan officer would — instantly and transparently:
- Conversational Q&A grounded in real content. Ask "what's the difference between a 7/1 ARM and a 30-year fixed for someone planning to move in 5 years?" and it answers from an indexed knowledge base, with citations — not hallucinations.
- Live, personalized rates. It runs an actual pricing engine: base rate + loan-level price adjustments (LLPAs) for credit score, LTV, occupancy, and product, then surfaces the real number.
- Program matching. It maps your situation to the loan programs you actually qualify for — conventional, FHA, VA, non-QM, bank-statement — instead of a generic list.
- A path to pre-approval. It walks you from "curious" to "ready," collecting only what's needed, when it's needed.
- Semantic search over the whole corpus. Under everything is a vector search layer so answers are retrieved from ~1,500 embedded content chunks across ~780 documents, ranked by relevance.
How we built it
We built it as a layered, contract-safe system so the AI never gets ahead of the data.
Stack: Next.js 16 + React + TypeScript on the front; a Hono edge-API layer with Zod-validated request/response schemas; Supabase/Postgres with pgvector for storage and retrieval; OpenAI text-embedding-3-small for embeddings; a multi-model reasoning layer that routes between OpenAI's GPT and Anthropic's Claude through the AI SDK's provider registry; Cloudflare Workers and Vercel for the runtime. And we shipped this fast because we built it with AI in the loop — using OpenAI Codex as a pair-programmer to scaffold routes, generate tests, and hunt down bugs.
1. The retrieval layer. Content is chunked, and each chunk is embedded and stored as a vector. At query time we embed the user's question and rank chunks by cosine similarity:
$$\text{similarity}(q, d) = \cos(\theta) = \frac{\mathbf{q}\cdot\mathbf{d}}{\lVert \mathbf{q}\rVert\,\lVert \mathbf{d}\rVert} = \frac{\sum_{i=1}^{n} q_i d_i}{\sqrt{\sum_{i=1}^{n} q_i^2}\,\sqrt{\sum_{i=1}^{n} d_i^2}}$$
Retrieval runs as a Postgres RPC over pgvector, returning the best chunk per document above a similarity threshold.
2. The pricing engine. Rather than quote a marketing rate, we compute an effective rate from the base sheet plus loan-level adjustments:
$$r_{\text{eff}} = r_{\text{base}} + \sum_{i} \text{LLPA}_i(\text{FICO}, \text{LTV}, \text{occupancy}, \dots) - \text{SRP}$$
and translate it into a monthly payment with the standard amortization formula, so the number the user sees is the number they'd actually pay:
$$M = P \cdot \frac{r\,(1+r)^{n}}{(1+r)^{n} - 1}$$
where \(P\) is the principal, \(r\) the monthly rate (\(r_{\text{eff}}/12\)), and \(n\) the number of payments.
3. The intelligence layer. The orchestrator is model-agnostic: it routes between GPT and Claude depending on the task — GPT for fast structured extraction and tool arguments, Claude for long-form, grounded synthesis — behind a single tool-calling loop. It decides when to search the corpus, when to hit the pricing engine, and when to ask a clarifying question, then composes a cited answer. Because it's provider-agnostic, a rate limit or outage on one model degrades gracefully to the other.
4. Guardrails. The frontend and backend live in separate repos, so we built a contract harness — 67 automated checks that verify every request/response shape matches field-for-field. If a schema drifts, CI fails before a user ever hits a broken endpoint.
Challenges we ran into
Retrieval nearly killed the project — in three acts.
Act 1 — search returned nothing. Documents were indexed and embedded, direct lookups worked, but every semantic query came back empty. We stopped guessing and measured the actual similarity distribution: with text-embedding-3-small, genuinely relevant matches score only 0.28–0.47, with a clean noise floor around 0.05–0.12. Our match_threshold was set to 0.6 — above anything the model could ever produce. The filter was mathematically guaranteed to return zero. We recalibrated to 0.25, sitting in the gap between signal and noise.
Act 2 — search returned garbage. With results flowing, a new problem: "VA loans" and "refinance" returned the same documents, including a random new-construction listing. The bug was in the SQL:
-- BEFORE: DISTINCT ON forces ORDER BY to lead with document_id,
-- so LIMIT takes the alphabetically-first matches, not the closest.
SELECT DISTINCT ON (chunk.document_id) ...
ORDER BY chunk.document_id, chunk.embedding <=> query_embedding
LIMIT match_count;
We were picking the best chunk per document correctly, but then returning documents in alphabetical order and cutting off with LIMIT — so "12 Alternative Loan Programs," "3/1 ARM," and "40-Year Mortgage" won every query by sort order, not relevance. The fix was to rank after deduping:
-- AFTER: dedupe per document, then rank the survivors by similarity.
SELECT * FROM (
SELECT DISTINCT ON (chunk.document_id) ..., similarity
FROM ... ORDER BY chunk.document_id, distance
) ranked
ORDER BY ranked.similarity DESC
LIMIT match_count;
The instant we shipped it, "VA loans" returned VA Loans (0.71) → Maximizing Your VA Loan Benefits → IRRRL → VA Loan Disqualifiers. Perfect.
Act 3 — a silent false-green. The ingest pipeline was reporting documents as successfully indexed even when zero of their chunks had embeddings — a blank chunk shifted every later embedding onto the wrong row and dropped the tail, and any provider error was swallowed to an empty array. A total embedding outage looked like 774/774 success with nothing searchable. We made the pipeline honest: position-align embeddings and report a zero-embedding document as failed, so a real problem surfaces instead of hiding.
Beyond retrieval: we chased a circular-import bug where a telemetry hook could turn a clean 401 into a 500 under load, fought Node heap OOMs in CI, and kept two repos' contracts in lockstep as the API evolved. Every one of these was invisible until we instrumented it.
Accomplishments that we're proud of
- Sub-second, grounded answers over a ~1,500-chunk corpus with genuinely relevant ranking.
- A self-healing embedding pipeline with a resumable backfill worker that sweeps any chunk that fails to embed, so search converges to complete on its own.
- A 67-check cross-repo contract harness that makes frontend/backend drift a build failure, not a production incident.
- A model-agnostic brain that routes between GPT and Claude and degrades gracefully when either is down.
- Real pricing, not vibes — the rate we quote is derived from the same LLPA/amortization math a lender uses.
What we learned
The biggest lesson: "search returns results" and "search returns the right results" are different milestones, and the gap between them is where products die. We learned that embedding models each occupy their own similarity range, so thresholds must be measured against real data, never guessed — and that correct ranking matters more than a fancier model. We also learned that leaning on GPT and Claude for different jobs — and on Codex to build — let a small team move at the speed of a much larger one. And we relearned an old truth the hard way: the bugs that hurt most are the silent ones. Instrumenting the system — measuring score distributions, making failures loud — beat every assumption we brought in.
What's next for SRK CAPITAL AI
- Hybrid retrieval — combine keyword (BM25) with vector search for exact-term precision on program names and numbers.
- Streaming answers so the advisor feels like a conversation, not a wait.
- End-to-end pre-approval — submit directly from the same chat.
- Personalized rate alerts — watch the market and ping users when their target payment becomes reachable.
Built With
- anthropic
- hono
- javascript
- next.js
- node.js
- openai
- pgvector
- postgresql
- python
- react
- redis
- rest-apis
- sql
- supabase
- typescript
- zod
Log in or sign up for Devpost to join the conversation.