bullet-proof fetch + empty corpus fallback
Browse files
rag.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
-
# rag.py β-
|
| 2 |
from __future__ import annotations
|
| 3 |
-
import os, re, json
|
| 4 |
from functools import lru_cache
|
| 5 |
from typing import List, Tuple
|
| 6 |
|
|
@@ -14,9 +14,13 @@ from supabase import create_client
|
|
| 14 |
# ------------------------------------------------------------------
|
| 15 |
# CONFIG
|
| 16 |
# ------------------------------------------------------------------
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
SUPABASE_URL = os.getenv("SUPABASE_URL")
|
| 21 |
SUPABASE_KEY = os.getenv("SUPABASE_KEY")
|
| 22 |
HF_TOKEN = os.getenv("HF_TOKEN")
|
|
@@ -33,7 +37,7 @@ MONEY_RE = re.compile(r"\b(price|cost|budget|cheap|expensive|money|usd|ksh|p
|
|
| 33 |
COMPLAIN_RE = re.compile(r"\b(complain|bad|terrible|awful|disappointed|angry|slow|rude)\b", re.I)
|
| 34 |
|
| 35 |
# ------------------------------------------------------------------
|
| 36 |
-
# FALLBACKS
|
| 37 |
# ------------------------------------------------------------------
|
| 38 |
FALLBACKS = {
|
| 39 |
"LD Events": {
|
|
@@ -91,25 +95,36 @@ def _fallback_answer(company: str, intent: str) -> str:
|
|
| 91 |
return FALLBACKS[company].get(intent, FALLBACKS[company]["default"])
|
| 92 |
|
| 93 |
# ------------------------------------------------------------------
|
| 94 |
-
#
|
| 95 |
# ------------------------------------------------------------------
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
# ------------------------------------------------------------------
|
| 103 |
@lru_cache(maxsize=1)
|
| 104 |
def get_vectorstore() -> FAISS:
|
| 105 |
-
texts =
|
| 106 |
-
|
| 107 |
-
|
|
|
|
| 108 |
|
| 109 |
-
|
| 110 |
-
|
| 111 |
embeddings = HuggingFaceEmbeddings(model_name=EMBED_MODEL)
|
| 112 |
-
return FAISS.from_documents(docs, embeddings)
|
| 113 |
|
| 114 |
# ------------------------------------------------------------------
|
| 115 |
# LLM
|
|
@@ -130,7 +145,7 @@ Question: {question}
|
|
| 130 |
Answer:""")
|
| 131 |
|
| 132 |
# ------------------------------------------------------------------
|
| 133 |
-
# MAIN ENTRY
|
| 134 |
# ------------------------------------------------------------------
|
| 135 |
def ask_question(phone: str, question: str) -> Tuple[str, List]:
|
| 136 |
intent = _detect_intent(question)
|
|
@@ -148,10 +163,10 @@ def ask_question(phone: str, question: str) -> Tuple[str, List]:
|
|
| 148 |
_save_chat(phone, question, answer)
|
| 149 |
return answer, []
|
| 150 |
|
| 151 |
-
# RAG path β
|
| 152 |
vs = get_vectorstore()
|
| 153 |
docs = vs.similarity_search(question, k=3)
|
| 154 |
-
if not docs:
|
| 155 |
answer = _fallback_answer(company, intent if intent in ("money", "complain") else "default")
|
| 156 |
_save_chat(phone, question, answer)
|
| 157 |
return answer, []
|
|
@@ -167,9 +182,6 @@ def ask_question(phone: str, question: str) -> Tuple[str, List]:
|
|
| 167 |
_save_chat(phone, question, answer)
|
| 168 |
return answer, result.get("source_documents", [])
|
| 169 |
|
| 170 |
-
# ------------------------------------------------------------------
|
| 171 |
-
# CHAT PERSISTENCE
|
| 172 |
-
# ------------------------------------------------------------------
|
| 173 |
def _save_chat(phone: str, q: str, a: str) -> None:
|
| 174 |
supabase.table("chat_memory").insert({"user_phone": phone, "role": "user", "message": q}).execute()
|
| 175 |
supabase.table("chat_memory").insert({"user_phone": phone, "role": "assistant", "message": a}).execute()
|
|
|
|
| 1 |
+
# rag.py β- bullet-proof: online fetch with fallback on any error
|
| 2 |
from __future__ import annotations
|
| 3 |
+
import os, re, json, requests
|
| 4 |
from functools import lru_cache
|
| 5 |
from typing import List, Tuple
|
| 6 |
|
|
|
|
| 14 |
# ------------------------------------------------------------------
|
| 15 |
# CONFIG
|
| 16 |
# ------------------------------------------------------------------
|
| 17 |
+
DATASET_API = "https://datasets-server.huggingface.co/rows"
|
| 18 |
+
DATASET = "NimrodDev/LD_Events2"
|
| 19 |
+
CONFIG = "default"
|
| 20 |
+
SPLIT = "train"
|
| 21 |
+
LIMIT = 500
|
| 22 |
+
EMBED_MODEL = "sentence-transformers/all-MiniLM-L6-v2"
|
| 23 |
+
LLM_MODEL = "microsoft/DialoGPT-medium"
|
| 24 |
SUPABASE_URL = os.getenv("SUPABASE_URL")
|
| 25 |
SUPABASE_KEY = os.getenv("SUPABASE_KEY")
|
| 26 |
HF_TOKEN = os.getenv("HF_TOKEN")
|
|
|
|
| 37 |
COMPLAIN_RE = re.compile(r"\b(complain|bad|terrible|awful|disappointed|angry|slow|rude)\b", re.I)
|
| 38 |
|
| 39 |
# ------------------------------------------------------------------
|
| 40 |
+
# FALLBACKS
|
| 41 |
# ------------------------------------------------------------------
|
| 42 |
FALLBACKS = {
|
| 43 |
"LD Events": {
|
|
|
|
| 95 |
return FALLBACKS[company].get(intent, FALLBACKS[company]["default"])
|
| 96 |
|
| 97 |
# ------------------------------------------------------------------
|
| 98 |
+
# BULLET-PROOF ONLINE FETCH β RETURNS EMPTY LIST ON ANY ERROR
|
| 99 |
# ------------------------------------------------------------------
|
| 100 |
+
@lru_cache(maxsize=1)
|
| 101 |
+
def get_texts() -> List[str]:
|
| 102 |
+
try:
|
| 103 |
+
url = f"{DATASET_API}?dataset={DATASET}&config={CONFIG}&split={SPLIT}&offset=0&length={LIMIT}"
|
| 104 |
+
r = requests.get(url, timeout=60)
|
| 105 |
+
r.raise_for_status()
|
| 106 |
+
rows = r.json()["rows"]
|
| 107 |
+
texts = [row["row"]["text"] for row in rows if row["row"].get("text")]
|
| 108 |
+
print(f"β Fetched {len(texts)} texts from {DATASET}")
|
| 109 |
+
return texts
|
| 110 |
+
except Exception as e:
|
| 111 |
+
print(f"β Dataset fetch failed: {e} β using empty corpus")
|
| 112 |
+
return []
|
| 113 |
+
|
| 114 |
+
# ------------------------------------------------------------------
|
| 115 |
+
# RAM-ONLY VECTOR STORE β HANDLES EMPTY CORPUS GRACEFULLY
|
| 116 |
# ------------------------------------------------------------------
|
| 117 |
@lru_cache(maxsize=1)
|
| 118 |
def get_vectorstore() -> FAISS:
|
| 119 |
+
texts = get_texts()
|
| 120 |
+
if not texts: # β no data β return empty FAISS
|
| 121 |
+
embeddings = HuggingFaceEmbeddings(model_name=EMBED_MODEL)
|
| 122 |
+
return FAISS.from_texts([""], embeddings) # dummy, retriever will be empty
|
| 123 |
|
| 124 |
+
splitter = RecursiveCharacterTextSplitter(chunk_size=600, chunk_overlap=50)
|
| 125 |
+
docs = splitter.create_documents(texts, metadatas=[{"source": DATASET}] * len(texts))
|
| 126 |
embeddings = HuggingFaceEmbeddings(model_name=EMBED_MODEL)
|
| 127 |
+
return FAISS.from_documents(docs, embeddings)
|
| 128 |
|
| 129 |
# ------------------------------------------------------------------
|
| 130 |
# LLM
|
|
|
|
| 145 |
Answer:""")
|
| 146 |
|
| 147 |
# ------------------------------------------------------------------
|
| 148 |
+
# MAIN ENTRY β NEVER CRASHES
|
| 149 |
# ------------------------------------------------------------------
|
| 150 |
def ask_question(phone: str, question: str) -> Tuple[str, List]:
|
| 151 |
intent = _detect_intent(question)
|
|
|
|
| 163 |
_save_chat(phone, question, answer)
|
| 164 |
return answer, []
|
| 165 |
|
| 166 |
+
# RAG path β same index every call (empty index β no docs β fallback)
|
| 167 |
vs = get_vectorstore()
|
| 168 |
docs = vs.similarity_search(question, k=3)
|
| 169 |
+
if not docs or docs[0].page_content.strip() == "": # empty dummy
|
| 170 |
answer = _fallback_answer(company, intent if intent in ("money", "complain") else "default")
|
| 171 |
_save_chat(phone, question, answer)
|
| 172 |
return answer, []
|
|
|
|
| 182 |
_save_chat(phone, question, answer)
|
| 183 |
return answer, result.get("source_documents", [])
|
| 184 |
|
|
|
|
|
|
|
|
|
|
| 185 |
def _save_chat(phone: str, q: str, a: str) -> None:
|
| 186 |
supabase.table("chat_memory").insert({"user_phone": phone, "role": "user", "message": q}).execute()
|
| 187 |
supabase.table("chat_memory").insert({"user_phone": phone, "role": "assistant", "message": a}).execute()
|