FinLit_App_V0 / app.py
aniketsirsikar's picture
Update app.py
b644b8f verified
Raw
History Blame
23 kB
"""
Hindi Banking Voice Assistant
CPU Basic safe — uses HF InferenceClient with providers that actually work.
STT : openai/whisper-large-v3 (hf-inference, ASR)
LLM : mistralai/Mistral-7B-Instruct-v0.3 (featherless provider, free tier)
TTS : facebook/mms-tts-hin (hf-inference, Hindi TTS, small model)
"""
import os
import io
import numpy as np
import soundfile as sf
import gradio as gr
from huggingface_hub import InferenceClient
from rag_pipeline import (
normalize_jargon,
translate_to_retrieval_query,
build_rag_prompt,
format_response_for_tts,
get_retriever,
)
# ─────────────────────────────────────────────
# CONFIG
# ─────────────────────────────────────────────
HF_TOKEN = os.environ.get("HF_TOKEN", "")
STT_MODEL = "openai/whisper-large-v3"
LLM_MODEL = "mistralai/Mistral-7B-Instruct-v0.3"
TTS_MODEL = "facebook/mms-tts-hin"
retriever = get_retriever()
print(f"Knowledge base ready: {len(retriever.doc_ids)} documents")
# ─────────────────────────────────────────────
# INFERENCE FUNCTIONS
# ─────────────────────────────────────────────
def get_client(provider: str) -> InferenceClient:
return InferenceClient(provider=provider, api_key=HF_TOKEN)
def stt_whisper(audio_array: np.ndarray, sample_rate: int) -> str:
"""Convert Hindi audio to text using Whisper via hf-inference."""
buf = io.BytesIO()
sf.write(buf, audio_array, sample_rate, format="WAV", subtype="PCM_16")
buf.seek(0)
client = get_client("hf-inference")
result = client.automatic_speech_recognition(
audio=buf.read(),
model=STT_MODEL,
)
# result is an ASROutput object with .text
return result.text.strip() if hasattr(result, "text") else str(result).strip()
def llm_generate(prompt: str) -> str:
"""Generate Hindi answer using Mistral via featherless (free tier)."""
client = get_client("featherless")
# Build messages in chat format
messages = [
{
"role": "system",
"content": (
"आप एक सहायक बैंकिंग सहायक हैं। केवल दी गई जानकारी के आधार पर "
"सरल हिंदी में 3-4 वाक्यों में उत्तर दें। "
"यदि जानकारी नहीं है तो कहें: 'यह जानकारी मेरे पास नहीं है। "
"कृपया अपने बैंक से संपर्क करें।' हमेशा हिंदी में उत्तर दें।"
),
},
{"role": "user", "content": prompt},
]
response = client.chat.completions.create(
model=LLM_MODEL,
messages=messages,
max_tokens=350,
temperature=0.7,
)
return response.choices[0].message.content.strip()
def tts_hindi(text: str):
"""Convert Hindi text to speech using MMS-TTS-HIN via hf-inference."""
client = get_client("hf-inference")
try:
audio_bytes = client.text_to_speech(text=text, model=TTS_MODEL)
buf = io.BytesIO(audio_bytes)
audio_array, sample_rate = sf.read(buf)
return int(sample_rate), audio_array.astype(np.float32)
except Exception as e:
print(f"TTS error: {e}")
return None
# ─────────────────────────────────────────────
# PIPELINE GENERATORS
# ─────────────────────────────────────────────
def run_voice_pipeline(audio_input):
"""Voice mode: mic audio → transcript, answer, citations, audio."""
if audio_input is None:
yield "", "कृपया माइक्रोफोन बटन दबाकर अपना प्रश्न पूछें।", "", None
return
if not HF_TOKEN:
yield "", "⚠️ HF_TOKEN Secret नहीं मिला। Space Settings → Secrets में जोड़ें।", "", None
return
try:
sample_rate, audio_array = audio_input
audio_float = audio_array.astype(np.float32)
if np.abs(audio_float).max() > 1.0:
audio_float /= 32768.0
if audio_float.ndim > 1:
audio_float = audio_float.mean(axis=1)
yield "⏳ आवाज़ पहचाना जा रहा है...", "", "", None
hindi_text = stt_whisper(audio_float, sample_rate)
if not hindi_text:
yield "", "आवाज़ स्पष्ट नहीं सुनाई दी। कृपया दोबारा कोशिश करें।", "", None
return
yield hindi_text, "⏳ जानकारी खोजी जा रही है...", "", None
normalized = normalize_jargon(hindi_text)
eng_query = translate_to_retrieval_query(normalized)
docs = retriever.retrieve(eng_query, top_k=3) or retriever.retrieve(hindi_text, top_k=2)
citations = "\n".join(f"• {d['title']}" for d in docs) if docs else "कोई स्रोत नहीं मिला।"
yield hindi_text, "⏳ उत्तर तैयार किया जा रहा है...", citations, None
# Build a concise context-augmented prompt for the chat model
context = "\n\n".join(
f"[{i+1}] {d['title']}\n{d['content'][:500]}"
for i, d in enumerate(docs)
) if docs else "कोई प्रासंगिक जानकारी नहीं मिली।"
full_prompt = (
f"नीचे दी गई जानकारी के आधार पर प्रश्न का उत्तर दें:\n\n"
f"{context}\n\n"
f"प्रश्न: {hindi_text}"
)
answer = llm_generate(full_prompt)
clean_answer = format_response_for_tts(answer) or \
"यह जानकारी मेरे पास नहीं है। कृपया अपने बैंक से संपर्क करें।"
yield hindi_text, clean_answer, citations, None
yield hindi_text, clean_answer, "⏳ ऑडियो बनाया जा रहा है...", None
audio_out = tts_hindi(clean_answer)
yield hindi_text, clean_answer, citations, audio_out
except Exception as e:
import traceback; traceback.print_exc()
yield "", f"⚠️ त्रुटि: {e}", "", None
def run_text_pipeline(text_input: str):
"""Text mode: typed question → answer, citations, audio."""
if not text_input or not text_input.strip():
yield text_input, "कृपया एक प्रश्न लिखें।", "", None
return
if not HF_TOKEN:
yield text_input, "⚠️ HF_TOKEN Secret नहीं मिला।", "", None
return
try:
yield text_input, "⏳ जानकारी खोजी जा रही है...", "", None
normalized = normalize_jargon(text_input)
eng_query = translate_to_retrieval_query(normalized)
docs = retriever.retrieve(eng_query, top_k=3) or retriever.retrieve(text_input, top_k=2)
citations = "\n".join(f"• {d['title']}" for d in docs) if docs else "कोई स्रोत नहीं मिला।"
yield text_input, "⏳ उत्तर तैयार किया जा रहा है...", citations, None
context = "\n\n".join(
f"[{i+1}] {d['title']}\n{d['content'][:500]}"
for i, d in enumerate(docs)
) if docs else "कोई प्रासंगिक जानकारी नहीं मिली।"
full_prompt = (
f"नीचे दी गई जानकारी के आधार पर प्रश्न का उत्तर दें:\n\n"
f"{context}\n\n"
f"प्रश्न: {text_input}"
)
answer = llm_generate(full_prompt)
clean_answer = format_response_for_tts(answer) or \
"यह जानकारी मेरे पास नहीं है। कृपया अपने बैंक से संपर्क करें।"
yield text_input, clean_answer, citations, None
yield text_input, clean_answer, "⏳ ऑडियो बनाया जा रहा है...", None
audio_out = tts_hindi(clean_answer)
yield text_input, clean_answer, citations, audio_out
except Exception as e:
import traceback; traceback.print_exc()
yield text_input, f"⚠️ त्रुटि: {e}", "", None
# ─────────────────────────────────────────────
# GRADIO UI
# ─────────────────────────────────────────────
EXAMPLES = [
"मुझे होम लोन के लिए कौन से दस्तावेज़ चाहिए?",
"EMI क्या होती है और कैसे calculate होती है?",
"मुद्रा लोन कैसे मिलेगा?",
"CIBIL score क्या होता है?",
"PM Awas Yojana में subsidy कैसे मिलती है?",
"जन धन खाता कैसे खोलें?",
"किसान क्रेडिट कार्ड क्या है?",
"Atal Pension Yojana में कैसे जुड़ें?",
"SBI personal loan की interest rate क्या है?",
"महिला स्वयं सहायता समूह को loan कैसे मिलेगा?",
"Street vendor ko SVANidhi loan kaise milega?",
"Bank ke khilaf complaint kahan karein?",
]
CSS = """
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+Devanagari:wght@300;400;500;700&family=Sora:wght@300;400;600;700&display=swap');
:root {
--saffron: #FF6B00;
--saffron-glow: rgba(255,107,0,0.15);
--green: #0A7A3E;
--navy: #0F1E3D;
--navy-mid: #162848;
--gold: #D4A017;
--gold-light: #F0C042;
--text-primary: #F4EFE6;
--text-secondary: #B8C4D8;
--border: rgba(212,160,23,0.3);
}
* { box-sizing: border-box; }
body, .gradio-container {
font-family: 'Sora','Noto Sans Devanagari',sans-serif !important;
background: var(--navy) !important;
color: var(--text-primary) !important;
}
.gradio-container { max-width: 1100px !important; margin: 0 auto !important; }
.tricolor { height:4px; background:linear-gradient(90deg,#FF6B00 33%,white 33% 66%,#0A7A3E 66%); }
.app-header {
background: linear-gradient(135deg,var(--navy) 0%,var(--navy-mid) 60%,#0D2847 100%);
border-bottom: 2px solid var(--gold);
padding: 28px 40px 22px; position: relative; overflow: hidden;
}
.app-header::before {
content:''; position:absolute; top:-60%; right:-5%;
width:350px; height:350px;
background:radial-gradient(circle,var(--saffron-glow) 0%,transparent 70%);
pointer-events:none;
}
.app-header::after { content:'🏦'; position:absolute; right:36px; top:50%; transform:translateY(-50%); font-size:72px; opacity:0.09; }
.header-badge { display:inline-flex; align-items:center; gap:8px; background:rgba(212,160,23,0.1); border:1px solid var(--gold); border-radius:100px; padding:4px 14px; font-size:11px; font-weight:600; letter-spacing:1.5px; text-transform:uppercase; color:var(--gold-light); margin-bottom:10px; }
.header-title { font-size:28px; font-weight:700; color:var(--text-primary); margin-bottom:5px; font-family:'Noto Sans Devanagari','Sora',sans-serif !important; }
.header-subtitle { font-size:13px; color:var(--text-secondary); font-weight:300; }
.notice { background:rgba(212,160,23,0.07); border:1px solid rgba(212,160,23,0.28); border-radius:10px; padding:10px 18px; font-size:12px; color:var(--gold-light); margin:12px 40px; display:flex; align-items:flex-start; gap:10px; line-height:1.6; }
.steps { display:flex; gap:6px; flex-wrap:wrap; padding:12px 40px; background:rgba(10,25,50,0.55); border-bottom:1px solid var(--border); }
.step { display:flex; align-items:center; gap:5px; background:rgba(255,255,255,0.03); border:1px solid var(--border); border-radius:100px; padding:4px 11px; font-size:11px; color:var(--text-secondary); }
.sn { width:16px; height:16px; background:var(--saffron); border-radius:50%; display:flex; align-items:center; justify-content:center; font-size:9px; font-weight:700; color:white; flex-shrink:0; }
label { color:var(--gold-light) !important; font-size:11px !important; font-weight:600 !important; letter-spacing:0.8px !important; text-transform:uppercase !important; }
textarea { background:rgba(10,20,45,0.85) !important; border:1px solid var(--border) !important; border-radius:10px !important; color:var(--text-primary) !important; font-family:'Noto Sans Devanagari','Sora',sans-serif !important; font-size:14px !important; line-height:1.7 !important; padding:13px !important; }
textarea:focus { border-color:var(--saffron) !important; outline:none !important; box-shadow:0 0 0 3px var(--saffron-glow) !important; }
.answer-area textarea { background:linear-gradient(135deg,rgba(10,122,62,0.07) 0%,rgba(10,20,45,0.9) 100%) !important; border:1px solid rgba(10,122,62,0.3) !important; color:#DFF0E6 !important; font-size:15px !important; line-height:1.8 !important; min-height:130px !important; }
.sources-area textarea { background:rgba(212,160,23,0.04) !important; border:1px solid rgba(212,160,23,0.18) !important; font-size:12px !important; color:var(--text-secondary) !important; }
.panel { background:var(--navy-mid) !important; border:1px solid var(--border) !important; border-radius:14px !important; padding:20px !important; margin:8px !important; }
button.primary { background:linear-gradient(135deg,var(--saffron) 0%,#D55000 100%) !important; border:none !important; border-radius:10px !important; color:white !important; font-weight:700 !important; font-size:13px !important; padding:12px 28px !important; cursor:pointer !important; transition:all 0.25s !important; box-shadow:0 4px 20px rgba(255,107,0,0.35) !important; letter-spacing:0.5px !important; text-transform:uppercase !important; }
button.primary:hover { transform:translateY(-2px) !important; box-shadow:0 8px 30px rgba(255,107,0,0.5) !important; }
.tab-nav button { background:transparent !important; border:none !important; border-bottom:2px solid transparent !important; color:var(--text-secondary) !important; font-size:13px !important; font-weight:500 !important; padding:10px 22px !important; border-radius:0 !important; transition:all 0.2s !important; }
.tab-nav button.selected { border-bottom-color:var(--saffron) !important; color:#FF8C33 !important; font-weight:600 !important; }
.footer { padding:16px 40px; text-align:center; font-size:11px; color:rgba(184,196,216,0.4); border-top:1px solid var(--border); background:rgba(8,15,35,0.5); }
::-webkit-scrollbar { width:5px; } ::-webkit-scrollbar-thumb { background:var(--gold); border-radius:3px; }
"""
def build_ui():
with gr.Blocks(css=CSS, title="बैंकिंग सहायक", theme=gr.themes.Base()) as demo:
gr.HTML('<div class="tricolor"></div>')
gr.HTML("""
<div class="app-header">
<div class="header-badge">🎙️ Voice · Hindi · Free Tier</div>
<div class="header-title">बैंकिंग सहायक</div>
<div class="header-subtitle">AI-powered Hindi Banking & Finance Assistant — Loans · Schemes · EMI · Accounts · Social Security</div>
</div>
""")
gr.HTML("""
<div class="notice">
<span>🔑</span>
<span><b>One-time setup:</b> Space <b>Settings → Secrets</b> → add
<code style="background:rgba(0,0,0,0.3);padding:1px 6px;border-radius:4px;">HF_TOKEN</code>
= your free HF token from hf.co/settings/tokens</span>
</div>
""")
gr.HTML("""
<div class="steps">
<div class="step"><span class="sn">1</span>हिंदी बोलें</div>
<div class="step"><span class="sn">2</span>Whisper STT</div>
<div class="step"><span class="sn">3</span>Jargon Norm</div>
<div class="step"><span class="sn">4</span>RAG Retrieval</div>
<div class="step"><span class="sn">5</span>Mistral-7B LLM</div>
<div class="step"><span class="sn">6</span>MMS Hindi TTS</div>
<div class="step"><span class="sn">7</span>हिंदी उत्तर</div>
</div>
""")
with gr.Tabs(elem_classes="tab-nav"):
with gr.Tab("🎙️ Voice Mode"):
with gr.Row():
with gr.Column(scale=1, elem_classes="panel"):
gr.HTML('<p style="color:#B8C4D8;font-size:13px;margin:0 0 12px;">माइक बटन दबाएं → हिंदी में बोलें → Submit</p>')
v_audio_in = gr.Audio(sources=["microphone"], type="numpy", label="🎤 अपना प्रश्न बोलें")
v_btn = gr.Button("🔍 उत्तर खोजें", variant="primary")
v_transcript= gr.Textbox(label="📝 Transcript", interactive=False, lines=2, placeholder="आपकी बात यहाँ दिखेगी...")
with gr.Column(scale=1, elem_classes="panel"):
v_answer = gr.Textbox(label="💬 उत्तर", interactive=False, lines=5, placeholder="उत्तर यहाँ आएगा...", elem_classes="answer-area")
v_audio_out = gr.Audio(label="🔊 उत्तर सुनें", type="numpy", autoplay=True)
v_citations = gr.Textbox(label="📚 स्रोत", interactive=False, lines=3, elem_classes="sources-area")
v_btn.click(fn=run_voice_pipeline, inputs=[v_audio_in],
outputs=[v_transcript, v_answer, v_citations, v_audio_out])
with gr.Tab("⌨️ Text Mode"):
with gr.Row():
with gr.Column(scale=1, elem_classes="panel"):
gr.HTML('<p style="color:#B8C4D8;font-size:13px;margin:0 0 12px;">हिंदी या Hinglish में टाइप करें</p>')
t_input = gr.Textbox(label="✏️ अपना प्रश्न लिखें", lines=3, placeholder="जैसे: होम लोन के लिए कौन से दस्तावेज़ चाहिए?")
t_btn = gr.Button("🔍 उत्तर खोजें", variant="primary")
t_transcript= gr.Textbox(label="📝 आपका प्रश्न", interactive=False, lines=2)
with gr.Column(scale=1, elem_classes="panel"):
t_answer = gr.Textbox(label="💬 उत्तर", interactive=False, lines=5, elem_classes="answer-area")
t_audio_out = gr.Audio(label="🔊 उत्तर सुनें", type="numpy", autoplay=True)
t_citations = gr.Textbox(label="📚 स्रोत", interactive=False, lines=3, elem_classes="sources-area")
t_btn.click(fn=run_text_pipeline, inputs=[t_input],
outputs=[t_transcript, t_answer, t_citations, t_audio_out])
t_input.submit(fn=run_text_pipeline, inputs=[t_input],
outputs=[t_transcript, t_answer, t_citations, t_audio_out])
gr.HTML('<div style="padding:6px 10px 0;"><p style="color:var(--gold);font-size:11px;font-weight:600;letter-spacing:1px;text-transform:uppercase;margin:8px 0 4px 32px;">📌 उदाहरण प्रश्न</p></div>')
gr.Examples(examples=[[q] for q in EXAMPLES], inputs=[t_input], label="", cache_examples=False)
with gr.Accordion("ℹ️ About / जानकारी", open=False):
gr.HTML("""
<div style="padding:18px;color:#B8C4D8;font-size:13px;line-height:1.8;">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-bottom:14px;">
<div>
<div style="color:#D4A017;font-weight:600;margin-bottom:8px;">🤖 Models</div>
<div>• <b>STT:</b> openai/whisper-large-v3 (hf-inference)</div>
<div>• <b>LLM:</b> mistralai/Mistral-7B-Instruct-v0.3 (featherless)</div>
<div>• <b>TTS:</b> facebook/mms-tts-hin (hf-inference)</div>
<br/>
<div style="color:#D4A017;font-weight:600;margin-bottom:6px;">⏱️ Response Time</div>
<div>• STT: ~5–10s &nbsp;• LLM: ~5–15s &nbsp;• TTS: ~3–8s</div>
<div><b>Total: ~15–35s per query</b></div>
</div>
<div>
<div style="color:#D4A017;font-weight:600;margin-bottom:8px;">📖 Knowledge Base</div>
<div>• 12 Major Indian Banks</div>
<div>• 11 Govt Credit Schemes (Mudra, PMAY, KCC...)</div>
<div>• 7 Social Security Schemes (APY, PMJJBY, EPFO...)</div>
<div>• Financial Terms (EMI, CIBIL, KYC, MCLR...)</div>
<div>• MFI, NBFC, RRBs, Co-op Banks</div>
<div>• Consumer Rights & RBI Ombudsman</div>
</div>
</div>
<div style="background:rgba(255,107,0,0.07);border:1px solid rgba(255,107,0,0.2);border-radius:8px;padding:10px 14px;font-size:12px;color:#FF8C33;">
⚠️ यह assistant केवल सामान्य जानकारी देता है। वित्तीय निर्णयों के लिए अपने बैंक या certified advisor से सलाह लें।
</div>
</div>
""")
gr.HTML('<div class="footer">बैंकिंग सहायक · Whisper + Mistral-7B + MMS-TTS · CPU Basic Safe · HF Inference Providers</div>')
return demo
if __name__ == "__main__":
demo = build_ui()
demo.launch(server_name="0.0.0.0", server_port=7860, show_error=True)