work · retrieval

using nlp to filter through prayers

duaos · next.js · supabase pgvector · mastra · openai · github →

intent on the heart embed 1536-d + hyde blend pgvector cosine names quran hadith refine du'a prophetic style
matched by meaning, not keywords. a guaranteed one of each category so you're never left empty-handed, and the arabic is only ever quoted from what was retrieved, never invented.

you carry an intention you can barely name, a worry, a hope, a goal, and somewhere in the ninety-nine names of allah, the quran, and the hadith there is language already shaped for exactly that. duaos is built to close that gap by meaning, not keywords: you describe what's on your heart, it finds the names and verses that match, and then it helps you refine a du'a in a prophetic style. i wanted it to be technically serious precisely because the subject is sacred. retrieval that touches scripture has to refuse to lie.

cosine search, in the database

every name, verse, and narration is embedded into a 1536-dimension vector and stored in postgres with pgvector. the match happens in sql, cosine distance via the <=> operator, with the similarity returned as 1 - distance and a jsonb containment filter that lets one function serve both the open search and the typed fallbacks.

SELECT id, content, metadata,
       1 - (embedding <=> query_embedding) AS similarity
FROM spiritual_assets
WHERE embedding IS NOT NULL
  AND (filter_metadata = '{}' OR metadata @> filter_metadata)
  AND (1 - (embedding <=> query_embedding)) >= match_threshold
ORDER BY embedding <=> query_embedding
LIMIT match_count;

that @> containment operator is the quiet hero, it means a single rpc handles "find anything relevant" and "find me exactly one bukhari hadith" without a second function. if a category comes back empty, three parallel queries with the threshold dropped to zero guarantee you always get at least one name, some verses, and some hadith. the user is never left empty-handed.

hyde: hallucinate, then search with it

short queries are the hard case. "i'm anxious" is three words and barely enough signal for a vector to grab. so for any query under twelve words, duaos uses a technique called hyde, hypothetical document embeddings: it asks a model to write a short, made-up du'a-style passage for the query, embeds that, and averages the two vectors element-wise.

export function blendEmbeddings(queryEmbedding, hydeEmbedding) {
  if (queryEmbedding.length !== hydeEmbedding.length) return queryEmbedding;
  return queryEmbedding.map((q, i) => (q + hydeEmbedding[i]) / 2);
}

you're not searching with the user's three words, you're searching with the richer shape of what they probably mean. the hallucination is a throwaway, never shown, used only to aim the search. if it fails, the code falls straight back to the raw embedding.

retrieval here is less a speed trick than a discipline: the model may only speak from what it was given, and what it was given is the actual text.

reranking for du'a, against jurisprudence

raw similarity has a failure mode here that matters: a legal ruling about marriage scores high on a query about wanting a spouse, but it isn't a du'a, it's fiqh. so a deterministic rerank multiplies the score by lexical signals, boosting supplication language and penalizing rulings and bare narrator fragments.

if (hasDuaSignal(m.content))          multiplier *= 1.2;   // "o allah", "grant me"
if (hasJurisprudenceSignal(m.content)) multiplier *= 0.75;  // "the ruling", "iddah"
if (isShortNarratorOnly(m.content))    multiplier *= 0.75;
sim = Math.min(1, sim * multiplier);

then a small model does a final relevance pass to drop the tangential, and on any error it returns everything unfiltered. graceful degradation at every llm step, because the worst outcome is a blank page when someone came looking for words to pray.

the rule that matters most: never invent arabic

this is the line i care about more than any latency number. the refine agent runs on a strict instruction: arabic script is allowed only when copied verbatim from a provided snippet. it must not generate, reconstruct, or transliterate into arabic on its own. a model confidently producing fake quranic arabic would be worse than useless, it would be a small act of corruption. so the prophetic-style refinement happens in the user's language, grounded in real retrieved text, and the sacred script is quoted, never synthesized. there's even a hardcoded prior: ask about a spouse or provision and the du'a of musa, rabbi inni lima anzalta ilayya min khairin faqir (al-qasas 28:24), gets injected into context, because some matches are too well-established to leave to a vector.

retrieval-augmented generation is usually sold as a way to make models more accurate. here it's something closer to a discipline: the model may only speak from what was given, and what was given is the actual text. the prophet ﷺ taught that du'a is the very essence of worship. the least i could do was build the tool so it never puts words in god's mouth. آمين.

next · goosetype → all work →