import os import json from textwrap import dedent from flask import Flask, render_template, request, jsonify from crewai import Agent, Crew, Process, Task from crewai_tools import SerperDevTool from langchain_google_genai import ChatGoogleGenerativeAI from typing import List, Dict, Any # Configuration (use .env in production) os.environ["GEMINI_API_KEY"] = "AIzaSyCQpoVCdk7h7MvAQKZOUfcnkQYVkHmAKwI" os.environ["SERPER_API_KEY"] = "9b90a274d9e704ff5b21c0367f9ae1161779b573" gemini_api_key = os.environ.get("GEMINI_API_KEY") serper_api_key = os.environ.get("SERPER_API_KEY") if not gemini_api_key: raise ValueError("Gemini API key missing.") if not serper_api_key: raise ValueError("Serper API key missing.") llm = ChatGoogleGenerativeAI( model="gemini-2.0-flash", temperature=0.7, google_api_key=gemini_api_key, max_output_tokens=8000, convert_system_message_to_human=True ) search_tool = SerperDevTool() researcher = Agent( role='Chercheur de Sujets Académiques', goal=dedent("""Find factual, precise information on {topic} using the web search tool. Focus on key concepts, definitions, dates, and notable facts."""), backstory=dedent("""A meticulous researcher with a developed critical mind. Synthesizes information from multiple sources to extract the factual essence."""), tools=[search_tool], llm=llm, verbose=True, allow_delegation=False, max_iter=5 ) quiz_creator = Agent( role='Concepteur Pédagogique de Quiz', goal=dedent("""Create an engaging multiple-choice quiz based on the factual information provided by the Researcher on {topic}. Formulate clear questions, plausible options, and identify the correct answer. Include a concise explanation for each question."""), backstory=dedent("""A specialist in pedagogical design who excels at transforming raw information into effective evaluation tools."""), llm=llm, verbose=True, allow_delegation=False, max_iter=3 ) def extract_json_from_result(result_text: str) -> List[Dict[str, Any]]: try: json_start = result_text.find('[') json_end = result_text.rfind(']') + 1 if json_start != -1 and json_end != 0 and json_end > json_start: json_str = result_text[json_start:json_end] parsed_json = json.loads(json_str) if isinstance(parsed_json, list): for item in parsed_json: if not all(k in item for k in ('question', 'options', 'correct_answer')): raise ValueError("Invalid quiz element structure in JSON.") return parsed_json else: raise ValueError("The extracted JSON is not a list.") else: try: parsed_json = json.loads(result_text) if isinstance(parsed_json, list): for item in parsed_json: if not all(k in item for k in ('question', 'options', 'correct_answer')): raise ValueError("Invalid quiz element structure in JSON.") return parsed_json else: raise ValueError("The JSON found is not a list.") except json.JSONDecodeError: raise ValueError("No valid JSON block (list of objects) found in the result.") except json.JSONDecodeError as e: raise ValueError(f"JSON decoding error: {str(e)}. Contenu reçu : '{result_text[:200]}...'") except ValueError as e: raise e def research_task(topic: str, agent: Agent) -> Task: return Task( description=dedent(f"""Thorough research on '{topic}'. Identify and compile key factual information: definitions, dates, important figures, fundamental concepts, and significant events. Structure the information clearly and concisely."""), expected_output=dedent(f"""A synthetic report containing the most relevant information on '{topic}', ready to be used to create a quiz. Must include precise and verifiable facts."""), agent=agent, ) def quiz_creation_task(topic: str, agent: Agent, context_task: Task) -> Task: return Task( description=dedent(f"""Based STRICTLY on the information provided in the context of the research on '{topic}', create a multiple-choice quiz. Generate between 8 and 12 pertinent questions. For each question, provide: 'question', 'options', 'correct_answer', and 'explanation'. The output format MUST be a valid JSON list and NOTHING ELSE."""), expected_output=dedent("""A valid JSON list, where each element is a quiz question with the keys 'question', 'options', 'correct_answer', and 'explanation'."""), agent=agent, context=[context_task] ) app = Flask(__name__) app.secret_key = os.urandom(24) @app.route('/') def index(): return render_template('index.html') @app.route('/generate', methods=['POST']) def generate_quiz_endpoint(): if not request.is_json: return jsonify({'error': 'Invalid request, JSON expected.'}), 400 data = request.get_json() topic = data.get('topic') if not topic or not isinstance(topic, str) or len(topic.strip()) == 0: return jsonify({'error': 'The "topic" field is missing or invalid.'}), 400 topic = topic.strip() try: task_research = research_task(topic=topic, agent=researcher) task_quiz = quiz_creation_task(topic=topic, agent=quiz_creator, context_task=task_research) quiz_crew = Crew( agents=[researcher, quiz_creator], tasks=[task_research, task_quiz], process=Process.sequential, verbose=2 ) crew_result = quiz_crew.kickoff(inputs={'topic': topic}) if not crew_result or not hasattr(quiz_crew, 'tasks_output') or not quiz_crew.tasks_output: return jsonify({'error': 'Quiz generation failed (no crew output).'}), 500 last_task_output = quiz_crew.tasks_output[-1] raw_output = last_task_output.raw if not raw_output: return jsonify({'error': 'Quiz generation failed (empty task output).'}), 500 try: quiz_data = extract_json_from_result(raw_output) return jsonify({'success': True, 'quiz': quiz_data}) except ValueError as json_error: return jsonify({'error': f'Error during quiz finalization: {json_error}'}), 500 except Exception as e: import traceback traceback.print_exc() return jsonify({'error': f'A server error occurred: {str(e)}'}), 500 if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, debug=True)