Building an AI Customer Support SaaS with Django, RAG and Self-Hosted LLMs
Over the past several months, I’ve been building AI-Autofy, an AI customer-support SaaS designed to let businesses train an assistant on their own website, documents, FAQs and business data.
At first glance, building an AI chatbot sounds straightforward:
Send a prompt to an LLM.
Display the response.
Add a chat widget.
In practice, once you need reliable business-specific answers, tenant isolation, live data, product information, images, analytics and predictable inference costs, the architecture becomes considerably more interesting.
This post covers some of the main lessons I learned while building it.
The basic architecture
The web application is built with Python and Django.
Django handles things such as:
Customer accounts
Subscriptions
AI configuration
Knowledge-base management
Chat history
Analytics
Widget configuration
Tenant separation
Integrations
The AI inference layer is separated from the main Django application.
This means the web application does not need to run the language model itself.
Instead, requests are sent to an AI service responsible for generating responses.
Why separate the AI service?
Running an LLM inside the main web application creates several problems.
Inference workloads have very different requirements from normal web requests.
A typical Django request might take milliseconds, while an AI response may involve:
Retrieval
Prompt construction
GPU inference
Streaming tokens
Tool calls
Live-data lookups
Separating these workloads allows the web application and AI infrastructure to scale independently.
It also makes it possible to change the model without redesigning the SaaS application.
Retrieval-Augmented Generation
A customer-support assistant should not rely entirely on the model’s general knowledge.
A business wants the AI to answer questions using its own information.
For example:
What is your refund policy?
or:
Do you provide support outside Ireland?
The relevant information might exist on the company website or inside a PDF.
I therefore use a retrieval pipeline.
Conceptually:
Customer question
|
v
Create embedding
|
v
Search business knowledge
|
v
Retrieve relevant documents
|
v
Build LLM prompt
|
v
Generate grounded answer
The important part is tenant isolation.
A document belonging to Company A must never appear in a response generated for Company B.
Every retrieval request therefore needs to remain scoped to the current tenant.
A vector database is only part of the solution
I use a vector database for semantic retrieval, but retrieval quality depends on much more than simply storing embeddings.
Things that matter include:
Chunk size
Metadata
Tenant filtering
Similarity thresholds
Query rewriting
Number of retrieved chunks
Prompt construction
Retrieving too little context can produce incomplete answers.
Retrieving too much can fill the context window with irrelevant information.
I found that relevance filtering is one of the most important parts of the system.
Static knowledge versus live data
A vector database works well for relatively static information.
But consider a question such as:
What products are currently available?
That information may change constantly.
Embedding yesterday’s product catalogue is not necessarily the right solution.
I therefore treat knowledge data and live data differently.
Knowledge data includes things such as:
Website content
FAQs
Documentation
Policies
Live data can include:
Products
Prices
Availability
Business-system information
The interesting problem is deciding when live data should be queried.
You do not want a product catalogue added to every prompt simply because it exists.
The user’s question needs to be relevant first.
Relevance gating
This turned out to be an important lesson.
Imagine a customer asks:
What are your opening hours?
If the application automatically injects product data into every request, the LLM may start mentioning products even though the question has nothing to do with them.
The same applies to images.
The solution is to introduce relevance checks before enriching the prompt.
Conceptually:
if is_product_question(message):
context += get_product_data()
The real implementation can be more sophisticated, but the principle is simple:
Give the model additional information only when it is relevant.
This improves both response quality and token efficiency.
Dynamic images have the same problem
AI responses can become much more useful when they contain relevant images.
For example, if somebody asks:
Can you show me the blue version?
an image may be very helpful.
But displaying an image because a generic keyword happened to match makes the assistant feel unreliable.
So image selection also needs relevance filtering.
A useful AI interface is not about showing everything available.
It is about showing the right information at the right moment.
Self-hosting the language model
One of the biggest architectural decisions was how to handle inference.
Using hosted AI APIs is extremely convenient, particularly during development.
However, predictable SaaS pricing becomes harder when every customer interaction has a variable external API cost.
I therefore experimented with self-hosted models running on GPU infrastructure.
The architecture is roughly:
Website Widget
|
v
Django
|
v
AI Service / Agent
|
+---- Vector Database
|
+---- Live Data
|
+---- LLM Inference
This gives more control over:
Model choice
Token limits
Capacity
Cost per message
Scaling
Data flow
There are trade-offs, of course.
Running inference infrastructure means dealing with GPU availability, model loading, monitoring and capacity planning.
Streaming responses
For chat applications, perceived latency matters almost as much as total generation time.
Waiting several seconds and then receiving an entire response feels much slower than seeing the answer appear incrementally.
Streaming therefore makes a significant difference to the user experience.
The flow becomes:
Browser
|
| question
v
Django
|
v
AI service
|
| token stream
v
Django
|
| streamed response
v
Browser
Even when total generation time remains similar, the application feels considerably more responsive.
Human escalation still matters
An AI support system should not pretend it can solve everything.
There are situations where a human should take over.
Examples include:
Complaints
Sensitive account issues
Missing information
Complex requests
Situations requiring human judgement
One of the design principles I have adopted is:
The AI should know when it does not have enough information.
That is more useful than confidently inventing an answer.
Multitenancy changes everything
Building an AI demo for one business is relatively easy.
Building a SaaS where hundreds of businesses can independently configure their assistants is different.
Each tenant may have:
Different instructions
Different knowledge
Different products
Different widgets
Different usage limits
Different conversation histories
Every step of the pipeline must preserve tenant context.
That includes retrieval, live-data access, logging and analytics.
Cost becomes an architectural feature
When building a SaaS product, AI cost is not just an infrastructure concern.
It directly affects the business model.
If a customer pays €20 per month, the platform cannot consume €30 of inference infrastructure serving that customer.
This means decisions such as these become important:
Context length
Number of retrieved documents
Model size
GPU utilization
Message limits
Caching
Prompt size
Concurrency
AI efficiency becomes part of product engineering.
What I would do differently
If I were starting again, I would spend more time on relevance and retrieval quality earlier.
It is tempting to focus on model size.
But for a customer-support system, a smaller model with excellent business context can often be more useful than a larger model receiving poor context.
I would prioritize:
Good retrieval
Strong tenant isolation
Relevance gating
Clear system instructions
Fast streaming
Reliable fallbacks
before spending too much time experimenting with larger models.
The result
These ideas eventually became part of AI-Autofy.
The platform lets businesses add their website, documents, FAQs and instructions, configure an AI assistant, test it and deploy it using a website widget.
I’ve also been adding capabilities for live business information, products, images, multilingual conversations, escalation and analytics.
You can see the project here:
I’m continuing to work on both the product and the infrastructure behind it.
For anyone else building AI SaaS products, I’d be interested to hear how you are approaching the same trade-off between model quality, inference cost and retrieval quality.
Top comments (0)