Download app.py from TheLoopStudio/Mathematics-AI: direct link, hf CLI and curl.
- Browser
- Download file 10.6 kB
-
https://huggingface.co/spaces/TheLoopStudio/Mathematics-AI/resolve/main/app.py
- Command line
-
hf download hf://spaces/TheLoopStudio/Mathematics-AI/app.py
-
curl -L -o app.py https://huggingface.co/spaces/TheLoopStudio/Mathematics-AI/resolve/main/app.py
10.6 kB
| import os | |
| import gradio as gr | |
| from huggingface_hub import InferenceClient | |
| from datasets import load_dataset | |
| import random | |
| import re | |
| import sympy as sp | |
| import threading | |
| # Global datasets | |
| math_samples = [] | |
| loading_status = {"loaded": False, "error": None} | |
| def load_sample_problems(): | |
| """Load sample problems - using fallback only to avoid storage limits""" | |
| global math_samples, loading_status | |
| # Skip dataset loading to avoid 50GB storage limit | |
| math_samples = get_fallback_samples() | |
| loading_status["loaded"] = True | |
| print(f"✅ Loaded {len(math_samples)} fallback samples") | |
| def get_fallback_samples(): | |
| """Extended fallback problems covering diverse topics""" | |
| return [ | |
| "Find the derivative of f(x) = 3x² + 2x - 1", | |
| "A triangle has sides 5, 12, and 13. What is its area?", | |
| "If log₂(x) + log₂(x+6) = 4, find x", | |
| "Calculate lim(x→0) sin(x)/x", | |
| "Solve: x + 2y = 7, 3x - y = 4", | |
| "Integrate sin(x) from 0 to π", | |
| "What is P(rolling three 6s in a row)?", | |
| "Simplify: (x² - 4)/(x - 2)", | |
| "Find the sum of 1 + 2 + 3 + ... + 100", | |
| "What is the 10th Fibonacci number?", | |
| "Calculate the area of a circle with radius 5", | |
| "Factor x² + 5x + 6", | |
| "Solve x² - 4x + 4 = 0", | |
| "Find tan(π/4)", | |
| "What is 15% of 240?", | |
| "Evaluate ∫x³ dx from 1 to 3", | |
| "Find the slope of the line through (2,3) and (5,9)", | |
| "Convert 45° to radians", | |
| "Solve |2x - 5| = 7", | |
| "Find the vertex of y = x² - 6x + 8", | |
| "Calculate the discriminant of 2x² + 3x - 5 = 0", | |
| "What is the standard deviation of [2, 4, 6, 8, 10]?", | |
| "Simplify √(72)", | |
| "Find cos(60°)", | |
| "Solve the inequality 3x - 7 > 11", | |
| ] | |
| # Start background loading | |
| threading.Thread(target=load_sample_problems, daemon=True).start() | |
| def create_math_system_message(): | |
| """System prompt for math tutoring""" | |
| return r"""You are Mathetics AI, an expert mathematics tutor. | |
| **Teaching Approach:** | |
| 1. Understand the problem clearly | |
| 2. Show step-by-step solutions with LaTeX | |
| 3. Verify answers when possible | |
| 4. Suggest alternative methods | |
| **LaTeX Formatting:** | |
| - Inline: $x^2 + y^2 = r^2$ | |
| - Display: $$\int_0^\pi \sin(x)\,dx = 2$$ | |
| - Box answers: $\boxed{42}$ | |
| - Fractions: $\frac{a}{b}$ | |
| - Limits: $\lim_{x \to 0}$ | |
| Be clear, precise, and educational.""" | |
| def render_latex(text): | |
| """Clean and normalize LaTeX for proper rendering""" | |
| if not text: | |
| return text | |
| # Convert LaTeX brackets to dollars | |
| text = re.sub(r'\\\[(.*?)\\\]', r'$$\1$$', text, flags=re.DOTALL) | |
| text = re.sub(r'\\\((.*?)\\\)', r'$\1$', text, flags=re.DOTALL) | |
| # Fix boxed answers outside math mode | |
| text = re.sub(r'(?<!\$)\\boxed\{([^}]+)\}(?!\$)', r'$\\boxed{\1}$', text) | |
| # Convert equation environments | |
| text = re.sub(r'\\begin\{equation\*?\}(.*?)\\end\{equation\*?\}', r'$$\1$$', text, flags=re.DOTALL) | |
| # Remove duplicate $$ | |
| text = re.sub(r'\$\$+', '$$', text) | |
| # Escape backslashes for proper rendering in Gradio | |
| text = text.replace('\\\\', '\\') | |
| return text | |
| def try_sympy_compute(message): | |
| """Enhanced SymPy computation with better parsing""" | |
| from sympy.parsing.sympy_parser import parse_expr, standard_transformations, implicit_multiplication_application | |
| msg_lower = message.lower() | |
| x = sp.Symbol('x') | |
| transforms = standard_transformations + (implicit_multiplication_application,) | |
| try: | |
| # Definite integrals | |
| match = re.search(r'\bint(?:egral)?(?:\s+of)?\s+(.+?)\s+from\s+(.+?)\s+to\s+(.+?)(?:\s|$)', msg_lower) | |
| if match: | |
| expr_str, lower, upper = match.groups() | |
| expr = parse_expr(expr_str.replace('^', '**'), transformations=transforms) | |
| result = sp.integrate(expr, (x, sp.sympify(lower.strip()), sp.sympify(upper.strip()))) | |
| return sp.latex(result, mode='plain') | |
| # Derivatives | |
| match = re.search(r'\bderiv(?:ative)?(?:\s+of)?\s+(.+?)(?:\s|$)', msg_lower) | |
| if match: | |
| expr_str = match.group(1).strip() | |
| expr = parse_expr(expr_str.replace('^', '**'), transformations=transforms) | |
| result = sp.diff(expr, x) | |
| return sp.latex(result, inv_trig_style='power') | |
| # Limits | |
| match = re.search(r'\blim(?:it)?.*?(?:\()?(.+?)\s+(?:as\s+)?(?:x\s*)?(?:→|->|to)\s*(.+?)(?:\)|$)', msg_lower) | |
| if match: | |
| expr_str, to_val = match.groups() | |
| expr = parse_expr(expr_str.replace('^', '**'), transformations=transforms) | |
| result = sp.limit(expr, x, sp.sympify(to_val.strip())) | |
| return sp.latex(result) | |
| # Triangle area (Heron's formula) | |
| match = re.search(r'\b(?:triangle|area)\b.*?(\d+)[,\s-]+(\d+)[,\s-]+(\d+)', message) | |
| if match and 'triangle' in msg_lower: | |
| a, b, c = map(float, match.groups()) | |
| s = (a + b + c) / 2 | |
| area = sp.sqrt(s * (s-a) * (s-b) * (s-c)) | |
| return sp.latex(area.evalf()) | |
| except Exception as e: | |
| print(f"⚠️ SymPy error: {e}") | |
| return None | |
| def respond(message, history, system_message, max_tokens, temperature, top_p): | |
| """Streaming response with error handling""" | |
| # Get HF token from environment | |
| hf_token = os.getenv("HF_TOKEN") | |
| if not hf_token: | |
| yield "❌ **Error:** HF_TOKEN not found. Set it in your environment or Hugging Face Spaces secrets." | |
| return | |
| client = InferenceClient( | |
| model="Qwen/Qwen2.5-Math-7B-Instruct", | |
| token=hf_token | |
| ) | |
| messages = [{"role": "system", "content": system_message}] | |
| # Safely handle history format | |
| for msg in history: | |
| if isinstance(msg, dict): | |
| role = msg.get("role", "user") | |
| content = msg.get("content", "") | |
| else: | |
| # Fallback for unexpected formats | |
| role = "user" | |
| content = str(msg) | |
| if content: # Only add non-empty messages | |
| messages.append({"role": role, "content": content}) | |
| messages.append({"role": "user", "content": message}) | |
| try: | |
| response_text = "" | |
| for chunk in client.chat_completion( | |
| messages, | |
| max_tokens=max_tokens, | |
| temperature=temperature, | |
| top_p=top_p, | |
| stream=True | |
| ): | |
| if chunk.choices[0].delta.content: | |
| response_text += chunk.choices[0].delta.content | |
| yield render_latex(response_text) | |
| # Add SymPy verification | |
| sympy_result = try_sympy_compute(message) | |
| if sympy_result: | |
| response_text += f"\n\n**✓ Verified with SymPy:** $${sympy_result}$$" | |
| yield render_latex(response_text) | |
| except Exception as e: | |
| error_msg = f"❌ **Error:** {str(e)}\n\nTry:\n- Simpler wording\n- Breaking into steps\n- Checking notation" | |
| yield error_msg | |
| def get_random_sample(): | |
| """Get random sample with loading status""" | |
| if not loading_status["loaded"]: | |
| return "⏳ Loading samples..." | |
| if not math_samples: | |
| return get_fallback_samples()[0] | |
| return random.choice(math_samples) | |
| # Gradio Interface | |
| with gr.Blocks(title="🧮 Mathematics AI", theme=gr.themes.Soft(), css=""" | |
| .katex { font-size: 1.1em; } | |
| .katex-display { margin: 1em 0; } | |
| """) as demo: | |
| gr.Markdown("# 🧮 **Mathematics AI**\n*Advanced Math Tutor powered by Qwen2.5-Math*") | |
| chatbot = gr.Chatbot( | |
| height=500, | |
| type='messages', | |
| label="💬 Conversation", | |
| latex_delimiters=[ | |
| {"left": "$$", "right": "$$", "display": True}, | |
| {"left": "$", "right": "$", "display": False} | |
| ], | |
| show_copy_button=True | |
| ) | |
| msg = gr.Textbox(placeholder="Ask any math problem...", show_label=False, scale=4) | |
| with gr.Row(): | |
| submit = gr.Button("🚀 Solve", variant="primary", scale=1) | |
| clear = gr.Button("🗑️ Clear", variant="secondary", scale=1) | |
| sample = gr.Button("🎲 Random", variant="secondary", scale=1) | |
| with gr.Accordion("⚙️ Advanced Settings", open=False): | |
| temp_slider = gr.Slider(0.1, 1.0, value=0.3, step=0.1, label="Temperature (creativity)") | |
| tokens_slider = gr.Slider(256, 2048, value=1024, step=128, label="Max Tokens") | |
| top_p_slider = gr.Slider(0.1, 1.0, value=0.85, step=0.05, label="Top-p (nucleus sampling)") | |
| with gr.Accordion("💡 Help & Examples", open=False): | |
| gr.Markdown(""" | |
| **Tips:** | |
| - Be specific: "derivative of sin(2x)" not "help with calculus" | |
| - Request steps: "show step-by-step" | |
| - Use clear notation: `x^2` for powers, `lim x->0` for limits | |
| **Examples:** | |
| - Calculus: "Find ∫x² dx from 0 to 5" | |
| - Algebra: "Solve x² + 5x + 6 = 0" | |
| - Geometry: "Area of triangle with sides 3, 4, 5" | |
| - Limits: "Calculate lim(x→0) sin(x)/x" | |
| """) | |
| gr.Examples( | |
| examples=[ | |
| ["Find the derivative of x² sin(x)"], | |
| ["Calculate the area of a triangle with sides 5, 12, 13"], | |
| ["Integrate x² from 0 to 2"], | |
| ["What is lim(x→0) sin(x)/x?"], | |
| ["Solve the equation x² - 5x + 6 = 0"], | |
| ], | |
| inputs=msg | |
| ) | |
| # Hidden system message (passed to respond) | |
| system_msg = gr.State(create_math_system_message()) | |
| def chat_response(message, history, sys_msg, max_tok, temp, top_p): | |
| """Handle chat with streaming""" | |
| if not message.strip(): | |
| return history, "" | |
| history.append({"role": "user", "content": message}) | |
| # Create assistant message slot | |
| history.append({"role": "assistant", "content": ""}) | |
| # Stream responses | |
| for response in respond(message, history[:-1], sys_msg, max_tok, temp, top_p): | |
| history[-1]["content"] = response | |
| yield history, "" | |
| return history, "" | |
| def clear_chat(): | |
| return [], "" | |
| msg.submit( | |
| chat_response, | |
| [msg, chatbot, system_msg, tokens_slider, temp_slider, top_p_slider], | |
| [chatbot, msg] | |
| ) | |
| submit.click( | |
| chat_response, | |
| [msg, chatbot, system_msg, tokens_slider, temp_slider, top_p_slider], | |
| [chatbot, msg] | |
| ) | |
| clear.click(clear_chat, outputs=[chatbot, msg]) | |
| sample.click(get_random_sample, outputs=msg) | |
| demo.launch() |