Fixed more or less reading import, attempted to do listening
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import random
|
||||
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
from fastapi import APIRouter, Depends, Path, Query
|
||||
from fastapi import APIRouter, Depends, Path, Query, UploadFile
|
||||
|
||||
from app.middlewares import Authorized, IsAuthenticatedViaBearerToken
|
||||
from app.controllers.abc import IListeningController
|
||||
@@ -11,6 +11,19 @@ from app.dtos.listening import SaveListeningDTO, GenerateListeningExercises, Dia
|
||||
controller = "listening_controller"
|
||||
listening_router = APIRouter()
|
||||
|
||||
@listening_router.post(
|
||||
'/import',
|
||||
dependencies=[Depends(Authorized([IsAuthenticatedViaBearerToken]))]
|
||||
)
|
||||
@inject
|
||||
async def upload(
|
||||
exercises: UploadFile,
|
||||
solutions: UploadFile = None,
|
||||
listening_controller: IListeningController = Depends(Provide[controller])
|
||||
):
|
||||
return await listening_controller.import_exam(exercises, solutions)
|
||||
|
||||
|
||||
@listening_router.get(
|
||||
'/{section}',
|
||||
dependencies=[Depends(Authorized([IsAuthenticatedViaBearerToken]))]
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import List
|
||||
|
||||
from fastapi import UploadFile
|
||||
|
||||
|
||||
class IListeningController(ABC):
|
||||
|
||||
@abstractmethod
|
||||
async def import_exam(self, exercises: UploadFile, solutions: UploadFile = None):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def generate_listening_dialog(self, section_id: int, topic: str, difficulty: str):
|
||||
pass
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import io
|
||||
|
||||
from starlette.responses import StreamingResponse
|
||||
from fastapi import UploadFile
|
||||
from starlette.responses import StreamingResponse, Response
|
||||
|
||||
from app.controllers.abc import IListeningController
|
||||
from app.dtos.listening import GenerateListeningExercises, Dialog
|
||||
@@ -12,6 +13,13 @@ class ListeningController(IListeningController):
|
||||
def __init__(self, listening_service: IListeningService):
|
||||
self._service = listening_service
|
||||
|
||||
async def import_exam(self, exercises: UploadFile, solutions: UploadFile = None):
|
||||
res = await self._service.import_exam(exercises, solutions)
|
||||
if not res:
|
||||
return Response(status_code=500)
|
||||
else:
|
||||
return res
|
||||
|
||||
async def generate_listening_dialog(self, section_id: int, topic: str, difficulty: str):
|
||||
return await self._service.generate_listening_dialog(section_id, topic, difficulty)
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import UploadFile
|
||||
from grpc import services
|
||||
from fastapi import UploadFile, Response
|
||||
|
||||
from app.controllers.abc import IReadingController
|
||||
from app.dtos.reading import ReadingDTO
|
||||
@@ -16,7 +15,11 @@ class ReadingController(IReadingController):
|
||||
self._logger = logging.getLogger(__name__)
|
||||
|
||||
async def import_exam(self, exercises: UploadFile, solutions: UploadFile = None):
|
||||
return await self._service.import_exam(exercises, solutions)
|
||||
res = await self._service.import_exam(exercises, solutions)
|
||||
if not res:
|
||||
return Response(status_code=500)
|
||||
else:
|
||||
return res
|
||||
|
||||
async def generate_reading_passage(self, passage: int, topic: Optional[str], word_count: Optional[int]):
|
||||
return await self._service.generate_reading_passage(passage, topic, word_count)
|
||||
|
||||
80
app/dtos/exams/listening.py
Normal file
80
app/dtos/exams/listening.py
Normal file
@@ -0,0 +1,80 @@
|
||||
from enum import Enum
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import List, Union, Optional, Literal
|
||||
from uuid import uuid4, UUID
|
||||
|
||||
|
||||
class ExerciseBase(BaseModel):
|
||||
id: UUID = Field(default_factory=uuid4)
|
||||
type: str
|
||||
prompt: str
|
||||
|
||||
|
||||
class TrueFalseSolution(str, Enum):
|
||||
TRUE = "true"
|
||||
FALSE = "false"
|
||||
NOT_GIVEN = "not_given"
|
||||
|
||||
class TrueFalseQuestions(BaseModel):
|
||||
prompt: str
|
||||
solution: TrueFalseSolution
|
||||
id: str
|
||||
|
||||
|
||||
class TrueFalseExercise(ExerciseBase):
|
||||
type: Literal["trueFalse"]
|
||||
questions: List[TrueFalseQuestions]
|
||||
|
||||
|
||||
class MCOption(BaseModel):
|
||||
id: str
|
||||
text: str
|
||||
|
||||
|
||||
class MCQuestion(BaseModel):
|
||||
id: str
|
||||
prompt: str
|
||||
options: List[MCOption]
|
||||
solution: str
|
||||
variant: str = "text"
|
||||
|
||||
|
||||
class MultipleChoiceExercise(ExerciseBase):
|
||||
type: Literal["multipleChoice"]
|
||||
questions: List[MCQuestion]
|
||||
|
||||
|
||||
class WriteBlanksVariant(str, Enum):
|
||||
QUESTIONS = "questions"
|
||||
FILL = "fill"
|
||||
FORM = "form"
|
||||
|
||||
|
||||
class WriteBlankSolution(BaseModel):
|
||||
id: str
|
||||
solution: List[str]
|
||||
|
||||
|
||||
class WriteBlanksExercise(ExerciseBase):
|
||||
type: Literal["writeBlanks"]
|
||||
maxWords: int
|
||||
solutions: List[WriteBlankSolution]
|
||||
text: str
|
||||
variant: Optional[WriteBlanksVariant]
|
||||
|
||||
|
||||
ListeningExercise = Union[
|
||||
TrueFalseExercise,
|
||||
MultipleChoiceExercise,
|
||||
WriteBlanksExercise
|
||||
]
|
||||
|
||||
|
||||
class ListeningSection(BaseModel):
|
||||
exercises: List[ListeningExercise]
|
||||
|
||||
|
||||
class ListeningExam(BaseModel):
|
||||
module: str = "listening"
|
||||
minTimer: Optional[int]
|
||||
sections: List[ListeningSection]
|
||||
@@ -1,7 +1,7 @@
|
||||
from enum import Enum
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import List, Union
|
||||
from typing import List, Union, Optional
|
||||
from uuid import uuid4, UUID
|
||||
|
||||
|
||||
@@ -15,10 +15,7 @@ class WriteBlanksExercise(BaseModel):
|
||||
maxWords: int
|
||||
solutions: List[WriteBlanksSolution]
|
||||
text: str
|
||||
|
||||
@property
|
||||
def prompt(self) -> str:
|
||||
return f"Choose no more than {self.maxWords} words and/or a number from the passage for each answer."
|
||||
prompt: str
|
||||
|
||||
|
||||
class MatchSentencesOption(BaseModel):
|
||||
@@ -32,20 +29,29 @@ class MatchSentencesVariant(str, Enum):
|
||||
HEADING = "heading"
|
||||
IDEAMATCH = "ideaMatch"
|
||||
|
||||
class MCOption(BaseModel):
|
||||
id: str
|
||||
text: str
|
||||
|
||||
class MCQuestion(BaseModel):
|
||||
id: str
|
||||
prompt: str
|
||||
options: List[MCOption]
|
||||
solution: str
|
||||
variant: Optional[str] = None
|
||||
|
||||
class MultipleChoice(BaseModel):
|
||||
questions: List[MCQuestion]
|
||||
type: str
|
||||
prompt: str
|
||||
|
||||
|
||||
class MatchSentencesExercise(BaseModel):
|
||||
options: List[MatchSentencesOption]
|
||||
sentences: List[MatchSentencesSentence]
|
||||
type: str = "matchSentences"
|
||||
variant: MatchSentencesVariant
|
||||
|
||||
@property
|
||||
def prompt(self) -> str:
|
||||
return (
|
||||
"Choose the correct heading for paragraphs from the list of headings below."
|
||||
if self.variant == MatchSentencesVariant.HEADING else
|
||||
"Choose the correct author for the ideas/opinions from the list of authors below."
|
||||
)
|
||||
prompt: str
|
||||
|
||||
class TrueFalseSolution(str, Enum):
|
||||
TRUE = "true"
|
||||
@@ -80,18 +86,9 @@ class FillBlanksExercise(BaseModel):
|
||||
type: str = "fillBlanks"
|
||||
words: List[FillBlanksWord]
|
||||
allowRepetition: bool = False
|
||||
prompt: str
|
||||
|
||||
@property
|
||||
def prompt(self) -> str:
|
||||
prompt = "Complete the summary below. Write the letter of the corresponding word(s) for it."
|
||||
|
||||
return (
|
||||
f"{prompt}"
|
||||
if len(self.solutions) == len(self.words) else
|
||||
f"{prompt}\\nThere are more words than spaces so you will not use them all."
|
||||
)
|
||||
|
||||
Exercise = Union[FillBlanksExercise, TrueFalseExercise, MatchSentencesExercise, WriteBlanksExercise]
|
||||
Exercise = Union[FillBlanksExercise, TrueFalseExercise, MatchSentencesExercise, WriteBlanksExercise, MultipleChoice]
|
||||
|
||||
|
||||
class Context(BaseModel):
|
||||
|
||||
32
app/mappers/listening.py
Normal file
32
app/mappers/listening.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from typing import Dict, Any
|
||||
|
||||
from app.dtos.exams.listening import TrueFalseExercise, MultipleChoiceExercise, WriteBlanksExercise, ListeningExam, \
|
||||
ListeningSection
|
||||
|
||||
|
||||
class ListeningMapper:
|
||||
@staticmethod
|
||||
def map_to_test_model(response: Dict[str, Any]) -> ListeningExam:
|
||||
sections = []
|
||||
for section in response.get('sections', []):
|
||||
section_exercises = []
|
||||
|
||||
for exercise in section['exercises']:
|
||||
exercise_type = exercise['type']
|
||||
|
||||
if exercise_type == 'trueFalse':
|
||||
section_exercises.append(TrueFalseExercise(**exercise))
|
||||
elif exercise_type == 'multipleChoice':
|
||||
section_exercises.append(MultipleChoiceExercise(**exercise))
|
||||
elif exercise_type == 'writeBlanks':
|
||||
section_exercises.append(WriteBlanksExercise(**exercise))
|
||||
else:
|
||||
raise ValueError(f"Unknown exercise type: {exercise_type}")
|
||||
|
||||
sections.append(ListeningSection(exercises=section_exercises))
|
||||
|
||||
return ListeningExam(
|
||||
sections=sections,
|
||||
minTimer=response.get('minTimer'),
|
||||
module="listening"
|
||||
)
|
||||
@@ -3,7 +3,7 @@ from typing import Dict, Any
|
||||
from app.dtos.exams.reading import (
|
||||
Part, Exam, Context, FillBlanksExercise,
|
||||
TrueFalseExercise, MatchSentencesExercise,
|
||||
WriteBlanksExercise
|
||||
WriteBlanksExercise, MultipleChoice
|
||||
)
|
||||
|
||||
|
||||
@@ -20,13 +20,18 @@ class ReadingMapper:
|
||||
'fillBlanks': FillBlanksExercise,
|
||||
'trueFalse': TrueFalseExercise,
|
||||
'matchSentences': MatchSentencesExercise,
|
||||
'writeBlanks': WriteBlanksExercise
|
||||
'writeBlanks': WriteBlanksExercise,
|
||||
'multipleChoice': MultipleChoice,
|
||||
}
|
||||
|
||||
exercises = []
|
||||
for exercise in part_exercises:
|
||||
exercise_type = exercise['type']
|
||||
exercises.append(model_map[exercise_type](**exercise))
|
||||
if exercise_type in model_map:
|
||||
model_class = model_map[exercise_type]
|
||||
exercises.append(model_class(**exercise))
|
||||
else:
|
||||
raise ValueError(f"Unknown exercise type: {exercise_type}")
|
||||
|
||||
part_kwargs = {
|
||||
"exercises": exercises,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import queue
|
||||
from abc import ABC, abstractmethod
|
||||
from queue import Queue
|
||||
from typing import Dict, List
|
||||
from typing import Dict, List, Any
|
||||
|
||||
from fastapi import UploadFile
|
||||
|
||||
@@ -23,3 +23,9 @@ class IListeningService(ABC):
|
||||
@abstractmethod
|
||||
async def get_dialog_from_audio(self, upload: UploadFile):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def import_exam(
|
||||
self, exercises: UploadFile, solutions: UploadFile = None
|
||||
) -> Dict[str, Any] | None:
|
||||
pass
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import asyncio
|
||||
from logging import getLogger
|
||||
import random
|
||||
from typing import Dict
|
||||
from typing import Dict, Any
|
||||
|
||||
from starlette.datastructures import UploadFile
|
||||
|
||||
@@ -13,6 +13,7 @@ from app.configs.constants import (
|
||||
FieldsAndExercises
|
||||
)
|
||||
from app.helpers import FileHelper
|
||||
from .import_listening import ImportListeningModule
|
||||
from .multiple_choice import MultipleChoice
|
||||
from .write_blank_forms import WriteBlankForms
|
||||
from .write_blanks import WriteBlanks
|
||||
@@ -46,6 +47,7 @@ class ListeningService(IListeningService):
|
||||
self._write_blanks = WriteBlanks(llm)
|
||||
self._write_blanks_forms = WriteBlankForms(llm)
|
||||
self._write_blanks_notes = WriteBlankNotes(llm)
|
||||
self._import = ImportListeningModule(llm)
|
||||
self._sections = {
|
||||
"section_1": {
|
||||
"topic": EducationalContent.TWO_PEOPLE_SCENARIOS,
|
||||
@@ -81,6 +83,12 @@ class ListeningService(IListeningService):
|
||||
}
|
||||
}
|
||||
|
||||
async def import_exam(
|
||||
self, exercises: UploadFile, solutions: UploadFile = None
|
||||
) -> Dict[str, Any] | None:
|
||||
return await self._import.import_from_file(exercises, solutions)
|
||||
|
||||
|
||||
async def generate_listening_dialog(self, section: int, topic: str, difficulty: str):
|
||||
return await self._sections[f'section_{section}']["generate_dialogue"](section, topic)
|
||||
|
||||
|
||||
180
app/services/impl/exam/listening/import_listening.py
Normal file
180
app/services/impl/exam/listening/import_listening.py
Normal file
@@ -0,0 +1,180 @@
|
||||
from logging import getLogger
|
||||
from typing import Dict, Any
|
||||
from uuid import uuid4
|
||||
import aiofiles
|
||||
from fastapi import UploadFile
|
||||
|
||||
from app.dtos.exams.listening import ListeningExam
|
||||
from app.helpers import FileHelper
|
||||
from app.mappers.listening import ListeningMapper
|
||||
from app.services.abc import ILLMService
|
||||
|
||||
|
||||
class ImportListeningModule:
|
||||
def __init__(self, llm_service: ILLMService):
|
||||
self._logger = getLogger(__name__)
|
||||
self._llm = llm_service
|
||||
|
||||
async def import_from_file(
|
||||
self,
|
||||
exercises: UploadFile,
|
||||
audio: UploadFile,
|
||||
solutions: UploadFile = None
|
||||
) -> Dict[str, Any] | None:
|
||||
path_id = str(uuid4())
|
||||
|
||||
ext, _ = await FileHelper.save_upload(exercises, "exercises", path_id)
|
||||
FileHelper.convert_file_to_html(
|
||||
f'./tmp/{path_id}/exercises.{ext}',
|
||||
f'./tmp/{path_id}/exercises.html'
|
||||
)
|
||||
|
||||
if solutions:
|
||||
ext, _ = await FileHelper.save_upload(solutions, "solutions", path_id)
|
||||
FileHelper.convert_file_to_html(
|
||||
f'./tmp/{path_id}/solutions.{ext}',
|
||||
f'./tmp/{path_id}/solutions.html'
|
||||
)
|
||||
|
||||
response = await self._get_listening_sections(path_id, solutions is not None)
|
||||
|
||||
FileHelper.remove_directory(f'./tmp/{path_id}')
|
||||
if response:
|
||||
return response.model_dump(exclude_none=True)
|
||||
return None
|
||||
|
||||
async def _get_listening_sections(
|
||||
self,
|
||||
path_id: str,
|
||||
has_solutions: bool = False
|
||||
) -> ListeningExam:
|
||||
async with aiofiles.open(
|
||||
f'./tmp/{path_id}/exercises.html', 'r', encoding='utf-8'
|
||||
) as f:
|
||||
exercises_html = await f.read()
|
||||
|
||||
messages = [
|
||||
self._instructions(has_solutions),
|
||||
{
|
||||
"role": "user",
|
||||
"content": f"Listening exercise sheet:\n\n{exercises_html}"
|
||||
}
|
||||
]
|
||||
|
||||
if has_solutions:
|
||||
async with aiofiles.open(
|
||||
f'./tmp/{path_id}/solutions.html', 'r', encoding='utf-8'
|
||||
) as f:
|
||||
solutions_html = await f.read()
|
||||
messages.append({
|
||||
"role": "user",
|
||||
"content": f"Solutions:\n\n{solutions_html}"
|
||||
})
|
||||
|
||||
return await self._llm.pydantic_prediction(
|
||||
messages,
|
||||
ListeningMapper.map_to_test_model,
|
||||
str(self._listening_json_schema())
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _multiple_choice_template() -> dict:
|
||||
return {
|
||||
"type": "multipleChoice",
|
||||
"prompt": "<general instructions for this section>",
|
||||
"questions": [
|
||||
{
|
||||
"id": "<question number as string>",
|
||||
"prompt": "<question text>",
|
||||
"options": [
|
||||
{
|
||||
"id": "<A/B/C/D>",
|
||||
"text": "<option text>"
|
||||
}
|
||||
],
|
||||
"solution": "<correct option letter>",
|
||||
"variant": "text"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _write_blanks_questions_template() -> dict:
|
||||
return {
|
||||
"type": "writeBlanks",
|
||||
"maxWords": "<number>",
|
||||
"prompt": "<instructions>",
|
||||
"text": "<questions separated by newlines '\n' and blanks {{id}} in them the blanks can only occur at the end of sentence>",
|
||||
"solutions": [
|
||||
{
|
||||
"id": "<question number as string>",
|
||||
"solution": ["<acceptable answer(s)>"]
|
||||
}
|
||||
],
|
||||
"variant": "questions"
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _write_blanks_fill_template() -> dict:
|
||||
return {
|
||||
"type": "writeBlanks",
|
||||
"maxWords": "<number>",
|
||||
"prompt": "<instructions>",
|
||||
"text": "<A summary with blanks denoted by {{id}}>",
|
||||
"solutions": [
|
||||
{
|
||||
"id": "<blank number as string inside {{}}>",
|
||||
"solution": ["<correct word>"]
|
||||
}
|
||||
],
|
||||
"variant": "fill"
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _write_blanks_form_template() -> dict:
|
||||
return {
|
||||
"type": "writeBlanks",
|
||||
"maxWords": "<number>",
|
||||
"prompt": "<instructions>",
|
||||
"text": "<questions separated by newlines '\n' and blanks {{id}} in them the blanks can happen mid text>",
|
||||
"solutions": [
|
||||
{
|
||||
"id": "<blank number as string inside {{}}>",
|
||||
"solution": ["<correct word>"]
|
||||
}
|
||||
],
|
||||
"variant": "form"
|
||||
}
|
||||
|
||||
def _instructions(self, has_solutions: bool = False) -> Dict[str, str]:
|
||||
solutions_str = " and its solutions" if has_solutions else ""
|
||||
return {
|
||||
"role": "system",
|
||||
"content": (
|
||||
f"You are processing a listening test exercise sheet{solutions_str}. "
|
||||
"Structure each exercise exactly according to these json templates:\n\n"
|
||||
f"1. Multiple Choice Questions:\n{self._multiple_choice_template()}\n\n"
|
||||
f"2. Write Blanks - Questions format:\n{self._write_blanks_questions_template()}\n\n"
|
||||
f"3. Write Blanks - Fill format:\n{self._write_blanks_fill_template()}\n\n"
|
||||
f"4. Write Blanks - Form format:\n{self._write_blanks_form_template()}\n\n"
|
||||
"\nImportant rules:\n"
|
||||
"1. Keep exact question numbering from the original\n"
|
||||
"2. Include all options for multiple choice questions\n"
|
||||
"3. Mark blanks with {{id}} where id is the question number\n"
|
||||
"4. Set maxWords according to the instructions\n"
|
||||
"5. Include all possible correct answers in solution arrays\n"
|
||||
"6. Maintain exact spacing and formatting from templates\n"
|
||||
"7. Use appropriate variant for writeBlanks (questions/fill/form)\n"
|
||||
"8. For text fields, use actual newlines between questions/sentences\n"
|
||||
)
|
||||
}
|
||||
|
||||
def _listening_json_schema(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"exercises": [
|
||||
self._multiple_choice_template(),
|
||||
self._write_blanks_questions_template(),
|
||||
self._write_blanks_fill_template(),
|
||||
self._write_blanks_form_template()
|
||||
]
|
||||
}
|
||||
@@ -39,7 +39,7 @@ class ImportReadingModule:
|
||||
exercises_html = await f.read()
|
||||
|
||||
messages = [
|
||||
self._instructions(),
|
||||
self._instructions(solutions),
|
||||
{
|
||||
"role": "user",
|
||||
"content": f"Exam question sheet:\n\n{exercises_html}"
|
||||
@@ -66,18 +66,20 @@ class ImportReadingModule:
|
||||
self._write_blanks(),
|
||||
self._fill_blanks(),
|
||||
self._match_sentences(),
|
||||
self._true_false()
|
||||
self._true_false(),
|
||||
self._multiple_choice()
|
||||
]
|
||||
return json
|
||||
|
||||
@staticmethod
|
||||
def _reading_exam_template():
|
||||
return {
|
||||
"minTimer": "<number of minutes as int not string>",
|
||||
"minTimer": "<integer representing minutes allowed for the exam>",
|
||||
"parts": [
|
||||
{
|
||||
"text": {
|
||||
"title": "<title of the passage>",
|
||||
"content": "<the text of the passage>",
|
||||
"title": "<title of the reading passage>",
|
||||
"content": "<full text content of the reading passage>",
|
||||
},
|
||||
"exercises": []
|
||||
}
|
||||
@@ -87,17 +89,18 @@ class ImportReadingModule:
|
||||
@staticmethod
|
||||
def _write_blanks():
|
||||
return {
|
||||
"maxWords": "<number of max words return the int value not string>",
|
||||
"maxWords": "<integer max words allowed per answer>",
|
||||
"solutions": [
|
||||
{
|
||||
"id": "<number of the question as string>",
|
||||
"id": "<question number as string>",
|
||||
"solution": [
|
||||
"<at least one solution can have alternative solutions (that dont exceed maxWords)>"
|
||||
"<acceptable answer(s) within maxWords limit>"
|
||||
]
|
||||
},
|
||||
}
|
||||
],
|
||||
"text": "<all the questions formatted in this way: <question>{{<id>}}\\n<question2>{{<id2>}}\\n >",
|
||||
"type": "writeBlanks"
|
||||
"text": "<numbered questions with format: <question text>{{<question number>}}\\n>",
|
||||
"type": "writeBlanks",
|
||||
"prompt": "<specific instructions for this exercise section>"
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
@@ -105,19 +108,20 @@ class ImportReadingModule:
|
||||
return {
|
||||
"options": [
|
||||
{
|
||||
"id": "<uppercase letter that identifies a paragraph>",
|
||||
"sentence": "<either a heading or an idea>"
|
||||
"id": "<paragraph letter A-F>",
|
||||
"sentence": "<THIS NEEDS TO BE A PARAGRAPH OF THE SECTION TEXT>"
|
||||
}
|
||||
],
|
||||
"sentences": [
|
||||
{
|
||||
"id": "<the question id not the option id>",
|
||||
"solution": "<id in options>",
|
||||
"sentence": "<heading or an idea>",
|
||||
"id": "<question number as string>",
|
||||
"solution": "<matching paragraph letter>",
|
||||
"sentence": "<A SHORT SENTENCE THAT CONVEYS AND IDEA OR HEADING>"
|
||||
}
|
||||
],
|
||||
"type": "matchSentences",
|
||||
"variant": "<heading OR ideaMatch (try to figure it out via the exercises instructions)>"
|
||||
"variant": "<heading OR ideaMatch (try to figure it out via the exercises instructions)>",
|
||||
"prompt": "<specific instructions for this exercise section>"
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
@@ -125,12 +129,34 @@ class ImportReadingModule:
|
||||
return {
|
||||
"questions": [
|
||||
{
|
||||
"prompt": "<question>",
|
||||
"solution": "<can only be one of these [\"true\", \"false\", \"not_given\"]>",
|
||||
"id": "<the question id>"
|
||||
"id": "<question number>",
|
||||
"prompt": "<statement to evaluate>",
|
||||
"solution": "<one of: true, false, not_given>",
|
||||
}
|
||||
],
|
||||
"type": "trueFalse"
|
||||
"type": "trueFalse",
|
||||
"prompt": "<specific instructions including T/F/NG marking scheme>"
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _multiple_choice():
|
||||
return {
|
||||
"questions": [
|
||||
{
|
||||
"id": "<question number>",
|
||||
"prompt": "<question text>",
|
||||
"options": [
|
||||
{
|
||||
"id": "<A, B, or C>",
|
||||
"text": "<option text>"
|
||||
}
|
||||
],
|
||||
"solution": "<correct option letter>",
|
||||
"variant": "text"
|
||||
}
|
||||
],
|
||||
"type": "multipleChoice",
|
||||
"prompt": "<specific instructions for this exercise section>"
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
@@ -138,53 +164,69 @@ class ImportReadingModule:
|
||||
return {
|
||||
"solutions": [
|
||||
{
|
||||
"id": "<blank id>",
|
||||
"solution": "<word>"
|
||||
"id": "<blank number>",
|
||||
"solution": "<correct word>"
|
||||
}
|
||||
],
|
||||
"text": "<section of text with blanks denoted by {{<blank id>}}>",
|
||||
"text": "<text passage with blanks marked as {{<blank number>}}>",
|
||||
"type": "fillBlanks",
|
||||
"words": [
|
||||
{
|
||||
"letter": "<uppercase letter that ids the words (may not be included and if not start at A)>",
|
||||
"word": "<word>"
|
||||
"letter": "<word identifier letter>",
|
||||
"word": "<word from word bank>"
|
||||
}
|
||||
]
|
||||
],
|
||||
"prompt": "<specific instructions for this exercise section>"
|
||||
}
|
||||
|
||||
def _instructions(self, solutions = False):
|
||||
def _instructions(self, solutions=False):
|
||||
solutions_str = " and its solutions" if solutions else ""
|
||||
tail = (
|
||||
"The solutions were not supplied so you will have to solve them. Do your utmost to get all the information and"
|
||||
"all the solutions right!"
|
||||
if not solutions else
|
||||
"Do your utmost to correctly identify the sections, its exercises and respective solutions"
|
||||
"Parse the exam carefully and identify:\n"
|
||||
"1. Time limit from instructions\n"
|
||||
"2. Reading passage title and full content\n"
|
||||
"3. All exercise sections and their specific instructions\n"
|
||||
"4. Question numbering and grouping\n"
|
||||
"5. Word limits and formatting requirements\n"
|
||||
"6. Specific marking schemes (e.g., T/F/NG)\n\n"
|
||||
+ (
|
||||
"Solutions were not provided - analyze the passage carefully to determine correct answers."
|
||||
if not solutions else
|
||||
"Use the provided solutions to fill in all answer fields accurately."
|
||||
)
|
||||
+
|
||||
"Pay extra attention to fillblanks exercises the solution and option wording must match in case!"
|
||||
"There can't be options in lowercase and solutions in uppercase!"
|
||||
"Also PAY ATTENTION TO SECTIONS, these most likely indicate parts, and in each section/part there "
|
||||
"should be a text, if there isn't a title for it choose a reasonable one based on its contents."
|
||||
)
|
||||
|
||||
return {
|
||||
"role": "system",
|
||||
"content": (
|
||||
f"You will receive html pertaining to an english exam question sheet{solutions_str}. Your job is to "
|
||||
f"structure the data into a single json with this template: {self._reading_exam_template()}\n"
|
||||
|
||||
"You will need find out how many parts the exam has a correctly place its exercises. You will "
|
||||
"encounter 4 types of exercises:\n"
|
||||
" - \"writeBlanks\": short answer questions that have a answer word limit, generally two or three\n"
|
||||
" - \"matchSentences\": a sentence needs to be matched with a paragraph\n"
|
||||
" - \"trueFalse\": questions that its answers can only be true false or not given\n"
|
||||
" - \"fillBlanks\": a text that has blank spaces on a section of text and a word bank which "
|
||||
"contains the solutions and sometimes random words to throw off the students\n"
|
||||
|
||||
"These 4 types of exercises will need to be placed in the correct json template inside each part, "
|
||||
"the templates are as follows:\n "
|
||||
|
||||
f"You are processing an English reading comprehension exam{solutions_str}. Structure the data according "
|
||||
f"to this json template: {self._reading_exam_template()}\n\n"
|
||||
|
||||
"The exam contains these exercise types:\n"
|
||||
"1. \"writeBlanks\": Short answer questions with strict word limits\n"
|
||||
"2. \"matchSentences\": Match headings or ideas with paragraphs, the sentences field\n"
|
||||
"3. \"trueFalse\": Evaluate statements as True/False/Not Given\n"
|
||||
"4. \"fillBlanks\": Complete text using provided word bank\n"
|
||||
"5. \"multipleChoice\": Select correct option from choices\n\n"
|
||||
|
||||
"Exercise templates:\n"
|
||||
f"writeBlanks: {self._write_blanks()}\n"
|
||||
f"matchSentences: {self._match_sentences()}\n"
|
||||
f"trueFalse: {self._true_false()}\n"
|
||||
f"fillBlanks: {self._fill_blanks()}\n\n"
|
||||
|
||||
f"fillBlanks: {self._fill_blanks()}\n"
|
||||
f"multipleChoice: {self._multiple_choice()}\n\n"
|
||||
|
||||
"Important details to capture:\n"
|
||||
"- Exercise section instructions and constraints\n"
|
||||
"- Question numbering and grouping\n"
|
||||
"- Word limits and formatting requirements\n"
|
||||
"- Marking schemes and answer formats\n\n"
|
||||
|
||||
f"{tail}"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -2,6 +2,9 @@ import json
|
||||
import re
|
||||
import logging
|
||||
from typing import List, Optional, Callable, TypeVar
|
||||
|
||||
from numba.core.transforms import consolidate_multi_exit_withs
|
||||
from numba.cuda import const
|
||||
from openai import AsyncOpenAI
|
||||
from openai.types.chat import ChatCompletionMessageParam
|
||||
|
||||
@@ -123,7 +126,9 @@ class OpenAI(ILLMService):
|
||||
while attempt < 3:
|
||||
result = await self._client.chat.completions.create(**params)
|
||||
result_content = result.choices[0].message.content
|
||||
|
||||
try:
|
||||
print(result_content)
|
||||
result_json = json.loads(result_content)
|
||||
return map_to_model(result_json)
|
||||
except Exception as e:
|
||||
|
||||
Reference in New Issue
Block a user