|
|
@@ -0,0 +1,332 @@
|
|
|
+package com.njuzr.eaibackend.service;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
|
+import com.njuzr.eaibackend.exception.MyException;
|
|
|
+import com.njuzr.eaibackend.mapper.*;
|
|
|
+import com.njuzr.eaibackend.po.*;
|
|
|
+import com.njuzr.eaibackend.utils.FileUtil;
|
|
|
+import lombok.AllArgsConstructor;
|
|
|
+import lombok.Data;
|
|
|
+import lombok.NoArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.http.HttpStatus;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Comparator;
|
|
|
+import java.util.List;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+/**
|
|
|
+ * @author BaiBai
|
|
|
+ * @date 2025-01-17 18:58
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+public class AIEvaluationService {
|
|
|
+ // 整体评价
|
|
|
+ // 两个输入 1.作文描述 2.作文内容
|
|
|
+ private static final String REWRITE_PROMPT_TEMPLATE = "假设你现在是一位英文专业的教师,正在批阅一位同学的写作作业,作业描述是:%s"
|
|
|
+ + "请做如下工作:\n"
|
|
|
+ + "1. 满分100分,请综合英文用词、句式等方面,给出评分和评分原因\n"
|
|
|
+ + "2. 请对以下英语作文进行详细分析,从词汇、句法、内容结构、逻辑连贯性、语言风格以及整体写作水平等方面展开。\n" +
|
|
|
+ " 词汇层面,评估词汇的丰富度,是否存在重复词汇,是否运用了适合文章主题和难度级别的词汇,有无高级词汇或专业术语的恰当使用。检查词汇拼写与词性搭配是否准确,以及词汇的感情色彩是否契合文章基调。\n" +
|
|
|
+ " 句法层面,剖析句子结构的多样性,是否合理运用了简单句、并列句、复合句等。判断句子语法是否正确,包括主谓一致、时态一致、语态使用是否恰当等。关注句子的长度和复杂度是否适中,是否存在句式过于冗长或过于简单的问题。\n" +
|
|
|
+ " 内容结构方面,分析文章是否紧扣主题,开头是否能够有效引入主题,中间论述是否条理清晰、层次分明,结尾是否有力且能升华主题。检查段落之间的过渡是否自然,每个段落是否有明确的中心思想,段落内部的内容是否紧密围绕该中心思想展开。\n" +
|
|
|
+ " 逻辑连贯性上,判断文章整体逻辑是否清晰,观点之间的衔接是否自然流畅,是否使用了合适的连接词、过渡语来增强逻辑关系。\n" +
|
|
|
+ " 语言风格上,考量文章是正式、半正式还是口语化风格,风格是否与文章类型和受众相匹配。\n" +
|
|
|
+ " 整体写作水平方面,综合上述各项因素,给出一个整体评价,指出文章的优点与不足,并提出具体的改进建议。\n"
|
|
|
+ + "格式举例:\n"
|
|
|
+ + "评分:95分\n" +
|
|
|
+ "\n" +
|
|
|
+ "评分原因:\n" +
|
|
|
+ "\n" +
|
|
|
+ "1. 词汇层面:文章词汇丰富,运用了多种词汇,如\"palpable sense of community\"、\"isolationist world\"、\"raw creativity\"、\"immense patience\"、\"academic rigor\"等,体现了作者对词汇的恰当运用。文章中未出现重复词汇,高级词汇和术语的使用也恰到好处,如\"algorithm design\"。\n" +
|
|
|
+ "\n" +
|
|
|
+ "2. 句法层面:文章句式多样,合理运用了简单句、并列句和复合句。句子结构清晰,语法正确,如主谓一致和时态一致等。句子的长度和复杂度适中,既不过于冗长也不过于简单。\n" +
|
|
|
+ "\n" +
|
|
|
+ "3. 内容结构方面:文章紧扣主题,开头通过激活时光机器的方式引人入胜。中间部分分别从校园氛围、学术氛围、社交氛围等方面展开,条理清晰,层次分明。结尾升华主题,表达对那段时光的感激之情,使文章更具感染力。段落之间的过渡自然,每个段落都有明确的中心思想。\n" +
|
|
|
+ "\n" +
|
|
|
+ "4. 逻辑连贯性方面:文章整体逻辑清晰,观点之间的衔接自然流畅,使用了合适的连接词和过渡语来增强逻辑关系。\n" +
|
|
|
+ "\n" +
|
|
|
+ "5. 语言风格方面:文章采用正式风格,适合文章类型和受众,既庄重又富有感染力。\n" +
|
|
|
+ "\n" +
|
|
|
+ "6. 整体写作水平方面:文章结构完整,内容丰富,语言流畅,表达清晰,具有很高的写作水平。\n" +
|
|
|
+ "\n" +
|
|
|
+ "改进建议:\n" +
|
|
|
+ "1. 可以适当增加一些形容词和副词,使文章更加生动形象。\n" +
|
|
|
+ "2. 在描述校园氛围和学术氛围时,可以加入一些具体的事例来增强说服力。\n" +
|
|
|
+ "3. 结尾部分可以进一步强调作者对那段时光的感激之情,使文章更具感染力。"
|
|
|
+ + "其余的任何描述都不需要,也不需要任何交互!\n"
|
|
|
+ + "以下是学生作业内容:\n\n"
|
|
|
+ + "%s";
|
|
|
+ // 逐句评价
|
|
|
+ private static final String SENTENCE_PROMPT_TEMPLATE = "假设你是一位英文专业的教师,正在批改一份写作作业,以下是作业相关信息:\n" +
|
|
|
+ "- 作业描述:%s\n" +
|
|
|
+ "- 学生作业内容:%s\n" +
|
|
|
+ "- 待分析的句子:%s\n" +
|
|
|
+ "\n" + "请从以下三个角度对该句子进行评价,并选择最需要修改的一个角度给出意见。若句子合理,则回复“合理”;若句子不合理,则按照以下格式回复:\n" +
|
|
|
+ "{角度小标题}\n" +
|
|
|
+ "{具体修改建议}\n" +
|
|
|
+ "\n" +
|
|
|
+ "例如\n" +
|
|
|
+ "\n" +
|
|
|
+ "1.合理\n" +
|
|
|
+ "\n" +
|
|
|
+ "则回复\n" +
|
|
|
+ "\n" +
|
|
|
+ "修改建议:合理\n" +
|
|
|
+ "\n" +
|
|
|
+ "2.词汇最不合理\n" +
|
|
|
+ "\n" +
|
|
|
+ "则回复\n" +
|
|
|
+ "\n" +
|
|
|
+ "词汇表达意思不够生动\n" +
|
|
|
+ "\n" +
|
|
|
+ "可以使用“the world outside the time machine swirled into obscurity”或“the world outside the time machine dissolved into a whirl of blurs”来替代,这样既能保持句子的流畅性,又能增加描述的生动性。" +
|
|
|
+ "评判标准如下:\n" +
|
|
|
+ "- **词汇分析**:\n" +
|
|
|
+ " - **词汇丰富度**:查看句子中词汇的多样性,避免词汇重复,确保词汇的难度符合作文要求。如高中英语作文应使用一定比例的高级词汇,大学英语四六级作文则对词汇丰富度和复杂度有更高要求。检查是否有简单词汇可被更精准、更具表现力的词汇替换,例如“good”可改为“excellent”“marvelous”“superb”等。\n" +
|
|
|
+ " - **词汇准确性**:确保词汇拼写正确,词性使用恰当,注意词汇搭配的合理性和符合英语表达习惯,例如“affect”(动词,影响)和“effect”(名词,影响)不可混淆,“make a decision”正确,“do a decision”错误。\n" +
|
|
|
+ "- **句法分析**:\n" +
|
|
|
+ " - **句型结构**:分析使用的基本句型(如主谓宾、主系表等),检查是否运用多种句型以丰富表达,避免单一。例如,应灵活运用简单句、并列句和复合句。确保句子结构完整,无成分缺失或多余,如“Because he is ill, so he didn't come to school.”应去掉“because”或“so”其中一个。\n" +
|
|
|
+ " - **语法正确性**:检查时态、语态是否正确且一致,例如描述过去事件用一般过去时,强调动作承受者用被动语态。同时,检查主谓是否一致,名词单复数是否正确,如“The book on the desk are mine.”应改为“The book on the desk is mine.”。\n" +
|
|
|
+ "- **内容结构分析**:\n" +
|
|
|
+ " - **与主题相关性**:判断句子是否紧密围绕作文主题展开,是否对主题表达有积极贡献。例如,若主题是“环境保护”,句子应涉及环境污染现状、原因、解决措施等相关内容。同时,检查句子在上下文中的连贯性,是否能自然衔接前后句,使文章逻辑清晰,如使用过渡句引导读者理解文章思路。\n" +
|
|
|
+ " - **逻辑连贯性**:分析句子内部及句子间的逻辑关系是否清晰,使用恰当连接词(如“and”“but”“however”“therefore”等)表达并列、转折、因果等逻辑关系。检查句子能否有效支持段落主旨,段落能否服务于文章整体结构,例如在论述观点时,句子应提供具体论据或例子支持论点。";
|
|
|
+
|
|
|
+ private final AIRequestService aiRequestService;
|
|
|
+
|
|
|
+ private final StudentAssignmentMapper studentAssignmentMapper;
|
|
|
+
|
|
|
+ private final AssignmentMapper assignmentMapper;
|
|
|
+
|
|
|
+ private final OverallEvaluationMapper overallEvaluationMapper;
|
|
|
+
|
|
|
+ private final SentenceEvaluationMapper sentenceEvaluationMapper;
|
|
|
+
|
|
|
+ private final EvaluationMapper evaluationMapper;
|
|
|
+
|
|
|
+ private final FileUtil fileUtil = new FileUtil();
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ public AIEvaluationService(AIRequestService aiRequestService, StudentAssignmentMapper studentAssignmentMapper, AssignmentMapper assignmentMapper, OverallEvaluationMapper overallEvaluationMapper, SentenceEvaluationMapper sentenceEvaluationMapper, EvaluationMapper evaluationMapper) {
|
|
|
+ this.aiRequestService = aiRequestService;
|
|
|
+ this.studentAssignmentMapper = studentAssignmentMapper;
|
|
|
+ this.assignmentMapper = assignmentMapper;
|
|
|
+ this.overallEvaluationMapper = overallEvaluationMapper;
|
|
|
+ this.sentenceEvaluationMapper = sentenceEvaluationMapper;
|
|
|
+ this.evaluationMapper = evaluationMapper;
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * AI作文整体智能批改
|
|
|
+ * @param assignmentId
|
|
|
+ * @param studentId
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public AIEntry rewrite(Long assignmentId, Long studentId) {
|
|
|
+ // 1 通过assignmentId和studentId找到engagement,获取当前文件链接
|
|
|
+ Engagement engagement = studentAssignmentMapper.findEngagementByStudentIdAndAssignmentId(studentId, assignmentId);
|
|
|
+ if(engagement == null)
|
|
|
+ throw MyException.create(HttpStatus.BAD_REQUEST, "作业参与不存在");
|
|
|
+
|
|
|
+ // 通过assignmentId找到作业,获取作业描述
|
|
|
+ Assignment assignment = assignmentMapper.selectById(assignmentId);
|
|
|
+ if (assignment == null)
|
|
|
+ throw MyException.create(HttpStatus.BAD_REQUEST, "作业不存在");
|
|
|
+
|
|
|
+ // 确定当前批改没有进行过
|
|
|
+ Evaluation evaluationJudge = evaluationMapper.selectOne(new QueryWrapper<Evaluation>()
|
|
|
+ .eq("assignment_id", assignmentId)
|
|
|
+ .eq("student_id", studentId)
|
|
|
+ .eq("version", engagement.getVersion()));
|
|
|
+ if(evaluationJudge != null){
|
|
|
+ throw MyException.create(HttpStatus.BAD_REQUEST, "作业已进行过AI分析");
|
|
|
+ }
|
|
|
+
|
|
|
+ String assignmentDescription = assignment.getDescription();
|
|
|
+
|
|
|
+ // 2 找到学生作业内容
|
|
|
+ String content = engagement.getTextContent();
|
|
|
+ log.info("作文内容为:" + content);
|
|
|
+
|
|
|
+ // 3 构造提示词
|
|
|
+ String prompt = String.format(REWRITE_PROMPT_TEMPLATE, assignmentDescription, content);
|
|
|
+ log.info("内置提示词为:"+ prompt);
|
|
|
+
|
|
|
+ // 4 请求AI
|
|
|
+ List<AIEntry> entry = new ArrayList<>();
|
|
|
+ entry.add(new AIEntry("user", prompt));
|
|
|
+ AIRequestService.AIResponse response = aiRequestService.requestChatGLM4(entry);
|
|
|
+
|
|
|
+ // 5 获取返回数据
|
|
|
+ String retContent;
|
|
|
+ String role;
|
|
|
+ Long timestamp;
|
|
|
+ try {
|
|
|
+ retContent = response.getChoices().get(0).getMessage().getContent();
|
|
|
+ role = response.getChoices().get(0).getMessage().getRole();
|
|
|
+ timestamp = response.getCreated();
|
|
|
+ log.info("AI返回的数据为:"+retContent);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("AI请求返回的数据内容有误,数据为{}", response);
|
|
|
+ throw new MyException(HttpStatus.INTERNAL_SERVER_ERROR.value(), HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()+":"+"请求失败");
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // 6 存储数据,已修改为MySQL存储。历史版本使用MangoDB,可前往仓库查看。
|
|
|
+ // 6.1 存储评价
|
|
|
+ OverallEvaluation overallEvaluation = new OverallEvaluation();
|
|
|
+ overallEvaluation.setStudentId(studentId);
|
|
|
+ overallEvaluation.setAssignmentId(assignmentId);
|
|
|
+ overallEvaluation.setContent(content);
|
|
|
+ overallEvaluation.setEvaluation(retContent);
|
|
|
+ // 评价的版本跟随作业版本
|
|
|
+ overallEvaluation.setVersion(engagement.getVersion());
|
|
|
+ overallEvaluationMapper.insert(overallEvaluation);
|
|
|
+
|
|
|
+ // 6.2 存储版本信息
|
|
|
+ Evaluation evaluation = new Evaluation();
|
|
|
+ evaluation.setAssignmentId(assignmentId);
|
|
|
+ evaluation.setStudentId(studentId);
|
|
|
+ evaluation.setVersion(engagement.getVersion());
|
|
|
+ evaluationMapper.insert(evaluation);
|
|
|
+
|
|
|
+ return new AIEntry(role, retContent);
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * AI作业逐句智能批改
|
|
|
+ * @param assignmentId
|
|
|
+ * @param studentId
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public List<SentenceEvaluation> rewritePerSentence(Long assignmentId, Long studentId){
|
|
|
+ // 1 通过assignmentId和studentId找到engagement,获取当前文件链接
|
|
|
+ Engagement engagement = studentAssignmentMapper.findEngagementByStudentIdAndAssignmentId(studentId, assignmentId);
|
|
|
+ if(engagement == null)
|
|
|
+ throw MyException.create(HttpStatus.BAD_REQUEST, "作业参与不存在");
|
|
|
+
|
|
|
+ // 通过assignmentId找到作业,获取作业描述
|
|
|
+ Assignment assignment = assignmentMapper.selectById(assignmentId);
|
|
|
+ if (assignment == null)
|
|
|
+ throw MyException.create(HttpStatus.BAD_REQUEST, "作业不存在");
|
|
|
+
|
|
|
+ // 确定当前批改没有进行过
|
|
|
+ Evaluation evaluationJudge = evaluationMapper.selectOne(new QueryWrapper<Evaluation>()
|
|
|
+ .eq("assignment_id", assignmentId)
|
|
|
+ .eq("student_id", studentId)
|
|
|
+ .eq("version", engagement.getVersion()));
|
|
|
+ if(evaluationJudge != null){
|
|
|
+ throw MyException.create(HttpStatus.BAD_REQUEST, "作业已进行过AI分析");
|
|
|
+ }
|
|
|
+
|
|
|
+ String assignmentDescription = assignment.getDescription();
|
|
|
+
|
|
|
+ // 2 找到学生作业内容
|
|
|
+ String content = engagement.getTextContent();
|
|
|
+ log.info("作文内容为:" + content);
|
|
|
+
|
|
|
+ // 3 分割作文的句子
|
|
|
+ // TODO: 句子分割需要详细考虑和判断
|
|
|
+ String[] res = content.split("\\.");
|
|
|
+
|
|
|
+ // 4 初始化AI请求变量
|
|
|
+ List<AIEntry> entry = new ArrayList<>();
|
|
|
+ AIRequestService.AIResponse response;
|
|
|
+
|
|
|
+ // 5 初始化获取返回数据
|
|
|
+ String retContent = "";
|
|
|
+ List<SentenceEvaluation> responses = new ArrayList<>();
|
|
|
+
|
|
|
+ // 6 逐句进行处理
|
|
|
+ for(int i = 0; i < res.length; i++) {
|
|
|
+ SentenceEvaluation sentenceEvaluation = new SentenceEvaluation();
|
|
|
+
|
|
|
+ // 1 构造提示词
|
|
|
+ String prompt2 = String.format(SENTENCE_PROMPT_TEMPLATE, assignmentDescription, content, res[i]);
|
|
|
+ log.info("内置提示词为:"+ prompt2);
|
|
|
+
|
|
|
+ // 2 请求AI
|
|
|
+ entry.clear();
|
|
|
+ entry.add(new AIEntry("user", prompt2));
|
|
|
+ response = aiRequestService.requestChatGLM4(entry);
|
|
|
+
|
|
|
+ // 3 获取返回数据
|
|
|
+ try {
|
|
|
+ sentenceEvaluation.setEvaluation(response.getChoices().get(0).getMessage().getContent());
|
|
|
+ log.info("AI返回的数据为:"+retContent);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("AI请求返回的数据内容有误,数据为{}", response);
|
|
|
+ throw new MyException(HttpStatus.INTERNAL_SERVER_ERROR.value(), HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()+":"+"请求失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4 存储数据
|
|
|
+ sentenceEvaluation.setAssignmentId(assignmentId);
|
|
|
+ sentenceEvaluation.setStudentId(studentId);
|
|
|
+ sentenceEvaluation.setContent(content);
|
|
|
+ sentenceEvaluation.setNo(i+1);
|
|
|
+ sentenceEvaluation.setSentence(res[i] + "\n");
|
|
|
+ sentenceEvaluation.setVersion(engagement.getVersion());
|
|
|
+ sentenceEvaluationMapper.insert(sentenceEvaluation);
|
|
|
+
|
|
|
+ // 5 填入返回信息
|
|
|
+ responses.add(sentenceEvaluation);
|
|
|
+ }
|
|
|
+ return responses;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获得AI作业整体评价记录详情
|
|
|
+ * @param assignmentId
|
|
|
+ * @param studentId
|
|
|
+ * @param version
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public String getRewrite(Long assignmentId, Long studentId, int version){
|
|
|
+ QueryWrapper<SentenceEvaluation> queryWrapper = new QueryWrapper<>();
|
|
|
+ queryWrapper.eq("assignment_id", assignmentId);
|
|
|
+ queryWrapper.eq("student_id", studentId);
|
|
|
+ queryWrapper.eq("version", version);
|
|
|
+ SentenceEvaluation sentenceEvaluation = sentenceEvaluationMapper.selectOne(queryWrapper);
|
|
|
+ if(sentenceEvaluation == null){
|
|
|
+ throw MyException.create(HttpStatus.BAD_REQUEST, "AI批改不存在");
|
|
|
+ }
|
|
|
+ return sentenceEvaluation.getContent();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获得AI作业逐句评价记录详情
|
|
|
+ * @param assignmentId
|
|
|
+ * @param studentId
|
|
|
+ * @param version
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public List<SentenceEvaluation> getRewritePerSentence(Long assignmentId, Long studentId, int version){
|
|
|
+ QueryWrapper<SentenceEvaluation> queryWrapper = new QueryWrapper<>();
|
|
|
+ queryWrapper.eq("assignment_id", assignmentId);
|
|
|
+ queryWrapper.eq("student_id", studentId);
|
|
|
+ queryWrapper.eq("version", version);
|
|
|
+ List<SentenceEvaluation> sentenceEvaluations = sentenceEvaluationMapper.selectList(queryWrapper);
|
|
|
+
|
|
|
+ // 按no属性进行排序
|
|
|
+ return sentenceEvaluations.stream().sorted(Comparator.comparing(SentenceEvaluation::getNo)).toList();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取所有AI评价版本记录
|
|
|
+ * @param assignmentId
|
|
|
+ * @param studentId
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public List<Evaluation> getVersionList(Long assignmentId, Long studentId){
|
|
|
+ QueryWrapper<Evaluation> queryWrapper = new QueryWrapper<>();
|
|
|
+ queryWrapper.eq("assignment_id", assignmentId);
|
|
|
+ queryWrapper.eq("student_id", studentId);
|
|
|
+
|
|
|
+ return evaluationMapper.selectList(queryWrapper);
|
|
|
+ }
|
|
|
+
|
|
|
+}
|