<?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: Mayank Sharma</title>
    <description>The latest articles on DEV Community by Mayank Sharma (@mayank_sharma_814005f2650).</description>
    <link>https://dev.to/mayank_sharma_814005f2650</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%2F4079245%2F789a0d17-e4e9-4661-9863-2edb593167a6.jpg</url>
      <title>DEV Community: Mayank Sharma</title>
      <link>https://dev.to/mayank_sharma_814005f2650</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mayank_sharma_814005f2650"/>
    <language>en</language>
    <item>
      <title>Building RupeeGPT: A Multilingual Voice AI Financial Assistant for Bharat</title>
      <dc:creator>Mayank Sharma</dc:creator>
      <pubDate>Sat, 15 Aug 2026 16:51:27 +0000</pubDate>
      <link>https://dev.to/mayank_sharma_814005f2650/building-rupeegpt-a-multilingual-voice-ai-financial-assistant-for-bharat-f78</link>
      <guid>https://dev.to/mayank_sharma_814005f2650/building-rupeegpt-a-multilingual-voice-ai-financial-assistant-for-bharat-f78</guid>
      <description>&lt;h1&gt;
  
  
  How I Built RupeeGPT: A Voice-First AI Financial Assistant for India in 10 Days
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;#VoiceForBharat&lt;/strong&gt; | Built with the fastest TTS API — Murf Falcon | 10 Days of Voice Agents&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Ten days ago, I started with a blank repo and a challenge: build a production-ready voice AI agent for Indian users — one that could speak naturally in English, Hindi, and Hinglish; remember returning callers; escalate to humans when things got serious; and hand off conversations to specialist agents without ever making the caller repeat themselves.&lt;/p&gt;

&lt;p&gt;What came out the other side is &lt;strong&gt;RupeeGPT&lt;/strong&gt; — a conversational AI financial assistant that helps any Indian user navigate banking, UPI, government welfare schemes, loans, and financial safety. Here's everything I built, what broke, how I fixed it, and how you can build your own.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: Finance Advice Is Inaccessible to Most Indians
&lt;/h2&gt;

&lt;p&gt;India has over 500 million smartphone users, but financial literacy remains a barrier for hundreds of millions of people — especially in tier-2 and tier-3 cities and rural areas. The information exists: government scheme portals, RBI guidelines, banking apps. But it's buried in bureaucratic language, English-only interfaces, and long PDF documents.&lt;/p&gt;

&lt;p&gt;A voice agent changes that. You don't need to read anything. You don't need to know the right portal URL. You just &lt;em&gt;talk&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who it's for:&lt;/strong&gt; First-generation bank account holders, rural farmers checking PM Kisan eligibility, street vendors exploring PM SVANidhi loans, anyone who's ever been told to "read the fine print" and couldn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why voice:&lt;/strong&gt; Voice meets people where they are. It removes the literacy barrier, it's faster than navigating apps, and for many rural users, calling is the most intuitive interface they know.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🎙️ User speaks
    → Deepgram STT (nova-3, multilingual)
    → Gemini LLM (gemini-3.5-flash-lite via LiveKit Inference)
    → Murf Falcon TTS (Anisha — Indian English, en-IN)
    → LiveKit real-time transport
    → 🔊 User hears
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backend:&lt;/strong&gt; Python 3.12, LiveKit Agents SDK, &lt;code&gt;uv&lt;/code&gt; for dependency management&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; Next.js 14 (App Router), TypeScript, Tailwind&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory:&lt;/strong&gt; MongoDB Atlas (persistent caller profiles)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transport:&lt;/strong&gt; LiveKit (WebRTC)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TTS:&lt;/strong&gt; Murf Falcon — more on why this matters below&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Feature 1: An Indian Voice That Actually Sounds Indian
&lt;/h2&gt;

&lt;p&gt;The default for most TTS-backed voice agents is a US English voice. For an Indian user asking about &lt;em&gt;PM Kisan Samman Nidhi&lt;/em&gt;, hearing a generic American accent reading scheme names in English phonetics feels jarring and impersonal.&lt;/p&gt;

&lt;p&gt;Murf Falcon's &lt;strong&gt;Anisha&lt;/strong&gt; voice — Indian English, &lt;code&gt;en-IN&lt;/code&gt;, Conversation style — changes this completely. But there was a subtlety: even with an Indian voice, scheme names like "PM Kisan Samman Nidhi" or "Pradhan Mantri Jan Dhan Yojana" are read with English phonetics when spelled in Roman script.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My fix: a TTS pronunciation layer&lt;/strong&gt; (&lt;code&gt;tts_hindi.py&lt;/code&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;_ENGLISH_TO_HINDI&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;...]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pm kisan samman nidhi&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;पीएम किसान सम्मान निधि&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pm jan dhan yojana&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;              &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;पीएम जन धन योजना&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pradhan mantri jan dhan yojana&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;प्रधानमंत्री जन धन योजना&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pm svanidhi&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;पीएम स्वनिधि&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aadhaar&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                         &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;आधार&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yojana&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                          &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;योजना&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="c1"&gt;# ... more
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before any text reaches Murf Falcon, it passes through this whitelist rewriter. Known Hindi/Indian terms are converted to Devanagari, so the voice says &lt;em&gt;"पीएम किसान सम्मान निधि"&lt;/em&gt; — exactly as a native speaker would say it on TV — instead of "P M Kisan Samman Nidhi" with English stress patterns.&lt;/p&gt;

&lt;p&gt;The rewriter is safe to apply for every language mode: a pure-English sentence with none of these terms passes through byte-for-byte unchanged. I also built a &lt;code&gt;detect_language()&lt;/code&gt; function that classifies each user utterance as &lt;code&gt;english&lt;/code&gt;, &lt;code&gt;hindi&lt;/code&gt;, or &lt;code&gt;hinglish&lt;/code&gt; using Devanagari character detection and a curated Hinglish marker word list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;HINGLISH_MARKERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mujhe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kaise&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kya&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chahiye&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;baat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;namaste&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yojana&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sarkari&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;paise&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rupaye&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bharat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent mirrors the caller's language — answers in Hindi if they speak Hindi, Hinglish if they code-switch — without ever asking them to repeat.&lt;/p&gt;




&lt;h2&gt;
  
  
  Feature 2: Personality, Objectives, and Safety Guardrails
&lt;/h2&gt;

&lt;p&gt;The system prompt defines the entire character of RupeeGPT: what it will help with, what it refuses, and how it escalates.&lt;/p&gt;

&lt;p&gt;Key guardrails baked into the system prompt:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Never ask for OTPs, PINs, passwords, or Aadhaar/PAN numbers&lt;/strong&gt; — ever, for any reason&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Never guarantee loan approval, scheme eligibility, or returns&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Never impersonate bank officials or government employees&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two mandatory escalation triggers:&lt;/strong&gt; suspected fraud/unauthorized transactions, and official decision overrides (e.g., custom loan limit requests)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the two escalation scenarios, the agent must:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Stop assisting and explain the situation&lt;/li&gt;
&lt;li&gt;Name &lt;em&gt;exactly&lt;/em&gt; what information it will share&lt;/li&gt;
&lt;li&gt;Get &lt;strong&gt;explicit spoken consent&lt;/strong&gt; before proceeding&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;create_escalation()&lt;/code&gt; only after consent&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This pattern — ask before acting, require a clear YES — became a design principle throughout the whole project.&lt;/p&gt;




&lt;h2&gt;
  
  
  Feature 3: Multilingual TTS — English, Hindi, and Hinglish
&lt;/h2&gt;

&lt;p&gt;The TTS node hooks into the LiveKit Agents pipeline using &lt;code&gt;Agent.default.tts_node&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tts_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model_settings&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;language&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_tts_language&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_tracked&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tts_hindi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stream_for_tts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tts_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;_tracked&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;model_settings&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;stream_for_tts&lt;/code&gt; function accumulates the LLM's streaming text output into complete sentences before passing each sentence through the Devanagari rewriter. This is important: if a scheme name like "PM Kisan Samman Nidhi" were split across two streamed chunks, the phrase-level rewriter would miss it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Feature 4: Persistent Memory for Returning Callers
&lt;/h2&gt;

&lt;p&gt;Every caller gets a persistent browser ID (stored in &lt;code&gt;localStorage&lt;/code&gt; and passed as a LiveKit participant attribute). The agent reads this at the start of every session and calls &lt;code&gt;lookup_user()&lt;/code&gt; to fetch any saved profile from MongoDB.&lt;/p&gt;

&lt;p&gt;But here's the part that took the most iteration: &lt;strong&gt;consent architecture&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The agent is not allowed to save &lt;em&gt;any&lt;/em&gt; personal fact without:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The caller explicitly sharing the fact&lt;/li&gt;
&lt;li&gt;The agent asking whether to remember it (naming the exact fact)&lt;/li&gt;
&lt;li&gt;The caller saying a clear YES&lt;/li&gt;
&lt;li&gt;The agent calling &lt;code&gt;grant_user_memory_consent()&lt;/code&gt; with that exact value&lt;/li&gt;
&lt;li&gt;Only then calling &lt;code&gt;save_user_memory()&lt;/code&gt; with the same value
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Tools must be called in sequence, only after explicit spoken consent:
# 1. grant_user_memory_consent(name="Rahul", ...)
# 2. save_user_memory(name="Rahul", ...)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;save_user_memory&lt;/code&gt; tool actively &lt;em&gt;checks&lt;/em&gt; the in-session consent store and blocks saves for anything that wasn't consented to in the current call. Returning callers are greeted naturally: &lt;em&gt;"Namaste Rahul, welcome back. Would you like to continue from PM Jan Dhan Yojana?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The MongoDB document looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"abc123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Rahul"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"language_preference"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hinglish"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"facts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"schemes_checked"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"PM Jan Dhan Yojana"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"eligibility_answers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"income_bracket"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"below 3 lakh"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"farmer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"last_interaction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-08-14T10:30:00Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Feature 5: Tools That Fetch Real Data
&lt;/h2&gt;

&lt;p&gt;Three function-calling tools give the agent live (or near-live) data:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;find_eligible_schemes&lt;/code&gt;&lt;/strong&gt; — Matches the caller's profile (age, state, income, occupation, caste, residence, disability, BPL status) against a local dataset of Indian government welfare schemes. Returns preliminary matches with names, benefits, documents required, and official portal URLs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;get_usd_inr_rate&lt;/code&gt;&lt;/strong&gt; — Live USD/INR exchange rate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;get_lending_rates&lt;/code&gt;&lt;/strong&gt; — Current base lending rates and MCLR data.&lt;/p&gt;

&lt;p&gt;The scheme-matching tool uses a careful LLM prompt to avoid hallucination:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It never invents schemes or eligibility criteria&lt;/li&gt;
&lt;li&gt;If the result set is empty, it says so clearly&lt;/li&gt;
&lt;li&gt;If the tool errors, it says only that it cannot check right now — never speculates&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Feature 6: Outbound Phone Calls
&lt;/h2&gt;

&lt;p&gt;Using LiveKit's SIP integration, the agent can place outbound calls to real phone numbers. The session pipeline automatically detects SIP participants and switches the noise cancellation model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;noise_cancellation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;noise_cancellation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BVCTelephony&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;participant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;rtc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ParticipantKind&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PARTICIPANT_KIND_SIP&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;noise_cancellation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BVC&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;BVCTelephony&lt;/code&gt; is optimized for the narrowband audio characteristics of SIP/PSTN calls. The call analytics dashboard logs whether each session was a &lt;code&gt;web&lt;/code&gt; or &lt;code&gt;sip&lt;/code&gt; call.&lt;/p&gt;




&lt;h2&gt;
  
  
  Feature 7: Human Escalation with a Live Dashboard
&lt;/h2&gt;

&lt;p&gt;When a caller reports suspected fraud or requests a decision override, the agent collects their name, contact number, issue summary, and urgency level — all with explicit consent — then calls &lt;code&gt;create_escalation()&lt;/code&gt;. This writes to &lt;code&gt;escalations.json&lt;/code&gt; (read by the Next.js frontend) and POSTs to a webhook endpoint.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Escalation Desk&lt;/strong&gt; (&lt;code&gt;/demo&lt;/code&gt; route) shows open escalations in real time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Caller name, contact, issue summary, urgency badge&lt;/li&gt;
&lt;li&gt;Status: Open / In Progress / Resolved&lt;/li&gt;
&lt;li&gt;Reference ID (e.g., &lt;code&gt;ESC-492716&lt;/code&gt;) that the agent reads back to the caller&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Feature 8: Call Analytics Dashboard
&lt;/h2&gt;

&lt;p&gt;Every call session — web or SIP — is logged on close. The &lt;code&gt;_on_close&lt;/code&gt; handler fires when LiveKit closes the room:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_on_close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;call_record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;room&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;created_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ended_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;duration_seconds&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;userdata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success_reason&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;userdata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success_reason&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;call_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;call_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;# 'web' or 'sip'
&lt;/span&gt;        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;userdata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;# Write to calls.json + POST to Next.js API
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A call is marked &lt;strong&gt;successful&lt;/strong&gt; when the caller either checks their government scheme eligibility or creates a human escalation.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;/dashboard&lt;/code&gt; page shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Total calls, success rate, successful calls, failed calls (live-updating every 3s)&lt;/li&gt;
&lt;li&gt;Filterable call log table with duration, type badge, timestamp, and outcome&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Feature 9: Agent Handoff to a Specialist
&lt;/h2&gt;

&lt;p&gt;This was Day 9, and probably the most elegant feature technically. When a caller needs deep, focused help with government schemes — step-by-step application guidance, documents checklist, portal navigation — the main assistant hands off to a dedicated &lt;code&gt;GovernmentSchemeSpecialist&lt;/code&gt; agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;transfer_to_scheme_specialist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RunContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;specialist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GovernmentSchemeSpecialist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;chat_ctx&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# full conversation history
&lt;/span&gt;    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;specialist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;# live transition, no interruption
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Handoff complete.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The specialist receives the full &lt;code&gt;chat_ctx&lt;/code&gt;, so the caller never has to repeat themselves. The specialist introduces itself once, then continues the conversation in-context. It's focused: it only handles government scheme questions, and explicitly declines general banking/UPI questions.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Hard Parts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Streaming text rewriting without splitting phrases
&lt;/h3&gt;

&lt;p&gt;When the LLM streams its reply in chunks, a phrase like "PM Kisan Samman Nidhi" might arrive as &lt;code&gt;"PM Kisan"&lt;/code&gt; in one chunk and &lt;code&gt;" Samman Nidhi"&lt;/code&gt; in the next. My first implementation fed each chunk directly through the regex rewriter — which meant phrase-boundary splits caused silent failures where terms stayed in Roman script.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Buffer streamed chunks and only emit text at sentence boundaries (&lt;code&gt;"."&lt;/code&gt;, &lt;code&gt;"!"&lt;/code&gt;, &lt;code&gt;"?"&lt;/code&gt;, &lt;code&gt;"\n"&lt;/code&gt;). Since scheme names never cross sentence boundaries, the rewriter always sees the full phrase. Added a safety flush at 512 characters for run-on sentences.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The LLM saving memory without consent
&lt;/h3&gt;

&lt;p&gt;Early versions of the memory tools had a subtle problem: the LLM would call &lt;code&gt;save_user_memory&lt;/code&gt; in the same turn the caller first mentioned a fact, before any consent was sought. I fixed this with two layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prompt-level:&lt;/strong&gt; Explicit multi-step instructions in the system prompt&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool-level:&lt;/strong&gt; &lt;code&gt;save_user_memory&lt;/code&gt; checks an in-session &lt;code&gt;consent&lt;/code&gt; dict before writing anything; if the consent key isn't there, it returns a detailed refusal explaining exactly what's missing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This meant the enforcement was in the code, not just in the LLM's instruction-following.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Deprecation of &lt;code&gt;gemini-2.5-flash&lt;/code&gt; mid-challenge
&lt;/h3&gt;

&lt;p&gt;Around Day 7, calls started returning 404 errors. The model &lt;code&gt;gemini-2.5-flash&lt;/code&gt; had been deprecated. Migrating to &lt;code&gt;gemini-3.5-flash-lite&lt;/code&gt; via the LiveKit Inference plugin fixed it — but it required updating both &lt;code&gt;agent.py&lt;/code&gt; and the test harness configuration. &lt;strong&gt;Always pin your model versions.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Getting consistent Hinglish detection
&lt;/h3&gt;

&lt;p&gt;My first Hinglish marker list was too broad — common words like "hai" appeared in some proper nouns — and too narrow — it missed many common code-switch patterns. I iterated through actual test conversations, adding and removing markers until &lt;code&gt;detect_language()&lt;/code&gt; was reliably stable across English, Hindi, and code-switched Hinglish inputs.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to Build Your Own
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Four Core Components
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Used in this project&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;STT&lt;/td&gt;
&lt;td&gt;Turns speech to text (the ears)&lt;/td&gt;
&lt;td&gt;Deepgram nova-3, multilingual&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM&lt;/td&gt;
&lt;td&gt;Generates responses (the brain)&lt;/td&gt;
&lt;td&gt;Gemini 3.5 Flash Lite&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TTS&lt;/td&gt;
&lt;td&gt;Turns text to speech (the voice)&lt;/td&gt;
&lt;td&gt;Murf Falcon, Anisha (en-IN)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transport&lt;/td&gt;
&lt;td&gt;Real-time audio&lt;/td&gt;
&lt;td&gt;LiveKit (WebRTC)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The key insight: these four components are independent and swappable. You can use any STT, any LLM, any TTS — as long as they're wired through a common agent runtime (LiveKit Agents in this case).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Murf Falcon for TTS?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;55ms model latency&lt;/strong&gt; — the agent sounds instant, not laggy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;130ms time-to-first-audio&lt;/strong&gt; — faster than any alternative I benchmarked&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;150+ voices&lt;/strong&gt; across 35+ languages including Indian English and Hindi&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;$0.01/1000 characters&lt;/strong&gt; — dramatically cheaper than comparable options&lt;/li&gt;
&lt;li&gt;Native Indian English voices that sound genuinely natural for Indian users&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Quickstart
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/murf-ai/murf-livekit-starter.git
&lt;span class="nb"&gt;cd &lt;/span&gt;murf-livekit-starter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Set up API keys — never commit these to git:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;backend/.env.local&lt;/code&gt; (copy from &lt;code&gt;backend/.env.example&lt;/code&gt;):&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=your_key
LIVEKIT_API_SECRET=your_secret
MURF_API_KEY=your_murf_key          # murf.ai/api/dashboard
DEEPGRAM_API_KEY=your_deepgram_key  # deepgram.com
GOOGLE_API_KEY=your_google_key      # aistudio.google.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create &lt;code&gt;frontend/.env.local&lt;/code&gt; (copy from &lt;code&gt;frontend/.env.example&lt;/code&gt;):&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=your_key
LIVEKIT_API_SECRET=your_secret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Install and run:&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="c"&gt;# Backend (Python)&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;backend
uv &lt;span class="nb"&gt;sync
&lt;/span&gt;uv run python src/agent.py download-files

&lt;span class="c"&gt;# Frontend (Node)&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ../frontend
pnpm &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="c"&gt;# Run everything from repo root&lt;/span&gt;
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x start_app.sh &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./start_app.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;strong&gt;&lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;&lt;/strong&gt;, click &lt;strong&gt;Start talking&lt;/strong&gt;, allow microphone access, and speak.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to customise
&lt;/h3&gt;

&lt;p&gt;The entire personality lives in one constant at the top of &lt;code&gt;backend/src/agent.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;SYSTEM_PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;You are RupeeGPT, a personal AI assistant for Indian users.
&lt;/span&gt;&lt;span class="gp"&gt;...&lt;/span&gt;
&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change that string and you have a completely different agent. Change the voice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;tts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;murf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TTS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;voice&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Anisha&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;en-IN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Conversation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Browse all voices: murf.ai/api/docs/voices-styles/voice-library
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tracing a conversation turn
&lt;/h3&gt;

&lt;p&gt;After &lt;code&gt;./start_app.sh&lt;/code&gt;, check the backend terminal for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;[STT] user said: &amp;lt;transcript&amp;gt;&lt;/code&gt; — what Deepgram heard&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[LLM] metrics model=gemini-3.5-flash-lite ttft=0.42s&lt;/code&gt; — LLM latency&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[TTS] metrics ttfb=0.13s&lt;/code&gt; — Murf Falcon time-to-first-byte&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[CALL LOG] Saved to file ...&lt;/code&gt; — session logged to dashboard&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Architecture Overview
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser (Next.js)
    ↕ WebRTC (audio)
  LiveKit Server
    ↕ WebRTC
Python Agent Worker
  ├─ Deepgram STT    (nova-3, multilingual)
  ├─ Gemini LLM      (gemini-3.5-flash-lite)
  ├─ Murf Falcon TTS (Anisha, en-IN)
  ├─ tts_hindi.py    (Devanagari pronunciation rewriter)
  ├─ memory.py       (MongoDB caller profiles)
  ├─ schemes.py      (government scheme matching)
  ├─ telephony/      (SIP outbound calls)
  └─ Function tools:
       lookup_user()
       save_user_memory()
       grant_user_memory_consent()
       find_eligible_schemes()
       get_usd_inr_rate()
       get_lending_rates()
       create_escalation()
       transfer_to_scheme_specialist()

Next.js frontend routes:
  /          Voice agent UI
  /demo      Escalation Desk dashboard
  /dashboard Call Analytics dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Design strict tool schemas from Day 1.&lt;/strong&gt; The OpenAI strict schema validator requires every object to declare &lt;code&gt;additionalProperties: false&lt;/code&gt;. Retrofitting this was painful. The &lt;code&gt;_pick_arg()&lt;/code&gt; helper pattern I built to handle both LLM invocations and test-harness direct calls is something I'd design in from the beginning.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pin model versions immediately.&lt;/strong&gt; &lt;code&gt;gemini-2.5-flash&lt;/code&gt; deprecating mid-challenge cost me debugging time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Integration test with real audio early.&lt;/strong&gt; Unit tests caught logic errors; only real voice sessions caught the chunk-splitting bug in the TTS rewriter.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Repository
&lt;/h2&gt;

&lt;p&gt;🔗 &lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/murf-ai/murf-livekit-starter" rel="noopener noreferrer"&gt;github.com/murf-ai/murf-livekit-starter&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ Never publish API keys, phone numbers, caller data, or any private information. Use &lt;code&gt;.env.local&lt;/code&gt; (gitignored) for all secrets.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;Ten days. One voice agent. Nine features that went from zero to production-ready code:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;✅ Indian voice with Murf Falcon (Anisha, en-IN)&lt;/li&gt;
&lt;li&gt;✅ Personality, guardrails, and safety rules&lt;/li&gt;
&lt;li&gt;✅ English / Hindi / Hinglish support with Devanagari TTS rewriting&lt;/li&gt;
&lt;li&gt;✅ Frontend showing agent state&lt;/li&gt;
&lt;li&gt;✅ Persistent memory with consent enforcement&lt;/li&gt;
&lt;li&gt;✅ Real data tools (government schemes, exchange rates, lending rates)&lt;/li&gt;
&lt;li&gt;✅ Outbound phone calls via SIP&lt;/li&gt;
&lt;li&gt;✅ Human escalation with a live dashboard&lt;/li&gt;
&lt;li&gt;✅ Specialist agent handoff&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The most important lesson: &lt;strong&gt;voice agents are not just chatbots with audio bolted on.&lt;/strong&gt; The interaction model is fundamentally different — no markdown, no bullet points, short turns, immediate feedback. You have to design for &lt;em&gt;listening&lt;/em&gt;, not reading.&lt;/p&gt;

&lt;p&gt;And for Indian users specifically, language flexibility and natural pronunciation are the difference between a tool that feels foreign and one that feels like talking to someone who genuinely gets it.&lt;/p&gt;

&lt;p&gt;If you're building in this space, I hope this gives you a useful foundation. The code is open, the architecture is documented, and the patterns — consent-gated memory, language-aware TTS rewriting, live handoffs — are all reusable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build something for Bharat. Ship it.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built during **10 Days of Voice Agents — VoiceForBharat Edition&lt;/em&gt;&lt;em&gt;, powered by the fastest TTS API: **Murf Falcon&lt;/em&gt;&lt;em&gt;.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Tag @MurfAI | #VoiceForBharat&lt;/em&gt;&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>api</category>
      <category>llm</category>
    </item>
  </channel>
</rss>
