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 | |
from sympy import * | |
import json | |
from pathlib import Path | |
import openai | |
# 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') | |
) | |
# Initialize OpenAI client | |
openai.api_key = os.environ.get('My_MathTest_Key') | |
if openai.api_key is None: | |
logger.error("OpenAI API key not found in environment variables") | |
raise ValueError("OpenAI API key not found. Please set the My_MathTest_Key environment variable.") | |
# Request tracking | |
MAX_REQUESTS_PER_DAY = 20 | |
request_history = deque(maxlen=1000) | |
# Define subtopic mappings | |
SUBJECT_SUBTOPICS = { | |
"Single Variable Calculus": [ | |
"limits", | |
"derivatives", | |
"integrals", | |
"related rates", | |
"linear approximation", | |
"integration techniques", | |
"improper integrals", | |
"area between curves", | |
"volume of revolution", | |
"surface area of revolution", | |
"arc length", | |
"parametric equations", | |
"polar coordinates", | |
"sequences", | |
"series", | |
"integration by parts", | |
"partial fractions", | |
"Taylor series", | |
"L'Hôpital's rule", | |
"mean value theorem", | |
"intermediate value theorem", | |
"extreme value theorem", | |
"fundamental theorem of calculus 1", | |
"fundamental theorem of calculus 2" | |
], | |
"Multivariable Calculus": [ | |
"partial derivatives", | |
"multiple integrals", | |
"vector fields", | |
"optimization", | |
"stokes theorem", | |
"green's theorem", | |
"line integrals", | |
"parametric equations", | |
"spherical coordinates" | |
], | |
"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" | |
], | |
} | |
SYMPY_GUIDELINES = """ | |
When writing SymPy code to verify solutions: | |
Import Guidelines: | |
- ALWAYS import with "from sympy..." using specific imports like this: | |
from sympy import Matrix, solve, Symbol, pi, cos, sin | |
- DO NOT use full namespace import like this: import sympy as sp | |
NOTE: For eigenvalue problems, use 'lam = Symbol('lam')' instead of importing from sympy.abc | |
1. Variable Declaration and Expressions: | |
- ALWAYS create symbolic expressions instead of literal numbers when working with mathematical operations: | |
```python | |
# CORRECT: | |
x = Symbol('x') | |
expr = x + 1 # Creates a symbolic expression | |
# INCORRECT: | |
expr = 1 # This is just a number, can't be differentiated | |
``` | |
- For polynomials and functions: | |
```python | |
# CORRECT: | |
x = Symbol('x') | |
p = x**2 + 2*x + 1 # Creates a polynomial expression | |
# INCORRECT: | |
p = 1 # This won't work for operations like diff() | |
``` | |
- When verifying operator actions: | |
```python | |
# CORRECT: | |
x = Symbol('x') | |
def verify_operator(p): | |
x = Symbol('x') # Always use Symbol inside functions too | |
return p.subs(x, 1) # Substitute values after creating expression | |
# INCORRECT: | |
def verify_operator(p): | |
return p # Passing raw numbers won't work | |
``` | |
- For integration bounds: | |
```python | |
# CORRECT: | |
t = Symbol('t') | |
expr = t**2 | |
result = integrate(expr, (t, 0, 1)) | |
# INCORRECT: | |
result = integrate(2, (t, 0, 1)) # Can't integrate a number | |
``` | |
2. Solving and Computing: | |
- Never use strings in solve() or other SymPy functions: | |
CORRECT: solve(eq, x) | |
INCORRECT: solve(eq, 'x') | |
- Define equations symbolically: | |
CORRECT: eq = 2*sqrt(h) - sqrt(12) + 5*k | |
INCORRECT: eq = 2*sqrt('h') - sqrt(12) + 5*k | |
3. Printing and Output: | |
- Include print statements for ALL calculations and results | |
- Print intermediate steps and final answers | |
- Print variable values after they are computed | |
- Use simple print statements instead of f-strings for SymPy expressions | |
- Print expressions with labels on separate lines: | |
```python | |
print("Expression label:") | |
print(expression) | |
``` | |
4. Numeric Calculations: | |
- Use Float() for decimal numbers in calculations | |
- Use float() for final printing of results | |
- Avoid evalf() as it may cause errors | |
- For numeric results: | |
```python | |
result = expression.evalf() | |
print("Result:") | |
print(float(result)) | |
``` | |
5. Working with Series and Sequences: | |
- Use Float() for sequence terms | |
- Convert sums to float() before printing | |
- For series calculations, print intermediate terms | |
6. Matrix Operations and Systems of Equations: | |
- Never use symbolic variables as matrix indices: | |
```python | |
# CORRECT: | |
i, j = 0, 1 # Use integers for indexing | |
M = Matrix([[1, 2], [3, 4]]) | |
element = M[i, j] | |
# INCORRECT: | |
x = Symbol('x') | |
element = M[x, 0] # This will raise an error | |
``` | |
- For matrix analysis, always convert equations to Matrix form: | |
```python | |
# CORRECT: | |
A = Matrix([[1, 2], [3, 4]]) | |
eigenvals = A.eigenvals() | |
# For system of equations: | |
x, y = symbols('x y') | |
system = Matrix([[2, 1], [1, -1]]) | |
b = Matrix([5, 1]) | |
solution = system.solve(b) | |
``` | |
- For matrix operations with variables: | |
```python | |
# CORRECT: | |
x = Symbol('x') | |
M = Matrix([[x, 1], [2, 3]]) | |
result = M * M # Matrix multiplication | |
# INCORRECT: | |
M[Symbol('i'), Symbol('j')] = x # Don't use symbolic indices | |
``` | |
- For systems of equations that might be linearly dependent, use row reduction instead of matrix inversion. Here's the template for handling such systems: | |
7. Limit Calculations: | |
- ALWAYS compute one-sided limits (from the left and the right) when evaluating any limit: | |
```python | |
# Example: | |
x = Symbol('x') | |
expr = (sin(x)*cos(x) - x + x**3) / (x**3 * sqrt(1 + x) - x**3) | |
# Calculate the limit from the left (x -> 0-): | |
left_limit = limit(expr, x, 0, dir='-') | |
print("Limit from the left (x -> 0-):") | |
print(left_limit) | |
# Calculate the limit from the right (x -> 0+): | |
right_limit = limit(expr, x, 0, dir='+') | |
print("Limit from the right (x -> 0+):") | |
print(right_limit) | |
``` | |
- After computing both one-sided limits, verify if they match: | |
```python | |
if left_limit == right_limit: | |
print("The two-sided limit exists and is:", left_limit) | |
else: | |
print("The two-sided limit does not exist.") | |
``` | |
- If the limit diverges (\(\infty\) or \(-\infty\)), explicitly state that in the print output. | |
- For piecewise or discontinuous functions, compute limits at all points of interest, including boundaries. | |
- **Important Note**: Always test limits symbolically first. If SymPy produces unexpected results, simplify the expression or expand it (e.g., using `series`) before re-evaluating the limit. | |
8. If calculating integrals, do so directly with respect to the original expression and original variable of integration, | |
not any integral that might have been rewritten with a variable substitution | |
For indefinite integrals: | |
```python | |
# Just compute and display the symbolic result | |
x = Symbol('x') | |
expr = x**2 | |
indefinite_integral = integrate(expr, x) | |
print("Indefinite integral:") | |
print(indefinite_integral) | |
``` | |
NOTE: If there was an indefinite integral before you manipulated the expression, always try the above approach before proceeding. | |
For numerical verification, always use definite integrals: | |
```python | |
# Use specific bounds and verify numerically | |
definite_integral = integrate(expr, (x, 0, 1)) | |
print("Definite integral value:") | |
print(float(definite_integral.evalf())) | |
# Include mpmath verification: | |
import mpmath | |
f = lambda x: x**2 | |
mpmath_result = mpmath.quad(f, [0, 1]) | |
print("mpmath verification:", float(mpmath_result)) | |
``` | |
9. If using SymPy to evalute an infinite sum, attempt using the infinite sum, and in addition report what the sum of the first 100 terms is as a sanity check. | |
10. Do not use SciPy. | |
11. Always use this template when working with systems of equations to handle potential linear dependence correctly. | |
```python | |
from sympy import Matrix, symbols, solve | |
def analyze_system(A, b): | |
'Analyze a system Ax = b using row reduction. Returns whether solution exists and if it's unique.' | |
# Augmented matrix [A|b] | |
aug = Matrix(A.row_join(b)) | |
# Get row echelon form | |
rref, pivots = aug.rref() | |
print("Row reduced augmented matrix:") | |
print(rref) | |
print("\\nPivot columns:", pivots) | |
# Get rank of coefficient matrix and augmented matrix | |
rank_A = Matrix(A).rank() | |
rank_aug = aug.rank() | |
print(f"\\nRank of coefficient matrix: {rank_A}") | |
print(f"Rank of augmented matrix: {rank_aug}") | |
if rank_aug > rank_A: | |
print("\\nNo solution exists") | |
return None | |
elif rank_A < A.cols: | |
print("\\nInfinitely many solutions exist") | |
return "infinite" | |
else: | |
print("\\nUnique solution exists") | |
return "unique" | |
# When solving a system Ax = b: | |
A = Matrix([[...], [...], [...]]) # coefficient matrix | |
b = Matrix([[...], [...], [...]]) # right-hand side | |
# Analyze system | |
result = analyze_system(A, b) | |
if result == "infinite": | |
# Get parametric form of solution | |
aug = Matrix(A.row_join(b)) | |
rref, pivots = aug.rref() | |
# Get free variables | |
vars = symbols('x y z') # adjust variable names as needed | |
free_vars = [var for i, var in enumerate(vars) if i not in pivots] | |
print("\\nParametric solution (t is free parameter):") | |
for i, var in enumerate(vars): | |
if i in pivots: | |
row = pivots.index(i) | |
expr = rref[row, -1] | |
for j, free_var in enumerate(free_vars): | |
expr -= rref[row, pivots[-1] + 1 + j] * free_var | |
print(f"{var} = {expr}") | |
else: | |
print(f"{var} = t") # use different parameter names for multiple free variables | |
``` | |
""" | |
def load_proof_repository(): | |
"""Load the proof repository from the repository file""" | |
repo_path = Path("Lebl-theorems-all.json") | |
try: | |
with open(repo_path, "r") as f: | |
return json.load(f) | |
except Exception as e: | |
logger.error(f"Error loading proof repository: {str(e)}") | |
return None | |
TOPIC_MAPPINGS = { | |
"integration": ["integral", "integrable", "riemann", "integrate", "antiderivative"], | |
"continuity": ["continuous", "discontinuous", "discontinuity", "uniformly continuous"], | |
"sequences": ["sequence", "convergent", "divergent", "monotone", "subsequence"], | |
"series": ["series", "sum", "convergent series", "power series"], | |
"differentiation": ["derivative", "differentiable", "differential"], | |
"limits": ["limit", "cluster point", "accumulation"], | |
"functions": ["function", "mapping", "surjective", "injective", "bijective"], | |
"bounded": ["bound", "bounded above", "bounded below", "supremum", "infimum"] | |
} | |
def get_related_terms(topic): | |
"""Get all related terms for a given topic""" | |
# Get direct mappings | |
related = TOPIC_MAPPINGS.get(topic.lower(), []) | |
# Add the original topic | |
related.append(topic.lower()) | |
# Remove duplicates while preserving order | |
return list(dict.fromkeys(related)) | |
def matches_topic(text, topic_terms): | |
"""Check if any topic terms appear in the text""" | |
text_lower = text.lower() | |
return any(term in text_lower for term in topic_terms) | |
def get_relevant_proofs(topic): | |
"""Get relevant proofs from repository based on topic, randomly selecting examples""" | |
repository = load_proof_repository() | |
if not repository: | |
logger.error("Failed to load proof repository") | |
return [] | |
logger.debug(f"Searching for proofs related to topic: {topic}") | |
topic_terms = get_related_terms(topic) | |
logger.debug(f"Related terms: {topic_terms}") | |
relevant_proofs = [] | |
for theorem in repository.get("dataset", {}).get("theorems", []): | |
# Check categories | |
categories = theorem.get("categories", []) | |
category_match = any(matches_topic(cat, topic_terms) for cat in categories) | |
# Check contents | |
contents = theorem.get("contents", []) | |
content_match = any(matches_topic(content, topic_terms) for content in contents) | |
# Check title | |
title = theorem.get("title", "") | |
title_match = matches_topic(title, topic_terms) | |
if (category_match or content_match or title_match): | |
if theorem.get("contents") and theorem.get("proofs"): | |
proof_content = { | |
"title": theorem.get("title", ""), | |
"contents": theorem.get("contents", []), | |
"proofs": [p.get("contents", []) for p in theorem.get("proofs", [])] | |
} | |
relevant_proofs.append(proof_content) | |
logger.debug(f"Found matching proof: {proof_content['title']}") | |
logger.debug(f"Matched via: {'categories' if category_match else 'contents' if content_match else 'title'}") | |
logger.debug(f"Found {len(relevant_proofs)} relevant proofs before sampling") | |
# Randomly select 3 proofs if we have more than 3 | |
if len(relevant_proofs) > 3: | |
selected = random.sample(relevant_proofs, 3) | |
logger.debug("Selected proofs for enhancement:") | |
for proof in selected: | |
logger.debug(f"- {proof['title']}") | |
return selected | |
return relevant_proofs | |
def enhance_prompt_with_proofs(system_prompt, subject, topic): | |
"""Enhance the system prompt with relevant proofs if subject is Real Analysis""" | |
if subject != "Real Analysis": | |
logger.debug("Skipping proof enhancement - not Real Analysis") | |
return system_prompt | |
relevant_proofs = get_relevant_proofs(topic) | |
if not relevant_proofs: | |
logger.debug(f"No relevant proofs found for topic: {topic}") | |
return system_prompt | |
logger.debug(f"Enhancing prompt with {len(relevant_proofs)} proofs") | |
# Add proof examples to the prompt | |
proof_examples = "\n\nReference these proof examples for style and approach:\n" | |
for proof in relevant_proofs: | |
logger.debug(f"Adding proof: {proof['title']}") | |
proof_examples += f"\nTheorem: {proof['title']}\n" | |
proof_examples += "Statement: " + " ".join(proof['contents']) + "\n" | |
if proof['proofs']: | |
first_proof = " ".join(proof['proofs'][0]) | |
logger.debug(f"Proof length: {len(first_proof)} characters") | |
proof_examples += "Proof: " + first_proof + "\n" | |
# Add specific instructions for using the examples | |
enhanced_prompt = f"""{system_prompt} | |
ADDITIONAL PROOF GUIDELINES: | |
1. Consider the following proof examples from an established textbook | |
2. Maintain similar level of rigor and detail | |
3. Use similar proof techniques where applicable | |
4. Follow similar notation and presentation style | |
{proof_examples}""" | |
return enhanced_prompt | |
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: | |
# Find where the question ends and solution begins | |
question_marker = "Here is a test question" | |
solution_marker = "Here is a detailed solution" | |
q_start = content.find(question_marker) | |
s_start = content.find(solution_marker) | |
if q_start != -1 and s_start != -1: | |
# Extract only the question part | |
question_content = content[q_start:s_start].strip() | |
# Remove any SymPy code or verification text if present | |
code_start = question_content.find('```python') | |
if code_start != -1: | |
question_content = question_content[:code_start].strip() | |
content = question_content | |
else: | |
# Fallback: try to split at the first occurrence of "Solution:" | |
parts = content.split('Solution:', 1) | |
content = parts[0].strip() | |
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 | |
- Randomly select one of these topic areas with equal probability | |
* Physics applications (motion, forces, work) | |
* Engineering scenarios | |
* Economics problems | |
* Biological systems | |
* Business applications | |
* Social science applications | |
* Data science applications | |
""", | |
"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: | |
- Be a Pure computation problem | |
- 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 | |
- Require numerical analysis that would only be possible to solve with a computer | |
""" | |
} | |
return problem_type_additions.get(question_type, "") | |
def get_solution_for_verification(response_text, SYMPY_CONFIRMED, final_verification=None): | |
""" | |
Extract the relevant parts of the solution for verification based on whether | |
the original solution was correct or not. Always preserves the original question. | |
Returns tuple of (question, solution). | |
""" | |
# Extract the question using the specific markers | |
question_start = "Here is a test question" | |
solution_start = "Here is a detailed solution to the test question" | |
# Find the question section | |
q_start = response_text.find(question_start) | |
q_end = response_text.find(solution_start) | |
if q_start == -1 or q_end == -1: | |
logger.error("Could not find question markers") | |
# Return best effort split - assume first paragraph is question | |
paragraphs = response_text.split('\n\n') | |
if len(paragraphs) > 1: | |
return paragraphs[0].strip(), '\n\n'.join(paragraphs[1:]).strip() | |
else: | |
# If we can't even split paragraphs, just return the whole text as both question and solution | |
return response_text.strip(), response_text.strip() | |
# Extract question | |
question = response_text[q_start:q_end].strip() | |
# If we have a final verification solution, use that | |
if final_verification: | |
marker = "Here is the complete verified solution:" | |
if marker in final_verification: | |
solution = final_verification.split(marker)[1].strip() | |
logger.debug("Using final verified solution for verification") | |
return question, solution | |
else: | |
# If marker not found in final verification, extract solution part | |
logger.debug("Marker not found in final verification, using full verification text") | |
return question, final_verification.strip() | |
# Otherwise, extract original solution (before SymPy code if present) | |
original_solution = response_text[q_end:] | |
sympy_start = original_solution.find('```python') | |
if sympy_start != -1: | |
solution = original_solution[:sympy_start].strip() | |
else: | |
solution = original_solution.strip() | |
logger.debug("Using original solution for verification") | |
return question, solution | |
def verify_with_chatgpt(question, solution): | |
""" | |
Send the solution to ChatGPT for verification and grading. | |
Returns the verification response. | |
""" | |
try: | |
# Extract SymPy verification if it exists | |
sympy_start = solution.find("Here's the SymPy code") | |
sympy_end = solution.find("Verification Analysis:") | |
sympy_section = "" | |
main_solution = solution | |
if sympy_start != -1 and sympy_end != -1: | |
sympy_section = solution[sympy_start:sympy_end].strip() | |
main_solution = solution[:sympy_start].strip() | |
# Construct the prompt for ChatGPT | |
verification_prompt = f"""As an expert mathematician, please verify and grade this mathematics solution. | |
Analyze the following aspects: | |
1. Mathematical Correctness (50 points): | |
- Are all calculations correct? | |
- Are proofs logically sound? | |
- Are all steps properly justified? | |
2. Completeness (20 points): | |
- Are all necessary cases considered? | |
- Are edge cases addressed? | |
- Are all required steps shown? | |
3. Clarity and Presentation (20 points): | |
- Is the solution well-organized? | |
- Are steps clearly explained? | |
- Is mathematical notation used correctly? | |
4. Mathematical Sophistication (10 points): | |
- Is the approach elegant? | |
- Are efficient methods used? | |
- Is mathematical insight demonstrated? | |
Question: | |
{question} | |
Student's Solution: | |
{main_solution} | |
{f"The student also provided the following SymPy verification of their solution: {sympy_section}" if sympy_section else ""} | |
Please consider this in your assessment of the work. | |
Please provide: | |
1. A brief point-by-point analysis of the solution | |
2. Specific comments on any errors or oversights | |
3. Suggestions for improvement (if any) | |
4. A numerical score out of 100 based on the criteria above | |
5. Finally, if and only if the numerical score is less than 90 out of 100, present a complete revised solution to which you would award a score of 100. | |
The complete revised solution if you provide one must show ALL steps and be fully self-contained. | |
Important Formatting Instructions: | |
- Put each solution step on its own line in $$ $$ | |
- For inline math expressions, always use $ $, do not use the LaTeX bracket notation, do not use backslash ( ), do not use backslash [ ] | |
- DO NOT use begin{{aligned}} or similar environments | |
- For currency amounts in dollars, write out the word dollars instead of using $ | |
* Example: 1000 dollars | |
- make sure there are never unmatched dollar signs as that will ruin the LaTeX rendering | |
Before marking a mathematical expression as incorrect: | |
- Check if the expression could be equivalent to what you think it should be through algebraic manipulation | |
- Write out the full steps to verify non-equivalence | |
- Consider multiple valid ways to write the same mathematical relationship | |
- Only mark expressions as wrong if you can prove they are not equivalent to the correct form | |
- When grading mathematical solutions: | |
Format your response with clear headers and bullet points.""" | |
# Call OpenAI API | |
response = openai.chat.completions.create( | |
model="o1-preview", | |
messages=[ | |
{ | |
"role": "assistant", | |
"content": "I am an expert mathematics professor who grades student solutions using LaTeX formatting. When writing inline math, I always write math using LaTeX $ $ delimiters - NEVER the backslash parentheses or backslash brackets approach. I am FORBIDDEN from ever using any notation that uses backslash followed by open parenthesis, backslash followed by open bracket, or backslash followed by square bracket. For displayed equations, I use $$ $$." | |
}, | |
{ | |
"role": "user", | |
"content": verification_prompt | |
} | |
], | |
temperature=1 | |
) | |
# Extract the verification text from the response | |
verification_text = response.choices[0].message.content | |
return verification_text | |
except Exception as e: | |
logger.error(f"Error in ChatGPT verification: {str(e)}") | |
return f"Error in ChatGPT verification: {str(e)}" | |
def append_chatgpt_verification(initial_response, SYMPY_CONFIRMED=None, final_verification=None): | |
""" | |
Main function to handle the ChatGPT verification process. | |
Now handles cases where SymPy verification wasn't performed. | |
""" | |
try: | |
# Get the appropriate solution text for verification | |
question, solution_text = get_solution_for_verification(initial_response, SYMPY_CONFIRMED, final_verification) | |
# Get ChatGPT's verification | |
chatgpt_verification = verify_with_chatgpt(question, solution_text) | |
if chatgpt_verification: | |
full_response = f"{initial_response}\n\nChatGPT Verification and Grading:\n{chatgpt_verification}" | |
return full_response | |
return initial_response | |
except Exception as e: | |
logger.error(f"Error in verification process: {str(e)}") | |
return initial_response + f"\n\nError in ChatGPT verification: {str(e)}" | |
def generate_question(subject, difficulty, question_type, subtopic=None, use_enhancement=False, include_chatgpt="no"): | |
"""Generate a single math question with additional verification""" | |
try: | |
logger.debug(f"ChatGPT verification: {'enabled' if include_chatgpt == 'yes' else 'disabled'}") | |
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}") | |
logger.debug(f"Textbook enhancement: {'enabled' if use_enhancement else 'disabled'}") | |
# 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) | |
selected_topic = subtopic if subtopic else random.choice(SUBJECT_SUBTOPICS[subject]) | |
logger.debug(f"Using selected subtopic: {selected_topic}") | |
problem_type_addition = get_problem_type_addition(question_type) | |
system_prompt = f"""You are a mathematics professor. | |
Part I. Write 10 {question_type} exam questions that can be solved analytically, without numerical methods, | |
in increasing order of difficulty that rigorously test a student's mastery on the topic {selected_topic} in {subject} | |
at the level that would be expected on a final exam at a top tier university (Stanford, Harvard, MIT, Caltech, Oxford, etc). | |
The easiest question should still require at least one step beyond direct formula recognitition. | |
The medium questions must require multiple techniques and require mastery of relevant mathematical concents. | |
The hardest two questions must require advance techniques would be very tricky even for an undergraduate mathematics major at a top university, but still must be analytically solvable. | |
Observe the following about {question_type} questions: {problem_type_addition}. | |
Note: If you specify a quadrant restriction (e.g. "in the first quadrant") in a problem with calculating area between lines/curves or volume between surfaces, make sure the question is CLEAR about what regions you intend to be included in the solution, by breaking up the question. Examples: | |
- NOT CLEAR question: Find the area of the region bounded by the curves y = sin(x), y = cos(x), and x = 7*pi/4 in the first quadrant. | |
- CLEAR question: Find the area of the region bounded by the curves y = sin(x), y = cos(x), and x = 7*pi/4. Then find the area of that region that intersects with the first quadrant. | |
Part II. Now select the problem that is number {difficulty} on your exam, state the question again and provide a solution. You MUST do this part as well, do not tell me to prompt again. | |
1. Begin the output for Part II with the text "Here is a test question that is a {question_type} question on {subject} covering {selected_topic} of difficulty level {difficulty} out of 10." | |
2. Important LaTeX formatting for both Part I and Part II | |
- Make sure that the question statement uses proper LaTeX math mode | |
- 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 | |
- When writing questions involving currency expressed in dollars NEVER use the `$` symbol as it will be interepreted as math mode. ALWAYS write out the word dollars. | |
* Example: 1000 dollars | |
3. For the detailed soltuion | |
- Begin the solution with "Here is a detailed solution to the test question." | |
- If the question involves geometry make sure to identify any general geometric formulas that apply, For example: | |
* Areas/volumes of common shapes and solids | |
* Cross-sectional areas of geometric figures | |
* Arc lengths and sector areas | |
- When setting up differential equations either in calculus or differential equation applications | |
* carefully consider the direction of change in variables | |
* ensure integration bounds align with the physical direction of the process being modeled | |
- The solution must be analytical. It must not rely on numerical methods. | |
* NO part of the solution may resort to or be based on numerical analysis. | |
* The only numerical calculations that should be done are those that could be done on a simple scientific calculator. | |
* Make sure to simplify completely as far as analytical methods will allow | |
4. Maintain clear formatting | |
5. For Computation or Application questions (NOT Proof questions): at the end of the solution output, print SymPy code that you would use to solve or verify the main equations in the question. | |
Observe the folloiwng SymPy Guidelines | |
{SYMPY_GUIDELINES} | |
6. For problems where the subject is Real Analysis, observe the following guidelines: | |
a. **Justify Every Step** | |
- Provide detailed reasoning for each step and explicitly justify every bounding argument, inequality, or limit claim. | |
- If concluding that terms vanish in a limit, clearly explain why. | |
- When using supremum/infimum, justify its behavior under limits, differentiation, or integration, ensuring it does not introduce discontinuities. | |
b. **Handling Limits and Differentiability** | |
- In epsilon-delta proofs, clearly explain why the chosen delta works. | |
- When using limit substitutions, justify why the transformation preserves limit behavior. | |
- If verifying differentiability, explicitly state why continuity at that point is necessary and confirm that continuity has been established before proceeding. | |
- If proving continuity or differentiability, check symmetry in approach from both sides (left-hand and right-hand limits or derivatives). | |
c. **Function Definitions and Explicit Statements** | |
- When proving continuity, explicitly confirm that f(x) is **defined** at the point of interest and state what its value is. | |
- If a function is given piecewise, clearly state the function values at transition points before evaluating limits. | |
d. **Limit Justifications and Transitions** | |
- When using standard limits briefly justify why it applies | |
- If a limit is computed informally before a formal epsilon-delta proof, explicitly state that the formal proof serves to confirm the computed limit rigorously. | |
- Ensure smooth logical transitions between different parts of the proof by briefly explaining why one step leads naturally to the next. | |
e. **Function Properties and Integrability** | |
- If stating that a function is Riemann integrable, compact, or uniformly continuous, explain why. | |
- If claiming a function is continuous for all x not equal to zero, explicitly justify why using function composition, bounded functions, or known theorems. | |
- When assuming an integral is finite, provide justification based on function class properties (e.g., Riemann integrability implies boundedness). | |
f. **Inequalities and Asymptotics** | |
- When using inequalities (e.g., Hölder’s, Jensen’s), explain why they apply and what function properties make them relevant. | |
- If using factorial ratios or infinite series sums, explicitly state their rate of convergence and reference known bounds (e.g., Stirling’s approximation). | |
g. **Uniform Convergence and Sequence Behavior** | |
- When proving uniform convergence, ensure that the bound obtained is independent of x to establish uniform control. | |
- If using asymptotic behavior (e.g., factorial ratios tending to zero), provide explicit justification rather than just stating the result. | |
h. **Clarify the Use of Key Theorems (e.g., Squeeze Theorem)** | |
- When using the squeeze theorem, clearly state why both bounding functions tend to the same limit and explicitly apply the theorem in the conclusion. | |
i. **Logical Flow and Transitions** | |
- After major steps (e.g., computing a limit, verifying continuity), summarize why the step was necessary and how it connects to the next part of the proof. | |
- If transitioning from an informal calculation to a formal proof, explicitly state the purpose of the formal proof in confirming the earlier result. | |
j. **Concluding and Intuitive Explanations** | |
- Conclude with an intuitive explanation of why the result makes sense, possibly connecting it to known theorems or simple examples. | |
- In notes after the proof, highlight potential sources of confusion for students and clarify tricky aspects of the problem. | |
7. When finding critical points in multivariable calculus: | |
- Always check what happens when any variable equals zero (except where undefined) | |
- Just because a point is ruled out of the domain doesn't mean that entire line/curve is ruled out | |
- When the Hessian is inconclusive, evaluate the function along the critical curves to determine behavior | |
- Don't rely solely on the Hessian - consider direct function evaluation and nearby points | |
8. When using symmetry arguments: | |
- Explicitly state what is symmetric | |
- Identify the axis/plane of symmetry | |
9. In calculus do not forget opportunities to apply power-reduction formulas for trig functions | |
- e.g.: Integral from 0 to pi of [cos(theta)]^(2n) d(theta) = (pi / 2^(2n)) * (2n choose n), where choose is the combinatorial choose function | |
10. In expanding or factoring polynomial expressions, be careful not to make errors | |
- (1+u^2)^2 is equal to (1+2u^2+u^4), NOT equal to (1+3u^2+u^4) | |
11. When finding points where dy/dx = 0 in parametric equations: | |
- (a) First find the ratio dy/dx = (dy/dt)/(dx/dt) | |
- (b) Find t-values where this ratio equals 0 | |
- (c) CRITICAL: For any t-values where both dy/dt = 0 AND dx/dt = 0: | |
- (d) These are potential "corner points" or cusps | |
- (e) Must apply parametric L'Hôpital's rule: | |
* Define f(t) = dy/dt and g(t) = dx/dt | |
* If f(t₀) = g(t₀) = 0, evaluate lim[t→t₀] f'(t)/g'(t) | |
* This limit, if it exists, gives the actual slope at t₀ | |
Only conclude the tangent is horizontal if this limit equals 0 | |
- (f) For each solution t: | |
- Calculate the corresponding point (x(t), y(t)) | |
- Verify the point lies on the curve | |
- State whether it's a regular point or special point (cusp, corner, etc.) | |
12. Be careful with trigonometric expressions involving powers | |
- Example: solving sin^2(x)=cos(x) can be solved as 1-cos^2(x)=cos(x) | |
""" | |
#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 | |
# Only enhance the prompt if explicitly requested AND it's a Real Analysis proof | |
if use_enhancement and subject == "Real Analysis" and question_type == "proof": | |
logger.debug("Applying textbook enhancement to prompt") | |
system_prompt = enhance_prompt_with_proofs(system_prompt, subject, selected_topic) | |
else: | |
logger.debug("Skipping textbook enhancement") | |
logger.debug("Sending request to Anthropic API") | |
message = anthropic.messages.create( | |
model = "claude-3-5-sonnet-20241022", | |
max_tokens=4096, | |
temperature=0.9, | |
messages=[{ | |
"role": "user", | |
"content": system_prompt | |
}] | |
) | |
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") | |
# Initialize verification variables | |
sympy_output = None | |
has_discrepancy = False | |
SYMPY_CONFIRMED = None | |
revised_solution = None | |
final_verification = None | |
# Execute SymPy code if present | |
sympy_output = extract_and_run_sympy_code_simple(response_text) | |
# Only proceed with verification if SymPy output exists | |
if sympy_output is not None: # Add this check | |
if "Error" in sympy_output: | |
verification_text = "SymPy verification failed with error. Manual solution must be verified independently." | |
SYMPY_CONFIRMED = "Inconclusive" | |
response_text = f"{response_text}\n\nSymPy Verification Results:\n```\n{sympy_output}\n```\n\nVerification Analysis:\n{verification_text}" | |
else: | |
resolution_text, has_discrepancy, revised_solution, SYMPY_CONFIRMED = 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_text}" | |
if has_discrepancy and revised_solution: | |
final_verification = perform_final_verification(revised_solution, SYMPY_CONFIRMED) | |
response_text += "\n\nFinal Expert Verification:\n" + final_verification | |
# add the ChatGPT verification | |
if include_chatgpt == "yes": | |
response_text = append_chatgpt_verification( | |
response_text, | |
SYMPY_CONFIRMED if sympy_output is not None else None, | |
final_verification if sympy_output is not None and has_discrepancy else None | |
) | |
# 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, None | |
def extract_and_run_sympy_code_simple(response_text): | |
""" | |
Extract SymPy code from the response and execute it. | |
""" | |
try: | |
# Extract code | |
sympy_start = response_text.find('```python') | |
if sympy_start == -1: | |
return None # Changed from error message to None | |
code_start = response_text.find('\n', sympy_start) + 1 | |
code_end = response_text.find('```', code_start) | |
if code_end == -1: | |
return None # Changed from error message to None | |
sympy_code = response_text[code_start:code_end].strip() | |
# Import SymPy at the module level | |
import sympy | |
# Create globals dict with all SymPy functions | |
globals_dict = {} | |
globals_dict.update(vars(sympy)) | |
globals_dict.update({ | |
'print': print, | |
'float': float, | |
'Symbol': sympy.Symbol, | |
'symbols': sympy.symbols, | |
'solve': sympy.solve, | |
'sqrt': sympy.sqrt, | |
'pi': sympy.pi, | |
'diff': sympy.diff, | |
'integrate': sympy.integrate, | |
'simplify': sympy.simplify, | |
'Matrix': sympy.Matrix | |
}) | |
# Remove the sympy import line from the code if present | |
lines = sympy_code.split('\n') | |
filtered_lines = [line for line in lines if not line.strip().startswith('from sympy import') and not line.strip().startswith('import sympy')] | |
modified_code = '\n'.join(filtered_lines) | |
# Capture output | |
import io | |
from contextlib import redirect_stdout | |
output_buffer = io.StringIO() | |
with redirect_stdout(output_buffer): | |
exec(modified_code, globals_dict) | |
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. | |
Returns tuple of (resolution_text, has_discrepancy, revised_solution) | |
""" | |
has_discrepancy = False #Initialize | |
resolution_text = "" | |
revised_solution = None | |
SYMPY_CONFIRMED = None # Initialize at the start | |
try: | |
resolution_prompt = f"""Here is a mathematics question with two answers. | |
1. First, write out both answers: | |
- Original solution: [write the final answer] | |
- SymPy solution: [write the SymPy answer] | |
2. To prove equivalence, you MUST do at least ONE of the following: | |
a) Algebraically transform one expression into the other through valid steps | |
b) Show that they evaluate to the sane number. | |
NOTE: Before you assert that the SymPy and Original solution evaluate to the same number, CAREFULLY calculate the value of each. If the expressions are not identical in symbolic form, double check: CALCULATE each solution CORRECTLY out to 10 decimal places and compare the results. | |
3. For special functions (like hypergeometric functions): | |
- Do not assume equivalence without verification | |
- Use series expansions or numerical evaluation at test points if needed | |
- Explicitly state if you cannot verify equivalence | |
4. After your analysis, conclude ONE of the following: | |
If equivalence is PROVEN: | |
- Write "SYMPY_CONFIRMED: True" on its own line (this means SymPy's output CONFIRMS the original solution) | |
- Explain exactly how you proved equivalence | |
- Show all steps of the verification | |
If equivalence CANNOT be proven: | |
- Write "SYMPY_CONFIRMED: False" on its own line | |
- Explain why equivalence cannot be established including errors you now detect in the original solution. | |
- Write "Here is the revised complete solution:" and then write out an ENTIRE correct solution from beginning to end showing all steps. | |
Do not try to shorten the text by saying that certain steps are the same as in the original solution. Write them out again. | |
Do not try to shorten the text by saying "the rest remains the same". Write it out again. | |
Under "Here is the revised complete solution:" there should be a complete solution to the question in full from beginning to end. | |
If verification is INCONCLUSIVE: | |
- Write "SYMPY_CONFIRMED: Inconclusive" on its own line | |
- Explain why equivalence cannot be determined | |
- Request a new SymPy verification with additional checks | |
Never claim solutions match without showing explicit mathematical proof of equivalence. | |
Please maintain the same LaTeX formatting as the original solution. | |
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_simple(resolution_text) | |
resolution_text += "\n\nNew SymPy Verification Results:\n```\n" + new_sympy_output + "\n```" | |
# Determine if there was a discrepancy that required a revised solution | |
# Check for any indication of inconsistency or error | |
inconsistency_phrases = [ | |
"inconsistent", "inconsistency", "incorrect", "error", "wrong", | |
"discrepancy", "mistaken", "mistake" | |
] | |
has_discrepancy = any(phrase in resolution_text.lower() for phrase in inconsistency_phrases) | |
# Look for the required marker phrase and extract the solution after it | |
marker = "Here is the revised complete solution:" | |
revised_solution = None | |
if has_discrepancy: | |
# Split at the marker | |
if marker in resolution_text: | |
parts = resolution_text.split(marker, maxsplit=1) | |
if len(parts) > 1: | |
revised_solution = parts[1].strip() | |
# If the solution seems too short (might be partial), don't accept it | |
if len(revised_solution) < 100: # Rough minimum length for a complete solution | |
revised_solution = None | |
# If we didn't find a complete solution, force a recheck | |
if not revised_solution: | |
logger.debug("Initial solution extraction failed, requesting a complete solution") | |
# Make a new API call specifically requesting a complete solution | |
complete_solution_prompt = f"""The previous solution had inconsistencies. Please provide a complete solution | |
from beginning to end. Start your response with exactly this phrase: | |
"Here is the revised complete solution:" | |
Then write out the entire solution, including all parts both correct and corrected. | |
Do not refer to the original solution or say any parts remain the same. | |
Original problem and verification results: | |
{initial_response} | |
SymPy Results: | |
{sympy_output}""" | |
try: | |
message = anthropic.messages.create( | |
model="claude-3-5-sonnet-20241022", | |
max_tokens=4096, | |
temperature=0.2, | |
messages=[{"role": "user", "content": complete_solution_prompt}] | |
) | |
new_response = message.content[0].text | |
if marker in new_response: | |
parts = new_response.split(marker, maxsplit=1) | |
if len(parts) > 1: | |
revised_solution = parts[1].strip() | |
except Exception as e: | |
logger.error(f"Error in solution recheck: {str(e)}") | |
# Parse whether SymPy was correct | |
SYMPY_CONFIRMED = None | |
if "SYMPY_CONFIRMED: True" in resolution_text: | |
SYMPY_CONFIRMED = True | |
elif "SYMPY_CONFIRMED: False" in resolution_text: | |
SYMPY_CONFIRMED = False | |
return resolution_text, has_discrepancy, revised_solution, SYMPY_CONFIRMED | |
except Exception as e: | |
logger.error(f"Error in discrepancy resolution: {str(e)}") | |
resolution_text = f"Error in resolution: {str(e)}" | |
has_discrepancy = False # Explicitly set in error case | |
revised_solution = None | |
return resolution_text, has_discrepancy, revised_solution, SYMPY_CONFIRMED | |
def perform_final_verification(revised_solution, SYMPY_CONFIRMED): | |
""" | |
Perform a final verification of the revised solution. | |
""" | |
verification_prompt = f"""As an expert mathematician, please carefully verify this revised solution to an advanced mathematics problem. | |
Revised Solution to Verify: | |
{revised_solution} | |
Please follow these steps exactly: | |
1. First, analyze the solution for: | |
- Mathematical correctness | |
- Missing cases or assumptions | |
- Completeness and rigor | |
- Necessary conditions and edge cases | |
- Any subtle errors or oversights | |
2. Write exactly this phrase to begin your analysis: | |
"Here is the complete verified solution:" | |
3. Then write out the ENTIRE solution from beginning to end, including: | |
- All correct parts from the original solution | |
- All needed corrections | |
- All additional cases and verifications | |
- Any missing steps or assumptions | |
- Any necessary additional proofs or derivations | |
4. The answer aligns with the {'SymPy' if SYMPY_CONFIRMED else 'original'} answer proven correct | |
Your complete solution must: | |
- Be completely self-contained, do not assume the reader has read the previous solution | |
- Not refer to the original solution, again do not assume the reader has read the previous solution | |
- Show every step of the calculation | |
- Include all necessary verifications | |
- Maintain proper LaTeX formatting with $ for inline math and $ on separate lines | |
- When referring to the dollar as a currency, never use the `$` symbol but rather write out the word dollar | |
Remember to write out the complete solution even if you are repeating things from the first solution - the goal is to have a single, complete, verified solution, INCLUDING all details and full mathematical rigor, and WITHOUT assuming the reader has read any of the previous solution material. | |
""" | |
try: | |
# Make API call for final verification | |
message = anthropic.messages.create( | |
model="claude-3-5-sonnet-20241022", | |
max_tokens=4096, | |
temperature=0.2, | |
messages=[{ | |
"role": "user", | |
"content": verification_prompt | |
}] | |
) | |
verification_result = message.content[0].text | |
# If verification includes new SymPy code, run it | |
if "```python" in verification_result: | |
new_sympy_output = extract_and_run_sympy_code_simple(verification_result) | |
verification_result += "\n\nFinal SymPy Verification:\n```\n" + new_sympy_output + "\n```" | |
return verification_result | |
except Exception as e: | |
logger.error(f"Error in final verification: {str(e)}") | |
return f"Error in final verification: {str(e)}" | |
# Add this function to update subtopic choices | |
def update_subtopics(subject): | |
logger.debug(f"update_subtopics called with subject: {subject}") | |
if subject in SUBJECT_SUBTOPICS: | |
choices = SUBJECT_SUBTOPICS[subject] | |
logger.debug(f"Found subtopics: {choices}") | |
return gr.Dropdown(choices=choices, visible=True) | |
logger.debug("No subtopics found for subject") | |
return gr.Dropdown(choices=[], visible=False) | |
# Create Gradio interface | |
with gr.Blocks() as interface: | |
gr.Markdown("# Advanced Mathematics Question Generator") | |
gr.Markdown("""Generates 10 university-level mathematics questions using Claude 3.5 Sonnet. | |
Solves the question of selected difficulty with Claude 3.5 Sonnet and SymPy verification. | |
Final verification possible with ChatGPT o1-Preview toggle. | |
Limited to 25 requests per day.""") | |
with gr.Row(): | |
with gr.Column(): | |
subject_dropdown = gr.Dropdown( | |
choices=[""]+list(SUBJECT_SUBTOPICS.keys()), # Use dictionary keys | |
label="Select Mathematics Subject", | |
info="Choose a subject for the question" | |
) | |
# Add this after subject_dropdown | |
subtopic_dropdown = gr.Dropdown( | |
choices=[], # Empty initially | |
label="Select Subtopic", | |
info="Choose a specific topic within the subject", | |
) | |
# Connect the update function | |
subject_dropdown.change( | |
update_subtopics, | |
inputs=[subject_dropdown], | |
outputs=[subtopic_dropdown] | |
) | |
difficulty_slider = gr.Slider( | |
minimum=1, | |
maximum=10, | |
step=1, | |
value=5, | |
label="Difficulty Level", | |
info="1: Very Easy, 5: Moderate, 10: Very Difficult" | |
) | |
question_type = gr.Radio( | |
choices=["computation", "proof", "application"], | |
label="Question Type", | |
info="Select the type of question you want", | |
value="computation" | |
) | |
# Add ChatGPT verification toggle | |
chatgpt_verify = gr.Radio( | |
choices=["yes", "no"], | |
label="Include ChatGPT Verification", | |
info="Enable/disable ChatGPT grading (disable saves credits)", | |
value="yes" | |
) | |
# enhancement checkbox | |
use_enhancement = gr.Radio( | |
choices=["yes", "no"], | |
label="Enhance with Textbook Material if Real Analysis", | |
info="Include relevant textbook examples to guide question generation", | |
value="no" | |
) | |
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)") | |
# Update the click event to include the new parameter | |
generate_btn.click( | |
generate_question, | |
inputs=[ | |
subject_dropdown, | |
difficulty_slider, | |
question_type, | |
subtopic_dropdown, # Add this | |
use_enhancement, | |
chatgpt_verify | |
], | |
outputs=[output_text, questions_file, full_file] | |
) | |
if __name__ == "__main__": | |
logger.info("Starting application") | |
interface.launch() |