精简后端功能模块并补充教学互动

This commit is contained in:
刘金宝
2026-06-08 16:49:45 +08:00
parent 11b1712b01
commit f0cdc454b3
18 changed files with 1120 additions and 1194 deletions
+19
View File
@@ -57,6 +57,25 @@ class MedicalConsultationOrchestrator:
)
return self.report_agent.build_report(scoring_result)
async def evaluate_teaching(
self,
*,
case: CaseBase,
teaching_payload: dict,
scoring_rules: list,
guideline_refs: list[dict],
score_type: str,
) -> dict:
"""教学互动评价编排:调用 Scoring Agent 后复用 Report Agent 整理报告结构。"""
scoring_result = await self.scoring_agent.score_teaching(
case=case,
teaching_payload=teaching_payload,
scoring_rules=scoring_rules,
guideline_refs=guideline_refs,
score_type=score_type,
)
return self.report_agent.build_report(scoring_result)
async def generate_hints(
self,
session: TrainingSession,
+156
View File
@@ -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,