Customer Support Agent Simulator: RAG Over Your Docs With Auto-Escalation
By DevShelfHub
Upload your product docs or FAQ, then chat with an AI support agent that answers only from your documentation and automatically escalates questions it cannot answer—RAG, LangChain, Chroma, Groq.
Customer Support Agent Simulator is an intermediate-level project that turns your product documentation into a live support chatbot. Upload FAQs, help articles, or any text documentation, build a local knowledge index, and chat with an AI agent that answers only from your docs — automatically escalating questions it cannot answer.
The project uses the same RAG pipeline as the notes Q&A bot: documents are chunked, embedded with local Hugging Face sentence-transformers, and stored in Chroma. Each chat turn retrieves the 4 most relevant documentation chunks, injects them as context, and asks Groq to answer as a named support agent. When the retrieved context does not cover the question, the agent triggers a scripted escalation response.
Built with Python, Streamlit, LangChain, Chroma, and Groq, this project demonstrates how RAG and persona prompting combine to create a deployable SaaS demo.
Purpose: apply RAG to a production-style use case — document-grounded answers with escalation logic, the same pattern used in enterprise support chatbots.
Typical use: paste a product FAQ into the sidebar, set an agent name and product name, click “Build Support KB,” then simulate customer questions to test coverage before shipping.
Key ideas you will touch:
- Persona prompting — agent name, product name, and escalation script injected into every system message for consistent brand voice.
- Grounded answers — “use ONLY the provided documentation” prevents hallucination about product details.
- Escalation detection — a simple keyword check on the response flags escalated cases with a UI warning.
- Chat history sliding window — last 6 turns kept to balance context and cost.
Overall flow
Sidebar: upload docs + set product name + agent persona
↓
Clicks "Build Support KB"
↓
Docs chunked → embedded locally (sentence-transformers) → stored in Chroma
↓
Customer asks a question in chat
↓
Retriever fetches top-4 chunks → injected as context
↓
Groq answers as named agent
↓
If answer cannot be grounded → escalation message + UI warning flag
Quick start — try it in 2 minutes
Step 1 — Fill the sidebar
- Product name: e.g.
SpotifyorMy SaaS App - Agent name: e.g.
Alex(or anything) - Paste your FAQ into the text box, for example:
Q: How do I cancel my subscription?
A: Go to Account Settings > Billing > Cancel Plan. You'll keep access until the end of your billing period.
Q: How do I reset my password?
A: Click "Forgot Password" on the login page. You'll receive an email within 2 minutes.
Q: What payment methods do you accept?
A: We accept Visa, Mastercard, PayPal, and UPI.
Step 2 — Click “Build Support KB”
Wait for the confirmation: “KB ready — X chunks indexed.”
Step 3 — Chat
Type a customer question in the chat box, e.g. “How do I cancel?”
Alex will answer from your docs. Ask something not in the docs (e.g. “What is your refund policy?”)
and it will say it’s escalating to a specialist.
You can also upload a real PDF product manual instead of pasting text — it will index the whole document the same way.
Demo
Step-by-Step Implementation
-
Set up the project
- Install Python 3.10+, create folder and virtual environment
-
Install dependencies
-
streamlit,groq,httpx,truststore,langchain,langchain-community,langchain-huggingface,langchain-text-splitters,chromadb,sentence-transformers,pypdf
-
-
Build the sidebar
- Product name and agent persona text inputs
- Multi-file uploader for PDF and TXT documentation
- Fallback text area for pasted FAQ content
-
Index the knowledge base
-
Decode all uploaded files, chunk with
RecursiveCharacterTextSplitter, embed, and store inst.session_state.support_vs
-
Decode all uploaded files, chunk with
-
Build the chat interface
-
Per-turn: retrieve top-4 chunks, inject as
contextin system message, include last 6 history turns - System message uses product name and agent persona from session state
-
Per-turn: retrieve top-4 chunks, inject as
-
Add escalation detection
-
After generating the response, check for escalation keywords; show
st.warning()if triggered
-
After generating the response, check for escalation keywords; show
Code implementation
# Customer Support Agent Simulator — Streamlit + LangChain + Chroma + Groq
# Save as app.py, add GROQ_API_KEY to .streamlit/secrets.toml, then: streamlit run app.py
import ssl
import httpx
import streamlit as st
import truststore
MAX_DOCS_CHARS = 20000
st.set_page_config(page_title="Customer Support Agent", layout="centered")
st.title("Customer Support Agent Simulator")
st.caption(
"Upload your product docs, FAQs, or support articles. The agent answers "
"customer questions using only your documentation — and flags hard cases."
)
_ssl_ctx = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
@st.cache_resource
def _groq_http_client():
return httpx.Client(verify=_ssl_ctx)
_MODEL = "llama-3.3-70b-versatile"
PRODUCT_NAME_DEFAULT = "Our Product"
# ── Sidebar: knowledge base setup ─────────────────────────────────────────────
with st.sidebar:
st.header("Knowledge Base")
product_name = st.text_input("Product / Company name", value=PRODUCT_NAME_DEFAULT)
agent_persona = st.text_input(
"Agent name / persona",
value="Alex",
help="The name your support agent will use",
)
doc_files = st.file_uploader(
"Upload docs (PDF or TXT)",
type=["pdf", "txt"],
accept_multiple_files=True,
)
doc_paste = st.text_area(
"Or paste FAQ / documentation",
height=200,
placeholder="Q: How do I reset my password?\nA: Go to Settings > Security > Reset Password...",
)
build_btn = st.button("Build Support KB", type="primary", use_container_width=True)
# ── Build knowledge base ───────────────────────────────────────────────────────
if build_btn:
import io
from langchain.schema import Document
from langchain_community.vectorstores import Chroma
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
all_text = ""
for f in (doc_files or []):
if f.name.endswith(".pdf"):
import pypdf
reader = pypdf.PdfReader(io.BytesIO(f.read()))
for page in reader.pages:
all_text += (page.extract_text() or "") + "\n"
else:
all_text += f.read().decode("utf-8", errors="replace") + "\n"
if doc_paste.strip():
all_text += doc_paste.strip()
all_text = all_text[:MAX_DOCS_CHARS]
if not all_text.strip():
st.sidebar.warning("Upload docs or paste FAQ content first.")
else:
with st.sidebar:
with st.spinner("Building knowledge base…"):
try:
splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=60)
docs = splitter.split_documents([Document(page_content=all_text)])
embeddings = HuggingFaceEmbeddings(
model_name="sentence-transformers/all-MiniLM-L6-v2"
)
st.session_state.support_vs = Chroma.from_documents(docs, embeddings)
st.session_state.product_name = product_name
st.session_state.agent_persona = agent_persona
st.session_state.doc_count = len(docs)
st.success(f"KB ready — {len(docs)} chunks indexed.")
except Exception as exc:
st.error(f"Index error: {exc}")
if "support_vs" in st.session_state:
st.sidebar.caption(
f"KB: {st.session_state.doc_count} chunks | "
f"Agent: {st.session_state.get('agent_persona', 'Alex')}"
)
# ── Chat interface ─────────────────────────────────────────────────────────────
if "support_history" not in st.session_state:
st.session_state.support_history = []
if "support_vs" not in st.session_state:
st.info("Set up the knowledge base in the sidebar to start chatting.")
else:
p_name = st.session_state.get("product_name", PRODUCT_NAME_DEFAULT)
a_name = st.session_state.get("agent_persona", "Alex")
st.caption(f"Chatting with **{a_name}** — {p_name} Support")
for msg in st.session_state.support_history:
role = msg["role"]
icon = "user" if role == "user" else "assistant"
with st.chat_message(icon):
st.markdown(msg["content"])
user_input = st.chat_input(f"Ask {a_name} a question…")
if user_input:
st.session_state.support_history.append({"role": "user", "content": user_input})
with st.chat_message("user"):
st.markdown(user_input)
# Retrieve relevant docs
try:
retriever = st.session_state.support_vs.as_retriever(search_kwargs={"k": 4})
docs = retriever.invoke(user_input)
context = "\n\n".join(d.page_content for d in docs)
except Exception:
context = ""
system_msg = (
f"You are {a_name}, a friendly and professional customer support agent for {p_name}. "
"Use ONLY the provided documentation to answer the customer's question. "
"If the answer is not in the documentation, respond with:\n"
"'I don't have enough information to answer that directly. "
"I'm escalating this to our specialist team — you'll hear back within 24 hours. "
"Is there anything else I can help you with?'\n\n"
"Keep responses concise, helpful, and professional. "
"Never make up information.\n\n"
f"Documentation:\n{context}"
)
messages = [{"role": "system", "content": system_msg}]
for h in st.session_state.support_history[-6:]:
messages.append(h)
with st.chat_message("assistant"):
with st.spinner(f"{a_name} is typing…"):
try:
import groq
client = groq.Groq(
api_key=st.secrets["GROQ_API_KEY"],
http_client=_groq_http_client(),
)
resp = client.chat.completions.create(
model=_MODEL,
messages=messages,
)
answer = resp.choices[0].message.content
# Flag escalation
is_escalated = "escalating" in answer.lower() or "specialist team" in answer.lower()
st.markdown(answer)
if is_escalated:
st.warning("This query has been flagged for human escalation.")
st.session_state.support_history.append(
{"role": "assistant", "content": answer}
)
except Exception as exc:
st.error(f"Error: {exc}")
if st.session_state.support_history:
if st.button("Clear Chat", key="clear_chat"):
st.session_state.support_history = []
st.rerun()
Complete code — Project link (GitHub)
📖 How the Code Works (Step-by-Step)
1. Persona injection
-
Agent name and product name are stored in
st.session_stateat index time and read at chat time — so the persona persists across turns without re-entering them.
2. Per-turn retrieval
- The retriever is called inside the chat handler, not at startup, so each question gets fresh top-4 chunks from the vector store without a stale global context.
3. Escalation guard
-
The system message instructs the model to use a specific escalation phrase when the context is
insufficient. A keyword check on the response then fires
st.warning()— no separate classifier needed.
Tips & Production Considerations
Keep your knowledge base up to date
Stale docs produce stale answers. If your product ships weekly, re-index the docs on a schedule (or on every deploy). A simple CI step that rebuilds the Chroma collection from the latest docs folder prevents the bot from citing deprecated features or old pricing.
Tune the escalation threshold, not the prompt temperature
The keyword-based escalation check is simple but effective. If the bot escalates too often, loosen the phrasing in the system prompt (e.g., "only escalate when zero relevant chunks are found"). If it escalates too rarely, add a retrieval-score threshold so low-confidence answers also trigger escalation.
Log conversations for quality review
In a production support system, logging every question, retrieved context, and response lets you audit accuracy, spot knowledge gaps, and identify which docs need expansion. Store logs with a session ID so you can replay full conversations during QA review.
Wire escalation to your existing ticketing system
The app shows a warning banner when it escalates. In production, replace that banner with an API call to Zendesk, Linear, or Slack so a human agent receives the conversation context along with the question. Include the retrieved chunks in the ticket so the agent does not start from scratch.
Add a feedback loop for answer quality
A thumbs-up / thumbs-down button after each answer is cheap to add with Streamlit and invaluable for measuring real accuracy. Negative ratings on specific topics tell you exactly which docs need rewriting or which retrieval parameters need tuning.
Customer Support Agent Simulator FAQ
What is the Customer Support Agent Simulator?
The Customer Support Agent Simulator is an intermediate Streamlit project where you upload product docs or an FAQ, then chat with an AI agent that answers strictly from your documentation and automatically escalates questions it cannot answer—built with LangChain RAG, Chroma, and Groq.
Is the Customer Support Agent Simulator free to use?
The source code is free and open on GitHub. Embeddings run locally via Hugging Face, so there is no embedding API cost. You only pay for Groq LLM usage, and Groq's free tier covers typical support conversations.
What tech stack does the Customer Support Agent Simulator use?
Python and Streamlit for the UI, LangChain for the RAG pipeline, Chroma as the local vector store, Sentence Transformers from Hugging Face for embeddings, and Groq for chat completions. No paid embedding API is required.
How does auto-escalation work in the Customer Support Agent Simulator?
The system prompt instructs the model to answer only when the retrieved chunks confidently cover the question, and to return a structured escalation message otherwise (e.g., "I don't see this in the docs—forwarding to support"). You can wire that escalation message to an email, Slack, or ticketing system.
Can I use the Customer Support Agent Simulator with my own docs?
Yes. Drop your PDFs, Markdown files, or HTML into the input directory and the app chunks, embeds, and indexes them on first run. The bot's persona and tone live in the system prompt and can be edited to match your brand voice.
What are alternatives to the Customer Support Agent Simulator?
For RAG over a single web page instead of a docs corpus, see the Website RAG Chatbot. For RAG over personal study notes with quiz generation, see the Personal Study Tutor.