Add LLM question import functionality and enhance question model with new fields
This commit is contained in:
@@ -27,6 +27,13 @@
|
||||
<li><a class="dropdown-item" href="{% url 'sbas:question_create' %}" title="Create a new question"><i class="bi bi-question-circle"></i> Question</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item-dropdown">
|
||||
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-expanded="false"><i class="bi bi-gear"></i> LLM</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="{% url 'sbas:llm_prompt_view' %}" title="Generate questions using LLM"><i class="bi bi-robot"></i> LLM prompt</a></li>
|
||||
<li><a class="dropdown-item" href="{% url 'sbas:import_llm_questions' %}" title="Import questions using LLM"><i class="bi bi-upload"></i> Import Questions</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
{% if request.user.is_superuser %}
|
||||
<li class="nav-item">
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
{% extends 'sbas/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Import LLM Questions</h1>
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<button class="btn btn-primary" type="submit">Upload and Import</button>
|
||||
</form>
|
||||
<p>Upload a single JSON array, a single object, or JSONL (one JSON object per line) matching the agreed schema.</p>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,133 @@
|
||||
{% extends 'sbas/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<h2>LLM Prompts</h2>
|
||||
<p>
|
||||
Below are the prompts used for interacting with large language models (LLMs) within the SBA system.
|
||||
</p>
|
||||
|
||||
<h3>Question Generation Prompt</h3>
|
||||
<pre>
|
||||
You are an expert radiologist tasked with generating high-quality multiple-choice questions for medical imaging education. Each question should consist of a clinical vignette, an image description, and five answer choices (A through E), with one correct answer and four plausible distractors.
|
||||
|
||||
Please adhere to the following guidelines when creating each question:
|
||||
1. Clinical Vignette: Provide a brief clinical scenario that sets the context for the question including relevant patient history and clinical findings.
|
||||
2. Make sure the question focuses on key radiological concepts, findings, or diagnoses.
|
||||
3. Answer Choices: Create five answer options. Ensure that one is the correct answer and the others are plausible distractors.
|
||||
4. Explanation: Provide a concise explanation for why the correct answer is right and why the distractors are incorrect.
|
||||
5. Difficulty Level: Aim for a moderate difficulty level suitable for training radiologists.
|
||||
6. Clarity and Precision: Use clear and precise language, avoiding ambiguity.
|
||||
7. Relevance: Ensure that the question is relevant to current radiological practices and guidelines.
|
||||
8. Questions should feature metadata tags for finding, structure, condition, presentation, and subspecialty.
|
||||
9. Each question output should be a json object with the following schema:
|
||||
10. For article sources pleese reference the article doi within the explanation field in the format [doi:10.xxxx/xxxxx].
|
||||
11. For statdx sources please reference the article in the format [statdx:article_id].
|
||||
12. References should be included inline within the explanation field.
|
||||
13. Questions need to have a radiology focus, please avoid questions that are purely clinical or biochemical without imaging relevance.
|
||||
|
||||
JSON Schema (draft-07 compatible)
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "SBA Question",
|
||||
"description": "Schema for importing LLM-generated questions into the sbas.Question model.",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"title",
|
||||
"stem",
|
||||
"a_answer",
|
||||
"b_answer",
|
||||
"c_answer",
|
||||
"d_answer",
|
||||
"e_answer",
|
||||
"best_answer",
|
||||
"category"
|
||||
],
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "Short title for the question."
|
||||
},
|
||||
"stem": {
|
||||
"type": "string",
|
||||
"description": "HTML or plain text question stem. Trim leading/trailing whitespace before saving."
|
||||
},
|
||||
"a_answer": { "type": "string" },
|
||||
"a_feedback": { "type": "string", "default": "" },
|
||||
"b_answer": { "type": "string" },
|
||||
"b_feedback": { "type": "string", "default": "" },
|
||||
"c_answer": { "type": "string" },
|
||||
"c_feedback": { "type": "string", "default": "" },
|
||||
"d_answer": { "type": "string" },
|
||||
"d_feedback": { "type": "string", "default": "" },
|
||||
"e_answer": { "type": "string" },
|
||||
"e_feedback": { "type": "string", "default": "" },
|
||||
"feedback": {
|
||||
"type": "string",
|
||||
"description": "General question feedback (may include HTML)."
|
||||
},
|
||||
"best_answer": {
|
||||
"type": "string",
|
||||
"enum": ["a", "b", "c", "d", "e"],
|
||||
"description": "One of 'a','b','c','d','e'."
|
||||
},
|
||||
"category": {
|
||||
"oneOf": [
|
||||
{ "type": "string", "description": "Category name ('Central Nervous and Head & Neck', 'Paediatric', 'Genito-urinary, Adrenal, Obstetrics & Gynaecology and Breast', 'Gastro-intestinal', 'Musculoskeletal and Trauma', 'Cardiothoracic and Vascular')" }
|
||||
]
|
||||
},
|
||||
"finding": {
|
||||
"type": "array",
|
||||
"description": "References to Finding objects. Prefer numeric IDs; names allowed if importer resolves them.",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{ "type": "integer" },
|
||||
{ "type": "string" }
|
||||
]
|
||||
},
|
||||
"uniqueItems": true
|
||||
},
|
||||
"structure": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{ "type": "integer" },
|
||||
{ "type": "string" }
|
||||
]
|
||||
},
|
||||
"uniqueItems": true
|
||||
},
|
||||
"condition": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{ "type": "integer" },
|
||||
{ "type": "string" }
|
||||
]
|
||||
},
|
||||
"uniqueItems": true
|
||||
},
|
||||
"presentation": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{ "type": "integer" },
|
||||
{ "type": "string" }
|
||||
]
|
||||
},
|
||||
"uniqueItems": true
|
||||
},
|
||||
"subspecialty": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{ "type": "integer" },
|
||||
{ "type": "string", "description": "Subspecialty name (e.g. 'Haematology & Oncology', 'Vascular', 'Uroradiology', 'Thoracic', 'Paediatric', 'Obstetric and Gynaecological', 'Neuroradiology', 'Musculoskeletal', 'Head and Neck', 'Gastrointestinal and hepatobiliary', 'Cardiac', 'Breast')" }
|
||||
]
|
||||
},
|
||||
"uniqueItems": true
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
</pre>
|
||||
@@ -29,14 +29,78 @@
|
||||
<a href="{% url 'sbas:exam_overview' pk=exam.pk %}">{{ exam }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div>
|
||||
Findings: {% if question.finding.exists %}
|
||||
<ul>
|
||||
{% for f in question.finding.all %}
|
||||
<li>{{ f.get_link|safe }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
None
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
Structures: {% if question.structure.exists %}
|
||||
<ul>
|
||||
{% for s in question.structure.all %}
|
||||
<li>{{ s.get_link|safe }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
None
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
Conditions: {% if question.condition.exists %}
|
||||
<ul>
|
||||
{% for c in question.condition.all %}
|
||||
<li>{{ c.get_link|safe }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
None
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
Presentations: {% if question.presentation.exists %}
|
||||
<ul>
|
||||
{% for p in question.presentation.all %}
|
||||
<li>{{ p.get_link|safe }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
None
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
Subspecialties: {% if question.subspecialty.exists %}
|
||||
<ul>
|
||||
{% for ss in question.subspecialty.all %}
|
||||
<li>{{ ss.get_link|safe }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
None
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
Category: {{ question.category }}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
Author: {% for user in question.author.all %}
|
||||
{{ author }},
|
||||
{% endfor %}
|
||||
Author(s):
|
||||
{% if question.author.exists %}
|
||||
{% for u in question.author.all %}
|
||||
{{ u.get_full_name|default:u.username }}{% if not forloop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
Unknown
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
Feedback: {{ question.feedback|linebreaks }}
|
||||
</div>
|
||||
|
||||
{% include 'question_notes.html' %}
|
||||
|
||||
Reference in New Issue
Block a user