Spaces:
Running
Running
| import os | |
| import gradio as gr | |
| from anthropic import Anthropic | |
| from datetime import datetime, timedelta | |
| from collections import deque | |
| import random | |
| import logging | |
| import tempfile | |
| from pathlib import Path | |
| from sympy import * | |
| # Set up logging | |
| logging.basicConfig( | |
| level=logging.DEBUG, | |
| format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' | |
| ) | |
| logger = logging.getLogger(__name__) | |
| # Initialize Anthropic client | |
| anthropic = Anthropic( | |
| api_key=os.environ.get('ANTHROPIC_API_KEY') | |
| ) | |
| # Request tracking | |
| MAX_REQUESTS_PER_DAY = 25 | |
| request_history = deque(maxlen=1000) | |
| def get_difficulty_parameters(difficulty_level): | |
| """Return specific parameters and constraints based on difficulty level""" | |
| parameters = { | |
| 1: { # Very Easy | |
| "description": "very easy, suitable for beginners", | |
| "constraints": [ | |
| "Use only basic concepts and straightforward calculations", | |
| "Break complex problems into smaller, guided steps", | |
| "Provide hints within the question when needed", | |
| "Use simple numbers and avoid complex algebraic expressions" | |
| ], | |
| "example_style": "Similar to standard homework problems", | |
| "model": "claude-3-5-sonnet-20241022" | |
| }, | |
| 2: { # Easy | |
| "description": "easy, but requiring some thought", | |
| "constraints": [ | |
| "Use basic concepts with minor complications", | |
| "Include two-step problems", | |
| "Minimal guidance provided", | |
| "Use moderately complex numbers or expressions" | |
| ], | |
| "example_style": "Similar to quiz questions", | |
| "model": "claude-3-5-sonnet-20241022" | |
| }, | |
| 3: { # Intermediate | |
| "description": "intermediate difficulty, testing deeper understanding", | |
| "constraints": [ | |
| "Combine 2-3 related concepts", | |
| "Include some non-obvious solution paths", | |
| "Require multi-step reasoning", | |
| "Use moderate algebraic complexity" | |
| ], | |
| "example_style": "Similar to midterm exam questions", | |
| "model": "claude-3-5-sonnet-20241022" | |
| }, | |
| 4: { # Difficult | |
| "description": "challenging, requiring strong mathematical maturity", | |
| "constraints": [ | |
| "Combine multiple concepts creatively", | |
| "Require insight and deep understanding", | |
| "Include non-standard approaches", | |
| "Use sophisticated mathematical reasoning" | |
| ], | |
| "example_style": "Similar to final exam questions", | |
| "model": "claude-3-5-sonnet-20241022" | |
| }, | |
| 5: { # Very Difficult | |
| "description": "very challenging, testing mastery and creativity at a graduate level", | |
| "constraints": [ | |
| "Create novel applications of theoretical concepts", | |
| "Require graduate-level mathematical reasoning", | |
| "Combine multiple advanced topics in unexpected ways", | |
| "Demand creative problem-solving approaches", | |
| "Include rigorous proof construction", | |
| "Require synthesis across mathematical domains", | |
| "Test deep theoretical understanding" | |
| ], | |
| "example_style": "Similar to graduate qualifying exams or advanced competition problems", | |
| "model": "claude-3-5-sonnet-20241022" | |
| } | |
| } | |
| return parameters.get(difficulty_level) | |
| def create_latex_document(content, questions_only=False): | |
| """Create a complete LaTeX document""" | |
| try: | |
| latex_header = r"""\documentclass{article} | |
| \usepackage{amsmath,amssymb} | |
| \usepackage[margin=1in]{geometry} | |
| \begin{document} | |
| \title{Mathematics Question} | |
| \maketitle | |
| """ | |
| latex_footer = r"\end{document}" | |
| if questions_only: | |
| # Modified to handle single question | |
| processed_content = content.split('Solution:')[0] | |
| content = processed_content | |
| full_document = f"{latex_header}\n{content}\n{latex_footer}" | |
| logger.debug(f"Created {'questions-only' if questions_only else 'full'} LaTeX document") | |
| return full_document | |
| except Exception as e: | |
| logger.error(f"Error creating LaTeX document: {str(e)}") | |
| raise | |
| def save_to_temp_file(content, filename): | |
| """Save content to a temporary file and return the path""" | |
| try: | |
| temp_dir = Path(tempfile.gettempdir()) / "math_test_files" | |
| temp_dir.mkdir(exist_ok=True) | |
| file_path = temp_dir / filename | |
| file_path.write_text(content, encoding='utf-8') | |
| logger.debug(f"Saved content to temporary file: {file_path}") | |
| return str(file_path) | |
| except Exception as e: | |
| logger.error(f"Error saving temporary file: {str(e)}") | |
| raise | |
| def get_problem_type_addition(question_type): | |
| """Return specific requirements based on problem type""" | |
| problem_type_additions = { | |
| "application": """ | |
| The application question MUST: | |
| - Present a real-world scenario or practical problem | |
| - Require modeling the situation mathematically | |
| - Connect abstract mathematical concepts to concrete situations | |
| - Include realistic context and data | |
| - Require students to: | |
| 1. Identify relevant mathematical concepts | |
| 2. Translate the practical problem into mathematical terms | |
| 3. Solve using appropriate mathematical techniques | |
| 4. Interpret the results in the context of the original problem | |
| Example contexts might include: | |
| - Physics applications (motion, forces, work) | |
| - Engineering scenarios (optimization, rates of change) | |
| - Economics problems (cost optimization, growth models) | |
| - Biological systems (population growth, reaction rates) | |
| - Business applications (profit maximization, inventory management) | |
| - Social science applications (demographic models, social network analysis) | |
| - Data science applications (regression, statistical analysis) | |
| """, | |
| "proof": """ | |
| The proof question MUST: | |
| - Require a formal mathematical proof | |
| - Focus on demonstrating logical reasoning | |
| - Require justification for each step | |
| - Emphasize theoretical understanding | |
| The proof question MAY NOT: | |
| - Include Real-world applications or scenarios | |
| - Include Pure computation problems | |
| - Ask only for numerical answers | |
| """, | |
| "computation": """ | |
| The computation question MUST: | |
| - Require specific algebraic calculations | |
| - Focus on mathematical techniques | |
| - Have concrete answers in the form of algebraic expressions (about half of questions) or numbers (about half of questions) | |
| - Test procedural knowledge | |
| The computation question MAY NOT: | |
| - Include extended real-world applications or scenarios | |
| - Ask for a proof | |
| """ | |
| } | |
| return problem_type_additions.get(question_type, "") | |
| def generate_question(subject, difficulty, question_type): | |
| """Generate a single math question""" | |
| try: | |
| if not os.environ.get('ANTHROPIC_API_KEY'): | |
| logger.error("Anthropic API key not found") | |
| return "Error: Anthropic API key not configured", None, None | |
| logger.debug(f"Generating {question_type} question for subject: {subject} at difficulty level: {difficulty}") | |
| # Check rate limit | |
| now = datetime.now() | |
| while request_history and (now - request_history[0]) > timedelta(days=1): | |
| request_history.popleft() | |
| if len(request_history) >= MAX_REQUESTS_PER_DAY: | |
| return "Daily request limit reached. Please try again tomorrow.", None, None | |
| request_history.append(now) | |
| topics = { | |
| "Single Variable Calculus": ["limits", "derivatives", "integrals", "series", "applications"], | |
| "Multivariable Calculus": ["partial derivatives", "multiple integrals", "vector fields", "optimization"], | |
| "Linear Algebra": ["matrices", "vector spaces", "eigenvalues", "linear transformations"], | |
| "Differential Equations": ["first order equations", "second order equations", "systems", "stability analysis"], | |
| "Real Analysis": ["sequences", "series", "continuity", "differentiation", "integration"], | |
| "Complex Analysis": ["complex functions", "analyticity", "contour integration", "residues"], | |
| "Abstract Algebra": ["groups", "rings", "fields", "homomorphisms"], | |
| "Probability Theory": ["probability spaces", "random variables", "distributions", "limit theorems"], | |
| "Numerical Analysis": ["approximation", "interpolation", "numerical integration", "error analysis"], | |
| "Topology": ["metric spaces", "continuity", "compactness", "connectedness"] | |
| } | |
| selected_topic = random.choice(topics.get(subject, ["general"])) | |
| logger.debug(f"Selected topic: {selected_topic}") | |
| difficulty_params = get_difficulty_parameters(difficulty) | |
| problem_type_addition = get_problem_type_addition(question_type) | |
| if difficulty == 5: | |
| system_prompt = f"""You are an expert mathematics professor creating a graduate-level exam question. | |
| STRICT REQUIREMENTS: | |
| 1. Write exactly 1 graduate-level {question_type} question on {subject} covering {selected_topic}. | |
| 2. Advanced Difficulty Requirements: | |
| This question must be suitable for PhD qualifying exams or advanced competitions. | |
| MUST include: | |
| - Novel applications of theoretical concepts | |
| - Graduate-level mathematical reasoning | |
| - Unexpected connections between different areas of {subject} | |
| - Creative problem-solving approaches | |
| - Rigorous proof requirements where applicable | |
| Follow these specific constraints: | |
| {chr(10).join(f' - {c}' for c in difficulty_params['constraints'])} | |
| {problem_type_addition} | |
| 3. Style Reference: | |
| Question should be {difficulty_params['example_style']} | |
| 4. The question MUST: | |
| - Bridge multiple mathematical domains | |
| - Require deep theoretical understanding | |
| - Test mastery of advanced concepts | |
| - Demand innovative solution approaches | |
| 5. For LaTeX formatting: | |
| - Use $ for inline math | |
| - Use $$ on separate lines for equations and solutions | |
| - Put each solution step on its own line in $$ $$ | |
| - DO NOT use \\begin{{aligned}} or similar environments | |
| 6. Include a detailed solution with thorough explanations of advanced concepts used | |
| 7. Maintain clear, precise formatting | |
| 8. At the end of the LaTeX solution output, print SymPy code that you would use to solve or verify the main equations in the question. | |
| 9. When writing SymPy code to verify solutions: | |
| - Include print statements for ALL calculations and results | |
| - Print intermediate steps and final answers | |
| - Print variable values after they are computed""" | |
| else: | |
| system_prompt = f"""You are an expert mathematics professor creating a {difficulty_params['description']} exam question. | |
| STRICT REQUIREMENTS: | |
| 1. Write exactly 1 {question_type} question on {subject} covering {selected_topic}. | |
| 2. Difficulty Level Guidelines: | |
| {difficulty_params['description'].upper()} | |
| Follow these specific constraints: | |
| {chr(10).join(f' - {c}' for c in difficulty_params['constraints'])} | |
| {problem_type_addition} | |
| 3. Style Reference: | |
| Question should be {difficulty_params['example_style']} | |
| 4. For LaTeX formatting: | |
| - Use $ for inline math | |
| - Use $$ on separate lines for equations and solutions | |
| - Put each solution step on its own line in $$ $$ | |
| - DO NOT use \\begin{{aligned}} or similar environments | |
| 5. Include a detailed solution | |
| 6. Maintain clear formatting | |
| 7. At the end of the LaTeX solution output, print SymPy code that you would use to solve or verify the main equations in the question. | |
| 8. When writing SymPy code to verify solutions: | |
| - Include print statements for ALL calculations and results | |
| - Print intermediate steps and final answers | |
| - Print variable values after they are computed""" | |
| #Consider | |
| #When writing SymPy code: | |
| #- Use FiniteSet(1, 2, 3) instead of Set([1, 2, 3]) for finite sets | |
| #- Import specific functions instead of using 'from sympy import *' | |
| #- Print results of each calculation step | |
| logger.debug("Sending request to Anthropic API") | |
| message = anthropic.messages.create( | |
| model=difficulty_params['model'], | |
| max_tokens=4096, | |
| temperature=0.7, | |
| messages=[{ | |
| "role": "user", | |
| "content": f"{system_prompt}\n\nWrite a question for {subject}." | |
| }] | |
| ) | |
| if not hasattr(message, 'content') or not message.content: | |
| logger.error("No content received from Anthropic API") | |
| return "Error: No content received from API", None, None | |
| response_text = message.content[0].text | |
| logger.debug("Successfully received response from Anthropic API") | |
| # Execute SymPy code and append results | |
| sympy_output = extract_and_run_sympy_code_simple(response_text) | |
| if sympy_output: | |
| # Check if SymPy ran successfully | |
| if "Error" not in sympy_output: | |
| resolution = check_and_resolve_discrepancy(response_text, sympy_output) | |
| response_text = f"{response_text}\n\nSymPy Verification Results:\n```\n{sympy_output}\n```\n\nVerification Analysis:\n{resolution}" | |
| else: | |
| # Just append SymPy results if there was an error | |
| response_text += f"\n\nSymPy Verification Results:\n```\n{sympy_output}\n```" | |
| # Create LaTeX content | |
| questions_latex = create_latex_document(response_text, questions_only=True) | |
| full_latex = create_latex_document(response_text, questions_only=False) | |
| # Save to temporary files | |
| questions_path = save_to_temp_file(questions_latex, "question.tex") | |
| full_path = save_to_temp_file(full_latex, "full_question.tex") | |
| logger.debug("Successfully created temporary files") | |
| return response_text, questions_path, full_path | |
| except Exception as e: | |
| logger.error(f"Error generating question: {str(e)}") | |
| return f"Error: {str(e)}", None, Non | |
| def extract_and_run_sympy_code_simple(response_text): | |
| """ | |
| Extract SymPy code from the response and execute it. | |
| Returns the output exactly as SymPy would produce it. | |
| """ | |
| try: | |
| # Extract code | |
| sympy_start = response_text.find('```python') | |
| if sympy_start == -1: | |
| return "No SymPy code found in the response." | |
| code_start = response_text.find('\n', sympy_start) + 1 | |
| code_end = response_text.find('```', code_start) | |
| if code_end == -1: | |
| return "Malformed SymPy code block." | |
| sympy_code = response_text[code_start:code_end].strip() | |
| # Set up basic SymPy environment | |
| import io | |
| import sympy | |
| from contextlib import redirect_stdout | |
| # Capture output | |
| output_buffer = io.StringIO() | |
| with redirect_stdout(output_buffer): | |
| exec(sympy_code, {"sympy": sympy, "print": print}) | |
| return output_buffer.getvalue().strip() or "No output produced" | |
| except Exception as e: | |
| return f"Error executing SymPy code: {str(e)}" | |
| def check_and_resolve_discrepancy(initial_response, sympy_output): | |
| """ | |
| Compare the SymPy output with the initial response and resolve any discrepancies | |
| by making another API call to Claude. | |
| """ | |
| try: | |
| resolution_prompt = f"""Here is a mathematics question with two answers. | |
| The first, called Original solution, is a complete solution. | |
| The second, called SymPy Verification, will only provide the final answer. | |
| If the SymPy Verification answer is consistent with the final answer Original solution, | |
| then please say that they are consistent and briefly explain why. | |
| If the two answers are inconsistent with each other then please: | |
| 1. Identify which solution is correct | |
| 2. Explain the error in the incorrect solution | |
| 3. Provide a revised complete solution that fixes any errors | |
| Original solution: | |
| {initial_response} | |
| SymPy Verification Results: | |
| {sympy_output} | |
| Please maintain the same LaTeX formatting as the original solution.""" | |
| # Make API call for resolution | |
| message = anthropic.messages.create( | |
| model="claude-3-5-sonnet-20241022", | |
| max_tokens=4096, | |
| temperature=0.2, | |
| messages=[{ | |
| "role": "user", | |
| "content": resolution_prompt | |
| }] | |
| ) | |
| resolution_text = message.content[0].text | |
| # Check if resolution contains new SymPy code | |
| if "```python" in resolution_text: | |
| new_sympy_output = extract_and_run_sympy_code(resolution_text) | |
| resolution_text += "\n\nNew SymPy Verification Results:\n```\n" + new_sympy_output + "\n```" | |
| return resolution_text | |
| except Exception as e: | |
| logger.error(f"Error in discrepancy resolution: {str(e)}") | |
| return initial_response | |
| # Create Gradio interface | |
| with gr.Blocks() as interface: | |
| gr.Markdown("# Advanced Mathematics Question Generator") | |
| gr.Markdown("""Generates a unique university-level mathematics question with solution using Claude 3. | |
| Each question features different topics and difficulty levels. Limited to 25 requests per day.""") | |
| with gr.Row(): | |
| with gr.Column(): | |
| subject_dropdown = gr.Dropdown( | |
| choices=[ | |
| "Single Variable Calculus", | |
| "Multivariable Calculus", | |
| "Linear Algebra", | |
| "Differential Equations", | |
| "Real Analysis", | |
| "Complex Analysis", | |
| "Abstract Algebra", | |
| "Probability Theory", | |
| "Numerical Analysis", | |
| "Topology" | |
| ], | |
| label="Select Mathematics Subject", | |
| info="Choose a subject for the question" | |
| ) | |
| difficulty_slider = gr.Slider( | |
| minimum=1, | |
| maximum=5, | |
| step=1, | |
| value=3, | |
| label="Difficulty Level", | |
| info="1: Very Easy, 2: Easy, 3: Moderate, 4: Difficult, 5: Very Difficult" | |
| ) | |
| question_type = gr.Radio( | |
| choices=["computation", "proof", "application"], | |
| label="Question Type", | |
| info="Select the type of question you want", | |
| value="computation" | |
| ) | |
| generate_btn = gr.Button("Generate Question") | |
| output_text = gr.Markdown( | |
| label="Generated Question Preview", | |
| latex_delimiters=[ | |
| {"left": "$$", "right": "$$", "display": True}, | |
| {"left": "$", "right": "$", "display": False} | |
| ] | |
| ) | |
| with gr.Row(): | |
| questions_file = gr.File(label="Question Only (LaTeX)") | |
| full_file = gr.File(label="Question with Solution (LaTeX)") | |
| generate_btn.click( | |
| generate_question, | |
| inputs=[ | |
| subject_dropdown, | |
| difficulty_slider, | |
| question_type | |
| ], | |
| outputs=[output_text, questions_file, full_file] | |
| ) | |
| if __name__ == "__main__": | |
| logger.info("Starting application") | |
| interface.launch() |