SarahXia0405 commited on
Commit
072270b
·
verified ·
1 Parent(s): 82b3136

Update api/clare_core.py

Browse files
Files changed (1) hide show
  1. api/clare_core.py +116 -134
api/clare_core.py CHANGED
@@ -1,5 +1,4 @@
1
  # api/clare_core.py
2
- import os
3
  import re
4
  import math
5
  from typing import List, Dict, Tuple, Optional
@@ -19,14 +18,13 @@ from langsmith import traceable
19
  from langsmith.run_helpers import set_run_metadata
20
 
21
 
22
- # ----------------------------
23
- # Speed/Prompt controls
24
- # ----------------------------
25
- # limit how much history we send to the model (token reduction = speed up)
26
- MAX_HISTORY_TURNS = int(os.getenv("CLARE_MAX_HISTORY_TURNS", "6").strip()) # user+assistant pairs
27
- MAX_TOPICS = int(os.getenv("CLARE_MAX_TOPICS", "10").strip())
28
- MAX_WEAKNESSES = int(os.getenv("CLARE_MAX_WEAKNESSES", "3").strip())
29
- MAX_SESSION_MEMORY_QS = int(os.getenv("CLARE_MAX_SESSION_MEMORY_QS", "3").strip())
30
 
31
 
32
  # ---------- syllabus 解析 ----------
@@ -46,42 +44,20 @@ def parse_syllabus_docx(file_path: str, max_lines: int = 15) -> List[str]:
46
  return topics
47
 
48
 
49
- # ---------- 简单“弱项”检测 ----------
50
  WEAKNESS_KEYWORDS = [
51
- "don't understand",
52
- "do not understand",
53
- "not understand",
54
- "not sure",
55
- "confused",
56
- "hard to",
57
- "difficult",
58
- "struggle",
59
- "不会",
60
- "不懂",
61
- "看不懂",
62
- "搞不清",
63
- "很难",
64
  ]
65
 
66
- # ---------- 简单“掌握”检测 ----------
67
  MASTERY_KEYWORDS = [
68
- "got it",
69
- "makes sense",
70
- "now i see",
71
- "i see",
72
- "understand now",
73
- "clear now",
74
- "easy",
75
- "no problem",
76
- "没问题",
77
- "懂了",
78
- "明白了",
79
- "清楚了",
80
  ]
81
 
82
 
83
  def update_weaknesses_from_message(message: str, weaknesses: List[str]) -> List[str]:
84
- lower_msg = (message or "").lower()
85
  if any(k in lower_msg for k in WEAKNESS_KEYWORDS):
86
  weaknesses = weaknesses or []
87
  weaknesses.append(message)
@@ -95,7 +71,7 @@ def update_cognitive_state_from_message(
95
  if state is None:
96
  state = {"confusion": 0, "mastery": 0}
97
 
98
- lower_msg = (message or "").lower()
99
  if any(k in lower_msg for k in WEAKNESS_KEYWORDS):
100
  state["confusion"] = state.get("confusion", 0) + 1
101
  if any(k in lower_msg for k in MASTERY_KEYWORDS):
@@ -116,13 +92,12 @@ def describe_cognitive_state(state: Optional[Dict[str, int]]) -> str:
116
  return "mixed or uncertain cognitive state."
117
 
118
 
119
- # ---------- Session Memory ----------
120
  def build_session_memory_summary(
121
  history: List[Tuple[str, str]],
122
  weaknesses: Optional[List[str]],
123
  cognitive_state: Optional[Dict[str, int]],
124
- max_questions: int = 3,
125
- max_weaknesses: int = 2,
126
  ) -> str:
127
  parts: List[str] = []
128
 
@@ -130,53 +105,44 @@ def build_session_memory_summary(
130
  recent_qs = [u for (u, _a) in history[-max_questions:]]
131
  trimmed_qs = []
132
  for q in recent_qs:
133
- q = (q or "").strip()
134
  if len(q) > 120:
135
  q = q[:117] + "..."
136
  trimmed_qs.append(q)
137
  if trimmed_qs:
138
- parts.append("Recent questions: " + " | ".join(trimmed_qs))
139
 
140
  if weaknesses:
141
  recent_weak = weaknesses[-max_weaknesses:]
142
  trimmed_weak = []
143
  for w in recent_weak:
144
- w = (w or "").strip()
145
  if len(w) > 120:
146
  w = w[:117] + "..."
147
  trimmed_weak.append(w)
148
- if trimmed_weak:
149
- parts.append("Recent difficulties: " + " | ".join(trimmed_weak))
150
 
151
  if cognitive_state:
152
- parts.append("Cognitive state: " + describe_cognitive_state(cognitive_state))
153
 
154
  if not parts:
155
- return "No prior session memory."
 
 
 
156
 
157
  return " | ".join(parts)
158
 
159
 
160
- # ---------- 语言检测 ----------
161
  def detect_language(message: str, preference: str) -> str:
162
  if preference in ("English", "中文"):
163
  return preference
164
- if re.search(r"[\u4e00-\u9fff]", message or ""):
165
  return "中文"
166
  return "English"
167
 
168
 
169
- def get_empty_input_prompt(lang: str) -> str:
170
- if lang == "中文":
171
- return "请先输入一个问题或想法,再按回车发送,我才能帮到你哦。"
172
- return "Please type a question or some text before sending, then hit Enter."
173
-
174
-
175
- def build_error_message(
176
- e: Exception,
177
- lang: str,
178
- op: str = "chat",
179
- ) -> str:
180
  if lang == "中文":
181
  prefix = {
182
  "chat": "抱歉,刚刚在和模型对话时出现了一点问题。",
@@ -193,7 +159,6 @@ def build_error_message(
193
  return prefix_en + " Please try again in a moment or rephrase your request."
194
 
195
 
196
- # ---------- Session 状态展示 ----------
197
  def render_session_status(
198
  learning_mode: str,
199
  weaknesses: Optional[List[str]],
@@ -214,9 +179,11 @@ def render_session_status(
214
  return "\n".join(lines)
215
 
216
 
217
- # ---------- Similarity helpers (kept; not called by server currently) ----------
 
 
218
  def _normalize_text(text: str) -> str:
219
- text = (text or "").lower().strip()
220
  text = re.sub(r"[^\w\s]", " ", text)
221
  text = re.sub(r"\s+", " ", text)
222
  return text
@@ -227,7 +194,7 @@ def _jaccard_similarity(a: str, b: str) -> float:
227
  tokens_b = set(b.split())
228
  if not tokens_a or not tokens_b:
229
  return 0.0
230
- return len(a_set := (tokens_a & tokens_b)) / len(tokens_a | tokens_b)
231
 
232
 
233
  def cosine_similarity(a: List[float], b: List[float]) -> float:
@@ -326,7 +293,7 @@ def safe_chat_completion(
326
  messages: List[Dict[str, str]],
327
  lang: str,
328
  op: str = "chat",
329
- temperature: float = 0.4, # ✅ slightly lower for stability/speed
330
  ) -> str:
331
  preferred_model = model_name or DEFAULT_MODEL
332
  last_error: Optional[Exception] = None
@@ -354,7 +321,14 @@ def safe_chat_completion(
354
  return build_error_message(last_error or Exception("unknown error"), lang, op)
355
 
356
 
357
- # ---------- 构建 messages (optimized) ----------
 
 
 
 
 
 
 
358
  def build_messages(
359
  user_message: str,
360
  history: List[Tuple[str, str]],
@@ -366,72 +340,81 @@ def build_messages(
366
  cognitive_state: Optional[Dict[str, int]],
367
  rag_context: Optional[str] = None,
368
  ) -> List[Dict[str, str]]:
369
- messages: List[Dict[str, str]] = [{"role": "system", "content": CLARE_SYSTEM_PROMPT}]
370
-
371
- # consolidate most system context into ONE message to reduce overhead
372
- sys_parts: List[str] = []
373
-
374
- # mode
375
- if learning_mode in LEARNING_MODE_INSTRUCTIONS:
376
- sys_parts.append(f"Learning mode: {learning_mode}. {LEARNING_MODE_INSTRUCTIONS[learning_mode]}")
377
 
378
- # syllabus/topics (limit)
379
  topics = course_outline if course_outline else DEFAULT_COURSE_TOPICS
380
  topics = (topics or [])[:MAX_TOPICS]
381
- if topics:
382
- sys_parts.append("Course topics: " + " | ".join(topics))
383
-
384
- # doc_type hint
385
- if doc_type and doc_type != "Syllabus":
386
- sys_parts.append(f"Supporting doc uploaded: {doc_type}.")
387
-
388
- # weaknesses (limit)
389
- if weaknesses:
390
- ww = weaknesses[-MAX_WEAKNESSES:]
391
- sys_parts.append("Student difficulties (recent): " + " | ".join(ww))
392
 
393
- # cognitive state (short)
394
- if cognitive_state:
395
- sys_parts.append("Cognitive state: " + describe_cognitive_state(cognitive_state))
396
 
397
- # session memory (short + limited)
398
  session_memory_text = build_session_memory_summary(
399
- history=history,
400
- weaknesses=weaknesses,
401
  cognitive_state=cognitive_state,
402
- max_questions=MAX_SESSION_MEMORY_QS,
403
- max_weaknesses=min(2, MAX_WEAKNESSES),
404
  )
405
- if session_memory_text:
406
- sys_parts.append("Session memory: " + session_memory_text)
407
 
408
- # language preference
 
 
 
 
 
 
 
 
 
 
 
 
409
  if language_preference == "English":
410
- sys_parts.append("Answer in English.")
411
  elif language_preference == "中文":
412
- sys_parts.append("请用中文回答。")
413
 
414
- if sys_parts:
415
- messages.append({"role": "system", "content": "\n".join(sys_parts)})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
416
 
417
- # rag context (keep as separate system block, but already capped in rag_engine)
418
- if rag_context:
419
- messages.append(
420
- {
421
- "role": "system",
422
- "content": (
423
- "Relevant excerpts (use as grounding; prefer these if conflict):\n\n"
424
- + rag_context
425
- ),
426
- }
427
- )
428
 
429
- # limit history turns for speed
430
- hist = history[-MAX_HISTORY_TURNS:] if history else []
431
- for user, assistant in hist:
432
- messages.append({"role": "user", "content": user})
433
- if assistant is not None:
434
- messages.append({"role": "assistant", "content": assistant})
435
 
436
  messages.append({"role": "user", "content": user_message})
437
  return messages
@@ -476,14 +459,13 @@ def chat_with_clare(
476
  messages=messages,
477
  lang=language_preference,
478
  op="chat",
479
- temperature=0.4,
480
  )
481
 
482
- history = (history or []) + [(message, answer)]
483
  return answer, history
484
 
485
 
486
- # ---------- 导出对话为 Markdown ----------
487
  def export_conversation(
488
  history: List[Tuple[str, str]],
489
  course_outline: List[str],
@@ -499,11 +481,11 @@ def export_conversation(
499
 
500
  if weaknesses:
501
  lines.append("- Observed student difficulties:\n")
502
- for w in (weaknesses or [])[-5:]:
503
  lines.append(f" - {w}\n")
504
  lines.append("\n---\n\n")
505
 
506
- for user, assistant in history or []:
507
  lines.append(f"**Student:** {user}\n\n")
508
  lines.append(f"**Clare:** {assistant}\n\n")
509
  lines.append("---\n\n")
@@ -511,7 +493,6 @@ def export_conversation(
511
  return "".join(lines)
512
 
513
 
514
- # ---------- 生成 quiz ----------
515
  @traceable(run_type="chain", name="generate_quiz_from_history")
516
  def generate_quiz_from_history(
517
  history: List[Tuple[str, str]],
@@ -522,7 +503,7 @@ def generate_quiz_from_history(
522
  language_preference: str,
523
  ) -> str:
524
  conversation_text = ""
525
- for user, assistant in (history or [])[-6:]:
526
  conversation_text += f"Student: {user}\nClare: {assistant}\n"
527
 
528
  topics_text = "; ".join((course_outline or [])[:8])
@@ -534,8 +515,8 @@ def generate_quiz_from_history(
534
  {
535
  "role": "system",
536
  "content": (
537
- "Create a short concept quiz with 3 questions (mix MCQ + short answer). "
538
- "Add 'Answer Key' at end. Adapt difficulty to student state."
539
  ),
540
  },
541
  {"role": "system", "content": f"Course topics: {topics_text}"},
@@ -550,6 +531,7 @@ def generate_quiz_from_history(
550
  if language_preference == "中文":
551
  messages.append({"role": "system", "content": "请用中文给出问题和答案。"})
552
 
 
553
  quiz_text = safe_chat_completion(
554
  model_name=model_name,
555
  messages=messages,
@@ -560,7 +542,6 @@ def generate_quiz_from_history(
560
  return quiz_text
561
 
562
 
563
- # ---------- 总结 ----------
564
  @traceable(run_type="chain", name="summarize_conversation")
565
  def summarize_conversation(
566
  history: List[Tuple[str, str]],
@@ -571,7 +552,7 @@ def summarize_conversation(
571
  language_preference: str,
572
  ) -> str:
573
  conversation_text = ""
574
- for user, assistant in (history or [])[-8:]:
575
  conversation_text += f"Student: {user}\nClare: {assistant}\n"
576
 
577
  topics_text = "; ".join((course_outline or [])[:8])
@@ -583,8 +564,8 @@ def summarize_conversation(
583
  {
584
  "role": "system",
585
  "content": (
586
- "Produce a concept-only summary in bullet points: definitions, key ideas, "
587
- "formulas, examples, takeaways. No personal chat."
588
  ),
589
  },
590
  {"role": "system", "content": f"Course topics: {topics_text}"},
@@ -592,12 +573,13 @@ def summarize_conversation(
592
  {"role": "system", "content": f"Cognitive state: {cog_text}"},
593
  {
594
  "role": "user",
595
- "content": "Recent conversation:\n\n" + conversation_text + "\n\nSummarize key concepts.",
596
  },
597
  ]
598
 
599
  if language_preference == "中文":
600
- messages.append({"role": "system", "content": "请用中文给出要点总结,只保留知识点,使用条目符号。"})
 
601
 
602
  summary_text = safe_chat_completion(
603
  model_name=model_name,
 
1
  # api/clare_core.py
 
2
  import re
3
  import math
4
  from typing import List, Dict, Tuple, Optional
 
18
  from langsmith.run_helpers import set_run_metadata
19
 
20
 
21
+ # -----------------------------
22
+ # Speed controls (token budget)
23
+ # -----------------------------
24
+ MAX_HISTORY_TURNS = 6 # 只带最近 N 轮(每轮=1个user+1个assistant)
25
+ MAX_TOPICS = 8 # syllabus topics 只带前 N 条
26
+ MAX_WEAKNESSES = 3 # 只带最后 N 条
27
+ MAX_RAG_CHARS_IN_PROMPT = 1200 # rag_context 再截断一次(双保险)
 
28
 
29
 
30
  # ---------- syllabus 解析 ----------
 
44
  return topics
45
 
46
 
 
47
  WEAKNESS_KEYWORDS = [
48
+ "don't understand", "do not understand", "not understand", "not sure", "confused",
49
+ "hard to", "difficult", "struggle",
50
+ "不会", "不懂", "看不懂", "搞不清", "很难",
 
 
 
 
 
 
 
 
 
 
51
  ]
52
 
 
53
  MASTERY_KEYWORDS = [
54
+ "got it", "makes sense", "now i see", "i see", "understand now", "clear now", "easy", "no problem",
55
+ "没问题", "懂了", "明白了", "清楚了",
 
 
 
 
 
 
 
 
 
 
56
  ]
57
 
58
 
59
  def update_weaknesses_from_message(message: str, weaknesses: List[str]) -> List[str]:
60
+ lower_msg = message.lower()
61
  if any(k in lower_msg for k in WEAKNESS_KEYWORDS):
62
  weaknesses = weaknesses or []
63
  weaknesses.append(message)
 
71
  if state is None:
72
  state = {"confusion": 0, "mastery": 0}
73
 
74
+ lower_msg = message.lower()
75
  if any(k in lower_msg for k in WEAKNESS_KEYWORDS):
76
  state["confusion"] = state.get("confusion", 0) + 1
77
  if any(k in lower_msg for k in MASTERY_KEYWORDS):
 
92
  return "mixed or uncertain cognitive state."
93
 
94
 
 
95
  def build_session_memory_summary(
96
  history: List[Tuple[str, str]],
97
  weaknesses: Optional[List[str]],
98
  cognitive_state: Optional[Dict[str, int]],
99
+ max_questions: int = 4,
100
+ max_weaknesses: int = 3,
101
  ) -> str:
102
  parts: List[str] = []
103
 
 
105
  recent_qs = [u for (u, _a) in history[-max_questions:]]
106
  trimmed_qs = []
107
  for q in recent_qs:
108
+ q = q.strip()
109
  if len(q) > 120:
110
  q = q[:117] + "..."
111
  trimmed_qs.append(q)
112
  if trimmed_qs:
113
+ parts.append("Recent student questions: " + " | ".join(trimmed_qs))
114
 
115
  if weaknesses:
116
  recent_weak = weaknesses[-max_weaknesses:]
117
  trimmed_weak = []
118
  for w in recent_weak:
119
+ w = w.strip()
120
  if len(w) > 120:
121
  w = w[:117] + "..."
122
  trimmed_weak.append(w)
123
+ parts.append("Recent difficulties mentioned by the student: " + " | ".join(trimmed_weak))
 
124
 
125
  if cognitive_state:
126
+ parts.append("Current cognitive state: " + describe_cognitive_state(cognitive_state))
127
 
128
  if not parts:
129
+ return (
130
+ "No prior session memory. Treat this as early stage of the conversation; "
131
+ "start simple and ask a quick check-up question."
132
+ )
133
 
134
  return " | ".join(parts)
135
 
136
 
 
137
  def detect_language(message: str, preference: str) -> str:
138
  if preference in ("English", "中文"):
139
  return preference
140
+ if re.search(r"[\u4e00-\u9fff]", message):
141
  return "中文"
142
  return "English"
143
 
144
 
145
+ def build_error_message(e: Exception, lang: str, op: str = "chat") -> str:
 
 
 
 
 
 
 
 
 
 
146
  if lang == "中文":
147
  prefix = {
148
  "chat": "抱歉,刚刚在和模型对话时出现了一点问题。",
 
159
  return prefix_en + " Please try again in a moment or rephrase your request."
160
 
161
 
 
162
  def render_session_status(
163
  learning_mode: str,
164
  weaknesses: Optional[List[str]],
 
179
  return "\n".join(lines)
180
 
181
 
182
+ # -----------------------
183
+ # Similarity helpers (kept)
184
+ # -----------------------
185
  def _normalize_text(text: str) -> str:
186
+ text = text.lower().strip()
187
  text = re.sub(r"[^\w\s]", " ", text)
188
  text = re.sub(r"\s+", " ", text)
189
  return text
 
194
  tokens_b = set(b.split())
195
  if not tokens_a or not tokens_b:
196
  return 0.0
197
+ return len(tokens_a & tokens_b) / len(tokens_a | tokens_b)
198
 
199
 
200
  def cosine_similarity(a: List[float], b: List[float]) -> float:
 
293
  messages: List[Dict[str, str]],
294
  lang: str,
295
  op: str = "chat",
296
+ temperature: float = 0.5,
297
  ) -> str:
298
  preferred_model = model_name or DEFAULT_MODEL
299
  last_error: Optional[Exception] = None
 
321
  return build_error_message(last_error or Exception("unknown error"), lang, op)
322
 
323
 
324
+ def _take_recent_history(history: List[Tuple[str, str]], max_turns: int) -> List[Tuple[str, str]]:
325
+ if not history:
326
+ return []
327
+ if max_turns <= 0:
328
+ return []
329
+ return history[-max_turns:]
330
+
331
+
332
  def build_messages(
333
  user_message: str,
334
  history: List[Tuple[str, str]],
 
340
  cognitive_state: Optional[Dict[str, int]],
341
  rag_context: Optional[str] = None,
342
  ) -> List[Dict[str, str]]:
343
+ """
344
+ SPEED: reduce tokens by:
345
+ - one consolidated system message
346
+ - limit history turns
347
+ - limit topics / weaknesses
348
+ - truncate rag_context
349
+ """
350
+ trimmed_history = _take_recent_history(history, MAX_HISTORY_TURNS)
351
 
 
352
  topics = course_outline if course_outline else DEFAULT_COURSE_TOPICS
353
  topics = (topics or [])[:MAX_TOPICS]
354
+ topics_text = " | ".join(topics)
 
 
 
 
 
 
 
 
 
 
355
 
356
+ weak_list = (weaknesses or [])[-MAX_WEAKNESSES:]
357
+ weak_text = " | ".join(weak_list) if weak_list else ""
 
358
 
 
359
  session_memory_text = build_session_memory_summary(
360
+ history=trimmed_history,
361
+ weaknesses=weak_list,
362
  cognitive_state=cognitive_state,
 
 
363
  )
 
 
364
 
365
+ mode_instruction = LEARNING_MODE_INSTRUCTIONS.get(learning_mode, "")
366
+
367
+ # RAG context double-safety truncate
368
+ rag_block = ""
369
+ if rag_context:
370
+ rag_context = rag_context[:MAX_RAG_CHARS_IN_PROMPT]
371
+ rag_block = (
372
+ "\n\nRelevant excerpts (use as primary grounding; prefer excerpts if conflict):\n"
373
+ + rag_context
374
+ )
375
+
376
+ # Language directive
377
+ lang_line = ""
378
  if language_preference == "English":
379
+ lang_line = "\nAnswer in English."
380
  elif language_preference == "中文":
381
+ lang_line = "\n请用中文回答。"
382
 
383
+ # Cognitive state directive (short)
384
+ cog_line = ""
385
+ if cognitive_state:
386
+ confusion = cognitive_state.get("confusion", 0)
387
+ mastery = cognitive_state.get("mastery", 0)
388
+ if confusion >= 2 and confusion >= mastery + 1:
389
+ cog_line = "\nStudent is under HIGH cognitive load: be concise, stepwise, concrete; check understanding."
390
+ elif mastery >= 2 and mastery >= confusion + 1:
391
+ cog_line = "\nStudent seems comfortable: you may go slightly deeper and connect concepts."
392
+ else:
393
+ cog_line = "\nStudent state is mixed: keep moderate pace and ask brief check questions."
394
+
395
+ # Doc type hint (short)
396
+ doc_line = ""
397
+ if doc_type and doc_type != "Syllabus":
398
+ doc_line = f"\nStudent uploaded supporting material: {doc_type}."
399
+
400
+ consolidated_system = (
401
+ CLARE_SYSTEM_PROMPT
402
+ + f"\n\nLearning mode: {learning_mode}. {mode_instruction}"
403
+ + f"\n\nCourse topics context: {topics_text}"
404
+ + (f"\nStudent difficulties (recent): {weak_text}" if weak_text else "\nStudent difficulties (recent): none")
405
+ + f"\nSession memory (this chat only): {session_memory_text}"
406
+ + doc_line
407
+ + cog_line
408
+ + lang_line
409
+ + rag_block
410
+ )
411
 
412
+ messages: List[Dict[str, str]] = [{"role": "system", "content": consolidated_system}]
 
 
 
 
 
 
 
 
 
 
413
 
414
+ for u, a in trimmed_history:
415
+ messages.append({"role": "user", "content": u})
416
+ if a is not None:
417
+ messages.append({"role": "assistant", "content": a})
 
 
418
 
419
  messages.append({"role": "user", "content": user_message})
420
  return messages
 
459
  messages=messages,
460
  lang=language_preference,
461
  op="chat",
462
+ temperature=0.5,
463
  )
464
 
465
+ history = history + [(message, answer)]
466
  return answer, history
467
 
468
 
 
469
  def export_conversation(
470
  history: List[Tuple[str, str]],
471
  course_outline: List[str],
 
481
 
482
  if weaknesses:
483
  lines.append("- Observed student difficulties:\n")
484
+ for w in weaknesses[-5:]:
485
  lines.append(f" - {w}\n")
486
  lines.append("\n---\n\n")
487
 
488
+ for user, assistant in history:
489
  lines.append(f"**Student:** {user}\n\n")
490
  lines.append(f"**Clare:** {assistant}\n\n")
491
  lines.append("---\n\n")
 
493
  return "".join(lines)
494
 
495
 
 
496
  @traceable(run_type="chain", name="generate_quiz_from_history")
497
  def generate_quiz_from_history(
498
  history: List[Tuple[str, str]],
 
503
  language_preference: str,
504
  ) -> str:
505
  conversation_text = ""
506
+ for user, assistant in history[-8:]:
507
  conversation_text += f"Student: {user}\nClare: {assistant}\n"
508
 
509
  topics_text = "; ".join((course_outline or [])[:8])
 
515
  {
516
  "role": "system",
517
  "content": (
518
+ "Create a short concept quiz (3 questions). Mix MCQ and short-answer. "
519
+ "Then provide an Answer Key. Adjust difficulty to cognitive state."
520
  ),
521
  },
522
  {"role": "system", "content": f"Course topics: {topics_text}"},
 
531
  if language_preference == "中文":
532
  messages.append({"role": "system", "content": "请用中文给出问题和答案。"})
533
 
534
+
535
  quiz_text = safe_chat_completion(
536
  model_name=model_name,
537
  messages=messages,
 
542
  return quiz_text
543
 
544
 
 
545
  @traceable(run_type="chain", name="summarize_conversation")
546
  def summarize_conversation(
547
  history: List[Tuple[str, str]],
 
552
  language_preference: str,
553
  ) -> str:
554
  conversation_text = ""
555
+ for user, assistant in history[-10:]:
556
  conversation_text += f"Student: {user}\nClare: {assistant}\n"
557
 
558
  topics_text = "; ".join((course_outline or [])[:8])
 
564
  {
565
  "role": "system",
566
  "content": (
567
+ "Produce a concept-only summary in bullet points. "
568
+ "Include definitions, key ideas, examples, takeaways. No personal chatter."
569
  ),
570
  },
571
  {"role": "system", "content": f"Course topics: {topics_text}"},
 
573
  {"role": "system", "content": f"Cognitive state: {cog_text}"},
574
  {
575
  "role": "user",
576
+ "content": "Recent conversation:\n\n" + conversation_text + "\n\nSummarize key concepts only.",
577
  },
578
  ]
579
 
580
  if language_preference == "中文":
581
+ messages.append({"role": "system", "content": "请用中文要点总结,只保留知识点,使用条目符号。"})
582
+
583
 
584
  summary_text = safe_chat_completion(
585
  model_name=model_name,