<?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>Uttar: grounded reply drafts for small sellers, on an open-weight model</title>
      <dc:creator>Mayank Sharma</dc:creator>
      <pubDate>Mon, 19 Oct 2026 04:56:20 +0000</pubDate>
      <link>https://dev.to/mayank_sharma_814005f2650/uttar-grounded-reply-drafts-for-small-sellers-on-an-open-weight-model-2nil</link>
      <guid>https://dev.to/mayank_sharma_814005f2650/uttar-grounded-reply-drafts-for-small-sellers-on-an-open-weight-model-2nil</guid>
      <description>&lt;p&gt;&lt;strong&gt;Uttar&lt;/strong&gt; (उत्तर — "answer") drafts replies for small Instagram/WhatsApp sellers who answer the same ten questions all day: &lt;em&gt;price? COD? Jorhat kitne din? size M hai?&lt;/em&gt; Paste the customer message, get 2–3 grounded drafts in the seller's own language and voice. The seller taps copy and sends it themselves.&lt;/p&gt;

&lt;p&gt;The core loop:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Seller pastes a customer message: &lt;code&gt;"bhaiya ye muga wala dupatta kitne ka hai? delivery ghar tak hoga?"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Uttar fuzzy-matches 1–3 products from the seller's own catalog (so the model never sees the whole price list — a small model stays accurate when you only show it what matters).&lt;/li&gt;
&lt;li&gt;Ollama runs &lt;strong&gt;gemma3:4b&lt;/strong&gt; with the matched facts, the shop's delivery/payment/return rules, and 20 of the seller's real past replies as few-shot examples.&lt;/li&gt;
&lt;li&gt;Output: &lt;em&gt;"Namaste bhaiya! Muga wala dupatta ₹2,400 ka hai. Jorhat tak delivery hoti hai, 3-4 din lagte hain ₹80 charge se…"&lt;/em&gt; — price from the catalog, delivery days from the rules, voice from the seller's own history.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never auto-sends.&lt;/strong&gt; The seller reviews, edits if needed, copies, sends from WhatsApp/Instagram themselves.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the answer isn't in the catalog, it says &lt;strong&gt;NEEDS_HUMAN&lt;/strong&gt; and flags the message instead of inventing. &lt;em&gt;"Border pe custom design bana sakte ho?"&lt;/em&gt; → refused, because the seller never told Uttar anything about customization. That refusal is the trust feature: replies can only come from facts the seller entered.&lt;/p&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%2Filfwtjlk79smyj1x4tnd.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%2Filfwtjlk79smyj1x4tnd.png" alt=" " width="799" height="516"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the open pieces are what make it work
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The cost math only closes with a self-hosted model.&lt;/strong&gt; The target price is ₹299–499/month per shop. Each draft request costs ~2,500–3,000 input tokens — not because the app is bloated, but because grounding is what makes a 4B model accurate: matched catalog slice + rules + 20 voice examples. At 3,000 messages/month (a busy shop, ~100/day):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GPT-4o-class pricing: ~9M input + 0.9M output ≈ &lt;strong&gt;$30+/month&lt;/strong&gt; — the model alone costs ~10× the plan price.&lt;/li&gt;
&lt;li&gt;Cheap mini-class pricing: ~$1.90/month — sounds fine until you add hosting for the app, and the margin on ₹299 evaporates while you still don't control Hinglish quality.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;gemma3:4b on a small server: ₹0 marginal.&lt;/strong&gt; The few-shot grounding that makes replies accurate is &lt;em&gt;free&lt;/em&gt; when tokens cost nothing. Per-token pricing punishes exactly the technique that keeps a small model honest.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Customer data never leaves the machine.&lt;/strong&gt; These are screenshots of people's order conversations — names, phone numbers, addresses. In Uttar, the catalog, the past replies, and every customer message go to localhost Ollama and nowhere else. There's no third-party API in the loop at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Swappable and tunable — which I needed within one day.&lt;/strong&gt; The contest's "pick whichever handles Hinglish best in your tests" is not a slogan here:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Accuracy (10 fixed Hinglish/Assamese msgs)&lt;/th&gt;
&lt;th&gt;Warm latency&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;gemma3:4b&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;9/10&lt;/td&gt;
&lt;td&gt;~11–12s&lt;/td&gt;
&lt;td&gt;Default — same accuracy, ~3× faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;qwen2.5:7b&lt;/td&gt;
&lt;td&gt;9/10&lt;/td&gt;
&lt;td&gt;~30–35s&lt;/td&gt;
&lt;td&gt;Better Assamese voice ("Dikhim kya chahiye?"), too slow for paste-and-reply&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two weeks after launch I could swap the model behind a dropdown, fine-tune few-shots per region, or change refusal behavior — without asking anyone's API team. Try telling a closed API to change its refusal style for your users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the open approach bit me — and what I changed
&lt;/h2&gt;

&lt;p&gt;I'm keeping the failures in, because they're the interesting part.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The model kept inventing policies.&lt;/strong&gt; First prompt: ask &lt;em&gt;"border customize kar sakte hain?"&lt;/em&gt; and gemma would cheerfully reply &lt;em&gt;"sorry, hum customize nahi karte"&lt;/em&gt; — except the seller never said that. A "no" not in your facts is just as fabricated as a fake price. Fixes, in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Hard rule: &lt;em&gt;"answering 'we don't do customization' when no policy is listed is WRONG, because that 'no' is also an invented fact. Silence = NEEDS_HUMAN"&lt;/em&gt; — with negative examples in the prompt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic guard&lt;/strong&gt; (&lt;code&gt;lib/guard.js&lt;/code&gt;): regex layer for known-unknown intents (customize, repair, wholesale…) that runs before the model. Small open models are inconsistent at refusal; a rules layer is not. Honest architecture: model for language, code for guarantees.&lt;/li&gt;
&lt;li&gt;Rule-category matching so pure policy questions (&lt;em&gt;"COD hai kya?"&lt;/em&gt; — no product tokens at all) reach the rules instead of dying at the fuzzy-match gate.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Measured results&lt;/strong&gt; (20-message test set, gemma3:4b, &lt;code&gt;scripts/realtest.js&lt;/code&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;19/20 correct behavior&lt;/strong&gt; — 15/15 in-catalog questions drafted, 4/5 refusals correct&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;9/9 price/fact checks passed&lt;/strong&gt; — every number in every draft came from the catalog&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Known failure&lt;/strong&gt;: &lt;em&gt;"dam koto muga orutia?"&lt;/em&gt; — "orutia" isn't in the catalog, and the model offers alternatives (muga scarf, dupatta) instead of a clean refusal. Arguably friendly, but it's the model guessing at product knowledge it doesn't have. It's also where Assamese product names in Roman script are weakest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One draft needed an edit&lt;/strong&gt;: a returns answer appended &lt;em&gt;"₹50 COD fee return ke time lagta hai"&lt;/em&gt; — stitched from two unrelated facts. Great demo of why a human approves every reply.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Honest caveat: it does not run on the seller's phone
&lt;/h2&gt;

&lt;p&gt;Most sellers live on phones, and running a 4B model on a phone isn't realistic yet. The setup is &lt;strong&gt;your server running Ollama&lt;/strong&gt; (a $5–10 VPS or your laptop for one shop). That still gives: zero marginal cost, no vendor lock-in, tuning freedom, and data staying off &lt;em&gt;somebody else's&lt;/em&gt; server. What it does not give: fully offline-on-device. Saying so plainly seemed more useful than a demo nobody's hardware can run.&lt;/p&gt;

&lt;p&gt;There's also no WhatsApp/Instagram API integration, deliberately. Official APIs are heavy, and scraping gets shops banned. Copy-paste is the MVP: the seller keeps their fingers on the send button, so nothing about this trips automation rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/mayankinkk/utar.git &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;utar
brew &lt;span class="nb"&gt;install &lt;/span&gt;ollama &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; brew services start ollama
ollama pull gemma3:4b
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run dev   &lt;span class="c"&gt;# http://localhost:3000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First run seeds a demo shop (Muga Ghar, Guwahati): 8 products, delivery/payment/return rules, 12 voice examples. The Setup tab is where a real seller enters their own list. It's a PWA — install it on a phone and it opens like an app.&lt;/p&gt;

&lt;p&gt;Stack: Next.js (JavaScript), Tailwind, better-sqlite3, Ollama. Fuzzy catalog matching is token scoring, no vector DB — for a 200-product shop, embeddings are a dependency you don't need.&lt;/p&gt;

&lt;h2&gt;
  
  
  First user
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Pending — to be filled with a real seller's ratings: "X of 20 send-as-is / edit / useless".&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Built for Hacktoberfest Open-Source AI Challenge Week 2 (Side Hustle). Agent session replay: &lt;em&gt;[paste DevRelay link here]&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>I built an AI that gives you one outdoor mission, then shuts up (Gemma + Ollama, runs locally)</title>
      <dc:creator>Mayank Sharma</dc:creator>
      <pubDate>Mon, 12 Oct 2026 05:26:20 +0000</pubDate>
      <link>https://dev.to/mayank_sharma_814005f2650/i-built-an-ai-that-gives-you-one-outdoor-mission-then-shuts-up-gemma-ollama-runs-locally-4k8p</link>
      <guid>https://dev.to/mayank_sharma_814005f2650/i-built-an-ai-that-gives-you-one-outdoor-mission-then-shuts-up-gemma-ollama-runs-locally-4k8p</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week1-2026-10-05"&gt;Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Offscreen&lt;/strong&gt; is a tiny app with one rule: the screen should be the shortest part of the experience.&lt;/p&gt;

&lt;p&gt;You tell it how much time you have (15 to 60 minutes). It checks your local weather and sunset time, and a local open model (Gemma via Ollama) writes &lt;strong&gt;one small, specific outdoor mission&lt;/strong&gt;. Not "go for a walk". Something like a shadow hunt: find five interesting shadows and photograph them. Then you tap "Speak &amp;amp; go", it reads the mission out loud, and you close the laptop.&lt;/p&gt;

&lt;p&gt;It's for anyone who opens their phone to "take a break" and ends up in a scroll hole. Generic advice like "go touch grass" never works for me. A concrete 15-minute task does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&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%2Fefxdmu0s6it3wiunks6s.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%2Fefxdmu0s6it3wiunks6s.png" alt=" " width="800" height="517"&gt;&lt;/a&gt;&lt;/p&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%2Fok71z54gtbq5an4epbjl.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%2Fok71z54gtbq5an4epbjl.png" alt=" " width="800" height="517"&gt;&lt;/a&gt;&lt;/p&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%2Fhbnp1htck0ipjzjzwv0z.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%2Fhbnp1htck0ipjzjzwv0z.png" alt=" " width="799" height="516"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/mayankinkk" rel="noopener noreferrer"&gt;
        mayankinkk
      &lt;/a&gt; / &lt;a href="https://github.com/mayankinkk/offscreen" rel="noopener noreferrer"&gt;
        offscreen
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;offscreen — Hacktoberfest 2026 Week 1: Touch Grass&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;One screen, 30 seconds, then go outside.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A local-first offscreen agent. You tell it how much time you have (15–60 min), it checks weather + sunset and a local open model (Gemma via Ollama) generates &lt;strong&gt;one small, specific outdoor mission&lt;/strong&gt; instead of “go for a walk” — then goes quiet. The screen is the shortest part.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Challenge prompt: &lt;em&gt;Build something with open-source AI at its core that gets people off the screen and into the world.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Entry:&lt;/strong&gt; &lt;code&gt;offscreen/&lt;/code&gt; — &lt;code&gt;npm run dev&lt;/code&gt; at &lt;code&gt;http://localhost:5173/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Challenge hub:&lt;/strong&gt; &lt;a href="https://dev.to/hacktoberfest" rel="nofollow"&gt;https://dev.to/hacktoberfest&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dates:&lt;/strong&gt; Oct 05–11, 2026&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Why this one wins the prompt&lt;/h3&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Not crowded:&lt;/strong&gt; The prompt lists &lt;em&gt;bird ID, garden planner, route builder&lt;/em&gt; — those will be flooded. A &lt;em&gt;mission generator&lt;/em&gt; with a &lt;em&gt;short-screen-time philosophy&lt;/em&gt; is a different angle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real “why open” story:&lt;/strong&gt; Your location, schedule and habits never leave your laptop, it works offline once the…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/mayankinkk/offscreen" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gemma 2B via Ollama&lt;/strong&gt; runs on my laptop and generates the mission. The prompt is time-aware: a 15-minute mission has to stay within a 5-minute walk, a 60-minute one can go further. The model returns JSON, which I parse and de-duplicate against my last 8 missions so it doesn't repeat itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open-Meteo&lt;/strong&gt; for weather and sunrise/sunset. No API key needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reverse geocoding&lt;/strong&gt; (BigDataCloud with a Nominatim fallback) so the app says "your city" instead of raw coordinates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ElevenLabs&lt;/strong&gt; reads the mission aloud, with the browser's Web Speech API as a free fallback. This is the "eyes off the screen" part.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React + Vite&lt;/strong&gt; for the UI, and a &lt;strong&gt;localStorage mission log&lt;/strong&gt; where I add photos and notes after each mission, with JSON/Markdown export.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fallback generator:&lt;/strong&gt; a hand-written pool of 16+ missions across time, weather and time-of-day, so the app still works if Ollama isn't running.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Field Test (the part that matters)
&lt;/h2&gt;

&lt;p&gt;[FILL THIS IN FROM YOUR field-log.md. Be specific and honest:]&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which missions did you actually do, and where?&lt;/li&gt;
&lt;li&gt;One mission that was great (what happened?)&lt;/li&gt;
&lt;li&gt;One mission the model got wrong or that felt cringe (be honest, this is what readers and judges respond to)&lt;/li&gt;
&lt;li&gt;Did the "speak and close the laptop" flow actually get you out the door?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;Offscreen needs your location, your free time, and your daily routine. That's exactly the data I don't want sitting on someone else's server.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Privacy:&lt;/strong&gt; the model runs on &lt;code&gt;localhost&lt;/code&gt;. The mission is generated on my machine. The only network calls are weather and place-name lookups.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost:&lt;/strong&gt; $0 to run, no per-call billing, no rate limits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control:&lt;/strong&gt; I can swap models and tune the prompt however I want. When Gemma gave bland missions, I changed the prompt, not a vendor's settings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Honest limitation:&lt;/strong&gt; mission generation works with no internet once the model is pulled, but the weather and place lookups need a connection. Without one, the app falls back to my built-in mission pool. So it's local-first, not fully offline.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Best Use of Gemma&lt;/li&gt;
&lt;li&gt;Best Use of ElevenLabs&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>I built a private WhatsApp digest for my friend who mutes every college group</title>
      <dc:creator>Mayank Sharma</dc:creator>
      <pubDate>Mon, 05 Oct 2026 05:12:29 +0000</pubDate>
      <link>https://dev.to/mayank_sharma_814005f2650/i-built-a-private-whatsapp-digest-for-my-friend-who-mutes-every-college-group-8e7</link>
      <guid>https://dev.to/mayank_sharma_814005f2650/i-built-a-private-whatsapp-digest-for-my-friend-who-mutes-every-college-group-8e7</guid>
      <description>&lt;p&gt;&lt;strong&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-weekend-2026-10-01"&gt;Hacktoberfest Weekend Challenge: Build for a Friend&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;My friend Mimi mutes every college WhatsApp group. It's not that she doesn't care, it's that 400 messages a day is unreadable. The result: missed deadlines, missed notices, and the classic "wait, the form was due yesterday?"&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;WhatsApp Group Digest&lt;/strong&gt;, a small tool that takes an exported WhatsApp chat and turns it into a short, readable digest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deadlines and assignments&lt;/strong&gt; with dates resolved properly (not just "tomorrow")&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exams and action items&lt;/strong&gt; that actually need a response&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Important updates and a TL;DR&lt;/strong&gt; so the rest of the chat can stay muted&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"What did I miss?"&lt;/strong&gt;, a weekly catch-up with key announcements, missed deadlines, events, and the occasional drama&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It runs fully on your own machine. The chat never leaves the laptop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&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%2F4palsaf6icbn9v1toali.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%2F4palsaf6icbn9v1toali.png" alt="WhatsApp Group Digest screenshot" width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To try it: export a group chat from WhatsApp (group name → &lt;strong&gt;Export chat&lt;/strong&gt; → &lt;strong&gt;Without media&lt;/strong&gt;), upload the &lt;code&gt;.txt&lt;/code&gt; file on the web page, and get your digest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/mayankinkk" rel="noopener noreferrer"&gt;
        mayankinkk
      &lt;/a&gt; / &lt;a href="https://github.com/mayankinkk/whatsapp-digest" rel="noopener noreferrer"&gt;
        whatsapp-digest
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;WhatsApp Group Digest&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;For the friend who mutes every college group. Upload a WhatsApp export and get a digest of deadlines, assignments, exams, and action items — powered entirely by open-source AI running locally via &lt;a href="https://ollama.com" rel="nofollow noopener noreferrer"&gt;Ollama&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why open?&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Private&lt;/strong&gt;: Your chat never leaves your machine. No cloud, no server, no data collection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline&lt;/strong&gt;: Works with no internet connection once the model is pulled.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Free&lt;/strong&gt;: Zero cost to run — no API keys, no usage limits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Swappable&lt;/strong&gt;: Run Gemma, Qwen, Llama — any open-weight model you choose.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Quick start&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; 1. Install Ollama (if you haven't)&lt;/span&gt;
curl -fsSL https://ollama.com/install.sh &lt;span class="pl-k"&gt;|&lt;/span&gt; sh

&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; 2. Pull a model&lt;/span&gt;
ollama pull gemma3:4b

&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; 3. Start Ollama&lt;/span&gt;
ollama serve &lt;span class="pl-k"&gt;&amp;amp;&lt;/span&gt;

&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; 4. Install and run the app&lt;/span&gt;
npm install
npm start&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Open &lt;a href="http://localhost:3000" rel="nofollow noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Export a chat from WhatsApp&lt;/h2&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Open the group → tap the group name&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Export chat&lt;/strong&gt; →…&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/mayankinkk/whatsapp-digest" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;The whole thing is deliberately small:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model:&lt;/strong&gt; &lt;a href="https://ai.google.dev/gemma" rel="noopener noreferrer"&gt;Gemma 3 (4B)&lt;/a&gt;, an open-weight model, running locally through &lt;strong&gt;Ollama&lt;/strong&gt;. The model is configurable with one environment variable, so swapping in Qwen or Llama is a one-line change (&lt;code&gt;OLLAMA_MODEL=qwen2.5:7b npm start&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend:&lt;/strong&gt; Node.js + Express. It parses the WhatsApp &lt;code&gt;.txt&lt;/code&gt; export, extracts messages with their timestamps and senders, and sends them to Ollama's local API with a prompt asking for a structured digest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; Vanilla JS + HTML/CSS. No framework, just an upload box and the results. The page also shows basic stats: message count, senders, and date range.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The date problem.&lt;/strong&gt; Chat messages are full of relative dates: "submit by tomorrow", "exam on Friday", "form closes tonight". Without knowing when the message was sent, those mean nothing, and a small model will happily guess. So the backend passes each message's timestamp along with its text, and the prompt asks the model to resolve relative dates against it. That way "tomorrow" becomes an actual calendar date in the digest instead of a vague word.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;Group chats are some of the most private text people have: names, plans, and personal conversations mixed in with the notices. Sending that to a closed API just to get a summary didn't feel right, and it isn't my call to make on behalf of everyone else in those groups.&lt;/p&gt;

&lt;p&gt;Running an open-weight model locally gave me things a closed API couldn't:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Private:&lt;/strong&gt; The chat never leaves the machine. No cloud, no server, no data collection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline:&lt;/strong&gt; Once the model is pulled, it works with no internet connection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Free:&lt;/strong&gt; No API keys, no usage limits, no subscription for a college student to worry about.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Swappable:&lt;/strong&gt; Gemma, Qwen, Llama, any open-weight model works. If a better one comes out next month, I change one variable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A closed API would have been quicker to set up, but it couldn't make the privacy promise, and for this project that promise is the whole point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma:&lt;/strong&gt; the digest is generated by Gemma 3 running locally through Ollama.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>hf26challenge</category>
    </item>
    <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>
