<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: MacLiamor</title>
    <description>The latest articles on DEV Community by MacLiamor (@macliamor_d698380ee2bd235).</description>
    <link>https://dev.to/macliamor_d698380ee2bd235</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2429100%2F4e8f77c5-ceda-4cb2-aa3e-d4c06ac6665e.png</url>
      <title>DEV Community: MacLiamor</title>
      <link>https://dev.to/macliamor_d698380ee2bd235</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/macliamor_d698380ee2bd235"/>
    <language>en</language>
    <item>
      <title>Building an AI Customer Support SaaS with Django, RAG and Self-Hosted LLMs</title>
      <dc:creator>MacLiamor</dc:creator>
      <pubDate>Sat, 15 Aug 2026 16:22:55 +0000</pubDate>
      <link>https://dev.to/macliamor_d698380ee2bd235/building-an-ai-customer-support-saas-with-django-rag-and-self-hosted-llms-3aoh</link>
      <guid>https://dev.to/macliamor_d698380ee2bd235/building-an-ai-customer-support-saas-with-django-rag-and-self-hosted-llms-3aoh</guid>
      <description>&lt;p&gt;Building an AI Customer Support SaaS with Django, RAG and Self-Hosted LLMs&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;At first glance, building an AI chatbot sounds straightforward:&lt;/p&gt;

&lt;p&gt;Send a prompt to an LLM.&lt;br&gt;
Display the response.&lt;br&gt;
Add a chat widget.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;This post covers some of the main lessons I learned while building it.&lt;/p&gt;

&lt;p&gt;The basic architecture&lt;/p&gt;

&lt;p&gt;The web application is built with Python and Django.&lt;/p&gt;

&lt;p&gt;Django handles things such as:&lt;/p&gt;

&lt;p&gt;Customer accounts&lt;br&gt;
Subscriptions&lt;br&gt;
AI configuration&lt;br&gt;
Knowledge-base management&lt;br&gt;
Chat history&lt;br&gt;
Analytics&lt;br&gt;
Widget configuration&lt;br&gt;
Tenant separation&lt;br&gt;
Integrations&lt;/p&gt;

&lt;p&gt;The AI inference layer is separated from the main Django application.&lt;/p&gt;

&lt;p&gt;This means the web application does not need to run the language model itself.&lt;/p&gt;

&lt;p&gt;Instead, requests are sent to an AI service responsible for generating responses.&lt;/p&gt;

&lt;p&gt;Why separate the AI service?&lt;/p&gt;

&lt;p&gt;Running an LLM inside the main web application creates several problems.&lt;/p&gt;

&lt;p&gt;Inference workloads have very different requirements from normal web requests.&lt;/p&gt;

&lt;p&gt;A typical Django request might take milliseconds, while an AI response may involve:&lt;/p&gt;

&lt;p&gt;Retrieval&lt;br&gt;
Prompt construction&lt;br&gt;
GPU inference&lt;br&gt;
Streaming tokens&lt;br&gt;
Tool calls&lt;br&gt;
Live-data lookups&lt;/p&gt;

&lt;p&gt;Separating these workloads allows the web application and AI infrastructure to scale independently.&lt;/p&gt;

&lt;p&gt;It also makes it possible to change the model without redesigning the SaaS application.&lt;/p&gt;

&lt;p&gt;Retrieval-Augmented Generation&lt;/p&gt;

&lt;p&gt;A customer-support assistant should not rely entirely on the model’s general knowledge.&lt;/p&gt;

&lt;p&gt;A business wants the AI to answer questions using its own information.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;What is your refund policy?&lt;/p&gt;

&lt;p&gt;or:&lt;/p&gt;

&lt;p&gt;Do you provide support outside Ireland?&lt;/p&gt;

&lt;p&gt;The relevant information might exist on the company website or inside a PDF.&lt;/p&gt;

&lt;p&gt;I therefore use a retrieval pipeline.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;/p&gt;

&lt;p&gt;Customer question&lt;br&gt;
        |&lt;br&gt;
        v&lt;br&gt;
Create embedding&lt;br&gt;
        |&lt;br&gt;
        v&lt;br&gt;
Search business knowledge&lt;br&gt;
        |&lt;br&gt;
        v&lt;br&gt;
Retrieve relevant documents&lt;br&gt;
        |&lt;br&gt;
        v&lt;br&gt;
Build LLM prompt&lt;br&gt;
        |&lt;br&gt;
        v&lt;br&gt;
Generate grounded answer&lt;/p&gt;

&lt;p&gt;The important part is tenant isolation.&lt;/p&gt;

&lt;p&gt;A document belonging to Company A must never appear in a response generated for Company B.&lt;/p&gt;

&lt;p&gt;Every retrieval request therefore needs to remain scoped to the current tenant.&lt;/p&gt;

&lt;p&gt;A vector database is only part of the solution&lt;/p&gt;

&lt;p&gt;I use a vector database for semantic retrieval, but retrieval quality depends on much more than simply storing embeddings.&lt;/p&gt;

&lt;p&gt;Things that matter include:&lt;/p&gt;

&lt;p&gt;Chunk size&lt;br&gt;
Metadata&lt;br&gt;
Tenant filtering&lt;br&gt;
Similarity thresholds&lt;br&gt;
Query rewriting&lt;br&gt;
Number of retrieved chunks&lt;br&gt;
Prompt construction&lt;/p&gt;

&lt;p&gt;Retrieving too little context can produce incomplete answers.&lt;/p&gt;

&lt;p&gt;Retrieving too much can fill the context window with irrelevant information.&lt;/p&gt;

&lt;p&gt;I found that relevance filtering is one of the most important parts of the system.&lt;/p&gt;

&lt;p&gt;Static knowledge versus live data&lt;/p&gt;

&lt;p&gt;A vector database works well for relatively static information.&lt;/p&gt;

&lt;p&gt;But consider a question such as:&lt;/p&gt;

&lt;p&gt;What products are currently available?&lt;/p&gt;

&lt;p&gt;That information may change constantly.&lt;/p&gt;

&lt;p&gt;Embedding yesterday’s product catalogue is not necessarily the right solution.&lt;/p&gt;

&lt;p&gt;I therefore treat knowledge data and live data differently.&lt;/p&gt;

&lt;p&gt;Knowledge data includes things such as:&lt;/p&gt;

&lt;p&gt;Website content&lt;br&gt;
FAQs&lt;br&gt;
Documentation&lt;br&gt;
Policies&lt;/p&gt;

&lt;p&gt;Live data can include:&lt;/p&gt;

&lt;p&gt;Products&lt;br&gt;
Prices&lt;br&gt;
Availability&lt;br&gt;
Business-system information&lt;/p&gt;

&lt;p&gt;The interesting problem is deciding when live data should be queried.&lt;/p&gt;

&lt;p&gt;You do not want a product catalogue added to every prompt simply because it exists.&lt;/p&gt;

&lt;p&gt;The user’s question needs to be relevant first.&lt;/p&gt;

&lt;p&gt;Relevance gating&lt;/p&gt;

&lt;p&gt;This turned out to be an important lesson.&lt;/p&gt;

&lt;p&gt;Imagine a customer asks:&lt;/p&gt;

&lt;p&gt;What are your opening hours?&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The same applies to images.&lt;/p&gt;

&lt;p&gt;The solution is to introduce relevance checks before enriching the prompt.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;/p&gt;

&lt;p&gt;if is_product_question(message):&lt;br&gt;
    context += get_product_data()&lt;/p&gt;

&lt;p&gt;The real implementation can be more sophisticated, but the principle is simple:&lt;/p&gt;

&lt;p&gt;Give the model additional information only when it is relevant.&lt;/p&gt;

&lt;p&gt;This improves both response quality and token efficiency.&lt;/p&gt;

&lt;p&gt;Dynamic images have the same problem&lt;/p&gt;

&lt;p&gt;AI responses can become much more useful when they contain relevant images.&lt;/p&gt;

&lt;p&gt;For example, if somebody asks:&lt;/p&gt;

&lt;p&gt;Can you show me the blue version?&lt;/p&gt;

&lt;p&gt;an image may be very helpful.&lt;/p&gt;

&lt;p&gt;But displaying an image because a generic keyword happened to match makes the assistant feel unreliable.&lt;/p&gt;

&lt;p&gt;So image selection also needs relevance filtering.&lt;/p&gt;

&lt;p&gt;A useful AI interface is not about showing everything available.&lt;/p&gt;

&lt;p&gt;It is about showing the right information at the right moment.&lt;/p&gt;

&lt;p&gt;Self-hosting the language model&lt;/p&gt;

&lt;p&gt;One of the biggest architectural decisions was how to handle inference.&lt;/p&gt;

&lt;p&gt;Using hosted AI APIs is extremely convenient, particularly during development.&lt;/p&gt;

&lt;p&gt;However, predictable SaaS pricing becomes harder when every customer interaction has a variable external API cost.&lt;/p&gt;

&lt;p&gt;I therefore experimented with self-hosted models running on GPU infrastructure.&lt;/p&gt;

&lt;p&gt;The architecture is roughly:&lt;/p&gt;

&lt;p&gt;Website Widget&lt;br&gt;
      |&lt;br&gt;
      v&lt;br&gt;
Django&lt;br&gt;
      |&lt;br&gt;
      v&lt;br&gt;
AI Service / Agent&lt;br&gt;
      |&lt;br&gt;
      +---- Vector Database&lt;br&gt;
      |&lt;br&gt;
      +---- Live Data&lt;br&gt;
      |&lt;br&gt;
      +---- LLM Inference&lt;/p&gt;

&lt;p&gt;This gives more control over:&lt;/p&gt;

&lt;p&gt;Model choice&lt;br&gt;
Token limits&lt;br&gt;
Capacity&lt;br&gt;
Cost per message&lt;br&gt;
Scaling&lt;br&gt;
Data flow&lt;/p&gt;

&lt;p&gt;There are trade-offs, of course.&lt;/p&gt;

&lt;p&gt;Running inference infrastructure means dealing with GPU availability, model loading, monitoring and capacity planning.&lt;/p&gt;

&lt;p&gt;Streaming responses&lt;/p&gt;

&lt;p&gt;For chat applications, perceived latency matters almost as much as total generation time.&lt;/p&gt;

&lt;p&gt;Waiting several seconds and then receiving an entire response feels much slower than seeing the answer appear incrementally.&lt;/p&gt;

&lt;p&gt;Streaming therefore makes a significant difference to the user experience.&lt;/p&gt;

&lt;p&gt;The flow becomes:&lt;/p&gt;

&lt;p&gt;Browser&lt;br&gt;
   |&lt;br&gt;
   | question&lt;br&gt;
   v&lt;br&gt;
Django&lt;br&gt;
   |&lt;br&gt;
   v&lt;br&gt;
AI service&lt;br&gt;
   |&lt;br&gt;
   | token stream&lt;br&gt;
   v&lt;br&gt;
Django&lt;br&gt;
   |&lt;br&gt;
   | streamed response&lt;br&gt;
   v&lt;br&gt;
Browser&lt;/p&gt;

&lt;p&gt;Even when total generation time remains similar, the application feels considerably more responsive.&lt;/p&gt;

&lt;p&gt;Human escalation still matters&lt;/p&gt;

&lt;p&gt;An AI support system should not pretend it can solve everything.&lt;/p&gt;

&lt;p&gt;There are situations where a human should take over.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;p&gt;Complaints&lt;br&gt;
Sensitive account issues&lt;br&gt;
Missing information&lt;br&gt;
Complex requests&lt;br&gt;
Situations requiring human judgement&lt;/p&gt;

&lt;p&gt;One of the design principles I have adopted is:&lt;/p&gt;

&lt;p&gt;The AI should know when it does not have enough information.&lt;/p&gt;

&lt;p&gt;That is more useful than confidently inventing an answer.&lt;/p&gt;

&lt;p&gt;Multitenancy changes everything&lt;/p&gt;

&lt;p&gt;Building an AI demo for one business is relatively easy.&lt;/p&gt;

&lt;p&gt;Building a SaaS where hundreds of businesses can independently configure their assistants is different.&lt;/p&gt;

&lt;p&gt;Each tenant may have:&lt;/p&gt;

&lt;p&gt;Different instructions&lt;br&gt;
Different knowledge&lt;br&gt;
Different products&lt;br&gt;
Different widgets&lt;br&gt;
Different usage limits&lt;br&gt;
Different conversation histories&lt;/p&gt;

&lt;p&gt;Every step of the pipeline must preserve tenant context.&lt;/p&gt;

&lt;p&gt;That includes retrieval, live-data access, logging and analytics.&lt;/p&gt;

&lt;p&gt;Cost becomes an architectural feature&lt;/p&gt;

&lt;p&gt;When building a SaaS product, AI cost is not just an infrastructure concern.&lt;/p&gt;

&lt;p&gt;It directly affects the business model.&lt;/p&gt;

&lt;p&gt;If a customer pays €20 per month, the platform cannot consume €30 of inference infrastructure serving that customer.&lt;/p&gt;

&lt;p&gt;This means decisions such as these become important:&lt;/p&gt;

&lt;p&gt;Context length&lt;br&gt;
Number of retrieved documents&lt;br&gt;
Model size&lt;br&gt;
GPU utilization&lt;br&gt;
Message limits&lt;br&gt;
Caching&lt;br&gt;
Prompt size&lt;br&gt;
Concurrency&lt;/p&gt;

&lt;p&gt;AI efficiency becomes part of product engineering.&lt;/p&gt;

&lt;p&gt;What I would do differently&lt;/p&gt;

&lt;p&gt;If I were starting again, I would spend more time on relevance and retrieval quality earlier.&lt;/p&gt;

&lt;p&gt;It is tempting to focus on model size.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;I would prioritize:&lt;/p&gt;

&lt;p&gt;Good retrieval&lt;br&gt;
Strong tenant isolation&lt;br&gt;
Relevance gating&lt;br&gt;
Clear system instructions&lt;br&gt;
Fast streaming&lt;br&gt;
Reliable fallbacks&lt;/p&gt;

&lt;p&gt;before spending too much time experimenting with larger models.&lt;/p&gt;

&lt;p&gt;The result&lt;/p&gt;

&lt;p&gt;These ideas eventually became part of AI-Autofy.&lt;/p&gt;

&lt;p&gt;The platform lets businesses add their website, documents, FAQs and instructions, configure an AI assistant, test it and deploy it using a website widget.&lt;/p&gt;

&lt;p&gt;I’ve also been adding capabilities for live business information, products, images, multilingual conversations, escalation and analytics.&lt;/p&gt;

&lt;p&gt;You can see the project here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.ai-autofy.com" rel="noopener noreferrer"&gt;https://www.ai-autofy.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’m continuing to work on both the product and the infrastructure behind it.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

</description>
      <category>python</category>
      <category>django</category>
      <category>ai</category>
      <category>sass</category>
    </item>
  </channel>
</rss>
