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 # 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-sonnet-20240229" }, 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-sonnet-20240229" }, 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-sonnet-20240229" }, 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-opus-20240229" }, 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-opus-20240229" } } 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 Test} \maketitle """ latex_footer = r"\end{document}" if questions_only: processed_content = [] current_question = [] for line in content.split('\n'): if 'Solution:' in line: processed_content.append('\n'.join(current_question)) current_question = [] continue if any(line.startswith(f"{i})") for i in range(1, 4)): if current_question: processed_content.append('\n'.join(current_question)) current_question = [line] elif current_question: current_question.append(line) if current_question: processed_content.append('\n'.join(current_question)) content = '\n\n'.join(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: # Create a temporary directory that persists temp_dir = Path(tempfile.gettempdir()) / "math_test_files" temp_dir.mkdir(exist_ok=True) # Create the file path file_path = temp_dir / filename # Write the content 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 validate_question_counts(computation, proof, application): """Validate that question counts sum to 3""" try: comp = int(computation) prf = int(proof) app = int(application) if comp + prf + app != 3: return False, "Total number of questions must equal 3" if any(x < 0 for x in [comp, prf, app]): return False, "Question counts cannot be negative" return True, "" except ValueError: return False, "Please enter valid numbers" def generate_test(subject, difficulty, computation_count, proof_count, application_count): """Generate a math test with enhanced difficulty scaling""" try: # Validate inputs is_valid, error_message = validate_question_counts( computation_count, proof_count, application_count ) if not is_valid: return error_message, None, None 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 test 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"], } selected_topics = random.sample(topics.get(subject, ["general"]), min(3, len(topics.get(subject, ["general"])))) logger.debug(f"Selected topics: {selected_topics}") # Create question type list based on user input question_types = ( ["computation"] * int(computation_count) + ["proof"] * int(proof_count) + ["application"] * int(application_count) ) difficulty_params = get_difficulty_parameters(difficulty) # Enhanced prompt for difficulty level 5 if difficulty == 5: system_prompt = f"""You are an expert mathematics professor creating graduate-level exam questions. STRICT REQUIREMENTS: 1. Write exactly 3 graduate-level questions on {subject} with these specific characteristics: - Question Types (in order): {', '.join(question_types)} - Topics to cover: {', '.join(selected_topics)} 2. Advanced Difficulty Requirements: These questions 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'])} 3. Style Reference: Questions should be {difficulty_params['example_style']} 4. Each 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. Number questions as 1), 2), 3) 7. Include detailed solutions with thorough explanations of advanced concepts used 8. Maintain clear, precise formatting""" else: system_prompt = f"""You are an expert mathematics professor creating {difficulty_params['description']} exam questions. STRICT REQUIREMENTS: 1. Write exactly 3 university-level questions on {subject} with these specific characteristics: - Question Types (in order): {', '.join(question_types)} - Topics to cover: {', '.join(selected_topics)} 2. Difficulty Level Guidelines: {difficulty_params['description'].upper()} Follow these specific constraints: {chr(10).join(f' - {c}' for c in difficulty_params['constraints'])} 3. Style Reference: Questions 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. Number questions as 1), 2), 3) 6. Include detailed solutions 7. Maintain clear formatting""" logger.debug("Sending request to Anthropic API") message = anthropic.messages.create( model=difficulty_params['model'], max_tokens=2999, temperature=0.7, messages=[{ "role": "user", "content": f"{system_prompt}\n\nWrite an exam 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") # 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, "questions.tex") full_path = save_to_temp_file(full_latex, "full_test.tex") logger.debug("Successfully created temporary files") return response_text, questions_path, full_path except Exception as e: logger.error(f"Error generating test: {str(e)}") return f"Error: {str(e)}", None, None # Create Gradio interface with gr.Blocks() as interface: gr.Markdown("# Advanced Mathematics Test Generator") gr.Markdown("""Generates unique university-level mathematics exam questions with solutions using Claude 3. Each test 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 exam questions" ) 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" ) with gr.Row(): with gr.Column(): computation_count = gr.Number( value=1, label="Number of Computation Questions", info="Enter the number of computation questions (total must be 3)", precision=0 ) proof_count = gr.Number( value=1, label="Number of Proof Questions", info="Enter the number of proof questions (total must be 3)", precision=0 ) application_count = gr.Number( value=1, label="Number of Application Questions", info="Enter the number of application questions (total must be 3)", precision=0 ) generate_btn = gr.Button("Generate Test") output_text = gr.Markdown( label="Generated Test Preview", latex_delimiters=[ {"left": "$$", "right": "$$", "display": True}, {"left": "$", "right": "$", "display": False} ] ) with gr.Row(): questions_file = gr.File(label="Questions Only (LaTeX)") full_file = gr.File(label="Full Test with Solutions (LaTeX)") generate_btn.click( generate_test, inputs=[ subject_dropdown, difficulty_slider, computation_count, proof_count, application_count ], outputs=[output_text, questions_file, full_file] ) if __name__ == "__main__": logger.info("Starting application") interface.launch()