nz-nz commited on
Commit
76f2051
·
verified ·
1 Parent(s): f61035c

Sync from GitHub via hub-sync

Browse files
Files changed (3) hide show
  1. content_pipeline.py +15 -3
  2. learning_engine.py +7 -2
  3. 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"Generate quiz questions from this passage:\n\n{chunk}"},
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, topic."},
 
 
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=True,
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]:]