<?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: Ritesh Patil</title>
    <description>The latest articles on DEV Community by Ritesh Patil (@ritesh_patil9).</description>
    <link>https://dev.to/ritesh_patil9</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%2F4078682%2Fab9da1d5-73b9-44b3-979a-78cb82871ef6.png</url>
      <title>DEV Community: Ritesh Patil</title>
      <link>https://dev.to/ritesh_patil9</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ritesh_patil9"/>
    <language>en</language>
    <item>
      <title>Building DeutschMate: A Voice-Powered German Tutor &amp; Interview Coach for Bharat 10 Days of Voice Agents</title>
      <dc:creator>Ritesh Patil</dc:creator>
      <pubDate>Sat, 15 Aug 2026 10:44:48 +0000</pubDate>
      <link>https://dev.to/ritesh_patil9/building-deutschmate-a-voice-powered-german-tutor-interview-coach-for-bharat-10-days-of-voice-4f75</link>
      <guid>https://dev.to/ritesh_patil9/building-deutschmate-a-voice-powered-german-tutor-interview-coach-for-bharat-10-days-of-voice-4f75</guid>
      <description>&lt;p&gt;&lt;strong&gt;My journey through 10 Days of Voice Agents — VoiceForBharat Edition, building a real-time healthcare voice agent with Murf Falcon, LiveKit, memory, tools, telephony, human escalation, analytics, and multi-agent handoffs.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;What if learning German could be as simple as speaking naturally?&lt;/p&gt;

&lt;p&gt;Instead of opening an app, navigating through lessons, and typing answers, imagine simply saying:&lt;/p&gt;

&lt;p&gt;“Mujhe German practice karni hai.”&lt;br&gt;
“Give me a German exercise.”&lt;br&gt;
“I want to practice for a German job interview.”&lt;/p&gt;

&lt;p&gt;That idea became DeutschMate — a voice-first German learning companion I built during 10 Days of Voice Agents — VoiceForBharat Edition.&lt;/p&gt;

&lt;p&gt;DeutschMate is designed for &lt;strong&gt;Bharat-based learners who want to improve their German through real conversations&lt;/strong&gt;, whether they're learning for education, work, relocation, or simply to become more confident speaking German.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/riteshpatil9686-lgtm/murf-voice-agent" rel="noopener noreferrer"&gt;DeutschMate - GitHub&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Learning a language is not just about memorizing vocabulary or completing written exercises. At some point, you have to &lt;strong&gt;speak&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Traditional language-learning interfaces often make learners move through menus, read questions, type answers, and practice in a way that doesn't always feel like a real conversation.&lt;/p&gt;

&lt;p&gt;For Indian learners, conversations can also naturally move between languages:&lt;/p&gt;

&lt;p&gt;“Mujhe German mein introductions practice karna hai.”&lt;br&gt;
“Can you give me an A1 exercise?”&lt;br&gt;
“Mujhe German job interview ke liye prepare karna hai.”&lt;/p&gt;

&lt;p&gt;DeutschMate is built to make that interaction feel more natural.&lt;/p&gt;

&lt;p&gt;Instead of treating voice as just another input method, I wanted voice to be &lt;strong&gt;the primary interface for learning and practicing German&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  What DeutschMate Does
&lt;/h2&gt;

&lt;p&gt;DeutschMate combines real-time voice interaction with learning tools, memory, phone calls, analytics, and specialist agents.&lt;/p&gt;

&lt;p&gt;🎙️ &lt;strong&gt;Real-time voice conversation&lt;/strong&gt;&lt;br&gt;
🇮🇳 &lt;strong&gt;Indian-English and code-mixed conversations&lt;/strong&gt;, with voice powered by &lt;strong&gt;Murf Falcon&lt;/strong&gt;&lt;br&gt;
📚 &lt;strong&gt;German exercises&lt;/strong&gt; for vocabulary, grammar, and conversational practice&lt;br&gt;
🧠 &lt;strong&gt;Learner memory&lt;/strong&gt; for returning users, including learning goals and topics covered&lt;br&gt;
📞 &lt;strong&gt;Outbound German practice calls&lt;/strong&gt;&lt;br&gt;
🧑‍🏫 &lt;strong&gt;Human escalation&lt;/strong&gt; when the learner needs help beyond what the agent should handle&lt;br&gt;
📊 &lt;strong&gt;Call analytics dashboard&lt;/strong&gt; showing total, successful, and failed calls&lt;br&gt;
💼 &lt;strong&gt;German Job Interview Coach&lt;/strong&gt; for specialized interview practice&lt;br&gt;
🤖 &lt;strong&gt;Agent handoffs&lt;/strong&gt; so the main tutor can delegate specialized conversations&lt;br&gt;
🛡️ &lt;strong&gt;Safety and behavioral guardrails&lt;/strong&gt; to keep the agent within its intended role&lt;/p&gt;

&lt;p&gt;The goal isn't simply to make an AI that can talk.&lt;/p&gt;

&lt;p&gt;The goal is to build a &lt;strong&gt;voice learning system that can understand what the learner is trying to accomplish and take the appropriate action&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  How the System Works
&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%2Fhn4ecmce7vtvu7e22i8g.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%2Fhn4ecmce7vtvu7e22i8g.png" alt="##How the System Works" width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Voice transport&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;LiveKit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Speech-to-text&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Deepgram&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LLM / Reasoning&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Google Gemini&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Text-to-speech&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Murf Falcon&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Backend&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Python + LiveKit Agents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Frontend&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Next.js / React&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Database&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Learner memory&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;German exercises&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Local JSON dataset + Agent Tools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Outbound telephony&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;LiveKit SIP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Call analytics&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;PostgreSQL + Analytics Dashboard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Specialist agent&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;German Job Interview Coach&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Human escalation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Email / SMTP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Real-time communication&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;LiveKit&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  The Voice Layer
&lt;/h2&gt;

&lt;p&gt;Murf Falcon handles the text-to-speech layer of DeutschMate, turning the agent's responses into natural spoken conversation.&lt;/p&gt;

&lt;p&gt;The voice experience is designed around the way Indian learners actually communicate - conversations can naturally move between English, Hindi, Hinglish, and German depending on the learner's comfort level.&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%2Fmo0aa2ncru4fdkwrhfrb.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%2Fmo0aa2ncru4fdkwrhfrb.png" alt=" " width="800" height="1191"&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%2Fc9a6l3yy1fkodr90reo8.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%2Fc9a6l3yy1fkodr90reo8.png" alt=" " width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  German Learning Tools
&lt;/h2&gt;

&lt;p&gt;DeutschMate isn't just a conversational chatbot.&lt;/p&gt;

&lt;p&gt;It has tools specifically designed around German learning.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;get_german_practice
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exercise system loads German exercises from:&lt;/p&gt;

&lt;p&gt;backend/data/german_exercises.json&lt;/p&gt;

&lt;p&gt;Each exercise contains information such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "id": "...",
  "level": "beginner",
  "topic": "...",
  "type": "...",
  "question": "...",
  "answer": "..."
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exercise loader can filter exercises by learner level, topic, and practice type before selecting an exercise.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Learner: "Give me a German exercise."


                ↓


get_german_practice()


                ↓


Select exercise
        ↓
Level / topic / type
        ↓
German question
        ↓
Learner answers verbally
        ↓
Gemini evaluates the response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One important design decision here was keeping the learning experience conversational.&lt;/p&gt;

&lt;p&gt;The learner doesn't have to click through a traditional quiz interface.&lt;/p&gt;

&lt;p&gt;They can simply &lt;strong&gt;speak their answer&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exercise Completion and Call Success
&lt;/h2&gt;

&lt;p&gt;For Day 8, I needed a concrete definition of what a successful call means.&lt;/p&gt;

&lt;p&gt;For DeutschMate:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A successful call means the learner completes the intended German learning task during the session.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because the original exercise system didn't have a programmatic completion state, I added an explicit completion mechanism.&lt;/p&gt;

&lt;p&gt;The agent can mark an exercise as completed after determining that the learner answered correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Learner
   ↓
German exercise
   ↓
Learner attempts answer
   ↓
Gemini evaluates response
   ↓
Correct?
   ├── Yes → mark_exercise_complete()
   │              ↓
   │          Success flag
   │
   └── No  → Continue practice
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the session ends, that state is used to record the call outcome.&lt;/p&gt;

&lt;p&gt;This gave the analytics system an actual definition of &lt;strong&gt;success vs. failure&lt;/strong&gt;, instead of simply counting every conversation as successful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Memory for Returning Learners
&lt;/h2&gt;

&lt;p&gt;One of the most useful features of DeutschMate is persistent learner memory.&lt;/p&gt;

&lt;p&gt;A returning learner shouldn't have to start from zero every time.&lt;/p&gt;

&lt;p&gt;With consent, DeutschMate can save useful learning information such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;learner name&lt;/li&gt;
&lt;li&gt;German level&lt;/li&gt;
&lt;li&gt;language preference&lt;/li&gt;
&lt;li&gt;learning goal&lt;/li&gt;
&lt;li&gt;topics covered&lt;/li&gt;
&lt;li&gt;common mistakes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The memory system explicitly checks consent before saving learner information.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;First session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Learner → "My name is Ritesh."
        → "I'm a beginner in German."
        → "I want German for interviews."


                ↓


        Memory consent


                ↓


        PostgreSQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a later session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Returning learner
        ↓
PostgreSQL lookup
        ↓
Previous learning context
        ↓
DeutschMate
        ↓
Personalized conversation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent logs also confirm that learner memory is loaded from PostgreSQL when a returning learner starts a session.&lt;/p&gt;

&lt;p&gt;The principle here is simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't store everything. Store useful learning context, and only when the learner has consented.&lt;/strong&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%2F47cagsasfzf28fth2fbg.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%2F47cagsasfzf28fth2fbg.png" alt=" " width="800" height="125"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Outbound Voice Calls
&lt;/h2&gt;

&lt;p&gt;The project also moved beyond the normal:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Learner → Agent&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;model.&lt;/p&gt;

&lt;p&gt;DeutschMate can initiate an outbound German practice call.&lt;/p&gt;

&lt;p&gt;The outbound session reuses the same learner memory and practice capabilities as the normal conversation flow.&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%2Ftsnw2l5c3s4cx5cugpr7.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%2Ftsnw2l5c3s4cx5cugpr7.png" alt=" " width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is also an important transparency rule for outbound calls.&lt;/p&gt;

&lt;p&gt;The agent introduces itself first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Hallo! This is DeutschMate, your AI German tutor.
I'm calling for your daily German practice session.
You can hang up anytime if you'd like to stop.
Are you ready to practice?"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The system explicitly avoids pretending to be a human caller and ends the call if the learner asks it to stop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Human Escalation
&lt;/h2&gt;

&lt;p&gt;AI shouldn't always be the final destination.&lt;/p&gt;

&lt;p&gt;Sometimes a learner simply needs a human teacher.&lt;/p&gt;

&lt;p&gt;DeutschMate supports escalation when the learner needs human assistance, but the important part is that &lt;strong&gt;consent is explicit&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Learner
   ↓
Needs human help
   ↓
Explain why escalation is needed
   ↓
Explain what information will be shared
   ↓
Ask for consent
   ↓
Clear confirmation?
   ├── No → Continue normally
   │
   └── Yes
        ↓
   create_escalation()
        ↓
   Generate reference ID
        ↓
   Save escalation request
        ↓
   Attempt human notification

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Learner: "I'm feeling overwhelmed and I need a teacher."


DeutschMate:
"I can send a short summary to a human teacher
so they can help you. Would you like me to send
that request?"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only after a clear confirmation does the escalation tool execute.&lt;/p&gt;

&lt;p&gt;The actual logs show the agent creating escalation requests after explicit consent and generating a reference ID for the request.&lt;/p&gt;

&lt;p&gt;This became an important lesson:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An AI tutor should know when it has reached the edge of what it should handle itself.&lt;/strong&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%2F79qkd8wgysg6grtlo4j4.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%2F79qkd8wgysg6grtlo4j4.png" alt=" " width="800" height="389"&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%2Fqqi86obzrr9fzoez33uz.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%2Fqqi86obzrr9fzoez33uz.png" alt=" " width="800" height="583"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Call Analytics Dashboard
&lt;/h2&gt;

&lt;p&gt;Once a voice agent starts handling real conversations, another question becomes important:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How well is it actually performing?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For Day 8, I added a &lt;code&gt;call_analytics&lt;/code&gt; system backed by PostgreSQL.&lt;/p&gt;

&lt;p&gt;The dashboard tracks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Total calls&lt;/li&gt;
&lt;li&gt;Successful calls&lt;/li&gt;
&lt;li&gt;Failed calls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The data comes from actual agent sessions rather than hardcoded dashboard values.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The basic flow is:

Voice Session
     ↓
Learner completes / does not complete task
     ↓
Session ends
     ↓
Analytics writer
     ↓
PostgreSQL
     ↓
Dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A successful German learning call is one where the intended exercise is completed.&lt;/p&gt;

&lt;p&gt;A failed call does not necessarily mean that the system crashed.&lt;/p&gt;

&lt;p&gt;It simply means the defined learning objective wasn't completed.&lt;/p&gt;

&lt;p&gt;This distinction matters because:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;System failure and user-outcome failure are not the same thing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;During development, the analytics writer also exposed a PostgreSQL connection-lifecycle issue during LiveKit shutdown. The logs initially showed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cannot perform operation: another operation is in progress
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ConnectionDoesNotExistError:
connection was closed in the middle of operation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That led to an analytics fix using an isolated database connection and retry handling rather than relying directly on a shared pool operation.&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%2Fos2sy27z0851ifafcn1r.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%2Fos2sy27z0851ifafcn1r.png" alt=" " width="800" height="633"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-Agent Handoff
&lt;/h2&gt;

&lt;p&gt;One of the most interesting parts of DeutschMate is that the main agent doesn't have to do everything itself.&lt;/p&gt;

&lt;p&gt;The main agent handles general German learning.&lt;/p&gt;

&lt;p&gt;When the learner asks for interview-specific help, the conversation can be handed to a specialist:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;German Job Interview Coach&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;Learner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"I have a German job interview tomorrow."


        ↓


Main DeutschMate Agent
        ↓
Recognizes interview intent
        ↓
handoff_to_job_interview_coach()
        ↓
German Job Interview Coach
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This isn't theoretical — the actual agent logs show the main DeutschMate agent detecting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Have interview tomorrow."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and executing:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;handoff_to_job_interview_coach&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;with the interview date passed into the handoff context.&lt;/p&gt;

&lt;p&gt;The idea is simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One agent coordinates. Specialists handle specialized tasks&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is much easier to extend than trying to put every possible capability into one enormous system prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Context Travels With the Handoff
&lt;/h2&gt;

&lt;p&gt;A handoff is only useful if the specialist understands why the learner was transferred.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Main Agent&lt;br&gt;
    ↓&lt;br&gt;
Intent: German job interview&lt;br&gt;
    ↓&lt;br&gt;
Interview date: tomorrow&lt;br&gt;
    ↓&lt;br&gt;
Target role: Data Science&lt;br&gt;
    ↓&lt;br&gt;
Job Interview Coach&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The specialist can then continue the conversation instead of asking the learner to repeat everything.&lt;/p&gt;

&lt;p&gt;At the same time, sensitive information shouldn't unnecessarily travel between agents.&lt;/p&gt;

&lt;p&gt;The goal is to pass &lt;strong&gt;task-relevant context&lt;/strong&gt;, not the entire conversation.&lt;/p&gt;
&lt;h2&gt;
  
  
  Privacy Was a Design Requirement, Not an Afterthought
&lt;/h2&gt;

&lt;p&gt;DeutschMate deals primarily with learning information rather than medical or financial information, but privacy still matters.&lt;/p&gt;

&lt;p&gt;The system follows several boundaries:&lt;/p&gt;

&lt;p&gt;**1. Learner memory requires consent&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Only useful learning context is stored&lt;/li&gt;
&lt;li&gt;Sensitive credentials are never part of learner memory&lt;/li&gt;
&lt;li&gt;API keys belong in environment variables&lt;/li&gt;
&lt;li&gt;Full private conversations should not be exposed through public analytics&lt;/li&gt;
&lt;li&gt;Specialist handoffs should carry relevant context rather than unnecessary private information**&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The memory implementation explicitly blocks saving when consent is not enabled.&lt;/p&gt;

&lt;p&gt;The principle is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Personalization should not require storing everything about the learner.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Project Structure
&lt;/h2&gt;

&lt;p&gt;The project is organized into separate backend and frontend applications:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;murf-livekit-starter/
│
├── backend/
│   ├── .github/
│   ├── data/
│   │   └── german_exercises.json
│   │
│   ├── src/
│   │   ├── agent.py
│   │   ├── analytics.py
│   │   ├── dashboard.py
│   │   ├── migrate.py
│   │   └── ...
│   │
│   ├── tests/
│   │   ├── test_agent.py
│   │   ├── test_practice.py
│   │   ├── test_analytics.py
│   │   └── test_day9_handoff.py
│   │
│   ├── .env.example
│   ├── Dockerfile
│   ├── pyproject.toml
│   ├── railway.toml
│   └── README.md
│
├── frontend/
│   ├── app/
│   ├── components/
│   ├── fonts/
│   ├── hooks/
│   ├── lib/
│   ├── public/
│   ├── styles/
│   ├── .env.example
│   ├── next.config...
│   └── package.json
│
└── README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to Run the Project
&lt;/h2&gt;

&lt;p&gt;Clone the repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/riteshpatil9686-lgtm/murf-livekit-starter.git
cd murf-livekit-starter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Backend&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd backend
uv sync
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Frontend&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd frontend
pnpm install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Environment variables&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create your local environment files and &lt;strong&gt;never commit them&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example:&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=your_livekit_url
LIVEKIT_API_KEY=your_livekit_api_key
LIVEKIT_API_SECRET=your_livekit_api_secret


MURF_API_KEY=your_murf_api_key
DEEPGRAM_API_KEY=your_deepgram_api_key
GOOGLE_API_KEY=your_google_api_key


DATABASE_URL=your_postgresql_database_url
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If outbound calling is enabled, additional SIP configuration is required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Never publish&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API keys&lt;/li&gt;
&lt;li&gt;API secrets&lt;/li&gt;
&lt;li&gt;Database credentials&lt;/li&gt;
&lt;li&gt;SMTP credentials&lt;/li&gt;
&lt;li&gt;SIP credentials&lt;/li&gt;
&lt;li&gt;Private learner information
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#Run the backend
cd backend
uv run python src/agent.py dev

#Run the frontend
cd frontend
pnpm dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://localhost:3000&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Allow microphone access and start talking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the Analytics Dashboard
&lt;/h2&gt;

&lt;p&gt;The Day 8 dashboard runs separately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd backend
python src/dashboard.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://localhost:8888&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The dashboard reads its numbers from PostgreSQL rather than hardcoding them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the Agent
&lt;/h2&gt;

&lt;p&gt;I tested the system across several different paths instead of testing only the happy path.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;German practice
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Give me a German exercise."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Returning learner
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Do you remember what we practiced last time?"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Interview handoff
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"I have a German job interview tomorrow."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Human escalation
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"I need help from a human teacher."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Code-mixed conversation
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Mujhe German mein practice karni hai."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Outbound practice&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Trigger the outbound practice flow and verify that DeutschMate introduces itself before beginning the lesson.&lt;/p&gt;

&lt;p&gt;The project also includes automated tests covering the agent and the newer analytics and handoff functionality.&lt;/p&gt;

&lt;p&gt;The latest backend test run completed with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;============================= test session starts =============================


collected 14 items


tests/test_agent.py ...
tests/test_analytics.py .
tests/test_day9_handoff.py ...
tests/test_escalation.py .......


============================== 14 passed ==============================


14 passed, 7 warnings

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The warnings were dependency deprecation warnings rather than test failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hardest Part: Making Everything Work Together
&lt;/h2&gt;

&lt;p&gt;The hardest part of this project wasn't getting an LLM to answer a question.&lt;/p&gt;

&lt;p&gt;It was getting all the moving pieces to work together.&lt;/p&gt;

&lt;p&gt;The system combines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Voice
  +
Real-Time Transport
  +
Speech-to-Text
  +
LLM
  +
Text-to-Speech
  +
Tools
  +
PostgreSQL
  +
Memory
  +
SIP
  +
Analytics
  +
Multi-Agent Handoff
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And each layer can introduce a completely different kind of problem.&lt;/p&gt;

&lt;p&gt;For example, the outbound calling implementation required dealing with LiveKit dispatch, SIP participant creation, SIP URI handling, learner metadata, memory loading, and the outbound-specific conversation flow.&lt;/p&gt;

&lt;p&gt;Another challenge was analytics during asynchronous session shutdown.&lt;/p&gt;

&lt;p&gt;The first implementation could encounter a PostgreSQL connection error while LiveKit was shutting down. Fixing that required understanding that the database operation needed its own connection lifecycle rather than simply changing the SQL query.&lt;/p&gt;

&lt;p&gt;That was probably one of my biggest lessons:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Voice AI isn't only an AI problem.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's real-time systems, networking, databases, asynchronous Python, telephony, frontend state, and AI orchestration — all working together.&lt;/p&gt;

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

&lt;p&gt;Before this challenge, I thought about a voice agent roughly like this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speech → AI → Speech&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After ten days, the architecture looks much more like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Voice
  +
Real-Time Transport
  +
Speech-to-Text
  +
Reasoning
  +
Tools
  +
Memory
  +
Telephony
  +
Analytics
  +
Human Escalation
  +
Specialist Agents

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the most important lesson wasn't about making an AI sound human.&lt;/p&gt;

&lt;p&gt;It was about building a system that knows:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;when to speak, when to use a tool, when to remember, when to transfer, when to ask for permission, and when to let another agent handle the task.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Build Next
&lt;/h2&gt;

&lt;p&gt;DeutschMate is still a starting point.&lt;/p&gt;

&lt;p&gt;The next things I'd like to improve are:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. More German exercises **— expand the exercise library across A1–C1 levels&lt;br&gt;
**2. Smarter exercise evaluation&lt;/strong&gt; — move beyond purely LLM-based evaluation toward more structured scoring&lt;br&gt;
&lt;strong&gt;3. Better learner progress tracking&lt;/strong&gt; — track improvement across grammar, vocabulary, speaking, and interview skills&lt;br&gt;
&lt;strong&gt;4. More Indian languages&lt;/strong&gt; — make code-mixed interaction even more natural&lt;br&gt;
&lt;strong&gt;5. Production telephony&lt;/strong&gt; — expand beyond development SIP testing&lt;br&gt;
&lt;strong&gt;6. Deeper analytics&lt;/strong&gt; — latency, tool success rate, handoff success, completion rate, and failure reasons&lt;br&gt;
&lt;strong&gt;7. Richer interview coaching&lt;/strong&gt; — role-specific interview questions, feedback, and structured mock interviews&lt;br&gt;
&lt;strong&gt;8. Stronger privacy architecture&lt;/strong&gt; — authentication, access controls, retention policies, and better separation of learner data&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Ten days ago, the goal was simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build a voice agent.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Today, DeutschMate can &lt;strong&gt;listen, understand, practice German with the learner, remember consented learning context, use tools, make outbound practice calls, track call outcomes, escalate to human help, and hand conversations to a specialized German Job Interview Coach.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The biggest shift in my thinking was realizing that a voice agent isn't just:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"ChatGPT, but with a voice."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It is a complete system.&lt;/p&gt;

&lt;p&gt;The voice is only the interface.&lt;/p&gt;

&lt;p&gt;Behind it are:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;real-time communication, reasoning, tools, memory, databases, telephony, safety boundaries, analytics, and specialized agents.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And for language learning, voice makes the experience particularly interesting because the learner isn't just asking an AI questions.&lt;/p&gt;

&lt;p&gt;They are actually &lt;strong&gt;speaking the language they are trying to learn.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's what I wanted DeutschMate to become:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A German tutor you can simply talk to.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Project &amp;amp; Resources
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Source code:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://github.com/riteshpatil9686-lgtm/murf-livekit-starter" rel="noopener noreferrer"&gt;DeutschMate — GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Challenge:&lt;/strong&gt;&lt;br&gt;
10 Days of Voice Agents — VoiceForBharat Edition&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Built with:&lt;/strong&gt;&lt;br&gt;
Murf Falcon + LiveKit + Deepgram + Google Gemini + PostgreSQL&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>productivity</category>
      <category>learning</category>
    </item>
  </channel>
</rss>
