Spaces:
Sleeping
Sleeping
File size: 7,394 Bytes
9b73f19 fc3f34d 5c31616 2be51ac 9b73f19 fc3f34d 990bd50 207e13d fc3f34d 990bd50 fc3f34d 9b73f19 2be51ac 5c31616 9b73f19 5c31616 9b73f19 fc3f34d 2be51ac 5c31616 9b73f19 2be51ac fc3f34d 2be51ac 5c31616 2be51ac 5c31616 c13b073 2be51ac fc3f34d 5c31616 fc3f34d 5c31616 2be51ac 9b73f19 5c31616 6b008eb 9b73f19 5c31616 fc3f34d 9b73f19 fc3f34d 2be51ac fc3f34d 2be51ac 9b73f19 fc3f34d 2be51ac fc3f34d e9be45f 40cec9a e9be45f 5c31616 e9be45f 5c31616 e9be45f | 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 | import os
import csv
import glob
from pypdf import PdfReader
from llama_index.core import VectorStoreIndex, Settings
from llama_index.core.node_parser import SentenceSplitter
from llama_index.core.schema import Document
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
# -------------------------------------------------------------------------
# LlamaIndex configuration
# - Embeddings: sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 (Supports 50+ languages)
# - LLM: disabled β we manage the LLM ourselves in src/llm.py
# -------------------------------------------------------------------------
Settings.embed_model = HuggingFaceEmbedding(
model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
)
Settings.llm = None
_index: VectorStoreIndex | None = None
# ββ File loaders βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def _load_csv(filepath: str) -> list[Document]:
"""Loads a CSV with 'Question' (and optionally 'Answer') columns."""
docs = []
try:
with open(filepath, newline="", encoding="utf-8") as fh:
reader = csv.DictReader(fh)
if reader.fieldnames is None or "Question" not in reader.fieldnames:
print(f"[RAG] Skipping {filepath}: no 'Question' column.")
return docs
has_answer = "Answer" in (reader.fieldnames or [])
for row in reader:
question = row.get("Question", "").strip()
answer = row.get("Answer", "").strip() if has_answer else ""
if question:
text = f"Q: {question}\nA: {answer}" if answer else f"Q: {question}"
docs.append(Document(text=text, metadata={"source": filepath}))
except Exception as exc:
print(f"[RAG] Error reading CSV {filepath}: {exc}")
return docs
def _load_pdf(filepath: str) -> list[Document]:
"""Extracts text from each page of a PDF as separate documents."""
docs = []
try:
reader = PdfReader(filepath)
basename = os.path.basename(filepath)
for i, page in enumerate(reader.pages):
text = page.extract_text()
if text and text.strip():
docs.append(Document(
text=text.strip(),
metadata={"source": basename, "page": i + 1},
))
print(f"[RAG] Loaded {len(docs)} pages from {basename}")
except Exception as exc:
print(f"[RAG] Error reading PDF {filepath}: {exc}")
return docs
def _load_text(filepath: str) -> list[Document]:
"""Loads a plain text file as a single document."""
docs = []
try:
with open(filepath, "r", encoding="utf-8") as fh:
text = fh.read().strip()
if text:
docs.append(Document(text=text, metadata={"source": filepath}))
except Exception as exc:
print(f"[RAG] Error reading text file {filepath}: {exc}")
return docs
# ββ Vector store init ββββββββββββββββββββββββββββββββββββββββββββββββββββ
def init_vector_store(data_path: str = "data") -> None:
"""
Scans the data directory and loads ALL supported files:
- .csv β FAQ-style (Question/Answer columns)
- .pdf β RBI guidelines, circulars, etc.
- .txt β Plain text documents
Builds an in-memory vector index from all loaded content.
"""
global _index
# Support both file path and directory path for backward compatibility
if os.path.isfile(data_path):
data_dir = os.path.dirname(data_path)
else:
data_dir = data_path
if not os.path.isdir(data_dir):
print(f"[RAG] Warning: data directory not found at '{data_dir}'. Vector store empty.")
return
documents: list[Document] = []
# Auto-discover all supported files
for pattern, loader in [("*.csv", _load_csv), ("*.pdf", _load_pdf), ("*.txt", _load_text)]:
for filepath in sorted(glob.glob(os.path.join(data_dir, pattern))):
loaded = loader(filepath)
documents.extend(loaded)
if not documents:
print("[RAG] Warning: No documents loaded from any file.")
return
try:
splitter = SentenceSplitter(chunk_size=512, chunk_overlap=50)
nodes = splitter.get_nodes_from_documents(documents)
_index = VectorStoreIndex(nodes)
print(f"[RAG] Loaded {len(documents)} documents β {len(nodes)} chunks into vector store.")
except Exception as exc:
print(f"[RAG] Error building vector index: {exc}")
# ββ Retrieval ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def retrieve_context(query: str, k: int = 5) -> str:
"""
Returns the top-k most relevant passages for a query string.
Falls back to an informative message if the index is not ready.
"""
if not _index:
return "Knowledge base not available."
if not query or not query.strip():
return "Knowledge base not available."
try:
retriever = _index.as_retriever(similarity_top_k=k)
results = retriever.retrieve(query)
if not results:
return "No relevant information found in the knowledge base."
return "\n\n---\n\n".join(node.get_content() for node in results)
except Exception as exc:
print(f"[RAG] Error during retrieval: {exc}")
return "Knowledge base lookup failed."
def retrieve_context_multi(english_query: str, hindi_text: str, k: int = 15) -> str:
"""
Searches using BOTH the English query and the original Hindi text,
deduplicates, and returns the top-k most relevant passages.
"""
if not _index:
return "Knowledge base not available."
seen_content: set[str] = set()
all_results = []
try:
retriever = _index.as_retriever(similarity_top_k=k)
# Search with English query
if english_query and english_query.strip():
for node in retriever.retrieve(english_query):
content = node.get_content()
if content not in seen_content:
seen_content.add(content)
all_results.append((node.get_score(), content))
# Search with Hindi text (backup)
if hindi_text and hindi_text.strip():
for node in retriever.retrieve(hindi_text):
content = node.get_content()
if content not in seen_content:
seen_content.add(content)
all_results.append((node.get_score(), content))
if not all_results:
return "No relevant information found in the knowledge base."
all_results.sort(key=lambda x: x[0], reverse=True)
top_results = [content for _, content in all_results[:k]]
print(f"[RAG] Retrieved {len(top_results)} unique passages from dual query")
return "\n\n---\n\n".join(top_results)
except Exception as exc:
print(f"[RAG] Error during retrieval: {exc}")
return "Knowledge base lookup failed."
|