From b93ead3a7b71d63e44653dd2dda71ddb388cc920 Mon Sep 17 00:00:00 2001 From: Cristiano Ferreira Date: Mon, 17 Jun 2024 22:51:59 +0100 Subject: [PATCH 1/7] Update speaking generation endpoints. --- app.py | 136 +++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 114 insertions(+), 22 deletions(-) diff --git a/app.py b/app.py index e7fa177..a2a2fe2 100644 --- a/app.py +++ b/app.py @@ -351,12 +351,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", @@ -1080,21 +1080,91 @@ 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", "") + + question = question + " In your answer you should consider: " + " ".join(prompts) + suffix + + 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 +1175,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 +1223,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 +1294,7 @@ def get_level_exam(): except Exception as e: return str(e) + @app.route('/level_utas', methods=['GET']) @jwt_required() def get_level_utas(): From 2adb7d1847602042379b18d818b23aa053ddd53e Mon Sep 17 00:00:00 2001 From: Cristiano Ferreira Date: Tue, 25 Jun 2024 20:49:27 +0100 Subject: [PATCH 2/7] Listening part 1. --- app.py | 5 +++-- helper/constants.py | 2 ++ helper/exercises.py | 20 ++++++++++++++++++-- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/app.py b/app.py index a2a2fe2..0072408 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 { diff --git a/helper/constants.py b/helper/constants.py index c5f924c..3074171 100644 --- a/helper/constants.py +++ b/helper/constants.py @@ -19,6 +19,8 @@ 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'] TOTAL_READING_PASSAGE_1_EXERCISES = 13 TOTAL_READING_PASSAGE_2_EXERCISES = 13 diff --git a/helper/exercises.py b/helper/exercises.py index 1d05bee..5b954e4 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) @@ -951,13 +961,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"]}' } ] From 9a696bbeb5d6baf24dfe62ce1c81f86a635a751e Mon Sep 17 00:00:00 2001 From: Cristiano Ferreira Date: Thu, 27 Jun 2024 21:29:22 +0100 Subject: [PATCH 3/7] Listening part 2. --- app.py | 2 +- helper/constants.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app.py b/app.py index 0072408..92edd15 100644 --- a/app.py +++ b/app.py @@ -86,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)) diff --git a/helper/constants.py b/helper/constants.py index 3074171..24c6281 100644 --- a/helper/constants.py +++ b/helper/constants.py @@ -21,6 +21,7 @@ READING_EXERCISE_TYPES = ['fillBlanks', 'writeBlanks', 'trueFalse', 'paragraphMa 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'] TOTAL_READING_PASSAGE_1_EXERCISES = 13 TOTAL_READING_PASSAGE_2_EXERCISES = 13 From a3cd1cdf590e8d129824a95e03065a2b1c1b6876 Mon Sep 17 00:00:00 2001 From: Cristiano Ferreira Date: Thu, 27 Jun 2024 22:03:59 +0100 Subject: [PATCH 4/7] Listening part 3 and 4. --- app.py | 2 +- helper/constants.py | 2 ++ helper/exercises.py | 17 +++++++++++------ 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/app.py b/app.py index 92edd15..e0afe36 100644 --- a/app.py +++ b/app.py @@ -116,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)) diff --git a/helper/constants.py b/helper/constants.py index 24c6281..67df516 100644 --- a/helper/constants.py +++ b/helper/constants.py @@ -22,6 +22,8 @@ LISTENING_EXERCISE_TYPES = ['multipleChoice', 'writeBlanksQuestions', 'writeBlan 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 5b954e4..8c12869 100644 --- a/helper/exercises.py +++ b/helper/exercises.py @@ -410,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.') @@ -477,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": @@ -733,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", @@ -747,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 + '"') } @@ -763,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", @@ -778,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 + '"') } From a8b46160d4f1a8627027b4072787b3e6e39a9e79 Mon Sep 17 00:00:00 2001 From: Cristiano Ferreira Date: Thu, 27 Jun 2024 22:31:57 +0100 Subject: [PATCH 5/7] Minor fixes to speaking. --- app.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app.py b/app.py index e0afe36..b1d302d 100644 --- a/app.py +++ b/app.py @@ -600,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", @@ -1144,7 +1144,9 @@ def generate_video_2(): question = data.get("question") suffix = data.get("suffix", "") - question = question + " In your answer you should consider: " + " ".join(prompts) + 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. " From e693f5ee2a311100b33e1791a746b8d729bed077 Mon Sep 17 00:00:00 2001 From: Cristiano Ferreira Date: Thu, 27 Jun 2024 22:48:42 +0100 Subject: [PATCH 6/7] Make speaking 1 questions simple. --- app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app.py b/app.py index b1d302d..5dcc446 100644 --- a/app.py +++ b/app.py @@ -592,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)) @@ -618,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 ' From 565874ad414145bdcbb9d73bbac3e97bb90509c7 Mon Sep 17 00:00:00 2001 From: Cristiano Ferreira Date: Fri, 28 Jun 2024 18:33:42 +0100 Subject: [PATCH 7/7] Minor improvements to speaking. --- app.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app.py b/app.py index 5dcc446..98a71b4 100644 --- a/app.py +++ b/app.py @@ -814,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, ' @@ -848,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", @@ -866,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.')