File size: 3,089 Bytes
9cbdd68
47868ec
c0ffcf0
5b2eeff
 
 
a132742
9cbdd68
b60cd75
47868ec
 
 
b60cd75
47868ec
 
 
 
a132742
47868ec
 
 
 
 
 
 
 
 
 
4cbd4c5
 
47868ec
 
b60cd75
5b2eeff
c0ffcf0
 
 
 
 
 
 
 
 
5b2eeff
c0ffcf0
 
 
5b2eeff
c0ffcf0
 
 
 
 
 
 
 
5b2eeff
c0ffcf0
 
5b2eeff
c0ffcf0
 
 
5b2eeff
c0ffcf0
 
 
5b2eeff
c0ffcf0
 
 
 
 
 
73b8ea4
c0ffcf0
 
73b8ea4
c0ffcf0
 
73b8ea4
c0ffcf0
 
5b2eeff
 
c0ffcf0
5b2eeff
c0ffcf0
 
 
 
 
 
 
 
 
5b2eeff
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# app/helpers/exercise_standardizer.py
from langchain_core.prompts import ChatPromptTemplate
from typing import Any, Literal, List, Union

from pydantic import BaseModel

from config.format_mappings import FORMAT_MAPPINGS_EXERCISES



async def standardize_exercise(user_query: str, exercise_format: str, template: ChatPromptTemplate, llm: Any) -> str:
    """
    Standardizes an exercise's format using the specified template and LLM, and updates the UI via standardized_format_state.
    """
    if exercise_format == "Raw (original)":
        return user_query  # No transformation needed

    formatting_instructions = FORMAT_MAPPINGS_EXERCISES.get(
        exercise_format,
        "Please reformat the given exercise to ease further processing."
    )

    prompt_std = await template.aformat_prompt(
        user_input=user_query,
        formatting_instructions=formatting_instructions
    )

    std_messages = prompt_std.to_messages()
    response = await llm.ainvoke(std_messages)
    standardized_exercise = getattr(response, "content", response)

    return standardized_exercise


class Exercise(BaseModel):
    id: int
    prompt: str
    choice_id_1: str
    choice_id_2: str
    choice_id_3: Union[str, None]
    choice_id_4: Union[str, None]
    correct_answer_id: Literal[1, 2, 3, 4]
    explanation: Union[str, None]

class ExerciseSet(BaseModel):
    id: int
    exercises: List[Exercise]




async def structurize_exercise(
    fluster_text: str,
    template: ChatPromptTemplate,
    llm: Any   # e.g. ChatOpenAI
) -> ExerciseSet:
    """
    Distills individual exercises and their components from the fluster text
    using a structured-output call that returns a Fluster pydantic object.
    """
    # 1) Format the prompt
    prompt_str = await template.aformat_prompt(fluster=fluster_text)
    messages = prompt_str.to_messages()

    # 2) Call the LLM with the schema
    response = await llm.with_structured_output(ExerciseSet).ainvoke(messages)
    exercise_set = response.choices[0].message.parsed

    # If the model refused or the schema was violated, you might get None or an error
    if exercise_set is None:
        raise ValueError(f"LLM refusal or invalid structured data.\nLLM response: {response}")

    return exercise_set

# unpacking a structurized exercise for display
def exercise_to_string(ex):
    choices = [ex.choice_id_1, ex.choice_id_2, ex.choice_id_3, ex.choice_id_4]
    choice_texts = [f"  {idx + 1}) {choice}" for idx, choice in enumerate(choices) if choice and choice != 'None']

    correct_choice_text = next(
        (f"  Correct answer: {idx + 1}. {choice}"
         for idx, choice in enumerate(choices) if choice == ex.correct_answer_id),
        "  Correct answer: Unknown"
    )

    explanation_text = f"  Explanation: {ex.explanation}" if ex.explanation else ""

    plaintext_exercise = (
            f"Exercise {ex.id}:\n"
            f"  {ex.prompt}\n"
            + "\n".join(choice_texts) + "\n"
            + correct_choice_text + "\n"
            + explanation_text + "\n\n"
    )

    return plaintext_exercise