<?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: Pruthviraj Janwade</title>
    <description>The latest articles on DEV Community by Pruthviraj Janwade (@pruthviraj_janwade_180b10).</description>
    <link>https://dev.to/pruthviraj_janwade_180b10</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%2F4076074%2Fbb710757-c25f-4372-8539-8946a341dce3.jpeg</url>
      <title>DEV Community: Pruthviraj Janwade</title>
      <link>https://dev.to/pruthviraj_janwade_180b10</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pruthviraj_janwade_180b10"/>
    <language>en</language>
    <item>
      <title>Stop Using WebSockets for AI Chat Apps: Why We Switched to Server-Sent Events (SSE) in Production</title>
      <dc:creator>Pruthviraj Janwade</dc:creator>
      <pubDate>Fri, 09 Oct 2026 06:29:57 +0000</pubDate>
      <link>https://dev.to/pruthviraj_janwade_180b10/stop-using-websockets-for-ai-chat-apps-why-we-switched-to-server-sent-events-sse-in-production-1k54</link>
      <guid>https://dev.to/pruthviraj_janwade_180b10/stop-using-websockets-for-ai-chat-apps-why-we-switched-to-server-sent-events-sse-in-production-1k54</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; When building an AI chat assistant with LLM token streaming, most tutorials recommend WebSockets or short polling. In production, WebSockets introduce stateful connection complexity, load balancer friction, and proxy issues, while polling creates unnecessary server load. Here is why we migrated &lt;strong&gt;IntelliDesk AI&lt;/strong&gt; from WebSockets to &lt;strong&gt;Server-Sent Events (SSE)&lt;/strong&gt;, how it brought our Time-to-First-Token under 180ms, and the four concrete engineering challenges we solved in our code.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Initial Architectural Decision and Its Limits
&lt;/h2&gt;

&lt;p&gt;In our initial version of IntelliDesk AI, we used WebSockets (via Flask-SocketIO) to stream responses from Groq's Llama 3.3 70B model to our React frontend.&lt;/p&gt;

&lt;p&gt;On paper, WebSockets seemed like the default choice for real-time applications. However, as we tested multi-user workloads and prepared our deployment architecture for NGINX and AWS Application Load Balancers, significant architectural drawbacks emerged:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Stateful Connection Overhead:&lt;/strong&gt; WebSockets require a persistent, bi-directional TCP connection. In distributed or containerized environments behind load balancers, scaling requires sticky sessions or a Redis Pub/Sub adapter to manage client-server mapping.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context and Threading Friction:&lt;/strong&gt; In Python WSGI/ASGI environments, Socket.IO often relies on greenlet context switching. Managing request application contexts, database sessions, and auth lifecycles across greenlets introduces subtle concurrency edge cases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proxy and Firewall Friction:&lt;/strong&gt; Reverse proxies and enterprise security appliances often buffer or terminate HTTP protocol upgrade requests (&lt;code&gt;Upgrade: websocket&lt;/code&gt;) unless explicitly configured with specialized timeouts.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When analyzing the actual communication pattern of our conversational assistant, the reality became clear:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The user sends a single prompt, and the server streams hundreds of tokens back.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is an inherently &lt;strong&gt;unidirectional&lt;/strong&gt; communication pattern. Using a full-duplex, stateful WebSocket connection for a one-way stream was unnecessary technical debt.&lt;/p&gt;




&lt;h2&gt;
  
  
  Evaluating the Three Transport Options
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. SHORT POLLING
Client: Sends request every 500ms -&amp;gt; Server: Returns partial/empty status
Result: High network overhead, SSL handshakes, and delayed perceived response.

2. WEBSOCKETS
Client &amp;lt;============== Full-Duplex Stateful TCP Connection ==============&amp;gt; Server
Result: Bi-directional, stateful, requires sticky sessions and persistent connection state.

3. SERVER-SENT EVENTS (SSE)
Client: Sends 1 HTTP POST with query -&amp;gt; Server: Streams text chunks over keep-alive HTTP
Result: Unidirectional, stateless, works natively with standard HTTP infrastructure and load balancers.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why Short Polling Failed
&lt;/h3&gt;

&lt;p&gt;Short polling introduces severe resource waste:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A typical 6-to-8 second AI response requires 12 to 16 separate HTTP requests per user query.&lt;/li&gt;
&lt;li&gt;Each request incurs round-trip latency, authentication checks, and database queries.&lt;/li&gt;
&lt;li&gt;Perceived latency is choppy because tokens appear in staggered batches rather than as a smooth reading stream.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why Server-Sent Events (SSE) Was the Superior Fit
&lt;/h3&gt;

&lt;p&gt;Server-Sent Events operates on standard HTTP:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The client sends an HTTP request.&lt;/li&gt;
&lt;li&gt;The server responds with &lt;code&gt;Content-Type: text/event-stream&lt;/code&gt; and &lt;code&gt;Transfer-Encoding: chunked&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The server pushes data chunks line-by-line (&lt;code&gt;data: ...\n\n&lt;/code&gt;) over the open HTTP connection.&lt;/li&gt;
&lt;li&gt;When generation ends, the connection closes cleanly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;SSE requires no protocol switching, is completely stateless at the transport layer, and works seamlessly through standard reverse proxies, CDNs, and load balancers.&lt;/p&gt;




&lt;h2&gt;
  
  
  How We Implemented SSE in IntelliDesk AI
&lt;/h2&gt;

&lt;p&gt;Here is the architecture of our current streaming pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[React 18 SPA]
     │
     │  POST /api/v1/ai/chat/stream
     │  Authorization: Bearer &amp;lt;JWT&amp;gt;
     ▼
[NGINX Reverse Proxy] (proxy_buffering off, X-Accel-Buffering: no)
     │
     ▼
[Flask Backend Controller] (stream_with_context, text/event-stream)
     │
     │  Semantic Search (RAG) + Groq Token Stream
     ▼
[Groq LPU Engine] (Llama 3.3 70B)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Backend: Flask Stream Generator
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;backend/app/controllers/ai_controller.py&lt;/code&gt;, we created an authenticated, rate-limited POST endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@ai_bp.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/chat/stream&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nd"&gt;@jwt_required&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nd"&gt;@limiter.limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;30/minute&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;chat_stream_sse&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;silent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query is required&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;

    &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_current_user_id&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;session_uuid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;session_uuid&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;generator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AIChatService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_chat_sse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;session_uuid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;session_uuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;stream_with_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;generator&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;mimetype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text/event-stream&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cache-Control&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no-cache&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Accel-Buffering&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;# Disables NGINX response buffering
&lt;/span&gt;            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Connection&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;       &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;keep-alive&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;backend/app/services/ai_service.py&lt;/code&gt;, the generator yields JSON payloads formatted according to the SSE wire standard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_sse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 1. Start event
&lt;/span&gt;&lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nf"&gt;_sse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;session_uuid&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_uuid&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;# 2. Token chunks from Groq API
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;LLMService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat_completion_stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chunk&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nf"&gt;_sse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chunk&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chunk&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]})&lt;/span&gt;

&lt;span class="c1"&gt;# 3. Final event with metadata and RAG sources
&lt;/span&gt;&lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nf"&gt;_sse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;done&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sources&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rag_sources&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tokens_used&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;total_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;latency_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;latency_ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Four Concrete Engineering Challenges We Resolved
&lt;/h2&gt;

&lt;p&gt;Implementing SSE in production required addressing four specific technical hurdles:&lt;/p&gt;




&lt;h3&gt;
  
  
  1. Native EventSource Authentication and Request Method Limitations
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Problem:&lt;/strong&gt;&lt;br&gt;
The browser's native &lt;code&gt;new EventSource(url)&lt;/code&gt; API only supports GET requests and does not allow custom HTTP request headers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Passing JWTs in URL query parameters (&lt;code&gt;/stream?token=xyz&lt;/code&gt;) exposes access tokens in access logs, CDN request history, and browser caches.&lt;/li&gt;
&lt;li&gt;Lengthy prompts and system context can exceed URL length limitations (HTTP 414).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Solution:&lt;/strong&gt;&lt;br&gt;
Instead of using &lt;code&gt;new EventSource()&lt;/code&gt;, we used the standard &lt;code&gt;fetch()&lt;/code&gt; API combined with &lt;code&gt;ReadableStream&lt;/code&gt; (&lt;code&gt;response.body.getReader()&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;API_BASE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/ai/chat/stream`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userMessageContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;session_uuid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;activeSessionUuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;abortCtrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows us to maintain a POST interface, pass large JSON payloads cleanly, and include standard Authorization headers.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. TCP Chunk Fragmentation and UTF-8 Boundaries
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Problem:&lt;/strong&gt;&lt;br&gt;
Network chunks returned by &lt;code&gt;reader.read()&lt;/code&gt; do not strictly align with SSE message delimiters (&lt;code&gt;\n\n&lt;/code&gt;). A single network read can cut an SSE JSON frame in half:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Chunk 1: data: {"type": "chunk", "content": "The syst
Chunk 2: em is operational"}\n\n
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Calling &lt;code&gt;JSON.parse()&lt;/code&gt; immediately on incoming bytes causes syntax parse errors. Additionally, multi-byte UTF-8 sequences (such as symbols or accented letters) can be split across packet boundaries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Solution:&lt;/strong&gt;&lt;br&gt;
We implemented a stateful buffer accumulator using &lt;code&gt;TextDecoder({ stream: true })&lt;/code&gt; and delimiter splitting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getReader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextDecoder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;decoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Retain incomplete frame in buffer&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;frame&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chunk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;appendContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Ignore unparseable or partial lines safely&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  3. Reverse Proxy Buffering (NGINX and Load Balancers)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Problem:&lt;/strong&gt;&lt;br&gt;
By default, reverse proxies such as NGINX buffer upstream responses to optimize packet sizes before transmitting data to the client. This behavior prevents tokens from rendering in real-time, resulting in a delay followed by a sudden burst of text.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Solution:&lt;/strong&gt;&lt;br&gt;
We addressed this at two levels:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Application Layer:&lt;/strong&gt; Added the &lt;code&gt;X-Accel-Buffering: no&lt;/code&gt; header to the Flask Response to instruct NGINX to disable proxy buffering for the stream.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proxy Layer:&lt;/strong&gt; Configured NGINX with unbuffered settings for the streaming location:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/api/v1/ai/chat/stream&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://flask_backend&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_buffering&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_cache&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Connection&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_http_version&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;chunked_transfer_encoding&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Client Disconnections and Component Unmounting
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Problem:&lt;/strong&gt;&lt;br&gt;
If a user navigated away from the assistant or switched chat sessions while the model was generating, the stream continued running in the background. This resulted in React unmounted component warnings and consumed unnecessary LLM inference quota.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Solution:&lt;/strong&gt;&lt;br&gt;
We integrated &lt;code&gt;AbortController&lt;/code&gt; into the component lifecycle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;abortCtrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AbortController&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;abortCtrlRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;abortCtrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;abortCtrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AbortError&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Handled gracefully&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Cleanup on unmount or session switch&lt;/span&gt;
&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;abortCtrlRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;activeSessionUuid&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aborting the fetch request drops the HTTP stream immediately, allowing the backend generator to stop execution.&lt;/p&gt;




&lt;h2&gt;
  
  
  Comparison: WebSockets vs. SSE in Production
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;WebSockets&lt;/th&gt;
&lt;th&gt;Server-Sent Events (SSE)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Protocol&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ws://&lt;/code&gt; / &lt;code&gt;wss://&lt;/code&gt; (Protocol Upgrade)&lt;/td&gt;
&lt;td&gt;Standard &lt;code&gt;http://&lt;/code&gt; / &lt;code&gt;https://&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Connection State&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Stateful&lt;/td&gt;
&lt;td&gt;Stateless at transport layer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Load Balancing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Requires sticky sessions or Redis Pub/Sub&lt;/td&gt;
&lt;td&gt;Works natively behind standard ALBs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Time-to-First-Token&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~400ms (Handshake + Socket connection)&lt;/td&gt;
&lt;td&gt;&amp;lt; 180ms (Direct HTTP POST)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Authentication&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Custom query/handshake logic&lt;/td&gt;
&lt;td&gt;Standard &lt;code&gt;Authorization: Bearer &amp;lt;JWT&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Client Implementation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Socket.IO library dependency&lt;/td&gt;
&lt;td&gt;Native &lt;code&gt;fetch()&lt;/code&gt; + &lt;code&gt;ReadableStream&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Choosing the right communication protocol depends on the directional requirements of your data:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If your application requires bidirectional, high-frequency exchanges (such as collaborative editing or gaming), &lt;strong&gt;WebSockets&lt;/strong&gt; remains the correct choice.&lt;/li&gt;
&lt;li&gt;If your application streams unidirectional data from server to client (such as LLM tokens or real-time metric feeds), &lt;strong&gt;Server-Sent Events (SSE)&lt;/strong&gt; offers lower operational overhead, native load balancer compatibility, and simpler client-side state handling.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Source Code
&lt;/h3&gt;

&lt;p&gt;The implementation discussed here is part of the open-source IntelliDesk AI project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a href="https://github.com/Pruthviraj-333/intellidesk-ai" rel="noopener noreferrer"&gt;github.com/Pruthviraj-333/intellidesk-ai&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architecture Walkthrough:&lt;/strong&gt; &lt;a href="https://youtu.be/LGIFagqh4N4" rel="noopener noreferrer"&gt;YouTube Demo&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>python</category>
      <category>react</category>
    </item>
    <item>
      <title>Building IntelliDesk AI: How I Architected a Production-Grade Enterprise ITSM Platform with RAG, WebSockets, and Celery</title>
      <dc:creator>Pruthviraj Janwade</dc:creator>
      <pubDate>Thu, 01 Oct 2026 11:54:32 +0000</pubDate>
      <link>https://dev.to/pruthviraj_janwade_180b10/building-intellidesk-ai-how-i-architected-a-production-grade-enterprise-itsm-platform-with-rag-25j4</link>
      <guid>https://dev.to/pruthviraj_janwade_180b10/building-intellidesk-ai-how-i-architected-a-production-grade-enterprise-itsm-platform-with-rag-25j4</guid>
      <description>&lt;p&gt;&lt;em&gt;By Pruthviraj Janwade&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;If you have ever worked in an enterprise environment, you know the dread of filing an IT support ticket.&lt;/p&gt;

&lt;p&gt;You navigate through a labyrinthine portal, fill out a 12-field form with dropdowns you do not understand, and wait 24 to 48 hours just to receive an email asking: &lt;em&gt;"Have you tried restarting your machine?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Tools like &lt;strong&gt;ServiceNow&lt;/strong&gt;, &lt;strong&gt;Jira Service Management&lt;/strong&gt;, and &lt;strong&gt;Zendesk&lt;/strong&gt; are enterprise powerhouses, but they were built in an era of manual triage and static forms. While large language models (LLMs) have taken consumer tech by storm, enterprise IT service management (ITSM) has remained largely trapped in old paradigms.&lt;/p&gt;

&lt;p&gt;Over the past few months, I set out to bridge this gap by building &lt;strong&gt;IntelliDesk AI&lt;/strong&gt; — a production-grade, AI-powered enterprise ITSM platform featuring a conversational RAG assistant, automated ticket lifecycle management, real-time analytics, and role-based access control.&lt;/p&gt;

&lt;p&gt;In this deep dive, I want to share the architectural decisions, design patterns, engineering challenges, and lessons learned while building this platform from scratch.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Core Philosophy: Conversational-First ITSM
&lt;/h2&gt;

&lt;p&gt;The primary goal of IntelliDesk AI was simple: &lt;strong&gt;eliminate the friction of IT support for both employees and agents.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of forcing users to fill out static forms, the primary entry point is &lt;strong&gt;IntelliBot&lt;/strong&gt; — an intelligent conversational assistant that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Understands Natural Language&lt;/strong&gt;: An employee simply types, &lt;em&gt;"My Wi-Fi keeps disconnecting every 10 minutes on the 3rd floor."&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performs Semantic Document Search (RAG)&lt;/strong&gt;: Retrieves relevant company knowledge base guides with exact source attribution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resolves Autonomously&lt;/strong&gt;: Walks the employee through step-by-step diagnostic and troubleshooting steps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-Creates &amp;amp; Escalates Tickets&lt;/strong&gt;: If self-service fails, IntelliBot extracts the key details (category, urgency, root symptoms), provisions a ticket in the database, and assigns it to the on-duty IT team — without the user filling out a single form field.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  2. High-Level System Architecture
&lt;/h2&gt;

&lt;p&gt;To ensure modularity, scalability, and maintainability, the system is designed around &lt;strong&gt;Clean Architecture&lt;/strong&gt; principles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌────────────────────────────────────────────────────────┐
│                   Browser (React SPA)                  │
└───────────────────────────┬────────────────────────────┘
                            │ (HTTP / WSS)
                            ▼
┌────────────────────────────────────────────────────────┐
│           NGINX (Reverse Proxy &amp;amp; Rate Limiter)         │
└──────────────┬──────────────────────────┬──────────────┘
               │ (Proxy API)              │ (Proxy WebSocket)
               ▼                          ▼
┌────────────────────────────────────────────────────────┐
│         Flask 3 REST API + Socket.IO (Eventlet)        │
│       Controller ──&amp;gt; Service ──&amp;gt; Repository ──&amp;gt; Model  │
└───────┬──────────────┬──────────────┬──────────────┬───┘
        │              │              │              │
        ▼              ▼              ▼              ▼
┌──────────────┐┌──────────────┐┌──────────────┐┌──────────────┐
│  PostgreSQL  ││ Redis Cache  ││   ChromaDB   ││   Groq API   │
│ (Primary DB) ││  &amp;amp; Broker    ││(Vector Store)││(Llama 3.3 70B│
└──────────────┘└──────┬───────┘└──────┬───────┘└──────────────┘
                       │               │
                       ▼               │
               ┌───────────────┐       │
               │ Celery Worker │◄──────┘
               │ (Async Tasks) │ (Local Sentence Transformers)
               └───────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmqynfih4nar1xecvknde.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmqynfih4nar1xecvknde.png" alt=" " width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Architectural Highlights
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strict Layer Separation&lt;/strong&gt;: 

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Controllers&lt;/code&gt;: Pure HTTP routing, input validation (Marshmallow), and status codes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Services&lt;/code&gt;: Business logic, domain rules, and workflow orchestration.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Repositories&lt;/code&gt;: Database queries abstracted via SQLAlchemy 2.0 ORM.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Models&lt;/code&gt;: Data definitions and relationship mappings.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Microservices Orchestration&lt;/strong&gt;: Fully containerized using Docker and orchestrated with Docker Compose (Postgres, Redis, ChromaDB, Flask, Celery Worker, Celery Beat, Flower, NGINX).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. Designing the Retrieval-Augmented Generation (RAG) Pipeline
&lt;/h2&gt;

&lt;p&gt;A major pitfall of many LLM projects is high hallucination rates and exorbitant API costs. Here is how I addressed both:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Document Processing &amp;amp; Chunking
&lt;/h3&gt;

&lt;p&gt;When an IT administrator uploads internal documentation (PDFs, DOCX, TXT):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Text is extracted cleanly using &lt;code&gt;PyPDF2&lt;/code&gt; or &lt;code&gt;python-docx&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A recursive character text splitter splits documents into chunks of &lt;strong&gt;800 characters with an overlap of 150 characters&lt;/strong&gt;. This overlap preserves semantic context across chunk boundaries.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 2: Zero-Cost Local Embeddings
&lt;/h3&gt;

&lt;p&gt;Instead of calling paid embedding APIs (e.g., OpenAI &lt;code&gt;text-embedding-ada-002&lt;/code&gt; or &lt;code&gt;text-embedding-3-small&lt;/code&gt;), I integrated &lt;strong&gt;&lt;code&gt;sentence-transformers/all-MiniLM-L6-v2&lt;/code&gt;&lt;/strong&gt; directly into the container.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It runs locally on CPU with inference times under 15ms per chunk.&lt;/li&gt;
&lt;li&gt;Output dimensionality: 384 vectors.&lt;/li&gt;
&lt;li&gt;Result: &lt;strong&gt;$0 embedding cost and zero network overhead.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 3: Vector Indexing &amp;amp; Semantic Search
&lt;/h3&gt;

&lt;p&gt;Embeddings are indexed in &lt;strong&gt;ChromaDB&lt;/strong&gt;. When a query comes in:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The user's prompt is embedded using the same MiniLM model.&lt;/li&gt;
&lt;li&gt;ChromaDB runs cosine similarity to fetch the &lt;strong&gt;Top-K most relevant chunks&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;A confidence score threshold filters out low-relevance matches to prevent hallucinations.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 4: LLM Generation with Strategy Pattern
&lt;/h3&gt;

&lt;p&gt;To avoid vendor lock-in, I implemented an AI provider abstraction using the &lt;strong&gt;Strategy Pattern&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LLMProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ABC&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nd"&gt;@abstractmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;system_message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GroqProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LLMProvider&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;system_message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Ultra-fast inference using Groq Llama 3.3 70B
&lt;/span&gt;        &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Groq’s LPU (Language Processing Unit) delivers inference speeds of &lt;strong&gt;~250-300 tokens/sec&lt;/strong&gt;, making real-time streaming feel instantaneous.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Real-Time Streaming: Replacing Polling with WebSockets
&lt;/h2&gt;

&lt;p&gt;A common issue with AI chat interfaces is the delay while waiting for the LLM to complete its full response.&lt;/p&gt;

&lt;p&gt;Initially, simple REST polling or Server-Sent Events (SSE) were considered, but because IntelliDesk already needed bidirectional communication for live dashboard updates, I chose &lt;strong&gt;Flask-SocketIO with an Eventlet worker&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Streaming Protocol
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Client emits &lt;code&gt;ai:chat&lt;/code&gt; with user query and session ID.&lt;/li&gt;
&lt;li&gt;Server validates authentication token and emits &lt;code&gt;ai:stream:start&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;As Groq yields text tokens, server immediately emits &lt;code&gt;ai:stream:chunk&lt;/code&gt; payloads.&lt;/li&gt;
&lt;li&gt;Upon completion, server emits &lt;code&gt;ai:stream:done&lt;/code&gt; along with formatted source citations and confidence metrics.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client (React)                             Server (Flask + SocketIO)
      │                                                │
      │ ─── emit('ai:chat', { prompt, sessionId }) ──&amp;gt; │
      │                                                │ ──&amp;gt; Vector Search (ChromaDB)
      │                                                │ ──&amp;gt; Stream from Groq (Llama 3.3)
      │ &amp;lt;── emit('ai:stream:start') ────────────────── │
      │ &amp;lt;── emit('ai:stream:chunk', { token: '1.' }) ──│
      │ &amp;lt;── emit('ai:stream:chunk', { token: ' Turn' })│
      │ &amp;lt;── emit('ai:stream:done', { citations }) ──── │
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This reduced perceived latency from &lt;strong&gt;4–6 seconds down to under 200ms&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Background Jobs &amp;amp; Asynchronous Workflows (Celery + Redis)
&lt;/h2&gt;

&lt;p&gt;Heavy operations should never block an HTTP request. I set up &lt;strong&gt;Celery 5&lt;/strong&gt; with &lt;strong&gt;Redis 7&lt;/strong&gt; using dedicated priority queues:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Queue&lt;/th&gt;
&lt;th&gt;Tasks Handled&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;documents&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PDF parsing, chunking, vector embedding generation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ai&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Background ticket intent classification &amp;amp; summarization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;email&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;SMTP notifications for ticket status and SLA warnings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reports&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Periodic CSAT, SLA metrics, and analytics compilation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;By pairing Celery with &lt;strong&gt;Celery Beat&lt;/strong&gt; and &lt;strong&gt;RedBeat&lt;/strong&gt;, scheduled tasks run continuously in the background (e.g., checking for SLA breach thresholds every 60 seconds). For monitoring, &lt;strong&gt;Flower&lt;/strong&gt; provides a visual dashboard of task throughput and worker health.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Frontend Engineering with React 18 &amp;amp; TypeScript
&lt;/h2&gt;

&lt;p&gt;The frontend was built to feel like modern software from Linear or Vercel:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;State Strategy&lt;/strong&gt;: Clear separation between server cache and client state:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TanStack Query (React Query)&lt;/strong&gt; handles server synchronization, automatic caching, and background invalidation for tickets and analytics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redux Toolkit&lt;/strong&gt; manages local state (active AI chat session, dark/light theme, UI modals).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Socket Lifecycle Management&lt;/strong&gt;: Custom React hooks manage WebSocket connection lifecycles, graceful reconnection, and event buffering to ensure messages aren't lost during page navigation.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  7. Zero-Dollar Infrastructure: Running Production for $0/Month
&lt;/h2&gt;

&lt;p&gt;One of my proudest milestones with this project was achieving enterprise-grade capability on a &lt;strong&gt;$0/month infrastructure footprint&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Compute API&lt;/strong&gt;: Render Free Tier&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend SPA&lt;/strong&gt;: Vercel Global Edge Network&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Primary Database&lt;/strong&gt;: Neon Serverless PostgreSQL&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embeddings&lt;/strong&gt;: Local CPU execution (&lt;code&gt;all-MiniLM-L6-v2&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM Inference&lt;/strong&gt;: Groq Free Developer Tier (Llama 3.3 70B)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vector DB&lt;/strong&gt;: Embedded ChromaDB instance&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  8. What's Next: Enterprise Kubernetes Deployment on AWS EKS
&lt;/h2&gt;

&lt;p&gt;With the application architecture fully validated, I am now moving to the next engineering milestone: &lt;strong&gt;deploying IntelliDesk AI onto an enterprise-grade AWS EKS (Elastic Kubernetes Service) cluster.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The AWS Deployment Blueprint:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure as Code (IaC)&lt;/strong&gt;: Provisioning AWS VPC, subnets, IAM Roles for Service Accounts (IRSA), and managed node groups using &lt;strong&gt;Terraform&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kubernetes Packaging&lt;/strong&gt;: Writing modular &lt;strong&gt;Helm Charts&lt;/strong&gt; for the Flask backend, Celery workers, and NGINX Ingress Controller.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Managed Services Integration&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Amazon RDS (PostgreSQL Multi-AZ)&lt;/li&gt;
&lt;li&gt;Amazon ElastiCache (Redis)&lt;/li&gt;
&lt;li&gt;Amazon S3 for secure document storage&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitOps Continuous Delivery&lt;/strong&gt;: Automated cluster reconciliation and deployments using &lt;strong&gt;ArgoCD&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability&lt;/strong&gt;: &lt;strong&gt;Prometheus&lt;/strong&gt; for cluster metrics and &lt;strong&gt;Grafana&lt;/strong&gt; for executive operational dashboards.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  9. Conclusion &amp;amp; Takeaways
&lt;/h2&gt;

&lt;p&gt;Building IntelliDesk AI taught me several core engineering lessons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Design for abstraction early&lt;/strong&gt;: Isolating the LLM provider behind a clean interface saved hours when switching between models.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RAG is only as good as chunking&lt;/strong&gt;: Tuning chunk overlap and semantic boundaries matters far more than simply picking a larger LLM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User experience is latency-bound&lt;/strong&gt;: Real-time streaming via WebSockets fundamentally transforms conversational AI from a sluggish utility into a delightful product.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Explore the Code
&lt;/h3&gt;

&lt;p&gt;The entire project is open-source under the MIT license:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Repository&lt;/strong&gt;: &lt;a href="https://github.com/Pruthviraj-333/intellidesk-ai" rel="noopener noreferrer"&gt;github.com/Pruthviraj-333/intellidesk-ai&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch the Video Walkthrough&lt;/strong&gt;: &lt;a href="https://youtu.be/LGIFagqh4N4" rel="noopener noreferrer"&gt;YouTube Demo&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;If you found this breakdown valuable, feel free to star the repo or connect with me on LinkedIn!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>react</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
