{"slug": "how-i-built-a-multi-tenant-rag-knowledge-base-with-source-cited-answers-pipeline", "title": "How I Built a Multi-Tenant RAG Knowledge Base with Source-Cited Answers — Pipeline, Multi-Tenancy, and Lessons", "summary": "KnowBase AI, a multi-tenant SaaS knowledge base built by a developer, enables businesses to upload documents and receive AI-generated answers grounded in their own content, complete with clickable source citations. The system implements a RAG pipeline with chunking, workspace-scoped retrieval for tenant isolation, and a provider-agnostic design supporting OpenAI, Gemini, and Claude, plus a demo mode for keyless testing. The developer highlights that the key differentiator is source-cited answers, which separates a gimmick from a trusted support tool.", "body_md": "Every \"build a RAG chatbot\" tutorial ends the same way: embed a few paragraphs, call `similaritySearch`\n\n, print the answer. That gets you a demo, not a product. The gap between a RAG demo and a RAG product you'd trust with a company's documents is where all the real engineering lives.\n\nI built **KnowBase AI**, a multi-tenant SaaS knowledge base where businesses upload documents and an AI assistant answers questions **grounded in their own content** — with source citations you can click. This post covers the RAG pipeline, how multi-tenancy changes the design, and the decisions I'd repeat.\n\nLive demo: [knowbase-ai.netlify.app](https://knowbase-ai.netlify.app) — no login needed, fully functional (it runs in demo mode with mock responses).\n\nRAG sounds simple: retrieve relevant context, feed it to the LLM, get a grounded answer. In production it means:\n\nEach step is a small product on its own. Here's the pipeline.\n\nDocuments arrive as files, URLs, or manual entries. The key decision is chunking — too big and retrieval is fuzzy, too small and you lose context. The pipeline chunks text with overlap so no meaning falls through the gaps:\n\n```\nexport function chunkText(text: string, size = 800, overlap = 200): string[] {\n  const chunks: string[] = [];\n  let i = 0;\n  while (i < text.length) {\n    chunks.push(text.slice(i, i + size));\n    i += size - overlap;\n  }\n  return chunks;\n}\n```\n\nEach chunk becomes a `DocumentChunk`\n\ntied to its source document, so retrieval can always trace back to where the information came from.\n\nA single-user RAG app and a multi-tenant SaaS share almost no code after the demo stage. Every query, chunk, and conversation must be scoped to a workspace:\n\n```\n// Every AI retrieval is scoped by workspaceId — a tenant can never\n// retrieve another tenant's chunks, even if the embedding matches.\nexport async function retrieve(workspaceId: string, query: string) {\n  return prisma.documentChunk.findMany({\n    where: {\n      document: { source: { workspaceId } },\n      text: { contains: query },\n    },\n    take: 5,\n  });\n}\n```\n\nThe data model enforces isolation at the schema level:\n\n`Workspace`\n\n— tenant container`WorkspaceMember`\n\n— roles: Owner / Admin / Member (RBAC via NextAuth.js v5)`KnowledgeSource`\n\n+ `Document`\n\n+ `DocumentChunk`\n\n— the RAG layer, always under a workspace`Conversation`\n\n+ `Message`\n\n— chat sessions, scoped per workspace`ApiUsage`\n\n— token tracking per workspaceOne of the best decisions: never hard-code a model. A thin provider interface means the product runs on OpenAI, Google Gemini, or Anthropic Claude by configuration, and it made the **demo mode** trivial:\n\n```\nexport interface AIProvider {\n  chat(messages: Message[]): AsyncIterable<string>;\n}\n\nexport const providers = {\n  openai: OpenAIProvider,\n  gemini: GeminiProvider,\n  claude: ClaudeProvider,\n};\n\n// No API keys configured? Run a fully functional mock.\nexport function getProvider(): AIProvider {\n  const configured = Object.entries(providers)\n    .find(([, P]) => new P().isConfigured());\n  return configured ? new configured[1]() : new MockProvider();\n}\n```\n\nDemo mode was the reason I could publish a real demo without leaking keys or asking visitors to sign up.\n\nAn AI answer with no receipts is just a guess. KnowBase streams responses over SSE and attaches the chunks that informed each answer, so users can click through to the source document:\n\n```\n// Simplified — stream tokens, then emit the citations that grounded them\nexport async function chat(conversationId: string, content: string) {\n  const chunks = await retrieve(workspaceId, content);\n  const stream = await provider.chat([\n    { role: \"system\", content: buildRagPrompt(chunks) },\n    ...history,\n    { role: \"user\", content },\n  ]);\n\n  return new Response(sse(stream, chunks), {\n    headers: { \"Content-Type\": \"text/event-stream\" },\n  });\n}\n```\n\nThis single feature separates a gimmick from a support tool. Customer-support teams don't trust \"trust me\" — they trust a cited answer they can verify in two clicks.\n\n| Layer | Technology |\n|---|---|\n| Framework | Next.js 16 (App Router, Turbopack) |\n| Language | TypeScript (strict) |\n| Database | SQLite + Prisma 7 (`@prisma/adapter-libsql` ) |\n| Auth | NextAuth.js v5 (Auth.js) + JWT |\n| AI | OpenAI / Gemini / Claude (provider abstraction) |\n| Styling | Tailwind CSS v4 + shadcn/ui |\n| Charts / Markdown | Recharts + React Markdown (remark-gfm) |\n| Forms | React Hook Form + Zod |\n| Deployment | Netlify |\n\nI'm a full-stack developer at [RA Technologies](https://ratechnologies.netlify.app), where we build SaaS and AI products with exactly this architecture — one codebase, strict types, a database that needs no server, and a demo anyone can click.\n\n`@prisma/adapter-libsql`\n\ngives you a real production-ish setup with zero config. Swap to PostgreSQL later only if you actually need it — most knowledge-base products don't.`workspaceId`\n\nat the query layer, never rely on the UI to filter.If you've built RAG in production, what did I get wrong? I'd love to hear it in the comments.", "url": "https://wpnews.pro/news/how-i-built-a-multi-tenant-rag-knowledge-base-with-source-cited-answers-pipeline", "canonical_source": "https://dev.to/raja-abbas-affandi/how-i-built-a-multi-tenant-rag-knowledge-base-with-source-cited-answers-pipeline-multi-tenancy-22oo", "published_at": "2026-08-15 08:41:03+00:00", "updated_at": "2026-08-15 09:11:51.400284+00:00", "lang": "en", "topics": ["artificial-intelligence", "large-language-models", "ai-products", "ai-tools", "developer-tools"], "entities": ["KnowBase AI", "OpenAI", "Google Gemini", "Anthropic Claude", "NextAuth.js", "Netlify"], "alternates": {"html": "https://wpnews.pro/news/how-i-built-a-multi-tenant-rag-knowledge-base-with-source-cited-answers-pipeline", "markdown": "https://wpnews.pro/news/how-i-built-a-multi-tenant-rag-knowledge-base-with-source-cited-answers-pipeline.md", "text": "https://wpnews.pro/news/how-i-built-a-multi-tenant-rag-knowledge-base-with-source-cited-answers-pipeline.txt", "jsonld": "https://wpnews.pro/news/how-i-built-a-multi-tenant-rag-knowledge-base-with-source-cited-answers-pipeline.jsonld"}}