<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Anant Jain</title>
    <description>The latest articles on DEV Community by Anant Jain (@aj_infinite_2208).</description>
    <link>https://dev.to/aj_infinite_2208</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4078541%2F2d8d62d9-b704-4b6d-881f-e7d5a73520d4.png</url>
      <title>DEV Community: Anant Jain</title>
      <link>https://dev.to/aj_infinite_2208</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aj_infinite_2208"/>
    <language>en</language>
    <item>
      <title>I Built a Voice Shopping Assistant for Local Kirana Stores in 10 Days — Here's Everything That Broke</title>
      <dc:creator>Anant Jain</dc:creator>
      <pubDate>Sat, 15 Aug 2026 06:55:56 +0000</pubDate>
      <link>https://dev.to/aj_infinite_2208/i-built-a-voice-shopping-assistant-for-local-kirana-stores-in-10-days-heres-everything-that-broke-k50</link>
      <guid>https://dev.to/aj_infinite_2208/i-built-a-voice-shopping-assistant-for-local-kirana-stores-in-10-days-heres-everything-that-broke-k50</guid>
      <description>&lt;p&gt;&lt;em&gt;Part of &lt;a href="https://github.com/Anantjain-infinite/voice-agent-local_commerce" rel="noopener noreferrer"&gt;10 Days of Voice Agents — VoiceForBharat Edition&lt;/a&gt; by Murf AI.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem, and who it's for
&lt;/h2&gt;

&lt;p&gt;Walk into a small kirana store in most Indian towns and the interaction is entirely voice — you ask what's in stock, you ask the price, you ask if they deliver. There's no app, no menu, no typing. That's not a limitation; it's how local commerce has always worked here, and for a lot of customers — older shoppers, people more comfortable in Hindi than English, people without a smartphone habit of tapping through apps — voice &lt;em&gt;is&lt;/em&gt; the natural interface, not a fallback for one.&lt;/p&gt;

&lt;p&gt;So for this challenge I built &lt;strong&gt;Bazaar Mitra&lt;/strong&gt; ("market friend"), a voice agent for the Local Commerce track: something a shop could put a phone number behind, that helps a customer find a product, get a real price, start a return, and — critically — knows when to stop pretending it can help and hand the problem to a person.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it actually does
&lt;/h2&gt;

&lt;p&gt;By the end of 10 days, a call with Bazaar Mitra can do all of this, in Hindi, English, or the code-mixed way people actually talk:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Recognize a returning caller and pick the conversation back up ("last time you asked about atta — need more?") instead of starting cold every time&lt;/li&gt;
&lt;li&gt;Look up real prices and stock from an actual catalogue, and price out a multi-item order — never a guessed number&lt;/li&gt;
&lt;li&gt;Make &lt;strong&gt;outbound&lt;/strong&gt; calls itself (e.g. confirming an order), and open every one of them by clearly stating who's calling, why, and how to make it stop&lt;/li&gt;
&lt;li&gt;Recognize the two situations it genuinely shouldn't handle alone — a payment/refund dispute, or someone asking for a human — and create a real ticket for a person, with the caller's consent, never silently&lt;/li&gt;
&lt;li&gt;Hand off returns and refund questions to a separate specialist agent mid-conversation, without making the caller repeat themselves&lt;/li&gt;
&lt;li&gt;Record whether each call actually succeeded, and show that on a small live dashboard&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How the system works
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Caller (phone or browser)
        │  real-time audio
        ▼
   LiveKit Room  ───────────────────────────────►  Agent Session
                                                      STT → LLM → TTS
                                                   (Deepgram → Gemini → Murf Falcon)
                                                            │
                                          ┌─────────────────┴─────────────────┐
                                          ▼                                   ▼
                                  Function tools                        spoken reply
                             (memory, catalogue,                      streams back to
                              escalation, returns)                     the caller
                                          │
                                          ▼
                                       SQLite
                              (callers · catalogue · escalations
                               · call outcomes · returns)
                                          │
                                          ▼
                                 Human dashboard
                              (open escalations + call
                                  analytics, Flask)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fezbe92cw9q3bbw1ryyt7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fezbe92cw9q3bbw1ryyt7.png" alt="*(A cleaner version of this diagram is attached  raw `architecture.png` endraw .)*" width="800" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Four pieces do all the work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Speech-to-text&lt;/strong&gt; (Deepgram, &lt;code&gt;nova-3&lt;/code&gt;, multi-language) turns what the caller says into text the model can read.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The LLM&lt;/strong&gt; (Gemini &lt;code&gt;flash-lite&lt;/code&gt;) is the actual brain — it decides what to say and, more importantly, &lt;em&gt;when to call a tool&lt;/em&gt; instead of just talking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Text-to-speech&lt;/strong&gt; (Murf Falcon) turns the reply back into audio. This is the piece that has to be fast, because every millisecond here is a millisecond of dead air on a live call — in testing it consistently came in around &lt;strong&gt;110ms time-to-first-audio&lt;/strong&gt;, which is the difference between a conversation and a phone call with a bot bolted on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LiveKit&lt;/strong&gt; is the real-time transport underneath all of it — the room both the caller and the agent join, over WebRTC for browser callers or SIP for phone calls.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything the agent &lt;em&gt;knows&lt;/em&gt; — a caller's name, the product catalogue, open escalations, past call outcomes — lives in one SQLite file, read and written through a small set of Python modules the agent calls as tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  The features that mattered most
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Memory, with consent baked in.&lt;/strong&gt; The agent can save a caller's name and shopping preferences — but only after explicitly asking and getting a "yes." Say no, and nothing is written. This isn't a nice-to-have; for a track like this, an agent that silently&lt;br&gt;
remembers everything you say is a worse product, not a better one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real data over guessing.&lt;/strong&gt; Early on it would have been easy to let the LLM just generate a price when asked. Instead, prices and stock come from an actual lookup tool, and if that "catalogue service" times out, the agent says so out loud instead of inventing a number. Same principle for order totals — the tool does the math, the model never does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Escalation as a first-class outcome, not a failure.&lt;/strong&gt; A call that ends in a properly created human ticket — with the caller's consent, a short factual summary, no transcript, no sensitive data — counts as &lt;em&gt;successful&lt;/em&gt; on the analytics dashboard, the&lt;br&gt;
same as a call that resolved on its own. Knowing when to hand off is the feature, not a&lt;br&gt;
fallback.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A specialist that doesn't try to do everything.&lt;/strong&gt; The Returns &amp;amp; Refunds specialist has exactly one job. It doesn't touch the catalogue or order tools. When the main agent&lt;br&gt;
hands off to it, the caller doesn't repeat themselves — the specialist gets the prior conversation and introduces itself before continuing.&lt;/p&gt;
&lt;h2&gt;
  
  
  The hardest part: making outbound calls actually work
&lt;/h2&gt;

&lt;p&gt;Everything up to this point ran in a browser tab. Outbound calling is where the project&lt;br&gt;
left "code that runs in one predictable environment" and hit the much messier reality of a real Windows dev machine — and this is where I lost the most time, so it's the part&lt;br&gt;
most worth writing down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bug one: two scripts, two different ideas of where &lt;code&gt;.env.local&lt;/code&gt; lives.&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;python -m dotenv&lt;/code&gt; resolves a bare &lt;code&gt;".env.local"&lt;/code&gt; relative to the &lt;em&gt;current working&lt;br&gt;
directory&lt;/em&gt; — not the script's location. One script lived in &lt;code&gt;backend/src/&lt;/code&gt;, my actual&lt;br&gt;
&lt;code&gt;.env.local&lt;/code&gt; lived in &lt;code&gt;backend/&lt;/code&gt;, and depending on which folder I happened to run the&lt;br&gt;
command from, the same script would either find its config or throw a bare &lt;code&gt;KeyError&lt;/code&gt;&lt;br&gt;
with no useful context. The fix wasn't a one-off patch — it was a small shared helper&lt;br&gt;
that checks a short list of likely locations (next to the script, one directory up,&lt;br&gt;
current directory) and loads whichever one actually exists, printing exactly where it&lt;br&gt;
looked if none did. Once I stopped guessing a single path and started checking the&lt;br&gt;
realistic candidates, the problem stopped being a problem for &lt;em&gt;every&lt;/em&gt; script, not just&lt;br&gt;
the one I'd just debugged.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bug two, right after: &lt;code&gt;pip install&lt;/code&gt; succeeding into the wrong Python entirely.&lt;/strong&gt;&lt;br&gt;
With the venv apparently active — prompt and all — &lt;code&gt;pip install flask&lt;/code&gt; reported success,&lt;br&gt;
and &lt;code&gt;python dashboard.py&lt;/code&gt; still said &lt;code&gt;ModuleNotFoundError: No module named 'flask'&lt;/code&gt;.&lt;br&gt;
The giveaway was in the install log itself: the package script had been written to&lt;br&gt;
&lt;code&gt;AppData\Local\Python\pythoncore-3.14-64\Scripts&lt;/code&gt;, not &lt;code&gt;backend\.venv\Scripts&lt;/code&gt;. &lt;code&gt;pip&lt;/code&gt;&lt;br&gt;
and &lt;code&gt;python&lt;/code&gt; were quietly resolving to two different interpreters. The real fix was&lt;br&gt;
boring but reliable: stop trusting a bare &lt;code&gt;pip&lt;/code&gt; command, and always run&lt;br&gt;
&lt;code&gt;python -m pip install ...&lt;/code&gt; instead, so the install is guaranteed to land in whichever&lt;br&gt;
interpreter &lt;code&gt;python&lt;/code&gt; itself resolves to. (And when even &lt;em&gt;that&lt;/em&gt; failed with "No module&lt;br&gt;
named pip," it turned out the venv had never had pip bootstrapped into it at all —&lt;br&gt;
&lt;code&gt;python -m ensurepip --upgrade&lt;/code&gt; fixed that layer.)&lt;/p&gt;

&lt;p&gt;Neither of these was a LiveKit problem, or even really a "hard" problem — they were&lt;br&gt;
environment-hygiene issues that happen to be very easy to hit on Windows and very confusing to diagnose from the error message alone. If you're building on Windows: check &lt;code&gt;sys.executable&lt;/code&gt; before you trust that an install worked, and never assume a script's config file is where you think it is.&lt;/p&gt;
&lt;h2&gt;
  
  
  How to build your own
&lt;/h2&gt;

&lt;p&gt;You don't need to solve any of the above to get started — here's the actual minimum path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Get the starter project.&lt;/strong&gt;&lt;br&gt;
The &lt;a href="https://github.com/murf-ai/murf-livekit-starter" rel="noopener noreferrer"&gt;Murf LiveKit Starter&lt;/a&gt; gives you a working STT → LLM → TTS pipeline out of the box; I built on top of it rather than starting from an empty file, and I'd recommend the same.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Set up the environment.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;backend
python &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
.venv&lt;span class="se"&gt;\S&lt;/span&gt;cripts&lt;span class="se"&gt;\A&lt;/span&gt;ctivate.ps1      &lt;span class="c"&gt;# Windows&lt;/span&gt;
&lt;span class="c"&gt;# source .venv/bin/activate     # macOS/Linux&lt;/span&gt;
python &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Add your API keys — never to code, always to &lt;code&gt;.env.local&lt;/code&gt;.&lt;/strong&gt;&lt;br&gt;
Create &lt;code&gt;backend/.env.local&lt;/code&gt; (note: &lt;code&gt;.local&lt;/code&gt;, and make sure it's in &lt;code&gt;.gitignore&lt;/code&gt; — never&lt;br&gt;
commit this file):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LIVEKIT_URL=wss://your-project.livekit.cloud
LIVEKIT_API_KEY=...
LIVEKIT_API_SECRET=...
DEEPGRAM_API_KEY=...
GOOGLE_API_KEY=...
MURF_API_KEY=...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you add outbound calling later, the Twilio trunk credentials go in the same file —&lt;br&gt;
not in the script, not in a committed config.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Run it.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python src/agent.py dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This starts a worker that waits for a room to join. Pair it with the starter's frontend&lt;br&gt;
(or LiveKit's own Agents Playground) to actually get a microphone into a room and talk&lt;br&gt;
to it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Have a real conversation.&lt;/strong&gt;&lt;br&gt;
Ask it something ordinary first. Then push on the edges — ask about a product it&lt;br&gt;
doesn't have, ask it to remember you, hang up and call back. The gaps show up fast, and&lt;br&gt;
they're the most useful part of testing a voice agent: you can't unit-test "does this&lt;br&gt;
feel like talking to someone helpful."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code:&lt;/strong&gt; &lt;code&gt;[https://github.com/Anantjain-infinite/voice-agent-local_commerce]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;No API keys, phone numbers, or caller data are in the repo — every secret loads from a git-ignored &lt;code&gt;.env.local&lt;/code&gt;, and the SQLite database file itself is also excluded.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd improve next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Swap the hand-built catalogue and return-policy data for a real POS/inventory integration — they're isolated behind single functions specifically so that swap is small when a real one exists.&lt;/li&gt;
&lt;li&gt;Add basic auth to the human dashboard before it's anything but a local demo.&lt;/li&gt;
&lt;li&gt;Expand the outbound triggers beyond order confirmation — a restock nudge based on a
caller's own order rhythm is sitting right there in the memory data already.&lt;/li&gt;
&lt;li&gt;Get real numbers on the analytics dashboard beyond a handful of test calls, and see whether the success/failure split actually matches what a human reviewing the calls would call a "good" conversation.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Built as part of &lt;a href="https://github.com/murf-ai/voice-for-bharat-challenge-2026" rel="noopener noreferrer"&gt;10 Days of Voice Agents — VoiceForBharat Edition&lt;/a&gt;, using &lt;a href="https://murf.ai" rel="noopener noreferrer"&gt;Murf Falcon&lt;/a&gt; for text-to-speech.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>voiceforbharat</category>
      <category>voiceagent</category>
      <category>murfai</category>
      <category>murffalcon</category>
    </item>
  </channel>
</rss>
