Spaces:
Build error
Build error
| import os | |
| import requests | |
| import json | |
| import time | |
| # βββ Backend credentials (read once at module load) βββββββββββββββββββββββββββ | |
| OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", "") | |
| OPENAI_MODEL = os.environ.get("OPENAI_MODEL", "gpt-4o-mini") | |
| GROQ_API_KEY = os.environ.get("GROQ_API_KEY", "") | |
| USE_GROQ = bool(GROQ_API_KEY) | |
| OLLAMA_URL = "http://127.0.0.1:11434" | |
| # Check active backends once at load time to prevent timeout delays during requests. | |
| # Priority: OpenAI β Groq β local Ollama | |
| AI_BACKEND_AVAILABLE = False | |
| if OPENAI_API_KEY or GROQ_API_KEY: | |
| AI_BACKEND_AVAILABLE = True | |
| else: | |
| try: | |
| # Fast 0.5s ping to local Ollama | |
| response = requests.get(f"{OLLAMA_URL}/", timeout=0.5) | |
| AI_BACKEND_AVAILABLE = (response.status_code == 200) | |
| except Exception: | |
| AI_BACKEND_AVAILABLE = False | |
| def has_active_ai_backend() -> bool: | |
| """Returns True if OpenAI, Groq, or local Ollama is active and reachable.""" | |
| return AI_BACKEND_AVAILABLE | |
| BANKING_KEYWORDS = [ | |
| "account", "loan", "card", "balance", | |
| "transfer", "bank", "interest", "emi", | |
| "credit", "debit", "kyc", "upi", "cheque", | |
| "deposit", "fd", "rd", "branch", "ifsc", | |
| "transaction", "payment", "savings", "checking", | |
| "mortgage", "investment", "fintech", "wallet", | |
| "rate", "rates", "support", "customer", "care", | |
| "help", "contact", "helpline", "number", "call", | |
| "document", "required", "identity", "proof", "open" | |
| ] | |
| SYSTEM_PROMPT = """You are BankBot, a professional banking assistant for Central Bank. | |
| You ONLY answer banking-related questions. If the question is not related to banking, politely refuse. | |
| Never answer questions about politics, sports, entertainment, coding, or personal advice. | |
| CORE GUIDELINES: | |
| 1. ALWAYS communicate in {language}. | |
| 2. CONTEXT AWARENESS: Use the provided chat history to understand follow-up questions. For example, if the user asks "What is the interest rate?" and then "for home loan", you must understand they are asking about home loan interest rates. | |
| 3. CLARIFYING QUESTIONS: If a user's query is ambiguous (e.g., "how much?"), ask for missing details (e.g., "How much for what service? Balance check or loan EMI?"). | |
| 4. CALCULATIONS: Perform financial calculations (EMI, Interest, Eligibility) if information is provided. | |
| 5. DOCUMENT ANALYSIS: If text from a PDF statement is provided, summarize it or answer specific questions about it. | |
| 6. PROFESSIONALISM: Maintain a helpful, formal, and secure tone.""" | |
| OLLAMA_URL = "http://127.0.0.1:11434" | |
| DEFAULT_OLLAMA_MODEL = os.environ.get("OLLAMA_MODEL", "llama3:latest") | |
| def is_banking_query(user_input): | |
| input_lower = user_input.lower() | |
| return any(word in input_lower for word in BANKING_KEYWORDS) | |
| def get_active_backend(): | |
| """Returns the highest-priority available backend name.""" | |
| if OPENAI_API_KEY: | |
| return "openai" | |
| if USE_GROQ: | |
| return "groq" | |
| return "ollama" | |
| def _build_messages(prompt, history=None, language="English"): | |
| sys_prompt = SYSTEM_PROMPT.format(language=language) | |
| messages = [{"role": "system", "content": sys_prompt}] | |
| if history: | |
| for msg in history[-10:]: | |
| if msg.get("role") and msg.get("content"): | |
| messages.append({"role": msg["role"], "content": msg["content"]}) | |
| messages.append({"role": "user", "content": prompt}) | |
| return messages | |
| def _get_available_ollama_models(): | |
| try: | |
| response = requests.get(f"{OLLAMA_URL}/api/tags", timeout=5) | |
| response.raise_for_status() | |
| data = response.json() | |
| return [model.get("name", "") for model in data.get("models", []) if model.get("name")] | |
| except Exception as e: | |
| print(f"Ollama model discovery error: {e}") | |
| return [] | |
| def _resolve_ollama_model(requested_model): | |
| available_models = _get_available_ollama_models() | |
| if not available_models: | |
| return requested_model | |
| if requested_model in available_models: | |
| return requested_model | |
| base_requested_model = requested_model.split(":", 1)[0] | |
| for candidate in available_models: | |
| if candidate.split(":", 1)[0] == base_requested_model: | |
| return candidate | |
| return available_models[0] | |
| def _ollama_error_message(model, error): | |
| return ( | |
| f"Ollama request failed for model '{model}': {error}. " | |
| "The Ollama server is reachable, but the model backend crashed internally. " | |
| "Try `ollama run llama3`, and if that fails restart Ollama with " | |
| "`taskkill /F /IM ollama.exe` followed by `ollama serve`." | |
| ) | |
| # βββ OpenAI Functions ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def get_openai_response(prompt, history=None, model=None, language="English"): | |
| """Fetches a response from the OpenAI API (gpt-4o-mini by default).""" | |
| if not OPENAI_API_KEY: | |
| return None | |
| try: | |
| from openai import OpenAI | |
| client = OpenAI(api_key=OPENAI_API_KEY) | |
| target_model = model or OPENAI_MODEL | |
| sys_prompt = SYSTEM_PROMPT.format(language=language) | |
| messages = [{"role": "system", "content": sys_prompt}] | |
| if history: | |
| for msg in history[-10:]: | |
| if msg.get("role") and msg.get("content"): | |
| messages.append({"role": msg["role"], "content": msg["content"]}) | |
| messages.append({"role": "user", "content": prompt}) | |
| response = client.chat.completions.create( | |
| model=target_model, | |
| messages=messages, | |
| temperature=0.1, | |
| max_tokens=1000, | |
| ) | |
| return response.choices[0].message.content | |
| except Exception as e: | |
| print(f"OpenAI Error: {e}") | |
| return None | |
| def stream_openai_response(prompt, history=None, model=None, language="English"): | |
| """Yields streamed response chunks from the OpenAI API.""" | |
| if not OPENAI_API_KEY: | |
| return | |
| try: | |
| from openai import OpenAI | |
| client = OpenAI(api_key=OPENAI_API_KEY) | |
| target_model = model or OPENAI_MODEL | |
| sys_prompt = SYSTEM_PROMPT.format(language=language) | |
| messages = [{"role": "system", "content": sys_prompt}] | |
| if history: | |
| for msg in history[-10:]: | |
| if msg.get("role") and msg.get("content"): | |
| messages.append({"role": msg["role"], "content": msg["content"]}) | |
| messages.append({"role": "user", "content": prompt}) | |
| stream = client.chat.completions.create( | |
| model=target_model, | |
| messages=messages, | |
| temperature=0.1, | |
| max_tokens=1000, | |
| stream=True, | |
| ) | |
| for chunk in stream: | |
| content = chunk.choices[0].delta.content | |
| if content: | |
| yield content | |
| except Exception as e: | |
| print(f"OpenAI Stream Error: {e}") | |
| # βββ Groq AI Functions ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def get_groq_response(prompt, history=None, model="llama-3.3-70b-versatile", language="English"): | |
| """Fetches a response from Groq AI API.""" | |
| try: | |
| from groq import Groq | |
| client = Groq(api_key=GROQ_API_KEY) | |
| sys_prompt = SYSTEM_PROMPT.format(language=language) | |
| messages = [{"role": "system", "content": sys_prompt}] | |
| if history: | |
| for msg in history[-10:]: | |
| if msg.get("role") and msg.get("content"): | |
| messages.append({"role": msg["role"], "content": msg["content"]}) | |
| messages.append({"role": "user", "content": prompt}) | |
| response = client.chat.completions.create( | |
| model=model, | |
| messages=messages, | |
| temperature=0.1, | |
| max_tokens=1000, | |
| ) | |
| return response.choices[0].message.content | |
| except Exception as e: | |
| print(f"Groq Error: {e}") | |
| return None | |
| def stream_groq_response(prompt, history=None, model="llama-3.3-70b-versatile", language="English"): | |
| """Yields streamed response chunks from Groq AI API.""" | |
| try: | |
| from groq import Groq | |
| client = Groq(api_key=GROQ_API_KEY) | |
| sys_prompt = SYSTEM_PROMPT.format(language=language) | |
| messages = [{"role": "system", "content": sys_prompt}] | |
| if history: | |
| for msg in history[-10:]: | |
| if msg.get("role") and msg.get("content"): | |
| messages.append({"role": msg["role"], "content": msg["content"]}) | |
| messages.append({"role": "user", "content": prompt}) | |
| stream = client.chat.completions.create( | |
| model=model, | |
| messages=messages, | |
| temperature=0.1, | |
| max_tokens=1000, | |
| stream=True, | |
| ) | |
| for chunk in stream: | |
| content = chunk.choices[0].delta.content | |
| if content: | |
| yield content | |
| except Exception as e: | |
| print(f"Groq Stream Error: {e}") | |
| yield None | |
| # βββ Ollama Functions βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def get_ollama_response(prompt, history=None, model=DEFAULT_OLLAMA_MODEL, language="English"): | |
| """Fetches a response from a local Ollama instance.""" | |
| url = f"{OLLAMA_URL}/api/chat" | |
| resolved_model = _resolve_ollama_model(model) | |
| messages = _build_messages(prompt, history=history, language=language) | |
| payload = { | |
| "model": resolved_model, | |
| "messages": messages, | |
| "stream": False, | |
| "options": {"temperature": 0.1, "top_p": 0.9, "num_predict": 500} | |
| } | |
| try: | |
| # (connect_timeout, read_timeout) β cap total generation at 25s | |
| response = requests.post(url, json=payload, timeout=(5, 25)) | |
| response.raise_for_status() | |
| data = response.json() | |
| return data.get("message", {}).get("content", "") | |
| except requests.exceptions.Timeout: | |
| # Don't retry on timeout β let the caller fall back to the next backend | |
| print(f"Ollama timed out for model '{resolved_model}'. Falling back to next backend.") | |
| return None | |
| except Exception as e: | |
| print(_ollama_error_message(resolved_model, e)) | |
| if resolved_model != "llama3": | |
| return get_ollama_response(prompt, history, model="llama3", language=language) | |
| return None | |
| def stream_ollama_response(prompt, history=None, model=DEFAULT_OLLAMA_MODEL, language="English"): | |
| """Yields chunks of the response from a local Ollama instance for streaming.""" | |
| url = f"{OLLAMA_URL}/api/chat" | |
| resolved_model = _resolve_ollama_model(model) | |
| messages = _build_messages(prompt, history=history, language=language) | |
| payload = { | |
| "model": resolved_model, | |
| "messages": messages, | |
| "stream": True, | |
| "options": {"temperature": 0.1, "top_p": 0.9, "num_predict": 500} | |
| } | |
| try: | |
| # (connect_timeout, read_timeout) β cap total generation at 25s | |
| response = requests.post(url, json=payload, timeout=(5, 25), stream=True) | |
| response.raise_for_status() | |
| for line in response.iter_lines(): | |
| if line: | |
| chunk = json.loads(line) | |
| if 'message' in chunk and 'content' in chunk['message']: | |
| yield chunk['message']['content'] | |
| if chunk.get('done'): | |
| break | |
| except requests.exceptions.Timeout: | |
| # Don't retry on timeout β let the caller fall back to the next backend | |
| print(f"Ollama stream timed out for model '{resolved_model}'. Falling back to next backend.") | |
| return | |
| except Exception as e: | |
| print(_ollama_error_message(resolved_model, e)) | |
| if resolved_model != "llama3": | |
| yield from stream_ollama_response(prompt, history, model="llama3", language=language) | |
| else: | |
| yield None | |
| # βββ Unified Wrapper Functions ββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def get_ai_response(prompt, history=None, language="English"): | |
| """ | |
| Auto-selects the best available backend. | |
| Priority: OpenAI β Groq β Ollama | |
| Returns None only when all backends are unavailable. | |
| """ | |
| if OPENAI_API_KEY: | |
| result = get_openai_response(prompt, history, language=language) | |
| if result: | |
| return result | |
| if USE_GROQ: | |
| result = get_groq_response(prompt, history, language=language) | |
| if result: | |
| return result | |
| return get_ollama_response(prompt, history, language=language) | |
| def stream_ai_response(prompt, history=None, language="English"): | |
| """ | |
| Auto-selects streaming from the best available backend. | |
| Priority: OpenAI β Groq β Ollama | |
| """ | |
| if OPENAI_API_KEY: | |
| chunks = list(stream_openai_response(prompt, history, language=language)) | |
| if chunks: | |
| yield from chunks | |
| return | |
| if USE_GROQ: | |
| chunks = list(stream_groq_response(prompt, history, language=language)) | |
| if chunks: | |
| yield from chunks | |
| return | |
| yield from stream_ollama_response(prompt, history, language=language) | |
| def check_ollama_connection(): | |
| """Checks if the local Ollama server is running.""" | |
| if USE_GROQ: | |
| return True | |
| try: | |
| response = requests.get(f"{OLLAMA_URL}/", timeout=2) | |
| return response.status_code == 200 | |
| except: | |
| return False | |