Spaces:
Sleeping
Sleeping
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() |