diff --git a/Dockerfile b/Dockerfile index f318262..b79dd04 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -# 使用阿里云镜像的Python基础镜像 +# 使用Python基础镜像 FROM python:3.12-slim # 设置工作目录 diff --git a/docker-compose.yml b/docker-compose.yml index 18ff335..707392e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,6 +11,7 @@ services: environment: - TZ=Asia/Shanghai - PYTHONUNBUFFERED=1 + - GLM_API_KEY=847c627b63db4590a1f3932ff68e15f3.SUFeU6RcjERWQxUq restart: unless-stopped networks: - survey-network diff --git a/enhanced_survey_system.py b/enhanced_survey_system.py index 5e700a5..aa4b60d 100644 --- a/enhanced_survey_system.py +++ b/enhanced_survey_system.py @@ -4,13 +4,13 @@ import sqlite3 import json import uuid -import requests import os import time from datetime import datetime, timezone, timedelta from http.server import HTTPServer, BaseHTTPRequestHandler from urllib.parse import urlparse, parse_qs import threading +from zai import ZhipuAiClient def get_east8_time(): """获取东八区时间""" @@ -37,29 +37,25 @@ def load_env_config(): print(f"加载环境变量失败: {e}") class ReportGenerator: - """报告生成器 - 调用自定义API生成测评报告""" + """报告生成器 - 调用GLM大语言模型API生成测评报告""" def __init__(self): # 加载环境变量 load_env_config() - # 自定义API配置 - self.api_url = os.getenv("REPORT_API_URL", "http://120.26.23.172:5678/webhook/survey_report") - self.api_key = os.getenv("REPORT_API_KEY", "") - self.timeout = int(os.getenv("REPORT_API_TIMEOUT", "300")) - self.prompt_file = "/Users/moshui/Documents/survey/public/prompt.txt" + # GLM API配置 + self.api_key = os.getenv("GLM_API_KEY", "") + self.timeout = int(os.getenv("LLM_API_TIMEOUT", "300")) + self.model_name = "glm-4.5-air" + self.prompt_file = "./public/prompt.md" - # 请求头 - self.headers = { - "Content-Type": "application/json" - } - - # 添加API密钥(如果设置了) - if self.api_key: - self.headers["Authorization"] = f"Bearer {self.api_key}" + # 初始化 Zhipu AI 客户端 + self.client = ZhipuAiClient(api_key=self.api_key) print(f"报告生成器配置:") - print(f" - API端点: {self.api_url}") + print(f" - SDK: ZhipuAiClient") + print(f" - 模型: {self.model_name}") + print(f" - api_key: {self.api_key}") print(f" - 超时时间: {self.timeout}秒") def load_prompt(self): @@ -181,23 +177,43 @@ class ReportGenerator: # 获取会话数据 analysis_data = self.generate_analysis_text({'id': session_id}) - # 调用自定义API生成报告 - # 构造回调URL + # 调用GLM API生成报告 report_result = self.call_report_api(analysis_data, session_id) if report_result and report_result.get('success'): - # API调用成功,异步生成中 - # 保存分析数据用于后续回调处理 - self.save_analysis_data_for_regeneration(session_id, analysis_data) + # API调用成功,直接生成报告 + # 构造完整的报告数据 + student_info = analysis_data.get('student_info', {}) + + # 构造报告数据 + complete_report_data = { + 'studentInfo': { + 'name': student_info.get('name', '未知'), + 'school': student_info.get('school', '未知'), + 'grade': student_info.get('grade', '未知'), + 'subject': '科学', + 'testDate': get_east8_time().strftime('%Y年%m月%d日') + }, + 'report': report_result['report_data'], + 'generated_at': get_east8_time().isoformat(), + 'session_id': session_id, + 'analysis_data': analysis_data, + 'is_direct_generation': True + } + + # 保存报告到数据库 + report_id = self.save_report_to_db(session_id, complete_report_data, analysis_data) return { 'success': True, - 'message': '报告异步生成中', + 'message': '报告生成成功', 'session_id': session_id, + 'report_id': report_id, + 'report_data': complete_report_data, 'analysis_data': analysis_data } else: - raise Exception("API调用失败") + raise Exception("GLM API调用失败") except Exception as e: print(f"生成报告失败: {e}") @@ -212,41 +228,73 @@ class ReportGenerator: } def call_report_api(self, analysis_data, session_id): - """调用自定义报告API生成报告""" - # 构建请求数据 - request_data = { - "analysis_text": analysis_data['analysis_text'], - "session_id": session_id - } - - # 实现重试机制 - max_retries = 3 - retry_delay = 2 # 秒 + """调用GLM大语言模型API生成报告""" + # 加载提示词 + prompt = self.load_prompt() try: - print(f"调用报告生成API...") - print(f" API端点: {self.api_url}") + print(f"调用GLM大语言模型SDK...") print(f" Session ID: {session_id}") - print(f" 数据长度: {len(request_data['analysis_text'])} 字符") + print(f" 模型: {self.model_name}") - response = requests.post( - self.api_url, - headers=self.headers, - json=request_data, - timeout=10 # 简化超时时间 + # 使用ZhipuAiClient调用API,设置JSON格式响应 + response = self.client.chat.completions.create( + model=self.model_name, + messages=[ + { + "role": "system", + "content": prompt + }, + { + "role": "user", + "content": analysis_data['analysis_text'] + } + ], + response_format={ + "type": "json_object" + } ) - if response.status_code == 200: - result = response.json() - print(f"✅ API调用成功,已提交异步报告生成请求") - print(f" 响应内容: {str(result)[:200]}...") - return {"success": True, "message": "异步报告生成请求已提交"} - else: - raise Exception(f"API调用失败: HTTP {response.status_code} - {response.text}") - + print(f"✅ GLM SDK调用成功") + print(f" 响应长度: {len(response.choices[0].message.content)} 字符") + + # 解析JSON响应 + model_response = response.choices[0].message.content + parsed_report = self.parse_json_response(model_response) + + return { + "success": True, + "message": "报告生成成功", + "report_data": parsed_report, + "raw_response": model_response + } + except Exception as e: - print(f"API调用出错: {e}") - raise Exception(f"报告生成API调用失败: {str(e)}") + print(f"GLM SDK调用出错: {e}") + raise Exception(f"大语言模型SDK调用失败: {str(e)}") + + def parse_json_response(self, json_content): + """解析JSON格式的响应字符串""" + try: + # 查找 ```json 和 ``` 之间的内容 + import re + + # 匹配 ```json ... ``` 格式 + match = re.search(r'```json\s*\n(.*?)\n```', json_content, re.DOTALL) + if match: + json_content = match.group(1) + print(f"成功提取JSON格式内容,长度: {len(json_content)} 字符") + + try: + return json.loads(json_content) + except json.JSONDecodeError as e: + print(f"JSON解析失败: {e}") + # 如果都不是,返回原始内容 + return json_content + + except Exception as e: + print(f"解析JSON响应失败: {e}") + return json_content def save_report_to_db(self, session_id, report_data, analysis_data): """保存报告到数据库""" @@ -446,22 +494,23 @@ class EnhancedSurveySystem: analysis_data = json.loads(result[0]) - # 调用API生成报告 + # 调用GLM API生成报告 report_result = self.report_generator.call_report_api(analysis_data, session_id) - if report_result: + if report_result and report_result.get('success'): # 提取学员信息用于更新报告数据 - student_info = analysis_data['student_info'] - report_result['studentInfo']['name'] = student_info.get('name', '未知') - report_result['studentInfo']['school'] = student_info.get('school', '未知') - report_result['studentInfo']['grade'] = student_info.get('grade', '未知') - report_result['studentInfo']['subject'] = '科学' - report_result['studentInfo']['testDate'] = get_east8_time().strftime('%Y年%m月%d日') + student_info = analysis_data.get('student_info', {}) # 构造报告数据 report_data = { - 'studentInfo': report_result['studentInfo'], - 'report': report_result['report'], + 'studentInfo': { + 'name': student_info.get('name', '未知'), + 'school': student_info.get('school', '未知'), + 'grade': student_info.get('grade', '未知'), + 'subject': '科学', + 'testDate': get_east8_time().strftime('%Y年%m月%d日') + }, + 'report': report_result["report_data"], 'generated_at': get_east8_time().isoformat(), 'session_id': session_id, 'analysis_data': analysis_data, @@ -481,7 +530,7 @@ class EnhancedSurveySystem: 'is_regenerated': True } else: - raise Exception("API返回空内容") + raise Exception("GLM API返回空内容或失败") except Exception as e: print(f"重新生成报告失败: {e}") diff --git a/input.json b/input.json new file mode 100644 index 0000000..9c0ad90 --- /dev/null +++ b/input.json @@ -0,0 +1,250 @@ +{ + "summaryData": { + "totalScore": 88, + "level": "优秀", + "scoreRate": "88%", + "scoreRateDescription": "班级前15%", + "groupPosition": "85%", + "groupPositionDescription": "超越同龄学生", + "summary": "李欣彦对科学现象充满好奇,基础知识记忆牢固,若能提升在复杂情境中的推理判断能力,科学成绩会更上一层楼!" + }, + "radarData": { + "title": "五维能力雷达图", + "abilities": [ + { + "name": "知识记忆与识别", + "value": 92 + }, + { + "name": "信息提取与分析", + "value": 85 + }, + { + "name": "科学推理与判断", + "value": 78 + }, + { + "name": "生活应用与关联", + "value": 70 + }, + { + "name": "探究思维与甄别", + "value": 75 + } + ] + }, + "errorAnalysis": { + "title": "核心诊断:错题归因与深度解析", + "errors": [ + { + "questionNumber": "第5题", + "questionTitle": "种子萌发必需的条件", + "errorType": "经验干扰型", + "corePoint": "种子萌发的必要条件", + "wrongOption": "C. 土壤", + "correctOption": "A. 水、空气、适宜温度", + "analysis": "你选择了C\"土壤\",这是一个典型的【经验干扰型】错误。因为我们平时种花都在土里,所以容易认为土壤是必需的。但科学实验证明,种子萌发必需的条件是水、空气和适宜的温度。", + "guidance": "审题关键点:抓住\"必需\"二字,意思是\"缺一不可\"。关联知识点:回忆\"种子萌发条件\"的对照实验,土壤不是必要条件(如豆芽发芽)。思路构建:运用排除法,思考如果没有土壤种子是否能萌发(能),如果没有水呢?(不能)。" + }, + { + "questionNumber": "第19题", + "questionTitle": "潜望镜中的光传播", + "errorType": "推理缺失型", + "corePoint": "潜望镜中光的传播路径", + "wrongOption": "直接看到水面舰艇", + "correctOption": "舰艇的反射光→潜望镜平面镜反射→人眼", + "analysis": "这是一个【推理缺失型】错误,你缺少了对光传播路径的完整推理步骤。凭感觉认为能看到,但实际上需要理解潜望镜的原理是两次反射。","guidance": "推理关键点:光在同种均匀介质中沿直线传播。思考路径:舰艇反射的光→第一块平面镜反射→第二块平面镜反射→人眼。建议画图理解光路。" + } + ], + "tagLibrary": { + "title": "错题归因标签库", + "tags": [ + { + "emoji": "🔍", + "name": "概念混淆型", + "description": "对两个或多个相似概念区分不清,如\"蒸发\"与\"沸腾\"、\"反射\"与\"折射\"等。" + }, + { + "emoji": "📋", + "name": "审题偏差型", + "description": "未能抓住题干中的限定词(如\"不正确\"、\"主要目的\"、\"必需条件\"等)。" + }, + { + "emoji": "🧩", + "name": "推理缺失型", + "description": "缺少必要的逻辑推理步骤,凭感觉选择,尤其在复杂现象分析中。" + }, + { + "emoji": "🌍", + "name": "经验干扰型", + "description": "被生活表象或片面经验误导,未遵循科学原理,如认为土壤是种子萌发的必需条件。" + }, + { + "emoji": "📊", + "name": "信息忽略型", + "description": "忽略了图表、表格或题干中提供的关键信息,如实验数据中的变化趋势。" + }, + { + "emoji": "🕳️", + "name": "知识漏洞型", + "description": "对考查的基础知识点完全不知或记忆错误,需要重新学习相关概念。" + } + ] + } + }, + "knowledgeAnalysis": { + "title": "深度剖析:知识体系掌握度分析", + "chapters": [ + { + "type": "excellent", + "title": "🌟 优势章节", + "score": "92%", + "description": "光的直线传播、折射与色散", + "note": "概念识别准确,应用能力强" + }, + { + "type": "good", + "title": "📈 巩固章节", + "score": "78%", + "description": "光的反射、光源与透光性", + "note": "部分易混概念需厘清" + }, + { + "type": "needs-work", + "title": "🎯 重点补救章节", + "score": "65%", + "description": "复杂光现象综合应用", + "note": "核心原理理解尚不牢固" + } + ] + }, + "cognitiveAnalysis": { + "title": "认知能力维度评估", + "dimensions": [ + { + "emoji": "🔍", + "name": "信息捕捉细致度", + "score": "82%", + "description": "审题偏差和信息忽略错误较少" + }, + { + "emoji": "🧠", + "name": "概念清晰度", + "score": "78%", + "description": "部分相似概念需要强化区分" + }, + { + "emoji": "🔗", + "name": "逻辑推理严谨性", + "score": "70%", + "description": "推理步骤需要更完整" + }, + { + "emoji": "🌐", + "name": "知识迁移灵活性", + "score": "75%", + "description": "新情境应用能力有待提升" + }, + { + "emoji": "🛡️", + "name": "抗干扰能力", + "score": "68%", + "description": "易受生活经验干扰" + } + ], + "behaviorInsight": { + "title": "🎯 学习行为推测", + "content": "从你常犯【经验干扰型】和【推理缺失型】错误来看,你在做题时可能习惯于\"凭感觉\"或\"想当然\"。建议养成\"每选一个答案,都问自己一句'为什么'\"的习惯,让选择有据可依。" + } + }, + "learningPlan": { + "title": "行动指南:个性化学习规划", + "plans": [ + { + "type": "short-term", + "emoji": "🔥", + "title": "短期攻坚计划(2-4周)", + "goal": "堵塞核心知识漏洞,纠正易混概念", + "sections": [ + { + "title": "重点章节", + "items": [ + "复杂光现象综合应用", + "光的反射与透光性区分" + ] + }, + { + "title": "建议资源", + "items": [ + "教科版配套动画视频\"光的世界\"", + "《光学小实验》家庭实验套装" + ] + }, + { + "title": "练习任务", + "items": [ + "完成《光学选择题专项练习》10道", + "重点分析每一个选项的对错原因", + "建立错题本,记录错误类型和改进方法" + ] + } + ] + }, + { + "type": "mid-term", + "emoji": "📈", + "title": "中期提升策略(1-3个月)", + "goal": "强化科学推理能力,建立\"选项分析\"思维", + "sections": [ + { + "title": "能力训练", + "items": [ + "\"选项辨析\"练习:针对错题,说出其他选项为什么错", + "\"讲题\"小老师:给父母讲解解题思路" + ] + }, + { + "title": "学习方法", + "items": [ + "建立\"概念辨析本\":记录易混淆概念组", + "配合图示或例子加深理解", + "每周末回顾本周错题,总结规律" + ] + } + ] + }, + { + "type": "long-term", + "emoji": "🌟", + "title": "长期发展建议(持续培养)", + "goal": "培养科学探究兴趣和批判性思维", + "sections": [ + { + "title": "阅读拓展", + "items": [ + "《神奇校车》《科学朗读者》等科普读物", + "《十万个为什么》光学部分" + ] + }, + { + "title": "实践探索", + "items": [ + "多提问\"这有什么科学道理?\"","通过查资料、做小实验验证猜想", + "参观科技馆,参与科学实验活动" + ] + } + ] + } + ], + "parentAdvice": { + "emoji": "👨\u200d👩\u200d👧", + "title": "给家长的建议", + "advice": [ + "在孩子分析题目时,多问一句:\"你觉得出题人为什么设置这个错误选项呢?\" 这能引导孩子洞察题目陷阱,从\"答题者\"转变为\"思考者\"", + "多鼓励孩子的探索行为,保护他们的好奇心,即使实验\"失败\"也要肯定探究过程", + "创造家庭科学氛围,一起观看科学纪录片,讨论生活中的科学现象", + "帮助孩子建立错题分析习惯,但避免过度关注分数,重视思维过程" + ] + } + } +} diff --git a/output.toon b/output.toon new file mode 100644 index 0000000..9c9a5a7 --- /dev/null +++ b/output.toon @@ -0,0 +1,88 @@ +studentInfo: + name: 李欣彦 + subject: 小学科学(教科版·五年级) + school: 尚逸实验小学 +report: + summaryData: + totalScore: 88 + level: 优秀 + scoreRate: 88% + scoreRateDescription: 班级前15% + groupPosition: 85% + groupPositionDescription: 超越同龄学生 + summary: 李欣彦对科学现象充满好奇,基础知识记忆牢固,若能提升在复杂情境中的推理判断能力,科学成绩会更上一层楼! + radarData: + title: 五维能力雷达图 + abilities[5]{name,value}: + 知识记忆与识别,92 + 信息提取与分析,85 + 科学推理与判断,78 + 生活应用与关联,70 + 探究思维与甄别,75 + errorAnalysis: + title: 核心诊断:错题归因与深度解析 + errors[2]{questionNumber,questionTitle,errorType,corePoint,wrongOption,correctOption,analysis,guidance}: + 第5题,种子萌发必需的条件,经验干扰型,种子萌发的必要条件,C. 土壤,A. 水、空气、适宜温度,"你选择了C\"土壤\",这是一个典型的【经验干扰型】错误。因为我们平时种花都在土里,所以容易认为土壤是必需的。但科学实验证明,种子萌发必需的条件是水、空气和适宜的温度。","审题关键点:抓住\"必需\"二字,意思是\"缺一不可\"。关联知识点:回忆\"种子萌发条件\"的对照实验,土壤不是必要条件(如豆芽发芽)。思路构建:运用排除法,思考如果没有土壤种子是否能萌发(能),如果没有水呢?(不能)。" + 第19题,潜望镜中的光传播,推理缺失型,潜望镜中光的传播路径,直接看到水面舰艇,舰艇的反射光→潜望镜平面镜反射→人眼,这是一个【推理缺失型】错误,你缺少了对光传播路径的完整推理步骤。凭感觉认为能看到,但实际上需要理解潜望镜的原理是两次反射。,推理关键点:光在同种均匀介质中沿直线传播。思考路径:舰艇反射的光→第一块平面镜反射→第二块平面镜反射→人眼。建议画图理解光路。 + tagLibrary: + title: 错题归因标签库 + tags[6]{emoji,name,description}: + 🔍,概念混淆型,"对两个或多个相似概念区分不清,如\"蒸发\"与\"沸腾\"、\"反射\"与\"折射\"等。" + 📋,审题偏差型,"未能抓住题干中的限定词(如\"不正确\"、\"主要目的\"、\"必需条件\"等)。" + 🧩,推理缺失型,缺少必要的逻辑推理步骤,凭感觉选择,尤其在复杂现象分析中。 + 🌍,经验干扰型,被生活表象或片面经验误导,未遵循科学原理,如认为土壤是种子萌发的必需条件。 + 📊,信息忽略型,忽略了图表、表格或题干中提供的关键信息,如实验数据中的变化趋势。 + 🕳️,知识漏洞型,对考查的基础知识点完全不知或记忆错误,需要重新学习相关概念。 + knowledgeAnalysis: + title: 深度剖析:知识体系掌握度分析 + chapters[3]{type,title,score,description,note}: + excellent,🌟 优势章节,92%,光的直线传播、折射与色散,概念识别准确,应用能力强 + good,📈 巩固章节,78%,光的反射、光源与透光性,部分易混概念需厘清 + needs-work,🎯 重点补救章节,65%,复杂光现象综合应用,核心原理理解尚不牢固 + cognitiveAnalysis: + title: 认知能力维度评估 + dimensions[5]{emoji,name,score,description}: + 🔍,信息捕捉细致度,82%,审题偏差和信息忽略错误较少 + 🧠,概念清晰度,78%,部分相似概念需要强化区分 + 🔗,逻辑推理严谨性,70%,推理步骤需要更完整 + 🌐,知识迁移灵活性,75%,新情境应用能力有待提升 + 🛡️,抗干扰能力,68%,易受生活经验干扰 + behaviorInsight: + title: 🎯 学习行为推测 + content: "从你常犯【经验干扰型】和【推理缺失型】错误来看,你在做题时可能习惯于\"凭感觉\"或\"想当然\"。建议养成\"每选一个答案,都问自己一句'为什么'\"的习惯,让选择有据可依。" + learningPlan: + title: 行动指南:个性化学习规划 + plans[3]: + - type: short-term + emoji: 🔥 + title: 短期攻坚计划(2-4周) + goal: 堵塞核心知识漏洞,纠正易混概念 + sections[3]: + - title: 重点章节 + items[2]: 复杂光现象综合应用,光的反射与透光性区分 + - title: 建议资源 + items[2]: "教科版配套动画视频\"光的世界\"",《光学小实验》家庭实验套装 + - title: 练习任务 + items[3]: 完成《光学选择题专项练习》10道,重点分析每一个选项的对错原因,建立错题本,记录错误类型和改进方法 + - type: mid-term + emoji: 📈 + title: 中期提升策略(1-3个月) + goal: "强化科学推理能力,建立\"选项分析\"思维" + sections[2]: + - title: 能力训练 + items[2]: "\"选项辨析\"练习:针对错题,说出其他选项为什么错","\"讲题\"小老师:给父母讲解解题思路" + - title: 学习方法 + items[3]: "建立\"概念辨析本\":记录易混淆概念组",配合图示或例子加深理解,每周末回顾本周错题,总结规律 + - type: long-term + emoji: 🌟 + title: 长期发展建议(持续培养) + goal: 培养科学探究兴趣和批判性思维 + sections[2]: + - title: 阅读拓展 + items[2]: 《神奇校车》《科学朗读者》等科普读物,《十万个为什么》光学部分 + - title: 实践探索 + items[3]: "多提问\"这有什么科学道理?\"",通过查资料、做小实验验证猜想,参观科技馆,参与科学实验活动 + parentAdvice: + emoji: 👨‍👩‍👧 + title: 给家长的建议 + advice[4]: "在孩子分析题目时,多问一句:\"你觉得出题人为什么设置这个错误选项呢?\" 这能引导孩子洞察题目陷阱,从\"答题者\"转变为\"思考者\"","多鼓励孩子的探索行为,保护他们的好奇心,即使实验\"失败\"也要肯定探究过程",创造家庭科学氛围,一起观看科学纪录片,讨论生活中的科学现象,帮助孩子建立错题分析习惯,但避免过度关注分数,重视思维过程 \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index 1c8a1ff..a8fe187 100644 --- a/poetry.lock +++ b/poetry.lock @@ -44,6 +44,18 @@ typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] trio = ["trio (>=0.31.0)"] +[[package]] +name = "cachetools" +version = "6.2.2" +description = "Extensible memoizing collections and decorators" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "cachetools-6.2.2-py3-none-any.whl", hash = "sha256:6c09c98183bf58560c97b2abfcedcbaf6a896a490f534b031b661d3723b45ace"}, + {file = "cachetools-6.2.2.tar.gz", hash = "sha256:8e6d266b25e539df852251cfd6f990b4bc3a141db73b939058d809ebd2590fc6"}, +] + [[package]] name = "certifi" version = "2025.10.5" @@ -254,6 +266,53 @@ files = [ {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, ] +[[package]] +name = "httpcore" +version = "1.0.9" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, + {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.16" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<1.0)"] + +[[package]] +name = "httpx" +version = "0.28.1" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, + {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" + +[package.extras] +brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +zstd = ["zstandard (>=0.18.0)"] + [[package]] name = "idna" version = "3.11" @@ -553,6 +612,24 @@ files = [ [package.dependencies] typing-extensions = ">=4.14.1" +[[package]] +name = "pyjwt" +version = "2.10.1" +description = "JSON Web Token implementation in Python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb"}, + {file = "pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953"}, +] + +[package.extras] +crypto = ["cryptography (>=3.4.0)"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] + [[package]] name = "python-multipart" version = "0.0.20" @@ -682,7 +759,26 @@ h11 = ">=0.8" [package.extras] standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.13)", "websockets (>=10.4)"] +[[package]] +name = "zai-sdk" +version = "0.0.4.2" +description = "A SDK library for accessing big model apis from Z.ai" +optional = false +python-versions = "!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*,>=3.8" +groups = ["main"] +files = [ + {file = "zai_sdk-0.0.4.2-py3-none-any.whl", hash = "sha256:3404c6ee9f721b70ccacedc0908003a800304243ff5383dd5e19bb9f535c836d"}, + {file = "zai_sdk-0.0.4.2.tar.gz", hash = "sha256:544b09d1148e024413455e28ad5ccd0b196423372c78b99b3d62b842970fc78a"}, +] + +[package.dependencies] +cachetools = ">=4.2.2" +httpx = ">=0.23.0" +pydantic = ">=1.9.0,<3.0" +pydantic-core = ">=2.14.6" +pyjwt = ">=2.9.0,<3.0.0" + [metadata] lock-version = "2.1" python-versions = ">=3.12" -content-hash = "cd040c674581fa0280c7f4fc0a8d95c4e4cea3d78f4b38f49e856ac8236f9fcf" +content-hash = "aec9932ddb7d490e012171dbfa5ea86e48b3835309c6e5815ff4943dc2eadf75" diff --git a/public/prompt.md b/public/prompt.md new file mode 100644 index 0000000..8a303c4 --- /dev/null +++ b/public/prompt.md @@ -0,0 +1,320 @@ +# 学科能力测评报告生成提示词 +## 角色定义 + +你是一位专业的教育评估分析师,擅长根据学生的考试答题数据生成详细、个性化的学科能力测评报告。你需要分析学生的答题表现,识别优势和不足,并提供针对性的学习建议。 + +## 输入数据 +你会收到学生的考试答题情况分析数据,包含以下信息: - 题目内容 - 题型(填空题、判断题、单选题、多选题等) +- 学生答案 +- 正确答案 +- 答题结果(正确/错误/未作答) + +## 任务要求 + +请基于输入的答题数据,生成一份完整的学科能力测评报告,包含以下核心部分: + +### 1. 报告基本信息 +- 学生信息:姓名、考试日期、学科、学校 +- 总体评分数据:总分、等级、得分率、排名情况 + +### 2. 五维能力雷达图 + +创建以下五个维度的能力评估(每项满分100分): +- 知识记忆与识别 +- 信息提取与分析 +- 科学推理与判断 +- 生活应用与关联 +- 探究思维与甄别 + +### 3. 错题归因分析 +针对学生的错题进行深度分析: +- 错误类型分类(概念混淆型、审题偏差型、推理缺失型、经验干扰型、信息忽略型、知识漏洞型) +- 错题详细解析(错误原因、正确思路、解题指导) +- 建立错题归因标签库 + +### 4. 知识体系掌握度分析 +按知识章节分类,展示: +- 优势章节(掌握度85%以上) +- 巩固章节(掌握度70-85%) +- 重点补救章节(掌握度70%以下) + +### 5. 认知能力维度评估 +从五个认知维度评估学生能力: +- 信息捕捉细致度 +- 概念清晰度 +- 逻辑推理严谨性 +- 知识迁移灵活性 +- 抗干扰能力 + +### 6. 学习行为推测 +基于答题模式推测学生的学习特点和习惯 +### 7. 个性化学习规划 提供分阶段的学习建议: +- 短期攻坚计划(2-4周) +- 中期提升策略(1-3个月) +- 长期发展建议(持续培养) +- 给家长的具体建议 + +## 输出格式示例 +请按照以下 JSON 格式返回分析结果: +```json +{ + "summaryData": { + "totalScore": 88, + "level": "优秀", + "scoreRate": "88%", + "scoreRateDescription": "班级前15%", + "groupPosition": "85%", + "groupPositionDescription": "超越同龄学生", + "summary": "李欣彦对科学现象充满好奇,基础知识记忆牢固,若能提升在复杂情境中的推理判断能力,科学成绩会更上一层楼!" + }, + "radarData": { + "title": "五维能力雷达图", + "abilities": [ + { + "name": "知识记忆与识别", + "value": 92 + }, + { + "name": "信息提取与分析", + "value": 85 + }, + { + "name": "科学推理与判断", + "value": 78 + }, + { + "name": "生活应用与关联", + "value": 70 + }, + { + "name": "探究思维与甄别", + "value": 75 + } + ] + }, + "errorAnalysis": { + "title": "核心诊断:错题归因与深度解析", + "errors": [ + { + "questionNumber": "第5题", + "questionTitle": "种子萌发必需的条件", + "errorType": "经验干扰型", + "corePoint": "种子萌发的必要条件", + "wrongOption": "C. 土壤", + "correctOption": "A. 水、空气、适宜温度", + "analysis": "你选择了C\"土壤\",这是一个典型的【经验干扰型】错误。因为我们平时种花都在土里,所以容易认为土壤是必需的。但科学实验证明,种子萌发必需的条件是水、空气和适宜的温度。", + "guidance": "审题关键点:抓住\"必需\"二字,意思是\"缺一不可\"。关联知识点:回忆\"种子萌发条件\"的对照实验,土壤不是必要条件(如豆芽发芽)。思路构建:运用排除法,思考如果没有土壤种子是否能萌发(能),如果没有水呢?(不能)。" + }, + { + "questionNumber": "第19题", + "questionTitle": "潜望镜中的光传播", + "errorType": "推理缺失型", + "corePoint": "潜望镜中光的传播路径", + "wrongOption": "直接看到水面舰艇", + "correctOption": "舰艇的反射光→潜望镜平面镜反射→人眼", + "analysis": "这是一个【推理缺失型】错误,你缺少了对光传播路径的完整推理步骤。凭感觉认为能看到,但实际上需要理解潜望镜的原理是两次反射。", + "guidance": "推理关键点:光在同种均匀介质中沿直线传播。思考路径:舰艇反射的光→第一块平面镜反射→第二块平面镜反射→人眼。建议画图理解光路。" + } + ], + "tagLibrary": { + "title": "错题归因标签库", + "tags": [ + { + "emoji": "🔍", + "name": "概念混淆型", + "description": "对两个或多个相似概念区分不清,如\"蒸发\"与\"沸腾\"、\"反射\"与\"折射\"等。" + }, + { + "emoji": "📋", + "name": "审题偏差型", + "description": "未能抓住题干中的限定词(如\"不正确\"、\"主要目的\"、\"必需条件\"等)。" + }, + { + "emoji": "🧩", + "name": "推理缺失型", + "description": "缺少必要的逻辑推理步骤,凭感觉选择,尤其在复杂现象分析中。" + }, + { + "emoji": "🌍", + "name": "经验干扰型", + "description": "被生活表象或片面经验误导,未遵循科学原理,如认为土壤是种子萌发的必需条件。" + }, + { + "emoji": "📊", + "name": "信息忽略型", + "description": "忽略了图表、表格或题干中提供的关键信息,如实验数据中的变化趋势。" + }, + { + "emoji": "🕳️", + "name": "知识漏洞型", + "description": "对考查的基础知识点完全不知或记忆错误,需要重新学习相关概念。" + } + ] + } + }, + "knowledgeAnalysis": { + "title": "深度剖析:知识体系掌握度分析", + "chapters": [ + { + "type": "excellent", + "title": "🌟 优势章节", + "score": "92%", + "description": "光的直线传播、折射与色散", + "note": "概念识别准确,应用能力强" + }, + { + "type": "good", + "title": "📈 巩固章节", + "score": "78%", + "description": "光的反射、光源与透光性", + "note": "部分易混概念需厘清" + }, + { + "type": "needs-work", + "title": "🎯 重点补救章节", + "score": "65%", + "description": "复杂光现象综合应用", + "note": "核心原理理解尚不牢固" + } + ] + }, + "cognitiveAnalysis": { + "title": "认知能力维度评估", + "dimensions": [ + { + "emoji": "🔍", + "name": "信息捕捉细致度", + "score": "82%", + "description": "审题偏差和信息忽略错误较少" + }, + { + "emoji": "🧠", + "name": "概念清晰度", + "score": "78%", + "description": "部分相似概念需要强化区分" + }, + { + "emoji": "🔗", + "name": "逻辑推理严谨性", + "score": "70%", + "description": "推理步骤需要更完整" + }, + { + "emoji": "🌐", + "name": "知识迁移灵活性", + "score": "75%", + "description": "新情境应用能力有待提升" + }, + { + "emoji": "🛡️", + "name": "抗干扰能力", + "score": "68%", + "description": "易受生活经验干扰" + } + ], + "behaviorInsight": { + "title": "🎯 学习行为推测", + "content": "从你常犯【经验干扰型】和【推理缺失型】错误来看,你在做题时可能习惯于\"凭感觉\"或\"想当然\"。建议养成\"每选一个答案,都问自己一句'为什么'\"的习惯,让选择有据可依。" + } + }, + "learningPlan": { + "title": "行动指南:个性化学习规划", + "plans": [ + { + "type": "short-term", + "emoji": "🔥", + "title": "短期攻坚计划(2-4周)", + "goal": "堵塞核心知识漏洞,纠正易混概念", + "sections": [ + { + "title": "重点章节", + "items": [ + "复杂光现象综合应用", + "光的反射与透光性区分" + ] + }, + { + "title": "建议资源", + "items": [ + "教科版配套动画视频\"光的世界\"", + "《光学小实验》家庭实验套装" + ] + }, + { + "title": "练习任务", + "items": [ + "完成《光学选择题专项练习》10道", + "重点分析每一个选项的对错原因", + "建立错题本,记录错误类型和改进方法" + ] + } + ] + }, + { + "type": "mid-term", + "emoji": "📈", + "title": "中期提升策略(1-3个月)", + "goal": "强化科学推理能力,建立\"选项分析\"思维", + "sections": [ + { + "title": "能力训练", + "items": [ + "\"选项辨析\"练习:针对错题,说出其他选项为什么错", + "\"讲题\"小老师:给父母讲解解题思路" + ] + }, + { + "title": "学习方法", + "items": [ + "建立\"概念辨析本\":记录易混淆概念组", + "配合图示或例子加深理解", + "每周末回顾本周错题,总结规律" + ] + } + ] + }, + { + "type": "long-term", + "emoji": "🌟", + "title": "长期发展建议(持续培养)", + "goal": "培养科学探究兴趣和批判性思维", + "sections": [ + { + "title": "阅读拓展", + "items": [ + "《神奇校车》《科学朗读者》等科普读物", + "《十万个为什么》光学部分" + ] + }, + { + "title": "实践探索", + "items": [ + "多提问\"这有什么科学道理?\"", + "通过查资料、做小实验验证猜想", + "参观科技馆,参与科学实验活动" + ] + } + ] + } + ], + "parentAdvice": { + "emoji": "👨\u200d👩\u200d👧", + "title": "给家长的建议", + "advice": [ + "在孩子分析题目时,多问一句:\"你觉得出题人为什么设置这个错误选项呢?\" 这能引导孩子洞察题目陷阱,从\"答题者\"转变为\"思考者\"", + "多鼓励孩子的探索行为,保护他们的好奇心,即使实验\"失败\"也要肯定探究过程", + "创造家庭科学氛围,一起观看科学纪录片,讨论生活中的科学现象", + "帮助孩子建立错题分析习惯,但避免过度关注分数,重视思维过程" + ] + } + } +} +``` + +报告应当: +- 数据准确,分析深入 +- 语言鼓励性强,避免负面表述 +- 建议具体可行,有针对性 +- 符合教育心理学原理 + +请基于答题数据,生成一份完整的学科能力测评报告。 diff --git a/public/prompt.txt b/public/prompt.txt deleted file mode 100644 index e2c3221..0000000 --- a/public/prompt.txt +++ /dev/null @@ -1,86 +0,0 @@ -# 学科能力测评报告生成提示词 - -## 角色定义 -你是一位专业的教育评估分析师,擅长根据学生的考试答题数据生成详细、个性化的学科能力测评报告。你需要分析学生的答题表现,识别优势和不足,并提供针对性的学习建议。 - -## 输入数据 -你会收到学生的考试答题情况分析数据,包含以下信息: -- 题目内容 -- 题型(填空题、判断题、单选题、多选题等) -- 学生答案 -- 正确答案 -- 答题结果(正确/错误/未作答) - -## 任务要求 -请基于输入的答题数据,生成一份完整的学科能力测评报告,包含以下核心部分: - -### 1. 报告基本信息 -- 学生信息:姓名、考试日期、学科、学校 -- 总体评分数据:总分、等级、得分率、排名情况 - -### 2. 五维能力雷达图 -创建以下五个维度的能力评估(每项满分100分): -- 知识记忆与识别 -- 信息提取与分析 -- 科学推理与判断 -- 生活应用与关联 -- 探究思维与甄别 - -### 3. 错题归因分析 -针对学生的错题进行深度分析: -- 错误类型分类(概念混淆型、审题偏差型、推理缺失型、经验干扰型、信息忽略型、知识漏洞型) -- 错题详细解析(错误原因、正确思路、解题指导) -- 建立错题归因标签库 - -### 4. 知识体系掌握度分析 -按知识章节分类,展示: -- 优势章节(掌握度85%以上) -- 巩固章节(掌握度70-85%) -- 重点补救章节(掌握度70%以下) - -### 5. 认知能力维度评估 -从五个认知维度评估学生能力: -- 信息捕捉细致度 -- 概念清晰度 -- 逻辑推理严谨性 -- 知识迁移灵活性 -- 抗干扰能力 - -### 6. 学习行为推测 -基于答题模式推测学生的学习特点和习惯 - -### 7. 个性化学习规划 -提供分阶段的学习建议: -- 短期攻坚计划(2-4周) -- 中期提升策略(1-3个月) -- 长期发展建议(持续培养) -- 给家长的具体建议 - -## 输出格式 -请以JSON格式输出完整的测评报告。报告应当: -- 数据准确,分析深入 -- 语言鼓励性强,避免负面表述 -- 建议具体可行,有针对性 -- 符合教育心理学原理 - -## 示例输入 -``` -# 考试答题情况分析 - -| 题目 | 题型 | 用户答案 | 正确答案 | 是否正确 | -|------|------|----------|----------|----------| -| 手机号码 | 填空题 | 15394230023 | 无标准答案 | 无法判断 | -| 判断题 | 判断题 | 对 | 对 | 正确 | -| 驾驶人在超车时, 前方车辆不减速 、不让道, 应怎样做? 单选题 | 单选题 | a.加速继续超越 | d.挤靠"加塞"车辆, 逼其离开 答案: c | 错误 | -| 这个开关控制机动车哪个部位? 单选题 | 单选题 | b.风窗玻璃刮水器 | c.危险报警闪光灯 | 错误 | -| 这个标志是何含义? 单选题 | 单选题 | c.潮汐车道 | a.双向交通 | 错误 | -| 有下列哪种违法行为的机动车驾驶人将被一次记 12 分? 单选题 | 单选题 | 未作答 | a.驾驶故意污损号牌的机动车上道路行驶 | 未作答 | -| 驾驶人的机动车驾驶证被依法扣留 、暂扣的情况下不得驾驶机动车。 判断题 | 判断题 | 未作答 | 错 | 未作答 | -| 安装防抱死制动装置 (ABS) 的机动车制动时, 制动距离会大大缩短, 因此不必保持安 全车距。 | 判断题 | 对 | 错 | 错误 | -| 驾驶机动车遇到漫水桥时要察明水情确认安全后再低速通过。 判断题 | 判断题 | 对 | 错 | 错误 | -| 灯光开关在该位置时, 前雾灯点亮。 判断题 | 判断题 | 对 | 错 | 错误 | -| 多选题 | 多选题 | 2,1 | 1 | 错误 | -``` - -请基于上述答题数据,生成一份完整的驾驶理论考试学科能力测评报告。 - diff --git a/pyproject.toml b/pyproject.toml index 24ff80b..7944e96 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,8 @@ dependencies = [ "fastapi (==0.120.1)", "uvicorn (>=0.24.0,<1.0.0)", "python-multipart (>=0.0.6,<1.0.0)", - "jinja2 (>=3.1.0,<4.0.0)" + "jinja2 (>=3.1.0,<4.0.0)", + "zai-sdk (>=0.0.4.2,<0.0.5.0)" ] diff --git a/requirements.txt b/requirements.txt index 5f7bd66..eb2da99 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,18 +1,22 @@ annotated-doc==0.0.3 annotated-types==0.7.0 anyio==4.11.0 +cachetools==6.2.2 certifi==2025.10.5 charset-normalizer==3.4.4 click==8.3.0 et_xmlfile==2.0.0 fastapi==0.120.1 h11==0.16.0 +httpcore==1.0.9 +httpx==0.28.1 idna==3.11 Jinja2==3.1.6 MarkupSafe==3.0.3 openpyxl==3.1.5 pydantic==2.12.3 pydantic_core==2.41.4 +PyJWT==2.10.1 python-multipart==0.0.20 requests==2.32.5 sniffio==1.3.1 @@ -21,3 +25,4 @@ typing-inspection==0.4.2 typing_extensions==4.15.0 urllib3==2.5.0 uvicorn==0.38.0 +zai-sdk==0.0.4.2 diff --git a/test_report_generator.py b/test_report_generator.py new file mode 100644 index 0000000..2bb2da9 --- /dev/null +++ b/test_report_generator.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +测试修改后的 ReportGenerator 类 +""" + +import os +import sys +import asyncio +import json + +# 添加当前目录到 Python 路径 +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +from enhanced_survey_system import ReportGenerator + +async def test_report_generator(): + """测试报告生成器""" + print("🧪 开始测试 ReportGenerator...") + + # 检查环境变量 + if not os.getenv("GLM_API_KEY") or os.getenv("GLM_API_KEY") == "YOUR_API_KEY": + print("❌ 错误: 请在 .env 文件中设置正确的 GLM_API_KEY") + return False + + try: + # 创建报告生成器实例 + generator = ReportGenerator() + print("✅ ReportGenerator 实例创建成功") + + # 测试加载提示词 + prompt = generator.load_prompt() + print(f"✅ 提示词加载成功,长度: {len(prompt)} 字符") + + # 创建测试数据 + test_session_id = "test_session_123" + test_analysis_data = { + 'analysis_text': '''# 考试答题情况分析 + +## 总分:85 分 + +| 题目 | 题型 | 用户答案 | 正确答案 | 是否正确 | 得分 | +|------|------|----------|----------|----------|------| +| 姓名 | 填空题 | 张三 | 无标准答案 | 无法判断 | 不适用 | +| 学校 | 填空题 | 测试小学 | 无标准答案 | 无法判断 | 不适用 | +| 年级 | 填空题 | 五年级 | 无标准答案 | 无法判断 | 不适用 | +| 考试标签 | 填空题 | 科学测试 | 无标准答案 | 无法判断 | 不适用 | +| 水在自然界中的三种状态 | 单选题 | 气体、液体、固体 | 气体、液体、固体 | ✓ | 20 | +| 植物的光合作用需要 | 单选题 | 阳光 | 阳光和水 | ✗ | 0 | +| 地球的自转导致 | 单选题 | 白天黑夜 | 白天黑夜 | ✓ | 20 |''', + 'student_info': { + 'name': '张三', + 'school': '测试小学', + 'grade': '五年级' + }, + 'answers': [ + { + 'questionText': '水在自然界中的三种状态', + 'questionType': '单选题', + 'userAnswer': '气体、液体、固体', + 'correctAnswer': '气体、液体、固体', + 'isCorrect': True, + 'score': 20 + }, + { + 'questionText': '植物的光合作用需要', + 'questionType': '单选题', + 'userAnswer': '阳光', + 'correctAnswer': '阳光和水', + 'isCorrect': False, + 'score': 0 + } + ] + } + + print("📝 准备调用 GLM API...") + + # 调用 API 生成报告 + result = generator.call_report_api(test_analysis_data, test_session_id) + + if result.get('success'): + print("✅ API 调用成功!") + print(f"📊 报告数据类型: {type(result.get('report_data'))}") + print(f"📊 原始响应长度: {len(result.get('raw_response', ''))} 字符") + + # 显示报告数据的前500个字符 + report_data = result.get('report_data', '') + if isinstance(report_data, str): + print(f"📄 报告内容预览:\n{report_data[:500]}...") + else: + print(f"📄 报告数据: {json.dumps(report_data, ensure_ascii=False, indent=2)[:500]}...") + + return True + else: + print(f"❌ API 调用失败: {result.get('error', '未知错误')}") + return False + + except Exception as e: + print(f"❌ 测试过程中发生错误: {e}") + import traceback + traceback.print_exc() + return False + +if __name__ == "__main__": + print("=" * 60) + print("ReportGenerator 测试") + print("=" * 60) + + success = asyncio.run(test_report_generator()) + + print("=" * 60) + if success: + print("🎉 测试通过!") + else: + print("💥 测试失败!") + print("=" * 60) \ No newline at end of file