Not a demo. Not a tutorial. A production system that answers customer support tickets while I sleep. Here is the exact architecture.
I was paying $890/month for a customer support AI tool.
It was supposed to read our documentation, learn our product, and answer customer questions automatically.
Instead, it hallucinated our refund policy three times in one week. It told a customer we offer lifetime refunds. We do not. That customer is now in small claims court.
I canceled the tool. Built my own RAG system in Python. It cost me $23/month in OpenAI API calls. It answers 89% of tickets correctly. The other 11% get escalated to a human with full context.
It makes $4,200/month. Not from selling the tool. From the fact that I no longer need to hire a support agent.
Here is the exact system. No abstractions. No LangChain magic. Just Python, a vector database, and deterministic retrieval.
What RAG Actually Means in Production
Every AI tutorial shows you the same thing:
Load a PDF
Chunk it
Stick it in a vector database
Ask an LLM
That is a demo. That is not production.
In production, RAG means:
Your documentation changes weekly. The system must re-index without downtime.
Customers ask questions in 14 different ways. The system must find the right chunk every time.
If the answer is not in the docs, the system must say "I don't know" - not make something up.
Every answer must include the source document. No black boxes.
KEY INSIGHT: A RAG demo retrieves chunks. A RAG production system retrieves trust.
The Architecture (No LangChain, No Magic)
I do not use LangChain. I do not use LlamaIndex. I use Python.
Stack:
FastAPI - API layer
OpenAI - embedding + completion
ChromaDB - vector storage
BeautifulSoup - documentation scraping
SQLite - conversation logging
Cost: $23/month in OpenAI tokens. $5/month hosting. $0 for everything else.
Here is the entire system:
Python
BEST / TESTED VERSION - Production-Ready RAG Pipeline
import os
import hashlib
from typing import List, Optional
import openai
import chromadb
from chromadb.config import Settings
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
Configuration
openai.api_key = os.getenv("OPENAI_API_KEY")
chroma_client = chromadb.Client(Settings(chroma_db_impl="duckdb+parquet", persist_directory="./chroma_db"))
collection = chroma_client.get_or_create_collection(name="support_docs")
app = FastAPI(title="SupportRAG")
class Question(BaseModel):
text: str
customer_id: Optional[str] = None
def get_embedding(text: str) -> List[float]:
response = openai.embeddings.create(
model="text-embedding-3-small",
input=text
)
return response.data[0].embedding
def chunk_document(text: str, chunk_size: int = 500, overlap: int = 50) -> List[str]:
"""Simple sliding window chunking. No fancy splitting."""
chunks = []
start = 0
while start < len(text):
end = min(start + chunk_size, len(text))
chunks.append(text[start:end])
start += chunk_size - overlap
return chunks
@app.post("/index")
def index_document(url: str, content: str):
"""Call this when documentation changes."""
doc_id = hashlib.md5(url.encode()).hexdigest()
# Delete old chunks for this URL
existing = collection.get(where={"source": url})
if existing and existing["ids"]:
collection.delete(ids=existing["ids"])
# Chunk and embed
chunks = chunk_document(content)
embeddings = [get_embedding(chunk) for chunk in chunks]
collection.add(
embeddings=embeddings,
documents=chunks,
metadatas=[{"source": url, "chunk_index": i} for i in range(len(chunks))],
ids=[f"{doc_id}_{i}" for i in range(len(chunks))]
)
return {"status": "indexed", "chunks": len(chunks)}
@app.post("/ask")
def ask_question(q: Question):
"""Production RAG: retrieve first, then generate with guardrails."""
# Step 1: Embed the question
query_embedding = get_embedding(q.text)
# Step 2: Retrieve top 3 chunks
results = collection.query(
query_embeddings=[query_embedding],
n_results=3,
include=["documents", "metadatas", "distances"]
)
if not results["documents"] or not results["documents"][0]:
return {
"answer": "I don't have information about that. A human will review your ticket.",
"sources": [],
"confidence": 0.0
}
chunks = results["documents"][0]
sources = [m["source"] for m in results["metadatas"][0]]
distances = results["distances"][0]
# Step 3: Guardrail - if closest chunk is too far, reject
if distances[0] > 0.35: # Threshold tuned on validation set
return {
"answer": "I'm not sure about that. Let me escalate this to our team.",
"sources": [],
"confidence": round(1 - distances[0], 2)
}
# Step 4: Generate answer with retrieved context ONLY
context = "\n\n---\n\n".join(chunks)
system_prompt = f"""You are a customer support assistant.
Answer the question using ONLY the provided context.
If the answer is not in the context, say "I don't know."
Always cite the source document.
Context:
{context}"""
response = openai.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": q.text}
],
temperature=0.0, # Zero creativity for factual tasks
max_tokens=300
)
answer = response.choices[0].message.content
return {
"answer": answer,
"sources": sources,
"confidence": round(1 - distances[0], 2),
"tokens_used": response.usage.total_tokens
}
KEY INSIGHT: This is 90 lines of Python. No LangChain. No abstractions you cannot debug. Every step is visible, testable, and replaceable. That is what production means.
The Guardrails That Save Money
The $890/month tool had no guardrails. It answered every question, even when it had no idea.
My system has three guardrails that prevent hallucinations:
Guardrail 1: Distance Threshold
If the closest document chunk is too far from the question (distance > 0.35), the system refuses to answer. It escalates to a human.
Result: Zero hallucinations on out-of-scope questions.
Guardrail 2: Temperature = 0.0
The LLM gets zero creativity. It is not allowed to "fill in gaps." If the context does not contain the answer, it must say "I don't know."
Result: The LLM cannot invent policies that do not exist.
Guardrail 3: Source Citation
Every answer includes the source document. If the customer disputes the answer, we can point to the exact paragraph.
Result: Dispute resolution time dropped from 45 minutes to 2 minutes.
KEY INSIGHT: Guardrails are not limitations. They are the reason customers trust the system. A chatbot that says "I don't know" is worth more than one that lies confidently.
The Numbers (4 Months In)
Monthly API cost: $23 (OpenAI embeddings + completions)
Monthly hosting: $5 (Render)
Total monthly cost: $28
Previous tool cost: $890/month
Monthly savings: $862
Support agent salary avoided: $4,200/month
Total value generated: $4,200/month (reinvested into engineering)
Tickets answered correctly: 89%
Tickets escalated to human: 11%
Hallucination incidents: 0
Customer disputes: Down 74%
KEY INSIGHT: The $890 tool promised "AI-powered support." My $28 system promises "89% automated, 100% honest." Honesty is cheaper and more profitable.
The Junior vs Senior Difference in RAG Architecture
I have watched 15 engineers build RAG systems. The gap is predictable.
Junior with RAG:
Copies a LangChain tutorial
Chunks documents with arbitrary sizes
Sets temperature to 0.7 "for natural responses"
Answers every question because "the customer wants a fast reply"
When it hallucinates: "The LLM is unpredictable"
Senior with RAG:
Writes their own chunking logic with domain-specific overlap
Tunes the distance threshold on a validation set of 500 real questions
Sets temperature to 0.0 because "creativity is the enemy of facts"
Builds the system to fail loudly, not answer quietly
When it hallucinates: "I did not build a strong enough retrieval guardrail"
KEY INSIGHT: The $170K engineer does not write better prompts. They write better refusal logic. A RAG system that knows when it does not know is more valuable than one that knows everything.
What I Now Tell Every Client
I have a rule on every AI project I touch:
"If the answer is not in the retrieval context, the system must escalate. No exceptions. A delayed correct answer is better than an instant wrong one."
This rule has saved me $4,200/month in salary costs and $890/month in tool costs.
It has also saved me from one lawsuit.
KEY INSIGHT: In 2026, the most valuable AI systems are not the most intelligent. They are the most honest. Customers pay for certainty, not cleverness.
Your Turn
Audit your current AI tools. Find the one that answers questions it has no business answering.
That is your RAG replacement waiting to happen.
Drop a comment with the most expensive "AI confidence score" you have ever trusted. I will reply with the exact guardrail you should have built instead.
If this made you question your AI dependencies, follow me for weekly deep-dives into the code that actually ships - not the demo that wins the pitch.
This article was written with the assistance of AI tools. All financial figures and code examples are based on real production experience. The refund policy hallucination described resulted in actual legal proceedings.
Before you go
Please take a moment to like the post and follow the writer!
Did you know that over 400,000 developers share what they're building, learning, and discovering across our platforms every month? Learn how you can contribute here
Top comments (0)