""" LLM Engine — Qwen2.5-1.5B-Instruct via HuggingFace InferenceClient. Primary : huggingface_hub InferenceClient (no compilation, works on HF Spaces) Fallback : llama-cpp-python local GGUF (offline / self-hosted) This is the bottom layer of the AnveshAI hierarchy: Math → math_engine (instant, rule-based) Knowledge → knowledge_engine (keyword retrieval from knowledge.txt) └─ no match → LLMEngine.generate (Qwen2.5-1.5B) Conversation → conversation_engine (pattern matching from conversation.txt) └─ no match → LLMEngine.generate (Qwen2.5-1.5B) """ import os MODEL_REPO = "Qwen/Qwen2.5-1.5B-Instruct-GGUF" MODEL_FILE = "qwen2.5-1.5b-instruct-q4_k_m.gguf" HF_MODEL_ID = "Qwen/Qwen2.5-72B-Instruct" SYSTEM_PROMPT = ( "You are AnveshAI Edge, an expert AI tutor specialising in JEE Advanced " "(Physics, Chemistry, and Mathematics). " "Your answers must meet the rigour and depth expected at JEE Advanced level. " "Always show complete, step-by-step working. Use standard JEE notation. " "State the relevant formula or principle before applying it. " "Do not repeat the question back. " "If you are unsure about something, say so clearly. " "Prefer concise, exam-focused explanations over verbose prose." ) MATH_SYSTEM_PROMPT = ( "You are an expert JEE Advanced mathematics tutor. " "You will be given a VERIFIED ANSWER computed by a symbolic engine. " "That answer is 100% correct — do NOT change it, do NOT recompute it. " "Your ONLY job is to explain, step by step, HOW a JEE Advanced student " "would work through the problem and arrive at that exact answer. " "Use JEE Advanced standard techniques (e.g. substitution, IBP, " "L'Hopital, cofactor expansion, characteristic equation, etc.). " "Every step must lead logically toward the verified answer. " "State the verified answer word-for-word at the end of your explanation. " "Keep the explanation concise and exam-focused." ) CHAT_SYSTEM_PROMPT = ( "You are AnveshAI, a friendly JEE Advanced study assistant. " "When the user sends a casual or non-academic message, reply briefly and " "naturally in 1-2 sentences — like a helpful study buddy, not an academic paper. " "Never analyse casual phrases or exclamations as if they are problems. " "If you cannot understand the message, politely ask what they need help with." ) MAX_TOKENS = 1024 TEMPERATURE = 0.7 MATH_TEMPERATURE = 0.1 TOP_P = 0.9 N_CTX = 4096 class LLMEngine: """ Two-backend LLM wrapper: 1. HuggingFace InferenceClient — used on HF Spaces / any machine with internet 2. llama-cpp-python local GGUF — used when HF Inference is unavailable Both expose identical generate() semantics so the rest of the codebase doesn't need to know which backend is active. """ def __init__(self) -> None: self._client = None # HF InferenceClient instance self._llm = None # llama_cpp Llama instance self._backend = None # "hf" | "local" | None self._failed = False self._fail_reason = "" self._load() # ------------------------------------------------------------------ # Initialisation # ------------------------------------------------------------------ def _load(self) -> None: if self._try_hf_client(): return self._try_local_llama() def _try_hf_client(self) -> bool: """Attempt to initialise the HuggingFace InferenceClient.""" try: from huggingface_hub import InferenceClient token = os.environ.get("HF_TOKEN") or os.environ.get("HUGGINGFACE_API_TOKEN") self._client = InferenceClient(model=HF_MODEL_ID, token=token) self._backend = "hf" print(f" [LLM] Backend: HuggingFace InferenceClient ({HF_MODEL_ID})", flush=True) return True except Exception as exc: print(f" [LLM] HF InferenceClient unavailable: {exc}", flush=True) return False def _try_local_llama(self) -> None: """Fall back to local llama-cpp-python GGUF model.""" try: from llama_cpp import Llama print( f" [LLM] Loading local {MODEL_FILE} " "(first run downloads ~1 GB, then cached) …", flush=True, ) self._llm = Llama.from_pretrained( repo_id=MODEL_REPO, filename=MODEL_FILE, n_ctx=N_CTX, n_threads=4, verbose=False, ) self._backend = "local" print(" [LLM] Backend: local llama-cpp Qwen2.5-1.5B-Instruct", flush=True) except Exception as exc: self._failed = True self._fail_reason = str(exc) print(f" [LLM] Both backends failed. Last error: {exc}", flush=True) def is_available(self) -> bool: return self._backend is not None and not self._failed # ------------------------------------------------------------------ # Public API # ------------------------------------------------------------------ def generate( self, user_input: str, context: str = "", system_prompt: str = "", temperature: float = TEMPERATURE, ) -> str: if self._failed or self._backend is None: return ( f"LLM unavailable ({self._fail_reason}). " "Check that huggingface_hub is installed and you have internet access." ) system_content = system_prompt if system_prompt else SYSTEM_PROMPT if context: system_content += f"\n\nRelevant background:\n{context}" messages = [ {"role": "system", "content": system_content}, {"role": "user", "content": user_input}, ] if self._backend == "hf": return self._generate_hf(messages, temperature) return self._generate_local(messages, temperature) def _generate_hf(self, messages: list[dict], temperature: float) -> str: try: response = self._client.chat_completion( messages=messages, max_tokens=MAX_TOKENS, temperature=max(temperature, 0.01), top_p=TOP_P, ) return response.choices[0].message.content.strip() except Exception as exc: return f"HF inference error: {exc}" def _generate_local(self, messages: list[dict], temperature: float) -> str: try: output = self._llm.create_chat_completion( messages=messages, max_tokens=MAX_TOKENS, temperature=temperature, top_p=TOP_P, ) return output["choices"][0]["message"]["content"].strip() except Exception as exc: return f"LLM generation error: {exc}"