| 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 |
|
|
| |
| 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 |
| |
| |
| 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", |
| ] |
|
|
| |
| 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 |
| |
| |
| text = re.sub(r'\\\[(.*?)\\\]', r'$$\1$$', text, flags=re.DOTALL) |
| text = re.sub(r'\\\((.*?)\\\)', r'$\1$', text, flags=re.DOTALL) |
| |
| |
| text = re.sub(r'(?<!\$)\\boxed\{([^}]+)\}(?!\$)', r'$\\boxed{\1}$', text) |
| |
| |
| text = re.sub(r'\\begin\{equation\*?\}(.*?)\\end\{equation\*?\}', r'$$\1$$', text, flags=re.DOTALL) |
| |
| |
| text = re.sub(r'\$\$+', '$$', text) |
| |
| |
| 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: |
| |
| 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') |
| |
| |
| 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') |
| |
| |
| 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) |
| |
| |
| 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""" |
| |
| 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}] |
| |
| |
| for msg in history: |
| if isinstance(msg, dict): |
| role = msg.get("role", "user") |
| content = msg.get("content", "") |
| else: |
| |
| role = "user" |
| content = str(msg) |
| |
| if content: |
| 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) |
| |
| |
| 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) |
|
|
| |
| 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 |
| ) |
| |
| |
| 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}) |
| |
| |
| history.append({"role": "assistant", "content": ""}) |
| |
| |
| 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() |