diff --git a/app.py b/app.py index e7fa177..98a71b4 100644 --- a/app.py +++ b/app.py @@ -53,7 +53,7 @@ def get_listening_section_1_question(): difficulty = request.args.get("difficulty", default=random.choice(difficulties)) if (len(req_exercises) == 0): - req_exercises = random.sample(LISTENING_EXERCISE_TYPES, 1) + req_exercises = random.sample(LISTENING_1_EXERCISE_TYPES, 1) number_of_exercises_q = divide_number_into_parts(TOTAL_LISTENING_SECTION_1_EXERCISES, len(req_exercises)) @@ -62,7 +62,8 @@ def get_listening_section_1_question(): app.logger.info("Generated conversation: " + str(processed_conversation)) start_id = 1 - exercises = generate_listening_conversation_exercises(parse_conversation(processed_conversation), req_exercises, + exercises = generate_listening_conversation_exercises(parse_conversation(processed_conversation), + req_exercises, number_of_exercises_q, start_id, difficulty) return { @@ -85,7 +86,7 @@ def get_listening_section_2_question(): difficulty = request.args.get("difficulty", default=random.choice(difficulties)) if (len(req_exercises) == 0): - req_exercises = random.sample(LISTENING_EXERCISE_TYPES, 2) + req_exercises = random.sample(LISTENING_2_EXERCISE_TYPES, 2) number_of_exercises_q = divide_number_into_parts(TOTAL_LISTENING_SECTION_2_EXERCISES, len(req_exercises)) @@ -115,7 +116,7 @@ def get_listening_section_3_question(): difficulty = request.args.get("difficulty", default=random.choice(difficulties)) if (len(req_exercises) == 0): - req_exercises = random.sample(LISTENING_EXERCISE_TYPES, 1) + req_exercises = random.sample(LISTENING_3_EXERCISE_TYPES, 1) number_of_exercises_q = divide_number_into_parts(TOTAL_LISTENING_SECTION_3_EXERCISES, len(req_exercises)) @@ -351,12 +352,12 @@ def grade_writing_task_2(): { "role": "user", "content": ( - 'Evaluate the given Writing Task 2 response based on the IELTS grading system, ensuring a ' - 'strict assessment that penalizes errors. Deduct points for deviations from the task, and ' - 'assign a score of 0 if the response fails to address the question. Additionally, provide an ' - 'exemplary answer with a minimum of 250 words, along with a detailed commentary highlighting ' - 'both strengths and weaknesses in the response.' - '\n Question: "' + question + '" \n Answer: "' + answer + '"') + 'Evaluate the given Writing Task 2 response based on the IELTS grading system, ensuring a ' + 'strict assessment that penalizes errors. Deduct points for deviations from the task, and ' + 'assign a score of 0 if the response fails to address the question. Additionally, provide an ' + 'exemplary answer with a minimum of 250 words, along with a detailed commentary highlighting ' + 'both strengths and weaknesses in the response.' + '\n Question: "' + question + '" \n Answer: "' + answer + '"') }, { "role": "user", @@ -591,7 +592,7 @@ def grade_speaking_task_1(): @app.route('/speaking_task_1', methods=['GET']) @jwt_required() def get_speaking_task_1_question(): - difficulty = request.args.get("difficulty", default=random.choice(difficulties)) + difficulty = request.args.get("difficulty", default="easy") first_topic = request.args.get("first_topic", default=random.choice(mti_topics)) second_topic = request.args.get("second_topic", default=random.choice(mti_topics)) @@ -599,7 +600,7 @@ def get_speaking_task_1_question(): "first_topic": "topic 1", "second_topic": "topic 2", "questions": [ - "Introductory question, should start with a greeting and introduce a question about the first topic.", + "Introductory question, should start with a greeting and introduce a question about the first topic, starting the topic with 'Let's talk about x' and then the question.", "Follow up question about the first topic", "Follow up question about the first topic", "Question about second topic", @@ -617,7 +618,7 @@ def get_speaking_task_1_question(): { "role": "user", "content": ( - 'Craft 5 thought-provoking questions of ' + difficulty + ' difficulty for IELTS Speaking Part 1 ' + 'Craft 5 simple questions of easy difficulty for IELTS Speaking Part 1 ' 'that encourages candidates to delve deeply into ' 'personal experiences, preferences, or insights on the topic ' 'of "' + first_topic + '" and the topic of "' + second_topic + '". Instruct the candidate ' @@ -813,7 +814,7 @@ def get_speaking_task_2_question(): 'Create a question of medium difficulty for IELTS Speaking Part 2 ' 'that encourages candidates to narrate a ' 'personal experience or story related to the topic ' - 'of "' + random.choice(mti_topics) + '". Include 3 prompts that ' + 'of "' + topic + '". Include 3 prompts that ' 'guide the candidate to describe ' 'specific aspects of the experience, ' 'such as details about the situation, ' @@ -847,7 +848,7 @@ def get_speaking_task_3_question(): json_format = { "topic": "topic", "questions": [ - "Introductory question, should start with a greeting and introduce a question about the topic.", + "Introductory question about the topic.", "Follow up question about the topic", "Follow up question about the topic", "Follow up question about the topic", @@ -865,8 +866,7 @@ def get_speaking_task_3_question(): "role": "user", "content": ( 'Formulate a set of 5 questions of hard difficulty for IELTS Speaking Part 3 that encourage candidates to engage in a ' - 'meaningful discussion on the topic of "' + random.choice( - mti_topics) + '". Provide inquiries, ensuring ' + 'meaningful discussion on the topic of "' + topic + '". Provide inquiries, ensuring ' 'they explore various aspects, perspectives, and implications related to the topic.' 'Make sure that the generated question does not contain forbidden subjects in muslim countries.') @@ -1080,21 +1080,93 @@ def save_speaking(): return str(e) -@app.route("/speaking/generate_speaking_video", methods=['POST']) +@app.route("/speaking/generate_video_1", methods=['POST']) @jwt_required() -def generate_speaking_video(): +def generate_video_1(): + try: + data = request.get_json() + sp3_questions = [] + avatar = data.get("avatar", random.choice(list(AvatarEnum)).value) + + request_id = str(uuid.uuid4()) + logging.info("POST - generate_video_1 - Received request to generate video 1. " + "Use this id to track the logs: " + str(request_id) + " - Request data: " + str( + request.get_json())) + + logging.info("POST - generate_video_1 - " + str(request_id) + " - Creating videos for speaking part 1.") + for question in data["questions"]: + logging.info("POST - generate_video_1 - " + str(request_id) + " - Creating video for question: " + question) + result = create_video(question, avatar) + logging.info("POST - generate_video_1 - " + str(request_id) + " - Video created: " + result) + if result is not None: + sound_file_path = VIDEO_FILES_PATH + result + firebase_file_path = FIREBASE_SPEAKING_VIDEO_FILES_PATH + result + logging.info( + "POST - generate_video_1 - " + str( + request_id) + " - Uploading video to firebase: " + firebase_file_path) + url = upload_file_firebase_get_url(FIREBASE_BUCKET, firebase_file_path, sound_file_path) + logging.info( + "POST - generate_video_1 - " + str( + request_id) + " - Uploaded video to firebase: " + url) + video = { + "text": question, + "video_path": firebase_file_path, + "video_url": url + } + sp3_questions.append(video) + else: + logging.error("POST - generate_video_1 - " + str( + request_id) + " - Failed to create video for part 1 question: " + question) + + response = { + "prompts": sp3_questions, + "first_title": data["first_topic"], + "second_title": data["second_topic"], + "type": "interactiveSpeaking", + "id": uuid.uuid4() + } + logging.info( + "POST - generate_video_1 - " + str( + request_id) + " - Finished creating videos for speaking part 1: " + str(response)) + return response + except Exception as e: + return str(e) + + +@app.route("/speaking/generate_video_2", methods=['POST']) +@jwt_required() +def generate_video_2(): try: data = request.get_json() avatar = data.get("avatar", random.choice(list(AvatarEnum)).value) prompts = data.get("prompts", []) question = data.get("question") - if len(prompts) > 0: - question = question + " In your answer you should consider: " + " ".join(prompts) - sp1_result = create_video(question, avatar) - if sp1_result is not None: - sound_file_path = VIDEO_FILES_PATH + sp1_result - firebase_file_path = FIREBASE_SPEAKING_VIDEO_FILES_PATH + sp1_result + suffix = data.get("suffix", "") + + # Removed as the examiner should not say what is on the card. + # question = question + " In your answer you should consider: " + " ".join(prompts) + suffix + question = question + "\nYou have 1 minute to take notes." + + request_id = str(uuid.uuid4()) + logging.info("POST - generate_video_2 - Received request to generate video 2. " + "Use this id to track the logs: " + str(request_id) + " - Request data: " + str( + request.get_json())) + + logging.info("POST - generate_video_2 - " + str(request_id) + " - Creating video for speaking part 2.") + logging.info("POST - generate_video_2 - " + str(request_id) + " - Creating video for question: " + question) + result = create_video(question, avatar) + logging.info("POST - generate_video_2 - " + str(request_id) + " - Video created: " + result) + + if result is not None: + sound_file_path = VIDEO_FILES_PATH + result + firebase_file_path = FIREBASE_SPEAKING_VIDEO_FILES_PATH + result + logging.info( + "POST - generate_video_2 - " + str( + request_id) + " - Uploading video to firebase: " + firebase_file_path) url = upload_file_firebase_get_url(FIREBASE_BUCKET, firebase_file_path, sound_file_path) + logging.info( + "POST - generate_video_2 - " + str( + request_id) + " - Uploaded video to firebase: " + url) sp1_video_path = firebase_file_path sp1_video_url = url @@ -1105,31 +1177,47 @@ def generate_speaking_video(): "video_url": sp1_video_url, "video_path": sp1_video_path, "type": "speaking", - "id": uuid.uuid4() + "id": uuid.uuid4(), + "suffix": suffix } else: - app.logger.error("Failed to create video for part 1 question: " + data["question"]) - return str("Failed to create video for part 1 question: " + data["question"]) + logging.error("POST - generate_video_2 - " + str( + request_id) + " - Failed to create video for part 2 question: " + question) + return str("Failed to create video for part 2 question: " + data["question"]) except Exception as e: return str(e) -@app.route("/speaking/generate_interactive_video", methods=['POST']) +@app.route("/speaking/generate_video_3", methods=['POST']) @jwt_required() -def generate_interactive_video(): +def generate_video_3(): try: data = request.get_json() sp3_questions = [] avatar = data.get("avatar", random.choice(list(AvatarEnum)).value) - app.logger.info('Creating videos for speaking part 3') + request_id = str(uuid.uuid4()) + logging.info("POST - generate_video_3 - Received request to generate video 3. " + "Use this id to track the logs: " + str(request_id) + " - Request data: " + str( + request.get_json())) + + logging.info("POST - generate_video_3 - " + str(request_id) + " - Creating videos for speaking part 3.") for question in data["questions"]: + logging.info("POST - generate_video_3 - " + str(request_id) + " - Creating video for question: " + question) result = create_video(question, avatar) + logging.info("POST - generate_video_3 - " + str(request_id) + " - Video created: " + result) + if result is not None: sound_file_path = VIDEO_FILES_PATH + result firebase_file_path = FIREBASE_SPEAKING_VIDEO_FILES_PATH + result + logging.info( + "POST - generate_video_3 - " + str( + request_id) + " - Uploading video to firebase: " + firebase_file_path) url = upload_file_firebase_get_url(FIREBASE_BUCKET, firebase_file_path, sound_file_path) + logging.info( + "POST - generate_video_3 - " + str( + request_id) + " - Uploaded video to firebase: " + url) video = { "text": question, "video_path": firebase_file_path, @@ -1137,14 +1225,19 @@ def generate_interactive_video(): } sp3_questions.append(video) else: - app.app.logger.error("Failed to create video for part 3 question: " + question) + logging.error("POST - generate_video_3 - " + str( + request_id) + " - Failed to create video for part 3 question: " + question) - return { + response = { "prompts": sp3_questions, "title": data["topic"], "type": "interactiveSpeaking", "id": uuid.uuid4() } + logging.info( + "POST - generate_video_3 - " + str( + request_id) + " - Finished creating videos for speaking part 3: " + str(response)) + return response except Exception as e: return str(e) @@ -1203,6 +1296,7 @@ def get_level_exam(): except Exception as e: return str(e) + @app.route('/level_utas', methods=['GET']) @jwt_required() def get_level_utas(): diff --git a/helper/constants.py b/helper/constants.py index c5f924c..67df516 100644 --- a/helper/constants.py +++ b/helper/constants.py @@ -19,6 +19,11 @@ GEN_TEXT_FIELDS = ['title'] LISTENING_GEN_FIELDS = ['transcript', 'exercise'] READING_EXERCISE_TYPES = ['fillBlanks', 'writeBlanks', 'trueFalse', 'paragraphMatch'] LISTENING_EXERCISE_TYPES = ['multipleChoice', 'writeBlanksQuestions', 'writeBlanksFill', 'writeBlanksForm'] +LISTENING_1_EXERCISE_TYPES = ['multipleChoice', 'writeBlanksQuestions', 'writeBlanksFill', 'writeBlanksFill', + 'writeBlanksForm', 'writeBlanksForm', 'writeBlanksForm', 'writeBlanksForm'] +LISTENING_2_EXERCISE_TYPES = ['multipleChoice', 'writeBlanksQuestions'] +LISTENING_3_EXERCISE_TYPES = ['multipleChoice3Options', 'writeBlanksQuestions'] +LISTENING_4_EXERCISE_TYPES = ['multipleChoice', 'writeBlanksQuestions', 'writeBlanksFill', 'writeBlanksForm'] TOTAL_READING_PASSAGE_1_EXERCISES = 13 TOTAL_READING_PASSAGE_2_EXERCISES = 13 diff --git a/helper/exercises.py b/helper/exercises.py index 1d05bee..8c12869 100644 --- a/helper/exercises.py +++ b/helper/exercises.py @@ -283,6 +283,16 @@ def generate_listening_1_conversation(topic: str): 'Make sure that the generated conversation does not contain forbidden subjects in ' 'muslim countries.') + }, + { + "role": "user", + "content": 'Try to have misleading discourse (refer multiple dates, multiple colors and etc).' + + }, + { + "role": "user", + "content": 'Try to have spelling of names (cities, people, etc)' + } ] token_count = count_total_tokens(messages) @@ -400,7 +410,7 @@ def generate_listening_4_monologue(topic: str): { "role": "user", "content": ( - 'Generate a comprehensive monologue on the academic subject ' + 'Generate a comprehensive and complex monologue on the academic subject ' 'of: "' + topic + '". Make sure that the generated monologue does not contain forbidden subjects in ' 'muslim countries.') @@ -467,7 +477,12 @@ def generate_listening_conversation_exercises(conversation: str, req_exercises: if req_exercise == "multipleChoice": question = gen_multiple_choice_exercise_listening_conversation(conversation, number_of_exercises, start_id, - difficulty) + difficulty, 4) + exercises.append(question) + print("Added multiple choice: " + str(question)) + elif req_exercise == "multipleChoice3Options": + question = gen_multiple_choice_exercise_listening_conversation(conversation, number_of_exercises, start_id, + difficulty, 3) exercises.append(question) print("Added multiple choice: " + str(question)) elif req_exercise == "writeBlanksQuestions": @@ -723,7 +738,7 @@ def assign_letters_to_paragraphs(paragraphs): return result -def gen_multiple_choice_exercise_listening_conversation(text: str, quantity: int, start_id, difficulty): +def gen_multiple_choice_exercise_listening_conversation(text: str, quantity: int, start_id, difficulty, n_options=4): messages = [ { "role": "system", @@ -737,7 +752,7 @@ def gen_multiple_choice_exercise_listening_conversation(text: str, quantity: int { "role": "user", "content": ( - 'Generate ' + str(quantity) + ' ' + difficulty + ' difficulty multiple choice questions of 4 options ' + 'Generate ' + str(quantity) + ' ' + difficulty + ' difficulty multiple choice questions of ' + str(n_options) + ' options ' 'of for this conversation:\n"' + text + '"') } @@ -753,7 +768,7 @@ def gen_multiple_choice_exercise_listening_conversation(text: str, quantity: int } -def gen_multiple_choice_exercise_listening_monologue(text: str, quantity: int, start_id, difficulty): +def gen_multiple_choice_exercise_listening_monologue(text: str, quantity: int, start_id, difficulty, n_options=4): messages = [ { "role": "system", @@ -768,7 +783,7 @@ def gen_multiple_choice_exercise_listening_monologue(text: str, quantity: int, s "role": "user", "content": ( 'Generate ' + str( - quantity) + ' ' + difficulty + ' difficulty multiple choice questions of 4 options ' + quantity) + ' ' + difficulty + ' difficulty multiple choice questions of ' + str(n_options) + ' options ' 'of for this monologue:\n"' + text + '"') } @@ -951,13 +966,19 @@ def gen_write_blanks_form_exercise_listening_conversation(text: str, quantity: i "role": "system", "content": ( 'You are a helpful assistant designed to output JSON on this format: ' - '{"form": ["key: value", "key2: value"]}') + '{"form": ["key": "value", "key2": "value"]}') }, { "role": "user", "content": ( 'Generate a form with ' + str( - quantity) + ' ' + difficulty + ' difficulty key-value pairs about this conversation:\n"' + text + '"') + quantity) + ' entries with information about this conversation:\n"' + text + '"') + + }, + { + "role": "user", + "content": 'It must be a form and not questions. ' + 'Example: {"form": ["Color of car": "blue", "Brand of car": "toyota"]}' } ]