Spaces:
Configuration error
Configuration error
| #!/usr/bin/env python3 | |
| """ | |
| MIT License | |
| Copyright (c) 2025 Lin Yang, Yichen Huang | |
| Permission is hereby granted, free of charge, to any person obtaining a copy | |
| of this software and associated documentation files (the "Software"), to deal | |
| in the Software without restriction, including without limitation the rights | |
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| copies of the Software, and to permit persons to whom the Software is | |
| furnished to do so, subject to the following conditions: | |
| The above copyright notice and this permission notice shall be included in all | |
| copies or substantial portions of the Software. | |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
| SOFTWARE. | |
| """ | |
| from flask import Flask, render_template, request, jsonify | |
| from flask_socketio import SocketIO, emit | |
| import os | |
| import sys | |
| import subprocess | |
| import tempfile | |
| import threading | |
| import time | |
| import json | |
| import re | |
| from datetime import datetime | |
| app = Flask(__name__) | |
| app.config['SECRET_KEY'] = 'imo-math-solver-secret-key' | |
| socketio = SocketIO(app, cors_allowed_origins="*") | |
| # Store active solving sessions | |
| active_sessions = {} | |
| AGENT_FILES = { | |
| 'gemini': 'code/agent.py', | |
| 'openai': 'code/agent_oai.py', | |
| 'xai': 'code/agent_xai.py' | |
| } | |
| def index(): | |
| """Serve the main frontend page.""" | |
| return render_template('index.html') | |
| def get_models(): | |
| """Return available API providers and their models.""" | |
| return jsonify({ | |
| 'providers': [ | |
| {'id': 'gemini', 'name': 'Google Gemini', 'model': 'gemini-2.5-pro'}, | |
| {'id': 'openai', 'name': 'OpenAI', 'model': 'gpt-5-pro-2025-10-06'}, | |
| {'id': 'xai', 'name': 'XAI', 'model': 'grok-4-0709'} | |
| ] | |
| }) | |
| def solve_problem(): | |
| """ | |
| API endpoint to solve a math problem. | |
| Expected JSON payload: | |
| { | |
| "problem": "problem statement text", | |
| "provider": "gemini|openai|xai", | |
| "max_runs": 10, | |
| "num_agents": 1, | |
| "other_prompts": "" | |
| } | |
| """ | |
| data = request.json | |
| problem_text = data.get('problem', '').strip() | |
| provider = data.get('provider', 'gemini') | |
| max_runs = int(data.get('max_runs', 10)) | |
| num_agents = int(data.get('num_agents', 1)) | |
| other_prompts = data.get('other_prompts', '').strip() | |
| session_id = data.get('session_id', str(time.time())) | |
| if not problem_text: | |
| return jsonify({'error': 'Problem statement is required'}), 400 | |
| if provider not in AGENT_FILES: | |
| return jsonify({'error': f'Invalid provider: {provider}'}), 400 | |
| # Create a temporary file for the problem | |
| with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False, encoding='utf-8') as f: | |
| f.write(problem_text) | |
| problem_file = f.name | |
| # Create log directory for this session | |
| log_dir = os.path.join('logs', f'session_{session_id}') | |
| os.makedirs(log_dir, exist_ok=True) | |
| # Store session info | |
| active_sessions[session_id] = { | |
| 'problem_file': problem_file, | |
| 'log_dir': log_dir, | |
| 'status': 'running', | |
| 'start_time': datetime.now().isoformat() | |
| } | |
| # Run the solver in a background thread | |
| def run_solver(): | |
| try: | |
| if num_agents == 1: | |
| # Single agent mode | |
| agent_file = AGENT_FILES[provider] | |
| log_file = os.path.join(log_dir, 'solution.log') | |
| cmd = [ | |
| sys.executable, agent_file, | |
| problem_file, | |
| '--log', log_file, | |
| '--max_runs', str(max_runs) | |
| ] | |
| if other_prompts: | |
| cmd.extend(['--other_prompts', other_prompts]) | |
| socketio.emit('status', { | |
| 'session_id': session_id, | |
| 'status': 'running', | |
| 'message': f'Starting {provider} agent...' | |
| }) | |
| process = subprocess.Popen( | |
| cmd, | |
| stdout=subprocess.PIPE, | |
| stderr=subprocess.PIPE, | |
| text=True | |
| ) | |
| # Stream output | |
| for line in process.stdout: | |
| socketio.emit('log', { | |
| 'session_id': session_id, | |
| 'line': line.strip() | |
| }) | |
| process.wait() | |
| # Read the solution from log file | |
| solution_found = False | |
| solution_text = '' | |
| try: | |
| with open(log_file, 'r', encoding='utf-8') as f: | |
| log_content = f.read() | |
| if "Found a correct solution in run" in log_content: | |
| solution_found = True | |
| solution_text = log_content | |
| except Exception as e: | |
| socketio.emit('error', { | |
| 'session_id': session_id, | |
| 'error': f'Error reading log: {str(e)}' | |
| }) | |
| active_sessions[session_id]['status'] = 'completed' | |
| active_sessions[session_id]['solution_found'] = solution_found | |
| socketio.emit('complete', { | |
| 'session_id': session_id, | |
| 'solution_found': solution_found, | |
| 'solution': solution_text, | |
| 'log_file': log_file | |
| }) | |
| else: | |
| # Parallel mode | |
| agent_file = AGENT_FILES[provider] | |
| cmd = [ | |
| sys.executable, 'code/run_parallel.py', | |
| problem_file, | |
| '-n', str(num_agents), | |
| '-d', log_dir, | |
| '-a', agent_file, | |
| '-m', str(max_runs) | |
| ] | |
| if other_prompts: | |
| cmd.extend(['-o', other_prompts]) | |
| socketio.emit('status', { | |
| 'session_id': session_id, | |
| 'status': 'running', | |
| 'message': f'Starting {num_agents} parallel {provider} agents...' | |
| }) | |
| process = subprocess.Popen( | |
| cmd, | |
| stdout=subprocess.PIPE, | |
| stderr=subprocess.PIPE, | |
| text=True, | |
| cwd=os.path.dirname(os.path.abspath(__file__)) | |
| ) | |
| # Stream output | |
| for line in process.stdout: | |
| socketio.emit('log', { | |
| 'session_id': session_id, | |
| 'line': line.strip() | |
| }) | |
| process.wait() | |
| # Check for solution in any log file | |
| solution_found = False | |
| solution_text = '' | |
| solution_log = None | |
| log_files = [f for f in os.listdir(log_dir) if f.endswith('.log')] | |
| for log_file_name in log_files: | |
| log_path = os.path.join(log_dir, log_file_name) | |
| try: | |
| with open(log_path, 'r', encoding='utf-8') as f: | |
| content = f.read() | |
| if "Found a correct solution in run" in content: | |
| solution_found = True | |
| solution_text = content | |
| solution_log = log_path | |
| break | |
| except Exception: | |
| pass | |
| active_sessions[session_id]['status'] = 'completed' | |
| active_sessions[session_id]['solution_found'] = solution_found | |
| socketio.emit('complete', { | |
| 'session_id': session_id, | |
| 'solution_found': solution_found, | |
| 'solution': solution_text, | |
| 'log_file': solution_log or log_dir | |
| }) | |
| except Exception as e: | |
| active_sessions[session_id]['status'] = 'error' | |
| socketio.emit('error', { | |
| 'session_id': session_id, | |
| 'error': str(e) | |
| }) | |
| finally: | |
| # Clean up temporary problem file | |
| try: | |
| os.unlink(problem_file) | |
| except Exception: | |
| pass | |
| # Start solver in background thread | |
| thread = threading.Thread(target=run_solver) | |
| thread.daemon = True | |
| thread.start() | |
| return jsonify({ | |
| 'session_id': session_id, | |
| 'status': 'started', | |
| 'message': 'Problem solving started' | |
| }) | |
| def get_session(session_id): | |
| """Get status of a solving session.""" | |
| if session_id not in active_sessions: | |
| return jsonify({'error': 'Session not found'}), 404 | |
| return jsonify(active_sessions[session_id]) | |
| if __name__ == '__main__': | |
| # Create logs directory if it doesn't exist | |
| os.makedirs('logs', exist_ok=True) | |
| print("=" * 60) | |
| print("IMO Math Problem Solver - Web Interface") | |
| print("=" * 60) | |
| print("Starting server on http://localhost:5000") | |
| print("Press Ctrl+C to stop the server") | |
| print("=" * 60) | |
| socketio.run(app, host='0.0.0.0', port=5001, debug=True, allow_unsafe_werkzeug=True) | |