Spaces:
Sleeping
Sleeping
Upload 2 files
Browse files- rag_pipeline.py +281 -0
- requirements.txt +6 -0
rag_pipeline.py
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
RAG Pipeline for Hindi Banking Voice Assistant
|
| 3 |
+
Uses: Whisper (STT) + Indic-Gemma (LLM) + Indic-Parler-TTS (TTS)
|
| 4 |
+
All retrieval in English, all responses in Hindi.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import re
|
| 8 |
+
import numpy as np
|
| 9 |
+
from typing import Optional
|
| 10 |
+
import torch
|
| 11 |
+
from knowledge_base import get_document_texts, KNOWLEDGE_BASE
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
# ─────────────────────────────────────────────
|
| 15 |
+
# FINANCE JARGON NORMALIZATION MAP
|
| 16 |
+
# Hindi/Hinglish → English canonical terms
|
| 17 |
+
# ─────────────────────────────────────────────
|
| 18 |
+
JARGON_MAP = {
|
| 19 |
+
# EMI variants
|
| 20 |
+
"kist": "EMI equated monthly installment",
|
| 21 |
+
"maahik kist": "monthly installment EMI",
|
| 22 |
+
"maasik bhugtan": "monthly payment EMI",
|
| 23 |
+
"kisten": "installments EMI",
|
| 24 |
+
"maahik bhugtan": "monthly installment EMI",
|
| 25 |
+
|
| 26 |
+
# Loan
|
| 27 |
+
"karz": "loan",
|
| 28 |
+
"udhaar": "loan credit",
|
| 29 |
+
"rin": "loan",
|
| 30 |
+
"loan lena": "apply for loan",
|
| 31 |
+
"loan milega": "loan eligibility",
|
| 32 |
+
"paise chahiye": "need money loan",
|
| 33 |
+
"paisa": "money funds",
|
| 34 |
+
"raqam": "amount loan",
|
| 35 |
+
|
| 36 |
+
# Interest
|
| 37 |
+
"byaj": "interest rate",
|
| 38 |
+
"sudh": "interest",
|
| 39 |
+
"byaj dar": "interest rate",
|
| 40 |
+
"faixed byaj": "fixed interest rate",
|
| 41 |
+
"badlav wala byaj": "floating interest rate",
|
| 42 |
+
|
| 43 |
+
# Bank account
|
| 44 |
+
"khata": "bank account",
|
| 45 |
+
"bachat khata": "savings account",
|
| 46 |
+
"khata kholna": "open bank account",
|
| 47 |
+
"bank mein khata": "bank account",
|
| 48 |
+
|
| 49 |
+
# Collateral/Guarantee
|
| 50 |
+
"zamanat": "collateral guarantee security",
|
| 51 |
+
"zamanatdar": "guarantor",
|
| 52 |
+
"girwi": "mortgage pledge",
|
| 53 |
+
"girvi rakhna": "pledge collateral",
|
| 54 |
+
|
| 55 |
+
# Documents
|
| 56 |
+
"kaagaz": "documents",
|
| 57 |
+
"dastavej": "documents",
|
| 58 |
+
"pehchaan patra": "identity proof",
|
| 59 |
+
"niwas praman": "address proof",
|
| 60 |
+
"aay praman": "income proof",
|
| 61 |
+
|
| 62 |
+
# Credit/CIBIL
|
| 63 |
+
"saakh": "credit score CIBIL",
|
| 64 |
+
"credit score kya hai": "what is credit score CIBIL",
|
| 65 |
+
"score": "CIBIL credit score",
|
| 66 |
+
|
| 67 |
+
# Principal/Tenure
|
| 68 |
+
"mool rashi": "principal amount",
|
| 69 |
+
"avadhi": "loan tenure duration",
|
| 70 |
+
"muddat": "loan tenure period",
|
| 71 |
+
"kitne saal": "how many years tenure",
|
| 72 |
+
"kitne mahine": "how many months tenure",
|
| 73 |
+
|
| 74 |
+
# Repayment
|
| 75 |
+
"wapasi": "repayment",
|
| 76 |
+
"bhugtan": "payment repayment",
|
| 77 |
+
"chukana": "repay loan",
|
| 78 |
+
"ada karna": "pay repay",
|
| 79 |
+
|
| 80 |
+
# Government schemes
|
| 81 |
+
"sarkar ki yojana": "government scheme",
|
| 82 |
+
"yojana": "scheme",
|
| 83 |
+
"sarkari loan": "government loan scheme",
|
| 84 |
+
"subsidy": "subsidy government benefit",
|
| 85 |
+
"anudan": "grant subsidy",
|
| 86 |
+
|
| 87 |
+
# Specific schemes
|
| 88 |
+
"mudra": "mudra loan PMMY",
|
| 89 |
+
"kisaan": "farmer kisan",
|
| 90 |
+
"kisan": "farmer kisan credit card",
|
| 91 |
+
"jan dhan": "PMJDY jan dhan account",
|
| 92 |
+
"bima": "insurance",
|
| 93 |
+
"jeevan bima": "life insurance PMJJBY",
|
| 94 |
+
"suraksha bima": "accident insurance PMSBY",
|
| 95 |
+
"pension": "pension APY Atal Pension Yojana",
|
| 96 |
+
"gramin bank": "rural bank RRB",
|
| 97 |
+
"shg": "self help group SHG women loan",
|
| 98 |
+
"samuh": "self help group SHG",
|
| 99 |
+
"mahila samuh": "women self help group SHG microfinance",
|
| 100 |
+
|
| 101 |
+
# Defaults/issues
|
| 102 |
+
"default": "loan default NPA",
|
| 103 |
+
"band ho gaya": "account closed loan default",
|
| 104 |
+
"paise nahin de paya": "unable to repay loan default",
|
| 105 |
+
"chhoot": "waiver loan waiver",
|
| 106 |
+
|
| 107 |
+
# Property
|
| 108 |
+
"ghar lena": "home purchase home loan",
|
| 109 |
+
"makan": "house home property",
|
| 110 |
+
"zameen": "land property",
|
| 111 |
+
"ghar banana": "home construction loan",
|
| 112 |
+
"flat": "apartment home loan",
|
| 113 |
+
|
| 114 |
+
# Grievance
|
| 115 |
+
"shikayat": "complaint grievance",
|
| 116 |
+
"problem": "complaint issue grievance",
|
| 117 |
+
"dhoka": "fraud complaint",
|
| 118 |
+
"pareshan": "problem issue complaint",
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
def normalize_jargon(text: str) -> str:
|
| 123 |
+
"""
|
| 124 |
+
Replace Hindi/Hinglish finance jargon with standardized English terms
|
| 125 |
+
to improve retrieval quality.
|
| 126 |
+
"""
|
| 127 |
+
text_lower = text.lower()
|
| 128 |
+
for hindi_term, english_term in JARGON_MAP.items():
|
| 129 |
+
if hindi_term in text_lower:
|
| 130 |
+
text_lower = text_lower.replace(hindi_term, english_term)
|
| 131 |
+
return text_lower
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
def translate_to_retrieval_query(hindi_text: str) -> str:
|
| 135 |
+
"""
|
| 136 |
+
Convert normalized Hindi/Hinglish text to an English retrieval query.
|
| 137 |
+
This uses rule-based translation for common patterns.
|
| 138 |
+
For production: could use a translation model.
|
| 139 |
+
"""
|
| 140 |
+
normalized = normalize_jargon(hindi_text)
|
| 141 |
+
|
| 142 |
+
# Extract key English terms that emerged from normalization
|
| 143 |
+
# plus keep any already-English words in the original
|
| 144 |
+
english_words = []
|
| 145 |
+
|
| 146 |
+
# Words already in English or normalized
|
| 147 |
+
for word in normalized.split():
|
| 148 |
+
if any(c.isalpha() for c in word):
|
| 149 |
+
english_words.append(word)
|
| 150 |
+
|
| 151 |
+
return " ".join(english_words[:20]) # cap at 20 words for retrieval
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
# ─────────────────────────────────────────────
|
| 155 |
+
# SIMPLE BM25-STYLE KEYWORD RETRIEVAL
|
| 156 |
+
# (No heavy dependencies — pure Python TF-IDF-like scoring)
|
| 157 |
+
# ─────────────────────────────────────────────
|
| 158 |
+
class SimpleRetriever:
|
| 159 |
+
def __init__(self):
|
| 160 |
+
self.documents = []
|
| 161 |
+
self.doc_ids = []
|
| 162 |
+
self.doc_words = []
|
| 163 |
+
self._build_index()
|
| 164 |
+
|
| 165 |
+
def _build_index(self):
|
| 166 |
+
"""Build keyword index over knowledge base."""
|
| 167 |
+
for doc_id, text in get_document_texts():
|
| 168 |
+
self.doc_ids.append(doc_id)
|
| 169 |
+
self.documents.append(text.lower())
|
| 170 |
+
words = set(re.findall(r'\b\w+\b', text.lower()))
|
| 171 |
+
self.doc_words.append(words)
|
| 172 |
+
|
| 173 |
+
# Also index tags for better matching
|
| 174 |
+
for i, doc in enumerate(KNOWLEDGE_BASE):
|
| 175 |
+
tags_text = " ".join(doc.get("tags", []))
|
| 176 |
+
self.documents[i] += " " + tags_text.lower()
|
| 177 |
+
self.doc_words[i].update(re.findall(r'\b\w+\b', tags_text.lower()))
|
| 178 |
+
|
| 179 |
+
def retrieve(self, query: str, top_k: int = 3) -> list[dict]:
|
| 180 |
+
"""Retrieve top_k most relevant documents for query."""
|
| 181 |
+
query_words = set(re.findall(r'\b\w+\b', query.lower()))
|
| 182 |
+
|
| 183 |
+
if not query_words:
|
| 184 |
+
return []
|
| 185 |
+
|
| 186 |
+
scores = []
|
| 187 |
+
for i, doc_words in enumerate(self.doc_words):
|
| 188 |
+
# Jaccard-like overlap + length normalization
|
| 189 |
+
overlap = len(query_words & doc_words)
|
| 190 |
+
score = overlap / (len(query_words) + 0.5)
|
| 191 |
+
|
| 192 |
+
# Bonus for exact phrase matches
|
| 193 |
+
for qw in query_words:
|
| 194 |
+
if len(qw) > 4 and qw in self.documents[i]:
|
| 195 |
+
score += 0.3
|
| 196 |
+
|
| 197 |
+
scores.append((score, i))
|
| 198 |
+
|
| 199 |
+
scores.sort(reverse=True)
|
| 200 |
+
top_indices = [idx for score, idx in scores[:top_k] if score > 0]
|
| 201 |
+
|
| 202 |
+
results = []
|
| 203 |
+
for idx in top_indices:
|
| 204 |
+
doc = KNOWLEDGE_BASE[idx]
|
| 205 |
+
results.append({
|
| 206 |
+
"id": doc["id"],
|
| 207 |
+
"title": doc["title"],
|
| 208 |
+
"content": doc["content"],
|
| 209 |
+
"category": doc["category"],
|
| 210 |
+
"score": scores[idx][0] if idx < len(scores) else 0,
|
| 211 |
+
})
|
| 212 |
+
|
| 213 |
+
return results
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
# Singleton retriever
|
| 217 |
+
_retriever = None
|
| 218 |
+
|
| 219 |
+
def get_retriever() -> SimpleRetriever:
|
| 220 |
+
global _retriever
|
| 221 |
+
if _retriever is None:
|
| 222 |
+
_retriever = SimpleRetriever()
|
| 223 |
+
return _retriever
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
def build_rag_prompt(user_question: str, retrieved_docs: list[dict]) -> str:
|
| 227 |
+
"""
|
| 228 |
+
Build the prompt for the Indic-Gemma LLM.
|
| 229 |
+
System prompt enforces Hindi, grounded answers, brevity.
|
| 230 |
+
"""
|
| 231 |
+
context_parts = []
|
| 232 |
+
for i, doc in enumerate(retrieved_docs, 1):
|
| 233 |
+
context_parts.append(f"[{i}] {doc['title']}\n{doc['content'][:600]}")
|
| 234 |
+
|
| 235 |
+
context = "\n\n".join(context_parts) if context_parts else "कोई प्रासंगिक जानकारी नहीं मिली।"
|
| 236 |
+
|
| 237 |
+
prompt = f"""<|system|>
|
| 238 |
+
आप एक सहायक बैंकिंग सहायक हैं जो भारतीय बैंकिंग, लोन, और सरकारी योजनाओं के बारे में सरल हिंदी में जानकारी देते हैं।
|
| 239 |
+
|
| 240 |
+
नियम:
|
| 241 |
+
1. केवल नीचे दी गई जानकारी के आधार पर उत्तर दें। अनुमान न लगाएं।
|
| 242 |
+
2. उत्तर छोटा, सरल और बोलने योग्य हो — 3-4 वाक्यों में।
|
| 243 |
+
3. यदि जानकारी उपलब्ध नहीं है, तो कहें: "यह जानकारी मेरे पास नहीं है। कृपया अपने बैंक से संपर्क करें।"
|
| 244 |
+
4. अंत में केवल एक जरूरी follow-up प्रश्न पूछें (यदि आवश्यक हो)।
|
| 245 |
+
5. हमेशा हिंदी में उत्तर दें।
|
| 246 |
+
|
| 247 |
+
संदर्भ जानकारी:
|
| 248 |
+
{context}
|
| 249 |
+
<|end|>
|
| 250 |
+
<|user|>
|
| 251 |
+
{user_question}
|
| 252 |
+
<|end|>
|
| 253 |
+
<|assistant|>"""
|
| 254 |
+
|
| 255 |
+
return prompt
|
| 256 |
+
|
| 257 |
+
|
| 258 |
+
def format_response_for_tts(text: str) -> str:
|
| 259 |
+
"""
|
| 260 |
+
Clean LLM output for TTS: remove special chars, ensure clean Hindi.
|
| 261 |
+
"""
|
| 262 |
+
# Remove markdown
|
| 263 |
+
text = re.sub(r'\*+', '', text)
|
| 264 |
+
text = re.sub(r'#+\s*', '', text)
|
| 265 |
+
text = re.sub(r'\[[\d]+\]', '', text) # remove citation markers
|
| 266 |
+
text = re.sub(r'\n+', ' ', text)
|
| 267 |
+
text = re.sub(r'\s+', ' ', text)
|
| 268 |
+
text = text.strip()
|
| 269 |
+
return text
|
| 270 |
+
|
| 271 |
+
|
| 272 |
+
def get_tts_description(text: str) -> str:
|
| 273 |
+
"""
|
| 274 |
+
Parler-TTS requires a description of the speaker/style.
|
| 275 |
+
Returns appropriate description for Hindi banking assistant.
|
| 276 |
+
"""
|
| 277 |
+
return (
|
| 278 |
+
"A calm, clear female voice speaking in Hindi. "
|
| 279 |
+
"The speech is measured and helpful, like a bank customer service representative. "
|
| 280 |
+
"Very clear pronunciation, moderate pace, friendly tone."
|
| 281 |
+
)
|
requirements.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# CPU Basic safe — all models via HF Inference API, nothing loads locally
|
| 2 |
+
gradio>=4.44.0
|
| 3 |
+
requests>=2.31.0
|
| 4 |
+
soundfile>=0.12.1
|
| 5 |
+
numpy>=1.24.0
|
| 6 |
+
# NO torch, NO transformers, NO parler-tts — all inference is API-based
|