clean final: HF Inference API embeddings (no disk)
Browse files
rag.py
CHANGED
|
@@ -6,20 +6,17 @@ from typing import List, Tuple
|
|
| 6 |
|
| 7 |
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
| 8 |
from langchain_community.vectorstores import FAISS
|
| 9 |
-
from langchain_huggingface import
|
| 10 |
from langchain_core.prompts import PromptTemplate
|
| 11 |
from langchain.chains import RetrievalQA
|
| 12 |
from supabase import create_client
|
| 13 |
|
| 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")
|
|
@@ -27,24 +24,20 @@ HF_TOKEN = os.getenv("HF_TOKEN")
|
|
| 27 |
|
| 28 |
supabase = create_client(SUPABASE_URL, SUPABASE_KEY)
|
| 29 |
|
| 30 |
-
# ------------------------------------------------------------------
|
| 31 |
-
|
| 32 |
-
# ------------------------------------------------------------------
|
| 33 |
GREETING_RE = re.compile(r"\b(hi|hello|hey|good morning|good afternoon|good evening)\b", re.I)
|
| 34 |
THANKS_RE = re.compile(r"\b(thank|thanks|appreciate)\b", re.I)
|
| 35 |
BYE_RE = re.compile(r"\b(bye|goodbye|see you|later)\b", re.I)
|
| 36 |
MONEY_RE = re.compile(r"\b(price|cost|budget|cheap|expensive|money|usd|ksh|payment|deposit)\b", re.I)
|
| 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": {
|
| 44 |
"greeting": "Hello! 👋 I’m Amina, your assistant for *LD Events* (weddings, graduations, corporate events) "
|
| 45 |
"and *Lamaki Designs* (construction & architectural plans).\n\n"
|
| 46 |
-
"Which service would you like to know about?\n\n"
|
| 47 |
-
,
|
| 48 |
"money": "Our pricing depends on venue / project size. Please share a few details so we can give you a tailored quote.",
|
| 49 |
"complain": "We’re sorry to hear this. A senior agent will contact you within 30 minutes to resolve the issue.",
|
| 50 |
"thanks": "You’re welcome! If you need anything else, just text back.",
|
|
@@ -54,8 +47,7 @@ FALLBACKS = {
|
|
| 54 |
"Lamaki Designs": {
|
| 55 |
"greeting": "Karibu! 🏗️ I’m Amina, your assistant for *Lamaki Designs* (construction, architectural plans, project management) "
|
| 56 |
"and *LD Events* (weddings, graduations, corporate events).\n\n"
|
| 57 |
-
"Which service would you like to know about?\n\n"
|
| 58 |
-
,
|
| 59 |
"money": "Cost varies by project size and materials. Kindly share your plot size / plan so we can estimate for you.",
|
| 60 |
"complain": "We apologise for the inconvenience. Our site manager will call you within 30 minutes to sort it out.",
|
| 61 |
"thanks": "Asante! Feel free to text any time.",
|
|
@@ -64,9 +56,7 @@ FALLBACKS = {
|
|
| 64 |
}
|
| 65 |
}
|
| 66 |
|
| 67 |
-
# ------------------------------------------------------------------
|
| 68 |
-
# HELPERS
|
| 69 |
-
# ------------------------------------------------------------------
|
| 70 |
def _company_from_text(text: str) -> str:
|
| 71 |
t = text.lower()
|
| 72 |
if any(k in t for k in ("ld events", "event", "wedding", "venue", "graduation")):
|
|
@@ -86,9 +76,7 @@ def _detect_intent(text: str) -> str:
|
|
| 86 |
def _fallback_answer(company: str, intent: str) -> str:
|
| 87 |
return FALLBACKS[company].get(intent, FALLBACKS[company]["default"])
|
| 88 |
|
| 89 |
-
# ------------------------------------------------------------------
|
| 90 |
-
# BULLET-PROOF ONLINE FETCH – RETURNS EMPTY LIST ON ANY ERROR
|
| 91 |
-
# ------------------------------------------------------------------
|
| 92 |
@lru_cache(maxsize=1)
|
| 93 |
def get_texts() -> List[str]:
|
| 94 |
try:
|
|
@@ -103,38 +91,30 @@ def get_texts() -> List[str]:
|
|
| 103 |
print(f"⚠ Dataset fetch failed: {e} – using empty corpus")
|
| 104 |
return []
|
| 105 |
|
| 106 |
-
# ------------------------------------------------------------------
|
| 107 |
-
# ------------------------------------------------------------------
|
| 108 |
-
# ------------------------------------------------------------------
|
| 109 |
@lru_cache(maxsize=1)
|
| 110 |
def get_vectorstore() -> FAISS:
|
| 111 |
texts = get_texts()
|
| 112 |
|
| 113 |
-
#
|
| 114 |
-
# --- FINAL: HF Inference Providers (no disk, no cache) ----------------
|
| 115 |
-
# --- FINAL: HF Inference Providers (no disk, no cache) ------------------
|
| 116 |
from langchain_huggingface import HuggingFaceInferenceAPIEmbeddings
|
| 117 |
embeddings = HuggingFaceInferenceAPIEmbeddings(
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
)
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
# ------------------------------------------------------------------------
|
| 124 |
-
|
| 125 |
-
if not texts: # no data → empty FAISS
|
| 126 |
return FAISS.from_texts([""], embeddings) # dummy
|
| 127 |
|
| 128 |
splitter = RecursiveCharacterTextSplitter(chunk_size=600, chunk_overlap=50)
|
| 129 |
docs = splitter.create_documents(texts, metadatas=[{"source": DATASET}] * len(texts))
|
| 130 |
return FAISS.from_documents(docs, embeddings)
|
| 131 |
|
| 132 |
-
# ------------------------------------------------------------------
|
| 133 |
-
# ------------------------------------------------------------------
|
| 134 |
@lru_cache(maxsize=1)
|
| 135 |
def get_llm():
|
| 136 |
return HuggingFaceEndpoint(
|
| 137 |
-
repo_id=
|
| 138 |
temperature=0.1,
|
| 139 |
max_new_tokens=150,
|
| 140 |
huggingfacehub_api_token=HF_TOKEN
|
|
@@ -146,29 +126,19 @@ Context: {context}
|
|
| 146 |
Question: {question}
|
| 147 |
Answer:""")
|
| 148 |
|
| 149 |
-
# ------------------------------------------------------------------
|
| 150 |
-
# MAIN ENTRY – NEVER CRASHES
|
| 151 |
-
# ------------------------------------------------------------------
|
| 152 |
def ask_question(phone: str, question: str) -> Tuple[str, List]:
|
| 153 |
intent = _detect_intent(question)
|
| 154 |
company = _company_from_text(question)
|
| 155 |
|
| 156 |
-
|
| 157 |
-
if intent == "greeting":
|
| 158 |
-
answer = _fallback_answer(company, "greeting")
|
| 159 |
-
_save_chat(phone, question, answer)
|
| 160 |
-
return answer, []
|
| 161 |
-
|
| 162 |
-
# other small-talk
|
| 163 |
-
if intent in ("thanks", "bye"):
|
| 164 |
answer = _fallback_answer(company, intent)
|
| 165 |
_save_chat(phone, question, answer)
|
| 166 |
return answer, []
|
| 167 |
|
| 168 |
-
# RAG path – same index every call (empty index → no docs → fallback)
|
| 169 |
vs = get_vectorstore()
|
| 170 |
docs = vs.similarity_search(question, k=3)
|
| 171 |
-
if not docs or docs[0].page_content.strip() == "":
|
| 172 |
answer = _fallback_answer(company, intent if intent in ("money", "complain") else "default")
|
| 173 |
_save_chat(phone, question, answer)
|
| 174 |
return answer, []
|
|
|
|
| 6 |
|
| 7 |
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
| 8 |
from langchain_community.vectorstores import FAISS
|
| 9 |
+
from langchain_huggingface import HuggingFaceEndpoint
|
| 10 |
from langchain_core.prompts import PromptTemplate
|
| 11 |
from langchain.chains import RetrievalQA
|
| 12 |
from supabase import create_client
|
| 13 |
|
| 14 |
+
# ------------------------------------------------------------------ CONFIG
|
|
|
|
|
|
|
| 15 |
DATASET_API = "https://datasets-server.huggingface.co/rows"
|
| 16 |
DATASET = "NimrodDev/LD_Events2"
|
| 17 |
CONFIG = "default"
|
| 18 |
SPLIT = "train"
|
| 19 |
LIMIT = 500
|
|
|
|
| 20 |
LLM_MODEL = "microsoft/DialoGPT-medium"
|
| 21 |
SUPABASE_URL = os.getenv("SUPABASE_URL")
|
| 22 |
SUPABASE_KEY = os.getenv("SUPABASE_KEY")
|
|
|
|
| 24 |
|
| 25 |
supabase = create_client(SUPABASE_URL, SUPABASE_KEY)
|
| 26 |
|
| 27 |
+
# ------------------------------------------------------------------ INTENT
|
| 28 |
+
import re
|
|
|
|
| 29 |
GREETING_RE = re.compile(r"\b(hi|hello|hey|good morning|good afternoon|good evening)\b", re.I)
|
| 30 |
THANKS_RE = re.compile(r"\b(thank|thanks|appreciate)\b", re.I)
|
| 31 |
BYE_RE = re.compile(r"\b(bye|goodbye|see you|later)\b", re.I)
|
| 32 |
MONEY_RE = re.compile(r"\b(price|cost|budget|cheap|expensive|money|usd|ksh|payment|deposit)\b", re.I)
|
| 33 |
COMPLAIN_RE = re.compile(r"\b(complain|bad|terrible|awful|disappointed|angry|slow|rude)\b", re.I)
|
| 34 |
|
| 35 |
+
# ------------------------------------------------------------------ FALLBACKS
|
|
|
|
|
|
|
| 36 |
FALLBACKS = {
|
| 37 |
"LD Events": {
|
| 38 |
"greeting": "Hello! 👋 I’m Amina, your assistant for *LD Events* (weddings, graduations, corporate events) "
|
| 39 |
"and *Lamaki Designs* (construction & architectural plans).\n\n"
|
| 40 |
+
"Which service would you like to know about?\n\n",
|
|
|
|
| 41 |
"money": "Our pricing depends on venue / project size. Please share a few details so we can give you a tailored quote.",
|
| 42 |
"complain": "We’re sorry to hear this. A senior agent will contact you within 30 minutes to resolve the issue.",
|
| 43 |
"thanks": "You’re welcome! If you need anything else, just text back.",
|
|
|
|
| 47 |
"Lamaki Designs": {
|
| 48 |
"greeting": "Karibu! 🏗️ I’m Amina, your assistant for *Lamaki Designs* (construction, architectural plans, project management) "
|
| 49 |
"and *LD Events* (weddings, graduations, corporate events).\n\n"
|
| 50 |
+
"Which service would you like to know about?\n\n",
|
|
|
|
| 51 |
"money": "Cost varies by project size and materials. Kindly share your plot size / plan so we can estimate for you.",
|
| 52 |
"complain": "We apologise for the inconvenience. Our site manager will call you within 30 minutes to sort it out.",
|
| 53 |
"thanks": "Asante! Feel free to text any time.",
|
|
|
|
| 56 |
}
|
| 57 |
}
|
| 58 |
|
| 59 |
+
# ------------------------------------------------------------------ HELPERS
|
|
|
|
|
|
|
| 60 |
def _company_from_text(text: str) -> str:
|
| 61 |
t = text.lower()
|
| 62 |
if any(k in t for k in ("ld events", "event", "wedding", "venue", "graduation")):
|
|
|
|
| 76 |
def _fallback_answer(company: str, intent: str) -> str:
|
| 77 |
return FALLBACKS[company].get(intent, FALLBACKS[company]["default"])
|
| 78 |
|
| 79 |
+
# ------------------------------------------------------------------ DATA FETCH
|
|
|
|
|
|
|
| 80 |
@lru_cache(maxsize=1)
|
| 81 |
def get_texts() -> List[str]:
|
| 82 |
try:
|
|
|
|
| 91 |
print(f"⚠ Dataset fetch failed: {e} – using empty corpus")
|
| 92 |
return []
|
| 93 |
|
| 94 |
+
# ------------------------------------------------------------------ EMBEDDINGS
|
|
|
|
|
|
|
| 95 |
@lru_cache(maxsize=1)
|
| 96 |
def get_vectorstore() -> FAISS:
|
| 97 |
texts = get_texts()
|
| 98 |
|
| 99 |
+
# HF Inference Providers – zero disk, zero cache
|
|
|
|
|
|
|
| 100 |
from langchain_huggingface import HuggingFaceInferenceAPIEmbeddings
|
| 101 |
embeddings = HuggingFaceInferenceAPIEmbeddings(
|
| 102 |
+
api_key=HF_TOKEN,
|
| 103 |
+
model_name="sentence-transformers/all-MiniLM-L6-v2"
|
| 104 |
+
)
|
| 105 |
+
|
| 106 |
+
if not texts:
|
|
|
|
|
|
|
|
|
|
| 107 |
return FAISS.from_texts([""], embeddings) # dummy
|
| 108 |
|
| 109 |
splitter = RecursiveCharacterTextSplitter(chunk_size=600, chunk_overlap=50)
|
| 110 |
docs = splitter.create_documents(texts, metadatas=[{"source": DATASET}] * len(texts))
|
| 111 |
return FAISS.from_documents(docs, embeddings)
|
| 112 |
|
| 113 |
+
# ------------------------------------------------------------------ LLM
|
|
|
|
| 114 |
@lru_cache(maxsize=1)
|
| 115 |
def get_llm():
|
| 116 |
return HuggingFaceEndpoint(
|
| 117 |
+
repo_id="microsoft/DialoGPT-medium",
|
| 118 |
temperature=0.1,
|
| 119 |
max_new_tokens=150,
|
| 120 |
huggingfacehub_api_token=HF_TOKEN
|
|
|
|
| 126 |
Question: {question}
|
| 127 |
Answer:""")
|
| 128 |
|
| 129 |
+
# ------------------------------------------------------------------ MAIN
|
|
|
|
|
|
|
| 130 |
def ask_question(phone: str, question: str) -> Tuple[str, List]:
|
| 131 |
intent = _detect_intent(question)
|
| 132 |
company = _company_from_text(question)
|
| 133 |
|
| 134 |
+
if intent in ("greeting", "thanks", "bye"):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
answer = _fallback_answer(company, intent)
|
| 136 |
_save_chat(phone, question, answer)
|
| 137 |
return answer, []
|
| 138 |
|
|
|
|
| 139 |
vs = get_vectorstore()
|
| 140 |
docs = vs.similarity_search(question, k=3)
|
| 141 |
+
if not docs or docs[0].page_content.strip() == "":
|
| 142 |
answer = _fallback_answer(company, intent if intent in ("money", "complain") else "default")
|
| 143 |
_save_chat(phone, question, answer)
|
| 144 |
return answer, []
|