import asyncio from logging import getLogger from fastapi import UploadFile from app.configs.constants import GPTModels, FieldsAndExercises, TemperatureSettings from app.dtos.reading import ReadingDTO from app.helpers import ExercisesHelper from app.services.abc import IReadingService, ILLMService from .fill_blanks import FillBlanks from .idea_match import IdeaMatch from .paragraph_match import ParagraphMatch from ..shared import TrueFalse, MultipleChoice from .import_reading import ImportReadingModule from .write_blanks import WriteBlanks class ReadingService(IReadingService): def __init__(self, llm: ILLMService): self._llm = llm self._fill_blanks = FillBlanks(llm) self._idea_match = IdeaMatch(llm) self._paragraph_match = ParagraphMatch(llm) self._true_false = TrueFalse(llm) self._write_blanks = WriteBlanks(llm) self._multiple_choice = MultipleChoice(llm) self._logger = getLogger(__name__) self._import = ImportReadingModule(llm) async def import_exam(self, exercises: UploadFile, solutions: UploadFile = None): return await self._import.import_from_file(exercises, solutions) async def generate_reading_passage(self, part: int, topic: str, word_count: int = 800): part_system_message = { "1": 'The generated text should be fairly easy to understand and have multiple paragraphs.', "2": 'The generated text should be fairly hard to understand and have multiple paragraphs.', "3": ( 'The generated text should be very hard to understand and include different points, theories, ' 'subtle differences of opinions from people, correctly sourced to the person who said it, ' 'over the specified topic and have multiple paragraphs.' ) } messages = [ { "role": "system", "content": ( 'You are a helpful assistant designed to output JSON on this format: ' '{"title": "title of the text", "text": "generated text"}') }, { "role": "user", "content": ( f'Generate an extensive text for IELTS Reading Passage {part}, of at least {word_count} words, ' f'on the topic of "{topic}". The passage should offer a substantial amount of ' 'information, analysis, or narrative relevant to the chosen subject matter. This text ' 'passage aims to serve as the primary reading section of an IELTS test, providing an ' 'in-depth and comprehensive exploration of the topic. Make sure that the generated text ' 'does not contain forbidden subjects in muslim countries.' ) }, { "role": "system", "content": part_system_message[str(part)] } ] if part == 3: messages.append({ "role": "user", "content": "Use real text excerpts on your generated passage and cite the sources." }) return await self._llm.prediction( GPTModels.GPT_4_O, messages, FieldsAndExercises.GEN_TEXT_FIELDS, TemperatureSettings.GEN_QUESTION_TEMPERATURE ) async def _generate_single_exercise(self, req_exercise, text: str, start_id: int, difficulty: str) -> dict: if req_exercise.type == "fillBlanks": question = await self._fill_blanks.gen_summary_fill_blanks_exercise( text, req_exercise.quantity, start_id, difficulty, req_exercise.num_random_words ) self._logger.info(f"Added fill blanks: {question}") return question elif req_exercise.type == "trueFalse": question = await self._true_false.gen_true_false_not_given_exercise( text, req_exercise.quantity, start_id, difficulty, "reading" ) self._logger.info(f"Added trueFalse: {question}") return question elif req_exercise.type == "writeBlanks": question = await self._write_blanks.gen_write_blanks_exercise( text, req_exercise.quantity, start_id, difficulty, req_exercise.max_words ) if ExercisesHelper.answer_word_limit_ok(question): self._logger.info(f"Added write blanks: {question}") return question else: self._logger.info("Did not add write blanks because it did not respect word limit") return {} elif req_exercise.type == "paragraphMatch": question = await self._paragraph_match.gen_paragraph_match_exercise( text, req_exercise.quantity, start_id ) self._logger.info(f"Added paragraph match: {question}") return question elif req_exercise.type == "ideaMatch": question = await self._idea_match.gen_idea_match_exercise( text, req_exercise.quantity, start_id ) question["variant"] = "ideaMatch" self._logger.info(f"Added idea match: {question}") return question elif req_exercise.type == "multipleChoice": question = await self._multiple_choice.gen_multiple_choice( text, req_exercise.quantity, start_id, difficulty, 4 ) self._logger.info(f"Added multiple choice: {question}") return question async def generate_reading_exercises(self, dto: ReadingDTO): exercise_tasks = [] start_id = 1 for req_exercise in dto.exercises: exercise_tasks.append( self._generate_single_exercise( req_exercise, dto.text, start_id, dto.difficulty ) ) start_id += req_exercise.quantity return { "exercises": await asyncio.gather(*exercise_tasks) }