Spaces:
Sleeping
Sleeping
File size: 13,013 Bytes
832eee4 60488e2 832eee4 015416d 832eee4 60488e2 832eee4 60488e2 015416d 832eee4 60488e2 832eee4 60488e2 832eee4 60488e2 832eee4 60488e2 832eee4 60488e2 832eee4 60488e2 832eee4 60488e2 832eee4 015416d 60488e2 832eee4 015416d 832eee4 60488e2 832eee4 60488e2 832eee4 60488e2 832eee4 015416d 832eee4 015416d 832eee4 60488e2 832eee4 60488e2 832eee4 60488e2 832eee4 4cded81 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 | """
RAG Pipeline — pure Python, zero external dependencies.
No torch, no numpy, no transformers. Safe on CPU Basic.
"""
import re
from knowledge_base import get_document_texts, KNOWLEDGE_BASE
# ─────────────────────────────────────────────
# FINANCE JARGON NORMALIZATION MAP
# Hindi/Hinglish → English canonical terms
# ─────────────────────────────────────────────
JARGON_MAP = {
# EMI variants
"kist": "EMI equated monthly installment",
"maahik kist": "monthly installment EMI",
"maasik bhugtan": "monthly payment EMI",
"kisten": "installments EMI",
"maahik bhugtan": "monthly installment EMI",
# Loan
"karz": "loan",
"udhaar": "loan credit",
"rin": "loan",
"loan lena": "apply for loan",
"loan milega": "loan eligibility",
"paise chahiye": "need money loan",
"paisa": "money funds",
"raqam": "amount loan",
# Interest
"byaj": "interest rate",
"sudh": "interest",
"byaj dar": "interest rate",
"faixed byaj": "fixed interest rate",
"badlav wala byaj": "floating interest rate",
# Bank account
"khata": "bank account",
"bachat khata": "savings account",
"khata kholna": "open bank account",
"bank mein khata": "bank account",
# Collateral/Guarantee
"zamanat": "collateral guarantee security",
"zamanatdar": "guarantor",
"girwi": "mortgage pledge",
"girvi rakhna": "pledge collateral",
# Documents
"kaagaz": "documents",
"dastavej": "documents",
"pehchaan patra": "identity proof",
"niwas praman": "address proof",
"aay praman": "income proof",
# Credit/CIBIL
"saakh": "credit score CIBIL",
"credit score kya hai": "what is credit score CIBIL",
"score": "CIBIL credit score",
# Principal/Tenure
"mool rashi": "principal amount",
"avadhi": "loan tenure duration",
"muddat": "loan tenure period",
"kitne saal": "how many years tenure",
"kitne mahine": "how many months tenure",
# Repayment
"wapasi": "repayment",
"bhugtan": "payment repayment",
"chukana": "repay loan",
"ada karna": "pay repay",
# Government schemes
"sarkar ki yojana": "government scheme",
"yojana": "scheme",
"sarkari loan": "government loan scheme",
"subsidy": "subsidy government benefit",
"anudan": "grant subsidy",
# Specific schemes
"mudra": "mudra loan PMMY",
"kisaan": "farmer kisan",
"kisan": "farmer kisan credit card",
"jan dhan": "PMJDY jan dhan account",
"bima": "insurance",
"jeevan bima": "life insurance PMJJBY",
"suraksha bima": "accident insurance PMSBY",
"pension": "pension APY Atal Pension Yojana",
"gramin bank": "rural bank RRB",
"shg": "self help group SHG women loan",
"samuh": "self help group SHG",
"mahila samuh": "women self help group SHG microfinance",
# Defaults/issues
"default": "loan default NPA",
"band ho gaya": "account closed loan default",
"paise nahin de paya": "unable to repay loan default",
"chhoot": "waiver loan waiver",
# Property
"ghar lena": "home purchase home loan",
"makan": "house home property",
"zameen": "land property",
"ghar banana": "home construction loan",
"flat": "apartment home loan",
"awas yojana": "awas yojana housing scheme pmay subsidy",
"awas": "housing pmay",
# Grievance
"shikayat": "complaint grievance",
"problem": "complaint issue grievance",
"dhoka": "fraud complaint",
"pareshan": "problem issue complaint",
}
def normalize_jargon(text: str) -> str:
"""Replace Hindi/Hinglish finance jargon with English equivalents."""
text_lower = text.lower()
for hindi_term, english_term in JARGON_MAP.items():
if hindi_term in text_lower:
text_lower = text_lower.replace(hindi_term, english_term)
return text_lower
def translate_to_retrieval_query(normalized_text: str) -> str:
"""Extract English words and key Hindi terms from normalized text for retrieval."""
# Keep English words (alpha) and common Hindi keywords that are in KB tags
words = [str(w) for w in normalized_text.split() if any(c.isalpha() for c in w)]
if not words:
# Fallback to the original text if no normalization happened
return str(normalized_text)
# Standard slicing for list of strings
result_words = words[0:20]
return " ".join(result_words)
# ─────────────────────────────────────────────
# KEYWORD RETRIEVER — pure Python, no dependencies
# ─────────────────────────────────────────────
class SimpleRetriever:
def __init__(self):
self.doc_ids = []
self.documents = [] # lowercased full text strings
self.doc_words = [] # sets of words per doc
self._build_index()
def _build_index(self):
for doc_id, text in get_document_texts():
self.doc_ids.append(doc_id)
lowered = text.lower()
self.documents.append(lowered)
self.doc_words.append(set(re.findall(r'\b\w+\b', lowered)))
# Augment with tags
for i, doc in enumerate(KNOWLEDGE_BASE):
tags_text = " ".join(doc.get("tags", [])).lower()
self.documents[i] += " " + tags_text
self.doc_words[i].update(re.findall(r'\b\w+\b', tags_text))
def retrieve(self, query: str, top_k: int = 3) -> list:
query_words = set(re.findall(r'\b\w+\b', query.lower()))
if not query_words:
return []
scores = []
for i, doc_words in enumerate(self.doc_words):
overlap = len(query_words & doc_words)
score = overlap / (len(query_words) + 0.5)
# Substantial bonus for exact phrase matching in document
if query.lower() in self.documents[i]:
score += 1.0
# Bonus for longer exact word matches
for qw in query_words:
if len(qw) > 3 and qw in self.documents[i]:
score += 0.2
scores.append((score, i))
scores.sort(reverse=True)
results = []
for score, idx in scores[:top_k]:
if score <= 0:
continue
doc = KNOWLEDGE_BASE[idx]
results.append({
"id": doc["id"],
"title": doc["title"],
"content": doc["content"],
"category": doc["category"],
})
return results
_retriever = None
def get_retriever() -> SimpleRetriever:
global _retriever
if _retriever is None:
_retriever = SimpleRetriever()
return _retriever
# ─────────────────────────────────────────────
# PROMPT BUILDER
# ─────────────────────────────────────────────
def build_rag_prompt(user_question: str, retrieved_docs: list) -> str:
"""Build Indic-Gemma prompt with retrieved context. Enforces Hindi output."""
if retrieved_docs:
context_parts = [
f"[{i+1}] {doc['title']}\n{doc['content'][:600]}"
for i, doc in enumerate(retrieved_docs)
]
context = "\n\n".join(context_parts)
else:
context = "कोई प्रासंगिक जानकारी नहीं मिली।"
return f"""<|system|>
आप एक सहायक बैंकिंग सहायक हैं जो भारतीय बैंकिंग, लोन, और सरकारी योजनाओं के बारे में सरल हिंदी में जानकारी देते हैं।
नियम:
1. नीचे दी गई जानकारी का उपयोग करें। यदि जानकारी बिल्कुल स्पष्ट नहीं है, तो सामान्य बैंकिंग ज्ञान का उपयोग कर सहायता करें लेकिन "यह जानकारी मेरे पास नहीं है" कहने से बचें यदि आप बेसिक सलाह दे सकते हैं।
2. उत्तर छोटा, सरल और बोलने योग्य हो — 3-4 वाक्यों में।
3. यदि जानकारी बिल्कुल भी उपलब्ध नहीं है, तभी कहें: "यह जानकारी मेरे पास नहीं है। कृपया अपने बैंक से संपर्क करें।"
4. अंत में केवल एक जरूरी follow-up प्रश्न पूछें (यदि आवश्यक हो)।
5. हमेशा हिंदी में उत्तर दें।
संदर्भ जानकारी:
{context}
<|end|>
<|user|>
{user_question}
<|end|>
<|assistant|>"""
def format_response_for_tts(text: str) -> str:
"""Strip markdown and extra whitespace from LLM output before sending to TTS."""
text = re.sub(r'\*+', '', text)
text = re.sub(r'#+\s*', '', text)
text = re.sub(r'\[[\d]+\]', '', text)
text = re.sub(r'\n+', ' ', text)
text = re.sub(r'\s+', ' ', text)
return text.strip()
# ─────────────────────────────────────────────
# EMBEDDING RETRIEVER — multilingual, Hindi-aware
# Uses paraphrase-multilingual-MiniLM-L12-v2
# Falls back to SimpleRetriever if ChromaDB not ready
# ─────────────────────────────────────────────
try:
from sentence_transformers import SentenceTransformer
from FlagEmbedding import FlagReranker
import chromadb
print("Loading embedding model (multilingual MiniLM)...")
embedder = SentenceTransformer(
'sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2'
)
print("Loading reranker (bge-reranker-v2-m3)...")
reranker = FlagReranker('BAAI/bge-reranker-v2-m3', use_fp16=False)
chroma_client = chromadb.PersistentClient(path="./chroma_db")
EMBEDDING_READY = True
print("Embedding retriever ready.")
except Exception as e:
print(f"Embedding retriever not available: {e}. Using keyword retriever.")
EMBEDDING_READY = False
def retrieve_with_embeddings(query: str, top_k: int = 3) -> list:
"""
Two-stage retrieval:
Stage 1 - ChromaDB embedding search (top 10)
Stage 2 - bge-reranker picks best 3
Falls back to SimpleRetriever if embeddings not ready.
Critical for Hinglish queries like
'home loan ka interest kitna hai for salaried?'
"""
if not EMBEDDING_READY:
print("DEBUG RAG: falling back to keyword retriever")
retriever = get_retriever()
return retriever.retrieve(query, top_k=top_k)
try:
collection = chroma_client.get_collection("banking_hindi")
except Exception:
print("DEBUG RAG: ChromaDB collection not found, run ingest.py first")
print("DEBUG RAG: falling back to keyword retriever")
retriever = get_retriever()
return retriever.retrieve(query, top_k=top_k)
try:
# Stage 1: embedding similarity search
query_embedding = embedder.encode([query]).tolist()
results = collection.query(
query_embeddings=query_embedding,
n_results=min(10, collection.count())
)
candidates = results['documents'][0]
metadatas = results['metadatas'][0]
print(f"DEBUG RAG: {len(candidates)} candidates from ChromaDB")
# Stage 2: rerank
pairs = [[query, doc] for doc in candidates]
scores = reranker.compute_score(pairs)
ranked = sorted(
zip(scores, candidates, metadatas),
reverse=True
)
top_results = [
{
"id": meta.get("id", ""),
"title": meta.get("title", ""),
"content": doc,
"category": meta.get("category", "")
}
for _, doc, meta in ranked[:top_k]
]
print(f"DEBUG RAG: top result = {top_results[0]['title'] if top_results else 'none'}")
return top_results
except Exception as e:
print(f"DEBUG RAG: embedding retrieval error: {e}")
retriever = get_retriever()
return retriever.retrieve(query, top_k=top_k)
|