
Your RAG chatbot demo worked flawlessly. You loaded a few PDFs into a vector store, wired up an embedding model, and the LLM answered every test question with impressive accuracy. Then you deployed it—and within a week, users were getting irrelevant answers, stale information, and hallucinated responses that your support team had to clean up.
This gap between demo and production is not a model problem. It is an architecture problem. Most RAG chatbots fail in production because they are built as a single monolithic call to an LLM with a static vector store bolted on. Real users ask unpredictable questions. Your data changes constantly. Multiple knowledge sources need to be queried. And when the AI does not know something, it should say so—not fabricate an answer.
This guide breaks down the seven most common production failures in RAG systems and provides a practical TypeScript architecture blueprint to solve them. Whether you are building a customer support agent for a SaaS product or an internal knowledge assistant, these patterns will help you move from a fragile demo to a production RAG chatbot your team can trust.
Failure 1: Monolithic Architecture vs. Orchestration
The primary reason chatbots fail in production is that they are built as a single, monolithic call to an LLM. A user message goes in, a prompt is assembled, and the LLM generates a response. This works in a demo because you control the inputs. In production, you do not.
A modular RAG architecture separates concerns into distinct layers, allowing each component to be tested, swapped, and scaled independently.
Production systems require an orchestration layer that treats the LLM as one component within a larger, modular architecture. The orchestrator manages state, routes requests, calls tools, retrieves context, and decides when to escalate. Without this layer, every edge case becomes a prompt engineering hack that breaks the next time your data changes.
A modular architecture separates concerns into distinct layers:
- User Interface Layer: Handles multi-channel communication (web, Slack, API).
- Orchestration Layer: Manages logic flow, conversation state, and tool routing.
- Retrieval Layer: Queries multiple data sources and ranks results.
- Generation Layer: Calls the LLM with grounded context and system instructions.
- Guardrail Layer: Validates outputs, checks confidence, and enforces access control.
In TypeScript, this means defining clear interfaces for each layer and composing them rather than hardcoding logic into a single function. Your orchestrator should be able to swap retrieval strategies, change LLM providers, or add a new data source without rewriting the generation logic.
Failure 2: Poor Data Ingestion (Beyond Vector Stores)
A common misconception is that RAG equals vector databases. It does not. RAG stands for Retrieval Augmented Generation—the retrieval part can come from any data source. Production systems often need to pull context from APIs, SQL databases, file systems, and vector stores simultaneously.
Consider a SaaS support chatbot. A user asks, "What is the status of my order #4821?" The answer is not in a vector store. It is in your orders database, behind an API call. If your architecture only supports vector search, the chatbot will either fail to answer or hallucinate a response.
A robust retrieval layer should support multiple source types:
- Vector stores for semantic search over unstructured documents.
- SQL databases for structured queries with exact matches.
- REST or GraphQL APIs for real-time data from external systems.
- File systems for documents that are too large or sensitive to embed.
In TypeScript, define a common interface for retrieval providers:
interface RetrievalProvider {
retrieve(query: string, options?: RetrievalOptions): Promise<RetrievalResult[]>;
}
Then implement specific providers for each data source and let the orchestrator decide which to query based on the user's intent. This approach prevents the brittle, single-source retrieval that causes most production failures.
Failure 3: Weak Retrieval and Stale Content
Even with multiple data sources, retrieval can fail in two ways: it returns irrelevant results, or it returns outdated information.
Weak retrieval happens when teams rely solely on semantic similarity without considering ranking, filtering, or query transformation. A user asking "How do I reset my password?" might get results about password policies, security settings, and account recovery—all semantically similar but not equally relevant. Without a ranking step or reranking model, the LLM receives noisy context and generates a subpar answer.
Stale content is the other side of the problem. Demos often use static documents that never change. In production, your data is constantly evolving—pricing updates, policy changes, new product features. If your vector store is not refreshed on a schedule or triggered by data changes, the chatbot will confidently provide outdated answers.
Solutions include:
- Reranking: Use a cross-encoder or a second LLM call to rerank retrieved chunks by relevance.
- Metadata filtering: Tag documents with timestamps, categories, and access levels so retrieval can filter before ranking.
- Incremental updates: Build a pipeline that detects changed documents and updates embeddings without reprocessing the entire corpus.
- TTL on embeddings: Set a time-to-live on vector entries so stale data is automatically flagged for refresh.
Some developers argue that RAG itself is brittle and lacks scientific rigor. The solution is not to abandon RAG but to add the engineering rigor—ranking, filtering, freshness checks—that makes retrieval reliable.
Failure 4: Hallucinations and Data Leaks
Hallucinations and data leaks are the failures that trigger internal reviews. When a chatbot fabricates an answer, it erodes user trust. When it exposes data the user should not see, it creates a security incident.
Production RAG systems must ground every response in verified data. This means the LLM should only generate answers based on the context it was given, and the system should be able to trace every claim back to a source document.
Key practices to prevent hallucinations:
- Strict system prompts: Instruct the LLM to only use the provided context and to say "I don't know" when the context is insufficient.
- Citation requirements: Ask the LLM to cite the source document or chunk ID for each claim. Validate that citations exist in the retrieved context.
- Confidence scoring: Use the retrieval similarity score or a separate evaluation model to estimate how confident the system is in the answer.
- Output validation: Run a post-generation check to verify that key facts in the response appear in the retrieved context.
To prevent data leaks, enforce access control at the retrieval layer. Before querying any data source, check the user's permissions and filter results accordingly. A user who should only see their own orders should never receive context from another customer's account, even if the LLM would technically have access to it.
Failure 5: Missing Confidence Thresholds and Routing
When a RAG chatbot receives a question it cannot answer, it should not guess. Yet many production systems have no mechanism to distinguish between a confident, well-grounded answer and a speculative one. The result is a chatbot that responds to every query with equal conviction, regardless of whether it actually found relevant information.
Routing logic with confidence thresholds ensures each query reaches the appropriate response path, from instant answers to human escalation.
Confidence thresholds solve this. After retrieval, the system evaluates how relevant the retrieved context is to the user's question. If the similarity score is below a defined threshold, the system does not generate an answer. Instead, it routes the query to an alternative path.
Routing logic becomes especially important when multiple knowledge banks are involved. A production system should distinguish between:
- Repeat questions that have approved, pre-written answers.
- Predictable requests that follow a guided flow or workflow.
- Open questions that require retrieval from business knowledge.
- Complex conversations that need human intervention.
This is where a modular orchestration layer pays off. The orchestrator can classify the user's intent, check confidence scores, and route accordingly. For example, Fetchply implements this pattern by giving every customer question the right path: repeat questions receive approved Instant Answers, predictable requests follow Guided Flows, open questions use business knowledge, and complex conversations reach the human team. This mirrors the routing logic that production RAG systems need to prevent the chatbot from answering when it should not.
Failure 6: No Human Handoff
No chatbot handles every conversation perfectly. When the AI fails—whether due to low confidence, an out-of-scope question, or a frustrated user—the system must escalate to a human team. Without this fallback, users are stranded, and the chatbot becomes a liability rather than a support tool.
Human handoff is not just a feature; it is a business reliability requirement. A production system should define clear escalation triggers:
- Confidence below threshold: The retrieval score or output validation indicates the answer may be unreliable.
- Out-of-scope detection: The user's question does not match any supported intent or knowledge domain.
- Repeated failures: The user asks the same question multiple times, indicating the previous answers were unsatisfactory.
- Explicit request: The user types a keyword like "agent" or "human" indicating they want to speak to a person.
When escalation triggers, the system should transfer the full conversation context to the human team. This includes the user's message history, the retrieved context the AI was working with, and the confidence scores at each step. The human agent should not have to start the conversation from scratch.
In a TypeScript architecture, this means your orchestrator needs a persistent conversation store and a notification mechanism—whether that is a Slack webhook, an email alert, or a ticket creation in your support system.
Failure 7: Lack of Auditability and Access Control
As RAG systems scale, auditability and access control become critical. Teams need to trace every result back to its source, understand why a particular answer was generated, and manage who can access what data.
Without auditability, a hallucination or data leak becomes impossible to debug. You cannot fix what you cannot trace. Without access control, every user effectively has access to the entire knowledge base, which is unacceptable in most B2B and enterprise contexts.
Production systems should log:
- The user's original query.
- The retrieval strategy used and the chunks returned.
- The prompt sent to the LLM, including system instructions and context.
- The LLM's raw response and any post-processing applied.
- The confidence score and routing decision.
This log should be queryable so teams can investigate specific conversations, identify patterns of failure, and improve the system over time.
Access control should be enforced at the retrieval layer, not the generation layer. Filter documents by the user's role, organization, or permissions before they enter the LLM's context window. Once data is in the prompt, you cannot reliably prevent the LLM from surfacing it.
Practical TypeScript Architecture for Production RAG
Putting it all together, here is a blueprint for a modular, auditable RAG architecture in TypeScript and Node.js.
// Core interfaces
interface QueryContext {
userId: string;
userRole: string;
conversationId: string;
message: string;
history: Message[];
}
interface RetrievalResult {
content: string;
source: string;
score: number;
metadata: Record<string, unknown>;
}
interface Orchestrator {
handleQuery(ctx: QueryContext): Promise<Response>;
}
// The orchestrator coordinates all layers
class RAGOrchestrator implements Orchestrator {
constructor(
private retrievers: RetrievalProvider[],
private generator: GenerationProvider,
private guardrails: GuardrailLayer,
private logger: AuditLogger,
private handoff: HumanHandoffService
) {}
async handleQuery(ctx: QueryContext): Promise<Response> {
// 1. Classify intent and route
const intent = await this.classifyIntent(ctx.message);
// 2. Retrieve from relevant sources with access control
const results = await this.retrieveWithAccessControl(ctx, intent);
// 3. Check confidence threshold
if (this.belowConfidenceThreshold(results)) {
return this.handoff.escalate(ctx, 'Low confidence score');
}
// 4. Generate with grounded context
const response = await this.generator.generate(ctx, results);
// 5. Validate output against guardrails
const validated = this.guardrails.validate(response, results);
if (!validated.passed) {
return this.handoff.escalate(ctx, validated.reason);
}
// 6. Log everything for auditability
this.logger.log({ ctx, intent, results, response, validated });
return validated.response;
}
}
This architecture addresses all seven failures: the orchestrator prevents monolithic calls, multiple retrievers handle diverse data sources, confidence thresholds gate generation, guardrails prevent hallucinations, the handoff service escalates when needed, and the audit logger ensures traceability.
Deployment Checklist for SaaS Founders and AI Engineers
Before shipping your RAG chatbot to production, verify that your system addresses each of these items:
- Architecture: Is the LLM one component in a modular orchestration layer, not the entire system?
- Data sources: Can the retrieval layer query vector stores, SQL databases, APIs, and file systems?
- Retrieval quality: Is there a reranking step and metadata filtering to improve relevance?
- Content freshness: Is there a pipeline to detect and refresh stale embeddings?
- Hallucination prevention: Does the system prompt restrict the LLM to provided context, and are citations validated?
- Confidence thresholds: Is there a defined score below which the system does not generate an answer?
- Routing logic: Are repeat questions, predictable requests, and open questions routed to different paths?
- Human handoff: Are there clear escalation triggers, and does the handoff include full conversation context?
- Auditability: Is every query, retrieval, generation, and routing decision logged for debugging?
- Access control: Are documents filtered by user permissions before entering the LLM context window?
If any of these items is missing, your chatbot is carrying a production risk that will surface under real user load.
Sources and further reading
- How to Build an AI Chatbot That Actually Works in Production — Not Just in Demo
- Why you should stop building RAG chatbots from scratch
- How I Built a RAG Chatbot in 45 Minutes (No Coding Required)
- RAG is not really a solution - OpenAI Developer Community
- Contact | Fetchply
- 5 Critical Mistakes When Building a RAG Chatbot and How to Avoid Them
- Building RAG Systems: From Zero to Hero
Comments (2)
Login to post a comment.
Igor Ganapolsky
Failure 2 and Failure 3 are usually the same bug wearing two hats, and the fix is boring plumbing rather than better retrieval. Give every chunk a deterministic id derived from source uri plus a content hash, put a unique index on it, and make ingestion an upsert against that id. Without it, a re-crawl of an edited page inserts a near-duplicate instead of replacing the old chunk, and now retrieval returns both the current and the stale paragraph — which reads exactly like "weak retrieval" but is really a missing unique constraint. Two follow-ons that paid off: keep a source-level generation counter so a document that vanished upstream can be tombstoned in one write instead of hunting orphaned chunks, and treat re-embedding after a model change as a versioned backfill (embeddings from two different models in one index quietly wreck your similarity scores, and nothing errors). For Failure 7, the audit row worth storing is the retrieved chunk ids and their scores at answer time, not just the final answer — when someone disputes a response weeks later, the question is always which chunk it read, and that's unrecoverable if the index has since been rebuilt.