Building a Four-Tier Parallel RAG Pipeline with Gemini BotForge, an AI no-code chatbot platform, built a four-tier parallel RAG pipeline using Gemini's gemini-embedding-2 model to handle messy user queries. The system runs semantic search, MongoDB full-text search, regex keyword search, and fuzzy matching simultaneously via Promise.all, merging results with weighted scoring to improve retrieval accuracy over naive vector search alone. When building BotForge, our AI no-code chatbot platform, we needed a retrieval system that could handle messy, real-world user queries — typos, partial phrases, semantically similar-but-differently-worded questions. A naive vector search alone wasn't good enough. It's powerful but brittle to out-of-vocabulary terms and exact keyword lookups. We ran four retrieval strategies simultaneously using Promise.all\ , then merged results with a weighted scoring function. \ javascript const semanticResults, textResults, regexResults, fuzzyResults = await Promise.all semanticSearch query, embeddings , // weight 1.8x mongoFullTextSearch query , // weight 1.5x regexKeywordSearch query , // weight 1.0x fuzzyPerWordMatch query , // weight 0.6x \ \ Using Gemini gemini-embedding-2\ to produce 3072-dimensional vectors , we compute cosine similarity against stored document embeddings. This catches meaning — "how do I reset my login?" matches "account recovery options" even with no shared words. A native MongoDB Atlas text index for fast, exact keyword hits. Great for technical terms, product names, and precise phrases. Each significant word in the query is compiled to a case-insensitive regex. Catches partial matches and hyphenated variants. Levenshtein distance matching per query word — handles typos and misspellings like "configuraton" → "configuration". Each result carries a base score from its retrieval strategy. We deduplicate by chunk ID, sum scores across strategies, and sort descending: \ javascript function mergeResults tiers, weights { const scoreMap = new Map tiers.forEach results, i = { results.forEach { id, score, chunk } = { const weighted = score weights i scoreMap.set id, { chunk, total: scoreMap.get id ?.total || 0 + weighted, } } } return ...scoreMap.values .sort a, b = b.total - a.total .slice 0, 5 } \ \ The parallel architecture was the key insight — running all four strategies concurrently keeps latency close to the slowest individual strategy semantic search rather than multiplying it.