@@ -1,4 +1,5 @@
|
||||
import random
|
||||
from typing import List
|
||||
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
from fastapi import APIRouter, Depends, Path, Query, UploadFile
|
||||
@@ -31,7 +32,7 @@ async def upload(
|
||||
@inject
|
||||
async def generate_listening_dialog(
|
||||
section: int = Path(..., ge=1, le=4),
|
||||
difficulty: str = Query(default=None),
|
||||
difficulty: List[str] = Query(default=None),
|
||||
topic: str = Query(default=None),
|
||||
listening_controller: IListeningController = Depends(Provide[controller])
|
||||
):
|
||||
|
||||
@@ -59,7 +59,7 @@ async def get_speaking_task(
|
||||
topic: Optional[str] = Query(None),
|
||||
first_topic: Optional[str] = Query(None),
|
||||
second_topic: Optional[str] = Query(None),
|
||||
difficulty: Optional[str] = None,
|
||||
difficulty: List[str] = Query(default=None),
|
||||
speaking_controller: ISpeakingController = Depends(Provide[controller])
|
||||
):
|
||||
if not second_topic:
|
||||
@@ -67,8 +67,7 @@ async def get_speaking_task(
|
||||
else:
|
||||
topic_or_first_topic = first_topic if first_topic else random.choice(EducationalContent.MTI_TOPICS)
|
||||
|
||||
if not difficulty:
|
||||
difficulty = random.choice(random.choice(EducationalContent.DIFFICULTIES))
|
||||
difficulty = [random.choice(EducationalContent.DIFFICULTIES)] if not difficulty else difficulty
|
||||
|
||||
second_topic = second_topic if second_topic else random.choice(EducationalContent.MTI_TOPICS)
|
||||
return await speaking_controller.get_speaking_part(task, topic_or_first_topic, second_topic, difficulty)
|
||||
|
||||
@@ -20,10 +20,10 @@ writing_router = APIRouter()
|
||||
async def generate_writing_academic(
|
||||
task: int = Path(..., ge=1, le=2),
|
||||
file: UploadFile = File(...),
|
||||
difficulty: Optional[List[str]] = None,
|
||||
difficulty: List[str] = Query(default=None),
|
||||
writing_controller: IWritingController = Depends(Provide[controller])
|
||||
):
|
||||
difficulty = random.choice(EducationalContent.DIFFICULTIES) if not difficulty else difficulty
|
||||
difficulty = [random.choice(EducationalContent.DIFFICULTIES)] if not difficulty else difficulty
|
||||
return await writing_controller.get_writing_task_academic_question(task, file, difficulty)
|
||||
|
||||
|
||||
@@ -34,10 +34,10 @@ async def generate_writing_academic(
|
||||
@inject
|
||||
async def generate_writing(
|
||||
task: int = Path(..., ge=1, le=2),
|
||||
difficulty: Optional[str] = None,
|
||||
difficulty: List[str] = Query(default=None),
|
||||
topic: str = Query(default=None),
|
||||
writing_controller: IWritingController = Depends(Provide[controller])
|
||||
):
|
||||
difficulty = random.choice(EducationalContent.DIFFICULTIES) if not difficulty else difficulty
|
||||
difficulty = [random.choice(EducationalContent.DIFFICULTIES)] if not difficulty else difficulty
|
||||
topic = random.choice(EducationalContent.MTI_TOPICS) if not topic else topic
|
||||
return await writing_controller.get_writing_task_general_question(task, topic, difficulty)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import List
|
||||
|
||||
from fastapi import UploadFile
|
||||
|
||||
@@ -10,7 +11,7 @@ class IListeningController(ABC):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def generate_listening_dialog(self, section_id: int, topic: str, difficulty: str):
|
||||
async def generate_listening_dialog(self, section_id: int, topic: str, difficulty: List[str]):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import List
|
||||
|
||||
|
||||
class ISpeakingController(ABC):
|
||||
|
||||
@abstractmethod
|
||||
async def get_speaking_part(self, task: int, topic: str, second_topic: str, difficulty: str):
|
||||
async def get_speaking_part(self, task: int, topic: str, second_topic: str, difficulty: List[str]):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import List
|
||||
|
||||
from fastapi.datastructures import UploadFile
|
||||
|
||||
@@ -6,9 +7,9 @@ from fastapi.datastructures import UploadFile
|
||||
class IWritingController(ABC):
|
||||
|
||||
@abstractmethod
|
||||
async def get_writing_task_general_question(self, task: int, topic: str, difficulty: str):
|
||||
async def get_writing_task_general_question(self, task: int, topic: str, difficulty: List[str]):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def get_writing_task_academic_question(self, task: int, attachment: UploadFile, difficulty: str):
|
||||
async def get_writing_task_academic_question(self, task: int, attachment: UploadFile, difficulty: List[str]):
|
||||
pass
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import io
|
||||
from typing import List
|
||||
|
||||
from fastapi import UploadFile
|
||||
from fastapi.responses import StreamingResponse, Response
|
||||
@@ -20,7 +21,7 @@ class ListeningController(IListeningController):
|
||||
else:
|
||||
return res
|
||||
|
||||
async def generate_listening_dialog(self, section_id: int, topic: str, difficulty: str):
|
||||
async def generate_listening_dialog(self, section_id: int, topic: str, difficulty: List[str]):
|
||||
return await self._service.generate_listening_dialog(section_id, topic, difficulty)
|
||||
|
||||
async def get_listening_question(self, dto: ListeningExercisesDTO):
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
from ielts_be.controllers import ISpeakingController
|
||||
from ielts_be.services import ISpeakingService, IVideoGeneratorService
|
||||
@@ -11,7 +12,7 @@ class SpeakingController(ISpeakingController):
|
||||
self._vid_gen = vid_gen
|
||||
self._logger = logging.getLogger(__name__)
|
||||
|
||||
async def get_speaking_part(self, task: int, topic: str, second_topic: str, difficulty: str):
|
||||
async def get_speaking_part(self, task: int, topic: str, second_topic: str, difficulty: List[str]):
|
||||
return await self._service.get_speaking_part(task, topic, second_topic, difficulty)
|
||||
|
||||
async def get_avatars(self):
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from typing import List
|
||||
|
||||
from fastapi import UploadFile, HTTPException
|
||||
|
||||
from ielts_be.controllers import IWritingController
|
||||
@@ -9,10 +11,10 @@ class WritingController(IWritingController):
|
||||
def __init__(self, writing_service: IWritingService):
|
||||
self._service = writing_service
|
||||
|
||||
async def get_writing_task_general_question(self, task: int, topic: str, difficulty: str):
|
||||
async def get_writing_task_general_question(self, task: int, topic: str, difficulty: List[str]):
|
||||
return await self._service.get_writing_task_general_question(task, topic, difficulty)
|
||||
|
||||
async def get_writing_task_academic_question(self, task: int, attachment: UploadFile, difficulty: str):
|
||||
async def get_writing_task_academic_question(self, task: int, attachment: UploadFile, difficulty: List[str]):
|
||||
if attachment.content_type not in ['image/jpeg', 'image/png']:
|
||||
raise HTTPException(status_code=400, detail="Invalid file type. Only JPEG and PNG allowed.")
|
||||
return await self._service.get_writing_task_academic_question(task, attachment, difficulty)
|
||||
|
||||
@@ -12,7 +12,8 @@ class LevelExercises(BaseModel):
|
||||
sa_qty: Optional[int] = None
|
||||
mc_qty: Optional[int] = None
|
||||
topic: Optional[str] = None
|
||||
difficulty: Optional[str] = None
|
||||
|
||||
class LevelExercisesDTO(BaseModel):
|
||||
exercises: List[LevelExercises]
|
||||
difficulty: Optional[str] = None
|
||||
difficulty: Optional[List[str]] = None
|
||||
|
||||
@@ -17,11 +17,12 @@ class SaveListeningDTO(BaseModel):
|
||||
class ListeningExercises(BaseModel):
|
||||
type: ListeningExerciseType
|
||||
quantity: int
|
||||
difficulty: Optional[str] = None
|
||||
|
||||
class ListeningExercisesDTO(BaseModel):
|
||||
text: str
|
||||
exercises: List[ListeningExercises]
|
||||
difficulty: Optional[str]
|
||||
difficulty: Optional[List[str]] = None
|
||||
|
||||
class InstructionsDTO(BaseModel):
|
||||
text: str
|
||||
|
||||
@@ -10,8 +10,9 @@ class ReadingExercise(BaseModel):
|
||||
quantity: int
|
||||
num_random_words: Optional[int] = Field(1)
|
||||
max_words: Optional[int] = Field(3)
|
||||
difficulty: Optional[str] = None
|
||||
|
||||
class ReadingDTO(BaseModel):
|
||||
text: str = Field(...)
|
||||
exercises: List[ReadingExercise] = Field(...)
|
||||
difficulty: Optional[str] = None
|
||||
difficulty: Optional[List[str]] = None
|
||||
|
||||
@@ -2,10 +2,12 @@ from .file import FileHelper
|
||||
from .text import TextHelper
|
||||
from .token_counter import count_tokens
|
||||
from .exercises import ExercisesHelper
|
||||
from .difficulty import DifficultyHelper
|
||||
|
||||
__all__ = [
|
||||
"FileHelper",
|
||||
"TextHelper",
|
||||
"count_tokens",
|
||||
"ExercisesHelper",
|
||||
"DifficultyHelper"
|
||||
]
|
||||
|
||||
40
ielts_be/helpers/difficulty.py
Normal file
40
ielts_be/helpers/difficulty.py
Normal file
@@ -0,0 +1,40 @@
|
||||
import math
|
||||
import random
|
||||
from typing import Optional, List, Iterator
|
||||
|
||||
from ielts_be.configs.constants import EducationalContent
|
||||
|
||||
|
||||
class DifficultyHelper:
|
||||
|
||||
def __init__(self, difficulties: Optional[List[str]]):
|
||||
self.difficulties = difficulties
|
||||
self.distributed: Optional[Iterator[str]] = None
|
||||
|
||||
def distribute_for_count(self, count: int) -> None:
|
||||
if not self.difficulties or count == 0:
|
||||
return
|
||||
|
||||
result = []
|
||||
remaining = count
|
||||
difficulties_count = len(self.difficulties)
|
||||
|
||||
for i, diff in enumerate(self.difficulties):
|
||||
if i == difficulties_count - 1:
|
||||
slots = remaining
|
||||
else:
|
||||
slots = math.ceil(remaining / (difficulties_count - i))
|
||||
|
||||
result.extend([diff] * slots)
|
||||
remaining -= slots
|
||||
|
||||
self.distributed = iter(result)
|
||||
|
||||
def pick_difficulty(self, difficulty: Optional[str]) -> str:
|
||||
if difficulty:
|
||||
return difficulty if difficulty != "Random" else random.choice(EducationalContent.DIFFICULTIES)
|
||||
|
||||
if self.distributed:
|
||||
return next(self.distributed)
|
||||
|
||||
return random.choice(EducationalContent.DIFFICULTIES)
|
||||
@@ -9,7 +9,7 @@ from fastapi import UploadFile
|
||||
class IListeningService(ABC):
|
||||
|
||||
@abstractmethod
|
||||
async def generate_listening_dialog( self, section_id: int, topic: str, difficulty: str):
|
||||
async def generate_listening_dialog( self, section_id: int, topic: str, difficulty: List[str]):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
|
||||
@@ -6,7 +6,7 @@ class ISpeakingService(ABC):
|
||||
|
||||
@abstractmethod
|
||||
async def get_speaking_part(
|
||||
self, part: int, topic: str, second_topic: str, difficulty: str
|
||||
self, part: int, topic: str, second_topic: str, difficulty: List[str]
|
||||
) -> Dict:
|
||||
pass
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Optional
|
||||
from typing import Optional, List
|
||||
|
||||
from fastapi import UploadFile
|
||||
|
||||
@@ -7,11 +7,11 @@ from fastapi import UploadFile
|
||||
class IWritingService(ABC):
|
||||
|
||||
@abstractmethod
|
||||
async def get_writing_task_general_question(self, task: int, topic: str, difficulty: str):
|
||||
async def get_writing_task_general_question(self, task: int, topic: str, difficulty: List[str]):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def get_writing_task_academic_question(self, task: int, attachment: UploadFile, difficulty: str):
|
||||
async def get_writing_task_academic_question(self, task: int, attachment: UploadFile, difficulty: List[str]):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
|
||||
@@ -8,6 +8,7 @@ import random
|
||||
|
||||
from ielts_be.configs.constants import EducationalContent
|
||||
from ielts_be.dtos.level import LevelExercisesDTO
|
||||
from ielts_be.helpers import DifficultyHelper
|
||||
from ielts_be.repositories import IDocumentStore
|
||||
from ielts_be.services import (
|
||||
ILevelService, ILLMService, IReadingService,
|
||||
@@ -50,19 +51,21 @@ class LevelService(ILevelService):
|
||||
async def upload_level(self, upload: UploadFile, solutions: Optional[UploadFile] = None) -> Dict:
|
||||
return await self._upload_module.generate_level_from_file(upload, solutions)
|
||||
|
||||
async def _generate_exercise(self, req_exercise, start_id):
|
||||
async def _generate_exercise(self, req_exercise, start_id, difficulty):
|
||||
if req_exercise.type == "mcBlank":
|
||||
questions = await self._mc.gen_multiple_choice("blank_space", req_exercise.quantity, start_id)
|
||||
questions = await self._mc.gen_multiple_choice("blank_space", req_exercise.quantity, difficulty, start_id)
|
||||
questions["variant"] = "mcBlank"
|
||||
questions["type"] = "multipleChoice"
|
||||
questions["prompt"] = "Choose the correct word or group of words that completes the sentences."
|
||||
questions["difficulty"] = difficulty
|
||||
return questions
|
||||
|
||||
elif req_exercise.type == "mcUnderline":
|
||||
questions = await self._mc.gen_multiple_choice("underline", req_exercise.quantity, start_id)
|
||||
questions = await self._mc.gen_multiple_choice("underline", req_exercise.quantity, difficulty, start_id)
|
||||
questions["variant"] = "mcUnderline"
|
||||
questions["type"] = "multipleChoice"
|
||||
questions["prompt"] = "Choose the underlined word or group of words that is not correct."
|
||||
questions["difficulty"] = difficulty
|
||||
return questions
|
||||
|
||||
elif req_exercise.type == "passageUtas":
|
||||
@@ -70,34 +73,42 @@ class LevelService(ILevelService):
|
||||
exercise = await self._passage_utas.gen_reading_passage_utas(
|
||||
start_id,
|
||||
req_exercise.quantity,
|
||||
difficulty,
|
||||
topic,
|
||||
req_exercise.text_size
|
||||
)
|
||||
exercise["prompt"] = "Read the text and answer the questions below."
|
||||
|
||||
exercise["difficulty"] = difficulty
|
||||
return exercise
|
||||
|
||||
elif req_exercise.type == "fillBlanksMC":
|
||||
exercise = await self._fill_blanks.gen_fill_blanks(
|
||||
start_id,
|
||||
req_exercise.quantity,
|
||||
difficulty,
|
||||
req_exercise.text_size,
|
||||
req_exercise.topic
|
||||
)
|
||||
exercise["prompt"] = "Read the text below and choose the correct word for each space."
|
||||
exercise["difficulty"] = difficulty
|
||||
return exercise
|
||||
|
||||
async def generate_exercises(self, dto: LevelExercisesDTO):
|
||||
start_ids = []
|
||||
current_id = 1
|
||||
tasks = []
|
||||
|
||||
distributor = DifficultyHelper(dto.difficulty)
|
||||
|
||||
none_count = sum(1 for ex in dto.exercises if ex.difficulty is None)
|
||||
distributor.distribute_for_count(none_count)
|
||||
|
||||
for req_exercise in dto.exercises:
|
||||
start_ids.append(current_id)
|
||||
difficulty = distributor.pick_difficulty(req_exercise.difficulty)
|
||||
tasks.append(
|
||||
self._generate_exercise(req_exercise, current_id, difficulty)
|
||||
)
|
||||
current_id += req_exercise.quantity
|
||||
|
||||
tasks = [
|
||||
self._generate_exercise(req_exercise, start_id)
|
||||
for req_exercise, start_id in zip(dto.exercises, start_ids)
|
||||
]
|
||||
questions = await gather(*tasks)
|
||||
questions = [{'id': str(uuid4()), **exercise} for exercise in questions]
|
||||
|
||||
@@ -105,10 +116,12 @@ class LevelService(ILevelService):
|
||||
|
||||
# Just here to support other modules that I don't know if they are supposed to still be used
|
||||
async def gen_multiple_choice(self, mc_variant: str, quantity: int, start_id: int = 1):
|
||||
return await self._mc.gen_multiple_choice(mc_variant, quantity, start_id)
|
||||
difficulty = random.choice(EducationalContent.DIFFICULTIES)
|
||||
return await self._mc.gen_multiple_choice(mc_variant, quantity, difficulty, start_id)
|
||||
|
||||
async def gen_reading_passage_utas(self, start_id, mc_quantity: int, topic=Optional[str]): # sa_quantity: int,
|
||||
return await self._passage_utas.gen_reading_passage_utas(start_id, mc_quantity, topic)
|
||||
difficulty = random.choice(EducationalContent.DIFFICULTIES)
|
||||
return await self._passage_utas.gen_reading_passage_utas(start_id, mc_quantity, difficulty, topic)
|
||||
|
||||
async def gen_blank_space_text_utas(self, quantity: int, start_id: int, size: int, topic: str):
|
||||
return await self._blank_space.gen_blank_space_text_utas(quantity, start_id, size, topic)
|
||||
|
||||
@@ -11,12 +11,10 @@ class FillBlanks:
|
||||
|
||||
|
||||
async def gen_fill_blanks(
|
||||
self, start_id: int, quantity: int, size: int = 300, topic=None
|
||||
self, start_id: int, quantity: int, difficulty: str, size: int = 300, topic=None
|
||||
):
|
||||
if not topic:
|
||||
topic = random.choice(EducationalContent.MTI_TOPICS)
|
||||
print(quantity)
|
||||
print(start_id)
|
||||
messages = [
|
||||
{
|
||||
"role": "system",
|
||||
@@ -34,8 +32,15 @@ class FillBlanks:
|
||||
'JSON object containing: the modified text, a solutions array with each word\'s correct '
|
||||
'letter (A-D), and a words array containing each id with four options where one is '
|
||||
'the original word (matching the solution) and three are plausible but incorrect '
|
||||
'alternatives that maintain grammatical consistency. '
|
||||
'You cannot use repeated words!' #TODO: Solve this after
|
||||
f'alternatives that maintain grammatical consistency and {difficulty} CEFR level complexity. '
|
||||
'You cannot use repeated words!'
|
||||
# TODO: Solve this after -> forgot about this TODO just saw now in
|
||||
# 1/11/25 what I meant by this is gpt still sometimes returns repeated
|
||||
# words even with explicit instructions to not do so, this is a general problem
|
||||
# for all exercises, for more robust validation use self._llm.pydantic_prediction
|
||||
# or implement a method that calls a mapper, catches validation error messages
|
||||
# from that mapper and asks gpt to retry if creating a pydantic model for each
|
||||
# operation proves to be unsustainable
|
||||
)
|
||||
}
|
||||
]
|
||||
|
||||
@@ -10,16 +10,16 @@ class MultipleChoice:
|
||||
self._mc_variants = mc_variants
|
||||
|
||||
async def gen_multiple_choice(
|
||||
self, mc_variant: str, quantity: int, start_id: int = 1
|
||||
self, mc_variant: str, quantity: int, difficulty: str, start_id: int = 1
|
||||
):
|
||||
mc_template = self._mc_variants[mc_variant]
|
||||
blank_mod = " blank space " if mc_variant == "blank_space" else " "
|
||||
|
||||
gen_multiple_choice_for_text: str = (
|
||||
'Generate {quantity} multiple choice{blank}questions of 4 options for an english level exam, some easy '
|
||||
'questions, some intermediate questions and some advanced questions. Ensure that the questions cover '
|
||||
'a range of topics such as verb tense, subject-verb agreement, pronoun usage, sentence structure, and '
|
||||
'punctuation. Make sure every question only has 1 correct answer.'
|
||||
'Generate {quantity} multiple choice{blank}questions of 4 options for an english level exam of {difficulty} '
|
||||
'CEFR level, some easy questions, some intermediate questions and some advanced questions. Ensure that '
|
||||
'the questions cover a range of topics such as verb tense, subject-verb agreement, pronoun usage, sentence '
|
||||
'structure, and punctuation. Make sure every question only has 1 correct answer.'
|
||||
)
|
||||
|
||||
messages = [
|
||||
@@ -31,7 +31,7 @@ class MultipleChoice:
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": gen_multiple_choice_for_text.format(quantity=str(quantity), blank=blank_mod)
|
||||
"content": gen_multiple_choice_for_text.format(quantity=str(quantity), blank=blank_mod, difficulty=difficulty)
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@@ -13,11 +13,11 @@ class PassageUtas:
|
||||
self._mc_variants = mc_variants
|
||||
|
||||
async def gen_reading_passage_utas(
|
||||
self, start_id, mc_quantity: int, topic: Optional[str], word_size: Optional[int] # sa_quantity: int,
|
||||
self, start_id, mc_quantity: int, difficulty: str, topic: Optional[str] = None, word_size: Optional[int] = None# sa_quantity: int,
|
||||
):
|
||||
|
||||
passage = await self._reading_service.generate_reading_passage(1, topic, word_size)
|
||||
mc_exercises = await self._gen_text_multiple_choice_utas(passage["text"], start_id, mc_quantity)
|
||||
mc_exercises = await self._gen_text_multiple_choice_utas(passage["text"], start_id, mc_quantity, difficulty)
|
||||
mc_exercises["type"] = "multipleChoice"
|
||||
"""
|
||||
exercises: {
|
||||
@@ -61,7 +61,7 @@ class PassageUtas:
|
||||
|
||||
return question["questions"]
|
||||
|
||||
async def _gen_text_multiple_choice_utas(self, text: str, start_id: int, mc_quantity: int):
|
||||
async def _gen_text_multiple_choice_utas(self, text: str, start_id: int, mc_quantity: int, difficulty: str):
|
||||
json_template = self._mc_variants["text_mc_utas"]
|
||||
|
||||
messages = [
|
||||
@@ -71,7 +71,9 @@ class PassageUtas:
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": f'Generate {mc_quantity} multiple choice questions of 4 options for this text:\n{text}'
|
||||
"content": (
|
||||
f'Generate {mc_quantity} multiple choice questions of 4 options, {difficulty} CEFR '
|
||||
f'level difficulty, for this text:\n{text}')
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import json
|
||||
import random
|
||||
import uuid
|
||||
|
||||
from ielts_be.configs.constants import EducationalContent
|
||||
from ielts_be.services import ILLMService
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import asyncio
|
||||
from logging import getLogger
|
||||
import random
|
||||
from typing import Dict, Any, Union
|
||||
from typing import Dict, Any, Union, List
|
||||
|
||||
from starlette.datastructures import UploadFile
|
||||
|
||||
@@ -13,7 +13,7 @@ from ielts_be.configs.constants import (
|
||||
NeuralVoices, GPTModels, TemperatureSettings, EducationalContent,
|
||||
FieldsAndExercises
|
||||
)
|
||||
from ielts_be.helpers import FileHelper
|
||||
from ielts_be.helpers import FileHelper, DifficultyHelper
|
||||
from .audio_to_dialog import AudioToDialog
|
||||
from .import_listening import ImportListeningModule
|
||||
from .write_blank_forms import WriteBlankForms
|
||||
@@ -21,7 +21,6 @@ from .write_blanks import WriteBlanks
|
||||
from .write_blank_notes import WriteBlankNotes
|
||||
from ..shared import TrueFalse, MultipleChoice
|
||||
|
||||
|
||||
class ListeningService(IListeningService):
|
||||
|
||||
CONVERSATION_TAIL = (
|
||||
@@ -94,7 +93,8 @@ class ListeningService(IListeningService):
|
||||
return await self._import.import_from_file(exercises, solutions)
|
||||
|
||||
|
||||
async def generate_listening_dialog(self, section: int, topic: str, difficulty: str):
|
||||
async def generate_listening_dialog(self, section: int, topic: str, difficulty: List[str]):
|
||||
# TODO: difficulties to difficulty
|
||||
return await self._sections[f'section_{section}']["generate_dialogue"](section, topic)
|
||||
|
||||
async def transcribe_dialog(self, audio: UploadFile):
|
||||
@@ -135,6 +135,11 @@ class ListeningService(IListeningService):
|
||||
start_id = 1
|
||||
exercise_tasks = []
|
||||
|
||||
diff_helper = DifficultyHelper(dto.difficulty)
|
||||
|
||||
none_count = sum(1 for ex in dto.exercises if ex.difficulty is None)
|
||||
diff_helper.distribute_for_count(none_count)
|
||||
|
||||
for req_exercise in dto.exercises:
|
||||
exercise_tasks.append(
|
||||
self._generate_exercise(
|
||||
@@ -142,7 +147,7 @@ class ListeningService(IListeningService):
|
||||
"dialog or monologue",
|
||||
dto.text,
|
||||
start_id,
|
||||
dto.difficulty
|
||||
diff_helper.pick_difficulty(req_exercise.difficulty)
|
||||
)
|
||||
)
|
||||
start_id += req_exercise.quantity
|
||||
@@ -157,6 +162,7 @@ class ListeningService(IListeningService):
|
||||
question = await self._multiple_choice.gen_multiple_choice(
|
||||
text, req_exercise.quantity, start_id, difficulty, n_options
|
||||
)
|
||||
question["difficulty"] = difficulty
|
||||
self._logger.info(f"Added multiple choice: {question}")
|
||||
return question
|
||||
|
||||
@@ -165,6 +171,7 @@ class ListeningService(IListeningService):
|
||||
dialog_type, text, req_exercise.quantity, start_id, difficulty
|
||||
)
|
||||
question["variant"] = "questions"
|
||||
question["difficulty"] = difficulty
|
||||
self._logger.info(f"Added write blanks questions: {question}")
|
||||
return question
|
||||
|
||||
@@ -173,6 +180,7 @@ class ListeningService(IListeningService):
|
||||
dialog_type, text, req_exercise.quantity, start_id, difficulty
|
||||
)
|
||||
question["variant"] = "fill"
|
||||
question["difficulty"] = difficulty
|
||||
self._logger.info(f"Added write blanks notes: {question}")
|
||||
return question
|
||||
|
||||
@@ -181,12 +189,14 @@ class ListeningService(IListeningService):
|
||||
dialog_type, text, req_exercise.quantity, start_id, difficulty
|
||||
)
|
||||
question["variant"] = "form"
|
||||
question["difficulty"] = difficulty
|
||||
self._logger.info(f"Added write blanks form: {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, "listening"
|
||||
)
|
||||
question["difficulty"] = difficulty
|
||||
self._logger.info(f"Added trueFalse: {question}")
|
||||
return question
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from fastapi import UploadFile
|
||||
|
||||
from ielts_be.configs.constants import GPTModels, FieldsAndExercises, TemperatureSettings
|
||||
from ielts_be.dtos.reading import ReadingDTO
|
||||
from ielts_be.helpers import ExercisesHelper
|
||||
from ielts_be.helpers import ExercisesHelper, DifficultyHelper
|
||||
from ielts_be.services import IReadingService, ILLMService
|
||||
from .fill_blanks import FillBlanks
|
||||
from .idea_match import IdeaMatch
|
||||
@@ -84,6 +84,7 @@ class ReadingService(IReadingService):
|
||||
question = await self._fill_blanks.gen_summary_fill_blanks_exercise(
|
||||
text, req_exercise.quantity, start_id, difficulty, req_exercise.num_random_words
|
||||
)
|
||||
question["difficulty"] = difficulty
|
||||
self._logger.info(f"Added fill blanks: {question}")
|
||||
return question
|
||||
|
||||
@@ -91,6 +92,7 @@ class ReadingService(IReadingService):
|
||||
question = await self._true_false.gen_true_false_not_given_exercise(
|
||||
text, req_exercise.quantity, start_id, difficulty, "reading"
|
||||
)
|
||||
question["difficulty"] = difficulty
|
||||
self._logger.info(f"Added trueFalse: {question}")
|
||||
return question
|
||||
|
||||
@@ -100,6 +102,7 @@ class ReadingService(IReadingService):
|
||||
)
|
||||
|
||||
if ExercisesHelper.answer_word_limit_ok(question):
|
||||
question["difficulty"] = difficulty
|
||||
self._logger.info(f"Added write blanks: {question}")
|
||||
return question
|
||||
else:
|
||||
@@ -110,6 +113,7 @@ class ReadingService(IReadingService):
|
||||
question = await self._paragraph_match.gen_paragraph_match_exercise(
|
||||
text, req_exercise.quantity, start_id
|
||||
)
|
||||
question["difficulty"] = difficulty
|
||||
self._logger.info(f"Added paragraph match: {question}")
|
||||
return question
|
||||
|
||||
@@ -118,12 +122,14 @@ class ReadingService(IReadingService):
|
||||
text, req_exercise.quantity, start_id
|
||||
)
|
||||
question["variant"] = "ideaMatch"
|
||||
question["difficulty"] = difficulty
|
||||
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
|
||||
)
|
||||
question["difficulty"] = difficulty
|
||||
self._logger.info(f"Added multiple choice: {question}")
|
||||
return question
|
||||
|
||||
@@ -131,13 +137,18 @@ class ReadingService(IReadingService):
|
||||
exercise_tasks = []
|
||||
start_id = 1
|
||||
|
||||
diff_helper = DifficultyHelper(dto.difficulty)
|
||||
|
||||
none_count = sum(1 for ex in dto.exercises if ex.difficulty is None)
|
||||
diff_helper.distribute_for_count(none_count)
|
||||
|
||||
for req_exercise in dto.exercises:
|
||||
exercise_tasks.append(
|
||||
self._generate_single_exercise(
|
||||
req_exercise,
|
||||
dto.text,
|
||||
start_id,
|
||||
dto.difficulty
|
||||
diff_helper.pick_difficulty(req_exercise.difficulty)
|
||||
)
|
||||
)
|
||||
start_id += req_exercise.quantity
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import logging
|
||||
import re
|
||||
import random
|
||||
|
||||
from typing import Dict, List
|
||||
|
||||
@@ -99,8 +100,9 @@ class SpeakingService(ISpeakingService):
|
||||
}
|
||||
|
||||
async def get_speaking_part(
|
||||
self, part: int, topic: str, second_topic: str, difficulty: str
|
||||
self, part: int, topic: str, second_topic: str, difficulty: List[str]
|
||||
) -> Dict:
|
||||
diff = difficulty[0] if len(difficulty) == 1 else random.choice(difficulty)
|
||||
task_values = self._tasks[f'task_{part}']['get']
|
||||
|
||||
if part == 1:
|
||||
@@ -157,7 +159,7 @@ class SpeakingService(ISpeakingService):
|
||||
]
|
||||
|
||||
response["type"] = part
|
||||
response["difficulty"] = difficulty
|
||||
response["difficulty"] = diff
|
||||
|
||||
if part in {2, 3}:
|
||||
response["topic"] = topic
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import random
|
||||
from typing import List, Dict, Optional
|
||||
|
||||
from fastapi import UploadFile
|
||||
@@ -16,7 +17,8 @@ class WritingService(IWritingService):
|
||||
self._llm = llm
|
||||
self._grade = GradeWriting(llm, file_storage, ai_detector)
|
||||
|
||||
async def get_writing_task_general_question(self, task: int, topic: str, difficulty: str):
|
||||
async def get_writing_task_general_question(self, task: int, topic: str, difficulty: List[str]):
|
||||
diff = difficulty[0] if len(difficulty) == 1 else random.choice(difficulty)
|
||||
messages = [
|
||||
{
|
||||
"role": "system",
|
||||
@@ -24,7 +26,7 @@ class WritingService(IWritingService):
|
||||
'You are a helpful assistant designed to output JSON on this format: {"prompt": "prompt content"}'
|
||||
)
|
||||
},
|
||||
*get_writing_args_general(task, topic, difficulty)
|
||||
*get_writing_args_general(task, topic, diff)
|
||||
]
|
||||
|
||||
llm_model = GPTModels.GPT_3_5_TURBO if task == 1 else GPTModels.GPT_4_O
|
||||
@@ -40,11 +42,12 @@ class WritingService(IWritingService):
|
||||
|
||||
return {
|
||||
"question": self._add_newline_before_hyphen(question) if task == 1 else question,
|
||||
"difficulty": difficulty,
|
||||
"topic": topic
|
||||
"topic": topic,
|
||||
"difficulty": diff
|
||||
}
|
||||
|
||||
async def get_writing_task_academic_question(self, task: int, file: UploadFile, difficulty: str):
|
||||
async def get_writing_task_academic_question(self, task: int, file: UploadFile, difficulty: List[str]):
|
||||
diff = difficulty[0] if len(difficulty) == 1 else random.choice(difficulty)
|
||||
messages = [
|
||||
{
|
||||
"role": "system",
|
||||
@@ -52,7 +55,7 @@ class WritingService(IWritingService):
|
||||
'You are a helpful assistant designed to output JSON on this format: {"prompt": "prompt content"}'
|
||||
)
|
||||
},
|
||||
*(await get_writing_args_academic(task, file))
|
||||
*(await get_writing_args_academic(task, file, diff))
|
||||
]
|
||||
|
||||
response = await self._llm.prediction(
|
||||
@@ -66,7 +69,7 @@ class WritingService(IWritingService):
|
||||
|
||||
return {
|
||||
"question": self._add_newline_before_hyphen(question) if task == 1 else question,
|
||||
"difficulty": difficulty,
|
||||
"difficulty": diff,
|
||||
}
|
||||
|
||||
async def grade_writing_task(self, task: int, question: str, answer: str, attachment: Optional[str] = None):
|
||||
|
||||
@@ -4,7 +4,7 @@ from typing import List, Dict
|
||||
from fastapi.datastructures import UploadFile
|
||||
|
||||
|
||||
async def get_writing_args_academic(task: int, attachment: UploadFile) -> List[Dict]:
|
||||
async def get_writing_args_academic(task: int, attachment: UploadFile, difficulty: str) -> List[Dict]:
|
||||
writing_args = {
|
||||
"1": {
|
||||
"prompt": (
|
||||
@@ -16,7 +16,8 @@ async def get_writing_args_academic(task: int, attachment: UploadFile) -> List[D
|
||||
'The generated prompt must:\n'
|
||||
'1. Clearly describe the type of visual representation in the image\n'
|
||||
'2. Provide a concise context for the data shown\n'
|
||||
'3. End with the standard IELTS Task 1 Academic instruction:\n'
|
||||
f'3. Be adequate for {difficulty} CEFR level users\n'
|
||||
'4. End with the standard IELTS Task 1 Academic instruction:\n'
|
||||
'"Summarise the information by selecting and reporting the main features, and make comparisons where relevant."'
|
||||
)
|
||||
},
|
||||
|
||||
@@ -3,5 +3,5 @@ from .logger import suppress_loggers
|
||||
|
||||
__all__ = [
|
||||
"handle_exception",
|
||||
"suppress_loggers"
|
||||
"suppress_loggers",
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user