from typing import List from openai import OpenAI from models import MultipleChoiceQuestion, MultipleChoiceOption, TEMPERATURE_UNAVAILABLE from prompts.questions import ( GENERAL_QUALITY_STANDARDS, MULTIPLE_CHOICE_STANDARDS, EXAMPLE_QUESTIONS, QUESTION_SPECIFIC_QUALITY_STANDARDS, CORRECT_ANSWER_SPECIFIC_QUALITY_STANDARDS, ANSWER_FEEDBACK_QUALITY_STANDARDS, ) from prompts.incorrect_answers import ( INCORRECT_ANSWER_EXAMPLES_WITH_EXPLANATION ) def _get_run_manager(): """Get run manager if available, otherwise return None.""" try: from ui.run_manager import get_run_manager return get_run_manager() except: return None def generate_multiple_choice_question(client: OpenAI, model: str, temperature: float, learning_objective: 'RankedLearningObjective', file_contents: List[str]) -> MultipleChoiceQuestion: """ Generate a multiple choice question for a learning objective. Args: learning_objective: Learning objective to generate a question for file_contents: List of file contents with source tags Returns: Generated multiple choice question """ run_manager = _get_run_manager() # Handle source references (could be string or list) source_references = learning_objective.source_reference if isinstance(source_references, str): source_references = [source_references] if run_manager: run_manager.log(f"Looking for content from source files: {source_references}", level="DEBUG") # Simply collect all content that matches any of the source references combined_content = "" for source_file in source_references: source_found = False for file_content in file_contents: # Look for the XML source tag with the matching filename if f"" in file_content: if run_manager: run_manager.log(f"Found matching source content for {source_file}", level="DEBUG") if combined_content: combined_content += "\n\n" combined_content += file_content source_found = True break # If no exact match found, try a more flexible match if not source_found: if run_manager: run_manager.log(f"No exact match for {source_file}, looking for partial matches", level="DEBUG") for file_content in file_contents: if source_file in file_content: if run_manager: run_manager.log(f"Found partial match for {source_file}", level="DEBUG") if combined_content: combined_content += "\n\n" combined_content += file_content source_found = True break # If still no matching content, use all file contents combined if not combined_content: if run_manager: run_manager.log(f"No content found for any source files, using all content", level="DEBUG") combined_content = "\n\n".join(file_contents) # Add multi-source instruction if needed multi_source_instruction = "" if len(source_references) > 1: multi_source_instruction = """ This learning objective spans multiple sources. Your question should: 1. Synthesize information across these sources 2. Test understanding of overarching themes or connections 3. Require knowledge from multiple sources to answer correctly """ # Create the prompt prompt = f""" Create a multiple choice question based on the following learning objective: {learning_objective.learning_objective} The correct answer to this is {learning_objective.correct_answer} Follow these important instructions for writing the quiz question: {GENERAL_QUALITY_STANDARDS} {MULTIPLE_CHOICE_STANDARDS} {EXAMPLE_QUESTIONS} {QUESTION_SPECIFIC_QUALITY_STANDARDS} {CORRECT_ANSWER_SPECIFIC_QUALITY_STANDARDS} These are the incorrect answer options: {learning_objective.incorrect_answer_options} Incorrect answers should follow the following examples with explanations: Here are some examples of high quality incorrect answer options for each learning objective: {INCORRECT_ANSWER_EXAMPLES_WITH_EXPLANATION} IMPORTANT: AVOID ABSOLUTE TERMS AND UNNECESSARY COMPARISONS Don't use words like "always," "never,", "mainly", "exclusively", "primarily" or "rather than". These words are absolute or extreme qualifiers and comparative terms that artificially limit or overgeneralize statements, creating false dichotomies or unsubstantiated hierarchies. More words you should avoid are: All, every, entire, complete, none, nothing, no one, only, solely, merely, completely, totally, utterly, always, forever, constantly, never, impossible, must, mandatory, required, instead of, as opposed to, exclusively, purely {ANSWER_FEEDBACK_QUALITY_STANDARDS} {multi_source_instruction} Below the course content that the quiz question is based on: {combined_content} """ # Generate question using instructor try: params = { "model": model, "messages": [ {"role": "system", "content": "You are an expert educational assessment creator specializing in creating high-quality multiple choice questions with detailed feedback for each option."}, {"role": "user", "content": prompt} ], "response_format": MultipleChoiceQuestion } if not TEMPERATURE_UNAVAILABLE.get(model, True): params["temperature"] = temperature completion = client.beta.chat.completions.parse(**params) response = completion.choices[0].message.parsed # Set learning objective ID and source reference response.id = learning_objective.id response.learning_objective_id = learning_objective.id response.learning_objective = learning_objective.learning_objective response.source_reference = learning_objective.source_reference # Verify all options have feedback for i, option in enumerate(response.options): if not option.feedback or option.feedback.strip() == "": if option.is_correct: option.feedback = "Good job! This is the correct answer." else: option.feedback = f"This answer is incorrect. Please review the material again." return response except Exception as e: print(f"Error generating question: {e}") # Create a fallback question options = [ MultipleChoiceOption( option_text=f"Option {chr(65+i)}", is_correct=(i==0), feedback=f"{'Correct' if i==0 else 'Incorrect'} answer." ) for i in range(4) ] return MultipleChoiceQuestion( id=learning_objective.id, question_text=f"Question for learning objective: {learning_objective.learning_objective}", options=options, learning_objective_id=learning_objective.id, learning_objective=learning_objective.learning_objective, source_reference=learning_objective.source_reference, )