Spaces:
Runtime error
Runtime error
| import os | |
| import logging | |
| import signal | |
| import sys | |
| import zipfile | |
| import gdown | |
| import time | |
| from google import genai | |
| from langchain_community.vectorstores import Chroma | |
| from langchain_community.embeddings import HuggingFaceEmbeddings | |
| # ============================== | |
| # Logging | |
| # ============================== | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| # ============================== | |
| # Gemini Client | |
| # ============================== | |
| GEMINI_API_KEY = os.getenv("GEMINI_API_KEY") | |
| if not GEMINI_API_KEY: | |
| raise ValueError("GEMINI_API_KEY is missing") | |
| client = genai.Client(api_key=GEMINI_API_KEY) | |
| MODEL_NAME = "models/gemini-2.5-flash-lite" | |
| # ============================== | |
| # Gemini Call (with retry) | |
| # ============================== | |
| def call_gemini(prompt: str, retries: int = 3): | |
| for attempt in range(retries): | |
| try: | |
| response = client.models.generate_content( | |
| model=MODEL_NAME, | |
| contents=prompt | |
| ) | |
| if response and response.text: | |
| return response.text | |
| return "Empty response from model." | |
| except Exception as e: | |
| msg = str(e) | |
| # handling quota error | |
| if "429" in msg or "RESOURCE_EXHAUSTED" in msg: | |
| wait_time = 5 * (attempt + 1) | |
| logger.warning(f"Quota hit. Retrying in {wait_time}s...") | |
| time.sleep(wait_time) | |
| continue | |
| logger.error(f"Gemini error: {e}") | |
| break | |
| return "Model is temporarily unavailable. Please try again later." | |
| # ============================== | |
| # Download Vector DB | |
| # ============================== | |
| if not os.path.exists("chroma_db"): | |
| print("Downloading vector DB...") | |
| url = "https://drive.google.com/uc?id=1yelCLmiRD2Qds-_8EsnKzUNY7uPT5xMg" | |
| gdown.download(url, "chroma_db.zip", quiet=False) | |
| print("Extracting DB...") | |
| with zipfile.ZipFile("chroma_db.zip", "r") as zip_ref: | |
| zip_ref.extractall(".") | |
| print("Vector DB Ready ✅") | |
| # ============================== | |
| # Load Vector DB | |
| # ============================== | |
| embedding = HuggingFaceEmbeddings( | |
| model_name="sentence-transformers/all-MiniLM-L6-v2" | |
| ) | |
| vector_db = Chroma( | |
| persist_directory="./chroma_db", | |
| embedding_function=embedding | |
| ) | |
| print("Vector DB Loaded ✅") | |
| # ============================== | |
| # Conversation Memory | |
| # ============================== | |
| class Conversation: | |
| def __init__(self): | |
| self.history = [] | |
| def add_user_message(self, message): | |
| self.history.append({"role": "user", "content": message}) | |
| def add_bot_message(self, message): | |
| self.history.append({"role": "assistant", "content": message}) | |
| def get_history(self): | |
| return self.history | |
| def clear_history(self): | |
| self.history = [] | |
| conversation = Conversation() | |
| # ============================== | |
| # Get Context | |
| # ============================== | |
| def get_relevant_context_from_db(query): | |
| try: | |
| results = vector_db.similarity_search(query, k=2) | |
| context = "\n\n".join([ | |
| doc.page_content[:1200] | |
| for doc in results | |
| ]) | |
| sources = list(set([ | |
| doc.metadata.get("source", "") | |
| for doc in results | |
| ])) | |
| return context, sources | |
| except Exception as e: | |
| logger.error(f"Vector DB error: {e}") | |
| return "", [] | |
| # ============================== | |
| # Prompt Builder | |
| # ============================== | |
| def build_prompt(query, context, history): | |
| history_text = "\n".join([ | |
| f"{msg['role']}: {msg['content']}" | |
| for msg in history[-3:] | |
| ]) if history else "" | |
| prompt = f""" | |
| You are an expert nutritionist and dietitian. | |
| RULES: | |
| - Answer ONLY the user question | |
| - Keep answers clean and organized | |
| - Use bullet points when helpful | |
| - Do NOT repeat sentences | |
| - Do NOT mention Ramadan unless user asks | |
| - If user asks for diet: | |
| give breakfast, lunch, dinner, snacks | |
| - If user asks for foods: | |
| include protein/calories if possible | |
| - If user writes Arabic: | |
| answer in Arabic | |
| - Keep answers practical and realistic | |
| Conversation: | |
| {history_text} | |
| Context: | |
| {context} | |
| User Question: | |
| {query} | |
| Answer: | |
| """ | |
| return prompt | |
| # ============================== | |
| # Main Function | |
| # ============================== | |
| def generate_chat_answer(query, history=[]): | |
| try: | |
| logger.info(f"Query: {query}") | |
| context, sources = get_relevant_context_from_db(query) | |
| prompt = build_prompt( | |
| query, | |
| context, | |
| history | |
| ) | |
| answer = call_gemini(prompt).strip() | |
| answer = answer.replace("**", "").replace("##", "") | |
| if not answer: | |
| answer = "Sorry, no response generated." | |
| # memory | |
| conversation.add_user_message(query) | |
| conversation.add_bot_message(answer) | |
| # add sources | |
| if sources: | |
| answer += "\n\nSources:\n" + "\n".join( | |
| [f"- {s}" for s in sources if s] | |
| ) | |
| return answer | |
| except Exception as e: | |
| logger.error(f"Error: {e}", exc_info=True) | |
| return "Something went wrong." | |
| # ============================== | |
| # Graceful shutdown | |
| # ============================== | |
| def signal_handler(sig, frame): | |
| print("\nShutting down...") | |
| sys.exit(0) | |
| signal.signal(signal.SIGINT, signal_handler) |