Sync from GitHub via hub-sync
Browse files- content_pipeline.py +15 -3
- learning_engine.py +7 -2
- llm.py +5 -3
content_pipeline.py
CHANGED
|
@@ -181,10 +181,15 @@ def generate_deck_from_images(images: list, n: int = 12) -> list[Card]:
|
|
| 181 |
" answer — a concise reference answer (1-3 sentences)\n"
|
| 182 |
" topic — a short tag naming the concept (e.g. 'Cell Biology')\n"
|
| 183 |
" difficulty — integer: 1 (recall), 2 (application), 3 (synthesis)\n\n"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
"No prose, no markdown fences, no text outside the JSON array."
|
| 185 |
)},
|
| 186 |
{"role": "user", "content": list(images) + [
|
| 187 |
-
"Generate quiz questions from these document page images."]},
|
| 188 |
]
|
| 189 |
# Same defensive strict-JSON + repair pass as the text path; images ride along
|
| 190 |
# in the message content and llm.chat() routes them to the vision model.
|
|
@@ -264,10 +269,15 @@ def _cards_from_chunk(chunk: str) -> list[Card]:
|
|
| 264 |
" topic — a short tag naming the concept (e.g. 'Cell Biology')\n"
|
| 265 |
" difficulty — integer: 1 (direct recall), 2 (application/explanation), "
|
| 266 |
"3 (synthesis/comparison)\n\n"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 267 |
"Mix all three difficulty levels across the questions. "
|
| 268 |
"No prose, no markdown fences, no explanation outside the JSON array."
|
| 269 |
)},
|
| 270 |
-
{"role": "user", "content": f"
|
| 271 |
]
|
| 272 |
# Strict-JSON with one repair pass: chat_json feeds a malformed reply back to
|
| 273 |
# the model demanding clean JSON before giving up. A chunk that still won't
|
|
@@ -354,7 +364,9 @@ def regenerate(card: Card, direction: str) -> Card:
|
|
| 354 |
"You rewrite a single quiz question to a different difficulty while "
|
| 355 |
"keeping the SAME underlying concept. Stay grounded in the passage — "
|
| 356 |
"do not introduce facts that are not in it. "
|
| 357 |
-
"Return ONLY a JSON object with keys: question, answer, topic."
|
|
|
|
|
|
|
| 358 |
)},
|
| 359 |
{"role": "user", "content": (
|
| 360 |
f"Passage:\n{card['source_chunk']}\n\n"
|
|
|
|
| 181 |
" answer — a concise reference answer (1-3 sentences)\n"
|
| 182 |
" topic — a short tag naming the concept (e.g. 'Cell Biology')\n"
|
| 183 |
" difficulty — integer: 1 (recall), 2 (application), 3 (synthesis)\n\n"
|
| 184 |
+
"Output format example — return ONE array of OBJECTS exactly like this "
|
| 185 |
+
"(not an array of strings), no other text:\n"
|
| 186 |
+
'[{"question": "What does X do?", "answer": "X does Y.", "topic": "Topic A", '
|
| 187 |
+
'"difficulty": 1}, {"question": "Why does Z occur?", "answer": "Because ...", '
|
| 188 |
+
'"topic": "Topic A", "difficulty": 2}]\n\n'
|
| 189 |
"No prose, no markdown fences, no text outside the JSON array."
|
| 190 |
)},
|
| 191 |
{"role": "user", "content": list(images) + [
|
| 192 |
+
"Generate the JSON array of quiz questions from these document page images."]},
|
| 193 |
]
|
| 194 |
# Same defensive strict-JSON + repair pass as the text path; images ride along
|
| 195 |
# in the message content and llm.chat() routes them to the vision model.
|
|
|
|
| 269 |
" topic — a short tag naming the concept (e.g. 'Cell Biology')\n"
|
| 270 |
" difficulty — integer: 1 (direct recall), 2 (application/explanation), "
|
| 271 |
"3 (synthesis/comparison)\n\n"
|
| 272 |
+
"Output format example — return ONE array of OBJECTS exactly like this "
|
| 273 |
+
"(not an array of strings), no other text:\n"
|
| 274 |
+
'[{"question": "What does X do?", "answer": "X does Y.", "topic": "Topic A", '
|
| 275 |
+
'"difficulty": 1}, {"question": "Why does Z occur?", "answer": "Because ...", '
|
| 276 |
+
'"topic": "Topic A", "difficulty": 2}]\n\n'
|
| 277 |
"Mix all three difficulty levels across the questions. "
|
| 278 |
"No prose, no markdown fences, no explanation outside the JSON array."
|
| 279 |
)},
|
| 280 |
+
{"role": "user", "content": f"Passage:\n{chunk}\n\nGenerate the JSON array now."},
|
| 281 |
]
|
| 282 |
# Strict-JSON with one repair pass: chat_json feeds a malformed reply back to
|
| 283 |
# the model demanding clean JSON before giving up. A chunk that still won't
|
|
|
|
| 364 |
"You rewrite a single quiz question to a different difficulty while "
|
| 365 |
"keeping the SAME underlying concept. Stay grounded in the passage — "
|
| 366 |
"do not introduce facts that are not in it. "
|
| 367 |
+
"Return ONLY a JSON object with keys: question, answer, topic. "
|
| 368 |
+
"Example (return ONE object exactly like this, no other text):\n"
|
| 369 |
+
'{"question": "What does X do?", "answer": "X does Y.", "topic": "Topic A"}'
|
| 370 |
)},
|
| 371 |
{"role": "user", "content": (
|
| 372 |
f"Passage:\n{card['source_chunk']}\n\n"
|
learning_engine.py
CHANGED
|
@@ -71,7 +71,10 @@ def grade_answer(card: Card, user_answer: str) -> GradeResult:
|
|
| 71 |
"You grade a student's answer against a reference answer. "
|
| 72 |
"Return ONLY a JSON object with keys: "
|
| 73 |
"score (integer 0-5), explanation (string for the student), "
|
| 74 |
-
"missed_concept (short string naming what they got wrong, or \"\")."
|
|
|
|
|
|
|
|
|
|
| 75 |
{"role": "user", "content":
|
| 76 |
f"Question: {card['question']}\nReference answer: {card['answer']}\n"
|
| 77 |
f"Student answer: {user_answer}\nGrade it."},
|
|
@@ -160,7 +163,9 @@ def generate_followups(card: Card, grade: GradeResult, n: int = 2) -> list[Card]
|
|
| 160 |
messages = [
|
| 161 |
{"role": "system", "content":
|
| 162 |
"The student missed a concept. Generate follow-up quiz questions that "
|
| 163 |
-
"drill it. Return ONLY a JSON array with keys: question, answer,
|
|
|
|
|
|
|
| 164 |
{"role": "user", "content":
|
| 165 |
f"Original question: {card['question']}\n"
|
| 166 |
f"Missed concept: {grade['missed_concept']}\n"
|
|
|
|
| 71 |
"You grade a student's answer against a reference answer. "
|
| 72 |
"Return ONLY a JSON object with keys: "
|
| 73 |
"score (integer 0-5), explanation (string for the student), "
|
| 74 |
+
"missed_concept (short string naming what they got wrong, or \"\"). "
|
| 75 |
+
"Example (return ONE object exactly like this, no other text):\n"
|
| 76 |
+
'{"score": 3, "explanation": "You captured the main idea but missed a '
|
| 77 |
+
'detail.", "missed_concept": "the specific detail"}'},
|
| 78 |
{"role": "user", "content":
|
| 79 |
f"Question: {card['question']}\nReference answer: {card['answer']}\n"
|
| 80 |
f"Student answer: {user_answer}\nGrade it."},
|
|
|
|
| 163 |
messages = [
|
| 164 |
{"role": "system", "content":
|
| 165 |
"The student missed a concept. Generate follow-up quiz questions that "
|
| 166 |
+
"drill it. Return ONLY a JSON array of OBJECTS with keys: question, answer, "
|
| 167 |
+
"topic. Example (return ONE array exactly like this, no other text):\n"
|
| 168 |
+
'[{"question": "What is X?", "answer": "X is Y.", "topic": "Topic A"}]'},
|
| 169 |
{"role": "user", "content":
|
| 170 |
f"Original question: {card['question']}\n"
|
| 171 |
f"Missed concept: {grade['missed_concept']}\n"
|
llm.py
CHANGED
|
@@ -192,12 +192,14 @@ def _chat_vision(messages: list[dict], max_tokens: int) -> str:
|
|
| 192 |
inputs[k] = v.to(dtype=getattr(torch, _resolve_dtype_name()))
|
| 193 |
|
| 194 |
with torch.no_grad():
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
out = _model.generate(
|
| 196 |
**inputs,
|
| 197 |
max_new_tokens=max_tokens,
|
| 198 |
-
do_sample=
|
| 199 |
-
temperature=0.7,
|
| 200 |
-
top_p=0.9,
|
| 201 |
downsample_mode="16x",
|
| 202 |
)
|
| 203 |
gen = out[0][inputs["input_ids"].shape[1]:]
|
|
|
|
| 192 |
inputs[k] = v.to(dtype=getattr(torch, _resolve_dtype_name()))
|
| 193 |
|
| 194 |
with torch.no_grad():
|
| 195 |
+
# Greedy decoding (do_sample=False): every caller wants a strict JSON
|
| 196 |
+
# object/array, and greedy is markedly more reliable at that than sampling
|
| 197 |
+
# for MiniCPM-V — verified on GPU. enable_thinking is already False so the
|
| 198 |
+
# tight token budget goes to the answer, not a <think> preamble.
|
| 199 |
out = _model.generate(
|
| 200 |
**inputs,
|
| 201 |
max_new_tokens=max_tokens,
|
| 202 |
+
do_sample=False,
|
|
|
|
|
|
|
| 203 |
downsample_mode="16x",
|
| 204 |
)
|
| 205 |
gen = out[0][inputs["input_ids"].shape[1]:]
|