
Nipun GoelA voice-first financial assistant for Bharat, built during the 10 Days of Voice Agents —...
A voice-first financial assistant for Bharat, built during the 10 Days of Voice Agents — VoiceForBharat Edition.
Financial services and government schemes can be difficult to navigate. Users may need to understand eligibility criteria, required documents, application information, deadlines, or what to do when something goes wrong.
For many users, especially users who are more comfortable speaking in Hindi, English, or Hinglish than typing long queries, a voice interface can provide a more natural way to access assistance.
This led to the idea behind FinSaathi.
Instead of making the user navigate through multiple forms and pages, FinSaathi allows the user to simply describe what they need.
"PMJJBY ke liye main eligible hoon?"
Or:
"Mere account se ek unauthorized transaction hua hai."
The goal is not to replace banks, government portals, or human support teams. The goal is to create a conversational first layer that can understand intent, provide appropriate assistance, perform supported actions, and know when the conversation should be handed to a human or specialist.
A text interface gives users time to edit and rewrite their questions. A voice conversation is different: the user explains their situation naturally and the system has to understand the intent in real time.
The interaction becomes:
Speak → Understand → Decide → Act → Respond
That simple loop became the foundation of FinSaathi.
FinSaathi is designed as an assistance system, not as a bank employee, financial regulator, or replacement for human support.
Financial conversations require strong safety boundaries.
FinSaathi explicitly avoids requesting sensitive authentication information such as:
When the agent cannot perform a requested action directly, it should communicate that limitation rather than inventing an action.
| Day | Focus | What was added |
|---|---|---|
| Day 1 | Core Voice Agent | Initial real-time conversational voice experience. |
| Day 2 | Personality & Guardrails | Defined objectives, behaviour, and safety boundaries. |
| Day 3 | Indian Conversations | Hindi, English, and Hinglish interaction patterns. |
| Day 4 | Memory | Persistent user information using the database. |
| Day 5 | Tools & Eligibility | Application-specific tools and scheme eligibility workflows. |
| Day 6 | Outbound Calling | Agent-initiated phone conversations through the calling layer. |
| Day 7 | Human Escalation | Human-support requests with generated reference IDs. |
| Day 8 | Call Analytics | Real call outcome tracking and dashboard metrics. |
| Day 9 | Specialist Handoff | Routing government-scheme workflows to a specialist agent. |
| Day 10 | Documentation | Architecture, implementation details, challenges, and lessons learned. |
The important part was that the features were not built as isolated demos. Each new capability had to work with the voice experience that already existed.
FinSaathi is built as a full-stack voice-agent system.
At a high level, the user speaks through the real-time communication layer. The speech is processed by the voice pipeline, the AI agent determines the next action, and the response is converted back into speech using Murf Falcon.
The agent can also access application-specific tools, memory, human escalation, analytics, and specialist workflows.
| Layer | Technology |
|---|---|
| Frontend | Next.js |
| AI Agent | LiveKit Agents |
| Real-Time Transport | LiveKit |
| Text-to-Speech | Murf Falcon |
| Backend | Python |
| API Layer | FastAPI |
| Database | SQLite |
| Phone Calling | SIP / LiveKit |
The first version of FinSaathi was intentionally kept simple.
Instead of trying to build every feature at once, I started with the core voice loop and gradually connected the additional services around it.
The basic voice-agent pipeline can be thought of as four major layers:
LiveKit provides the real-time communication layer.
The simplified flow is:
User speaks → LiveKit → Speech Recognition → AI Agent → Response → Murf Falcon → User
This makes the interaction feel much closer to a normal conversation.
The AI agent is the decision-making layer of FinSaathi. Its responsibility is to understand the user's intent and determine whether the request can be answered directly or whether a tool or specialist is required.
For example:
User: "PMJJBY ke liye main eligible hoon?"
The agent can identify this as an eligibility request, collect information, invoke the eligibility workflow, store the result, and explain the outcome.
The overall loop is:
Understand → Decide → Act → Respond
An LLM can generate useful responses, but it cannot reliably perform application-specific operations without access to tools.
For FinSaathi, tools were introduced for:
A simplified tool pattern looks like this:
@function_tool
async def check_eligibility(
scheme: str,
user_information: dict
):
result = perform_eligibility_check(
scheme,
user_information
)
return result
The model decides when the tool is needed, while the actual business logic remains inside the tool.
A voice assistant becomes more useful when it can remember relevant information across conversations.
FinSaathi uses SQLite to persist information required by the application:
The architecture is:
Agent → database.py → SQLite
while dashboard requests follow:
Frontend → FastAPI → database.py → SQLite
This kept database operations in one place instead of scattering SQL logic across components.
def get_escalations():
connection = sqlite3.connect(DB_PATH)
connection.row_factory = sqlite3.Row
rows = connection.execute(
"SELECT * FROM escalations ORDER BY created_at DESC"
).fetchall()
connection.close()
return [dict(row) for row in rows]
Not every financial situation should be handled entirely by an AI agent.
For a potentially unauthorized transaction, FinSaathi can provide safe guidance and ask whether the user wants human support.
The workflow is:
User reports issue → Safe guidance → User gives consent → Escalation created → Reference ID generated → Human Support Dashboard
A unique reference ID is generated for every escalation.
For example:
FS-A5323F
A stored escalation can contain:
@function_tool
async def create_escalation(
reason: str,
summary: str,
urgency: str = "normal",
preferred_followup: str = "phone"
):
request_id = create_escalation_record(
reason=reason,
summary=summary,
urgency=urgency,
preferred_followup=preferred_followup
)
return request_id
For FinSaathi, a successful call means that the intended financial workflow was completed or that an appropriate resolution or escalation was reached.
A failed call does not necessarily mean that the software crashed. A user may leave before completing an eligibility workflow.
The dashboard records:
The architecture is:
Actual Call → Outcome → Database → FastAPI → Analytics Dashboard
The values come from actual browser or SIP interactions rather than hardcoded demo numbers.
record_call_outcome(
call_id=call_id,
outcome="SUCCESS"
)
FinSaathi was extended beyond incoming browser conversations.
The outbound workflow allows the system to initiate supported phone conversations.
Application Data → Outbound Logic → SIP / LiveKit → Voice Agent → User
One of the challenging parts was debugging the call lifecycle. There were situations where the AI greeting started but the call terminated before the conversation could continue.
The investigation required checking:
This taught me that voice debugging requires looking at the complete lifecycle rather than only the AI response.
As FinSaathi grew, putting every workflow into one large agent became less attractive.
The solution was to introduce specialist agents.
For government-scheme conversations:
Main Agent → Government Scheme Specialist → Eligibility / Scheme Workflow → Result
The specialist can focus on:
A handoff should not feel like starting a completely new conversation. The specialist needs enough context from the previous conversation.
@function_tool
async def transfer_to_scheme_specialist():
copied_context = self.chat_ctx.copy(
exclude_instructions=True
)
specialist = GovernmentSchemeSpecialist(
chat_ctx=copied_context
)
return (
specialist,
"Connecting you with our government scheme specialist."
)
@function_tool
async def check_eligibility(
scheme: str,
user_information: dict
):
result = perform_eligibility_check(
scheme,
user_information
)
return result
This keeps the eligibility logic deterministic and testable while allowing the agent to decide when the workflow is needed.
@function_tool
async def create_escalation(
reason: str,
summary: str,
urgency: str,
preferred_followup: str
):
request_id = create_escalation_record(
reason=reason,
summary=summary,
urgency=urgency,
preferred_followup=preferred_followup
)
return request_id
record_call_outcome(
call_id=call_id,
outcome="SUCCESS"
)
@function_tool
async def transfer_to_scheme_specialist():
copied_context = self.chat_ctx.copy(
exclude_instructions=True
)
return GovernmentSchemeSpecialist(
chat_ctx=copied_context
)
An outbound call could start with an AI greeting and then terminate unexpectedly.
The important realization was that the generated response was not necessarily the problem.
I had to investigate the worker lifecycle, SIP configuration, network connectivity, room state, agent state, and call termination events.
Lesson: Voice debugging requires observing the entire real-time lifecycle.
While building the dashboard API, I encountered a missing FastAPI dependency and later an ASGI loading error where Uvicorn could not find the expected app object.
The debugging process was:
Install dependency → verify import → verify module → verify app object → start Uvicorn → test endpoint
Lesson: Verify each backend layer independently before connecting it to the frontend.
The database module does not need to run as a separate process.
The agent can directly use:
agent.py → database.py → SQLite
The dashboard uses:
Frontend → FastAPI → database.py → SQLite
Understanding this distinction made the architecture much clearer.
A specialist handoff is not simply a function call. The new agent needs enough context to understand the user's previous conversation while keeping its own instructions separate.
Lesson: Context preservation and instruction isolation are both important in multi-agent systems.
Choice: Keep database operations inside database.py.
Benefit: Agent tools and API routes can reuse the same data-access functions.
Choice: Use FastAPI as the API layer.
Benefit: The frontend receives structured data while the database remains behind the backend boundary.
Choice: Use function tools for eligibility, escalation, analytics, and other supported workflows.
Benefit: The agent decides when an action is required while the application remains responsible for performing it.
Choice: Route domain-specific conversations to specialist agents.
Benefit: Smaller responsibilities, clearer instructions, and easier future expansion.
You will need:
uv.git clone https://github.com/NipunGoel02/murf-ai-project
cd murf-ai-project
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
Never commit real credentials.
cd backend
uv sync
Use the LiveKit Agents startup command defined in the repository.
cd frontend
npm install
pnpm dev
Use the package manager and scripts defined by the repository if they differ.
uv run uvicorn src.escalation_api:app --reload --port 8000
Open the frontend and test:
A public repository should never contain:
Use environment variables for secrets.
Before publishing the repository, inspect the files and Git history to ensure that secrets were not accidentally committed.
The public analytics dashboard should also avoid exposing sensitive caller information.
Check:
app Is Missing
Verify that the module exposes:
from fastapi import FastAPI
app = FastAPI()
Then start it using the correct module path:
uv run uvicorn src.escalation_api:app --reload --port 8000
Debug the complete path:
Agent → Database → API → Browser
Verify the database record first, then the API response, and finally the frontend request.
Make sure the handoff transfers relevant conversation context while keeping specialist instructions separate.
Future specialists could cover:
The analytics system could track:
The SIP layer could be extended with production carrier integrations and more robust call-state handling.
The system could be extended to support more Indian languages and regional speech patterns.
Human agents could receive privacy-safe context so users do not have to repeat their entire issue.
At first, a voice agent looked like:
Speech → AI → Speech
After these 10 days, I realized that a useful voice agent needs much more:
Voice + LLM + Memory + Tools + Safety + Database + Real-Time Transport + Human Escalation + Analytics + Specialist Handoffs
The biggest lesson was:
A useful voice agent is not just an AI that can talk. It is a system that can understand, act, remember, measure its performance, and know when a human or specialist should take over.
FinSaathi started as a simple voice conversation and gradually became a complete financial assistance workflow.
The most valuable part of the challenge was learning how these pieces fit together into one system.
GitHub Repository: https://github.com/NipunGoel02/murf-ai-project
Built using Murf Falcon, the fastest TTS API.
Building FinSaathi during the 10 Days of Voice Agents — VoiceForBharat Edition was an opportunity to work with voice AI beyond simple prompt engineering.
I worked with real-time communication, speech, LLM orchestration, tools, persistent data, outbound calling, human escalation, analytics, and specialist-agent routing.
The biggest takeaway was simple:
The hardest part of a voice agent is not making it speak. The hardest part is making everything around the conversation reliable.
FinSaathi is still a prototype, but it represents the kind of voice-first system I want to continue building: conversational, useful, safe, and connected to real-world workflows.