The dream of a truly conversational financial advisor, one that understands your spoken Nepali queries and provides real-time market insights, has long been just that—a dream for many in Nepal. Traditional automated systems often stumble with local languages, struggle to integrate live data, or demand bespoke, costly infrastructure. I recently set out to build a proof-of-concept for such an advisor, combining the power of open-source speech-to-text, live financial APIs, Anthropic's Claude, and advanced voice synthesis from VoiceStudio. This post is for developers eager to move beyond basic chatbot examples and construct a reproducible, production-ready pattern for a voice-enabled assistant. You'll learn how to stitch these complex components together, focusing on the practical challenges of integrating real-time data and delivering natural-sounding Nepali voice responses, ultimately proving that building a sophisticated, multilingual voice interface is not just feasible, but within reach.
Key Takeaways
- Orchestrating a multi-component voice AI system involves careful integration of STT, LLM, data APIs, and TTS services, each with its own latency and error characteristics.
- Effective prompt engineering is crucial for guiding LLMs to provide contextually relevant, multilingual financial advice.
- Leveraging streaming APIs for both speech-to-text (e.g., live microphone input) and text-to-speech (e.g., VoiceStudio) is key to achieving low-latency, real-time conversational experiences.
- Robust error handling for external API calls and audio processing is non-negotiable for production-grade voice applications.
- While "voice cloning" can refer to custom voice models, for practical applications, selecting a high-quality, pre-configured voice ID from a TTS service is often sufficient to establish a consistent persona.
The Problem
Our goal was to bridge a significant gap in the Nepali financial landscape: the absence of an automated, accessible tool that could understand spoken Nepali, query live financial data, and respond intelligently in natural-sounding Nepali voice. Existing solutions were either text-based, limited to English, or required extensive custom development for local language support. We needed a pipeline that could take a user's voice input, convert it to text, use an LLM to interpret the query and fetch relevant data, then synthesize a spoken Nepali answer back to the user, all with minimal perceived delay. The challenge wasn't just individual components, but their seamless integration into a cohesive, responsive system.
Data and Sources
For this project, we relied on a few critical external services and open-source models:
- Coindesk Bitcoin Price API: Provides real-time and historical Bitcoin price data. We specifically used the current price endpoint.
- VoiceStudio API Documentation: A text-to-speech service offering high-quality, natural-sounding voices, including streaming capabilities.
- OpenAI Whisper Model: A robust open-source speech-to-text model capable of handling multiple languages, including Nepali, when run locally.
- Claude Code SDK (Anthropic): Our chosen large language model for understanding user intent, reasoning, and generating appropriate responses.
Data accessed on 2024-07-28.
Step 1 — Voice Configuration Setup
Before we can synthesize any speech, we need to configure our text-to-speech service. While the term "voice cloning setup" might suggest training a new voice from scratch, in a practical scenario, it often means selecting and configuring a high-quality, pre-existing voice model provided by the TTS service that aligns with our desired persona. For VoiceStudio, this involves obtaining an API key and identifying a suitable voice ID. For a Nepali financial advisor, we'd ideally pick a voice that sounds natural and authoritative in Nepali. Since custom voice cloning has a separate, often manual, training phase outside the scope of a real-time script, we'll assume we have a `VOICESTUDIO_VOICE_ID` ready, representing a high-quality voice capable of speaking Nepali.
Here's how we'd initialize our VoiceStudio client:
import os
import requests
import anthropic
import whisper
import numpy as np
import soundfile as sf
import io
import wave
from dotenv import load_dotenv
load_dotenv() # Load environment variables from .env file
# API Keys and IDs
ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY")
VOICESTUDIO_API_KEY = os.getenv("VOICESTUDIO_API_KEY")
VOICESTUDIO_VOICE_ID = os.getenv("VOICESTUDIO_VOICE_ID", "YOUR_NEPALI_VOICE_ID") # Replace with a suitable Nepali voice ID
if not ANTHROPIC_API_KEY or not VOICESTUDIO_API_KEY:
raise ValueError("ANTHROPIC_API_KEY and VOICESTUDIO_API_KEY must be set in .env")
# Initialize clients
claude_client = anthropic.Anthropic(api_key=ANTHROPIC_API_KEY)
# VoiceStudio API endpoint (adjust if different for your region/plan)
VOICESTUDIO_TTS_URL = "https://api.voice.studio/v1/tts"
This snippet sets up our environment variables and client initializations. The `VOICESTUDIO_VOICE_ID` is a placeholder; in a real application, you'd select one from VoiceStudio's available voices that supports Nepali or has the desired characteristics.
Step 2 — Real‑Time Speech‑to‑Text
The first interaction point for our advisor is understanding the user's spoken query. We chose OpenAI's Whisper model for its excellent multilingual capabilities, including Nepali, and its ability to run locally, avoiding external STT API costs. While a truly "real-time" system would process audio chunks from a microphone stream, for reproducibility in a script, we'll simulate this by processing a pre-recorded WAV file. The core problem here is converting raw audio into accurate text.
def transcribe_audio(audio_path: str) -> str:
"""
Transcribes an audio file using the Whisper model.
"""
try:
model = whisper.load_model("base") # Or "small", "medium" for better accuracy
result = model.transcribe(audio_path, fp16=False, language="ne") # Force Nepali language
return result["text"]
except Exception as e:
print(f"Error during audio transcription: {e}")
raise
# Example usage (for testing, will be integrated into main script)
# user_audio_file = "user_query_nepali.wav"
# transcribed_text = transcribe_audio(user_audio_file)
# print(f"Transcribed: {transcribed_text}")
The `transcribe_audio` function loads a Whisper model (you can choose larger models like "small" or "medium" for better accuracy, but "base" is good for demonstration) and transcribes the input audio file. Crucially, we specify `language="ne"` to guide Whisper towards Nepali, improving accuracy for our specific use case. For a live system, you'd feed audio chunks from a microphone to Whisper's streaming API or process them in batches.
Step 3 — Fetching Live Financial Data
A financial advisor is only as good as its data. Our next step is to integrate live financial market information. For this example, we're focusing on Bitcoin prices using the Coindesk API, which is straightforward and publicly accessible. The challenge is to reliably fetch this data and handle potential API failures.
def get_bitcoin_price() -> float | None:
"""
Fetches the current Bitcoin price from Coindesk API.
"""
try:
response = requests.get("https://api.coindesk.com/v1/bpi/currentprice.json")
response.raise_for_status() # Raise an exception for HTTP errors
data = response.json()
price = data["bpi"]["USD"]["rate_float"]
return price
except requests.exceptions.RequestException as e:
print(f"Error fetching Bitcoin price from Coindesk: {e}")
return None
except KeyError as e:
print(f"Error parsing Coindesk API response: {e}")
return None
# Example usage (for testing)
# current_btc_price = get_bitcoin_price()
# if current_btc_price:
# print(f"Current Bitcoin Price: ${current_btc_price:,.2f} USD")
The `get_bitcoin_price` function makes a simple HTTP GET request to the Coindesk API. We include `response.raise_for_status()` for robust error handling, immediately catching any non-2xx HTTP responses. We also added a `KeyError` check in case the API response structure changes, preventing unexpected crashes. This ensures our data fetching is resilient.
Step 4 — Prompt Engineering for Nepali Response
Now that we have the user's query and live data, we need our LLM to process this information and formulate an intelligent, contextually relevant response in Nepali. This is where prompt engineering becomes critical. We need to instruct Claude not only to act as a financial advisor but also to incorporate the fetched data and respond specifically in Nepali.
def get_claude_response(user_query: str, btc_price: float | None) -> str:
"""
Gets a financial advice response from Claude, incorporating Bitcoin price.
"""
btc_info = f"The current Bitcoin price is ${btc_price:,.2f} USD." if btc_price else "I could not retrieve the current Bitcoin price."
prompt = f"""You are a helpful financial advisor in Nepal. Your task is to answer user questions related to finance, specifically incorporating real-time data when relevant.
Always respond in Nepali. Be concise and professional.
Here's some current information:
{btc_info}
User's query: "{user_query}"
Your Nepali response:"""
try:
response = claude_client.messages.create(
model="claude-3-opus-20240229", # Or another suitable Claude model
max_tokens=200,
messages=[
{"role": "user", "content": prompt}
]
)
return response.content[0].text
except anthropic.APIError as e:
print(f"Error calling Claude API: {e}")
raise
Our `get_claude_response` function constructs a detailed prompt. It sets Claude's persona, specifies the language of the response ("Always respond in Nepali"), provides the latest Bitcoin price, and then includes the user's transcribed query. This structured approach helps Claude understand its role and generate the desired output. We've chosen `claude-3-opus-20240229` for its advanced reasoning, but you could opt for other Claude models based on your latency and cost requirements.
Step 5 — Claude Response Handling
Once Claude provides its Nepali text response, the next step is to prepare it for voice synthesis. This typically involves ensuring the text is clean, formatted correctly, and ready for the TTS engine. There isn't much complex "handling" beyond ensuring we receive valid text from Claude, but it's a critical bridge between the LLM and TTS layers.
# The `get_claude_response` function already returns the raw text from Claude.
# We'll use this directly in the next step.
# For more complex scenarios, you might add text normalization or SSML processing here.
# For example, ensuring numbers are written out, or abbreviations are expanded for better TTS pronunciation.
In this architecture, Claude's direct text output is generally clean enough for VoiceStudio. However, in more advanced scenarios, especially for highly specific financial terms or numbers, you might want to implement a text normalization step here, perhaps using a custom rule-based system or another small LLM to convert numerical values into their Nepali word equivalents (e.g., "१० लाख" instead of "1,000,000") for better pronunciation by the TTS engine. For this demonstration, we'll keep it simple and pass Claude's raw text.
Step 6 — VoiceStudio TTS Streaming
The final, and perhaps most impactful, step is converting Claude's Nepali text response back into natural-sounding speech. VoiceStudio excels at this, offering high-quality voices and, critically for a real-time system, streaming capabilities. Streaming allows us to start playing audio as soon as the first few bytes are generated, rather than waiting for the entire audio file, significantly reducing perceived latency.
def synthesize_speech_stream(text: str, output_filepath: str):
"""
Synthesizes speech from text using VoiceStudio and saves it to a WAV file.
Demonstrates streaming by writing chunks as they arrive.
"""
headers = {
"Authorization": f"Bearer {VOICESTUDIO_API_KEY}",
"Content-Type": "application/json"
}
data = {
"text": text,
"voice_id": VOICESTUDIO_VOICE_ID,
"output_format": "wav", # Request WAV for easy playback/saving
"stream": True # Enable streaming
}
try:
with requests.post(VOICESTUDIO_TTS_URL, headers=headers, json=data, stream=True) as response:
response.raise_for_status()
# Save the streamed audio to a WAV file
with wave.open(output_filepath, 'wb') as wf:
# VoiceStudio typically sends WAV header first, then audio data
# We need to correctly handle this for a proper WAV file.
# For simplicity, we'll assume a basic WAV header will be written by `soundfile`
# or that `requests.iter_content` handles the full stream.
# A more robust implementation might parse the first chunk for header info.
# A common approach for streaming TTS is to collect chunks and then write.
# For true streaming playback, you'd use an audio library like PyAudio.
audio_chunks = []
for chunk in response.iter_content(chunk_size=4096):
if chunk:
audio_chunks.append(chunk)
# Combine chunks and write to a temporary file-like object for soundfile
audio_bytes = b"".join(audio_chunks)
if not audio_bytes:
raise ValueError("No audio data received from VoiceStudio.")
# VoiceStudio might return raw PCM or a full WAV.
# Assuming it returns a complete WAV file.
with open(output_filepath, 'wb') as f:
f.write(audio_bytes)
print(f"Speech synthesized and saved to {output_filepath}")
except requests.exceptions.RequestException as e:
print(f"Error calling VoiceStudio TTS API: {e}")
raise
except ValueError as e:
print(f"Error processing VoiceStudio response: {e}")
raise
The `synthesize_speech_stream` function sends Claude's text to the VoiceStudio API. We set `stream: True` in the request payload and use `response.iter_content()` to process the audio data in chunks. For this script, we collect all chunks and then save them to a WAV file. In a real-time application, you would feed these chunks directly to an audio playback library (like `pyaudio`) to achieve instant, streaming playback, creating a highly responsive user experience. The error handling ensures we catch network issues or malformed responses from VoiceStudio.
Step 7 — Production Enhancements
While the core pipeline is functional, a production-ready system demands more. Here are some critical enhancements:
- Asynchronous Processing: Each step (STT, API calls, TTS) can be I/O bound. Using `asyncio` and `aiohttp` would allow these operations to run concurrently, significantly reducing overall latency, especially for real-time interactions. For example, fetching Bitcoin data could happen while Whisper is still transcribing. (Unleashing 10K RPM: Building a Resilient, High-Throughput Python CLI with Asyncio and Aiohttp covers this in detail).
- Microphone Input Integration: Replace the static WAV file input with real-time audio capture using libraries like `pyaudio` or `sounddevice`. This would involve chunking the live audio and feeding it to Whisper.
- Context Management: For multi-turn conversations, the LLM needs memory. Implementing a conversation history and passing it to Claude would allow the advisor to remember previous questions and answers.
- Caching: Frequently requested data (like current Bitcoin price, if updated slowly) could be cached to reduce API calls and improve response times.
- Observability: Logging, monitoring, and tracing are essential to understand performance bottlenecks and debug issues in a distributed system.
- Error Recovery & Retries: Implement exponential backoff for API calls to handle transient network issues gracefully.
- Security: Protect API keys using secure environment variables or secrets management services, and ensure secure communication (HTTPS is handled by `requests`).
These enhancements transform a proof-of-concept into a robust, reliable, and scalable service.
Complete Script
The full runnable script combining all steps:
#!/usr/bin/env python3
import os
import requests
import anthropic
import whisper
import numpy as np
import soundfile as sf
import io
import wave
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
# --- Configuration ---
ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY")
VOICESTUDIO_API_KEY = os.getenv("VOICESTUDIO_API_KEY")
VOICESTUDIO_VOICE_ID = os.getenv("VOICESTUDIO_VOICE_ID", "YOUR_NEPALI_VOICE_ID") # Replace with a suitable Nepali voice ID from VoiceStudio
# Ensure API keys are set
if not ANTHROPIC_API_KEY or not VOICESTUDIO_API_KEY:
raise ValueError("ANTHROPIC_API_KEY and VOICESTUDIO_API_KEY must be set in .env file.")
# Initialize clients
claude_client = anthropic.Anthropic(api_key=ANTHROPIC_API_KEY)
VOICESTUDIO_TTS_URL = "https://api.voice.studio/v1/tts"
# --- Helper Functions ---
def transcribe_audio(audio_path: str) -> str:
"""
Transcribes an audio file using the Whisper model.
"""
try:
print(f"Transcribing audio from: {audio_path}...")
model = whisper.load_model("base") # Or "small", "medium" for better accuracy
result = model.transcribe(audio_path, fp16=False, language="ne") # Force Nepali language
print("Transcription complete.")
return result["text"]
except FileNotFoundError:
print(f"Error: Audio file not found at {audio_path}")
raise
except Exception as e:
print(f"Error during audio transcription: {e}")
raise
def get_bitcoin_price() -> float | None:
"""
Fetches the current Bitcoin price from Coindesk API.
"""
print("Fetching current Bitcoin price...")
try:
response = requests.get("https://api.coindesk.com/v1/bpi/currentprice.json")
response.raise_for_status() # Raise an exception for HTTP errors
data = response.json()
price = data["bpi"]["USD"]["rate_float"]
print(f"Bitcoin price fetched: ${price:,.2f} USD")
return price
except requests.exceptions.RequestException as e:
print(f"Error fetching Bitcoin price from Coindesk: {e}")
return None
except KeyError as e:
print(f"Error parsing Coindesk API response: {e}. Response might have changed format.")
return None
def get_claude_response(user_query: str, btc_price: float | None) -> str:
"""
Gets a financial advice response from Claude, incorporating Bitcoin price.
Always responds in Nepali.
"""
print("Generating Claude response...")
btc_info = f"The current Bitcoin price is ${btc_price:,.2f} USD." if btc_price is not None else "I could not retrieve the current Bitcoin price."
prompt = f"""You are a helpful financial advisor in Nepal. Your task is to answer user questions related to finance, specifically incorporating real-time data when relevant.
Always respond concisely and professionally in Nepali.
Here's some current information:
{btc_info}
User's query: "{user_query}"
Your Nepali response:"""
try:
response = claude_client.messages.create(
model="claude-3-opus-20240229", # Or another suitable Claude model
max_tokens=200,
messages=[
{"role": "user", "content": prompt}
]
)
claude_text = response.content[0].text
print(f"Claude responded: {claude_text}")
return claude_text
except anthropic.APIError as e:
print(f"Error calling Claude API: {e}")
raise
def synthesize_speech_stream(text: str, output_filepath: str):
"""
Synthesizes speech from text using VoiceStudio and saves it to a WAV file.
"""
print(f"Synthesizing speech with VoiceStudio to {output_filepath}...")
headers = {
"Authorization": f"Bearer {VOICESTUDIO_API_KEY}",
"Content-Type": "application/json"
}
data = {
"text": text,
"voice_id": VOICESTUDIO_VOICE_ID,
"output_format": "wav",
"stream": True
}
try:
with requests.post(VOICESTUDIO_TTS_URL, headers=headers, json=data, stream=True) as response:
response.raise_for_status()
audio_bytes = b"".join(response.iter_content(chunk_size=4096))
if not audio_bytes:
raise ValueError("No audio data received from VoiceStudio.")
with open(output_filepath, 'wb') as f:
f.write(audio_bytes)
print(f"Speech synthesis complete. Audio saved to {output_filepath}")
except requests.exceptions.RequestException as e:
print(f"Error calling VoiceStudio TTS API: {e}. Status code: {e.response.status_code if e.response else 'N/A'}")
print(f"Response content: {e.response.text if e.response else 'N/A'}")
raise
except ValueError as e:
print(f"Error processing VoiceStudio response: {e}")
raise
# --- Main Logic ---
def main():
# --- Setup for demonstration ---
# For a real system, this would come from a live microphone.
# Create a dummy WAV file for testing if it doesn't exist.
input_audio_path = "user_query_nepali.wav"
output_audio_path = "advisor_response_nepali.wav"
if not os.path.exists(input_audio_path):
print(f"Creating a dummy audio file '{input_audio_path}' for demonstration.")
# Create a simple sine wave audio file (replace with actual Nepali speech for real testing)
samplerate = 16000 # Hz
duration = 3 # seconds
frequency = 440 # Hz (A4 note)
t = np.linspace(0., duration, int(samplerate * duration), endpoint=False)
amplitude = 0.5
data = amplitude * np.sin(2. * np.pi * frequency * t)
sf.write(input_audio_path, data, samplerate)
print(f"Please replace '{input_audio_path}' with an actual Nepali voice query for meaningful results, e.g., 'आजको बिटक्वाइनको मूल्य कति छ?'")
# Exit if a meaningful audio file is not provided, as Whisper won't understand sine wave.
# For a real demo, you'd ensure this is a pre-recorded Nepali query.
return
print("\n--- Starting Nepali Voice Financial Advisor ---")
try:
# 1. Transcribe User's Audio
user_query_text = transcribe_audio(input_audio_path)
print(f"User said: '{user_query_text}'")
# 2. Fetch Live Financial Data
current_btc_price = get_bitcoin_price()
# 3. Get LLM Response
advisor_response_text = get_claude_response(user_query_text, current_btc_price)
print(f"Advisor will say: '{advisor_response_text}'")
# 4. Synthesize Speech Response
synthesize_speech_stream(advisor_response_text, output_audio_path)
print(f"You can now play '{output_audio_path}' to hear the advisor's response.")
except (FileNotFoundError, requests.exceptions.RequestException, anthropic.APIError, ValueError) as e:
print(f"\nAn unrecoverable error occurred during the process: {e}")
except Exception as e:
print(f"\nAn unexpected error occurred: {e}")
if __name__ == "__main__":
main()
Expected Output
To run this script, you'll need to:
- Save the code above as `advisor.py`.
- Create a `.env` file in the same directory with your `ANTHROPIC_API_KEY` and `VOICESTUDIO_API_KEY`. Also, set `VOICESTUDIO_VOICE_ID` to a specific voice ID that supports Nepali.
- Install the necessary libraries: `pip install python-dotenv requests anthropic-sdk openai-whisper soundfile numpy`.
- (Crucially) Create a WAV file named `user_query_nepali.wav` in the same directory. This file should contain a spoken Nepali query, for example, "आजको बिटक्वाइनको मूल्य कति छ?" (What is today's Bitcoin price?). The script will create a dummy sine wave if it doesn't exist, but Whisper won't understand it, leading to poor LLM output.
- Run the script:
python advisor.py
You should see console output similar to this (transcription and LLM response will vary based on your audio input and API responses):
--- Starting Nepali Voice Financial Advisor ---
Transcribing audio from: user_query_nepali.wav...
Transcription complete.
User said: 'आजको बिटक्वाइनको मूल्य कति छ?'
Fetching current Bitcoin price...
Bitcoin price fetched: $60,000.00 USD
Generating Claude response...
Claude responded: 'आजको बिटक्वाइनको मूल्य $60,000.00 USD रहेको छ। कृपया ध्यान दिनुहोस् कि बजार अस्थिर हुन सक्छ।'
Advisor will say: 'आजको बिटक्वाइनको मूल्य $60,000.00 USD रहेको छ। कृपया ध्यान दिनुहोस् कि बजार अस्थिर हुन सक्छ।'
Synthesizing speech with VoiceStudio to advisor_response_nepali.wav...
Speech synthesis complete. Audio saved to advisor_response_nepali.wav
You can now play 'advisor_response_nepali.wav' to hear the advisor's response.
Upon successful execution, a file named `advisor_response_nepali.wav` will be created in your directory, containing the spoken Nepali response from the financial advisor.
Limitations and Tradeoffs
While this architecture is powerful, it comes with inherent limitations and tradeoffs:
- Latency: Each step in the pipeline (STT, API call, LLM, TTS) introduces latency. While streaming helps perceived latency, the cumulative processing time can still be significant for very rapid-fire conversations. Asynchronous execution is key but adds complexity.
- Cost: While Whisper can run locally, Claude and VoiceStudio are paid services. Costs can scale rapidly with usage. Optimizing prompt length, LLM model choice, and TTS voice quality are direct cost levers.
- Accuracy & Hallucination: Whisper's Nepali transcription, while good, isn't perfect, especially with accents or background noise. Claude, like all LLMs, can occasionally hallucinate or provide overly generic advice if the prompt isn't precise enough.
- Real-time Audio Input: The current script uses a file. Implementing true real-time microphone input requires robust audio buffering, chunking, and handling of audio devices, which adds significant complexity and platform-specific considerations.
- Single-Turn vs. Multi-Turn: This example is single-turn. Building a multi-turn conversational agent requires careful state management, context window handling for the LLM, and strategies for when to re-fetch data.
- Data Scope: The financial data is limited to Bitcoin price. Expanding to NEPSE, other global markets, or personal finance would require integrating many more data sources and more sophisticated data parsing.
This approach is a bad fit for scenarios demanding ultra-low latency (sub-200ms end-to-end), entirely offline operation (due to cloud LLM/TTS), or requiring deep, highly personalized financial planning without human oversight.
Frequently Asked Questions
How can I make this truly real-time with microphone input?
You'd replace the `transcribe_audio` function's file input with a stream from a microphone using libraries like `PyAudio` or `Sounddevice`. You would continuously record small chunks of audio, feed them to Whisper's streaming API (if available, or process in small batches), and then process the resulting text. For TTS, you'd feed the VoiceStudio audio chunks directly to `PyAudio` for immediate playback rather than saving to a file.
What if I want to use a different language or a custom voice?
For different languages, ensure Whisper supports it (which it generally does for many), and critically, update your Claude prompt to specify the desired output language. For VoiceStudio, you'd select a `VOICESTUDIO_VOICE_ID` corresponding to a voice that supports your target language. Custom voice cloning often involves a separate training process with recordings of a specific speaker, and then you'd use the resulting custom voice ID.
How do I handle complex financial queries beyond Bitcoin price?
This requires more sophisticated prompt engineering for Claude and integration with more diverse financial APIs. You would need to design tools or functions that Claude can call (using Claude's tool-use capabilities, similar to how we've discussed building autonomous data agents) to fetch NEPSE data, stock prices, economic indicators, etc. Each tool would have its own API call and error handling.
What are the alternatives to VoiceStudio for Nepali TTS?
While VoiceStudio offers high quality, other options exist. Some cloud providers like Google Cloud Text-to-Speech or Amazon Polly offer Nepali voices. For open-source, projects like VITS or Tortoise-TTS can be fine-tuned on Nepali datasets, but they require significant computational resources and expertise to deploy and manage. (We've previously explored KittenTTS for low-latency Nepali voice interfaces, which is another option.)
What I'd Change
If I were to build this into a truly production-grade service, my first priority would be to completely refactor it using an asynchronous framework like FastAPI with `asyncio`. The current synchronous execution, while simple for demonstration, introduces unacceptable latency for a conversational agent. I'd parallelize the STT, data fetching, and even parts of the LLM interaction where possible, leveraging Python's `async/await` patterns. Secondly, I would implement robust context management for multi-turn conversations, probably using a vector database to store conversation history and retrieve relevant past interactions to augment Claude's prompts. This would move the advisor beyond simple Q&A to a more natural, stateful dialogue. Finally, for the STT component, I would invest in fine-tuning Whisper on a custom Nepali financial vocabulary to boost accuracy, especially for specific market terms or company names that might not be in its general training data. This combination of asynchronous design, intelligent context, and domain-specific STT would transform this proof-of-concept into a truly responsive and intelligent Nepali financial advisor.