import os
import time
# Check if spaces library is available (for Hugging Face Spaces ZeroGPU)
# Importing spaces BEFORE torch is a strict ZeroGPU requirement
try:
import spaces
has_spaces = True
print("Hugging Face Spaces library loaded successfully.")
except ImportError:
has_spaces = False
print("Hugging Face Spaces library not found. Running in standard environment.")
import torch
from typing import Generator
from fastapi.responses import HTMLResponse
from gradio import Server
# Initialize the Gradio Server (extends FastAPI)
app = Server()
# Define HTML path
HTML_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "index.html")
# Set device
device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"System detected device: {device}")
# Determine if model should be loaded
force_load = os.environ.get("FORCE_MODEL_LOAD", "false").lower() == "true"
is_fallback = True
model = None
tokenizer = None
MODEL_ID = "KyleHessling1/Qwopus3.6-27B-Fusion-GGUF:Q4_K_M"
if device == "cuda" or force_load:
try:
from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer, GenerationConfig
from threading import Thread
print(f"Attempting to load model '{MODEL_ID}'...")
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, trust_remote_code=True)
if torch.backends.mps.is_available() and force_load:
print("Loading model on MPS (Apple Silicon GPU) with float16...")
model = AutoModelForCausalLM.from_pretrained(
MODEL_ID,
torch_dtype=torch.float16,
low_cpu_mem_usage=True
).to("mps")
else:
print("Loading model in bfloat16...")
model = AutoModelForCausalLM.from_pretrained(
MODEL_ID,
torch_dtype=torch.bfloat16,
device_map="auto",
low_cpu_mem_usage=True
)
is_fallback = False
print("Model loaded successfully!")
except Exception as e:
print(f"Error loading model: {e}")
print("Falling back to simulation mode.")
is_fallback = True
else:
print("No CUDA GPU detected and FORCE_MODEL_LOAD is false. Falling back to simulation mode.")
is_fallback = True
def get_mock_response(message: str) -> tuple[str, str]:
"""Generates detailed mock thinking and answers for local development testing."""
message_lower = message.lower()
if "palindrom" in message_lower or "leetcode" in message_lower or "code" in message_lower or "python" in message_lower:
thought = (
"1. We need to solve the Longest Palindromic Substring problem.\n"
"2. Let's analyze the constraints and possible approaches.\n"
" - Approach 1: Brute Force. Check all O(N^2) substrings. Checking takes O(N), total O(N^3). Too slow.\n"
" - Approach 2: Dynamic Programming. Let DP[i][j] be true if substring s[i..j] is a palindrome.\n"
" - DP[i][j] = (s[i] == s[j]) && (j - i < 3 || DP[i+1][j-1])\n"
" - Time complexity: O(N^2), Space complexity: O(N^2).\n"
" - Approach 3: Expand Around Center. For each index, expand outward for both odd and even length palindromes.\n"
" - Time complexity: O(N^2), Space complexity: O(1). This is optimal in terms of space.\n"
" - Approach 4: Manacher's Algorithm. Dynamic programming combined with centers expansion. O(N) time and space.\n"
"3. Let's implement the Expand Around Center approach as it is highly readable and O(1) space.\n"
"4. Verification: check boundary cases like single character, empty string, string with all identical characters, etc.\n"
"5. Formulate final response with explanation, code, and complexity analysis."
)
body = (
"Here is the optimal Python implementation of the **Longest Palindromic Substring** problem using the **Expand Around Center** approach (\\(O(N^2)\\) time, \\(O(1)\\) space).\n\n"
"### Expand Around Center (Python)\n\n"
"```python\n"
"class Solution:\n"
" def longestPalindrome(self, s: str) -> str:\n"
" if not s or len(s) < 1:\n"
" return \"\"\n"
" \n"
" start, end = 0, 0\n"
" \n"
" def expand_around_center(left: int, right: int) -> int:\n"
" while left >= 0 and right < len(s) and s[left] == s[right]:\n"
" left -= 1\n"
" right += 1\n"
" # Return the length of the palindrome found\n"
" return right - left - 1\n"
" \n"
" for i in range(len(s)):\n"
" # Odd-length palindromes (single character center)\n"
" len1 = expand_around_center(i, i)\n"
" # Even-length palindromes (two character center)\n"
" len2 = expand_around_center(i, i + 1)\n"
" \n"
" max_len = max(len1, len2)\n"
" if max_len > end - start:\n"
" # Adjust start and end indices based on current center\n"
" start = i - (max_len - 1) // 2\n"
" end = i + max_len // 2\n"
" \n"
" return s[start:end + 1]\n"
"```\n\n"
"### Complexity Analysis\n"
"- **Time Complexity:** \\(O(N^2)\\). We expand around \\(2N - 1\\) centers. For each center, expansion can take up to \\(O(N)\\) steps.\n"
"- **Space Complexity:** \\(O(1)\\). Only constant extra space is used."
)
elif "deck" in message_lower or "probab" in message_lower or "card" in message_lower or "math" in message_lower or "solve" in message_lower or "equation" in message_lower:
thought = (
"1. The user is asking a probability/math question: 'If a card is drawn from a standard deck, what is the probability that it is a spade or a face card?'\n"
"2. Let's define the sample space and events:\n"
" - Total cards in a standard deck: N(S) = 52.\n"
" - Event A: Drawing a spade. There are 13 spades in a deck. So N(A) = 13.\n"
" - Event B: Drawing a face card (Jack, Queen, King). There are 3 face cards per suit, and 4 suits. So N(B) = 3 * 4 = 12.\n"
" - We need to find the probability of Spade OR Face Card: P(A or B).\n"
"3. Let's recall the addition rule of probability:\n"
" - P(A or B) = P(A) + P(B) - P(A and B)\n"
"4. What is Event (A and B)? It is drawing a card that is both a spade AND a face card.\n"
" - These are the Jack of Spades, Queen of Spades, and King of Spades. N(A and B) = 3.\n"
"5. Let's plug the numbers in:\n"
" - P(A) = 13/52\n"
" - P(B) = 12/52\n"
" - P(A and B) = 3/52\n"
" - P(A or B) = 13/52 + 12/52 - 3/52 = (13 + 12 - 3)/52 = 22/52.\n"
"6. Simplify the fraction:\n"
" - 22/52 = 11/26.\n"
" - Decimal value: ~0.4231 (or 42.3%).\n"
"7. Structure the explanation clearly, showing the formulas, steps, and intermediate values using LaTeX mathematical notations."
)
body = (
"To find the probability that a randomly drawn card from a standard deck is either a **spade** or a **face card**, we can use the addition rule of probability.\n\n"
"### 1. Identify the Sample Spaces\n"
"- **Total cards in a deck:** \\(N(S) = 52\\)\n"
"- **Spades in a deck (Event \\(A\\)):** There are 13 spades. Hence, \\(N(A) = 13\\).\n"
"- **Face cards in a deck (Event \\(B\\)):** There are 3 face cards (Jack, Queen, King) per suit, across 4 suits. Hence, \\(N(B) = 3 \\times 4 = 12\\).\n\n"
"### 2. Find the Intersection (Spade Face Cards)\n"
"Some cards belong to both sets: Jack of Spades, Queen of Spades, and King of Spades. \n"
"Let this intersection be Event \\(A \\cap B\\):\n"
"\\[N(A \\cap B) = 3\\]\n\n"
"### 3. Apply the Addition Rule\n"
"The probability of the union of two events is given by:\n"
"\\[P(A \\cup B) = P(A) + P(B) - P(A \\cap B)\\]\n\n"
"Substitute the values:\n"
"\\[P(A \\cup B) = \\frac{13}{52} + \\frac{12}{52} - \\frac{3}{52}\\]\n"
"\\[P(A \\cup B) = \\frac{13 + 12 - 3}{52} = \\frac{22}{52}\\]\n\n"
"### 4. Simplify the Result\n"
"Reducing \\(\\frac{22}{52}\\) by dividing the numerator and denominator by 2:\n"
"\\[P(A \\cup B) = \\frac{11}{26} \\approx 0.4231 \\text{ (or } 42.31\\%\\text{)}\\]\n\n"
"**Conclusion:** The probability of drawing a spade or a face card is **\\(\\frac{11}{26}\\)**, which is approximately **42.3%**."
)
elif "box" in message_lower or "fruit" in message_lower or "label" in message_lower or "logic" in message_lower:
thought = (
"1. Three boxes: Box A (labeled Apples), Box B (labeled Oranges), Box C (labeled Mixed).\n"
"2. Fact: *Every single label is incorrect*.\n"
" - Box labeled Apples has Oranges or Mixed.\n"
" - Box labeled Oranges has Apples or Mixed.\n"
" - Box labeled Mixed has Apples or Oranges.\n"
"3. Let's draw a fruit from the \"Mixed\" box. Why?\n"
" - Since the label \"Mixed\" is wrong, it must contain either 100% Apples or 100% Oranges.\n"
" - If we draw a fruit and it's an Apple, then the \"Mixed\" box is 100% Apples.\n"
" - Now we have the remaining boxes labeled \"Apples\" and \"Oranges\".\n"
" - The remaining contents are \"Oranges\" and \"Mixed\".\n"
" - The box labeled \"Oranges\" cannot contain Oranges (as all labels are wrong). So it must contain \"Mixed\".\n"
" - Consequently, the box labeled \"Apples\" must contain \"Oranges\".\n"
"4. What if we drew an Orange? By symmetry:\n"
" - The \"Mixed\" box is 100% Oranges.\n"
" - The box labeled \"Apples\" cannot contain Apples, so it must contain \"Mixed\".\n"
" - The box labeled \"Oranges\" must contain \"Apples\".\n"
"5. The proof is sound and deterministic. Explain case by case with a markdown table."
)
body = (
"This is a classic logical puzzle. The key to solving it is to **draw a fruit from the box labeled \"Mixed\"**.\n\n"
"Here is the step-by-step reasoning:\n\n"
"### 1. Understand the Rules\n"
"- There are three boxes containing: **Apples**, **Oranges**, or **Mixed (both)**.\n"
"- **Crucial Fact:** *All* labels are currently incorrect. This means:\n"
" - The box labeled **\"Apples\"** does *not* contain only apples (it contains either Oranges or Mixed).\n"
" - The box labeled **\"Oranges\"** does *not* contain only oranges (it contains either Apples or Mixed).\n"
" - The box labeled **\"Mixed\"** does *not* contain both (it contains either 100% Apples or 100% Oranges).\n\n"
"### 2. Choose the \"Mixed\" Box First\n"
"Since you know the box labeled \"Mixed\" is *definitely* not mixed, it must be either **100% Apples** or **100% Oranges**.\n\n"
"- Draw one fruit from the box labeled **\"Mixed\"**.\n\n"
"#### Case A: You draw an Apple\n"
"1. Since this box cannot be mixed, and it contains an apple, it **must contain only Apples**. (Label this box **Apples**).\n"
"2. You have two boxes left, labeled **\"Apples\"** and **\"Oranges\"**, and two contents left to assign: **Oranges** and **Mixed**.\n"
"3. Look at the box labeled **\"Oranges\"**. Because all labels are wrong, this box *cannot* contain Oranges. Therefore, it **must contain Mixed**.\n"
"4. This leaves the box labeled **\"Apples\"** to **contain only Oranges**.\n\n"
"#### Case B: You draw an Orange\n"
"1. Since this box cannot be mixed, and it contains an orange, it **must contain only Oranges**. (Label this box **Oranges**).\n"
"2. You have two boxes left, labeled **\"Apples\"** and **\"Oranges\"**, and two contents left to assign: **Apples** and **Mixed**.\n"
"3. Look at the box labeled **\"Apples\"**. Because all labels are wrong, this box *cannot* contain Apples. Therefore, it **must contain Mixed**.\n"
"4. This leaves the box labeled **\"Oranges\"** to **contain only Apples**.\n\n"
"### Summary Table\n\n"
"| Label on Box | Drawn Fruit | Actual Contents | Box 2 Actual | Box 3 Actual |\n"
"| :--- | :--- | :--- | :--- | :--- |\n"
"| **\"Mixed\"** | Apple 🍎 | **Apples** | **\"Oranges\"** label $\\rightarrow$ **Mixed** | **\"Apples\"** label $\\rightarrow$ **Oranges** |\n"
"| **\"Mixed\"** | Orange 🍊 | **Oranges** | **\"Apples\"** label $\\rightarrow$ **Mixed** | **\"Oranges\"** label $\\rightarrow$ **Apples** |\n\n"
"By drawing just **one fruit from the \"Mixed\" box**, you can confidently relabel all three boxes!"
)
else:
thought = (
"1. The user prompt is general: '" + message + "'.\n"
"2. Let's formulate a structured response that explains who I am and how I can help.\n"
"3. Demonstrate reasoning by showing my system stats, architecture (Qwen2.5-Coder backbone), and optimal use cases (coding, math, logic).\n"
"4. Conclude with a helpful, inviting sign-off."
)
body = (
"Hello! I am Qwopus, a small language model optimized for deep, verifiable reasoning in math, coding, and STEM.\n\n"
"To answer your request, here is a summary of how I operate:\n\n"
"### 1. Model Capabilities\n"
"- **Architecture:** Finetuned on top of Qwen3.5-27B using a curriculum-based SFT pipeline and Reinforcement Learning (MGPO).\n"
"- **Reasoning Process:** I generate intermediate thoughts inside `