精简后端功能模块并补充教学互动
This commit is contained in:
@@ -58,6 +58,162 @@ class ScoringAgent:
|
||||
}
|
||||
return data
|
||||
|
||||
async def score_teaching(
|
||||
self,
|
||||
*,
|
||||
case: CaseBase,
|
||||
teaching_payload: dict,
|
||||
scoring_rules: list,
|
||||
guideline_refs: list[dict],
|
||||
score_type: str,
|
||||
) -> dict:
|
||||
"""教学互动评价:根据题目、标准答案、学生作答和评分规则生成结构化评价。"""
|
||||
start = time.perf_counter()
|
||||
messages = self._build_teaching_messages(case, teaching_payload, scoring_rules, guideline_refs, score_type)
|
||||
try:
|
||||
response = await self.llm.chat(
|
||||
messages,
|
||||
settings.llm_fast_model,
|
||||
thinking_enabled=settings.llm_fast_thinking_enabled,
|
||||
reasoning_effort=None,
|
||||
response_format={"type": "json_object"} if settings.llm_scoring_json_response else None,
|
||||
max_tokens=min(settings.llm_scoring_max_tokens, 1600),
|
||||
)
|
||||
data = json.loads(response.content)
|
||||
data = self._normalize_score_payload(data, score_type, guideline_refs)
|
||||
data["_llm_model"] = response.model
|
||||
data["_latency_metrics"] = {"scoring_latency_ms": response.latency_ms, "fallback_used": False}
|
||||
return data
|
||||
except (AppError, json.JSONDecodeError, KeyError, TypeError, ValueError) as exc:
|
||||
logger.warning("teaching_scoring_agent.fallback case_id=%s error=%s", case.id, exc.__class__.__name__)
|
||||
data = self._fallback_teaching_score(score_type, guideline_refs, teaching_payload)
|
||||
data["_llm_model"] = f"local-fallback-{settings.llm_fast_model}"
|
||||
data["_latency_metrics"] = {
|
||||
"scoring_latency_ms": int((time.perf_counter() - start) * 1000),
|
||||
"fallback_used": True,
|
||||
"fallback_reason": exc.__class__.__name__,
|
||||
}
|
||||
return data
|
||||
|
||||
def _build_teaching_messages(
|
||||
self,
|
||||
case: CaseBase,
|
||||
teaching_payload: dict,
|
||||
scoring_rules: list,
|
||||
guideline_refs: list[dict],
|
||||
score_type: str,
|
||||
) -> list[dict]:
|
||||
"""教学评分提示词:只传教学互动评价需要的病例、题目、答案和评分规则。"""
|
||||
payload = {
|
||||
"score_type": score_type,
|
||||
"case": {
|
||||
"case_id": case.id,
|
||||
"title": case.title,
|
||||
"chief_complaint": case.chief_complaint,
|
||||
"description": self._truncate(case.description, 320),
|
||||
"knowledge_points": case.knowledge_points or [],
|
||||
"key_points": case.key_points or [],
|
||||
},
|
||||
"teaching": teaching_payload,
|
||||
"scoring_rules": self._compact_scoring_rules(scoring_rules),
|
||||
"guidelines": self._compact_guidelines(guideline_refs),
|
||||
}
|
||||
system = (
|
||||
"你是医学教学互动评价专家,只输出合法 JSON。"
|
||||
"请根据病例、教学目标、选择题、标准答案、解析文本、学生作答和评分规则生成教学评价。"
|
||||
"输出字段固定为 score_type,total_score,dimension_scores,errors,improvement_plan,"
|
||||
"evidence_summary,guideline_refs,overall_comment,score_details。"
|
||||
"dimension_scores 包含 知识掌握、临床推理、检查理解、治疗决策、人文沟通 维度,"
|
||||
"每项包含 dimension,score,max_score,comment,evidence,deductions,improvement。"
|
||||
"score_details 对应 scoring_rules 或题目维度,每项包含 rule_id,dimension,score,"
|
||||
"deducted_reason,evidence_message_ids,ai_confidence,comment。"
|
||||
"必须指出答对题目、答错题目、错误原因、下一步学习重点。"
|
||||
"本评价仅用于医学教学训练,不替代真实临床诊疗。"
|
||||
)
|
||||
return [{"role": "system", "content": system}, {"role": "user", "content": json.dumps(payload, ensure_ascii=False)}]
|
||||
|
||||
def _fallback_teaching_score(self, score_type: str, guideline_refs: list[dict], teaching_payload: dict) -> dict:
|
||||
"""教学评分兜底:LLM 不可用时按选择题正确率生成稳定评价结构。"""
|
||||
results = teaching_payload.get("answer_results") or []
|
||||
total = len(results)
|
||||
correct = sum(1 for item in results if item.get("is_correct"))
|
||||
accuracy = correct / total if total else 0
|
||||
total_score = round(accuracy * 100, 1) if total else 0
|
||||
incorrect = [item for item in results if not item.get("is_correct")]
|
||||
incorrect_titles = [f"{item.get('question_id')}: {item.get('stem', '')}" for item in incorrect[:5]]
|
||||
data = {
|
||||
"score_type": "percentage",
|
||||
"total_score": total_score,
|
||||
"dimension_scores": [
|
||||
{
|
||||
"dimension": "知识掌握",
|
||||
"score": round(total_score * 0.35, 1),
|
||||
"max_score": 35,
|
||||
"comment": f"共 {total} 题,答对 {correct} 题。",
|
||||
"evidence": [f"正确率 {round(accuracy * 100, 1)}%"],
|
||||
"deductions": incorrect_titles,
|
||||
"improvement": "复习错题对应知识点和病例解析。",
|
||||
},
|
||||
{
|
||||
"dimension": "临床推理",
|
||||
"score": round(total_score * 0.25, 1),
|
||||
"max_score": 25,
|
||||
"comment": "根据选择题表现评估临床判断链路。",
|
||||
"evidence": [item.get("stem", "") for item in results[:3]],
|
||||
"deductions": incorrect_titles,
|
||||
"improvement": "把题目选项与病例主诉、体征和检查结果逐项对应。",
|
||||
},
|
||||
{
|
||||
"dimension": "检查理解",
|
||||
"score": round(total_score * 0.15, 1),
|
||||
"max_score": 15,
|
||||
"comment": "重点关注检查项目与病情严重程度判断。",
|
||||
"evidence": teaching_payload.get("scoring_focus", "").split("、")[:3],
|
||||
"deductions": [],
|
||||
"improvement": "理解血氧、胸片和炎症指标在肺炎评估中的作用。",
|
||||
},
|
||||
{
|
||||
"dimension": "治疗决策",
|
||||
"score": round(total_score * 0.15, 1),
|
||||
"max_score": 15,
|
||||
"comment": "根据题目表现评估治疗原则掌握情况。",
|
||||
"evidence": teaching_payload.get("teaching_goal", "").split("、")[:3],
|
||||
"deductions": [],
|
||||
"improvement": "复习抗感染、平喘、氧合监测和风险预案。",
|
||||
},
|
||||
{
|
||||
"dimension": "人文沟通",
|
||||
"score": round(total_score * 0.10, 1),
|
||||
"max_score": 10,
|
||||
"comment": "教学互动中需继续强化家属沟通和健康教育。",
|
||||
"evidence": ["教学互动题包含沟通与健康教育相关内容。"],
|
||||
"deductions": [],
|
||||
"improvement": "向家属说明病情、观察指标、复诊指征和用药注意事项。",
|
||||
},
|
||||
],
|
||||
"score_details": [],
|
||||
"errors": [
|
||||
{
|
||||
"title": "教学题目答题错误",
|
||||
"description": ";".join(incorrect_titles) if incorrect_titles else "暂无明显错题。",
|
||||
"severity": "medium" if incorrect_titles else "low",
|
||||
"related_dimension": "知识掌握",
|
||||
}
|
||||
],
|
||||
"improvement_plan": [
|
||||
"复盘错题解析,明确每个选项与病例证据的对应关系。",
|
||||
"把病例中的主诉、体征、检查和治疗原则整理成一条临床推理链。",
|
||||
"针对血氧、胸片、炎症指标和医患沟通进行专项复习。",
|
||||
],
|
||||
"evidence_summary": [
|
||||
f"教学互动共提交 {total} 题,答对 {correct} 题。",
|
||||
"评分依据包括题目标准答案、解析文本、教学目标和评分规则。",
|
||||
],
|
||||
"guideline_refs": guideline_refs,
|
||||
"overall_comment": f"本次教学互动正确率为 {round(accuracy * 100, 1)}%,请结合错题解析继续巩固病例关键知识点。",
|
||||
}
|
||||
return self._convert_to_five_point(data) if score_type == "five_point" else data
|
||||
|
||||
def _build_messages(
|
||||
self,
|
||||
session: TrainingSession,
|
||||
|
||||
Reference in New Issue
Block a user