<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ chatbot - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ chatbot - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sat, 30 May 2026 14:17:33 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/chatbot/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Build and Deploy a Production-Ready WhatsApp Bot with FastAPI, Evolution API, Docker, EasyPanel, and GCP ]]>
                </title>
                <description>
                    <![CDATA[ WhatsApp bots are widely used for customer support, automated replies, notifications, and internal tools. Instead of relying on expensive third-party platforms, you can build and deploy your own self- ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-and-deploy-a-production-ready-whatsapp-bot/</link>
                <guid isPermaLink="false">699877ac3dc17c4862f466c7</guid>
                
                    <category>
                        <![CDATA[ chatbot ]]>
                    </category>
                
                    <category>
                        <![CDATA[ whatsapp ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Cloud Computing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ google cloud ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Cloud ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Raju Manoj ]]>
                </dc:creator>
                <pubDate>Fri, 20 Feb 2026 15:03:08 +0000</pubDate>
                <media:content url="https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/5e1e335a7a1d3fcc59028c64/de480f02-206a-4325-b4c2-788d33d746b1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>WhatsApp bots are widely used for customer support, automated replies, notifications, and internal tools. Instead of relying on expensive third-party platforms, you can build and deploy your own self-hosted WhatsApp bot using modern open-source tools.</p>
<p>In this tutorial, you’ll learn how to build and deploy a production-ready WhatsApp bot using:</p>
<ul>
<li><p>FastAPI</p>
</li>
<li><p>Evolution API</p>
</li>
<li><p>Docker</p>
</li>
<li><p>EasyPanel</p>
</li>
<li><p>Google Cloud Platform (GCP)</p>
</li>
</ul>
<p>By the end of this guide, you will have a fully working WhatsApp bot connected to your own WhatsApp account and deployed on a cloud virtual machine.</p>
<h2 id="heading-table-of-contents"><strong>Table of Contents</strong></h2>
<ul>
<li><p><a href="#heading-how-the-architecture-works">How the Architecture Works</a></p>
</li>
<li><p><a href="#heading-how-your-whatsapp-bot-works">How Your WhatsApp Bot Works</a></p>
</li>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-step-1-create-firewall-rules-on-gcp">Step 1: Create Firewall Rules on GCP</a></p>
</li>
<li><p><a href="#heading-step-2-create-a-virtual-machine-ubuntu-2204">Step 2: Create a Virtual Machine (Ubuntu 22.04)</a></p>
</li>
<li><p><a href="#heading-step-3-ssh-into-the-vm">Step 3: SSH into the VM</a></p>
</li>
<li><p><a href="#heading-step-4-install-docker">Step 4: Install Docker</a></p>
</li>
<li><p><a href="#heading-step-5-install-easypanel">Step 5: Install EasyPanel</a></p>
</li>
<li><p><a href="#heading-step-6-open-the-easypanel-dashboard">Step 6: Open the EasyPanel Dashboard</a></p>
</li>
<li><p><a href="#heading-step-7-deploy-evolution-api">Step 7: Deploy Evolution API</a></p>
</li>
<li><p><a href="#heading-step-8-connect-whatsapp">Step 8: Connect WhatsApp</a></p>
</li>
<li><p><a href="#heading-step-9-deploy-the-fastapi-bot">Step 9: Deploy the FastAPI Bot</a></p>
</li>
<li><p><a href="#heading-step-10-connect-the-webhook-telling-evolution-api-where-to-send-messages">Step 10: Connect the Webhook - Telling Evolution API Where to Send Messages</a></p>
</li>
<li><p><a href="#heading-step-11-final-test">Step 11: Final Test</a></p>
</li>
<li><p><a href="#heading-production-considerations">Production Considerations</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-how-the-architecture-works">How the Architecture Works</h2>
<p>Before we start installing anything, let’s understand how the system works.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770444944919/cbcd4016-c98d-4379-9c7c-2d22ab9e309a.png" alt="This diagram shows how a WhatsApp message flows from the user’s WhatsApp app, through WhatsApp servers, into a GCP VM (via firewall, Docker, and EasyPanel) where Evolution API receives it, triggers a FastAPI bot via webhook, processes the logic, and sends the reply back to the user through WhatsApp." width="2619" height="4286" loading="lazy">

<h2 id="heading-how-your-whatsapp-bot-works">How Your WhatsApp Bot Works</h2>
<p>Before we continue setting things up, let's make sure you understand what's actually happening behind the scenes. Don't worry – no technical experience needed here.</p>
<h3 id="heading-imagine-a-postal-service">Imagine a postal service</h3>
<p>Think of your WhatsApp bot like a very fast, automated postal service:</p>
<ul>
<li><p>Someone sends you a letter (a WhatsApp message)</p>
</li>
<li><p>A postal worker (Evolution API) picks it up and brings it to your office</p>
</li>
<li><p>Your office manager (FastAPI bot) reads it and writes a reply</p>
</li>
<li><p>The postal worker takes the reply back and delivers it</p>
</li>
</ul>
<p>That's it. That's the whole system.</p>
<h3 id="heading-the-7-steps">The 7 steps</h3>
<ol>
<li><p>Someone sends a message to your WhatsApp number – just like texting a friend.</p>
</li>
<li><p>Evolution API notices the message – it's constantly watching your WhatsApp number for new messages, like a receptionist sitting by the phone.</p>
</li>
<li><p>Evolution API passes the message to your bot – it sends the message content to your app and says <em>"hey, you've got a new message!"</em></p>
</li>
<li><p>Your bot reads the message and decides what to say – this is where your code does its job.</p>
</li>
<li><p>Your bot sends the reply back to Evolution API – <em>"okay, send this response."</em></p>
</li>
<li><p>Evolution API delivers the reply through WhatsApp.</p>
</li>
<li><p>The user sees the reply on their phone – usually within seconds.</p>
</li>
</ol>
<h3 id="heading-one-line-summary">One line summary</h3>
<pre><code class="language-plaintext">User → WhatsApp → Evolution API → Your Bot → Evolution API → WhatsApp → User
</code></pre>
<p>Every step in this guide is just setting up one piece of that chain. Once they're all connected, the whole thing runs on its own automatically.</p>
<p>This architecture allows you to automate replies while keeping full control of your infrastructure.</p>
<h3 id="heading-why-these-tools">Why These Tools?</h3>
<p>Let’s briefly understand why we’re using each tool.</p>
<h4 id="heading-fastapi">FastAPI</h4>
<p>FastAPI is a modern Python framework for building APIs. It is fast, lightweight, and ideal for handling webhook requests from Evolution API.</p>
<h4 id="heading-evolution-api">Evolution API</h4>
<p>Evolution API is a self-hosted WhatsApp automation server built on top of Baileys. It connects your personal WhatsApp account without requiring official WhatsApp Business API approval.</p>
<h4 id="heading-docker">Docker</h4>
<p>Docker allows us to run applications in containers. This makes deployments consistent, portable, and production-ready.</p>
<h4 id="heading-easypanel">EasyPanel</h4>
<p>EasyPanel is a graphical platform for managing Docker services. Instead of writing Docker Compose files manually, we use EasyPanel’s UI to deploy and manage our services easily.</p>
<h4 id="heading-google-cloud-platform-gcp">Google Cloud Platform (GCP)</h4>
<p>GCP provides the virtual machine that hosts our infrastructure. We will use an Ubuntu 22.04 server to run Docker, EasyPanel, Evolution API, and our FastAPI bot.</p>
<p>I chose these tools because they are practical, lightweight, and suitable for real-world production deployments.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before starting, make sure you have:</p>
<ul>
<li><p>A Google Cloud, AWS, or Azure account</p>
</li>
<li><p>Billing enabled</p>
</li>
<li><p>A project selected</p>
</li>
<li><p>Access to Cloud Shell</p>
</li>
<li><p>Basic Linux and Docker knowledge</p>
</li>
</ul>
<h2 id="heading-step-1-create-firewall-rules-on-gcp">Step 1: Create Firewall Rules on GCP</h2>
<p>We need to allow traffic to specific ports on our VM. So, we run this command in GCP Cloud Shell:</p>
<pre><code class="language-bash">gcloud compute firewall-rules create easypanel-whatsapp-fw \
 --network default \
 --direction INGRESS \
 --priority 1000 \
 --action ALLOW \
 --rules tcp:22,tcp:80,tcp:443,tcp:3000,tcp:8080,tcp:9000,tcp:5000-5999 \
 --source-ranges 0.0.0.0/0 \
 --description "SSH, EasyPanel, Evolution API, Bot"
</code></pre>
<p>This command:</p>
<ul>
<li><p>Creates a <strong>firewall rule</strong> named <code>easypanel-whatsapp-fw</code></p>
</li>
<li><p>On the <strong>default network</strong></p>
</li>
<li><p>Allows incoming internet traffic (<code>INGRESS</code>)</p>
</li>
<li><p>Opens these ports:</p>
<ul>
<li><p><code>22</code> → SSH (server access)</p>
</li>
<li><p><code>80</code> → HTTP</p>
</li>
<li><p><code>443</code> → HTTPS</p>
</li>
<li><p><code>3000, 8080, 9000</code> → App panels / APIs</p>
</li>
<li><p><code>5000–5999</code> → Custom app range</p>
</li>
</ul>
</li>
<li><p>Allows access from <strong>any IP address</strong> (<code>0.0.0.0/0</code>)</p>
</li>
</ul>
<p>Basically It opens your server so people (and you) can access your apps and services from the internet. This firewall rule allows external traffic to reach your VM.</p>
<h2 id="heading-step-2-create-a-virtual-machine-ubuntu-2204">Step 2: Create a Virtual Machine (Ubuntu 22.04)</h2>
<p>Now we'll create the server that hosts everything. Run the following command in the GCP Cloud Shell to set up a virtual machine with Ubuntu 22.04.</p>
<pre><code class="language-bash">gcloud compute instances create whatsapp-vm \
  --zone=asia-south1-a \
  --machine-type=e2-medium \
  --image-family=ubuntu-2204-lts \
  --image-project=ubuntu-os-cloud \
  --boot-disk-size=30GB \
  --tags=easypanel
</code></pre>
<p>This command creates a new virtual machine (VM) on Google Cloud:</p>
<ul>
<li><p><strong>Name:</strong> <code>whatsapp-vm</code></p>
</li>
<li><p><strong>Location (zone):</strong> <code>asia-south1-a</code> (India region)</p>
</li>
<li><p><strong>Machine size:</strong> <code>e2-medium</code> (2 vCPU, 4GB RAM)</p>
</li>
<li><p><strong>Operating System:</strong> Ubuntu 22.04 LTS</p>
</li>
<li><p><strong>Disk size:</strong> 30GB</p>
</li>
<li><p><strong>Tag:</strong> <code>easypanel</code> (used to apply firewall rules)</p>
</li>
</ul>
<p>This creates a Linux server in Google Cloud that you can use to host EasyPanel, WhatsApp bot, or your APIs.</p>
<p>Note: Wait about one minute for the instance to start.</p>
<h2 id="heading-step-3-ssh-into-the-vm">Step 3: SSH into the VM</h2>
<p>Connect to your server by using SSH to access the virtual machine you just created on Google Cloud.</p>
<pre><code class="language-bash">gcloud compute ssh whatsapp-vm --zone=asia-south1-a
</code></pre>
<p>This command connects to your virtual machine named <code>whatsapp-vm</code> in the zone <code>asia-south1-a</code> using SSH (secure remote login).</p>
<p>It logs you into your Google Cloud server so you can start installing software and running commands. After running this, you will see a terminal prompt – that means you are now inside your Ubuntu server and ready to go.</p>
<h2 id="heading-step-4-install-docker">Step 4: Install Docker</h2>
<p>Docker is needed to run EasyPanel and the Evolution API.</p>
<p><strong>First update the system:</strong></p>
<pre><code class="language-bash">sudo apt update -y
sudo apt install -y curl
</code></pre>
<p>This does two things:</p>
<ol>
<li><p><code>sudo apt update -y</code>→ Updates your server’s package list (refreshes available software info).</p>
</li>
<li><p><code>sudo apt install -y curl</code>→ Installs <strong>curl</strong>, a tool used to download things from the internet using the terminal.</p>
</li>
</ol>
<p>It prepares your server and installs a tool needed to download and install other software.</p>
<p><strong>Then install Docker:</strong></p>
<pre><code class="language-bash">curl -fsSL https://get.docker.com | sudo sh
</code></pre>
<p>This command uses <code>curl</code> to download Docker’s official installation script. The <code>|</code> (pipe) sends it directly to <code>sudo sh</code>, which runs the script as administrator.</p>
<p>It automatically installs <strong>Docker</strong> on your server.</p>
<p>After this finishes, Docker should be installed.</p>
<p><strong>Enable Docker:</strong></p>
<pre><code class="language-bash">sudo systemctl enable docker
sudo systemctl start docker
</code></pre>
<p>This command does two things:</p>
<ol>
<li><p><code>enable docker</code>→ Makes Docker start automatically every time the server reboots.</p>
</li>
<li><p><code>start docker</code>→ Starts Docker right now.</p>
</li>
</ol>
<p>It turns Docker ON now and makes sure it stays ON after restart.</p>
<p><strong>Allow the Ubuntu user to run Docker:</strong></p>
<pre><code class="language-bash">sudo usermod -aG docker ubuntu
</code></pre>
<p>This command adds the user <code>ubuntu</code> to the Docker group.</p>
<p>This is important: By default, you must use <code>sudo</code> before every Docker command.After running this, the Ubuntu user can run Docker without needing sudo every time.</p>
<p>Note: This command assumes your username is <code>ubuntu</code>, which is the default on Google Cloud VMs. If your username is different, replace Ubuntu with your actual username.</p>
<p><strong>Exit the session and reconnect:</strong></p>
<pre><code class="language-bash">exit
gcloud compute ssh whatsapp-vm --zone=asia-south1-a
</code></pre>
<ol>
<li><p><code>exit</code>→ Logs you out of your current server session.</p>
</li>
<li><p><code>gcloud compute ssh whatsapp-vm --zone=asia-south1-a</code>→ Logs you back into your Google Cloud VM.</p>
</li>
</ol>
<p>Why we do this: After adding the <code>ubuntu</code> user to the Docker group, you must log out and log back in for the permission changes to work.</p>
<p><strong>Test Docker:</strong></p>
<pre><code class="language-bash">docker run hello-world
</code></pre>
<p>This command downloads a small test image called <strong>hello-world</strong>, runs it inside Docker, and prints a success message if Docker is working correctly.</p>
<p>It checks if Docker is installed and working properly. If you see “Hello from Docker!”, Docker is working correctly.</p>
<h2 id="heading-step-5-install-easypanel">Step 5: Install EasyPanel</h2>
<p>EasyPanel provides a user interface for deploying Docker services. Run this command in the VM:</p>
<pre><code class="language-bash">curl -sSL https://get.easypanel.io | sudo bash
</code></pre>
<p>This command:</p>
<ul>
<li><p>Downloads the official <strong>EasyPanel installation script</strong></p>
</li>
<li><p>Runs it with administrator (sudo) permission</p>
</li>
<li><p>Automatically installs and configures EasyPanel on your server</p>
</li>
</ul>
<p>It installs EasyPanel on your VM so you can manage apps using a web dashboard instead of commands. Installation takes about one minute.</p>
<h2 id="heading-step-6-open-the-easypanel-dashboard">Step 6: Open the EasyPanel Dashboard</h2>
<p>Once you have your IP address, open a new tab in your browser and type it in like this:</p>
<pre><code class="language-plaintext">http://&lt;YOUR_PUBLIC_IP&gt;:3000
</code></pre>
<p>For example, if your IP was 34.123.45.67, you would type:</p>
<pre><code class="language-plaintext">http://34.123.45.67:3000
</code></pre>
<p>EasyPanel runs on port 3000 by default – that's why we add :3000 at the end. Without it, your browser won't know which service to open on the server.</p>
<p>Create an admin account and log in; the EasyPanel login page will appear.</p>
<p>Click <strong>“Create Admin Account”</strong>.</p>
<p>Fill in:</p>
<ul>
<li><p><strong>Username</strong> (choose something you’ll remember)</p>
</li>
<li><p><strong>Email</strong></p>
</li>
<li><p><strong>Password</strong> (make it strong!)</p>
</li>
<li><p>Submit the form.</p>
</li>
</ul>
<p>You are now logged in as the admin and can start managing apps, APIs, and bots through the EasyPanel dashboard.</p>
<p>You will see a page like the one below:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771478879926/c222ae8c-8714-4af2-a631-c6fead8185e8.png" alt="easypanel page" style="display:block;margin:0 auto" width="1908" height="1060" loading="lazy">

<h2 id="heading-step-7-deploy-evolution-api">Step 7: Deploy Evolution API</h2>
<ol>
<li><p>Create a new project (for example: <code>whatsapp-1</code>)</p>
</li>
<li><p>Go to Services → Templates</p>
</li>
<li><p>Select Evolution API</p>
</li>
<li><p>Deploy the latest version</p>
</li>
</ol>
<p>Wait until all services turn green. You will see a page like the one below.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771479080612/ce8453b2-e20e-4c37-b150-de4e032ba794.png" alt="Deploying evolution-api" style="display:block;margin:0 auto" width="1919" height="995" loading="lazy">

<p>Next, open Environment Variables and locate:</p>
<pre><code class="language-plaintext">AUTHENTICATION_API_KEY
</code></pre>
<p>Copy the AUTHENTICATION_API_KEY.</p>
<p>Open the Evolution API dashboard</p>
<p>Inside EasyPanel, find your Evolution API service. You will see a clickable domain link – it usually looks something like:</p>
<pre><code class="language-plaintext">https://evolution-api.easypanel.host
</code></pre>
<p>Click that link to open it in your browser. You will see a JSON response confirming the service is running.</p>
<p>Once you open the link, you’ll see a JSON response confirming success. To proceed with login, copy the <strong>Manager link</strong> displayed in the response. This link opens the management dashboard where you can authenticate and begin using the Evolution API. The screenshot below highlights the manager URL along with version details for easy reference</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771480117778/017524df-f8be-41bd-9d6c-0ba04b499fe9.png" alt="manager URL" style="display:block;margin:0 auto" width="840" height="396" loading="lazy">

<p>Copy the manager link and open it in a new tab, then copy the AUTHENTICATION_API_KEY, which you did in the previous step. This is how it looks, as you can see below:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771480570638/9e18ca51-1637-4261-be22-7cd89ee0459f.png" alt="Evolution manager" style="display:block;margin:0 auto" width="1919" height="831" loading="lazy">

<p>Create a new instance:</p>
<ul>
<li><p>Choose channel: <strong>Baileys</strong></p>
</li>
<li><p>Leave phone number blank</p>
</li>
<li><p>Give your instance a name</p>
</li>
</ul>
<p>Save the instance.</p>
<h2 id="heading-step-8-connect-whatsapp">Step 8: Connect WhatsApp</h2>
<p>Inside your instance dashboard:</p>
<ol>
<li><p>Click <strong>Get QR</strong></p>
</li>
<li><p>Scan it using WhatsApp on your phone</p>
</li>
</ol>
<p>Once connected, your chats and contacts will sync automatically. If syncing fails, disconnect and reconnect the session.</p>
<h2 id="heading-step-9-deploy-the-fastapi-bot">Step 9: Deploy the FastAPI Bot</h2>
<p>Now we’ll deploy the bot service.</p>
<h3 id="heading-1-go-to-easypanel">1: Go to EasyPanel</h3>
<p>You’re opening the EasyPanel dashboard you just installed. This is where you can manage apps, servers, and services using a graphical interface instead of terminal commands.</p>
<h3 id="heading-2-create-a-new-project">2: Create a new project</h3>
<p>A “project” is like a container or folder for your bot service. It organizes all files, settings, and deployments for this app.</p>
<h3 id="heading-3-add-an-app-service">3: Add an App service</h3>
<p>“App service” means a running instance of your application. In this case, it will be the WhatsApp bot.</p>
<h3 id="heading-4-choose-git-deployment">4: Choose Git deployment</h3>
<p>Git deployment lets you connect a code repository to EasyPanel.This will automatically download your code from GitHub and run it inside Docker.</p>
<h3 id="heading-5-paste-your-repository-url">5: Paste your repository URL</h3>
<pre><code class="language-plaintext">https://github.com/rajumanoj333/wabot
</code></pre>
<p>This is the GitHub repository containing the WhatsApp bot code. EasyPanel will clone this repo and prepare the app automatically.</p>
<h3 id="heading-6-domains-in-easypanel">6: Domains in EasyPanel</h3>
<p>This section lets you assign a URL or domain name to your app service. Even if you don’t have a custom domain, you can use your server’s public IP. Your WhatsApp bot app runs on port 9000 inside the server.</p>
<h3 id="heading-7-set-the-port-to-9000">7: Set the port to <code>9000</code></h3>
<p>By setting the domain to use port 9000, EasyPanel knows where to send traffic.</p>
<p>Example URL after this step:</p>
<pre><code class="language-plaintext">https://your-project.easypanel.host
</code></pre>
<p>This is the public address people (and other services) will use to reach your bot.</p>
<p>You’re telling EasyPanel:</p>
<blockquote>
<p>“Whenever someone accesses this project, forward them to the bot service running on port 9000.”</p>
</blockquote>
<p>Without this step, the bot service would run but <strong>you wouldn’t be able to access it from your browser or other apps</strong>.</p>
<h3 id="heading-configure-environment-variables">Configure Environment Variables</h3>
<p>Set the following variables:</p>
<pre><code class="language-plaintext">EVOLUTION_API_URL=http://evolution-api:8080
EVOLUTION_API_KEY=YOUR_AUTHENTICATION_API_KEY
INSTANCE_NAME=your_instance_name
</code></pre>
<p>Note: You might notice two different names here – AUTHENTICATION_API_KEY (used in EasyPanel) and EVOLUTION_API_KEY (used in your bot code). They are the same key. Just copy the value from EasyPanel and paste it into both places.</p>
<h2 id="heading-step-10-connect-the-webhook-telling-evolution-api-where-to-send-messages">Step 10: Connect the Webhook – Telling Evolution API Where to Send Messages</h2>
<p>At this point, you have two separate things running:</p>
<ol>
<li><p><strong>Evolution API</strong>: the service that connects to WhatsApp and handles messages</p>
</li>
<li><p><strong>Your app (fastapi bot)</strong>: the chatbot brain you deployed in the previous steps</p>
</li>
</ol>
<p>Right now, these two don't know each other exists. They're like two people in different rooms with no way to pass notes between them. A <strong>webhook</strong> fixes that.</p>
<h3 id="heading-so-what-exactly-is-a-webhook">So what exactly is a webhook?</h3>
<p>A webhook is simply a URL (a web address) that you hand to one service so it can automatically notify another service when something happens.</p>
<p>You're going to tell Evolution API <em>"whenever a WhatsApp message arrives, forward it to this address."</em> Your app will be sitting at that address, waiting to receive it, read it, and send a reply.</p>
<p>Think of it like a forwarding address at the post office. When mail (a WhatsApp message) arrives, it gets automatically redirected to your app's door.</p>
<h3 id="heading-lets-set-it-up">Let's set it up</h3>
<h4 id="heading-1-open-your-evolution-api-dashboard">1. Open your Evolution API dashboard.</h4>
<p>You should already have this open from earlier steps. In the left sidebar, click on <strong>Events</strong>, then click on <strong>Webhook</strong>. This is where you control how Evolution API sends data to your app.</p>
<h4 id="heading-2-turn-the-webhook-on">2. Turn the webhook on.</h4>
<p>At the top of the page, you'll see a toggle next to the word <strong>"Enabled"</strong>. Click it so it turns green. This tells Evolution API that you want to start using a webhook.</p>
<h4 id="heading-3-enter-your-apps-webhook-url">3. Enter your app's webhook URL.</h4>
<p>In the <strong>URL</strong> field, type your app's address with <code>/webhook</code> added to the end, like this:</p>
<pre><code class="language-plaintext">https://your-domain.easypanel.host/webhook
</code></pre>
<p>Replace <code>your-domain</code> with the actual domain name you set up when you deployed your app. The <code>/webhook</code> part at the end is important: it's a specific page your app has set up just for receiving these messages. Without it, Evolution API would be knocking on the wrong door.</p>
<h4 id="heading-4-leave-webhook-by-events-and-webhook-base64-turned-off-for-now">4. Leave "Webhook by Events" and "Webhook Base64" turned off for now.</h4>
<p>These are advanced options you won't need for a basic chatbot.</p>
<h4 id="heading-5-scroll-down-to-the-events-section-and-enable-these-two-events">5. Scroll down to the Events section and enable these two events:</h4>
<ul>
<li><p><strong>MESSAGES_UPSERT</strong>: This triggers every time someone sends your WhatsApp number a message. Without this, your app would never know a message arrived.</p>
</li>
<li><p><strong>SEND_MESSAGE</strong>: This triggers when a message is sent <em>out</em>. It helps your app confirm that replies are going through correctly.</p>
</li>
</ul>
<p>You can leave all the other events (like <code>APPLICATION_STARTUP</code>) turned off. They handle things like group chats and contact updates, which aren't needed for what we're building.</p>
<p><strong>6. Click Save.</strong></p>
<h3 id="heading-quick-recap-of-what-you-just-did">Quick recap of what you just did</h3>
<p>You created a direct line between Evolution API and your app. Now, the moment someone messages your WhatsApp number, Evolution API will instantly pass that message along to your app. Your app reads it, figures out a response, and sends one back all automatically.</p>
<p>This is the step that brings your chatbot to life. Without it, nothing would happen when someone sent you a message. With it, the whole system clicks into place.</p>
<h2 id="heading-step-11-final-test">Step 11: Final Test</h2>
<p>Send a message from a different WhatsApp number (not the connected one).</p>
<p>Send:</p>
<pre><code class="language-plaintext">Hi
</code></pre>
<p>If everything is configured correctly, your bot should reply:</p>
<pre><code class="language-plaintext">👋 Hello! Bot is working.
</code></pre>
<p>Congratulations! Your WhatsApp bot is now live.</p>
<h2 id="heading-production-considerations">Production Considerations</h2>
<p>For real-world deployments, consider:</p>
<ul>
<li><p>Restricting firewall rules instead of allowing 0.0.0.0/0</p>
</li>
<li><p>Using HTTPS with a custom domain</p>
</li>
<li><p>Securing API keys with a secret manager</p>
</li>
<li><p>Monitoring logs and container health</p>
</li>
<li><p>Setting up automatic backups</p>
</li>
</ul>
<p>This tutorial demonstrates the core working system, but these improvements will make your deployment more secure and scalable.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>You now have a fully self-hosted WhatsApp bot running on a cloud VM using FastAPI, Evolution API, Docker, EasyPanel, and GCP.</p>
<p>This setup gives you:</p>
<ul>
<li><p>Full control over infrastructure</p>
</li>
<li><p>No dependency on expensive SaaS platforms</p>
</li>
<li><p>Production-ready container deployment</p>
</li>
<li><p>Scalable architecture</p>
</li>
</ul>
<p>From here, you can extend your bot with:</p>
<p>AI integrations : connect your bot to ChatGPT or Gemini or Claude so it can answer questions intelligently instead of just sending fixed replies.</p>
<p>Database storage: save incoming messages, user details, or conversation history to a database like PostgreSQL or MongoDB.</p>
<p>Custom automation workflows trigger actions based on keywords, like sending a PDF when someone types "menu" or booking an appointment when they type "schedule".</p>
<p>CRM integrations :connect your bot to tools like HubSpot or Notion to automatically log leads and customer conversations.Building your own infrastructure is one of the best ways to deeply understand how modern backend systems work together.</p>
<p>Happy building!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build an AI-Powered RAG Chatbot with Amazon Lex, Bedrock, and S3 ]]>
                </title>
                <description>
                    <![CDATA[ Chatbots are widely adopted among software companies, especially those that interact heavily with customers. It is typically used for tasks such as customer support, answering questions, and providing information on websites, apps, and messaging plat... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-an-ai-powered-rag-chatbot/</link>
                <guid isPermaLink="false">6930baf2becf9d70ca80fcd0</guid>
                
                    <category>
                        <![CDATA[ chatbot ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chisom Uma ]]>
                </dc:creator>
                <pubDate>Wed, 03 Dec 2025 22:34:26 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1764801036311/e1bb9ed8-f64e-433f-916f-fd3079aac4d3.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Chatbots are widely adopted among software companies, especially those that interact heavily with customers. It is typically used for tasks such as customer support, answering questions, and providing information on websites, apps, and messaging platforms.</p>
<p>These days, as expected, some chatbots are AI-powered and can generate answers to queries through Retrieval-Augmented Generation (RAG). I have been curious about how this works, built it out myself, and now, we’ll look at how to build an AI-powered RAG chatbot.</p>
<p>For this tutorial, you’ll build a RAG chatbot that answers queries about travel policies to Mars. The chatbot retrieves its answers from our own data source (travel policy documents) stored in an S3 bucket. The document serves as our internal data source for the chatbot to reference when generating prompts.</p>
<p>Instead of scripted responses from pre-trained data, the chatbot will pull contextual answers directly from the knowledge base.</p>
<p>Let's get started :)</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-retrieval-augmented-generation-rag">What is Retrieval-Augmented Generation (RAG)?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-amazon-bedrock">What is Amazon Bedrock?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-getting-started-access-models-on-bedrock">Getting Started: Access models on Bedrock</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-1-upload-travel-policy-documents-to-the-s3-bucket">Step 1: Upload Travel Policy Documents to the S3 Bucket</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-2-create-a-knowledge-base-in-amazon-bedrock">Step 2: Create a Knowledge base in Amazon Bedrock</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-3-create-an-amazon-lex-chatbot">Step 3: Create an Amazon Lex Chatbot</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-4-add-a-welcome-intent-to-your-chatbot">Step 4: Add a Welcome Intent to Your Chatbot</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-5-build-the-chatbot">Step 5: Build the Chatbot</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-6-adding-amazon-qnaintent">Step 6: Adding Amazon QnAIntent</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li><p>An AWS account, logged in as an IAM user with admin privileges</p>
</li>
<li><p>Access to Amazon Titan Embeddings G1 - text model on Amazon Bedrock</p>
</li>
<li><p>Access to Anthropic Claude 3.5 Sonnet on Amazon Bedrock.</p>
</li>
<li><p>Access to travel policy documents. You can download these from Google Drive <a target="_blank" href="https://drive.google.com/file/d/1kyewU4eCFnaYS3wQ7Fyv22G3ycthbfJb/view">here</a>.</p>
</li>
<li><p>Experience using the AWS console.</p>
</li>
<li><p>No coding required.</p>
</li>
</ul>
<h2 id="heading-what-is-retrieval-augmented-generation-rag">What is Retrieval-Augmented Generation (RAG)?</h2>
<p>Large Language Models (LLMs) like GPT-4 and Claude are basically everywhere. They get some things amazingly right and others very interestingly wrong, like hallucinations, where the model generates factually incorrect or fabricated information. This brings us to the idea of RAG.</p>
<p><em>Marina Danilevsky</em>, <em>Senior Research Scientist at IBM</em>, in a <a target="_blank" href="https://youtu.be/T-D1OfcDW1M?si=YFYcEeulZpXf9AXN">lecture</a>, referred to RAG as a “framework” for helping LLMs be more accurate and up-to-date.</p>
<p>Before going into the full scope of RAG, let’s talk briefly about the “generation” part. Generation, in the context of RAG, refers to LLMs that generate texts in response to a user query, referred to as a prompt.</p>
<p>These LLMs can sometimes give incorrect answers, due to limited context or outdated information. Especially because they only fetch information from pre-trained data. Imagine you're asked how many Grammy Awards your favorite artist has, and you give an answer you read in a magazine four years ago. You might be correct, but there are two problems with this answer: first, you didn't cite a source, and second, it's outdated.</p>
<p>This is the problem LLMs have traditionally had. The answers were outdated, and no credible sources were cited.</p>
<p>Now, imagine if you had looked up the answer first, from a reputable source on Google. Your answer would be more accurate and factual, and if there was ever a doubt from the person who asked the question, you could easily share the link to the reputable source on Google, and there would be no further doubts or questions.</p>
<p>What does this have to do with LLMs and RAG? Well, now, instead of the LLM only getting answers from its pre-trained data, risking providing outdated answers, when RAG gets involved, it retrieves answers to queries directly from a content store, which could comprise external sources, such as the internet, or internal sources, such as documents (which will be used in this tutorial). This way, its generated answers are more accurate.</p>
<p>RAG helps the LLM stay up to date by further retrieving information from other sources rather than solely from its pre-trained data.</p>
<h2 id="heading-what-is-amazon-bedrock">What is Amazon Bedrock?</h2>
<p><a target="_blank" href="https://aws.amazon.com/bedrock/?trk=68c792bf-53f8-44a0-a8eb-87bc8e0048bf&amp;sc_channel=ps&amp;ef_id=CjwKCAiAraXJBhBJEiwAjz7MZalEEwHhrurF7NUoWofbXeTPsMNnKXsegyAKvkDEfBF2f7Jd4xxwuhoCWW8QAvD_BwE:G:s&amp;s_kwcid=AL!4422!3!692062173758!e!!g!!amazon%20bedrock!21054971963!158684190945&amp;gad_campaignid=21054971963&amp;gbraid=0AAAAADjHtp__vpwZM9pm6Gjqc9UY3wYEa&amp;gclid=CjwKCAiAraXJBhBJEiwAjz7MZalEEwHhrurF7NUoWofbXeTPsMNnKXsegyAKvkDEfBF2f7Jd4xxwuhoCWW8QAvD_BwE">Amazon Bedrock</a> is AWS's managed service that gives you access to foundation models, essentially the core AI engines that power generative AI applications. The beauty of Bedrock is that it handles all the heavy lifting for you. No need to provision GPUs, set up model pipelines, or deal with infrastructure headaches.</p>
<p>It's a single platform where you can experiment with, customize, and deploy top-tier AI models from providers like <em>Anthropic</em>, <em>Stability A</em>I, and Amazon's own <em>Titan</em> models (used in this tutorial).</p>
<p>Here's a practical example: let’s say you're building a customer support chatbot. With Bedrock, you simply pick a language model that fits your needs, fine-tune it for your specific use case, and integrate it into your app, all without touching server configuration or infrastructure code.</p>
<h2 id="heading-getting-started-access-models-on-bedrock">Getting Started: Access models on Bedrock</h2>
<p>To get access to models on AWS via Bedrock:</p>
<ul>
<li><p>Log in to your AWS IAM account with root privileges.</p>
</li>
<li><p>Navigate to <strong>Amazon Bedrock &gt; Model catalog.</strong></p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764493545469/839771fb-c3aa-47d4-b1b9-60cd67ba26c5.png" alt="Image of Amazon bedrock model catalog page" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<ul>
<li>Locate the “Titan Embeddings G1 - Text” model and “Claude 3.5 Sonnet” models.</li>
</ul>
<p>When you click these models, you are directed to a page with more details. You don’t need to do anything on this page. We will be using these models later in this tutorial. In the following sections, we’ll walk through the steps to build the chatbot.</p>
<h2 id="heading-step-1-upload-travel-policy-documents-to-the-s3-bucket">Step 1: Upload Travel Policy Documents to the S3 Bucket</h2>
<p>To upload documents, navigate to the Amazon S3 page in your AWS console, then create a bucket. For more details on creating a bucket, refer to the <a target="_blank" href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/create-bucket-overview.html">AWS documentation</a>. Next, upload the downloaded document to the S3 bucket.</p>
<p>Note that the document is zipped; you will need to unzip it before uploading.</p>
<h2 id="heading-step-2-create-a-knowledge-base-in-amazon-bedrock">Step 2: Create a Knowledge base in Amazon Bedrock</h2>
<p>Now that we have created our S3 buckets and uploaded our documents, we can’t just hook up our chatbot built with Lex directly to the S3 buckets. S3 isn’t really “smart” from an AI perspective. To get the AI capabilities needed to make this work, we need Amazon Bedrock.</p>
<p>First, we need to create a knowledge base in Amazon Bedrock.</p>
<p>To get started, head back to the Bedrock page opened up earlier and navigate to <strong>Build</strong> &gt; <strong>Knowledge Bases</strong>. Click <strong>Create</strong>. From the dropdown, choose “Knowledge base with vector store.” Leave IAM permissions as “Create and use a new service role”. This is what allows Bedrock to access other services. Choose “Amazon S3” as the data source type. Click <strong>Next</strong>.</p>
<p>Next, click <strong>Browse S3</strong> and select the created bucket with the uploaded documents. Click <strong>Next</strong>. On the next page, click <strong>Select model</strong> to choose an embedding model. Select the “Titan Embeddings G1 - Text”, then select “Amazon OpenSearch Serverless” and click <strong>Apply</strong>.</p>
<p>Leave everything else the same and click <strong>Next</strong>. On the next page, click <strong>Create Knowledge Base</strong>. Note that this takes some time (a few minutes), so you need to be patient with this step. Once your knowledge base is created, you’ll be taken to a new page with a message like one in the image below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764493716382/fd92d1d9-51ef-470f-bae9-b1cb636a260e.png" alt="image of successful Bedrock knowledge creation " class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>The second message tells you that you need to sync the knowledge base with data sources. To do this, scroll down to the <em>Data source</em> section, select the data source, then click <strong>Sync</strong>. Wait a few seconds and everything syncs.</p>
<p><strong>Note:</strong> If you have more data than we have in this tutorial (just four PDFs), syncing may take longer.</p>
<p>Now, we have our Bedrock knowledge base set up. The knowledge base connects to the S3 bucket containing the travel documents.</p>
<p>It's now time to create the chatbot. For this, we’ll use <a target="_blank" href="https://aws.amazon.com/lex/">Amazon Lex</a>.</p>
<h2 id="heading-step-3-create-an-amazon-lex-chatbot">Step 3: Create an Amazon Lex Chatbot</h2>
<p>In your AWS console, navigate to Amazon Lex, then click <strong>Create bot</strong>. Select <strong>Create a blank bot</strong> under the <em>Traditional</em> creation method. For the bot name, you can call it “Mars travel bot” or any name you prefer.</p>
<p>Under the “<em>IAM permissions</em>” section, select <strong>Create a role with basic Amazon Lex permissions</strong>. Under the “<em>Children’s Online Privacy Protection Act (COPPA)</em>” section, select <strong>No,</strong> since our bot isn't subject to COPPA, and click <strong>Next</strong>.</p>
<p>On the next page, enter a short description in the <em>Description</em> text field. Select your preferred voice interaction option available for text-to-speech. This is the voice your users will hear when they use the chatbot.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764493888022/29ad9758-c0b7-42f4-8308-8255137c4649.png" alt="Image of Amazon Lex bot voices" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>The cool thing about Lex is that you can play a voice sample for each voice. This can help you make the best decision for your business. Next, click <strong>Done</strong>.</p>
<h2 id="heading-step-4-add-a-welcome-intent-to-your-chatbot">Step 4: Add a Welcome Intent to Your Chatbot</h2>
<p>After hitting the <strong>Done</strong> button, you should see a page for creating an intent next. An intent is basically an action that fulfils a user's request.</p>
<p>Let's start with creating a welcome intent. To get started, change Intent name to “WelcomeIntent”. Then scroll down to the “<em>Sample utterances</em>” section and add utterances. These are example texts that you expect a user to type or speak when they start using your chatbot. So, if the user says “Hi” the chatbot responds with a welcome message. For this tutorial, I added the following expected utterances:</p>
<ul>
<li><p>“Hi”</p>
</li>
<li><p>“Hey”</p>
</li>
<li><p>“hello”</p>
</li>
</ul>
<p>You can add as many as you want.</p>
<p>In the “<em>Initial response</em>” section, you can provide a response to the user's utterance. Under the <strong>Message group</strong> dropdown, you can type in something like “Hi! welcome! How can I help you today?” Next, click the <em>Advanced options</em> button. This reveals a dialog box. Under <em>Set values</em>, select the “Wait for users input” option. You can select other options, but for this tutorial, we are going with this. Click <strong>Update options</strong>.</p>
<p>When you navigate back to the <em>Intents</em> page, you’ll notice a “Fallbackintent” intent automatically generated for you. This intent is supposed to be invoked when a user launches your bot with an utterance that differs from the one created for the welcome intent.</p>
<h2 id="heading-step-5-build-the-chatbot">Step 5: Build the Chatbot</h2>
<p>In the previous step, we built an intent for the bot. Now it's time to build the actual chatbot that bundles up all of this configuration into something usable.</p>
<p>To get started, click <strong>Build</strong> at the top-right side of your screen.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764494047899/cac4a609-f551-4b3e-9714-1330da3e7908.png" alt="Image of building bot" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Once the building is completed, you’ll get a message at the top of the page. Now, it’s time to test the bot. Next, click <strong>Test</strong> at the top-right side of your screen.</p>
<p>You get a pre-built chatbot for testing your implementation. Enter a text or utterance, in this case, for example, “Hi”, and you get an initial response. Remember, the utterance and initial response were set in the previous section.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764494109571/391fbcc9-b3b0-498c-85f9-43e28d12b58d.png" alt="Image of interaction with bot" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>When you click on Inspect. You’ll see the current intent. In this case, the welcomeIntent.</p>
<p>At this point, we haven’t fully integrated the AI capabilities required to get answers about travel policies to Mars.</p>
<h2 id="heading-step-6-adding-amazon-qnaintent">Step 6: Adding Amazon QnAIntent</h2>
<p>The Amazon QnAIntent introduces GenAI capabilities to our bot. It is a built-in intent that uses Generative AI to fulfill Frequently Asked Questions (FAQ) requests by querying the authorized knowledge content.</p>
<p>To get started, navigate to <strong>Add intent &gt; Use built-in intent</strong> on the Intents page. Select the QnAIntent option, as shown in the image below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764494196339/d28020cb-c3a2-4595-aae0-f02463d422a7.png" alt="Image of built-in intent" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Give it a name of your choice. Click <strong>Add</strong>. You’ll be directed to the intent page. In the “<em>QnA configuration”</em> section, select <strong>Claude3.5 Sonnet</strong> as the desired model.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764494285444/9d49daed-02ed-44a6-a7e6-763611f3a214.png" alt="Image of model and knowledge base config in Lex" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>For the ID, since we had already created a knowledge base earlier, navigate back to <strong>Amazon Bedrock</strong> &gt; <strong>Knowledge Bases</strong> and copy your <em>Knowledge Base ID</em> and paste it into the “Knowledge base for Amazon Bedrock Id” field.  Click <strong>Save intent</strong>. Before testing your changes, click <strong>Build</strong> to build the bot again.</p>
<p>Now, let’s run a little test with the chatbot. I will be prompting it about items I can expense for my trip.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764494425584/65ec0a99-e438-44d3-ad62-965d82200142.png" alt="Image of AI implementation and interaction with Chabot, retrieving answers to queries from S3 via Bedrock knowledgebase" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>The image above shows me having a conversation with the chatbot. I sent an utterance for the welcome intent, and it responded with a welcome message. When I asked the chatbot about what items I can expense for the trip, it pulled the information from the Bedrock knowledge base, which is connected to the S3 bucket housing the travel policy documents.</p>
<p>Try experimenting with other questions like “How much does my trip cost?” or “Can I bring my pets?”</p>
<p>Want to add a proper web UI to your bot? Follow the step-by-step instructions in this <a target="_blank" href="https://github.com/aws-samples/aws-lex-web-ui">GitHub repository</a>.</p>
<p>FYI - you should delete resources such as your knowledge base, S3 bucket, and vector store (navigate to <strong>Amazon OpenSearch Service</strong> &gt; <strong>Serverless</strong> &gt; <strong>Dashboard</strong> and delete the knowledge base vector collection) to avoid incurring any unwanted charges from AWS.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>You've just built an AI-powered chatbot that pulls answers from your own data sources. No more generic responses or outdated information. By combining Amazon Lex, Bedrock, S3, and RAG, you've created a system that actually understands your documentation/knowledge base and delivers accurate, contextual answers.</p>
<p>The real power here isn't just in the technology stack, it's in what you can do with it. Scale this approach to handle customer support queries, internal HR questions, product documentation, or any scenario where you need instant, accurate responses from your own knowledge base.</p>
<p>This is just the beginning. Experiment with different foundation models in Bedrock, expand your knowledge base with more documents, or refine your intents to handle more complex conversations. The infrastructure is built, now it's time to customize it for your specific use case.</p>
<p>If you found this tutorial helpful, consider sharing it with your team or fellow developers.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Conversational AI Chatbot with Stream Chat and React ]]>
                </title>
                <description>
                    <![CDATA[ Modern chat applications are increasingly incorporating voice input capabilities because they offer a more engaging and versatile user experience. This also improves accessibility, allowing users with different needs to interact more comfortably with... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-a-conversational-ai-chatbot-with-stream-chat-and-react/</link>
                <guid isPermaLink="false">6851d017416010d301ffa732</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ chatbot ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Timothy Olanrewaju ]]>
                </dc:creator>
                <pubDate>Tue, 17 Jun 2025 20:29:11 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1750192110424/b23653af-c2de-4631-973f-dcac3c0bdb41.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Modern chat applications are increasingly incorporating voice input capabilities because they offer a more engaging and versatile user experience. This also improves accessibility, allowing users with different needs to interact more comfortably with such applications. </p>
<p>In this tutorial, I’ll guide you through the process of creating a conversational AI application that integrates real-time chat functionality with voice recognition. By leveraging Stream Chat for robust messaging and the Web Speech API for speech to text conversion, you’ll build a multi-faceted chat application that supports both voice and text interaction.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-sneak-peek">Sneak Peek</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-core-technologies">Core Technologies</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-backend-implementation-guide">Backend Implementation Guide</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-project-setup">Project Setup</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-frontend-implementation-guide">Frontend Implementation Guide</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-project-setup-1">Project Setup</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-understanding-the-app-component">Understanding the App Component</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-adding-ai-to-the-channel">Adding AI to the Channel</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-configuring-the-channelheader">Configuring the ChannelHeader</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-adding-an-ai-state-indicator">Adding an AI State Indicator</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-building-the-speech-to-text-functionality">Building the Speech to Text Functionality</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-complete-process-flow">Complete Process Flow</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before we begin, ensure you have the following:</p>
<ul>
<li><p>A Stream account with an API key and secret (Read on how to get them <a target="_blank" href="https://getstream.io/blog/stream-getting-started-guide/">here</a>)</p>
</li>
<li><p>Access to an LLM API (like OpenAI, Anthropic).</p>
</li>
<li><p>Node.js and npm/yarn installed.</p>
</li>
<li><p>Basic knowledge of React and TypeScript.</p>
</li>
<li><p>Modern browser with WebSpeech API support (like Chrome, Edge)</p>
</li>
</ul>
<h2 id="heading-sneak-peek">Sneak Peek</h2>
<p>Let’s take a quick look at the app we’ll be building in this tutorial. This way, you get a feel for what it does before we jump into the details.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749925010635/5228ae93-ff56-4b0f-8ea8-c7a160973191.gif" alt="5228ae93-ff56-4b0f-8ea8-c7a160973191" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>If you’re now excited, let’s get straight into it!</p>
<h2 id="heading-core-technologies">Core Technologies</h2>
<p>This application is powered by three main players: Stream Chat, the Web Speech API, and a Node.js + Express backend.</p>
<p><a target="_blank" href="https://getstream.io/">Stream Chat</a> is a platform that helps you easily build and integrate rich, real-time chat and messaging experiences into your applications. It offers a variety of SDKs (Software Development Kits) for different platforms (like Android, iOS, React) and pre-built UI components to streamline development. Its robustness and engaging chat functionality make it a great choice for this app – we don’t need to build anything from scratch.</p>
<p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API">Web Speech API</a> is a browser standard that allows you to integrate voice input and output into your apps, enabling features like speech recognition (converting spoken speech to text) and speech synthesis (converting text to speech). We’ll use the speech recognition feature in this project.</p>
<p>The Node.js + Express backend manages correct agent instantiation and processes the conversational responses generated by our LLM API.</p>
<h2 id="heading-backend-implementation-guide">Backend Implementation Guide</h2>
<p>Let’s begin with our backend, the engine room – where user input is routed to the appropriate AI model, and a processed response is returned. Our backend supports multiple AI models, specifically OpenAI and Anthropic.</p>
<h3 id="heading-project-setup">Project Setup</h3>
<ol>
<li><p>Create a folder, call it ‘<strong>My-Chat-Application</strong>’.</p>
</li>
<li><p>Clone this <a target="_blank" href="https://github.com/GetStream/ai-assistant-nodejs.git">Github repository</a></p>
</li>
<li><p>After cloning, rename the folder to ‘<strong>backend</strong>’</p>
</li>
<li><p>Open the <code>.env.example</code> file and provide the necessary keys (you’ll need to provide either the OpenAI or Anthropic key – the Open Weather key is optional).</p>
</li>
<li><p>Rename the <code>env.example</code>file to <code>.env</code></p>
</li>
<li><p>Install dependencies by running this command:</p>
<pre><code class="lang-javascript"> npm install
</code></pre>
</li>
<li><p>Run the project by entering this command:</p>
<pre><code class="lang-javascript"> npm start
</code></pre>
<p> Your backend should be running smoothly on <code>localhost:3000</code>.</p>
</li>
</ol>
<h2 id="heading-frontend-implementation-guide">Frontend Implementation Guide</h2>
<p>This section explores two broad, interrelated components: the chat structure and speech recognition.</p>
<h3 id="heading-project-setup-1">Project Setup</h3>
<p>We will be creating and setting up our React project with the Stream Chat React SDK. We'll use Vite with the TypeScript template. To do that, navigate to your <strong>My-Chat-Application</strong> folder, open your terminal and enter this command:</p>
<pre><code class="lang-javascript">npm create vite frontend -- --template react-ts
cd chat-example
npm i stream-chat stream-chat-react
</code></pre>
<p>With our frontend project set up, we can now run the app:</p>
<pre><code class="lang-javascript">npm run dev
</code></pre>
<h3 id="heading-understanding-the-app-component">Understanding the App Component</h3>
<p>The main focus here is to initialize a chat client, connect a user, create a channel, and render the chat interface. We’ll go through all these processes step by step to help you understand them better:</p>
<h4 id="heading-define-constants">Define Constants</h4>
<p>First, we need to provide some important credentials that we need for user creation and chat client setup. You can find these credentials on your Stream <a target="_blank" href="https://dashboard.getstream.io/">dashboard</a>.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> apiKey = <span class="hljs-string">"xxxxxxxxxxxxx"</span>;
<span class="hljs-keyword">const</span> userId = <span class="hljs-string">"111111111"</span>;
<span class="hljs-keyword">const</span> userName = <span class="hljs-string">"John Doe"</span>;
<span class="hljs-keyword">const</span> userToken = <span class="hljs-string">"xxxxxxxxxx.xxxxxxxxxxxx.xx_xxxxxxx-xxxxx_xxxxxxxx"</span>; <span class="hljs-comment">//your stream secret key</span>
</code></pre>
<blockquote>
<p>Note: These are dummy credentials. Make sure to use your own credentials.</p>
</blockquote>
<h4 id="heading-create-a-user">Create a User</h4>
<p>Next, we need to create a user object. We’ll create it using an ID, name and a generated avatar URL:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> user: User = {
  <span class="hljs-attr">id</span>: userId,
  <span class="hljs-attr">name</span>: userName,
  <span class="hljs-attr">image</span>: <span class="hljs-string">`https://getstream.io/random_png/?name=<span class="hljs-subst">${userName}</span>`</span>,
};
</code></pre>
<h4 id="heading-setup-a-client">Setup a Client</h4>
<p>We need to track the state of the active chat channel using the <code>useState</code> hook to ensure seamless real-time messaging in this Stream Chat application. A custom hook called <code>useCreateChatClient</code> initializes the chat client with an API key, user token, and user data:</p>
<pre><code class="lang-javascript">  <span class="hljs-keyword">const</span> [channel, setChannel] = useState&lt;StreamChannel&gt;();
  <span class="hljs-keyword">const</span> client = useCreateChatClient({
    apiKey,
    <span class="hljs-attr">tokenOrProvider</span>: userToken,
    <span class="hljs-attr">userData</span>: user,
  });
</code></pre>
<h4 id="heading-initialize-channel">Initialize Channel</h4>
<p>Now, we initialize a messaging channel to enable real-time communication in the Stream Chat application. When the chat client is ready, the <code>useEffect</code> hook triggers the creation of a messaging channel named <code>my_channel</code>, adding the user as a member. This channel is then stored in the channel state, ensuring that the app is primed for dynamic conversation rendering.</p>
<pre><code class="lang-javascript">  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (!client) <span class="hljs-keyword">return</span>;
    <span class="hljs-keyword">const</span> channel = client.channel(<span class="hljs-string">"messaging"</span>, <span class="hljs-string">"my_channel"</span>, {
      <span class="hljs-attr">members</span>: [userId],
    });

    setChannel(channel);
  }, [client]);
</code></pre>
<h4 id="heading-render-chat-interface">Render Chat Interface</h4>
<p>With all the integral parts of our chat application all set up, we’ll return a JSX to define the chat interface's structure and components:</p>
<pre><code class="lang-javascript"> <span class="hljs-keyword">if</span> (!client) <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>Setting up client &amp; connection...<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>;

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Chat</span> <span class="hljs-attr">client</span>=<span class="hljs-string">{client}</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Channel</span> <span class="hljs-attr">channel</span>=<span class="hljs-string">{channel}</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Window</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">MessageList</span> /&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">MessageInput</span> /&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Window</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Thread</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Channel</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Chat</span>&gt;</span></span>
  );
</code></pre>
<p>In this JSX structure:</p>
<ul>
<li><p>If the client is not ready, it displays a "Setting up client &amp; connection..." message.</p>
</li>
<li><p>Once the client is ready, it renders the chat interface using:</p>
<ul>
<li><p><code>&lt;Chat&gt;</code>: Wraps the Stream Chat context with the initialized client.</p>
</li>
<li><p><code>&lt;Channel&gt;</code>: Sets the active channel.</p>
</li>
<li><p><code>&lt;Window&gt;</code>: Contains the main chat UI components:</p>
<ul>
<li><p><code>&lt;MessageList&gt;</code>: Displays the list of messages.</p>
</li>
<li><p><code>&lt;MessageInput&gt;</code>: Uses a custom <code>CustomMessageInput</code> for sending messages.</p>
</li>
</ul>
</li>
<li><p><code>&lt;Thread&gt;</code>: Renders threaded replies.</p>
</li>
</ul>
</li>
</ul>
<p>With this, we've set up our chat interface and channel, and we have a client ready. Here's what our interface looks like so far:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749901280964/4b788065-27ae-40b4-9882-c74bacef0fc4.png" alt="stream chat interface" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h3 id="heading-adding-ai-to-the-channel">Adding AI to the Channel</h3>
<p>Remember, this chat application is designed to interact with an AI, so we need to be able to both add and remove the AI from the channel. On the UI, we’ll add a button in the channel header to enable users add and remove AI. But we still need to determine whether or not we already have it in the channel to know which option to display.</p>
<p>For that we’ll create a custom hook called <code>useWatchers</code>. It monitors the presence of the AI using a concept called <code>watchers</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { useCallback, useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> { Channel } <span class="hljs-keyword">from</span> <span class="hljs-string">'stream-chat'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> useWatchers = <span class="hljs-function">(<span class="hljs-params">{ channel }: { channel: Channel }</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> [watchers, setWatchers] = useState&lt;string[]&gt;([]);
  <span class="hljs-keyword">const</span> [error, setError] = useState&lt;<span class="hljs-built_in">Error</span> | <span class="hljs-literal">null</span>&gt;(<span class="hljs-literal">null</span>);

  <span class="hljs-keyword">const</span> queryWatchers = useCallback(<span class="hljs-keyword">async</span> () =&gt; {
    setError(<span class="hljs-literal">null</span>);

    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> channel.query({ <span class="hljs-attr">watchers</span>: { <span class="hljs-attr">limit</span>: <span class="hljs-number">5</span>, <span class="hljs-attr">offset</span>: <span class="hljs-number">0</span> } });
      setWatchers(result?.watchers?.map(<span class="hljs-function">(<span class="hljs-params">watcher</span>) =&gt;</span> watcher.id).filter((id): id is string =&gt; id !== <span class="hljs-literal">undefined</span>) || [])
      <span class="hljs-keyword">return</span>;
    } <span class="hljs-keyword">catch</span> (err) {
      setError(err <span class="hljs-keyword">as</span> <span class="hljs-built_in">Error</span>);
    }
  }, [channel]);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    queryWatchers();
  }, [queryWatchers]);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> watchingStartListener = channel.on(<span class="hljs-string">'user.watching.start'</span>, <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> userId = event?.user?.id;
      <span class="hljs-keyword">if</span> (userId &amp;&amp; userId.startsWith(<span class="hljs-string">'ai-bot'</span>)) {
        setWatchers(<span class="hljs-function">(<span class="hljs-params">prevWatchers</span>) =&gt;</span> [
          userId,
          ...(prevWatchers || []).filter(<span class="hljs-function">(<span class="hljs-params">watcherId</span>) =&gt;</span> watcherId !== userId),
        ]);
      }
    });

    <span class="hljs-keyword">const</span> watchingStopListener = channel.on(<span class="hljs-string">'user.watching.stop'</span>, <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> userId = event?.user?.id;
      <span class="hljs-keyword">if</span> (userId &amp;&amp; userId.startsWith(<span class="hljs-string">'ai-bot'</span>)) {
        setWatchers(<span class="hljs-function">(<span class="hljs-params">prevWatchers</span>) =&gt;</span>
          (prevWatchers || []).filter(<span class="hljs-function">(<span class="hljs-params">watcherId</span>) =&gt;</span> watcherId !== userId)
        );
      }
    });

    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
      watchingStartListener.unsubscribe();
      watchingStopListener.unsubscribe();
    };
  }, [channel]);

  <span class="hljs-keyword">return</span> { watchers, error };
};
</code></pre>
<h3 id="heading-configuring-the-channelheader">Configuring the ChannelHeader</h3>
<p>We can now build a new channel header component by utilizing the <code>useChannelStateContext</code> hook to access the channel and initialize the custom <code>useWatchers</code> hook. Using the watchers' data, we define an <code>aiInChannel</code> variable to display relevant text. Based on this variable, we invoke either the <code>start-ai-agent</code> or <code>stop-ai-agent</code> endpoint on the Node.js backend.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { useChannelStateContext } <span class="hljs-keyword">from</span> <span class="hljs-string">'stream-chat-react'</span>;
<span class="hljs-keyword">import</span> { useWatchers } <span class="hljs-keyword">from</span> <span class="hljs-string">'./useWatchers'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ChannelHeader</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> { channel } = useChannelStateContext();
  <span class="hljs-keyword">const</span> { watchers } = useWatchers({ channel });

  <span class="hljs-keyword">const</span> aiInChannel =
    (watchers ?? []).filter(<span class="hljs-function">(<span class="hljs-params">watcher</span>) =&gt;</span> watcher.includes(<span class="hljs-string">'ai-bot'</span>)).length &gt; <span class="hljs-number">0</span>;
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'my-channel-header'</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>{(channel?.data as { name?: string })?.name ?? 'Voice-and-Text AI Chat'}<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{addOrRemoveAgent}</span>&gt;</span>
        {aiInChannel ? 'Remove AI' : 'Add AI'}
      <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">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">addOrRemoveAgent</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">if</span> (!channel) <span class="hljs-keyword">return</span>;
    <span class="hljs-keyword">const</span> endpoint = aiInChannel ? <span class="hljs-string">'stop-ai-agent'</span> : <span class="hljs-string">'start-ai-agent'</span>;
    <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">`http://127.0.0.1:3000/<span class="hljs-subst">${endpoint}</span>`</span>, {
      <span class="hljs-attr">method</span>: <span class="hljs-string">'POST'</span>,
      <span class="hljs-attr">headers</span>: { <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span> },
      <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify({ <span class="hljs-attr">channel_id</span>: channel.id, <span class="hljs-attr">platform</span>: <span class="hljs-string">'openai'</span> }),
    });
  }
}
</code></pre>
<h3 id="heading-adding-an-ai-state-indicator">Adding an AI State Indicator</h3>
<p>AIs take a bit of time to process information, so while the AI is processing, we add an indicator to reflect its status. We create a <code>AIStateIndicator</code> that does that for us:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { AIState } <span class="hljs-keyword">from</span> <span class="hljs-string">'stream-chat'</span>;
<span class="hljs-keyword">import</span> { useAIState, useChannelStateContext } <span class="hljs-keyword">from</span> <span class="hljs-string">'stream-chat-react'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">MyAIStateIndicator</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> { channel } = useChannelStateContext();
  <span class="hljs-keyword">const</span> { aiState } = useAIState(channel);
  <span class="hljs-keyword">const</span> text = textForState(aiState);
  <span class="hljs-keyword">return</span> text &amp;&amp; <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'my-ai-state-indicator'</span>&gt;</span>{text}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>;

  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">textForState</span>(<span class="hljs-params">aiState: AIState</span>): <span class="hljs-title">string</span> </span>{
    <span class="hljs-keyword">switch</span> (aiState) {
      <span class="hljs-keyword">case</span> <span class="hljs-string">'AI_STATE_ERROR'</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-string">'Something went wrong...'</span>;
      <span class="hljs-keyword">case</span> <span class="hljs-string">'AI_STATE_CHECKING_SOURCES'</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-string">'Checking external resources...'</span>;
      <span class="hljs-keyword">case</span> <span class="hljs-string">'AI_STATE_THINKING'</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-string">"I'm currently thinking..."</span>;
      <span class="hljs-keyword">case</span> <span class="hljs-string">'AI_STATE_GENERATING'</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-string">'Generating an answer for you...'</span>;
      <span class="hljs-keyword">default</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-string">''</span>;
    }
  }
}
</code></pre>
<h3 id="heading-building-the-speech-to-text-functionality">Building the Speech to Text Functionality</h3>
<p>Up to this point, we have a functional chat application that sends messages and receives feedback from an AI. Now, we want to enable voice interaction, allowing users to speak to the AI instead of typing manually.</p>
<p>To achieve this, we’ll set up speech-to-text functionality within a <code>CustomMessageInput</code> component. Let’s walk through the entire process, step by step, to understand how to achieve it.</p>
<h4 id="heading-initial-states-configuration">Initial States Configuration</h4>
<p>When the <code>CustomMessageInput</code> component first mounts, it begins by establishing its foundational state structure:</p>
<pre><code class="lang-javascript">  <span class="hljs-keyword">const</span> [isRecording, setIsRecording] = useState&lt;boolean&gt;(<span class="hljs-literal">false</span>);
  <span class="hljs-keyword">const</span> [isRecognitionReady, setIsRecognitionReady] = useState&lt;boolean&gt;(<span class="hljs-literal">false</span>);
  <span class="hljs-keyword">const</span> recognitionRef = useRef&lt;any&gt;(<span class="hljs-literal">null</span>);
  <span class="hljs-keyword">const</span> isManualStopRef = useRef&lt;boolean&gt;(<span class="hljs-literal">false</span>);
  <span class="hljs-keyword">const</span> currentTranscriptRef = useRef&lt;string&gt;(<span class="hljs-string">""</span>);
</code></pre>
<p>This initialization step is crucial because it establishes the component's ability to track multiple concurrent states: whether recording is active, whether the speech API is ready, and various persistence mechanisms for managing the speech recognition lifecycle.</p>
<h4 id="heading-context-integration">Context Integration</h4>
<p>In Stream Chat, the <code>MessageInputContext</code> is established within the <code>MessageInput</code> component. It provides data to the Input UI component and its children. Since we want to use the values stored within the <code>MessageInputContext</code> to build our own custom input UI component, we’ll be calling the <code>useMessageInputContext</code> custom hook:</p>
<pre><code class="lang-javascript">  <span class="hljs-comment">// Access the MessageInput context</span>
  <span class="hljs-keyword">const</span> { handleSubmit, textareaRef } = useMessageInputContext();
</code></pre>
<p>This step ensures that the voice input feature integrates seamlessly with the existing chat infrastructure, sharing the same <code>textarea</code> reference and submission mechanisms that other input methods use.</p>
<h4 id="heading-web-speech-api-detection-and-initialization">Web Speech API Detection and Initialization</h4>
<p>The Web Speech API is not supported by some browsers, which is why we need to check if the browser running this application is compatible. The component's first major process involves detecting and initializing the Web Speech API:</p>
<pre><code class="lang-javascript"> <span class="hljs-keyword">const</span> SpeechRecognition = (<span class="hljs-built_in">window</span> <span class="hljs-keyword">as</span> any).SpeechRecognition||(<span class="hljs-built_in">window</span> <span class="hljs-keyword">as</span> any).webkitSpeechRecognition;
</code></pre>
<p>Once the API is detected, the component configures the speech recognition service with optimal settings.</p>
<h4 id="heading-event-handler-configuration">Event Handler Configuration</h4>
<p>We’ll have two event handlers: the result processing handler and the lifecycle event handler.</p>
<p>The result processing handler processes speech recognition output. It demonstrates a two-phase processing approach where interim results provide immediate feedback while final results are accumulated for accuracy.</p>
<pre><code class="lang-javascript">      recognition.onresult = <span class="hljs-function">(<span class="hljs-params">event: any</span>) =&gt;</span> {
        <span class="hljs-keyword">let</span> finalTranscript = <span class="hljs-string">""</span>;
        <span class="hljs-keyword">let</span> interimTranscript = <span class="hljs-string">""</span>;

        <span class="hljs-comment">// Process all results from the last processed index</span>
        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = event.resultIndex; i &lt; event.results.length; i++) {
          <span class="hljs-keyword">const</span> transcriptSegment = event.results[i][<span class="hljs-number">0</span>].transcript;
          <span class="hljs-keyword">if</span> (event.results[i].isFinal) {
            finalTranscript += transcriptSegment + <span class="hljs-string">" "</span>;
          } <span class="hljs-keyword">else</span> {
            interimTranscript += transcriptSegment;
          }
        }

        <span class="hljs-comment">// Update the current transcript</span>
        <span class="hljs-keyword">if</span> (finalTranscript) {
          currentTranscriptRef.current += finalTranscript;
        }

        <span class="hljs-comment">// Combine stored final transcript with current interim results</span>
        <span class="hljs-keyword">const</span> combinedTranscript = (currentTranscriptRef.current + interimTranscript).trim();

        <span class="hljs-comment">// Update the textarea</span>
        <span class="hljs-keyword">if</span> (combinedTranscript) {
          updateTextareaValue(combinedTranscript);
        }
      };
</code></pre>
<p>The lifecycle event handler ensures that the component responds appropriately to each phase of the speech recognition lifecycle events (<code>onstart</code>, <code>onend</code> and <code>onerror</code>):</p>
<pre><code class="lang-javascript">      recognition.onstart = <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Speech recognition started"</span>);
        setIsRecording(<span class="hljs-literal">true</span>);
        currentTranscriptRef.current = <span class="hljs-string">""</span>; <span class="hljs-comment">// Reset transcript on start</span>
      };

      recognition.onend = <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Speech recognition ended"</span>);
        setIsRecording(<span class="hljs-literal">false</span>);

        <span class="hljs-comment">// If it wasn't manually stopped and we're still supposed to be recording, restart</span>
        <span class="hljs-keyword">if</span> (!isManualStopRef.current &amp;&amp; isRecording) {
          <span class="hljs-keyword">try</span> {
            recognition.start();
          } <span class="hljs-keyword">catch</span> (error) {
            <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error restarting recognition:"</span>, error);
          }
        }

        isManualStopRef.current = <span class="hljs-literal">false</span>;
      };

      recognition.onerror = <span class="hljs-function">(<span class="hljs-params">event: any</span>) =&gt;</span> {
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Speech recognition error:"</span>, event.error);
        setIsRecording(<span class="hljs-literal">false</span>);
        isManualStopRef.current = <span class="hljs-literal">false</span>;

        <span class="hljs-keyword">switch</span> (event.error) {
          <span class="hljs-keyword">case</span> <span class="hljs-string">"no-speech"</span>:
            <span class="hljs-built_in">console</span>.warn(<span class="hljs-string">"No speech detected"</span>);
            <span class="hljs-comment">// Don't show alert for no-speech, just log it</span>
            <span class="hljs-keyword">break</span>;
          <span class="hljs-keyword">case</span> <span class="hljs-string">"not-allowed"</span>:
            alert(
              <span class="hljs-string">"Microphone access denied. Please allow microphone permissions."</span>,
            );
            <span class="hljs-keyword">break</span>;
          <span class="hljs-keyword">case</span> <span class="hljs-string">"network"</span>:
            alert(<span class="hljs-string">"Network error occurred. Please check your connection."</span>);
            <span class="hljs-keyword">break</span>;
          <span class="hljs-keyword">case</span> <span class="hljs-string">"aborted"</span>:
            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Speech recognition aborted"</span>);
            <span class="hljs-keyword">break</span>;
          <span class="hljs-keyword">default</span>:
            <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Speech recognition error:"</span>, event.error);
        }
      };

      recognitionRef.current = recognition;
      setIsRecognitionReady(<span class="hljs-literal">true</span>);
      } <span class="hljs-keyword">else</span> {
      <span class="hljs-built_in">console</span>.warn(<span class="hljs-string">"Web Speech API not supported in this browser."</span>);
      setIsRecognitionReady(<span class="hljs-literal">false</span>);
      }
</code></pre>
<h4 id="heading-starting-voice-input">Starting Voice Input</h4>
<p>When a user clicks the microphone button, the component initiates a multi-step process that involves requesting microphone permissions and providing clear error handling if users deny access.</p>
<pre><code class="lang-javascript"> <span class="hljs-keyword">const</span> toggleRecording = <span class="hljs-keyword">async</span> (): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-keyword">void</span>&gt; =&gt; {
    <span class="hljs-keyword">if</span> (!recognitionRef.current) {
      alert(<span class="hljs-string">"Speech recognition not available"</span>);
      <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-keyword">if</span> (isRecording) {
      <span class="hljs-comment">// Stop recording</span>
      isManualStopRef.current = <span class="hljs-literal">true</span>;
      recognitionRef.current.stop();
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-keyword">try</span> {
        <span class="hljs-comment">// Request microphone permission</span>
        <span class="hljs-keyword">await</span> navigator.mediaDevices.getUserMedia({ <span class="hljs-attr">audio</span>: <span class="hljs-literal">true</span> });

        <span class="hljs-comment">// Clear current text and reset transcript before starting</span>
        currentTranscriptRef.current = <span class="hljs-string">""</span>;
        updateTextareaValue(<span class="hljs-string">""</span>);

        <span class="hljs-comment">// Start recognition</span>
        recognitionRef.current.start();
      } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Microphone access error:"</span>, error);
        alert(
          <span class="hljs-string">"Unable to access microphone. Please check permissions and try again."</span>,
        );
      }
    }
  };
</code></pre>
<h4 id="heading-resetting-state-and-start-recognition">Resetting State and Start Recognition</h4>
<p>Before beginning speech recognition, the component resets its internal state. This reset ensures that each new voice input session starts with a clean slate, preventing interference from previous sessions.</p>
<pre><code class="lang-javascript">currentTranscriptRef.current = <span class="hljs-string">""</span>;
updateTextareaValue(<span class="hljs-string">""</span>);
recognitionRef.current.start();
</code></pre>
<h4 id="heading-real-time-speech-processing">Real-Time Speech Processing</h4>
<p>Two things happen simultaneously during this process:</p>
<ol>
<li><p><strong>Continuous Result Processing :</strong> As the user speaks, the component continuously processes incoming speech data through a sophisticated pipeline:</p>
<ul>
<li><p>Each speech segment is classified as either interim (temporary) or final (confirmed).</p>
</li>
<li><p>Final results are accumulated in the persistent transcript reference.</p>
</li>
<li><p>Interim results are combined with accumulated finals for immediate display.</p>
</li>
</ul>
</li>
<li><p><strong>Dynamic Textarea Updates:</strong> The component updates the <code>textarea</code> in real-time using a custom DOM manipulation approach:</p>
<pre><code class="lang-javascript"> <span class="hljs-keyword">const</span> updateTextareaValue = <span class="hljs-function">(<span class="hljs-params">value: string</span>) =&gt;</span> {
   <span class="hljs-keyword">const</span> nativeInputValueSetter = <span class="hljs-built_in">Object</span>.getOwnPropertyDescriptor(
     <span class="hljs-built_in">window</span>.HTMLTextAreaElement.prototype,
     <span class="hljs-string">'value'</span>
   )?.set;

   <span class="hljs-keyword">if</span> (nativeInputValueSetter) {
     nativeInputValueSetter.call(textareaRef.current, value);
     <span class="hljs-keyword">const</span> inputEvent = <span class="hljs-keyword">new</span> Event(<span class="hljs-string">'input'</span>, { <span class="hljs-attr">bubbles</span>: <span class="hljs-literal">true</span> });
     textareaRef.current.dispatchEvent(inputEvent);
   }
 };
</code></pre>
<p> This step involves bypassing React's conventional controlled component behavior to provide immediate feedback, while still maintaining compatibility with React's event system.</p>
</li>
</ol>
<h4 id="heading-user-interface-feedback">User Interface Feedback</h4>
<p>To make voice interactions feel smoother for users, we’ll add some visual feedback features. These include:</p>
<ol>
<li><p><strong>Toggling between mic and stop icons</strong></p>
<p> We show a microphone icon when idle and a stop icon when recording is active. This provides a clear indication of the recording state.</p>
<pre><code class="lang-javascript"> &lt;button
   className={<span class="hljs-string">`voice-input-button <span class="hljs-subst">${isRecording ? <span class="hljs-string">'recording'</span> : <span class="hljs-string">'idle'</span>}</span>`</span>}
   title={isRecording ? <span class="hljs-string">"Stop recording"</span> : <span class="hljs-string">"Start voice input"</span>}
 &gt;
   {isRecording ? (
     <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Square</span> <span class="hljs-attr">size</span>=<span class="hljs-string">{20}</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"voice-icon recording-icon"</span> /&gt;</span></span>
   ) : (
     <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Mic</span> <span class="hljs-attr">size</span>=<span class="hljs-string">{20}</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"voice-icon idle-icon"</span> /&gt;</span></span>
   )}
 &lt;/button&gt;
</code></pre>
</li>
<li><p><strong>Recording notification banner</strong></p>
<p> A notification banner appears at the top of the screen to indicate that voice recording is in progress. This notification ensures users are aware when the microphone is active, addressing privacy and usability concerns.</p>
<pre><code class="lang-javascript"> {isRecording &amp;&amp; (
   <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"recording-notification show"</span>&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"recording-icon"</span>&gt;</span>🎤<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
     Recording... Click stop when finished
   <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
 )}
</code></pre>
</li>
</ol>
<h4 id="heading-message-integration-and-submission">Message Integration and Submission</h4>
<p>The transcribed text integrates seamlessly with the existing chat system through the shared <code>textarea</code> reference and context-provided submission handler:</p>
<pre><code class="lang-javascript">&lt;SendButton sendMessage={handleSubmit} /&gt;
</code></pre>
<p>This integration means that voice-generated messages follow the same submission pathway as typed messages, maintaining consistency with the chat system's behavior. After message submission, the component ensures proper cleanup of its internal state, preparing for the next voice input session.</p>
<h4 id="heading-passing-the-custommessageinput-component">Passing the CustomMessageInput component</h4>
<p>Having built our custom messaging input component, we’ll now pass it to the <code>Input</code> prop of the <code>MessageInput</code> component in our <code>App.tsx</code>:</p>
<pre><code class="lang-javascript">&lt;MessageInput Input={CustomMessageInput} /&gt;
</code></pre>
<h2 id="heading-complete-process-flow"><strong>Complete Process Flow</strong></h2>
<p>Here’s how the application works:</p>
<ol>
<li><p>After the component mounts, you add the AI to the chat by clicking the <strong>Add AI</strong> button.</p>
</li>
<li><p>Click the <strong>mic icon</strong> to start recording.</p>
</li>
<li><p>Your browser will ask for permission to use the microphone.</p>
</li>
<li><p>If you <strong>deny</strong> permission, recording won't begin.</p>
</li>
<li><p>If you <strong>allow</strong> permission, recording and transcription start simultaneously.</p>
</li>
<li><p>Click the <strong>stop (square) icon</strong> to end the recording.</p>
</li>
<li><p>Click the <strong>send button</strong> to submit your message.</p>
</li>
<li><p>The AI processes your input and generates a response.</p>
</li>
</ol>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this tutorial, you’ve learned how to build a powerful conversational chatbot using Stream Chat and React. The application supports both text and voice inputs.</p>
<p>If you want to create your own engaging chat experiences, you can explore Stream <a target="_blank" href="https://getstream.io/chat/">Chat</a> and <a target="_blank" href="https://getstream.io/video/">Video</a> features to take your projects to the next level.</p>
<p>Get the full source code for this project <a target="_blank" href="https://github.com/TimothyOlanrewaju/My-Chat-Application">here</a>. If you enjoyed reading this article, connect with me on <a target="_blank" href="https://www.linkedin.com/in/timothy-olanrewaju750">LinkedIn</a> or follow me on <a target="_blank" href="https://x.com/SmoothTee_DC">X</a> for more programming-related posts and articles.</p>
<p>See you on the next one!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Medical Chatbot with Flutter and Gemini: A Beginner’s Guide ]]>
                </title>
                <description>
                    <![CDATA[ In today's digital age, the demand for accessible and accurate health information is higher than ever. Leveraging the power of artificial intelligence, we can create intelligent chatbots that provide reliable health-related guidance. This beginner's ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-a-medical-chatbot-with-flutter-and-gemini/</link>
                <guid isPermaLink="false">684c61db3b494a20ba49fddb</guid>
                
                    <category>
                        <![CDATA[ Flutter ]]>
                    </category>
                
                    <category>
                        <![CDATA[ flutter-aware ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AISprint  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ chatbot ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Atuoha Anthony ]]>
                </dc:creator>
                <pubDate>Fri, 13 Jun 2025 17:37:31 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1749830721631/4675d1f6-ad64-46a3-86e1-ce8a2c84323f.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In today's digital age, the demand for accessible and accurate health information is higher than ever. Leveraging the power of artificial intelligence, we can create intelligent chatbots that provide reliable health-related guidance.</p>
<p>This beginner's guide will walk you through building a powerful and specialized medical chatbot using Flutter and Google's Gemini API. The chatbot will be able to receive input from various modalities like text, audio, camera, files, and a gallery, and it will be strictly confined to answering health-related questions.</p>
<h3 id="heading-table-of-contents">Table of Contents:</h3>
<ul>
<li><p><a class="post-section-overview" href="#heading-the-power-of-ai-in-healthcare">The Power of AI in Healthcare</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-your-development-environment">How to Set Up Your Development Environment</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-project-structure">Project Structure</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-code-implementation-and-explanation">Code Implementation and Explanation</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-important-considerations-and-future-enhancements">Important Considerations and Future Enhancements</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-screenshots">Screenshots and Completed Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-wrapping-up">Wrapping Up</a></p>
</li>
</ul>
<h2 id="heading-the-power-of-ai-in-healthcare">The Power of AI in Healthcare</h2>
<p>AI-powered chatbots are transforming various industries, and healthcare is no exception. They offer a scalable and efficient way to disseminate information, answer frequently asked questions, and even provide initial assessments. By focusing on health-related queries, our chatbot will act as a specialized assistant, providing concise and accurate information to users.</p>
<h3 id="heading-core-technologies">Core Technologies</h3>
<p>We’ll build our medical chatbot using the following key technologies:</p>
<ul>
<li><p><strong>Flutter:</strong> Google's UI toolkit for building natively compiled applications for mobile, web, and desktop from a single codebase.<strong><sup>1</sup></strong> Its rich set of widgets and expressive UI make it ideal for creating engaging chat interfaces.</p>
</li>
<li><p><strong>Google Gemini API:</strong> Google's most capable and flexible AI model. Gemini is multimodal, meaning it can process and understand different types of information, including text, images, audio, and video. This capability is crucial for our chatbot to handle diverse user inputs.</p>
</li>
<li><p><code>flutter_ai_toolkit</code>: A Flutter package that provides a set of AI chat-related widgets and an abstract LLM provider API, simplifying the integration of AI models into your Flutter app. It offers out-of-the-box support for Gemini.</p>
</li>
<li><p><code>google_generative_ai</code>: The official Dart package for interacting with Google's Generative AI models (Gemini).</p>
</li>
</ul>
<h2 id="heading-how-to-set-up-your-development-environment">How to Set Up Your Development Environment</h2>
<p>Before we dive into the code, make sure you have Flutter installed and configured on your system. If not, follow the <a target="_blank" href="https://flutter.dev/docs/get-started/install">official Flutter installation guide here</a>.</p>
<h3 id="heading-get-your-gemini-api-key">Get Your Gemini API Key</h3>
<p>To interact with the Gemini API, you need an API key. This key authenticates your application and allows it to send requests to the Gemini model.</p>
<p>Here's how to get your Gemini API key:</p>
<ol>
<li><p><strong>Go to Google AI Studio:</strong> Open your web browser and navigate to <a target="_blank" href="https://aistudio.google.com/">https://aistudio.google.com/</a>.</p>
</li>
<li><p><strong>Log in with your Google account:</strong> If you're not already logged in, you'll be prompted to sign in with your Google account.</p>
</li>
<li><p><strong>Click "Get API key in Google AI Studio":</strong> On the Google AI Studio homepage, you'll see a prominent button with this text. Click it.</p>
</li>
<li><p><strong>Review and approve terms of service:</strong> A pop-up will appear asking you to consent to the Google APIs Terms of Service and Gemini API Additional Terms of Service. Read them carefully, check the necessary boxes, and click "Continue."</p>
</li>
<li><p><strong>Create your API key:</strong> You'll now have the option to "Create API key in new project" or "Create API key in existing project." Choose the one that suits your needs. Your API key will be auto-generated.</p>
</li>
<li><p><strong>Copy your API key:</strong> <strong>Crucially, copy this API key immediately and store it securely.</strong> It will not be shown again. <strong>Do NOT hardcode your API key directly into your production code, especially for client-side applications.</strong> For development purposes, we will use it directly in our <code>MedicalChatScreen</code> for simplicity, but for a real-world application, consider using environment variables or a secure backend to manage your API key.</p>
</li>
</ol>
<h3 id="heading-add-dependencies-pubspecyaml">Add Dependencies (<code>pubspec.yaml</code>)</h3>
<p>Open your <code>pubspec.yaml</code> file (located at the root of your Flutter project) and add the following dependencies under <code>dependencies</code>:</p>
<pre><code class="lang-bash">dependencies:
  flutter:
    sdk: flutter
  flutter_ai_toolkit: ^0.6.8
  google_generative_ai: ^0.4.6
</code></pre>
<p>After adding these, run <code>flutter pub get</code> in your terminal to fetch the packages.</p>
<h2 id="heading-project-structure">Project Structure</h2>
<p>Our project will have a simple structure:</p>
<ul>
<li><p><code>lib/main.dart</code>: The entry point of our Flutter application.</p>
</li>
<li><p><code>lib/screens/chat.dart</code>: Contains the main chat interface for our medical chatbot.</p>
</li>
</ul>
<h2 id="heading-code-implementation-and-explanation">Code Implementation and Explanation</h2>
<p>Let's break down the provided code and understand each part.</p>
<h4 id="heading-libmaindart"><code>lib/main.dart</code></h4>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:ai_demo/screens/chat.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;

<span class="hljs-keyword">void</span> main() {
  runApp(<span class="hljs-keyword">const</span> MyApp());
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyApp</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">const</span> MyApp({<span class="hljs-keyword">super</span>.key});

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> MaterialApp(
      title: <span class="hljs-string">'Medical ChatBot'</span>,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: <span class="hljs-keyword">const</span> MedicalChatScreen(),
    );
  }
}
</code></pre>
<p>Here’s what’s going on in this code:</p>
<ul>
<li><p><code>import 'package:ai_demo/screens/chat.dart';</code>: This line imports the <code>chat.dart</code> file from the <code>screens</code> folder. This is where our <code>MedicalChatScreen</code> widget is defined.</p>
</li>
<li><p><code>import 'package:flutter/material.dart';</code>: This imports the fundamental Flutter Material Design widgets, essential for building the UI.</p>
</li>
<li><p><code>void main() { runApp(const MyApp()); }</code>: This is the entry point of every Flutter application. <code>runApp()</code> takes a widget as an argument and makes it the root of the widget tree.</p>
</li>
<li><p><code>class MyApp extends StatelessWidget</code>: <code>MyApp</code> is the root widget of our application. <code>StatelessWidget</code> means its properties don't change over time.</p>
</li>
<li><p><code>Widget build(BuildContext context)</code>: This method is where the UI of the <code>MyApp</code> widget is built.</p>
</li>
<li><p><code>MaterialApp</code>: This widget provides the basic Material Design visual structure for a Flutter app.</p>
<ul>
<li><p><code>title: 'Medical ChatBot'</code>: Sets the title of the application, which might be displayed in the device's task switcher or browser tab.</p>
</li>
<li><p><code>theme: ThemeData(...)</code>: Defines the visual theme of the application.</p>
<ul>
<li><code>colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple)</code>: Generates a color scheme based on a primary "seed" color (<code>Colors.deepPurple</code>), ensuring a consistent and harmonious look across the app.</li>
</ul>
</li>
<li><p><code>home: const MedicalChatScreen()</code>: Sets the initial screen of the application to our <code>MedicalChatScreen</code> widget.</p>
</li>
</ul>
</li>
</ul>
<h4 id="heading-libscreenschatdart"><code>lib/screens/chat.dart</code></h4>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter_ai_toolkit/flutter_ai_toolkit.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:google_generative_ai/google_generative_ai.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MedicalChatScreen</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatefulWidget</span> </span>{
  <span class="hljs-keyword">const</span> MedicalChatScreen({<span class="hljs-keyword">super</span>.key});

  <span class="hljs-meta">@override</span>
  State&lt;MedicalChatScreen&gt; createState() =&gt; _MedicalChatScreenState();
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_MedicalChatScreenState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span>&lt;<span class="hljs-title">MedicalChatScreen</span>&gt; </span>{
  <span class="hljs-built_in">String</span> apiKey = <span class="hljs-string">""</span>; <span class="hljs-comment">// IMPORTANT: Replace with your actual Gemini API Key</span>

  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> initState() {
    <span class="hljs-keyword">super</span>.initState();
    <span class="hljs-comment">// It's a good practice to load the API key from a secure source</span>
    <span class="hljs-comment">// rather than hardcoding it, especially for production apps.</span>
    <span class="hljs-comment">// For this demo, we'll keep it simple.</span>
  }

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.white,
        automaticallyImplyLeading: <span class="hljs-keyword">false</span>,
        title: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">"Medical ChatBot"</span>),
      ),
      body: LlmChatView(
        suggestions: <span class="hljs-keyword">const</span> [
          <span class="hljs-string">"I've been feeling dizzy lately. What now?"</span>,
          <span class="hljs-string">"How do I know if I need to see a doctor?"</span>,
          <span class="hljs-string">"What should I eat to boost my immunity?"</span>
        ],
        style: LlmChatViewStyle(
          backgroundColor: Colors.white,
          chatInputStyle: ChatInputStyle(
            hintText: <span class="hljs-string">"Enter your message"</span>,
            decoration: <span class="hljs-keyword">const</span> BoxDecoration().copyWith(
              borderRadius: BorderRadius.circular(<span class="hljs-number">50</span>),
            ),
          ),
        ),
        provider: GeminiProvider(
          model: GenerativeModel(
            model: <span class="hljs-string">"gemini-2.0-flash"</span>,
            apiKey: apiKey,
            systemInstruction: Content.system(
              <span class="hljs-string">"You are a professional medical health assistant. Only respond to health and medically related questions and make them concise and straight to the point without too much explanation."</span>
                  <span class="hljs-string">"If a question is unrelated to health or medicine, politely inform the user that you can only answer medical-related queries."</span>,
            ),
          ),
        ),
        welcomeMessage:
        <span class="hljs-string">"Hello👋 I’m here to help with your medical questions. Please tell me how I can assist you."</span>
      ),
    );
  }
}
</code></pre>
<p>What’s going on in <code>chat.dart</code>:</p>
<ul>
<li><p><code>import 'package:flutter/material.dart';</code>: Imports Material Design widgets.</p>
</li>
<li><p><code>import 'package:flutter_ai_toolkit/flutter_ai_toolkit.dart';</code>: Imports the <code>flutter_ai_toolkit</code> package, which provides the <code>LlmChatView</code> and <code>GeminiProvider</code>.</p>
</li>
<li><p><code>import 'package:google_generative_ai/google_generative_ai.dart';</code>: Imports the <code>google_generative_ai</code> package, which allows us to interact with the Gemini model.</p>
</li>
<li><p><code>class MedicalChatScreen extends StatefulWidget</code>: Our chat screen is a <code>StatefulWidget</code> because its <code>apiKey</code> and potentially other chat-related states might change.</p>
</li>
<li><p><code>_MedicalChatScreenState createState() =&gt; _MedicalChatScreenState();</code>: Creates the mutable state for this widget.</p>
</li>
<li><p><code>String apiKey = "";</code>: <strong>This is where you need to paste your Gemini API key.</strong> Replace <code>""</code> with the actual key you obtained from Google AI Studio. For example: <code>String apiKey = "YOUR_GEMINI_API_KEY_HERE";</code>.</p>
<ul>
<li><strong>Security note:</strong> As mentioned before, hardcoding API keys is not recommended for production applications. Consider using environment variables, a secrets management service (like Firebase Remote Config or Google Cloud Secret Manager), or a backend server to handle API requests securely.</li>
</ul>
</li>
<li><p><code>initState()</code>: This method is called once when the widget is inserted into the widget tree. It's a good place for initial setup. In this case, it's empty but serves as a placeholder for potential future initialization like loading the API key securely.</p>
</li>
<li><p><code>Scaffold</code>: Implements the basic Material Design visual layout structure.</p>
<ul>
<li><p><code>appBar</code>: Displays a top app bar.</p>
<ul>
<li><p><code>backgroundColor: Colors.white</code>: Sets the background color of the app bar to white.</p>
</li>
<li><p><code>automaticallyImplyLeading: false</code>: Prevents Flutter from automatically adding a back button if this screen is pushed onto a navigation stack.</p>
</li>
<li><p><code>title: const Text("Medical ChatBot")</code>: Sets the title text of the app bar.</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><code>body: LlmChatView(...)</code>: This is the core widget from the <code>flutter_ai_toolkit</code> that provides the chat UI and handles the interaction with the LLM.</p>
<ul>
<li><p><code>suggestions: const [...]</code>: Provides a list of initial suggested prompts to the user when the chat is empty. These prompts guide the user on the types of questions the chatbot can answer.</p>
</li>
<li><p><code>style: LlmChatViewStyle(...)</code>: Customizes the appearance of the chat view.</p>
<ul>
<li><p><code>backgroundColor: Colors.white</code>: Sets the background color of the chat area.</p>
</li>
<li><p><code>chatInputStyle: ChatInputStyle(...)</code>: Styles the text input field.</p>
<ul>
<li><p><code>hintText: "Enter your message"</code>: Placeholder text in the input field.</p>
</li>
<li><p><code>decoration: const BoxDecoration().copyWith(borderRadius: BorderRadius.circular(50))</code>: Styles the input field with rounded corners.</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><code>provider: GeminiProvider(...)</code>: This is where we configure our Gemini model as the AI provider for the <code>LlmChatView</code>.</p>
<ul>
<li><p><code>model: GenerativeModel(...)</code>: Creates an instance of the Gemini model.</p>
<ul>
<li><p><code>model: "gemini-2.0-flash"</code>: Specifies the particular Gemini model to use. "gemini-2.0-flash" is a lightweight and fast model suitable for many applications. Other models like "gemini-pro" are also available, offering different capabilities and costs.</p>
</li>
<li><p><code>apiKey: apiKey</code>: Passes your obtained Gemini API key to the model.</p>
</li>
<li><p><code>systemInstruction: Content.system(...)</code>: <strong>This is crucial for defining the chatbot's persona and limitations.</strong> It's a system message sent to the Gemini model at the beginning of the conversation (and potentially with every turn, depending on the implementation details of <code>flutter_ai_toolkit</code> and <code>google_generative_ai</code>).</p>
<ul>
<li><p><code>"You are a professional medical health assistant. Only respond to health and medical-related questions and make them concise and straight to the point without too much explanation."</code>: This is the primary instruction. It tells Gemini to act as a medical assistant and to be precise in its health-related responses.</p>
</li>
<li><p><code>"If a question is unrelated to health or medicine, politely inform the user that you can only answer medical-related queries."</code>: This instruction ensures that the chatbot stays within its defined scope and doesn't hallucinate or provide irrelevant answers to non-medical questions, which is vital for a specialized health bot.</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><p><code>welcomeMessage: "Hello👋 I’m here to help with your medical questions. Please tell me how I can assist you."</code>: A friendly message displayed to the user when they first open the chat screen, setting the context for the conversation.</p>
</li>
</ul>
</li>
</ul>
<h3 id="heading-how-to-handle-multi-modal-inputs">How to Handle Multi-Modal Inputs</h3>
<p>The <code>flutter_ai_toolkit</code> package, when used with <code>GeminiProvider</code>, intrinsically supports multi-modal inputs. The <code>LlmChatView</code> automatically provides UI elements for:</p>
<ul>
<li><p><strong>Text input:</strong> The standard text field for typing messages.</p>
</li>
<li><p><strong>Audio input:</strong> A microphone icon will typically be present, allowing users to record voice messages that are then transcribed and sent to Gemini.</p>
</li>
<li><p><strong>Camera input:</strong> A camera icon will allow users to take a photo and send it to the chatbot. Gemini can then process the image and provide a response.</p>
</li>
<li><p><strong>File input:</strong> An attachment icon (often a paperclip) will enable users to select files (like documents or images) from their device to send to the chatbot.</p>
</li>
<li><p><strong>Gallery input:</strong> Similar to file input, but specifically for selecting images or videos from the device's photo gallery.</p>
</li>
</ul>
<p>The <code>flutter_ai_toolkit</code> abstracts away the complexities of handling these different input types, converting them into a format that the <code>google_generative_ai</code> package and subsequently the Gemini model can understand and process. For instance, images are sent as <code>ImagePart</code> within the <code>Content</code> object, and audio might be transcribed to text before being sent, or sent as <code>AudioPart</code> if the model supports direct audio input.</p>
<p>The <code>systemInstruction</code> we set for the <code>GenerativeModel</code> is crucial here. While the <code>flutter_ai_toolkit</code> handles the input, the Gemini model's ability to understand various modalities in the context of health questions depends on its training and our clear instructions.</p>
<p>For example, if a user uploads an image of a rash, the system instruction helps guide Gemini to interpret it from a medical perspective (though it's important to remember that an AI chatbot is not a substitute for professional medical diagnosis).</p>
<h3 id="heading-how-to-run-your-chatbot">How to Run Your Chatbot</h3>
<ol>
<li><p><strong>Replace</strong> <code>apiKey</code>: In <code>lib/screens/chat.dart</code>, replace <code>""</code> with your actual Gemini API key.</p>
</li>
<li><p><strong>Run the application:</strong> In your terminal, navigate to your project's root directory and run: <strong>Bash</strong></p>
<pre><code class="lang-bash"> flutter run
</code></pre>
</li>
</ol>
<p>This will launch the application on a connected device or emulator. You should see the "Medical ChatBot" app with the welcome message and suggested prompts. Try typing some health-related questions, and also experiment with the multi-modal input options (microphone, camera, attachment icon) if your device/emulator supports them.</p>
<h2 id="heading-important-considerations-and-future-enhancements">Important Considerations and Future Enhancements</h2>
<ul>
<li><p><strong>API key security:</strong> Just to reiterate the importance of not hardcoding API keys in production. For a deployed app, consider using environment variables, backend services, or Flutter's build configurations to inject the API key securely.</p>
</li>
<li><p><strong>Error handling:</strong> The current code doesn't explicitly show error handling for API calls. In a real application, you'd want to handle network errors, invalid API keys, or rate limits gracefully. The <code>flutter_ai_toolkit</code> and <code>google_generative_ai</code> packages provide mechanisms for this.</p>
</li>
<li><p><strong>User Experience (UX):</strong></p>
<ul>
<li><p><strong>Loading indicators:</strong> Show a loading indicator while the AI is generating a response.</p>
</li>
<li><p><strong>Input validation:</strong> For certain inputs (for example, file types), you might want to add client-side validation.</p>
</li>
<li><p><strong>Clearance/history:</strong> Implement features to clear chat history or save past conversations.</p>
</li>
</ul>
</li>
<li><p><strong>Medical disclaimer:</strong> Crucially, any medical chatbot should include a prominent disclaimer stating that it is not a substitute for professional medical advice, diagnosis, or treatment. It should always advise users to consult with a qualified healthcare professional for any health concerns.</p>
</li>
<li><p><strong>Privacy and data security:</strong> When dealing with health-related information, data privacy is paramount. Ensure your application complies with relevant regulations (for example, HIPAA in the U.S., GDPR in Europe) and that user data is handled securely. The Gemini API has its own data policies you should review.</p>
</li>
<li><p><strong>Advanced system instructions:</strong> For a more sophisticated medical chatbot, you could expand the <code>systemInstruction</code> to include specific medical knowledge domains, preferred response formats (for example, always list bullet points for symptoms), or even direct the AI to ask clarifying questions.</p>
</li>
<li><p><strong>Tool use/function calling:</strong> Gemini supports tool use (function calling), allowing the AI to interact with external services. For a health bot, this could mean:</p>
<ul>
<li><p>Looking up drug information from a database.</p>
</li>
<li><p>Finding nearby clinics or pharmacies.</p>
</li>
<li><p>Accessing up-to-date medical research.</p>
</li>
<li><p>This would require more complex setup with backend functions that the AI can call.</p>
</li>
</ul>
</li>
<li><p><strong>Speech-to-Text (STT) and Text-to-Speech (TTS):</strong> While <code>flutter_ai_toolkit</code> handles audio input, you might want more fine-grained control over STT and TTS services for improved voice interaction.</p>
</li>
<li><p><strong>Image processing and medical imaging:</strong> For truly advanced medical applications, you might integrate specialized image processing libraries for analyzing medical images (for example, X-rays, MRIs), but this is a complex domain requiring expert knowledge and regulatory compliance. Our current setup allows Gemini to interpret images, but it relies on Gemini's general vision capabilities.</p>
</li>
</ul>
<h2 id="heading-screenshots">Screenshots</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749131323671/5768237e-2f4b-4c6b-aae6-23486dc8bb46.png" alt="5768237e-2f4b-4c6b-aae6-23486dc8bb46" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>You can check out the full project here: <a target="_blank" href="https://github.com/Atuoha/ai_medical_assistant">https://github.com/Atuoha/ai_medical_assistant</a></p>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>You've now successfully built a foundational medical chatbot using Flutter and Google Gemini! This application demonstrates how to integrate a powerful AI model with a multi-modal user interface, while also enforcing specific behavioral constraints (health-only questions).</p>
<p>By extending this base with robust error handling, enhanced UX, and potentially advanced AI features like tool use, you can create even more sophisticated and valuable healthcare applications.</p>
<p>Remember to always prioritize user safety and data privacy when developing AI solutions in the medical domain.</p>
<h3 id="heading-flutter-and-dart-packages"><strong>Flutter and Dart Packages:</strong></h3>
<ul>
<li><p><code>flutter_ai_toolkit</code>:</p>
<ul>
<li><p>Pub.dev page: <a target="_blank" href="https://pub.dev/packages/flutter_ai_toolkit">https://pub.dev/packages/flutter_ai_toolkit</a></p>
</li>
<li><p>Flutter Documentation (AI Toolkit): <a target="_blank" href="https://docs.flutter.dev/ai-toolkit">https://docs.flutter.dev/ai-toolkit</a></p>
</li>
</ul>
</li>
<li><p><code>google_generative_ai</code>:</p>
<ul>
<li>Pub.dev page: <a target="_blank" href="https://www.google.com/search?q=https://pub.dev/packages/google_generative_ai">https://pub.dev/packages/google_generative_ai</a></li>
</ul>
</li>
</ul>
<p><strong>Google Gemini API and AI Studio:</strong></p>
<ul>
<li><p>Google AI Studio: <a target="_blank" href="https://aistudio.google.com/">https://aistudio.google.com/</a></p>
</li>
<li><p>Get a Gemini API Key (Google AI for Developers): <a target="_blank" href="https://ai.google.dev/gemini-api/docs/api-key">https://ai.google.dev/gemini-api/docs/api-key</a></p>
</li>
<li><p>Gemini API Documentation (Google AI for Developers): <a target="_blank" href="https://ai.google.dev/api">https://ai.google.dev/api</a> (General API documentation)</p>
</li>
</ul>
<p><strong>Flutter Documentation:</strong></p>
<ul>
<li>Flutter Official Website (Installation Guide): <a target="_blank" href="https://flutter.dev/docs/get-started/install">https://flutter.dev/docs/get-started/install</a></li>
</ul>
<p><strong>General Concepts (for further reading):</strong></p>
<ul>
<li><p><strong>Material Design:</strong> <a target="_blank" href="https://m3.material.io/">https://m3.material.io/</a> (For understanding Flutter's UI principles)</p>
</li>
<li><p><strong>Large Language Models (LLMs):</strong> A broad topic, but understanding the basics of how LLMs work will enhance comprehension of the <code>systemInstruction</code> and model behavior.</p>
</li>
<li><p><strong>Multimodal AI:</strong> Research on multimodal AI provides context for why Gemini can handle various input types (text, image, audio, and so on).</p>
</li>
<li><p><strong>API Key Security Best Practices:</strong> For production applications, it's crucial to understand secure API key management (for example, environment variables, secret management services). A good starting point would be general security best practices for API keys.</p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Add Live Chat to Your Applications with Rocket.chat ]]>
                </title>
                <description>
                    <![CDATA[ The fastest way to gather valuable information about your site’s users is still by talking to them. And what better way to do this than by adding a chat system to your app? For my case, I just wanted to add a chat system to my portfolio website so I ]]>
                </description>
                <link>https://www.freecodecamp.org/news/add-live-chat-to-your-applications-with-rocketchat/</link>
                <guid isPermaLink="false">67f3d275332a812ead444418</guid>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ chatbot ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Spruce Emmanuel ]]>
                </dc:creator>
                <pubDate>Mon, 07 Apr 2025 13:26:13 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1744032217209/1ad8ea61-a8bd-4bea-9bec-152e52db7377.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>The fastest way to gather valuable information about your site’s users is still by talking to them. And what better way to do this than by adding a chat system to your app?</p>
<p>For my case, I just wanted to add a chat system to my portfolio website so I could get valuable info from potential employers and clients. I ended up building something like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743755398731/f7bce275-ed01-4e7f-98b6-0eed955dc428.png" alt="Live chat demo screenshot" width="2742" height="1842" loading="lazy"></p>
<h3 id="heading-table-of-contents"><strong>Table of Contents</strong></h3>
<ol>
<li><p><a class="post-section-overview" href="#why-rocketchat-you-may-ask">Why</a> <a target="_blank" href="http://Rocket.Chat">Rocket.Chat</a><a class="post-section-overview" href="#why-rocketchat-you-may-ask">, you may ask?</a></p>
</li>
<li><p><a class="post-section-overview" href="#prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#getting-started">Getting Started</a></p>
<ul>
<li><p><a class="post-section-overview" href="#step-1-set-up-the-rocketchat-server">Step 1: Set Up the</a> <a target="_blank" href="http://Rocket.Chat">Rocket.Chat</a> <a class="post-section-overview" href="#step-1-set-up-the-rocketchat-server">Server</a></p>
</li>
<li><p><a class="post-section-overview" href="#step-2-configure-the-rocketchat-server">Step 2: Configure the</a> <a target="_blank" href="http://Rocket.Chat">Rocket.Chat</a> <a class="post-section-overview" href="#step-2-configure-the-rocketchat-server">Server</a></p>
</li>
<li><p><a class="post-section-overview" href="#step-3-register-the-visitor">Step 3: Register the Visitor</a></p>
<ul>
<li><p><a class="post-section-overview" href="#how-to-register-the-visitor">How to Register the Visitor</a></p>
</li>
<li><p><a class="post-section-overview" href="#how-to-create-or-retrieve-the-chat-room">How to Create or Retrieve the Chat Room</a></p>
</li>
<li><p><a class="post-section-overview" href="#how-to-retrieve-livechat-configuration">How to Retrieve Livechat Configuration</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#step-4-create-the-connection-to-websocket">Step 4: Create the Connection to WebSocket</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-why-rocketchat-you-may-ask">Why Rocket.Chat, you may ask?</h2>
<p>Rocket.Chat is a great option because:</p>
<ul>
<li><p><strong>Open Source:</strong> It’s free and customizable.</p>
</li>
<li><p><strong>Comprehensive APIs:</strong> Their APIs make integration simple.</p>
</li>
<li><p><strong>Flexible Hosting:</strong> Self-host your own or use their cloud version with a free trial (which we’ll use here).</p>
</li>
</ul>
<h3 id="heading-prerequisites">Prerequisites</h3>
<p>Before you continue, there are a few things you should know and have:</p>
<ul>
<li><p>A running Rocket.Chat server (either self-hosted or on Rocket.Chat Cloud). Here, I'll show you how to set up one with Rocket.Chat Cloud.</p>
</li>
<li><p>A working knowledge of JavaScript fundamentals.</p>
</li>
</ul>
<h2 id="heading-getting-started">Getting Started</h2>
<p>First things first, let's set up a Rocket.Chat server. Again, you can either self host your own or use their cloud version. And don't worry – you don't have to pay anything right now or for this tutorial, as they provide a 30 day free trial.</p>
<h3 id="heading-step-1-set-up-the-rocketchat-server">Step 1: Set Up the Rocket.Chat Server</h3>
<p>Head over to <a target="_blank" href="https://cloud.rocket.chat">https://cloud.rocket.chat</a> and create your free account.</p>
<p>Once you're logged in, click on the <strong>"Change to SaaS trial"</strong> button to launch a cloud-hosted server.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743755542942/1b57e01d-2338-4af7-9a77-9140f65bb1f7.png" alt="Change to SaaS trial button" width="2742" height="1842" loading="lazy"></p>
<p>Next, create a Cloud Workspace by providing your workspace name, URL, and server region.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sdorooqpo032qjibt1vp.png" alt="Rocket.Chat Cloud Workspace screenshot" width="2742" height="1842" loading="lazy"></p>
<p>It will take a little while to set up. When it’s done, you should see something similar to this:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rcpvsdl49rw9t7bv7vfn.png" alt="Rocket.Chat dashboard screenshot" width="2742" height="1842" loading="lazy"></p>
<p>Now copy your server URL—it should look like this: <a target="_blank" href="https://example.rocket.chat"><code>https://example.rocket.chat</code></a>.</p>
<h3 id="heading-step-2-configure-the-rocketchat-server">Step 2: Configure the Rocket.Chat Server</h3>
<p>Before diving into the code, we need to configure our server so we can use the livechat API.</p>
<p>To start, open your Rocket.Chat server and click on the menu button, then click on <strong>Omnichannel</strong>.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8cql8hzejwep1zhxxhgg.png" alt="Rocket.Chat Omnichannel menu screenshot" width="2737" height="892" loading="lazy"></p>
<p>Click on <strong>Agents</strong> on the sidebar and add yourself as an agent.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zsnnbjsbo64h8d5zya8u.png" alt="Rocket.Chat Omnichannel Agents section screenshot" width="2742" height="1842" loading="lazy"></p>
<p>Next, click on <strong>Departments</strong> and create a Department. I'll call mine <strong>Chats</strong>.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ej4n16odg3iy578ltna0.png" alt="Rocket.Chat Omnichannel Departments section screenshot" width="2742" height="1842" loading="lazy"></p>
<p>Now you need to configure a few things about the Livechat widget:</p>
<ul>
<li><p>Make sure you turn on the offline form and set the Email Address to Send Offline Messages.</p>
</li>
<li><p>Also, configure your business hours to the times you'll be available.</p>
</li>
</ul>
<h3 id="heading-step-3-register-the-visitor">Step 3: Register the Visitor</h3>
<p>Next, we need to register the visitor and create a room for them. To do this, you need to collect the visitor's name and email and generate a random unique ID.</p>
<h4 id="heading-how-to-register-the-visitor">How to Register the Visitor</h4>
<p>First, we need to register the visitor in the server. We need their name, email, and token. You send those to this endpoint: <code>/api/v1/livechat/visitor</code>. Here's an example code that you might send from your backend:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> body = {
  <span class="hljs-attr">name</span>: <span class="hljs-string">"Visitor Name"</span>,          <span class="hljs-comment">// Replace with the visitor's name</span>
  <span class="hljs-attr">email</span>: <span class="hljs-string">"visitor@example.com"</span>,  <span class="hljs-comment">// Replace with the visitor's email</span>
  <span class="hljs-attr">token</span>: <span class="hljs-string">"unique-visitor-token"</span>  <span class="hljs-comment">// Replace with a generated unique token</span>
};

fetch(<span class="hljs-string">`<span class="hljs-subst">${process.env.ROCKETCHAT_URL}</span>/api/v1/livechat/visitor`</span>, {
  <span class="hljs-attr">method</span>: <span class="hljs-string">'POST'</span>,
  <span class="hljs-attr">headers</span>: {
    <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>,
    <span class="hljs-string">'Cache-Control'</span>: <span class="hljs-string">'no-cache'</span>
  },
  <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify(body)
})
  .then(<span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> response.json())
  .then(<span class="hljs-function"><span class="hljs-params">data</span> =&gt;</span> {
    <span class="hljs-keyword">if</span> (data.success) {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Visitor registered:"</span>, data);
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Visitor registration failed:"</span>, data);
    }
  })
  .catch(<span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error in visitor registration:"</span>, error));
</code></pre>
<h4 id="heading-how-to-create-or-retrieve-the-chat-room">How to Create or Retrieve the Chat Room</h4>
<p>After you've registered the visitor, you need to create a room for them so they can send you messages and you can respond.</p>
<p>Call this endpoint <code>/api/v1/livechat/room</code> with the visitor token as a query parameter. If the visitor already has a room, it’ll be returned. If not, a new one will be created. This is how you can make that request from your backend:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> token = <span class="hljs-string">"unique-visitor-token"</span>; <span class="hljs-comment">// Replace with the actual visitor token</span>

fetch(<span class="hljs-string">`<span class="hljs-subst">${process.env.ROCKETCHAT_URL}</span>/api/v1/livechat/room?token=<span class="hljs-subst">${token}</span>`</span>, {
  <span class="hljs-attr">method</span>: <span class="hljs-string">'GET'</span>,
  <span class="hljs-attr">headers</span>: { <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span> },
})
  .then(<span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> response.json())
  .then(<span class="hljs-function"><span class="hljs-params">data</span> =&gt;</span> {
    <span class="hljs-keyword">if</span> (data.success) {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Room retrieved:"</span>, data);
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Failed to retrieve room:"</span>, data);
    }
  })
  .catch(<span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error in retrieving room:"</span>, error));
</code></pre>
<h4 id="heading-how-to-retrieve-livechat-configuration">How to Retrieve Livechat Configuration</h4>
<p>Lastly, we need to get the info about the visitor and the agent we registered. Use this API endpoint to get the visitor token, room ID, and agent info. You can use it to check if the agent is online before trying to connect to the WebSocket.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> token = <span class="hljs-string">"unique-visitor-token"</span>; <span class="hljs-comment">// Replace with the actual visitor token</span>
<span class="hljs-keyword">const</span> url = <span class="hljs-string">`<span class="hljs-subst">${process.env.ROCKETCHAT_URL}</span>/api/v1/livechat/config?token=<span class="hljs-subst">${token}</span>`</span>;

fetch(url, {
  <span class="hljs-attr">method</span>: <span class="hljs-string">'GET'</span>,
  <span class="hljs-attr">headers</span>: { <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span> },
})
  .then(<span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> response.json())
  .then(<span class="hljs-function"><span class="hljs-params">data</span> =&gt;</span> {
    <span class="hljs-keyword">if</span> (data.success) {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Livechat config:"</span>, data);
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Failed to get livechat config:"</span>, data);
    }
  })
  .catch(<span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error fetching livechat config:"</span>, error));
</code></pre>
<h3 id="heading-step-4-create-the-connection-to-websocket">Step 4: Create the Connection to WebSocket</h3>
<p>To establish the live chat experience, we need to open a WebSocket connection to Rocket.Chat and handle messaging.</p>
<h4 id="heading-websocket-connection-example">WebSocket Connection Example</h4>
<p>First, open the WebSocket like this:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> rocketChatSocket = <span class="hljs-keyword">new</span> WebSocket(<span class="hljs-string">"ws://example.rocket.chat/websocket"</span>);
</code></pre>
<p>Then connect:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> connectRequest = {
  <span class="hljs-attr">msg</span>: <span class="hljs-string">"connect"</span>,
  <span class="hljs-attr">version</span>: <span class="hljs-string">"1"</span>,
  <span class="hljs-attr">support</span>: [<span class="hljs-string">"1"</span>, <span class="hljs-string">"pre2"</span>, <span class="hljs-string">"pre1"</span>]
};
rocketChatSocket.send(<span class="hljs-built_in">JSON</span>.stringify(connectRequest));
</code></pre>
<p>You can keep the connection alive by responding to the server's <code>"ping"</code> messages with a <code>"pong"</code>.</p>
<pre><code class="lang-js">rocketChatSocket.onmessage = <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> data = <span class="hljs-built_in">JSON</span>.parse(event.data);
    <span class="hljs-keyword">if</span> (data.msg === <span class="hljs-string">"ping"</span>) {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Received ping from server, sending pong"</span>);
      rocketChatSocket.send(<span class="hljs-built_in">JSON</span>.stringify({ <span class="hljs-attr">msg</span>: <span class="hljs-string">"pong"</span> }));
    }
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error parsing WebSocket message:"</span>, error);
  }
};
</code></pre>
<p>You can subscribe to the room created for the visitor. Just use the visitor’s token and room ID from the previous sections.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> subscribeRequest = {
  <span class="hljs-attr">msg</span>: <span class="hljs-string">"sub"</span>,
  <span class="hljs-attr">id</span>: <span class="hljs-string">"unique-subscription-id"</span>, <span class="hljs-comment">// Replace with your unique ID</span>
  <span class="hljs-attr">name</span>: <span class="hljs-string">"stream-room-messages"</span>,
  <span class="hljs-attr">params</span>: [
    <span class="hljs-string">"fetched-room-id"</span>, <span class="hljs-comment">// Replace with the room ID variable</span>
    {
      <span class="hljs-attr">useCollection</span>: <span class="hljs-literal">false</span>,
      <span class="hljs-attr">args</span>: [
        { <span class="hljs-attr">visitorToken</span>: <span class="hljs-string">"visitor-token"</span> } <span class="hljs-comment">// Replace with your visitor token variable</span>
      ],
    },
  ],
};
rocketChatSocket.send(<span class="hljs-built_in">JSON</span>.stringify(subscribeRequest));
</code></pre>
<p>You can also listen for incoming messages. Here’s how you can process new messages as they arrive:</p>
<pre><code class="lang-js">rocketChatSocket.onmessage = <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> data = <span class="hljs-built_in">JSON</span>.parse(event.data);
    <span class="hljs-keyword">if</span> (
      data.msg === <span class="hljs-string">"changed"</span> &amp;&amp;
      data.collection === <span class="hljs-string">"stream-room-messages"</span>
    ) {
      <span class="hljs-comment">// Handle new messages</span>
      <span class="hljs-keyword">if</span> (data.fields &amp;&amp; data.fields.args &amp;&amp; data.fields.args.length &gt; <span class="hljs-number">0</span>) {
        <span class="hljs-keyword">const</span> newMessage = data.fields.args[<span class="hljs-number">0</span>];
        <span class="hljs-comment">// Assume isValidChatMessage is defined to validate the message format</span>
        <span class="hljs-keyword">if</span> (isValidChatMessage(newMessage)) {
          <span class="hljs-comment">// Update your messages list here</span>
          <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"New message received:"</span>, newMessage);
        }
      }
    }
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error parsing WebSocket message:"</span>, error);
  }
};
</code></pre>
<p>What if you want to send livechat messages? Just use this code to do so:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> sendMessageRequest = {
  <span class="hljs-attr">msg</span>: <span class="hljs-string">"method"</span>,
  <span class="hljs-attr">method</span>: <span class="hljs-string">"sendMessageLivechat"</span>,
  <span class="hljs-attr">params</span>: [
    {
      <span class="hljs-attr">_id</span>: <span class="hljs-string">"unique-message-id"</span>,  <span class="hljs-comment">// Replace with a generated unique ID for the message</span>
      <span class="hljs-attr">rid</span>: <span class="hljs-string">"room-id"</span>,            <span class="hljs-comment">// Replace with the actual room ID</span>
      <span class="hljs-attr">msg</span>: <span class="hljs-string">"Your message here"</span>,  <span class="hljs-comment">// Replace with the message text you want to send</span>
      <span class="hljs-attr">token</span>: <span class="hljs-string">"visitor-token"</span>     <span class="hljs-comment">// Replace with the actual visitor token</span>
    }
  ],
  <span class="hljs-attr">id</span>: <span class="hljs-string">"unique-request-id"</span>        <span class="hljs-comment">// Replace with a unique request ID</span>
};

rocketChatSocket.send(<span class="hljs-built_in">JSON</span>.stringify(sendMessageRequest));
</code></pre>
<p>In your actual implementation, you can integrate these examples into your backend or client-side logic as needed.</p>
<p>You can take a look at the <a target="_blank" href="https://github.com/iamspruce/resume">source code</a> for how I implemented mine with Next.js or you can look at the live <a target="_blank" href="https://resume-alpha-jet-70.vercel.app">demo</a>.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Adding a Livechat feature to your web apps shouldn't be hard. With Rocket.Chat's livechat API, you can quickly integrate chat functionality and gain valuable insights from your users. I even built an <a target="_blank" href="https://www.npmjs.com/package/rocketchat-livechat-sdk">SDK wrapper</a> to make it easier to use.</p>
<p>Now it’s your turn! Try out Rocket.Chat’s API and build your own live chat system. You can explore more in the Rocket.Chat <a target="_blank" href="https://docs.rocket.chat">documentation</a>.</p>
<p>Happy coding!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Start Building Projects with LLMs ]]>
                </title>
                <description>
                    <![CDATA[ If you’re an aspiring AI professional, becoming an LLM engineer offers an exciting and promising career path. But where should you start? What should your trajectory look like? How should you learn? In one of my previous posts, I laid out the complet... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-start-building-projects-with-llms/</link>
                <guid isPermaLink="false">66faf2011a0aeb460edd6a88</guid>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ llm ]]>
                    </category>
                
                    <category>
                        <![CDATA[ engineering ]]>
                    </category>
                
                    <category>
                        <![CDATA[ chatbot ]]>
                    </category>
                
                    <category>
                        <![CDATA[ langchain ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Harshit Tyagi ]]>
                </dc:creator>
                <pubDate>Mon, 30 Sep 2024 18:46:25 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1727442031549/2b9f61f1-d25d-4c10-8a9e-c63fe7ee7cad.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you’re an aspiring AI professional, becoming an LLM engineer offers an exciting and promising career path.</p>
<p>But where should you start? What should your trajectory look like? How should you learn?</p>
<p>In one of my <a target="_blank" href="https://dswharshit.medium.com/roadmap-to-become-an-ai-engineer-roadmap-6d9558d970cf">previous</a> <a target="_blank" href="https://dswharshit.medium.com/roadmap-to-become-an-ai-engineer-roadmap-6d9558d970cf">posts</a>, I laid out the complete roadmap to become an AI / LLM Engineer. Reading this article will give you insights into the types of skills you’ll need to acquire and how to start learning.</p>
<h2 id="heading-the-best-way-to-learn-is-to-build">The Best Way to Learn  is to  BUILD!</h2>
<p>As Andrej Karpathy puts it:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727441366598/07d24597-c31d-45b5-a99c-fbb485ce3459.png" alt="Karpathy's message on how to become an expert at a thing" width="1170" height="410" loading="lazy"></p>
<p>Andrej emphasizes that you should build concrete projects, and explain everything you learn in your own words. (He also instructs us to only compare ourselves to a younger version of ourselves – never to others.)</p>
<p>And I agree – building projects is the best way to not just learn but really grok these concepts. It will further sharpen the skills you’re learning to think about cutting edge use cases.</p>
<p>But the main challenge with this learning philosophy is that good projects can be hard to find.</p>
<p>And that’s the problem I am trying to resolve. I want to help people, including myself, discover and build practical and real-world projects that help you develop skills that are worth showcasing in your portfolio.</p>
<h2 id="heading-heres-what-well-cover">Here’s What We’ll Cover:</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-what-should-be-your-first-project">What Should Be Your First Project?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-project-1-summarise-youtube-videos">Project #1: YouTube Video Summarizer</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-setup-and-requirements">Setup and Requirements</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-introduction-to-document-loaders">Introduction to Document Loaders</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-processing-youtube-transcripts">Processing YouTube Transcripts</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-using-langchain-for-summarization">Using LangChain for Summarization</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-deploying-the-summarizer-on-whatsapp">Deploying the Summarizer on WhatsApp</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-creating-a-flask-api">Creating a Flask API</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-connecting-with-twilio-for-whatsapp-integration">Connecting with Twilio for WhatsApp integration</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-project-2-build-a-bot-that-can-handle-different-types-of-user-queries">Project #2 preview: Multi-purpose Customer Service Bot</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-project-3-rag-powered-support-bot">Project #3 preview: RAG-Powered Support Bot</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-what-should-be-your-first-project">What Should Be Your First Project?</h2>
<p>If you’re a beginner who knows basic to intermediate programming, your initial projects should showcase that you can comfortably build applications with LLMs.</p>
<p>They should demonstrate that:</p>
<ul>
<li><p>you know what APIs are</p>
</li>
<li><p>you know how to consume them</p>
</li>
<li><p>you know how to build products that people actually want to use</p>
</li>
</ul>
<p>Building a chatbot provides a great starting point, but at this point everyone has developed one. And there are many solutions for easy Streamlit based prototypes. So, you need to develop something that’s actually usable and has the potential to reach a wider audience.</p>
<p>I’d suggest building a chatbot for WhatsApp or Discord or Telegram. Build a chatbot which solves a problem people struggle with, a problem that companies have started to build solutions for.</p>
<p>If I had to pick a good and, arguably, the most common AI project that every company has started to work on, it would be RAG-powered chatbots.</p>
<p>But before you get to building RAG-powered bots, you should start building something slightly more basic but practical with LLMs.</p>
<p>To kick things off, let’s start by building a YouTube Summariser.</p>
<h2 id="heading-project-1-summarise-youtube-videos">Project #1: Summarise YouTube Videos</h2>
<p>We’ll build the first part of this project in this tutorial: the core functionality of a YouTube video summariser tool.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727441993970/d318b7d9-37d5-4e93-a862-4d8c6e23886b.png" alt="Wiplane's project on building Youtube summariser whatsapp chatbot" width="880" height="896" loading="lazy"></p>
<p>Our bot will:</p>
<ul>
<li><p>Receive the YouTube URL.</p>
</li>
<li><p>Validate if the URL is correct.</p>
</li>
<li><p>Retrieve the transcript of the video</p>
</li>
<li><p>Use an LLM to analyze and summarize the video’s content.</p>
</li>
<li><p>Return the summary to the user.</p>
</li>
</ul>
<h3 id="heading-setup-and-requirements">Setup and Requirements</h3>
<p>For this project, we’ll code the core functionality in a Jupyter Notebook using the following Python packages:</p>
<ul>
<li><p><code>langchain-together</code> — for the LLM using the LangChain &lt;&gt; Together AI integration</p>
</li>
<li><p><code>langchain-community</code> — for specific data loaders</p>
</li>
<li><p><code>langchain</code> — for programming with LLMs</p>
</li>
<li><p><code>pytube</code> — for fetching video info</p>
</li>
<li><p><code>youtube-transcript-api</code> — for youtube video transcript</p>
</li>
</ul>
<p>We’ll use the Llama 3.1 model offered as an API by <a target="_blank" href="https://www.together.ai/">Together AI</a>.</p>
<p><strong>Together AI</strong> is a cloud platform that offers the open source models as inference APIs. without worrying about the underlying infrastructure.</p>
<p>Let’s start by installing these:</p>
<pre><code class="lang-bash">!pip install — upgrade — quiet langchain
!pip install — quiet langchain-community
!pip install — upgrade — quiet langchain-together
!pip install youtube_transcript_api
!pip install pytube
</code></pre>
<p>Now let’s set up our LLM:</p>
<pre><code class="lang-python"><span class="hljs-comment">## setting up the language model</span>
<span class="hljs-keyword">from</span> langchain_together <span class="hljs-keyword">import</span> ChatTogether
<span class="hljs-keyword">import</span> api_key

llm = ChatTogether(api_key=api_key.api,temperature=<span class="hljs-number">0.0</span>, 
                   model=<span class="hljs-string">"meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo"</span>)
</code></pre>
<p>The next step is to process the YouTube videos as a data source. For this we’ll need to understand the concept of document loaders.</p>
<h3 id="heading-introduction-to-document-loaders">Introduction to Document Loaders</h3>
<p>Document loaders provide a unified interface to load data from various sources into a standardized Document format.</p>
<ul>
<li><p>They automatically extract and attach relevant metadata to the loaded content.</p>
</li>
<li><p>The metadata can include source information, timestamps, or other contextual data that can be valuable for downstream processing.</p>
</li>
<li><p>LangChain offers loaders for CSV, PDF, HTML, JSON, and even specialized loaders for sources like YouTube transcripts or GitHub repositories, as listed in <a target="_blank" href="https://python.langchain.com/docs/how_to/#document-loaders">their integrations page</a>.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727441974919/e979be2a-c1d8-4936-aa45-58d909855ace.png" alt="LangChain supports different types of document loaders" width="2118" height="1394" loading="lazy"></p>
<h4 id="heading-categories-of-document-loaders">Categories of Document Loaders</h4>
<p>Document loaders in LangChain can be broadly categorized into two types:</p>
<ol>
<li><strong>File Type-Based Loaders</strong></li>
</ol>
<ul>
<li><p>Parse and load documents based on specific file formats</p>
</li>
<li><p>Examples include: CSV, PDF, HTML, Markdown</p>
</li>
</ul>
<p><strong>2. Data Source-Based Loaders</strong></p>
<ul>
<li><p>Retrieve data from various external sources</p>
</li>
<li><p>Load the data into Document objects</p>
</li>
<li><p>Examples include: YouTube, Wikipedia, GitHub</p>
</li>
</ul>
<h4 id="heading-integration-capabilities">Integration Capabilities</h4>
<ul>
<li><p>LangChain’s document loaders can integrate with almost any file format you might need.</p>
</li>
<li><p>They also support many third-party data sources.</p>
</li>
</ul>
<p>For our project, we’ll use the YoutubeLoader to get the transcripts in the required format.</p>
<h4 id="heading-youtubeloader-from-langchain-to-get-transcript">YoutubeLoader from LangChain to Get Transcript:</h4>
<pre><code class="lang-python"><span class="hljs-comment">## import the youtube documnent loader from LangChain</span>
<span class="hljs-keyword">from</span> langchain_community.document_loaders <span class="hljs-keyword">import</span> YoutubeLoader

video_url = <span class="hljs-string">'https://www.youtube.com/watch?v=gaWxyWwziwE'</span>
loader = YoutubeLoader.from_youtube_url(video_url, add_video_info=<span class="hljs-literal">False</span>)
data = loader.load()
</code></pre>
<h3 id="heading-process-the-youtube-transcript">Process the YouTube Transcript</h3>
<ul>
<li><p>Display raw transcript content</p>
</li>
<li><p>Use the LLM to summarize and extract key points from the transcript:</p>
</li>
</ul>
<pre><code class="lang-python"><span class="hljs-comment"># show the extracted page content</span>
data[<span class="hljs-number">0</span>].page_content
</code></pre>
<p>The <code>page_content</code> attribute contains the complete transcript as shown in the output below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727441916343/b834abbf-f4d5-4464-a421-257ef95fcbd1.png" alt="Youtube video transcript from the youtube loader" width="2890" height="860" loading="lazy"></p>
<p>Now that we have the transcript, we simply need to pass this to the LLM we configured above along with the prompt to summarise.</p>
<p>First, let’s understand a simple method:</p>
<p>Langchain offers the <code>invoke()</code> method to which you need to pass the system message and the user or human message.</p>
<p>The system message is essentially the instructions for the LLM on how it is supposed to process the human request.</p>
<p>And the human message is simply what we want the LLM to do.</p>
<pre><code class="lang-python"><span class="hljs-comment"># This code creates a list of messages for the language model:</span>
<span class="hljs-comment"># 1. A system message with instructions on how to summarize the video transcript</span>
<span class="hljs-comment"># 2. A human message containing the actual video transcript</span>

<span class="hljs-comment"># The messages are then passed to the language model (llm) for processing</span>
<span class="hljs-comment"># The model's response is stored in the 'ai_msg' variable and returned</span>

messages = [
    (
        <span class="hljs-string">"system"</span>, 
        <span class="hljs-string">"""Read through the entire transcript carefully.
           Provide a concise summary of the video's main topic and purpose.
           Extract and list the five most interesting or important points from the transcript. For each point: State the key idea in a clear and concise manner.

        - Ensure your summary and key points capture the essence of the video without including unnecessary details.
        - Use clear, engaging language that is accessible to a general audience.
        - If the transcript includes any statistical data, expert opinions, or unique insights, prioritize including these in your summary or key points."""</span>,
    ),
    (<span class="hljs-string">"human"</span>, data[<span class="hljs-number">0</span>].page_content),
]
ai_msg = llm.invoke(messages)
ai_msg
</code></pre>
<p>But this method won’t work when you have more variables and when you want a more dynamic solution.</p>
<h4 id="heading-for-this-langchain-offers-prompttemplate">For this, LangChain offers PromptTemplate:</h4>
<p>A PromptTemplate in LangChain is a powerful tool that helps in creating dynamic prompts for large language models (LLMs). It allows you to define a template with placeholders for variables that can be filled in with actual values at runtime.</p>
<p>This helps in managing and reusing prompts efficiently, ensuring consistency and reducing the likelihood of errors in prompt creation.</p>
<p>A PromptTemplate consists of:</p>
<ul>
<li><p><strong>Template String</strong>: The actual prompt text with placeholders for variables.</p>
</li>
<li><p><strong>Input Variables</strong>: A list of variables that will be replaced in the template string at runtime.</p>
</li>
</ul>
<pre><code class="lang-python"><span class="hljs-comment"># Set up a prompt template for summarizing a video transcript using LangChain</span>

<span class="hljs-comment"># Import necessary classes from LangChain</span>
<span class="hljs-keyword">from</span> langchain.prompts <span class="hljs-keyword">import</span> PromptTemplate
<span class="hljs-keyword">from</span> langchain <span class="hljs-keyword">import</span> LLMChain

<span class="hljs-comment"># Define a PromptTemplate for summarizing video transcripts</span>
<span class="hljs-comment"># The template includes instructions for the AI model on how to process the transcript</span>
product_description_template = PromptTemplate(
    input_variables=[<span class="hljs-string">"video_transcript"</span>],
    template=<span class="hljs-string">"""
    Read through the entire transcript carefully.
           Provide a concise summary of the video's main topic and purpose.
           Extract and list the five most interesting or important points from the transcript. 
           For each point: State the key idea in a clear and concise manner.

        - Ensure your summary and key points capture the essence of the video without including unnecessary details.
        - Use clear, engaging language that is accessible to a general audience.
        - If the transcript includes any statistical data, expert opinions, or unique insights, 
        prioritize including these in your summary or key points.

    Video transcript: {video_transcript}    """</span>
)
</code></pre>
<h3 id="heading-how-to-use-llmchain-lcel-for-summarization">How to Use LLMChain / LCEL for Summarization</h3>
<p>A chain is a sequence of steps that consists of a language model, PromptTemplate, and an optional output parser.</p>
<ul>
<li><p>Create an LLMChain with the custom prompt template</p>
</li>
<li><p>Generate a summary of the video transcript using the chain</p>
</li>
</ul>
<p>Here, we are using LLMChain but you can also use LangChain Expression Language as well to do this:</p>
<pre><code class="lang-python"><span class="hljs-comment">## invoke the chain with the video transcript </span>
chain = LLMChain(llm=llm, prompt=product_description_template)

<span class="hljs-comment"># Run the chain with the provided product details</span>
summary = chain.invoke({
    <span class="hljs-string">"video_transcript"</span>: data[<span class="hljs-number">0</span>].page_content
})
</code></pre>
<p>This will give you the summary object which has the text attribute that contains the response in markdown format.</p>
<pre><code class="lang-python">summary[<span class="hljs-string">'text'</span>]
</code></pre>
<p>The raw response will look like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727441806141/be122b5b-6774-46be-92ab-1f9e651b5045.png" alt="summary response from simple LLM chain" width="2340" height="470" loading="lazy"></p>
<p>To see the Markdown formatted response:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> IPython.display <span class="hljs-keyword">import</span> Markdown, display

display(Markdown(summary[<span class="hljs-string">'text'</span>]))
</code></pre>
<p>And there you go:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727441776170/98223339-03d2-483c-84ef-9400d2eb33f2.png" alt="Structure summary display using Markdown function " width="2272" height="866" loading="lazy"></p>
<p>So, the core functionality of our YouTube summariser is now working.</p>
<p>But this is working in your Jupyter Notebook, to make it more accessible, we’d need to get this functionality deployed on WhatsApp.</p>
<h3 id="heading-how-to-serve-the-yt-summariser-on-whatsapp">How to serve the YT summariser on WhatsApp</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727421384448/cd7f0f37-f25b-4b46-a4a9-0bcd5bf0f0fd.png" alt="Establishing connection between youtube and flask server using Twilio" class="image--center mx-auto" width="1905" height="318" loading="lazy"></p>
<p>For this, we’d need to serve our YT summarisation functionality as an API endpoint for which we are going to use Flask. You can also use FastAPI.</p>
<p>Now we’ll turn all the code in the Jupyter notebook into functions. So, add a function to check if it is a valid youtube URL, then define the <code>summarise</code> function that is basically a compilation of what we wrote in the Jupyter notebook.</p>
<p>You can configure our endpoint in the following manner:</p>
<pre><code class="lang-python"><span class="hljs-meta">@app.route('/summary', methods=['POST'])</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">summary</span>():</span>
    url = request.form.get(<span class="hljs-string">'Body'</span>)  <span class="hljs-comment"># Get the JSON data from the request body</span>
    print(url)
    <span class="hljs-keyword">if</span> is_youtube_url(url):
        response = summarise(url)
    <span class="hljs-keyword">else</span>:
        response = <span class="hljs-string">"please check if this is a correct youtube video url"</span>
    print(response)
    resp = MessagingResponse()
    msg = resp.message()
    msg.body(response)
    <span class="hljs-keyword">return</span> str(resp)
</code></pre>
<p>Once your <code>app.py</code> is ready with your Flask API, run the Python script, and you should have your server running locally on your system.</p>
<p>The next step is to make your local server connect with WhatsApp, and that’s where we’ll use Twilio.</p>
<p>Twilio allows us to implement this handshake by offering a WhatsApp sandbox to test your bot. You can follow the steps in this guide <a target="_blank" href="https://www.twilio.com/docs/whatsapp/quickstart/python">here</a> to build this connection.</p>
<p>I got the connection established:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727422495235/4a60a190-2d57-4726-be7c-1e062c4528e5.png" alt="Configure twilio sandbox settings" class="image--center mx-auto" width="1274" height="496" loading="lazy"></p>
<p>Now, we can start testing our WhatsApp bot:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727422721636/339fd977-6b63-4f57-ba40-e677c32e1814.png" alt="Summariser chatbot screenshot" class="image--center mx-auto" width="1508" height="1290" loading="lazy"></p>
<p>Amazing!</p>
<p>I explain all the steps in detail in my project-based course on <a target="_blank" href="https://www.wiplane.com/whatsapp-chatbot"><strong>Building LLM-powered WhatsApp Chatbots</strong></a><strong>.</strong></p>
<p>It’s a <strong>3-project course</strong> that contains two other more complex projects. I’ll give you a brief summary of those other projects here so you can try them out for yourselves. And if you’re interested, you can check out the course as well.</p>
<h2 id="heading-project-2-build-a-bot-that-can-handle-different-types-of-user-querieshttpswwwwiplanecomwhatsapp-chatbot"><a target="_blank" href="https://www.wiplane.com/whatsapp-chatbot">Project #2 — Build a Bot that Can Handle Different Types of User Queries</a></h2>
<p>This bot acts as a customer service representative for an airline. It can answer questions related to flight status, baggage inquiries, ticket booking, and more. It uses Langchain’s Router and LLM models to dynamically generate responses based on the user’s input.</p>
<ul>
<li><p>Different prompt templates are defined for various customer queries, such as flight status, baggage inquiries, and complaints.</p>
</li>
<li><p>Based on the query, the router selects the appropriate template and generates a response.</p>
</li>
<li><p>Twilio then sends the response back to the WhatsApp chat.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727441691086/54bcc4a9-8e04-4509-a361-ee4eb15bca08.png" alt="Wiplane's project 2 - Airline customer support to handle different types of queries" width="880" height="977" loading="lazy"></p>
<h2 id="heading-project-3-rag-powered-support-bothttpswwwwiplanecomwhatsapp-chatbot"><a target="_blank" href="https://www.wiplane.com/whatsapp-chatbot">Project #3 — RAG-Powered Support Bot</a> </h2>
<p>This chatbot answers questions related to airline services using a document-based system. The document is converted into embeddings, which are then queried using Langchain’s RAG system to generate responses. Companies want developers these days who have these skills, so this is an especially practical project.</p>
<ul>
<li><p>The guidelines/rules document is embedded using FAISS and HuggingFace models.</p>
</li>
<li><p>When a user submits a question, the RAG system retrieves relevant information from the document.</p>
</li>
<li><p>The system then generates a response using a pre-trained LLM and sends it back via Twilio.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727441686023/fe55ec78-96dd-42bd-aeae-ceaad24aae44.png" alt="Wiplane's project 3 - RAG powered support bot" width="880" height="1090" loading="lazy"></p>
<p>These 3 projects will get you started so you can continue experimenting and learning more about AI engineering.</p>
<p><a target="_blank" href="https://www.wiplane.com/whatsapp-chatbot"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727306395800/82bf4b68-a79b-4f40-b4fe-61f99fa445ab.png" alt="Wiplane's 3 project course on building LLM powered whatsapp chatbots" class="image--center mx-auto" width="3420" height="1238" loading="lazy"></a></p>
<p>Customer Support is the most funded category in AI because it reduces the cost instantly if AI can handle communication with disgruntled users.</p>
<p>So, we build bots that can handle different types of queries, intelligent RAG powered bots which will have access to proprietary documents to provided up-to-date information to the users.</p>
<p>That’s why I created <a target="_blank" href="https://www.wiplane.com/whatsapp-chatbot">this project-based course</a> to help you start building with LLMs.</p>
<p>Check out the course preview here:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/6R5DMyqMOz4" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p> </p>
<p>And to thank you for reading this guide, you can use the code FREECODECAMP to get a 20% discount on my course.</p>
<p>I want to make this affordably accessible for all those who are sincere about building with AI, so I’ve priced it affordably at $14.99 USD.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this tutorial, we focused on building a fun YouTube video summarizer tool that is served on WhatsApp.</p>
<p>The bot's core functionality includes:</p>
<ul>
<li><p>Receiving a YouTube URL</p>
</li>
<li><p>Validating the URL</p>
</li>
<li><p>Retrieving the video transcript</p>
</li>
<li><p>Using an LLM to summarize the content</p>
</li>
<li><p>Returning the summary to the user</p>
</li>
</ul>
<p>We used a number of Python packages including langchain-together, langchain-community, langchain, pytube, and youtube-transcript-api.</p>
<p>The project uses the Llama 3.1 model via Together AI's API.</p>
<p>We built the core summarisation functionality using</p>
<ul>
<li><p>Using LangChain's invoke() method with system and human messages</p>
</li>
<li><p>Using PromptTemplate and LLMChain for more dynamic solutions</p>
</li>
</ul>
<p>To make the tool accessible via WhatsApp:</p>
<ul>
<li><p>The functionality is served as an API endpoint using Flask</p>
</li>
<li><p>Twilio is used to connect the local server with WhatsApp</p>
</li>
<li><p>A WhatsApp sandbox is used for testing the bot</p>
</li>
</ul>
<p>To continue building further projects, check out the course.</p>
<p>It is a beginner track course where you start from learning to build with LLMs, then apply those skills to build 3 different types of LLM applications. Not just that – you learn to serve your applications as WA chatbots.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
