BtB-ExpC commited on
Commit
a132742
·
1 Parent(s): ccef0cf

first push of 4-track learning_objectives_generator

Browse files
app/helpers/exercise_standardizer.py CHANGED
@@ -1,7 +1,7 @@
1
  # app/helpers/exercise_standardizer.py
2
  from langchain_core.prompts import ChatPromptTemplate
3
  from typing import Any
4
- from config.format_mappings import FORMAT_MAPPINGS
5
 
6
 
7
 
@@ -12,7 +12,7 @@ async def standardize_exercise(user_query: str, exercise_format: str, template:
12
  if exercise_format == "Raw (original)":
13
  return user_query # No transformation needed
14
 
15
- formatting_instructions = FORMAT_MAPPINGS.get(
16
  exercise_format,
17
  "Please reformat the given exercise to ease further processing."
18
  )
 
1
  # app/helpers/exercise_standardizer.py
2
  from langchain_core.prompts import ChatPromptTemplate
3
  from typing import Any
4
+ from config.format_mappings import FORMAT_MAPPINGS_EXERCISES
5
 
6
 
7
 
 
12
  if exercise_format == "Raw (original)":
13
  return user_query # No transformation needed
14
 
15
+ formatting_instructions = FORMAT_MAPPINGS_EXERCISES.get(
16
  exercise_format,
17
  "Please reformat the given exercise to ease further processing."
18
  )
app/helpers/study_text_standardizer.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app/helpers/exercise_standardizer.py
2
+ from langchain_core.prompts import ChatPromptTemplate
3
+ from typing import Any
4
+ from config.format_mappings import FORMAT_MAPPINGS_STUDY_TEXTS
5
+
6
+
7
+
8
+ async def standardize_studytext(user_query: str, studytext_format: str, template: ChatPromptTemplate, llm: Any) -> str:
9
+ """
10
+ Standardizes a studytext's format using the specified template and LLM, and updates the UI via standardized_format_state.
11
+ """
12
+ if studytext_format == "Raw (original)":
13
+ return user_query # No transformation needed
14
+
15
+ formatting_instructions = FORMAT_MAPPINGS_STUDY_TEXTS.get(
16
+ studytext_format,
17
+ "Please reformat the given studytext to ease further processing."
18
+ )
19
+
20
+ prompt_std = await template.aformat_prompt(
21
+ user_input=user_query,
22
+ formatting_instructions=formatting_instructions
23
+ )
24
+
25
+ std_messages = prompt_std.to_messages()
26
+ response = await llm.ainvoke(std_messages)
27
+ standardized_studytext = getattr(response, "content", response)
28
+
29
+ return standardized_studytext
30
+
app/ui/diagnoser_tab.py CHANGED
@@ -46,7 +46,7 @@ def build_diagnoser_tab():
46
  diagnoser_input = gr.Textbox(label="Enter exercise in any format",
47
  placeholder="Exercise body: <mc:exercise xmlns:mc= ...")
48
  # A button to run the chain
49
- diagnoser_button = gr.Button("Submit")
50
 
51
  # Create 10 Response textboxes
52
  diagnoser_responses = [
 
46
  diagnoser_input = gr.Textbox(label="Enter exercise in any format",
47
  placeholder="Exercise body: <mc:exercise xmlns:mc= ...")
48
  # A button to run the chain
49
+ diagnoser_button = gr.Button("Diagnose")
50
 
51
  # Create 10 Response textboxes
52
  diagnoser_responses = [
app/ui/distractors_tab.py CHANGED
@@ -73,7 +73,7 @@ def build_distractors_tab():
73
 
74
  distractors_input = gr.Textbox(label="Enter exercise(s) in any format",
75
  placeholder="Stelling: Dit is een ..... voorbeeld van een stelling. A. Mooi B. Lelijk ...")
76
- distractors_button = gr.Button("Submit")
77
 
78
  # Create 10 Response textboxes
79
  distractors_responses = [
 
73
 
74
  distractors_input = gr.Textbox(label="Enter exercise(s) in any format",
75
  placeholder="Stelling: Dit is een ..... voorbeeld van een stelling. A. Mooi B. Lelijk ...")
76
+ distractors_button = gr.Button("Brainstorm")
77
 
78
  # Create 10 Response textboxes
79
  distractors_responses = [
app/ui/learning_objectives_tab.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from chains.learning_objectives_generator.runner import run_learning_objectives_generator
3
+ from config.llm_config import llms
4
+
5
+ def build_learning_objectives_tab():
6
+ with gr.TabItem("🧠 Identify Learning Objectives"):
7
+ gr.HTML(
8
+ """
9
+ <div style="margin-bottom: 10px;">
10
+ <span style="font-size: 1.5em; cursor: help;" title="Generate learning objectives for the given study text">
11
+ ℹ️
12
+ </span>
13
+ </div>
14
+ """
15
+ )
16
+ # 2 dropdowns for the user-chosen LLMs:
17
+ model_choice_1 = gr.Dropdown(
18
+ choices=list(llms.keys()),
19
+ label="LLM 1"
20
+ )
21
+ model_choice_2 = gr.Dropdown(
22
+ choices=list(llms.keys()),
23
+ label="LLM 2"
24
+ )
25
+ text_format = gr.Dropdown(
26
+ choices=["Markdown", "XML", "Plaintext", "Raw (input not reformatted)"],
27
+ value="Markdown",
28
+ label="Studytext Reformat",
29
+ interactive=True,
30
+ )
31
+
32
+ studytext_input = gr.Textbox(label="Enter a study text in any format", placeholder="<h3>Infusie en infuussystemen</h3> <h4>Inleiding</h4> ...")
33
+ learning_objectives_button = gr.Button("Identify LOs")
34
+
35
+ # 2×2 textboxes => 4 total
36
+ # For clarity:
37
+ # row 1 => (box_0, box_1)
38
+ # row 2 => (box_2, box_3)
39
+ with gr.Row():
40
+ box_0 = gr.Textbox(label="Prompt A + LLM 1", interactive=False)
41
+ box_1 = gr.Textbox(label="Prompt B + LLM 1", interactive=False)
42
+ with gr.Row():
43
+ box_2 = gr.Textbox(label="Prompt A + LLM 2", interactive=False)
44
+ box_3 = gr.Textbox(label="Prompt B + LLM 2", interactive=False)
45
+
46
+
47
+
48
+ # Return references if needed
49
+ return (model_choice_1,
50
+ model_choice_2,
51
+ text_format,
52
+ studytext_input,
53
+ learning_objectives_button,
54
+ [box_0, box_1, box_2, box_3])
chains/learning_objectives_generator/learning_objectives_chain.py CHANGED
@@ -6,16 +6,7 @@ from langchain_core.prompts.chat import ChatPromptTemplate
6
 
7
  class LearningObjectivesChain(BaseModel):
8
  """
9
- Orchestrates multi-step generation of learning objectives:
10
- 1) Two parallel calls to 'learning_objective_generator' (LLM1, LLM2).
11
- 2) Combine those outputs.
12
- 3) 'learning_objective_eliminator' on the combined output (Main LLM).
13
- 4) 'learning_objective_finetuner' on the output of that (Main LLM).
14
- 5) 'learning_objective_presenter' to finalize (Main LLM).
15
 
16
- Each step can have its own ChatPromptTemplate and uses the relevant LLM.
17
-
18
- If you want a separate alt LLM for step #3 or #4, just expand as needed.
19
  """
20
 
21
  # Templates
 
6
 
7
  class LearningObjectivesChain(BaseModel):
8
  """
 
 
 
 
 
 
9
 
 
 
 
10
  """
11
 
12
  # Templates
chains/learning_objectives_generator/runner.py CHANGED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ from typing import AsyncGenerator
3
+ from config.llm_config import llms
4
+ from config.chain_configs import chain_configs
5
+ from app.helpers.study_text_standardizer import standardize_studytext
6
+
7
+ async def run_learning_objectives_generator(
8
+ user_input_text: str,
9
+ model_choice_1: str,
10
+ model_choice_2: str,
11
+ text_format: str
12
+ ) -> AsyncGenerator:
13
+ """
14
+ Orchestrates the entire pipeline:
15
+ 1) Standardize the study text
16
+ 2) Generate (2 prompts × 2 LLMs) => 4 partial results
17
+ 3) Sanitize each partial result
18
+ 4) Yield partial updates in real time as each track completes
19
+ """
20
+ # 1) Standardize the text once
21
+ config = chain_configs["learning_objectives"] # you define this in chain_configs.py
22
+ standardized = await standardize_studytext(
23
+ user_input_text, text_format,
24
+ config["template_studytext_standardize"],
25
+ config["llm_standardize_studytext"]
26
+ )
27
+
28
+ # Prepare references for the generation prompts:
29
+ prompt_a = config["template_gen_prompt_a"]
30
+ prompt_b = config["template_gen_prompt_b"]
31
+ sanitize_prompt = config["template_sanitize"]
32
+
33
+ # pick the LLMs from user choices (with fallback to config)
34
+ llm_a = llms.get(model_choice_1, config["default_llm_a"])
35
+ llm_b = llms.get(model_choice_2, config["default_llm_b"])
36
+
37
+ # We will store the final sanitized results in an array of 4 strings
38
+ # (2 prompts × 2 LLMs)
39
+ partial_results = ["", "", "", ""]
40
+
41
+ # We'll define a short async helper for each track:
42
+ # 'track_index' is 0..3 so we know which of the 4 textboxes to fill
43
+ # 'gen_prompt' is either prompt_a or prompt_b
44
+ # 'gen_llm' is either llm_a or llm_b
45
+ async def run_track(track_index: int, gen_prompt, gen_llm):
46
+ # Step: generate
47
+ gen_msg = await gen_prompt.aformat_prompt(standardized_text=standardized)
48
+ gen_resp = await gen_llm.ainvoke(gen_msg.to_messages())
49
+ generation_output = getattr(gen_resp, "content", gen_resp)
50
+
51
+ # Step: sanitize
52
+ sanitize_msg = await sanitize_prompt.aformat_prompt(raw_output=generation_output)
53
+ sanitize_resp = await llm_a.ainvoke(sanitize_msg.to_messages()) # or use a separate LLM for sanitization
54
+ sanitized_output = getattr(sanitize_resp, "content", sanitize_resp)
55
+
56
+ return (track_index, sanitized_output)
57
+
58
+ # Build the 4 tasks:
59
+ # - track 0 => prompt A, LLM 1
60
+ # - track 1 => prompt B, LLM 1
61
+ # - track 2 => prompt A, LLM 2
62
+ # - track 3 => prompt B, LLM 2
63
+
64
+ tasks = [
65
+ run_track(0, prompt_a, llm_a),
66
+ run_track(1, prompt_b, llm_a),
67
+ run_track(2, prompt_a, llm_b),
68
+ run_track(3, prompt_b, llm_b),
69
+ ]
70
+
71
+ # We'll run them in parallel and yield updates as each finishes
72
+ done_count = 0
73
+ for coro in asyncio.as_completed(tasks):
74
+ track_index, final_text = await coro
75
+ partial_results[track_index] = final_text
76
+ done_count += 1
77
+
78
+ # yield partial update
79
+ # We yield a tuple with the 4 track results.
80
+ # The UI will map each item to the correct textbox.
81
+ yield tuple(partial_results) + (standardized, )
82
+
config/chain_configs.py CHANGED
@@ -1,6 +1,7 @@
1
  # config/chain_configs.py
2
  from config.templates import (
3
- standardize_template,
 
4
  template_diagnose_double_negation,
5
  template_diagnose_correct_answer_stands_out,
6
  template_diagnose_distractor_clearly_wrong,
@@ -8,17 +9,21 @@ from config.templates import (
8
  diagnose_scorecard_template,
9
  template_distractors_brainstorm_1,
10
  template_distractors_brainstorm_2,
11
- template_consolidate_distractors
 
 
12
  )
13
  from chains.diagnoser.diagnoser_chain import DiagnoserChain
14
  from chains.distractors.distractors_chain import DistractorsChain
 
15
  from config.llm_config import llms
16
 
17
  # Note: The default LLM here is GPT-4o (low temp); the UI can override this choice.
 
18
  chain_configs = {
19
  "diagnoser": {
20
  "class": DiagnoserChain,
21
- "template_standardize": standardize_template,
22
  "llm_standardize": llms["GPT-4o-mini (zero temp)"], # Always fixed
23
  "llm_4o_mini": llms["GPT-4o-mini (low temp)"],
24
  "llm_4o": llms["GPT-4o (low temp)"],
@@ -34,7 +39,7 @@ chain_configs = {
34
  },
35
  "distractors": {
36
  "class": DistractorsChain,
37
- "template_standardize": standardize_template,
38
  "llm_standardize": llms["GPT-4o-mini (zero temp)"], # Always fixed
39
  "template_distractors_brainstorm_1": template_distractors_brainstorm_1,
40
  "template_distractors_brainstorm_2": template_distractors_brainstorm_2,
@@ -43,4 +48,14 @@ chain_configs = {
43
  "template_consolidate": template_consolidate_distractors,
44
  "llm_consolidate": llms["GPT-4o (low temp)"], # or something else
45
  },
 
 
 
 
 
 
 
 
 
46
  }
 
 
1
  # config/chain_configs.py
2
  from config.templates import (
3
+ template_standardize_exercise,
4
+ template_standardize_studytext,
5
  template_diagnose_double_negation,
6
  template_diagnose_correct_answer_stands_out,
7
  template_diagnose_distractor_clearly_wrong,
 
9
  diagnose_scorecard_template,
10
  template_distractors_brainstorm_1,
11
  template_distractors_brainstorm_2,
12
+ template_consolidate_distractors,
13
+ template_gen_prompt_a,
14
+ template_gen_prompt_b
15
  )
16
  from chains.diagnoser.diagnoser_chain import DiagnoserChain
17
  from chains.distractors.distractors_chain import DistractorsChain
18
+ from chains.learning_objectives_generator.learning_objectives_chain import LearningObjectivesChain
19
  from config.llm_config import llms
20
 
21
  # Note: The default LLM here is GPT-4o (low temp); the UI can override this choice.
22
+
23
  chain_configs = {
24
  "diagnoser": {
25
  "class": DiagnoserChain,
26
+ "template_standardize": template_standardize_exercise,
27
  "llm_standardize": llms["GPT-4o-mini (zero temp)"], # Always fixed
28
  "llm_4o_mini": llms["GPT-4o-mini (low temp)"],
29
  "llm_4o": llms["GPT-4o (low temp)"],
 
39
  },
40
  "distractors": {
41
  "class": DistractorsChain,
42
+ "template_standardize": template_standardize_exercise,
43
  "llm_standardize": llms["GPT-4o-mini (zero temp)"], # Always fixed
44
  "template_distractors_brainstorm_1": template_distractors_brainstorm_1,
45
  "template_distractors_brainstorm_2": template_distractors_brainstorm_2,
 
48
  "template_consolidate": template_consolidate_distractors,
49
  "llm_consolidate": llms["GPT-4o (low temp)"], # or something else
50
  },
51
+ "learning_objectives": {
52
+ "class": LearningObjectivesChain,
53
+ "template_standardize_studytext": template_standardize_studytext,
54
+ "llm_standardize": llms["GPT-4o-mini (zero temp)"], # Always fixed
55
+ "template_gen_prompt_a": template_gen_prompt_a,
56
+ "template_gen_prompt_b": template_gen_prompt_b,
57
+ "default_llm_a": llms["o1"],
58
+ "default_llm_b": llms["o3-mini (high reasoning_effort)"]
59
+ },
60
  }
61
+
config/format_mappings.py CHANGED
@@ -1,6 +1,7 @@
1
  # format_mappings.py
2
 
3
- FORMAT_MAPPINGS = {
 
4
  "Markdown": (
5
  "Please reformat in Markdown, following this example:\n"
6
  "**Theorie:** \n"
@@ -50,3 +51,38 @@ FORMAT_MAPPINGS = {
50
  "1. Het gevoel geen connectie te hebben met anderen"
51
  )
52
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # format_mappings.py
2
 
3
+ # Intermediate plain processing
4
+ FORMAT_MAPPINGS_EXERCISES = {
5
  "Markdown": (
6
  "Please reformat in Markdown, following this example:\n"
7
  "**Theorie:** \n"
 
51
  "1. Het gevoel geen connectie te hebben met anderen"
52
  )
53
  }
54
+
55
+
56
+ FORMAT_MAPPINGS_STUDY_TEXTS = {
57
+ "Markdown": (
58
+ "Please reformat into Markdown."
59
+ ),
60
+ "XML": (
61
+ """
62
+ Please reformat into XML, use tags like <title></title> and <b></b>.
63
+ """
64
+ ),
65
+ "Plaintext": (
66
+ "Please reformat into neat plaintext, without any tags or other formatting."
67
+ )
68
+ }
69
+
70
+ # Final processing
71
+ studytext_HTML = (
72
+ """
73
+ Please reformat into XML. The target conventions are:
74
+ - always start with a title like this: <h3>TITLE</h3>;
75
+ - subheadings are just in bold. Usually there won't be any subheadings though, just different paragraphs;
76
+ - for bold text, use <strong></strong>;
77
+ - divide the text up into <p></p>-blocks;
78
+ - for lists, adhere to the following capitalization and interpunction rules:
79
+ <p>This is an example list, pay attention to lowercase beginnings and final interpunction endings of each item:</p>
80
+ <ul>
81
+ <li>each item starts with lowercase;</li>
82
+ <li>always. Even if it's several sentences long. Like this one;</li>
83
+ <li>each item ends with a semicolon;</li>
84
+ <li>except for the very latest item;</li>
85
+ <li>that (this) final item, ends with a full-stop, a period.</li>
86
+ </ul>
87
+ """
88
+ )
config/templates.py CHANGED
@@ -1,8 +1,7 @@
1
  # config/templates.py
2
  from langchain_core.prompts.chat import ChatPromptTemplate
3
 
4
- # Template to standardize the exercise description.
5
- standardize_template = ChatPromptTemplate(
6
  messages=[
7
  ("system", "You reformat a given multiple choice exercise into a standardized format. {formatting_instructions}\n\n"
8
  "Only 3 elements are always mandatory:\n"
@@ -18,6 +17,15 @@ standardize_template = ChatPromptTemplate(
18
  input_variables=["user_input", "formatting_instructions"]
19
  )
20
 
 
 
 
 
 
 
 
 
 
21
  template_diagnose_double_negation = ChatPromptTemplate(
22
  messages=[
23
  ("system", """Analyze a multiple-choice exercise for the presence of double negatives: either two negations in the question/statement itself, or a negation in the question/statement AND in an answer option.
@@ -221,8 +229,6 @@ template_distractors_brainstorm_2 = ChatPromptTemplate(
221
  )
222
 
223
 
224
-
225
-
226
  template_consolidate_distractors = ChatPromptTemplate(
227
  messages=[
228
  ("system", "You are given several lists of potential distractors (answer options to a multiple choice exercise), that need to be consolidated and/or trimmed down into one list. "
@@ -238,3 +244,127 @@ template_consolidate_distractors = ChatPromptTemplate(
238
  input_variables=["standardized_exercise", "brainstorm_outputs", "final_distractors_specification"]
239
  )
240
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # config/templates.py
2
  from langchain_core.prompts.chat import ChatPromptTemplate
3
 
4
+ template_standardize_exercise = ChatPromptTemplate(
 
5
  messages=[
6
  ("system", "You reformat a given multiple choice exercise into a standardized format. {formatting_instructions}\n\n"
7
  "Only 3 elements are always mandatory:\n"
 
17
  input_variables=["user_input", "formatting_instructions"]
18
  )
19
 
20
+ template_standardize_studytext = ChatPromptTemplate(
21
+ messages=[
22
+ ("system", "You reformat a given study text into a standardized format. {formatting_instructions}\n\n"
23
+ ),
24
+ ("human", "Here's the given study text:\n{user_input}")
25
+ ],
26
+ input_variables=["user_input", "formatting_instructions"]
27
+ )
28
+
29
  template_diagnose_double_negation = ChatPromptTemplate(
30
  messages=[
31
  ("system", """Analyze a multiple-choice exercise for the presence of double negatives: either two negations in the question/statement itself, or a negation in the question/statement AND in an answer option.
 
229
  )
230
 
231
 
 
 
232
  template_consolidate_distractors = ChatPromptTemplate(
233
  messages=[
234
  ("system", "You are given several lists of potential distractors (answer options to a multiple choice exercise), that need to be consolidated and/or trimmed down into one list. "
 
244
  input_variables=["standardized_exercise", "brainstorm_outputs", "final_distractors_specification"]
245
  )
246
 
247
+
248
+ template_gen_prompt_a = ChatPromptTemplate(
249
+ messages=[
250
+ ("system", """
251
+ You are given a study text that is part of an e-learning and an accompanying list of learning objectives based on the text. Your goal is to refine the learning objectives, such that they adhere to the requirements as closely as possible. These learning objectives will later serve as the basis for multiple-choice exercises, and for this purpose it is crucial that they live up to the requirements in every way.
252
+
253
+ # General approach
254
+ - First intensely study and really internalize the requirements for good learning objectives (listed below).
255
+ - Then, rewrite and improve the learning objectives to better fall in line with the requirements.
256
+
257
+ # Requirements for individual learning objectives
258
+ Good learning objectives:
259
+ - Start with 'The student knows that '
260
+ - Are unambiguous, and contain what later will become the specific correct answer for any multiple choice exercises that would test the learning objective
261
+ - Represent exactly the knowledge as written in the study text
262
+ - Use exactly the same terminology that's used in the study text
263
+ - Mirror also the general language level of the study text. If the text is written with very simple words, then the learning objectives should be also written in very simple words
264
+ - Mirror also the voice of the text (passive or active voice) and the perspective of the text (second or third person)
265
+ - Are as concise as can be: they contain the smallest possible knowledge element. A learning objective does not combine multiple facts, but rather isolates individual facts
266
+ - Avoid absolute terms that overstate their universality, like 'always' and 'never', unless that actually is true 100% of the time (usually there are exceptions to every rule, so account for those in your phrasing)
267
+ - Alternatively avoid vague terms that make what they wanna say too meaningless, like 'can', 'could', 'might' and 'may' (many things 'can', 'could' or 'might be', this doesn't say much)
268
+ - Also avoid subjective terms like 'often', 'sometimes', 'many', 'few', 'common', 'rare'. Instead, make more specific and falsifiable claims like 'in most cases' or 'A is more common than B'
269
+ - Avoid the use of 'important', again a signal word indicating subjectivity. Only use 'important' in statements that you cannot rephrase, yet are actually indisputable ánd meaningful to know when phrased in this way
270
+
271
+ # Process
272
+ - For each learning objective, go over all of the requirements, like methodically checking off a checklist.
273
+ - For any aspect of any learning objective that upon reflection doesn't adhere to the requirements as well as it could, carry out a rewrite (or split up one learning objective into two, for example) of the learning objective.
274
+ - Iteratively keep doing this for each of the individual learning objectives again and again, until you are certain that they are the best versions they can be: each entirely and maximally satisfying the requirements for good learning objectives.
275
+ - Take as much time as you need to get it perfect, then return the list of final learning objectives (for this, use the same language as the study text).
276
+ """),
277
+ ("human", "{standardized_text}")
278
+ ],
279
+ input_variables=["standardized_text"]
280
+ )
281
+
282
+ template_gen_prompt_b = ChatPromptTemplate(
283
+ messages=[
284
+ ("system", """
285
+ You are given a study text. Based on this, you will identify learning objectives. Follow the following protocol meticulously:
286
+
287
+ # Protocol for Creating Exercises for eLearning Modules
288
+ ## General Working Method
289
+
290
+ ### Text Orientation
291
+ Assigned a study text, your initial task is to read it to understand the topic for creating exercises.
292
+
293
+ ### Learning objectives
294
+ Based on the text, define clear, concise learning objectives. Make sure you have enough learning objectives so that all information is covered, but not too many so that learning objectives won't overlap. It's really important that every learning objective only states 1 single fact and doesn't combine multiple facts. Choose objectives based on text analysis and audience level. Objectives always start with 'The student knows that'.
295
+
296
+ Observe the following rules meticulously when writing learning objectives:
297
+
298
+ #### Avoid 'always' and 'never'
299
+ Don't use words like 'always' or 'never' in learning objectives, it is likely an exception exists. Instead use constructions like 'X *fits with* Y', 'X *suggests* Y', 'X *is more common than* Y'
300
+
301
+ So not:
302
+ > BAD: The students knows that fever *never* occurs with a viral infection.
303
+ > BAD: The students knows that fever *always* occurs with a viral infection.
304
+
305
+ But:
306
+ > GOOD: The students knows that fever is a symptom *that fits with* a viral infection.
307
+
308
+ #### Avoid 'can', 'could', 'may' or 'might'
309
+ Don't use words like *'can'*, *'could'*, *'may'* or *'might'* in learning objectives, because almost everything can, could or might be something, so too suggestive. Instead use constructions like 'X *fits with* Y', 'X *suggests* Y', 'X *is more common than* Y'
310
+
311
+ So not:
312
+ > BAD: The students knows that pain *might* occur with rheumatoid arthritis.
313
+ > BAD: The students knows that pain *can* occur with rheumatoid arthritis.
314
+ > BAD: The students knows that pain *could* occur with rheumatoid arthritis.
315
+ > BAD: The students knows that pain *may* occur with rheumatoid arthritis.
316
+
317
+ But:
318
+ > GOOD: The students knows that pain *is a symptom of* rheumatoid arthritis.
319
+
320
+ #### Avoid subjective terminology like many, few and common
321
+ Don't use words like *'many'*, *'few'*, *'common'*, *'rare'* et cetera as these are subjective. Instead be specific. Instead use terminology like *'in most cases'* or compare: *'symptom A is more common than symptom B'*.
322
+
323
+ Example of a bad learning objective, leading to suggestive or subjective answers:
324
+ > BAD: The student knows that fever commonly occurs with a viral infection.
325
+
326
+ #### Avoid important, essential significant
327
+ Don't use words like *'important'*, *'essential'*, *'significant'* et cetera in learning objectives, as these are prone to subjectivity.
328
+
329
+ Examples of a bad learning objective, leading to suggestive or subjective answers:
330
+ > BAD: The students knows that it's *important* to rest when having a viral infection.
331
+
332
+ The objective uses the word *'important'* which is subjective.
333
+
334
+ > BAD: The students knows that it's *essential* to rest when having a viral infection.
335
+
336
+ The objective uses the word *'essential'* which is subjective.
337
+
338
+ > BAD: The students knows that fever has a *significant* effect on how people feel when they are sick.
339
+
340
+ The objective uses the word *'significant'* which is subjective.
341
+
342
+ > BAD: The student knows that drinking *enough* water is *important* to stay hydrated.
343
+
344
+ The objective uses the word *'important'* which is subjective. Also the word *'enough'* is subjective.
345
+
346
+
347
+ A good example is:
348
+
349
+ > GOOD: The student knows that proteinuria is a symptom of nephrotic syndrome.
350
+
351
+ An example of a bad learning objective:
352
+
353
+ > BAD: The student knows the symptoms of nephrotic syndrome.
354
+
355
+ The bad objective does not specify a single fact as symptoms are not specified.
356
+
357
+ A good example is:
358
+
359
+ > GOOD: The student knows that besides pain, rheumatoid arthritis also causes loss of mobility.
360
+
361
+ An example of a bad learning objective:
362
+
363
+ > BAD: The student knows that problems with movement due to joint problems, such as rheumatism, can be painful or completely limit movement
364
+
365
+ The latter objective does not specify a single fact but combines two (can be painful or completely limit movement). The first objective focuses on the 'loss of mobility' element, while the 'pain- element' is already considered known. The exercises generated by this learning objective will test the 'loss of mobility' element (so not the 'pain-element')
366
+ """),
367
+ ("human", "{standardized_text}")
368
+ ],
369
+ input_variables=["standardized_text"]
370
+ )
main.py CHANGED
@@ -3,8 +3,10 @@ import gradio as gr
3
  import logging
4
  from app.ui.diagnoser_tab import build_diagnoser_tab
5
  from app.ui.distractors_tab import build_distractors_tab
 
6
  from chains.diagnoser.runner import run_diagnoser
7
  from chains.distractors.runner import run_distractors
 
8
  from utils.auth import login as auth_login
9
 
10
  logger = logging.getLogger(__name__)
@@ -30,7 +32,7 @@ with gr.Blocks() as interface:
30
  # --- Main App (initially hidden) ---
31
  with gr.Column(visible=False, elem_id="main_app") as app_container:
32
 
33
- # --- Standardized Exercise/Studytext Display (Initially Invisible Because it's empty) ---
34
  # A row for Title & the standardized text & copy button
35
  with gr.Row():
36
  with gr.Column(scale=3):
@@ -73,25 +75,14 @@ with gr.Blocks() as interface:
73
  final_distractors_specification,
74
  ) = build_distractors_tab()
75
 
76
- with gr.TabItem("🚧 Generate learning objectives"):
77
- # Insert an HTML info icon with a tooltip at the top of the tab content.
78
- gr.HTML(
79
- """
80
- <div style="margin-bottom: 10px;">
81
- <span style="font-size: 1.5em; cursor: help;" title="Generate learning objectives for the given study text">
82
- ℹ️
83
- </span>
84
- </div>
85
- """
86
- )
87
- learning_objectives_input = gr.Textbox(label="Enter a study text in any format", placeholder="<h3>Infusie en infuussystemen</h3> <h4>Inleiding</h4> ...")
88
- learning_objectives_button = gr.Button("Submit")
89
- gr.Markdown("**Response(s):**")
90
- # Create 5 Response textboxes
91
- distractors_responses = [
92
- gr.Textbox(label=f"Response {i + 1}", interactive=False, visible=(i == 0))
93
- for i in range(10)
94
- ]
95
 
96
  # -------------------------------
97
  # Set Up Interactions
@@ -124,5 +115,17 @@ with gr.Blocks() as interface:
124
  outputs=distractors_responses + [standardized_format_display],
125
  )
126
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  # Launch the app.
128
  interface.launch()
 
3
  import logging
4
  from app.ui.diagnoser_tab import build_diagnoser_tab
5
  from app.ui.distractors_tab import build_distractors_tab
6
+ from app.ui.learning_objectives_tab import build_learning_objectives_tab
7
  from chains.diagnoser.runner import run_diagnoser
8
  from chains.distractors.runner import run_distractors
9
+ from chains.learning_objectives_generator.runner import run_learning_objectives_generator
10
  from utils.auth import login as auth_login
11
 
12
  logger = logging.getLogger(__name__)
 
32
  # --- Main App (initially hidden) ---
33
  with gr.Column(visible=False, elem_id="main_app") as app_container:
34
 
35
+ # --- Standardized Exercise/Study text Display (Initially Invisible Because it's empty) ---
36
  # A row for Title & the standardized text & copy button
37
  with gr.Row():
38
  with gr.Column(scale=3):
 
75
  final_distractors_specification,
76
  ) = build_distractors_tab()
77
 
78
+ # Build Learning Objectives Generator tab
79
+ (model_choice_1,
80
+ model_choice_2,
81
+ text_format,
82
+ studytext_input,
83
+ learning_objectives_button,
84
+ [box_0, box_1, box_2, box_3]
85
+ ) = build_learning_objectives_tab()
 
 
 
 
 
 
 
 
 
 
 
86
 
87
  # -------------------------------
88
  # Set Up Interactions
 
115
  outputs=distractors_responses + [standardized_format_display],
116
  )
117
 
118
+ learning_objectives_button.click(
119
+ fn=run_learning_objectives_generator, # Our async generator
120
+ inputs=[studytext_input, model_choice_1, model_choice_2, text_format],
121
+ outputs=[box_0, box_1, box_2, box_3, standardized_format_display],
122
+ # 'stream=True' is needed to allow partial updates
123
+ # in a generator function.
124
+ # In recent Gradio versions, we just do `every=1` or `queue=True`
125
+ queue=True,
126
+ api_name=None,
127
+ # or "stream=True" depending on your version of Gradio
128
+ )
129
+
130
  # Launch the app.
131
  interface.launch()