<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Mohammad Palla's Dev Log</title>
    <link>https://palla.co.in/blog</link>
    <description>Voice AI, RAG, performance and web: by Mohammad Palla.</description>
    <language>en</language>
    <atom:link href="https://palla.co.in/rss.xml" rel="self" type="application/rss+xml"/>
    <lastBuildDate>Sun, 08 Nov 2026 13:39:37 GMT</lastBuildDate>
    <item>
      <title>My Girl Said “I’m Ready” and Then Made Me Wait 23 Minutes, APIs do the same thing</title>
      <link>https://palla.co.in/blog/browser-timing-tab-queued-requests-performance</link>
      <guid isPermaLink="true">https://palla.co.in/blog/browser-timing-tab-queued-requests-performance</guid>
      <pubDate>Sun, 04 Jan 2026 12:00:00 GMT</pubDate>
      <description>My girl said, “I’m ready, let’s go.” So I did what any reasonable person would do. I sat down. Relaxed. Waited. Five minutes passed. Ten minutes passed. Twentythree minutes passed. Turns out… she wasn’t ready. She was…</description>
      <category>react</category>
      <category>frontend</category>
      <category>dev-tools</category>
      <category>api</category>
      <content:encoded><![CDATA[<p>My girl said, <em>“I’m ready, let’s go.”</em></p>
<p>So I did what any reasonable person would do.</p>
<p>I sat down.</p>
<p>Relaxed.</p>
<p>Waited.</p>
<p>Five minutes passed.</p>
<p>Ten minutes passed.</p>
<p>Twenty-three minutes passed.</p>
<p>Turns out… she wasn’t ready.</p>
<p>She was <strong>stalling</strong>.</p>
<p>And that’s exactly how browsers treat your API requests.</p>
<p>Your backend might be ready.</p>
<p>Your frontend might be ready.</p>
<p>But Chrome? Chrome might just be sitting there like:</p>
<blockquote>
<p>“Yeah… hold on. Not yet.”</p>
</blockquote>
<hr>
<h2 id="the-tab-everyone-pretends-doesnt-exist"><a class="anchor" href="#the-tab-everyone-pretends-doesnt-exist" aria-hidden="true" tabindex="-1">#</a>The Tab Everyone Pretends Doesn’t Exist</h2>
<p>Let’s be honest most debugging sessions look like this:</p>
<ul>
<li><strong>Console</strong> → checking for red errors ✅</li>
<li><strong>Network</strong> → checking request status and response times ✅</li>
<li><strong>Elements</strong> → maybe poking around ❓</li>
<li><strong>Timing</strong> → <em>wait… what’s that?</em> ❌</li>
</ul>
<p>Even when developers do open Network, they usually stop at:</p>
<ul>
<li>Headers</li>
<li>Payload</li>
<li>Response</li>
</ul>
<p>Meanwhile, <strong>Timing</strong> sits right there, quietly exposing the <em>real</em> reason your app feels slow.</p>
<figure><img src="https://palla.co.in/api/media/6aeb8e7545f5fea945cbe74d.webp" alt="Illustration for &quot;My Girl Said “I’m Ready” and Then Made Me Wait 23 Minutes, APIs do the same thing&quot;" loading="lazy" decoding="async"></figure>
<hr>
<h2 id="that-tiny-timing-tab-youve-been-ignoring"><a class="anchor" href="#that-tiny-timing-tab-youve-been-ignoring" aria-hidden="true" tabindex="-1">#</a>That Tiny “Timing” Tab You’ve Been Ignoring</h2>
<p>This view lives <strong>inside Chrome DevTools → Network</strong>, after you click a request, right next to:</p>
<ul>
<li>Headers</li>
<li>Payload</li>
<li>Preview</li>
<li>Response</li>
<li>Initiator</li>
<li><strong>Timing</strong> ← this one</li>
</ul>
<p>Most developers never make it past “Response.” That’s a mistake.</p>
<p>Because the Timing tab doesn’t tell you <em>what</em> the server returned it tells you <strong>how long the browser made your request wait before it even got a chance</strong>.</p>
<p>Now look closely at the screenshot.</p>
<h3 id="whats-actually-happening-here"><a class="anchor" href="#whats-actually-happening-here" aria-hidden="true" tabindex="-1">#</a>What’s Actually Happening Here</h3>
<p>From the Timing breakdown:</p>
<ul>
<li><strong>Queueing:</strong> ~<strong>5.05s</strong></li>
<li><strong>Stalled:</strong> ~<strong>7.65s</strong></li>
<li><strong>Waiting for server response:</strong> <strong>10.22s</strong></li>
<li><strong>Content download:</strong> ~<strong>1.78ms</strong></li>
</ul>
<p>Total time before the frontend gets usable data:</p>
<p>👉 <strong>22.92 seconds</strong></p>
<p>Here’s the punchline:</p>
<blockquote>
<p>The server only needed 10.22 seconds. The page waited 22.92 seconds before showing the main data.</p>
</blockquote>
<p>That means <strong>more than half the time was spent doing absolutely nothing</strong> just waiting for the browser to allow the request to proceed.</p>
<hr>
<h3 id="queueing-vs-stalled-in-human-terms"><a class="anchor" href="#queueing-vs-stalled-in-human-terms" aria-hidden="true" tabindex="-1">#</a>Queueing vs Stalled (In Human Terms)</h3>
<ul>
<li><strong>Queueing</strong> The browser wanted to send the request… but couldn’t. All available connection slots for that domain were already taken.</li>
<li><strong>Stalled</strong> A slot <em>finally</em> opened up, but the browser still had to wait on internal scheduling, connection reuse, or other in-flight requests.</li>
</ul>
<p>None of this involves your backend. None of this shows up in server logs. None of this throws an error.</p>
<p>From the server’s point of view, this request was fast.</p>
<p>From the user’s point of view, your app felt broken.</p>
<hr>
<h3 id="why-this-screenshot-is-so-dangerous-and-so-useful"><a class="anchor" href="#why-this-screenshot-is-so-dangerous-and-so-useful" aria-hidden="true" tabindex="-1">#</a>Why This Screenshot Is So Dangerous (And So Useful)</h3>
<p>If you only looked at:</p>
<ul>
<li>the Response tab → “server is slow-ish”</li>
<li>backend logs → “everything looks normal”</li>
</ul>
<p>You’d miss the real issue completely.</p>
<p>The Timing tab exposes the uncomfortable truth:</p>
<blockquote>
<p>Your app wasn’t slow because the server took too long. It was slow because the browser made the request wait in line.</p>
</blockquote>
<hr>
<h2 id="the-browser-is-throttling-you-and-its-not-asking-permission"><a class="anchor" href="#the-browser-is-throttling-you-and-its-not-asking-permission" aria-hidden="true" tabindex="-1">#</a>The Browser Is Throttling You, And It’s Not Asking Permission</h2>
<p>Browsers enforce a <strong>maximum number of concurrent connections per origin</strong>.</p>
<p>For most modern browsers, that limit is roughly:</p>
<blockquote>
<p>~6 simultaneous requests per domain</p>
</blockquote>
<p>Once that limit is hit:</p>
<ul>
<li>New requests are <strong>queued</strong></li>
<li>They don’t reach the server</li>
<li>They don’t show up in backend logs</li>
<li>They just… wait</li>
</ul>
<p>This happens <em>before</em> JavaScript errors, <em>before</em> your API, and <em>before</em> any monitoring tools notice.</p>
<hr>
<h2 id="yes-youve-written-this-code-dont-lie"><a class="anchor" href="#yes-youve-written-this-code-dont-lie" aria-hidden="true" tabindex="-1">#</a>Yes, You’ve Written This Code Don’t Lie</h2>
<p>Imagine a dashboard page that loads the following <strong>on initial render</strong>:</p>
<ul>
<li>User profile</li>
<li>Organization settings</li>
<li>Permissions</li>
<li>Feature toggles</li>
<li>Notifications count</li>
<li>Sidebar navigation config</li>
<li><strong>Main dashboard data (critical)</strong></li>
</ul>
<p>All fired at once.</p>
<p>All sent to the <strong>same API domain</strong>.</p>
<p>All triggered by various <code>useEffect</code> hooks because “they’re independent.”</p>
<p>The browser looks at this and says:</p>
<blockquote>
<p>“Cool. I’ll take the first six.</p>
<p>The rest can wait.”</p>
</blockquote>
<p>Guess which request often gets queued?</p>
<p>Yep the <strong>one the page actually depends on</strong>.</p>
<hr>
<h2 id="why-everything-looks-fine-until-you-look-here"><a class="anchor" href="#why-everything-looks-fine-until-you-look-here" aria-hidden="true" tabindex="-1">#</a>Why Everything Looks Fine Until You Look Here</h2>
<figure><img src="https://palla.co.in/api/media/6aeb8e7745f5fea945cbe74e.webp" alt="Dev trying to get to the root cause" title="Dev trying to get to the root cause" loading="lazy" decoding="async"><figcaption>Dev trying to get to the root cause</figcaption></figure>
<p>From a developer’s point of view:</p>
<ul>
<li>APIs respond fast when tested alone</li>
<li>Backend logs show low latency</li>
<li>No errors in Console</li>
<li>No failed requests in Network</li>
</ul>
<p>But in the Timing tab you see something like:</p>
<ul>
<li>Queueing: <strong>6–8 seconds</strong></li>
<li>Request sent: <strong>microseconds</strong></li>
<li>Waiting (server): <strong>20ms</strong></li>
<li>Download: <strong>1ms</strong></li>
</ul>
<p>That’s when it clicks.</p>
<hr>
<h2 id="why-the-browser-doesnt-care-which-request-is-important"><a class="anchor" href="#why-the-browser-doesnt-care-which-request-is-important" aria-hidden="true" tabindex="-1">#</a>Why the Browser Doesn’t Care Which Request Is “Important”</h2>
<p>From the browser’s perspective, these are all equal:</p>
<pre><code class="hljs language-javascript"><span class="hljs-title function_">fetch</span>(<span class="hljs-string">&#x27;/profile&#x27;</span>)
<span class="hljs-title function_">fetch</span>(<span class="hljs-string">&#x27;/settings&#x27;</span>)
<span class="hljs-title function_">fetch</span>(<span class="hljs-string">&#x27;/permissions&#x27;</span>)
<span class="hljs-title function_">fetch</span>(<span class="hljs-string">&#x27;/features&#x27;</span>)
<span class="hljs-title function_">fetch</span>(<span class="hljs-string">&#x27;/notifications&#x27;</span>)
<span class="hljs-title function_">fetch</span>(<span class="hljs-string">&#x27;/navigation&#x27;</span>)
<span class="hljs-title function_">fetch</span>(<span class="hljs-string">&#x27;/dashboard-data&#x27;</span>)
</code></pre><p>Same origin.</p>
<p>Same priority.</p>
<p>Same timing.</p>
<p>The browser doesn’t understand your business logic.</p>
<p>It only understands <strong>connection slots</strong>.</p>
<p>Requests are allowed through <strong>in the order they’re initiated</strong>, not by importance.</p>
<hr>
<h2 id="the-real-bug-is-lets-just-fetch-everything"><a class="anchor" href="#the-real-bug-is-lets-just-fetch-everything" aria-hidden="true" tabindex="-1">#</a>The Real Bug Is “Let’s Just Fetch Everything”</h2>
<p>This isn’t about “too many requests.”</p>
<p>It’s about:</p>
<ul>
<li>Too many requests</li>
<li>At the same time</li>
<li>Competing for limited browser connections</li>
</ul>
<p>This pattern is extremely common in:</p>
<ul>
<li>React apps</li>
<li>Hook-based data fetching</li>
<li>“Load everything on mount” designs</li>
</ul>
<blockquote>
<p>🔥 Hot take</p>
<p>If your page <em>needs</em> 6+ API calls at the exact same time just to become usable, the problem isn’t performance — it’s architecture, and the way how you are writing code.</p>
</blockquote>
<hr>
<h2 id="fixing-it-by-designing-for-zero-queued-requests"><a class="anchor" href="#fixing-it-by-designing-for-zero-queued-requests" aria-hidden="true" tabindex="-1">#</a>Fixing It by Designing for Zero Queued Requests</h2>
<p>The real fix is not “optimize the slow request.”</p>
<p>The real fix is to <strong>design your system so that the browser never has a reason to queue a critical request at all</strong>.</p>
<hr>
<h2 id="how-to-debug-this-in-practice"><a class="anchor" href="#how-to-debug-this-in-practice" aria-hidden="true" tabindex="-1">#</a>How to Debug This in Practice</h2>
<p>Next time someone says <em>“the app feels slow”</em>, do this:</p>
<ol>
<li>Open <strong>DevTools → Network</strong></li>
<li>Click the slow request</li>
<li>Open the <strong>Timing</strong> tab (next to Headers / Payload / Response)</li>
<li>Look at:</li>
</ol>
<ul>
<li>Queueing time</li>
<li>Waiting time</li>
</ul>
<p>If <strong>Queueing &gt; 3–4ms</strong>, you’ve found the culprit.</p>
<figure><img src="https://palla.co.in/api/media/6aeb8e7945f5fea945cbe74f.webp" alt="Illustration for &quot;My Girl Said “I’m Ready” and Then Made Me Wait 23 Minutes, APIs do the same thing&quot;" loading="lazy" decoding="async"></figure>
<hr>
<h2 id="common-mistakes-to-avoid"><a class="anchor" href="#common-mistakes-to-avoid" aria-hidden="true" tabindex="-1">#</a>Common Mistakes to Avoid</h2>
<ul>
<li>❌ Treating all requests as equal</li>
<li>❌ Firing all requests on component mount</li>
<li>❌ Assuming no errors = good performance</li>
<li>❌ Ignoring the Timing tab entirely</li>
</ul>
<p>Performance bugs don’t always throw stack traces.</p>
<p>Some just quietly waste seconds.</p>
<hr>
<h2 id="key-takeaways-the-stuff-worth-remembering"><a class="anchor" href="#key-takeaways-the-stuff-worth-remembering" aria-hidden="true" tabindex="-1">#</a>Key Takeaways (The Stuff Worth Remembering)</h2>
<ul>
<li>The Timing tab lives <strong>inside Network</strong> and it’s critical</li>
<li>Queueing means the browser hasn’t sent the request yet</li>
<li>Browsers limit concurrent connections per domain</li>
<li>Too many parallel requests introduce hidden latency</li>
<li>Better sequencing beats aggressive preloading</li>
<li>The real fix is to <strong>design your data fetching so critical requests never have to wait in the queue</strong></li>
</ul>
<hr>
<h2 id="wrapping-it-up-design-so-nothing-queues"><a class="anchor" href="#wrapping-it-up-design-so-nothing-queues" aria-hidden="true" tabindex="-1">#</a>Wrapping It Up (Design So Nothing Queues)</h2>
<p>Your app wasn’t broken.</p>
<p>Your backend wasn’t slow.</p>
<p>Your code wasn’t “wrong” in the traditional sense.</p>
<p>The problem was architectural: you let the browser treat every request as equally important and equally urgent.</p>
<p>Once you:</p>
<ul>
<li>Define a clear critical path</li>
<li>Fire that path with minimal competition</li>
<li>Batch and delay non-essential calls</li>
<li>Centralize orchestration instead of sprinkling <code>fetch</code> everywhere</li>
</ul>
<p>…you stop “optimizing slow requests” and start <strong>designing a system where important requests never get queued in the first place</strong>.</p>
<p>So next time performance feels off, don’t just ask “how do I make this request faster?”</p>
<p>Ask:</p>
<blockquote>
<p>“Why did the browser have to queue this at all and how can I design things so it never needs to?”</p>
</blockquote>
<p>That’s where the real performance win lives, long before the request ever shows up in your logs.</p>
]]></content:encoded>
    </item>
    <item>
      <title>What Makes Real-Time Voice AI agents Feel Real</title>
      <link>https://palla.co.in/blog/what-makes-real-time-feel-real</link>
      <guid isPermaLink="true">https://palla.co.in/blog/what-makes-real-time-feel-real</guid>
      <pubDate>Sat, 06 Sep 2025 12:00:00 GMT</pubDate>
      <description>She interrupted me. Mid sentence. And weirdly… I loved it. Not because I enjoy being cut off, but because for the first time, an AI assistant felt human enough to jump into the conversation. That’s the magic of realtime…</description>
      <category>ai</category>
      <category>gen ai</category>
      <category>openai</category>
      <category>python</category>
      <category>voice-ai</category>
      <category>agents</category>
      <content:encoded><![CDATA[<p><strong>She interrupted me.</strong></p>
<p>Mid sentence.</p>
<p>And weirdly… I loved it.</p>
<p>Not because I enjoy being cut off, but because for the first time, an AI assistant felt <strong>human</strong> enough to jump into the conversation.</p>
<p>That’s the magic of <strong>real-time voice AI.</strong></p>
<hr>
<h2 id="the-story-behind-the-silence"><a class="anchor" href="#the-story-behind-the-silence" aria-hidden="true" tabindex="-1">#</a>The Story Behind the Silence</h2>
<ul>
<li><strong>Turn-Based Voice AI</strong> feels like a classroom: you speak, then the AI waits….. silently….. until you’re done. Only then does it think, respond, and speak. Predictable… but awkward.</li>
<li><strong>Real-Time Voice AI</strong>, however, listens and responds <em>as you speak</em>. It interrupts to clarify, builds anticipation, and makes the interaction feel alive. It’s not just hearing you it’s <em>conversing</em> with you.</li>
</ul>
<hr>
<h2 id="what-makes-real-time-feel-real"><a class="anchor" href="#what-makes-real-time-feel-real" aria-hidden="true" tabindex="-1">#</a>What Makes Real-Time Feel Real?</h2>
<div class="table-wrap"><table>
<thead>
<tr>
<th>Component</th>
<th>Turn-Based Flow</th>
<th>Real-Time Flow</th>
</tr>
</thead>
<tbody><tr>
<td>STT</td>
<td>Waits for full sentence before transcribing.</td>
<td>Streams partial transcriptions (chunks) on the fly.</td>
</tr>
<tr>
<td>LLM</td>
<td>Starts after transcription completes.</td>
<td>Begins processing as soon as partial input arrives.</td>
</tr>
<tr>
<td>TTS</td>
<td>Generates full output before speaking.</td>
<td>Speaks as soon as first tokens are ready.</td>
</tr>
<tr>
<td>UX</td>
<td>Delayed, segmented.</td>
<td>Smooth, conversational, anticipatory.</td>
</tr>
</tbody></table>
</div><p>But under the hood? It’s <strong>orchestration chaos</strong> managing barge in detection, aligning streams, handling interruptions, and keeping latency under 1 second.</p>
<hr>
<h2 id="when-to-pick-which"><a class="anchor" href="#when-to-pick-which" aria-hidden="true" tabindex="-1">#</a>When to Pick Which?</h2>
<ol>
<li><strong>Turn-Based</strong> (classic STT → LLM → TTS pipeline):</li>
</ol>
<p>✅ Easier to build and debug.</p>
<p>❌ Feels robotic with 0.7 to 3s delays.</p>
<ol start="3">
<li><strong>Real-Time (Speech-to-Speech)</strong>:</li>
</ol>
<p>✅ Natural, fluid, human like.</p>
<p>❌ Architecturally complex, less modular.</p>
<hr>
<h2 id="in-practice"><a class="anchor" href="#in-practice" aria-hidden="true" tabindex="-1">#</a>In Practice</h2>
<p>Modern systems still rely on <strong>STT → NLP → TTS</strong>, but optimized with:</p>
<ul>
<li><strong>Streaming ASR</strong> (&lt;300 ms)</li>
<li><strong>Low-latency inference</strong> (&lt;500 ms)</li>
<li><strong>Chunked TTS</strong> (&lt;200 ms to first audio)</li>
</ul>
<p>Done right, the whole pipeline feels instant.</p>
<hr>
<h2 id="tldr"><a class="anchor" href="#tldr" aria-hidden="true" tabindex="-1">#</a>TL;DR</h2>
<p>Turn-based AI listens.</p>
<p>Real-time AI <em>converses</em>.</p>
<p>And that tiny shift from waiting to weaving makes the difference between talking <em>to</em> a machine and talking <em>with</em> one.</p>
<hr>
]]></content:encoded>
    </item>
    <item>
      <title>How I Built a Talking, Knowledgeable AI Sidekick (and How You Can too build a Voice AI RAG agent )</title>
      <link>https://palla.co.in/blog/voice-ai-rag-agent</link>
      <guid isPermaLink="true">https://palla.co.in/blog/voice-ai-rag-agent</guid>
      <pubDate>Mon, 30 Jun 2025 12:00:00 GMT</pubDate>
      <description>It all started on a rainy afternoon. I was talking to my computer (as one does when he is a remote worker), and realized: Wouldn’t it be cool if my computer could actually listen, understand, and answer me with real…</description>
      <category>ai</category>
      <category>gen ai</category>
      <category>hugging face</category>
      <category>llama-index</category>
      <category>openai</category>
      <category>python</category>
      <category>voice-ai</category>
      <category>rag</category>
      <category>agents</category>
      <content:encoded><![CDATA[<h2 id="a-story-of-code-and-a-chatty-voice-ai-agent-that-actually-knows-stuff-from-your-"><a class="anchor" href="#a-story-of-code-and-a-chatty-voice-ai-agent-that-actually-knows-stuff-from-your-" aria-hidden="true" tabindex="-1">#</a>A Story of Code and a Chatty Voice AI agent That Actually Knows Stuff from your docs</h2>
<h3 id="chapter-1-the-dream"><a class="anchor" href="#chapter-1-the-dream" aria-hidden="true" tabindex="-1">#</a>Chapter 1: The Dream</h3>
<p>It all started on a rainy afternoon. I was talking to my computer (as one does when he is a remote worker), and realized:</p>
<p>Wouldn’t it be cool if my computer could actually listen, understand, and answer me with real knowledge from my own files?</p>
<p>Not just “Hey Siri, what’s the weather?” but “Hey AI, what’s in my project docs?” or “Remind me what the HR policy says about bringing cats to work?”</p>
<p>And so, the quest began:</p>
<p><strong>I would build a Voice AI RAG agent!</strong></p>
<p>(That’s Retrieval-Augmented Generation, but let’s just call it “RAG” because it sounds like a pirate.)</p>
<hr>
<h3 id="chapter-2-the-ingredients"><a class="anchor" href="#chapter-2-the-ingredients" aria-hidden="true" tabindex="-1">#</a>Chapter 2: The Ingredients</h3>
<p>Before you can summon your own digital sidekick, you’ll need a few magical artifacts:</p>
<ul>
<li><strong>Python 3.11+</strong> (the spellbook)</li>
<li><strong><a href="https://cartesia.ai/" target="_blank" rel="noopener noreferrer">Cartesia</a></strong> (for making your AI talk like a human, not a fax machine)</li>
<li><strong><a href="https://www.assemblyai.com/" target="_blank" rel="noopener noreferrer">AssemblyA</a>I</strong> (so your AI can understand your voice, even if you mumble)</li>
<li><strong><a href="https://www.anthropic.com/" target="_blank" rel="noopener noreferrer">Anthropic Claude</a></strong> (the brain—OpenAI is cool, but Claude is the new wizard in town)</li>
<li><strong><a href="https://livekit.io/" target="_blank" rel="noopener noreferrer">LiveKit</a></strong> (for real-time voice rooms, so your AI can join you in a virtual “room”)</li>
<li><strong>A pile of your own documents</strong> (so your AI knows your world)</li>
<li><strong>API keys</strong> (the secret runes—don’t lose them!)</li>
</ul>
<hr>
<h3 id="chapter-3-the-spell-aka-the-code"><a class="anchor" href="#chapter-3-the-spell-aka-the-code" aria-hidden="true" tabindex="-1">#</a>Chapter 3: The Spell (a.k.a. The Code)</h3>
<p>Here’s the full incantation. Don’t worry, I’ll explain every part after you read it.</p>
<p><em>(Copy, paste, and prepare to be amazed!)</em></p>
<pre><code class="hljs language-python"><span class="hljs-keyword">import</span> logging
<span class="hljs-keyword">import</span> os
<span class="hljs-keyword">from</span> dotenv <span class="hljs-keyword">import</span> load_dotenv
<span class="hljs-keyword">from</span> livekit.agents <span class="hljs-keyword">import</span> JobContext, JobProcess, WorkerOptions, cli
<span class="hljs-keyword">from</span> livekit.agents.job <span class="hljs-keyword">import</span> AutoSubscribe
<span class="hljs-keyword">from</span> livekit.agents.llm <span class="hljs-keyword">import</span> (
    ChatContext,
)
<span class="hljs-keyword">from</span> livekit.agents.pipeline <span class="hljs-keyword">import</span> VoicePipelineAgent
<span class="hljs-keyword">from</span> livekit.plugins <span class="hljs-keyword">import</span> cartesia, silero, llama_index, assemblyai

load_dotenv()

logger = logging.getLogger(<span class="hljs-string">&quot;voice-assistant&quot;</span>)
<span class="hljs-keyword">from</span> llama_index.llms.anthropic <span class="hljs-keyword">import</span> Anthropic
<span class="hljs-keyword">from</span> llama_index.core <span class="hljs-keyword">import</span> (
    SimpleDirectoryReader,
    StorageContext,
    VectorStoreIndex,
    load_index_from_storage,
    Settings
)
<span class="hljs-keyword">from</span> llama_index.core.chat_engine.types <span class="hljs-keyword">import</span> ChatMode
<span class="hljs-keyword">from</span> llama_index.embeddings.huggingface <span class="hljs-keyword">import</span> HuggingFaceEmbedding

load_dotenv()

<span class="hljs-comment"># Set up the embedding model and LLM</span>
embed_model = HuggingFaceEmbedding(model_name=<span class="hljs-string">&quot;BAAI/bge-small-en-v1.5&quot;</span>)
llm = Anthropic(model=<span class="hljs-string">&quot;claude-3-haiku-20240307&quot;</span>, max_tokens=<span class="hljs-number">512</span>)
Settings.llm = llm
Settings.embed_model = embed_model

<span class="hljs-comment"># check if storage already exists</span>
PERSIST_DIR = <span class="hljs-string">&quot;./chat-engine-storage&quot;</span>
<span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> os.path.exists(PERSIST_DIR):
    <span class="hljs-comment"># load the documents and create the index</span>
    documents = SimpleDirectoryReader(<span class="hljs-string">&quot;docs&quot;</span>).load_data()
    index = VectorStoreIndex.from_documents(documents)
    <span class="hljs-comment"># store it for later</span>
    index.storage_context.persist(persist_dir=PERSIST_DIR)
<span class="hljs-keyword">else</span>:
    <span class="hljs-comment"># load the existing index</span>
    storage_context = StorageContext.from_defaults(persist_dir=PERSIST_DIR)
    index = load_index_from_storage(storage_context)

<span class="hljs-keyword">def</span> <span class="hljs-title function_">prewarm</span>(<span class="hljs-params">proc: JobProcess</span>):
    proc.userdata[<span class="hljs-string">&quot;vad&quot;</span>] = silero.VAD.load()

<span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">entrypoint</span>(<span class="hljs-params">ctx: JobContext</span>):

    chat_context = ChatContext().append(
        role=<span class="hljs-string">&quot;system&quot;</span>,
        text=(
            <span class="hljs-string">&quot;You are a funny, witty assistant.&quot;</span>
            <span class="hljs-string">&quot;Respond with short and concise answers. Avoid using unpronouncable punctuation or emojis.&quot;</span>
        ),
    )
    
    chat_engine = index.as_chat_engine(chat_mode=ChatMode.CONTEXT)

    logger.info(<span class="hljs-string">f&quot;Connecting to room <span class="hljs-subst">{ctx.room.name}</span>&quot;</span>)
    <span class="hljs-keyword">await</span> ctx.connect(auto_subscribe=AutoSubscribe.AUDIO_ONLY)

    participant = <span class="hljs-keyword">await</span> ctx.wait_for_participant()
    logger.info(<span class="hljs-string">f&quot;Starting voice assistant for participant <span class="hljs-subst">{participant.identity}</span>&quot;</span>)

    agent = VoicePipelineAgent(
        vad=ctx.proc.userdata[<span class="hljs-string">&quot;vad&quot;</span>],
        stt=assemblyai.STT(),
        llm=llama_index.LLM(chat_engine=chat_engine),
        tts=cartesia.TTS(
            model=<span class="hljs-string">&quot;sonic-2&quot;</span>,
            voice=<span class="hljs-string">&quot;bf0a246a-8642-498a-9950-80c35e9276b5&quot;</span>,
        ),
        chat_ctx=chat_context,
    )

    agent.start(ctx.room, participant)

    <span class="hljs-keyword">await</span> agent.say(
        <span class="hljs-string">&quot;Hey there! How can I help you today?&quot;</span>,
        allow_interruptions=<span class="hljs-literal">True</span>,
    )

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">&quot;__main__&quot;</span>:
    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;Starting voice agent with Anthropic...&quot;</span>)
    cli.run_app(
        WorkerOptions(
            entrypoint_fnc=entrypoint,
            prewarm_fnc=prewarm,
        ),
    ) 
</code></pre><hr>
<h3 id="chapter-4-the-magic-explained"><a class="anchor" href="#chapter-4-the-magic-explained" aria-hidden="true" tabindex="-1">#</a>Chapter 4: The Magic Explained</h3>
<p>Let’s break down this spellbook, line by line:</p>
<p><strong>1. Imports and Setup</strong></p>
<p>We import all the libraries:</p>
<ul>
<li>livekit for voice rooms</li>
<li>cartesia for text-to-speech</li>
<li>assemblyai for speech-to-text</li>
<li>llama_index for RAG (so your AI can actually <em>know</em> things from your docs)</li>
<li>Anthropic for the LLM (the brain)</li>
</ul>
<p>We also load environment variables with dotenv—because hardcoding API keys is a rookie mistake.</p>
<p><strong>2. Embeddings and LLM</strong></p>
<pre><code class="hljs language-python">embed_model = HuggingFaceEmbedding(model_name=<span class="hljs-string">&quot;BAAI/bge-small-en-v1.5&quot;</span>)
llm = Anthropic(model=<span class="hljs-string">&quot;claude-3-haiku-20240307&quot;</span>, max_tokens=<span class="hljs-number">512</span>)
Settings.llm = llm
Settings.embed_model = embed_model
</code></pre><ul>
<li>The embedding model turns your docs into “AI food” (vectors).</li>
<li>The LLM (Claude) is the brain that answers questions using those vectors.</li>
</ul>
<p><strong>3. Document Indexing</strong></p>
<pre><code class="hljs language-python">PERSIST_DIR = <span class="hljs-string">&quot;./chat-engine-storage&quot;</span>
<span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> os.path.exists(PERSIST_DIR):
    documents = SimpleDirectoryReader(<span class="hljs-string">&quot;docs&quot;</span>).load_data()
    index = VectorStoreIndex.from_documents(documents)
    index.storage_context.persist(persist_dir=PERSIST_DIR)
<span class="hljs-keyword">else</span>:
    storage_context = StorageContext.from_defaults(persist_dir=PERSIST_DIR)
    index = load_index_from_storage(storage_context)
</code></pre><ul>
<li>If you haven’t indexed your docs before, it reads everything in docs/ and builds a knowledge base.</li>
<li>If you have, it loads the existing index (so it doesn’t have to re-read your 500-page PDF every time).</li>
</ul>
<p><strong>4. Voice Activity Detection (VAD)</strong></p>
<pre><code class="hljs language-python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">prewarm</span>(<span class="hljs-params">proc: JobProcess</span>):
    proc.userdata[<span class="hljs-string">&quot;vad&quot;</span>] = silero.VAD.load()
</code></pre><ul>
<li>This makes sure your AI only listens when you’re actually talking, not when you’re yelling at your cat.</li>
</ul>
<p><strong>5. The Entrypoint: Where the Magic Happens</strong></p>
<pre><code class="hljs language-python"><span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">entrypoint</span>(<span class="hljs-params">ctx: JobContext</span>):
    chat_context = ChatContext().append(
        role=<span class="hljs-string">&quot;system&quot;</span>,
        text=(
            <span class="hljs-string">&quot;You are a funny, witty assistant.&quot;</span>
            <span class="hljs-string">&quot;Respond with short and concise answers. Avoid using unpronouncable punctuation or emojis.&quot;</span>
        ),
    )
    chat_engine = index.as_chat_engine(chat_mode=ChatMode.CONTEXT)
    ...
</code></pre><ul>
<li>Sets the “personality” of your AI (witty, concise, no weird punctuation).</li>
<li>Prepares the chat engine with your indexed docs.</li>
</ul>
<pre><code class="hljs language-python">    logger.info(<span class="hljs-string">f&quot;Connecting to room <span class="hljs-subst">{ctx.room.name}</span>&quot;</span>)
    <span class="hljs-keyword">await</span> ctx.connect(auto_subscribe=AutoSubscribe.AUDIO_ONLY)
    participant = <span class="hljs-keyword">await</span> ctx.wait_for_participant()
    logger.info(<span class="hljs-string">f&quot;Starting voice assistant for participant <span class="hljs-subst">{participant.identity}</span>&quot;</span>)
</code></pre><ul>
<li>Connects to a LiveKit room (more on this soon).</li>
<li>Waits for a participant (that’s you!) to join.</li>
</ul>
<pre><code class="hljs language-python">    agent = VoicePipelineAgent(
        vad=ctx.proc.userdata[<span class="hljs-string">&quot;vad&quot;</span>],
        stt=assemblyai.STT(),
        llm=llama_index.LLM(chat_engine=chat_engine),
        tts=cartesia.TTS(
            model=<span class="hljs-string">&quot;sonic-2&quot;</span>,
            voice=<span class="hljs-string">&quot;bf0a246a-8642-498a-9950-80c35e9276b5&quot;</span>,
        ),
        chat_ctx=chat_context,
    )
    agent.start(ctx.room, participant)
    <span class="hljs-keyword">await</span> agent.say(
        <span class="hljs-string">&quot;Hey there! How can I help you today?&quot;</span>,
        allow_interruptions=<span class="hljs-literal">True</span>,
    )
</code></pre><ul>
<li>Sets up the full voice pipeline: listens, understands, thinks, and talks back.</li>
<li>Greets you with a friendly message.</li>
</ul>
<p><strong>6. The Main Event</strong></p>
<pre><code class="hljs language-python">
<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">&quot;__main__&quot;</span>:
    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;Starting voice agent with Anthropic...&quot;</span>)
    cli.run_app(
        WorkerOptions(
            entrypoint_fnc=entrypoint,
            prewarm_fnc=prewarm,
        ),
    ) 
</code></pre><hr>
<h4 id="chapter-5-summoning-your-ai-aka-running-the-code"><a class="anchor" href="#chapter-5-summoning-your-ai-aka-running-the-code" aria-hidden="true" tabindex="-1">#</a>Chapter 5: Summoning Your AI (a.k.a. Running the Code)</h4>
<ol>
<li><strong>Install your dependencies</strong> (see requirements.txt).</li>
<li><strong>Put your API keys</strong> in a .env file:</li>
</ol>
<pre><code class="hljs language-plain">    <span class="hljs-attr">ANTHROPIC_API_KEY</span>=your_anthropic_key
    <span class="hljs-attr">ASSEMBLYAI_API_KEY</span>=your_assemblyai_key
    <span class="hljs-attr">CARTESIA_API_KEY</span>=your_cartesia_api_key
    <span class="hljs-attr">LIVEKIT_URL</span>=your_livekit_url
    <span class="hljs-attr">LIVEKIT_API_KEY</span>=your_livekit_api_key
    <span class="hljs-attr">LIVEKIT_API_SECRET</span>=your_livekit_api_secret
</code></pre><ol>
<li><strong>Add your documents</strong> to the docs/ folder.</li>
<li><strong>Run</strong></li>
</ol>
<pre><code class="hljs language-bash">    python voice_agent_anthropic.py start
</code></pre><hr>
<h3 id="chapter-6-entering-the-livekit-room"><a class="anchor" href="#chapter-6-entering-the-livekit-room" aria-hidden="true" tabindex="-1">#</a>Chapter 6: Entering the LiveKit Room</h3>
<ul>
<li><strong>What’s a LiveKit room?</strong></li>
</ul>
<p>Think of it as a virtual meeting room where your AI is always waiting for you.</p>
<ul>
<li><strong>How do you join?</strong></li>
</ul>
<p>Use the <a href="https://agents-playground.livekit.io/" target="_blank" rel="noopener noreferrer">LiveKit Playground</a> or your own LiveKit client, enter your room name, and your AI will greet you like an old friend (who actually remembers your last conversation).</p>
<hr>
<h3 id="chapter-7-the-result"><a class="anchor" href="#chapter-7-the-result" aria-hidden="true" tabindex="-1">#</a>Chapter 7: The Result</h3>
<p>Now, you can:</p>
<ul>
<li><strong>Talk to your AI</strong>: Ask questions, get answers from your own docs.</li>
<li><strong>Get witty, concise responses</strong>: No more boring bots!</li>
<li><strong>Impress your friends</strong>: “Yeah, my Voice AI actually knows what’s in my files.”</li>
</ul>
<p>if you have any doubts you can contact me 🙂</p>
<h4 id="happy-coding"><a class="anchor" href="#happy-coding" aria-hidden="true" tabindex="-1">#</a>Happy coding! 🚀</h4>
]]></content:encoded>
    </item>
    <item>
      <title>How to build and display a custom download button to install PWA (Progressive Web App) on your website</title>
      <link>https://palla.co.in/blog/custom-button-to-install-pwa</link>
      <guid isPermaLink="true">https://palla.co.in/blog/custom-button-to-install-pwa</guid>
      <pubDate>Thu, 21 Nov 2024 12:00:00 GMT</pubDate>
      <description>Introduction Progressive Web Apps (PWAs) have gained massive popularity in modern web development. PWAs are web applications designed to deliver applike experiences directly through the browser. They can be installed on…</description>
      <category>pwa</category>
      <category>react</category>
      <category>zustand</category>
      <category>state management</category>
      <category>download button</category>
      <content:encoded><![CDATA[<h2 id="building-a-custom-install-button-for-your-progressive-web-app-pwa"><a class="anchor" href="#building-a-custom-install-button-for-your-progressive-web-app-pwa" aria-hidden="true" tabindex="-1">#</a>Building a Custom Install Button for Your Progressive Web App (PWA)</h2>
<p><strong>Introduction</strong></p>
<p>Progressive Web Apps (PWAs) have gained massive popularity in modern web development. PWAs are web applications designed to deliver app-like experiences directly through the browser. They can be installed on a device’s home screen, work offline, and utilize push notifications, making them a bridge between native apps and web apps.</p>
<p>One of the key features of a PWA is the ability to allow users to install it directly from the browser. While browsers often display a default &quot;Add to Home Screen&quot; banner, creating a custom install button ensures better control over the user experience.</p>
<p>In this blog, we’ll walk through how we created a <strong>custom download button</strong> for our PWA using:</p>
<ol>
<li>The <code>beforeinstallprompt</code> event.</li>
<li>State management (using Zustand).</li>
<li>A seamless experience that doesn’t require a page refresh.</li>
</ol>
<hr>
<h2 id="what-is-the-beforeinstallprompt-event"><a class="anchor" href="#what-is-the-beforeinstallprompt-event" aria-hidden="true" tabindex="-1">#</a>What is the beforeinstallprompt Event?</h2>
<p>The <code>beforeinstallprompt</code> event is fired by the browser when a web app meets the PWA installation criteria (e.g., HTTPS, manifest, service worker). This event gives developers control over how and when to show the installation prompt, allowing for custom UI elements like an &quot;Install App&quot; button.</p>
<h2 id="key-points-about-beforeinstallprompt"><a class="anchor" href="#key-points-about-beforeinstallprompt" aria-hidden="true" tabindex="-1">#</a>Key Points about beforeinstallprompt:</h2>
<ul>
<li><strong>Prevents default behavior</strong>: By calling <code>e.preventDefault()</code>, you stop the browser from automatically showing the default install banner.</li>
<li><strong>Stores the event</strong>: The event must be stored temporarily to trigger the prompt later when the user clicks the custom install button.</li>
<li><strong>Single-use</strong>: The <code>beforeinstallprompt</code> event can be used only once after firing. If dismissed, it won&#39;t fire again unless the user navigates to another page.</li>
</ul>
<hr>
<h2 id="the-challenge"><a class="anchor" href="#the-challenge" aria-hidden="true" tabindex="-1">#</a>The Challenge</h2>
<p>While the <code>beforeinstallprompt</code> event is straightforward to use, it has limitations:</p>
<ol>
<li>The event is lost when navigating between pages, requiring a reload to trigger again.</li>
<li>Storing and reusing the event requires careful handling since it&#39;s an event object and not serializable.</li>
</ol>
<p>Our goal was to overcome these challenges by implementing <strong>state management</strong> to store and reuse the event, allowing for a seamless install flow without refreshing the page.</p>
<hr>
<h2 id="solution-managing-the-beforeinstallprompt-event-with-zustand"><a class="anchor" href="#solution-managing-the-beforeinstallprompt-event-with-zustand" aria-hidden="true" tabindex="-1">#</a>Solution: Managing the beforeinstallprompt Event with Zustand</h2>
<p>Zustand is a lightweight state management library perfect for global state management in React applications. We used Zustand to store the <code>beforeinstallprompt</code> event, making it accessible across components without needing to refresh the page.</p>
<p>Here’s how we did it:</p>
<hr>
<h2 id="1-capturing-the-beforeinstallprompt-event"><a class="anchor" href="#1-capturing-the-beforeinstallprompt-event" aria-hidden="true" tabindex="-1">#</a>1. Capturing the beforeinstallprompt Event</h2>
<p>On the login page, we added an event listener to capture the <code>beforeinstallprompt</code> event and stored it in the Zustand store for later use.</p>
<pre><code class="hljs language-javascript"><span class="hljs-keyword">import</span> { useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;react&#x27;</span>;
<span class="hljs-keyword">import</span> useInstallPromptStore <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;./stores/useInstallPromptStore&#x27;</span>;

<span class="hljs-keyword">const</span> <span class="hljs-title function_">LoginPage</span> = (<span class="hljs-params"></span>) =&gt; {
  <span class="hljs-keyword">const</span> setDeferredPrompt = <span class="hljs-title function_">useInstallPromptStore</span>(<span class="hljs-function">(<span class="hljs-params">state</span>) =&gt;</span> state.<span class="hljs-property">setDeferredPrompt</span>);

  <span class="hljs-title function_">useEffect</span>(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> <span class="hljs-title function_">handleBeforeInstallPrompt</span> = (<span class="hljs-params">e</span>) =&gt; {
      e.<span class="hljs-title function_">preventDefault</span>(); <span class="hljs-comment">// Prevent the default banner</span>
      <span class="hljs-title function_">setDeferredPrompt</span>(e); <span class="hljs-comment">// Store the event in Zustand</span>
      <span class="hljs-variable language_">window</span>.<span class="hljs-property">sessionStorage</span>.<span class="hljs-title function_">setItem</span>(<span class="hljs-string">&#x27;deferredPromptFired&#x27;</span>, <span class="hljs-literal">true</span>); <span class="hljs-comment">// Optional: Store a flag</span>
    };

    <span class="hljs-variable language_">window</span>.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">&#x27;beforeinstallprompt&#x27;</span>, handleBeforeInstallPrompt);

    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-variable language_">window</span>.<span class="hljs-title function_">removeEventListener</span>(<span class="hljs-string">&#x27;beforeinstallprompt&#x27;</span>, handleBeforeInstallPrompt);
    };
  }, [setDeferredPrompt]);

  <span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>Login Page<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>;
};
</code></pre><hr>
<h2 id="2-storing-the-event-in-zustand"><a class="anchor" href="#2-storing-the-event-in-zustand" aria-hidden="true" tabindex="-1">#</a>2. Storing the Event in Zustand</h2>
<p>We created a Zustand store to manage the <code>beforeinstallprompt</code> event and a flag to track whether it has been fired.</p>
<pre><code class="hljs language-javascript"><span class="hljs-keyword">import</span> { create } <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;zustand&#x27;</span>;

<span class="hljs-keyword">const</span> useInstallPromptStore = <span class="hljs-title function_">create</span>(<span class="hljs-function">(<span class="hljs-params">set</span>) =&gt;</span> ({
  <span class="hljs-attr">deferredPrompt</span>: <span class="hljs-literal">null</span>, <span class="hljs-comment">// Store the event</span>
  <span class="hljs-attr">isPromptFired</span>: <span class="hljs-literal">false</span>, <span class="hljs-comment">// Flag to indicate event firing</span>

  <span class="hljs-attr">setDeferredPrompt</span>: <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> <span class="hljs-title function_">set</span>({ <span class="hljs-attr">deferredPrompt</span>: event, <span class="hljs-attr">isPromptFired</span>: <span class="hljs-literal">true</span> }),  
  <span class="hljs-attr">clearDeferredPrompt</span>: <span class="hljs-function">() =&gt;</span> <span class="hljs-title function_">set</span>({ <span class="hljs-attr">deferredPrompt</span>: <span class="hljs-literal">null</span>, <span class="hljs-attr">isPromptFired</span>: <span class="hljs-literal">false</span> }),
}));

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> useInstallPromptStore;
</code></pre><hr>
<h2 id="3-displaying-the-custom-install-button"><a class="anchor" href="#3-displaying-the-custom-install-button" aria-hidden="true" tabindex="-1">#</a>3. Displaying the Custom Install Button</h2>
<p>On the next page (e.g., the main dashboard or home page), we retrieved the stored <code>beforeinstallprompt</code> event from the Zustand store and used it to display a custom install button.</p>
<pre><code class="hljs language-javascript"><span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;react&#x27;</span>;
<span class="hljs-keyword">import</span> useInstallPromptStore <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;./stores/useInstallPromptStore&#x27;</span>;

<span class="hljs-keyword">const</span> <span class="hljs-title function_">HomePage</span> = (<span class="hljs-params"></span>) =&gt; {
  <span class="hljs-keyword">const</span> deferredPrompt = <span class="hljs-title function_">useInstallPromptStore</span>(<span class="hljs-function">(<span class="hljs-params">state</span>) =&gt;</span> state.<span class="hljs-property">deferredPrompt</span>);
  <span class="hljs-keyword">const</span> isPromptFired = <span class="hljs-title function_">useInstallPromptStore</span>(<span class="hljs-function">(<span class="hljs-params">state</span>) =&gt;</span> state.<span class="hljs-property">isPromptFired</span>);
  <span class="hljs-keyword">const</span> clearDeferredPrompt = <span class="hljs-title function_">useInstallPromptStore</span>(<span class="hljs-function">(<span class="hljs-params">state</span>) =&gt;</span> state.<span class="hljs-property">clearDeferredPrompt</span>);

  <span class="hljs-keyword">const</span> [showInstallButton, setShowInstallButton] = <span class="hljs-title function_">useState</span>(<span class="hljs-literal">false</span>);

  <span class="hljs-title function_">useEffect</span>(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> promptFired = <span class="hljs-variable language_">window</span>.<span class="hljs-property">sessionStorage</span>.<span class="hljs-title function_">getItem</span>(<span class="hljs-string">&#x27;deferredPromptFired&#x27;</span>);
    <span class="hljs-keyword">if</span> (isPromptFired || promptFired) {
      <span class="hljs-title function_">setShowInstallButton</span>(<span class="hljs-literal">true</span>);
    }
  }, [isPromptFired]);

  <span class="hljs-keyword">const</span> <span class="hljs-title function_">handleInstallClick</span> = (<span class="hljs-params"></span>) =&gt; {
    <span class="hljs-keyword">if</span> (deferredPrompt) {
      deferredPrompt.<span class="hljs-title function_">prompt</span>(); <span class="hljs-comment">// Trigger the install prompt</span>
      deferredPrompt.<span class="hljs-property">userChoice</span>.<span class="hljs-title function_">then</span>(<span class="hljs-function">(<span class="hljs-params">choiceResult</span>) =&gt;</span> {
        <span class="hljs-keyword">if</span> (choiceResult.<span class="hljs-property">outcome</span> === <span class="hljs-string">&#x27;accepted&#x27;</span>) {
          <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&#x27;User accepted the install prompt&#x27;</span>);
        } <span class="hljs-keyword">else</span> {
          <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&#x27;User dismissed the install prompt&#x27;</span>);
        }
        <span class="hljs-title function_">clearDeferredPrompt</span>(); <span class="hljs-comment">// Clear the stored event after use</span>
        <span class="hljs-variable language_">window</span>.<span class="hljs-property">sessionStorage</span>.<span class="hljs-title function_">removeItem</span>(<span class="hljs-string">&#x27;deferredPromptFired&#x27;</span>);
      });
    }
  };

  <span class="hljs-keyword">return</span> (
    <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      {showInstallButton &amp;&amp; <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{handleInstallClick}</span>&gt;</span>Install App<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-title class_">HomePage</span>;
</code></pre><hr>
<h2 id="why-this-works"><a class="anchor" href="#why-this-works" aria-hidden="true" tabindex="-1">#</a>Why This Works</h2>
<ol>
<li><strong>Centralized State with Zustand</strong>:</li>
</ol>
<p>Using Zustand, we stored the <code>beforeinstallprompt</code> event in a central location, making it accessible across components without needing to reload the page.</p>
<ol start="3">
<li><strong>Session Flag for Fallback</strong>:</li>
</ol>
<p>By storing a flag (<code>deferredPromptFired</code>) in <code>sessionStorage</code>, we ensured the install button is displayed even if the user navigates back and forth between pages.</p>
<ol start="5">
<li><strong>Custom UI for Better Experience</strong>:</li>
</ol>
<p>The custom install button gave us full control over the user experience, ensuring the prompt appears when users are most likely to engage with it.</p>
<hr>
<h2 id="conclusion"><a class="anchor" href="#conclusion" aria-hidden="true" tabindex="-1">#</a>Conclusion</h2>
<p>Creating a custom install button for your PWA using the <code>beforeinstallprompt</code> event and Zustand is a robust solution for managing the PWA installation flow. By leveraging state management, you can provide a seamless user experience, eliminate the need for page reloads, and encourage more users to install your app.</p>
<p>PWAs are powerful tools for bridging the gap between web and native applications. Adding a touch of customization like this can make a significant difference in user adoption.</p>
<p>Happy coding! 🚀</p>
]]></content:encoded>
    </item>
    <item>
      <title>Avoiding Ad Overcrowding with Geospatial Indexes</title>
      <link>https://palla.co.in/blog/geospatial-indexes-mongodb</link>
      <guid isPermaLink="true">https://palla.co.in/blog/geospatial-indexes-mongodb</guid>
      <pubDate>Sun, 04 Aug 2024 12:00:00 GMT</pubDate>
      <description>Alright, folks, gather 'round. Let's talk about a little problem I solved while working on a project. Imagine a world where ads are like cockroaches—everywhere, creeping up next to each other, grossing everyone out. Our…</description>
      <category>mongodb</category>
      <category>geospatial</category>
      <category>geojson</category>
      <category>ads</category>
      <content:encoded><![CDATA[<p>Alright, folks, gather &#39;round. Let&#39;s talk about a little problem I solved while working on a project. Imagine a world where ads are like cockroaches—everywhere, creeping up next to each other, grossing everyone out. Our task? To make sure these pesky ads don’t end up on top of each other or within 3 meters like this.</p>
<figure><img src="https://palla.co.in/api/media/6aeb8e7c45f5fea945cbe751.webp" alt="Illustration for &quot;Avoiding Ad Overcrowding with Geospatial Indexes&quot;" loading="lazy" decoding="async"></figure>
<p>How did we do it? With a dash of Mongoose magic and a sprinkle of geospatial indexing. We’re dealing with ads, and not the kind that you can skip after 5 seconds. No, these are the persistent kind, and they can’t be all over the same place. We need to make sure each ad is spaced out like introverts at a party. The challenge? Preventing any ad from being placed within 3 meters of another.</p>
<h2 id="the-solution"><a class="anchor" href="#the-solution" aria-hidden="true" tabindex="-1">#</a>The Solution</h2>
<p>Enter Mongoose, our trusty sidekick, armed with geospatial superpowers. By using Mongoose&#39;s support for GeoJSON objects and 2dsphere indexes, we can efficiently handle geospatial data and queries. Here&#39;s how I did it:</p>
<h2 id="schema-design"><a class="anchor" href="#schema-design" aria-hidden="true" tabindex="-1">#</a>Schema Design</h2>
<p>First, the schema. You know, the thing that defines the structure of our data. Here’s what it looks like:</p>
<pre><code class="hljs language-javascript"><span class="hljs-keyword">import</span> mongoose <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;mongoose&quot;</span>;

<span class="hljs-keyword">const</span> <span class="hljs-title class_">Schema</span> = mongoose.<span class="hljs-property">Schema</span>;

<span class="hljs-keyword">const</span> adPostSchema = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Schema</span>({
  <span class="hljs-comment">// other irrelevant stuff</span>
  <span class="hljs-title class_">Latitude</span>: <span class="hljs-title class_">Number</span>,
  <span class="hljs-title class_">Longitude</span>: <span class="hljs-title class_">Number</span>,
  <span class="hljs-attr">location</span>: {
    <span class="hljs-attr">type</span>: { <span class="hljs-attr">type</span>: <span class="hljs-title class_">String</span>, <span class="hljs-attr">enum</span>: [<span class="hljs-string">&quot;Point&quot;</span>], <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span> },
    <span class="hljs-attr">coordinates</span>: { <span class="hljs-attr">type</span>: [<span class="hljs-title class_">Number</span>], <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span> },
  },
});

adPostSchema.<span class="hljs-title function_">index</span>({ <span class="hljs-attr">location</span>: <span class="hljs-string">&quot;2dsphere&quot;</span> });

<span class="hljs-keyword">const</span> <span class="hljs-title class_">AdPost</span> = mongoose.<span class="hljs-title function_">model</span>(<span class="hljs-string">&quot;AdPost&quot;</span>, adPostSchema);

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-title class_">AdPost</span>;
</code></pre><p>Let me break it down for you:</p>
<ul>
<li><code>location.type</code>: This is always &quot;Point&quot;. It’s not rocket science, just geometry.</li>
<li><code>location.coordinates</code>: It’s a pair of numbers—longitude and latitude. Yes, the Earth is round.</li>
<li><code>2dsphere</code> <strong>index</strong>: This is where the magic happens. It tells MongoDB, &quot;Hey, treat this like a globe.&quot; Because, guess what? The Earth isn’t flat, no matter what your conspiracy-theorist uncle says.</li>
</ul>
<h2 id="geospatial-query-implementation"><a class="anchor" href="#geospatial-query-implementation" aria-hidden="true" tabindex="-1">#</a>Geospatial Query Implementation</h2>
<p>To prevent ad overcrowding, we crafted a query to check for existing ads within a 3-meter radius before adding a new one. Think of it as a digital bouncer, ensuring no ad gets too close for comfort:</p>
<pre><code class="hljs language-javascript"><span class="hljs-keyword">const</span> <span class="hljs-title function_">postAd</span> = <span class="hljs-keyword">async</span> (<span class="hljs-params">req, res</span>) =&gt; {
  <span class="hljs-keyword">const</span> {
    <span class="hljs-comment">// other irrelevent stuff</span>
    <span class="hljs-title class_">Latitude</span>,
    <span class="hljs-title class_">Longitude</span>,
  } = req.<span class="hljs-property">body</span>;

  <span class="hljs-keyword">try</span> {
    <span class="hljs-comment">// Define the location in GeoJSON format</span>
    <span class="hljs-keyword">const</span> location = {
      <span class="hljs-attr">type</span>: <span class="hljs-string">&quot;Point&quot;</span>,
      <span class="hljs-attr">coordinates</span>: [<span class="hljs-title class_">Longitude</span>, <span class="hljs-title class_">Latitude</span>],
    };

    <span class="hljs-comment">// Check for existing ads within 3 meters (0.003 km)</span>
    <span class="hljs-keyword">const</span> existingAd = <span class="hljs-keyword">await</span> <span class="hljs-title class_">AdPost</span>.<span class="hljs-title function_">findOne</span>({
      <span class="hljs-attr">location</span>: {
        <span class="hljs-attr">$near</span>: {
          <span class="hljs-attr">$geometry</span>: location,
          <span class="hljs-attr">$maxDistance</span>: <span class="hljs-number">3</span>, <span class="hljs-comment">// distance in meters</span>
        },
      },
    });

    <span class="hljs-keyword">if</span> (existingAd) {
      <span class="hljs-keyword">return</span> res.<span class="hljs-title function_">status</span>(<span class="hljs-number">400</span>).<span class="hljs-title function_">json</span>({ <span class="hljs-attr">message</span>: <span class="hljs-string">&quot;An ad already exists within 3 meters of this location.&quot;</span> });
    }

    <span class="hljs-comment">// Proceed to save the ad if no conflicts are found</span>

  } <span class="hljs-keyword">catch</span> (error) {
    res.<span class="hljs-title function_">status</span>(<span class="hljs-number">500</span>).<span class="hljs-title function_">send</span>(<span class="hljs-string">&quot;Error adding post: &quot;</span>);
    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(error);
  }
};
</code></pre><p>Here&#39;s what happens:</p>
<ul>
<li>We define the location using GeoJSON</li>
<li>We use the <code>$near</code> operator to find any ads within a 3-meter radius. If we find one, we block the new ad. Simple as that.</li>
</ul>
<h2 id="conclusion"><a class="anchor" href="#conclusion" aria-hidden="true" tabindex="-1">#</a>Conclusion</h2>
<p>And that’s it. We managed to keep ads from piling up like trash on a New York street. Thanks to the 2dsphere index, we can efficiently and accurately manage ad placements without the headache. For those who want to dive deeper into the glorious details, check out <a href="https://mongoosejs.com/docs/geojson.html" target="_blank" rel="noopener noreferrer">Mongoose&#39;s GeoJSON documentation</a>. Enjoy.</p>
]]></content:encoded>
    </item>
    <item>
      <title>How to build a RAG app with groq (llama-3) and llama-index, Hugging Face without OpenAI API keys and with minimal compute</title>
      <link>https://palla.co.in/blog/RAG-app-with-groq-and-llama-index-without-OpenAI</link>
      <guid isPermaLink="true">https://palla.co.in/blog/RAG-app-with-groq-and-llama-index-without-OpenAI</guid>
      <pubDate>Wed, 24 Jul 2024 12:00:00 GMT</pubDate>
      <description>Let's build a RetrievalAugmented Generation (RAG) application using Groq (Llama3) and LlamaIndex without any OpenAI API keys and with minimal compute. If this project can run on my laptop, it can run on anyone’s laptop…</description>
      <category>ai</category>
      <category>gen ai</category>
      <category>llama</category>
      <category>openai</category>
      <category>python</category>
      <category>llama-index</category>
      <category>groq</category>
      <category>lpu</category>
      <category>hugging face</category>
      <content:encoded><![CDATA[<h2 id="building-a-rag-app-with-groq-llama-3-hugging-face-opensource-model-and-llama-ind"><a class="anchor" href="#building-a-rag-app-with-groq-llama-3-hugging-face-opensource-model-and-llama-ind" aria-hidden="true" tabindex="-1">#</a>Building a RAG App with Groq (Llama-3), Hugging Face opensource model and Llama-Index: A Love-Hate Story</h2>
<p>Let&#39;s build a Retrieval-Augmented Generation (RAG) application using Groq (Llama-3) and Llama-Index without any OpenAI API keys and with minimal compute. If this project can run on my laptop, it can run on anyone’s laptop because I only have 8 GB of RAM and an SSD—no graphics card, no fancy stuff. But before we dive into the code, let me share a little tale about my experience with Llama-Index.</p>
<p>Llama-Index is a gem. However, its documentation is... how do I put this nicely? Let&#39;s just say it could use a lot more hand-holding and a lot less cryptic messaging. I was frustrated because I was unable to build a RAG chat app with <strong>my data</strong>, <strong>my custom prompt</strong>, and <strong>chat history memory</strong> in Llama-Index. The documentation didn’t even hint that it was possible! I almost gave up, but then I decided to dive into the source code of Llama-Index to see how it was implemented. And bless the engineers who wrote that code—it&#39;s beautifully well-structured and commented. Finally, I understood how to implement exactly what I wanted and even found some great ways to customize it to fit my needs.</p>
<p>So, if you&#39;re ready, let&#39;s get our hands dirty and build some cool stuff!</p>
<h2 id="setting-up-the-environment"><a class="anchor" href="#setting-up-the-environment" aria-hidden="true" tabindex="-1">#</a>Setting Up the Environment</h2>
<p>First, we need to set up our environment. We use Flask to create a web application and several modules from Llama-Index for document processing and embedding.</p>
<pre><code class="hljs language-python">pip install Flask requests llama-index==<span class="hljs-number">0.10</span><span class="hljs-number">.18</span> llama-index-llms-groq==<span class="hljs-number">0.1</span><span class="hljs-number">.3</span> llama-index-embeddings-huggingface==<span class="hljs-number">0.2</span><span class="hljs-number">.0</span>
</code></pre><p>Installing a few necessary packages</p>
<pre><code class="hljs language-python"><span class="hljs-keyword">from</span> flask <span class="hljs-keyword">import</span> Flask, request, render_template
<span class="hljs-keyword">import</span> re
<span class="hljs-keyword">from</span> llama_index.core <span class="hljs-keyword">import</span> (
VectorStoreIndex,
SimpleDirectoryReader,
StorageContext,
ServiceContext,
load_index_from_storage,
)
<span class="hljs-keyword">from</span> llama_index.embeddings.huggingface <span class="hljs-keyword">import</span> HuggingFaceEmbedding
<span class="hljs-keyword">from</span> llama_index.core.node_parser <span class="hljs-keyword">import</span> SentenceSplitter
<span class="hljs-keyword">from</span> llama_index.llms.groq <span class="hljs-keyword">import</span> Groq
<span class="hljs-keyword">from</span> llama_index.core.base.llms.types <span class="hljs-keyword">import</span> ChatMessage, MessageRole
<span class="hljs-keyword">import</span> warnings
warnings.filterwarnings(<span class="hljs-string">&#x27;ignore&#x27;</span>)
</code></pre><p>Ignoring warnings is very important because:</p>
<figure><img src="https://palla.co.in/api/media/6aeb8e6a45f5fea945cbe746.webp" alt="Illustration for &quot;How to build a RAG app with groq (llama-3) and llama-index, Hugging Face without OpenAI API keys and with minimal compute&quot;" loading="lazy" decoding="async"></figure>
<h2 id="initializing-flask-app-and-api-key"><a class="anchor" href="#initializing-flask-app-and-api-key" aria-hidden="true" tabindex="-1">#</a>Initializing Flask App and API Key</h2>
<p>Next, we initialize the Flask app and set up the API key for <a href="https://console.groq.com/keys" target="_blank" rel="noopener noreferrer">Groq</a>.</p>
<pre><code class="hljs language-python">GROQ_API_KEY = <span class="hljs-string">&quot;&lt;insert your API key here&gt;&quot;</span>
app = Flask(__name__)
</code></pre><h2 id="defining-the-rag-chatbot-function"><a class="anchor" href="#defining-the-rag-chatbot-function" aria-hidden="true" tabindex="-1">#</a>Defining the RAG Chatbot Function</h2>
<p>The core of our application is the <code>chat_with_rag</code> function. This function takes a user message and chat history, processes the documents, and generates a response using the RAG approach. In your root directory make a txt or a pdf file named “paul_graham_essays.txt” and put in some of his essays</p>
<pre><code class="hljs language-python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">chat_with_rag</span>(<span class="hljs-params">message, history</span>):
		reader = SimpleDirectoryReader(input_files=[<span class="hljs-string">&quot;./paul_graham_essays.txt&quot;</span>]) 
		documents = reader.load_data()
		text_splitter = SentenceSplitter(chunk_size=<span class="hljs-number">1024</span>, chunk_overlap=<span class="hljs-number">200</span>)
		nodes = text_splitter.get_nodes_from_documents(documents, show_progress=<span class="hljs-literal">True</span>)
		
		embed_model = HuggingFaceEmbedding(model_name=<span class="hljs-string">&quot;sentence-transformers/all-MiniLM-L6-v2&quot;</span>)
		
		llm = Groq(model=<span class="hljs-string">&quot;llama3-70b-8192&quot;</span>, api_key=GROQ_API_KEY)
		
		prompt = <span class="hljs-string">&quot;enter your system prompt here&quot;</span>
		
		service_context = ServiceContext.from_defaults(embed_model=embed_model, llm=llm, 
		system_prompt=prompt)
		
		vector_index = VectorStoreIndex.from_documents(documents, show_progress=<span class="hljs-literal">True</span>, 
		service_context=service_context, node_parser=nodes)
		
		vector_index.storage_context.persist(persist_dir=<span class="hljs-string">&quot;./storage_mini&quot;</span>)
		
		storage_context = StorageContext.from_defaults(persist_dir=<span class="hljs-string">&quot;./storage_mini&quot;</span>)
		
		index = load_index_from_storage(storage_context, service_context=service_context)
		
		query_engine = index.as_chat_engine(service_context=service_context,
		chat_mode=<span class="hljs-string">&#x27;condense_plus_context&#x27;</span>)
		
		resp = query_engine.chat(message, chat_history=history)
		
		<span class="hljs-keyword">return</span> resp.response
</code></pre><p><strong>Step-by-step Explanation:</strong></p>
<ul>
<li><strong>Document Reading</strong>: We use <code>SimpleDirectoryReader</code> to read documents from a file.</li>
<li><strong>Text Splitting</strong>: The <code>SentenceSplitter</code> splits the documents into chunks for efficient processing.</li>
<li><strong>Embedding Model</strong>: We use <code>HuggingFaceEmbedding</code> with the <code>sentence-transformers/all-MiniLM-L6-v2</code> light weight model.</li>
<li><strong>Language Model</strong>: The Groq model is initialized with the <code>llama3-70b-8192</code> model. (Why Gooq? because it uses LPUs which are insanely fast you should read more about)</li>
<li><strong>Service Context</strong>: Combines the embedding model, language model, and a system prompt. (I found this from source code, its not mentioned in the docs)</li>
</ul>
<figure><img src="https://palla.co.in/api/media/6aeb8e6b45f5fea945cbe747.webp" alt="Illustration for &quot;How to build a RAG app with groq (llama-3) and llama-index, Hugging Face without OpenAI API keys and with minimal compute&quot;" loading="lazy" decoding="async"></figure>
<ul>
<li><strong>Vector Index</strong>: Creates a vector index from the documents and persists it to disk.</li>
<li><strong>Query Engine</strong>: Loads the index and sets up the query engine to handle chat interactions.</li>
</ul>
<p>with query_engine you can use 2 methods</p>
<figure><img src="https://palla.co.in/api/media/6aeb8e6d45f5fea945cbe748.webp" alt="Illustration for &quot;How to build a RAG app with groq (llama-3) and llama-index, Hugging Face without OpenAI API keys and with minimal compute&quot;" loading="lazy" decoding="async"></figure>
<p>and another one that I have used is</p>
<figure><img src="https://palla.co.in/api/media/6aeb8e6e45f5fea945cbe749.webp" alt="Illustration for &quot;How to build a RAG app with groq (llama-3) and llama-index, Hugging Face without OpenAI API keys and with minimal compute&quot;" loading="lazy" decoding="async"></figure>
<p>Something else that you can play with is this line in chat_with_rag function</p>
<pre><code class="hljs language-python">query_engine = index.as_chat_engine(service_context=service_context, chat_mode=<span class="hljs-string">&#x27;condense_plus_context&#x27;</span>)
</code></pre><p>I used chat_mode=&#39;condense_plus_context&#39; but you can customize things according to your need</p>
<figure><img src="https://palla.co.in/api/media/6aeb8e7045f5fea945cbe74a.webp" alt="Illustration for &quot;How to build a RAG app with groq (llama-3) and llama-index, Hugging Face without OpenAI API keys and with minimal compute&quot;" loading="lazy" decoding="async"></figure>
<h2 id="setting-up-flask-routes"><a class="anchor" href="#setting-up-flask-routes" aria-hidden="true" tabindex="-1">#</a>Setting Up Flask Routes</h2>
<p>We define our chat routes in our Flask application</p>
<pre><code class="hljs language-python"><span class="hljs-meta">@app.route(<span class="hljs-params"><span class="hljs-string">&#x27;/chat&#x27;</span>, methods=[<span class="hljs-string">&#x27;POST&#x27;</span>]</span>)</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">chat</span>():
		data = request.json
		history_json = data.get(<span class="hljs-string">&#x27;messages&#x27;</span>, [])
		system_prompt = {
		    <span class="hljs-string">&#x27;role&#x27;</span>: <span class="hljs-string">&#x27;system&#x27;</span>,
		    <span class="hljs-string">&#x27;content&#x27;</span>: <span class="hljs-string">&#x27;&lt;enter your system prompt here optional&gt;&#x27;</span>
		}
		history_json.insert(<span class="hljs-number">0</span>, system_prompt)
		
		user_message = <span class="hljs-string">&#x27;&#x27;</span>
		history = []
		
		<span class="hljs-keyword">if</span> history_json:
		    user_message = history_json[-<span class="hljs-number">1</span>][<span class="hljs-string">&#x27;content&#x27;</span>]
		    history = [convert_to_chat_message(msg[<span class="hljs-string">&#x27;role&#x27;</span>], msg[<span class="hljs-string">&#x27;content&#x27;</span>]) <span class="hljs-keyword">for</span> msg <span class="hljs-keyword">in</span> history_json]
		
		bot_response = chat_with_rag(user_message, history=history)
		
		<span class="hljs-keyword">return</span> {<span class="hljs-string">&#x27;response&#x27;</span>: bot_response}
</code></pre><p><strong>Explanation:</strong></p>
<ul>
<li><strong>Chat Route</strong>: Handles POST requests with user messages, processes the chat history, and returns the bot&#39;s response. data.get.messages is an array of json objects that should come from your client or from your other server if you have a microservice arch. Each json should be in the form</li>
</ul>
<pre><code class="hljs language-json"><span class="hljs-punctuation">{</span>role<span class="hljs-punctuation">:</span><span class="hljs-string">&quot;user or assistant&quot;</span><span class="hljs-punctuation">,</span> content<span class="hljs-punctuation">:</span><span class="hljs-string">&quot;some sample text&quot;</span><span class="hljs-punctuation">}</span>
</code></pre><h2 id="helper-function"><a class="anchor" href="#helper-function" aria-hidden="true" tabindex="-1">#</a>Helper Function</h2>
<p>The <code>convert_to_chat_message</code> function converts chat history from JSON format to <code>ChatMessage</code> objects. This step is very important if you want to maintain a chat_history and basically chat with your RAG and guess what…… this step is not mentioned in the docs 😞</p>
<pre><code class="hljs language-python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">convert_to_chat_message</span>(<span class="hljs-params">role: <span class="hljs-built_in">str</span>, content: <span class="hljs-built_in">str</span></span>) -&gt; ChatMessage:
    <span class="hljs-string">&quot;&quot;&quot;Converts a role and content into a ChatMessage object.&quot;&quot;&quot;</span>
    <span class="hljs-comment"># Convert role string to MessageRole enum</span>
    role_enum = MessageRole(role)
    <span class="hljs-comment"># Create and return a ChatMessage object</span>
    <span class="hljs-keyword">return</span> ChatMessage(role=role_enum, content=content)
</code></pre><h2 id="running-the-application"><a class="anchor" href="#running-the-application" aria-hidden="true" tabindex="-1">#</a>Running the Application</h2>
<p>Finally, we run the Flask app.</p>
<pre><code class="hljs language-python"><span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">&#x27;__main__&#x27;</span>:
    app.run(debug=<span class="hljs-literal">True</span>)
</code></pre><h2 id="conclusion"><a class="anchor" href="#conclusion" aria-hidden="true" tabindex="-1">#</a>Conclusion</h2>
<p>By following these steps, you can build a RAG application using Groq and Llama-Index without relying on OpenAI API keys. This setup ensures minimal compute requirements while maintaining the efficiency and effectiveness of the RAG approach. Just remember, while Llama-Index might have a bit of a learning curve due to its documentation, it&#39;s definitely worth the effort. Happy coding!</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
