141 lines
5.7 KiB
Python
141 lines
5.7 KiB
Python
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 .true_false import TrueFalse
|
|
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._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
|
|
)
|
|
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
|
|
|
|
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)
|
|
}
|