-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathquiz_engine.py
More file actions
188 lines (148 loc) · 5.83 KB
/
Copy pathquiz_engine.py
File metadata and controls
188 lines (148 loc) · 5.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
"""出题 + 批改 + SM-2 间隔重复引擎"""
from models import Concept, QuizQuestion, QuizRecord
from state import AppState
from llm_client import chat, chat_json
from datetime import date, timedelta
_GENERATE_SYSTEM = """你是一个专业的教育出题专家。
你的任务是根据给定的知识点,生成高质量的选择题和简答题。
要求:
1. 题目必须围绕提供的知识点,不能偏题
2. 选择题4个选项,只有一个正确答案,干扰项要有迷惑性
3. 简答题需要明确的答题要点
4. 每道题附带详细解析
5. 难度与知识点的难度匹配"""
_GRADE_SYSTEM = """你是一个专业的教育批改专家。
你的任务是对学生的答案进行评分和给出反馈。
评分标准(1-5):
1: 完全错误,与正确答案毫无关系
2: 有部分关联但不正确
3: 部分正确,但遗漏关键要点
4: 基本正确,有少量不足
5: 完全正确,甚至超出预期
你需要返回JSON: {"score": 1-5, "feedback": "批改意见", "is_correct": true/false}"""
def _sm2_update(record: QuizRecord, quality: int):
"""SM-2 算法核心 — 更新 ease, interval, repetitions, next_review"""
if quality >= 3:
if record.sm2_repetitions == 0:
record.sm2_interval = 1
elif record.sm2_repetitions == 1:
record.sm2_interval = 6
else:
record.sm2_interval = round(record.sm2_interval * record.sm2_ease)
record.sm2_repetitions += 1
else:
record.sm2_repetitions = 0
record.sm2_interval = 1
# 更新 ease 因子
record.sm2_ease = record.sm2_ease + (0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02))
record.sm2_ease = max(1.3, record.sm2_ease)
record.score = quality
record.next_review = (date.today() + timedelta(days=record.sm2_interval)).isoformat()
record.is_correct = quality >= 4
def generate_questions(
concept_ids: list[str] | None = None,
count: int = 5,
q_type: str = "mixed",
) -> list[QuizQuestion]:
"""根据知识点生成题目"""
state = AppState()
# 获取目标概念
if concept_ids:
concepts = [state.concepts[cid] for cid in concept_ids if cid in state.concepts]
else:
concepts = list(state.concepts.values())
if not concepts:
return []
# 取前10个概念(防止token爆炸)
concepts = concepts[:10]
# 构建概念描述
concept_desc = "\n".join(
f"- {c.name}({c.category},难度{c.difficulty}):{c.description}"
for c in concepts
)
choice_count = count // 2 + count % 2 if q_type == "mixed" else (count if q_type == "choice" else 0)
short_count = count - choice_count
prompt = f"""请根据以下知识点生成{count}道题目:
知识点列表:
{concept_desc}
要求生成 {choice_count} 道选择题和 {short_count} 道简答题。
返回JSON格式:
{{
"questions": [
{{
"q_type": "choice",
"question": "题目内容",
"options": ["A. xxx", "B. xxx", "C. xxx", "D. xxx"],
"correct_answer": "A",
"explanation": "解析",
"difficulty": 3,
"concept_names": ["知识点名称"]
}}
]
}}"""
try:
result = chat_json(prompt, _GENERATE_SYSTEM, temperature=0.4)
except Exception as e:
print(f"[quiz_engine] 出题失败: {e}")
return []
questions = []
for item in result.get("questions", []):
# 将 concept_names 转为 concept_ids
cids = []
for name in item.get("concept_names", []):
concept = state.get_concept_by_name(name)
if concept:
cids.append(concept.id)
q = QuizQuestion(
question=item.get("question", "").strip(),
correct_answer=item.get("correct_answer", "").strip(),
explanation=item.get("explanation", "").strip(),
concept_ids=cids,
q_type=item.get("q_type", "choice"),
options=item.get("options", []),
difficulty=min(5, max(1, int(item.get("difficulty", 3)))),
)
if q.question:
questions.append(q)
state.add_question(q)
state.save()
return questions
def grade_answer(question: QuizQuestion, user_answer: str) -> QuizRecord:
"""批改答案,返回 QuizRecord(含 SM-2 参数)"""
q_type_label = "选择题(选项: " + ", ".join(question.options) + ")" if question.q_type == "choice" else "简答题"
prompt = f"""请批改以下学生的答案:
题目类型: {q_type_label}
题目: {question.question}
正确答案: {question.correct_answer}
标准解析: {question.explanation}
学生答案: {user_answer}
请评分并给出反馈。返回JSON: {{"score": 1-5, "feedback": "批改意见", "is_correct": true/false}}"""
try:
result = chat_json(prompt, _GRADE_SYSTEM, temperature=0.1)
except Exception as e:
print(f"[quiz_engine] 批改失败: {e}")
# 降级:简单字符串匹配
is_correct = user_answer.strip().upper() == question.correct_answer.strip().upper()
result = {"score": 5 if is_correct else 1, "feedback": "系统自动判定", "is_correct": is_correct}
record = QuizRecord(
question_id=question.id,
user_answer=user_answer,
is_correct=result.get("is_correct", False),
concept_ids=question.concept_ids,
score=result.get("score", 3),
feedback=result.get("feedback", ""),
)
# SM-2 更新
_sm2_update(record, record.score)
state = AppState()
state.add_record(record)
state.save()
return record
def get_today_review() -> list[tuple[QuizRecord, QuizQuestion]]:
"""获取今日待复习题目"""
state = AppState()
return state.get_due_reviews()
def get_weakness_report(top_n: int = 10) -> list[tuple[Concept, float]]:
"""获取薄弱知识点报告"""
state = AppState()
return state.get_weak_concepts(top_n)