Learning · · 4 min read
Adding Real‑Time Chat Streaming to My Python Chatbot
I walk through how I gave my Python chatbot live‑typing responses using Supabase Realtime, what worked, what didn’t, and tips for beginners.
I recently wanted my Python chatbot to feel more like a live conversation instead of waiting for the whole answer to be generated. That’s why I started looking for a way to push each token as it arrives, and the phrase that kept popping up in Google was real-time chat streaming python. In this post I share how I wired Supabase Realtime into my simple Flask‑based bot, the small hacks that made it work, and the pitfalls I ran into. If you’re comfortable with Python, VS Code and a free Supabase project, you can follow along without spending any money.
Before diving into streaming I made sure my development environment was solid. I followed my own step‑by‑step guide to set up a cheap Python AI workspace, which helped me keep everything reproducible on a low‑end laptop here in Pundri. Having a clean setup saved me from chasing obscure version conflicts later when I tried to install the Supabase Python client.
real-time chat streaming python
Why I wanted streaming When a user asks a question, the language model usually streams tokens back one by one. If the front‑end waits for the full response, the conversation feels sluggish. By forwarding each token to the browser as soon as it’s generated, the user sees the answer appear like a human typing. This small UX change makes the bot feel more responsive and encourages longer interactions.
Setting up Supabase Realtime
Supabase offers a Realtime server that can push JSON rows over websockets. I first created a new project on supabase.com, then enabled the Realtime feature for a table called "chat_messages". The table only needs three columns: id (uuid), session_id (text), and content (text). After enabling Realtime I copied the anon public key – that’s what the Python client will use to subscribe.
- Create a free Supabase project and note the project URL.
- In the dashboard, go to "Database → Realtime" and toggle it on.
- Create a table "chat_messages" with columns id, session_id, and content.
- Copy the anon public API key from "Settings → API".
- Install the Python client: `pip install supabase`.
I also read my guide on Supabase Edge Functions to understand how the platform handles websockets behind the scenes. The Edge Functions article reminded me to keep the payload tiny – just the new token – because Realtime pushes the whole row to every subscriber.
Integrating the stream with my bot
The chatbot itself lives in a Flask route that calls the Gemini API via Claude Code. Instead of waiting for the full response, I used an async generator that yields each token. After every token I insert a new row into the "chat_messages" table; Supabase instantly broadcasts it to any client listening on the same session_id.
async def stream_response(prompt: str, session_id: str):
async for token in gemini_stream(prompt):
# Insert token into Supabase so the front‑end receives it
await supabase.from_('chat_messages').insert({
'session_id': session_id,
'content': token
})
yield token
On the front‑end I used the Supabase JavaScript client to listen for INSERT events: ```js supabase .from('chat_messages') .on('INSERT', payload => { const token = payload.new.content; appendToChatWindow(token); }) .subscribe(); ``` Each incoming token is appended to the chat window, giving the illusion of a person typing.
Testing the stream locally was a bit fiddly. I ran the Flask server in debug mode and opened the web page in Chrome. The first few tokens arrived instantly, but after a burst of activity the connection sometimes stalled. I discovered that the default timeout for Supabase Realtime is 10 seconds, so I increased it in the JavaScript client configuration.
Handling edge cases and latency
A few practical issues popped up: * **Duplicate tokens** – Occasionally the same token was inserted twice because the Gemini stream retried after a network hiccup. I added a simple deduplication step on the client side, ignoring a token if it matched the previous one. * **Out‑of‑order delivery** – Websocket messages can arrive out of order under heavy load. To keep ordering, I added an incremental sequence number to each row and sorted them before rendering. * **Connection drops** – When the websocket disconnects, the front‑end automatically reconnects, but any tokens generated in the meantime are lost. To mitigate this I store the full response in a temporary column "full_message" and, on reconnection, fetch the latest version from the database.
If you plan to scale to many concurrent users, consider creating a separate Realtime channel per session_id. Supabase lets you filter broadcasts, so each user only receives their own tokens. This reduces bandwidth and keeps the UI snappy.
What I learned and next steps
Adding real‑time chat streaming python taught me that the biggest gains often come from small UX tweaks rather than massive model upgrades. The Supabase Realtime service is free on the hobby tier, and with just a few lines of code you can turn a silent bot into a lively conversational partner. My next experiment is to push voice‑to‑text data through the same pipeline so the bot can respond with spoken words as they are generated. You can see the full source code and run it yourself on my projects page.
Frequently asked questions
What is real‑time chat streaming in a Python chatbot?
It is a technique where each token generated by the language model is sent to the user immediately, instead of waiting for the whole answer. This makes the conversation feel faster and more natural.
Do I need a paid Supabase plan for Realtime?
No. Supabase’s hobby tier includes Realtime for free, which is enough for small projects and learning experiments like the one described in this post.
Can I use this approach with frameworks other than Flask?
Absolutely. The core idea is to insert tokens into a Supabase table and listen for INSERT events. Any backend (FastAPI, Django, Node) that can write to Supabase will work the same way.