NimrodDev commited on
Commit
33d913e
Β·
1 Parent(s): cd7e1d5

bullet-proof fetch + empty corpus fallback

Browse files
Files changed (1) hide show
  1. rag.py +37 -25
rag.py CHANGED
@@ -1,6 +1,6 @@
1
- # rag.py –- zero-disk, single-index, API-fetched text, offline runtime
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
- TEXT_FILE = "ld_events_text.json" # local file created at build time
18
- EMBED_MODEL = "sentence-transformers/all-MiniLM-L6-v2"
19
- LLM_MODEL = "microsoft/DialoGPT-medium"
 
 
 
 
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 – UNBIASED GREETING
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
- # RAM-ONLY DOCUMENT LOADER – LOCAL JSON CREATED AT BUILD TIME
95
  # ------------------------------------------------------------------
96
- def load_texts() -> List[str]:
97
- with open(os.path.join(os.path.dirname(__file__), TEXT_FILE), encoding="utf-8") as f:
98
- return [row["text"] for row in json.load(f) if row.get("text")]
99
-
100
- # ------------------------------------------------------------------
101
- # SINGLE-BUILD VECTOR STORE (cached for life of worker)
 
 
 
 
 
 
 
 
 
 
102
  # ------------------------------------------------------------------
103
  @lru_cache(maxsize=1)
104
  def get_vectorstore() -> FAISS:
105
- texts = load_texts()
106
- splitter = RecursiveCharacterTextSplitter(chunk_size=600, chunk_overlap=50)
107
- docs = splitter.create_documents(texts, metadatas=[{"source": "api"}] * len(texts))
 
108
 
109
- # use pre-cached model dir (read-only)
110
- os.environ["HF_HOME"] = "/code/.cache"
111
  embeddings = HuggingFaceEmbeddings(model_name=EMBED_MODEL)
112
- return FAISS.from_documents(docs, embeddings) # built ONCE per worker
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 – re-uses the *same* index every call
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()