Spaces:
Paused
Paused
chih.yikuan Claude Opus 4.6 (1M context) commited on
Commit ·
3efc9d0
1
Parent(s): 5722fbf
Fix: Handle missing teacher answers — include all student answers instead
Browse filesWhen 標準答案 is not uploaded, filter_student_wrong_answers now returns
ALL student answers + ALL questions (instead of empty arrays), with a
separate prompt template that asks the LLM to infer correctness.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
chatkit/backend/app/report_generator.py
CHANGED
|
@@ -51,10 +51,11 @@ def filter_student_wrong_answers(
|
|
| 51 |
student: dict,
|
| 52 |
questions_data: dict,
|
| 53 |
teacher_answers_data: dict,
|
| 54 |
-
) -> tuple[list[dict], list[dict], list[dict]]:
|
| 55 |
"""Compare a student's answers with teacher answers, return wrong items.
|
| 56 |
|
| 57 |
-
Returns (wrong_questions, wrong_student_answers, relevant_teacher_answers).
|
|
|
|
| 58 |
"""
|
| 59 |
teacher_map: dict[int, dict] = {}
|
| 60 |
for ta in teacher_answers_data.get("answers", []):
|
|
@@ -68,6 +69,12 @@ def filter_student_wrong_answers(
|
|
| 68 |
if num is not None:
|
| 69 |
question_map[int(num)] = q
|
| 70 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
wrong_questions: list[dict] = []
|
| 72 |
wrong_answers: list[dict] = []
|
| 73 |
relevant_teacher: list[dict] = []
|
|
@@ -91,7 +98,7 @@ def filter_student_wrong_answers(
|
|
| 91 |
if q:
|
| 92 |
wrong_questions.append(q)
|
| 93 |
|
| 94 |
-
return wrong_questions, wrong_answers, relevant_teacher
|
| 95 |
|
| 96 |
|
| 97 |
STUDENT_REPORT_SYSTEM_PROMPT = """You are ClassLens, an AI teaching assistant that creates a focused, personalized exam analysis report for a single student.
|
|
@@ -173,10 +180,53 @@ STUDENT_REPORT_PROMPT = """請為以下學生生成個人化的錯題分析報
|
|
| 173 |
請針對每一道錯題提供詳細分析,找出錯誤模式,並給出具體的學習建議。"""
|
| 174 |
|
| 175 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 176 |
def get_default_prompt() -> str:
|
| 177 |
return STUDENT_REPORT_PROMPT
|
| 178 |
|
| 179 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
def build_student_prompt(
|
| 181 |
student: dict,
|
| 182 |
questions_data: dict,
|
|
@@ -184,21 +234,17 @@ def build_student_prompt(
|
|
| 184 |
) -> dict:
|
| 185 |
"""Build the filled prompt for a student (for preview).
|
| 186 |
|
| 187 |
-
Returns dict with filled_prompt, student_name, total_questions, wrong_count.
|
| 188 |
"""
|
| 189 |
total_questions = len(questions_data.get("questions", []))
|
| 190 |
-
wrong_questions, wrong_answers, relevant_teacher = filter_student_wrong_answers(
|
| 191 |
student, questions_data, teacher_answers_data,
|
| 192 |
)
|
| 193 |
student_name = student.get("name", "Unknown")
|
| 194 |
|
| 195 |
-
filled =
|
| 196 |
-
student_name
|
| 197 |
-
|
| 198 |
-
wrong_count=len(wrong_answers),
|
| 199 |
-
wrong_questions=json.dumps({"questions": wrong_questions}, ensure_ascii=False, indent=2),
|
| 200 |
-
wrong_answers=json.dumps(wrong_answers, ensure_ascii=False, indent=2),
|
| 201 |
-
teacher_answers=json.dumps(relevant_teacher, ensure_ascii=False, indent=2),
|
| 202 |
)
|
| 203 |
|
| 204 |
return {
|
|
@@ -206,6 +252,7 @@ def build_student_prompt(
|
|
| 206 |
"student_name": student_name,
|
| 207 |
"total_questions": total_questions,
|
| 208 |
"wrong_count": len(wrong_answers),
|
|
|
|
| 209 |
}
|
| 210 |
|
| 211 |
|
|
@@ -233,13 +280,13 @@ async def generate_student_report(
|
|
| 233 |
client = AsyncOpenAI(api_key=settings.openai_api_key)
|
| 234 |
|
| 235 |
total_questions = len(questions_data.get("questions", []))
|
| 236 |
-
wrong_questions, wrong_answers, relevant_teacher = filter_student_wrong_answers(
|
| 237 |
student, questions_data, teacher_answers_data,
|
| 238 |
)
|
| 239 |
|
| 240 |
student_name = student.get("name", "Unknown")
|
| 241 |
|
| 242 |
-
if not wrong_answers:
|
| 243 |
# All correct — return a simple congratulations page
|
| 244 |
return f"""<!DOCTYPE html>
|
| 245 |
<html lang="zh-TW"><head><meta charset="UTF-8"><title>{student_name} - 分析報告</title>
|
|
@@ -248,13 +295,9 @@ async def generate_student_report(
|
|
| 248 |
h1{{color:#4ecdc4;margin-bottom:1rem}}p{{color:#94a3b8;font-size:1.1rem}}</style></head>
|
| 249 |
<body><div class="card"><h1>🎉 全部答對!</h1><p><strong>{student_name}</strong> 在本次考試中答對了全部 {total_questions} 題,表現優異!</p></div></body></html>"""
|
| 250 |
|
| 251 |
-
filled_prompt =
|
| 252 |
-
student_name
|
| 253 |
-
|
| 254 |
-
wrong_count=len(wrong_answers),
|
| 255 |
-
wrong_questions=json.dumps({"questions": wrong_questions}, ensure_ascii=False, indent=2),
|
| 256 |
-
wrong_answers=json.dumps(wrong_answers, ensure_ascii=False, indent=2),
|
| 257 |
-
teacher_answers=json.dumps(relevant_teacher, ensure_ascii=False, indent=2),
|
| 258 |
)
|
| 259 |
|
| 260 |
prompt_tokens = _estimate_tokens(STUDENT_REPORT_SYSTEM_PROMPT) + _estimate_tokens(filled_prompt)
|
|
|
|
| 51 |
student: dict,
|
| 52 |
questions_data: dict,
|
| 53 |
teacher_answers_data: dict,
|
| 54 |
+
) -> tuple[list[dict], list[dict], list[dict], bool]:
|
| 55 |
"""Compare a student's answers with teacher answers, return wrong items.
|
| 56 |
|
| 57 |
+
Returns (wrong_questions, wrong_student_answers, relevant_teacher_answers, has_teacher_answers).
|
| 58 |
+
When teacher answers are missing, returns ALL student answers & questions instead.
|
| 59 |
"""
|
| 60 |
teacher_map: dict[int, dict] = {}
|
| 61 |
for ta in teacher_answers_data.get("answers", []):
|
|
|
|
| 69 |
if num is not None:
|
| 70 |
question_map[int(num)] = q
|
| 71 |
|
| 72 |
+
# No teacher answers → return ALL student answers for LLM to analyze
|
| 73 |
+
if not teacher_map:
|
| 74 |
+
all_questions = questions_data.get("questions", [])
|
| 75 |
+
all_answers = student.get("answers", [])
|
| 76 |
+
return all_questions, all_answers, [], False
|
| 77 |
+
|
| 78 |
wrong_questions: list[dict] = []
|
| 79 |
wrong_answers: list[dict] = []
|
| 80 |
relevant_teacher: list[dict] = []
|
|
|
|
| 98 |
if q:
|
| 99 |
wrong_questions.append(q)
|
| 100 |
|
| 101 |
+
return wrong_questions, wrong_answers, relevant_teacher, True
|
| 102 |
|
| 103 |
|
| 104 |
STUDENT_REPORT_SYSTEM_PROMPT = """You are ClassLens, an AI teaching assistant that creates a focused, personalized exam analysis report for a single student.
|
|
|
|
| 180 |
請針對每一道錯題提供詳細分析,找出錯誤模式,並給出具體的學習建議。"""
|
| 181 |
|
| 182 |
|
| 183 |
+
STUDENT_REPORT_PROMPT_NO_KEY = """請為以下學生生成個人化的考試分析報告。
|
| 184 |
+
|
| 185 |
+
## 學生姓名:{student_name}
|
| 186 |
+
|
| 187 |
+
## 考試總題數:{total_questions}
|
| 188 |
+
|
| 189 |
+
## 考試題目:
|
| 190 |
+
{wrong_questions}
|
| 191 |
+
|
| 192 |
+
## 該學生的全部答案:
|
| 193 |
+
{wrong_answers}
|
| 194 |
+
|
| 195 |
+
⚠️ 注意:未提供標準答案,請根據題目內容與學生答案進行分析。
|
| 196 |
+
請嘗試判斷答案的正確性,找出可能的錯誤,並給出具體的學習建議。"""
|
| 197 |
+
|
| 198 |
+
|
| 199 |
def get_default_prompt() -> str:
|
| 200 |
return STUDENT_REPORT_PROMPT
|
| 201 |
|
| 202 |
|
| 203 |
+
def _fill_student_prompt(
|
| 204 |
+
student_name: str,
|
| 205 |
+
total_questions: int,
|
| 206 |
+
wrong_questions: list[dict],
|
| 207 |
+
wrong_answers: list[dict],
|
| 208 |
+
relevant_teacher: list[dict],
|
| 209 |
+
has_teacher_answers: bool,
|
| 210 |
+
) -> str:
|
| 211 |
+
"""Fill the appropriate prompt template for a student."""
|
| 212 |
+
if has_teacher_answers:
|
| 213 |
+
return STUDENT_REPORT_PROMPT.format(
|
| 214 |
+
student_name=student_name,
|
| 215 |
+
total_questions=total_questions,
|
| 216 |
+
wrong_count=len(wrong_answers),
|
| 217 |
+
wrong_questions=json.dumps({"questions": wrong_questions}, ensure_ascii=False, indent=2),
|
| 218 |
+
wrong_answers=json.dumps(wrong_answers, ensure_ascii=False, indent=2),
|
| 219 |
+
teacher_answers=json.dumps(relevant_teacher, ensure_ascii=False, indent=2),
|
| 220 |
+
)
|
| 221 |
+
else:
|
| 222 |
+
return STUDENT_REPORT_PROMPT_NO_KEY.format(
|
| 223 |
+
student_name=student_name,
|
| 224 |
+
total_questions=total_questions,
|
| 225 |
+
wrong_questions=json.dumps({"questions": wrong_questions}, ensure_ascii=False, indent=2),
|
| 226 |
+
wrong_answers=json.dumps(wrong_answers, ensure_ascii=False, indent=2),
|
| 227 |
+
)
|
| 228 |
+
|
| 229 |
+
|
| 230 |
def build_student_prompt(
|
| 231 |
student: dict,
|
| 232 |
questions_data: dict,
|
|
|
|
| 234 |
) -> dict:
|
| 235 |
"""Build the filled prompt for a student (for preview).
|
| 236 |
|
| 237 |
+
Returns dict with filled_prompt, student_name, total_questions, wrong_count, has_teacher_answers.
|
| 238 |
"""
|
| 239 |
total_questions = len(questions_data.get("questions", []))
|
| 240 |
+
wrong_questions, wrong_answers, relevant_teacher, has_teacher = filter_student_wrong_answers(
|
| 241 |
student, questions_data, teacher_answers_data,
|
| 242 |
)
|
| 243 |
student_name = student.get("name", "Unknown")
|
| 244 |
|
| 245 |
+
filled = _fill_student_prompt(
|
| 246 |
+
student_name, total_questions,
|
| 247 |
+
wrong_questions, wrong_answers, relevant_teacher, has_teacher,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 248 |
)
|
| 249 |
|
| 250 |
return {
|
|
|
|
| 252 |
"student_name": student_name,
|
| 253 |
"total_questions": total_questions,
|
| 254 |
"wrong_count": len(wrong_answers),
|
| 255 |
+
"has_teacher_answers": has_teacher,
|
| 256 |
}
|
| 257 |
|
| 258 |
|
|
|
|
| 280 |
client = AsyncOpenAI(api_key=settings.openai_api_key)
|
| 281 |
|
| 282 |
total_questions = len(questions_data.get("questions", []))
|
| 283 |
+
wrong_questions, wrong_answers, relevant_teacher, has_teacher = filter_student_wrong_answers(
|
| 284 |
student, questions_data, teacher_answers_data,
|
| 285 |
)
|
| 286 |
|
| 287 |
student_name = student.get("name", "Unknown")
|
| 288 |
|
| 289 |
+
if has_teacher and not wrong_answers:
|
| 290 |
# All correct — return a simple congratulations page
|
| 291 |
return f"""<!DOCTYPE html>
|
| 292 |
<html lang="zh-TW"><head><meta charset="UTF-8"><title>{student_name} - 分析報告</title>
|
|
|
|
| 295 |
h1{{color:#4ecdc4;margin-bottom:1rem}}p{{color:#94a3b8;font-size:1.1rem}}</style></head>
|
| 296 |
<body><div class="card"><h1>🎉 全部答對!</h1><p><strong>{student_name}</strong> 在本次考試中答對了全部 {total_questions} 題,表現優異!</p></div></body></html>"""
|
| 297 |
|
| 298 |
+
filled_prompt = _fill_student_prompt(
|
| 299 |
+
student_name, total_questions,
|
| 300 |
+
wrong_questions, wrong_answers, relevant_teacher, has_teacher,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
)
|
| 302 |
|
| 303 |
prompt_tokens = _estimate_tokens(STUDENT_REPORT_SYSTEM_PROMPT) + _estimate_tokens(filled_prompt)
|