#4 优化智评

Слито
XiaoYu слито 4 коммит(ов) из FanYanPeng/feat/ai-evaluation в FanYanPeng/refactor 1 год назад

+ 75 - 0
src/main/java/com/njuzr/eaibackend/constant/PromptConstant.java

@@ -0,0 +1,75 @@
+package com.njuzr.eaibackend.constant;
+
+public class PromptConstant {
+    // 整体评价
+    // 两个输入 1.作文描述 2.作文内容
+    public 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";
+    // 逐句评价
+    public 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”等)表达并列、转折、因果等逻辑关系。检查句子能否有效支持段落主旨,段落能否服务于文章整体结构,例如在论述观点时,句子应提供具体论据或例子支持论点。";
+
+}

+ 18 - 0
src/main/java/com/njuzr/eaibackend/controller/EvaluationController.java

@@ -95,5 +95,23 @@ public class EvaluationController {
         return MyResponse.success(aiEvaluationService.getVersionList(assignmentId, studentId));
     }
 
+    /**
+     * 用于支持单句重试
+     * @param assignmentId
+     * @param studentId
+     * @param version
+     * @param sentenceNo
+     * @return
+     */
+    @PostMapping("/sentence/byOne")
+    public MyResponse evaluateSentenceByOne(
+            @RequestParam Long assignmentId,
+            @RequestParam Long studentId,
+            @RequestParam Integer version,
+            @RequestParam Integer sentenceNo
+    ){
+        return MyResponse.success(aiEvaluationService.evaluateSentenceByOne(assignmentId, studentId, version, sentenceNo));
+    }
+
 
 }

+ 19 - 0
src/main/java/com/njuzr/eaibackend/mapper/SentenceEvaluationMapperServiceImpl.java

@@ -1,5 +1,6 @@
 package com.njuzr.eaibackend.mapper;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.njuzr.eaibackend.po.Evaluation;
 import com.njuzr.eaibackend.po.SentenceEvaluation;
@@ -7,4 +8,22 @@ import org.springframework.stereotype.Service;
 
 @Service
 public class SentenceEvaluationMapperServiceImpl extends ServiceImpl<SentenceEvaluationMapper, SentenceEvaluation> {
+
+    public boolean saveOrUpdateByCondition(SentenceEvaluation entity) {
+        // 1. 构建唯一条件
+        LambdaQueryWrapper<SentenceEvaluation> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(SentenceEvaluation::getAssignmentId, entity.getAssignmentId())
+                .eq(SentenceEvaluation::getStudentId, entity.getStudentId())
+                .eq(SentenceEvaluation::getVersion, entity.getVersion())
+                .eq(SentenceEvaluation::getNo, entity.getNo());
+
+        // 2. 尝试更新非空字段
+        int rows = baseMapper.update(entity, queryWrapper);
+        if (rows == 0) {
+            // 3. 无匹配记录则插入
+            baseMapper.insert(entity);
+        }
+        return true;
+    }
+
 }

+ 135 - 110
src/main/java/com/njuzr/eaibackend/service/AIEvaluationService.java

@@ -2,6 +2,7 @@ package com.njuzr.eaibackend.service;
 
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.njuzr.eaibackend.constant.PromptConstant;
 import com.njuzr.eaibackend.exception.MyException;
 import com.njuzr.eaibackend.mapper.*;
 import com.njuzr.eaibackend.po.*;
@@ -29,76 +30,6 @@ import java.util.concurrent.TimeoutException;
 public class AIEvaluationService {
 
     private static final int RETRY_COUNT = 3;
-    // 整体评价
-    // 两个输入 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;
 
@@ -136,56 +67,56 @@ public class AIEvaluationService {
     }
 
     public CompletableFuture<AIEntry> evaluationAsync(Long assignmentId, Long studentId) {
-        return CompletableFuture.supplyAsync(() -> evaluation(assignmentId, studentId), aiTaskExecutor);
+        return CompletableFuture.supplyAsync(() -> evaluation(assignmentId, studentId), aiTaskExecutor)
+                .exceptionally(ex -> { throw new MyException(HttpStatus.INTERNAL_SERVER_ERROR.value(), "AI请求失败:" + ex.getMessage());})
+                .orTimeout(40, TimeUnit.SECONDS);
     }
+    
     /**
      * AI作文整体智能批改
      * @param assignmentId
+     *
      * @param studentId
      * @return
      */
     public AIEntry evaluation(Long assignmentId, Long studentId) {
         StopWatch sw = StopWatch.createStarted();
+        try{
+            Engagement engagement = studentAssignmentMapper.findEngagementByStudentIdAndAssignmentId(studentId, assignmentId);
+            if(engagement == null) throw MyException.create(HttpStatus.BAD_REQUEST, "作业参与不存在");
+            Assignment assignment = assignmentMapper.selectById(assignmentId);
+            if (assignment == null) throw MyException.create(HttpStatus.BAD_REQUEST, "作业不存在");
+            Evaluation evaluationJudge = evaluationMapperServiceImpl.getByVersion(assignmentId, studentId, engagement.getVersion());
+            if(evaluationJudge != null) throw MyException.create(HttpStatus.BAD_REQUEST, "作业已进行过AI分析");
+            initEvaluationRecord(assignmentId, studentId, engagement);
 
-        Engagement engagement = studentAssignmentMapper.findEngagementByStudentIdAndAssignmentId(studentId, assignmentId);
-        if(engagement == null) throw MyException.create(HttpStatus.BAD_REQUEST, "作业参与不存在");
-        Assignment assignment = assignmentMapper.selectById(assignmentId);
-        if (assignment == null) throw MyException.create(HttpStatus.BAD_REQUEST, "作业不存在");
-        Evaluation evaluationJudge = evaluationMapperServiceImpl.getByVersion(assignmentId, studentId, engagement.getVersion());
-        if(evaluationJudge != null) throw MyException.create(HttpStatus.BAD_REQUEST, "作业已进行过AI分析");
-        log.info("evaluation start");
-        initEvaluationRecord(assignmentId, studentId, engagement);
-        log.info("evaluation end");
-        String prompt = String.format(REWRITE_PROMPT_TEMPLATE, assignment.getDescription(), engagement.getTextContent());
-        AIRequestService.AIResponse response = requestAI(prompt, 0);
-        String retContent;
-        String role;
-        try {
-            retContent = response.getChoices().get(0).getMessage().getContent();
-            role = response.getChoices().get(0).getMessage().getRole();
-            log.info("evaluation requestAI Response:"+retContent);
+            // 发起LLM请求
+            String prompt = String.format(PromptConstant.REWRITE_PROMPT_TEMPLATE, assignment.getDescription(), engagement.getTextContent());
+            AIRequestService.AIResponse response = requestAIWithRetry(prompt, 0);
+            String retContent = response.getChoices().get(0).getMessage().getContent();
+            String role = response.getChoices().get(0).getMessage().getRole();
+
+            OverallEvaluation overallEvaluation = new OverallEvaluation()
+                .setStudentId(studentId)
+                .setAssignmentId(assignmentId)
+                .setContent(engagement.getTextContent())
+                .setEvaluation(retContent)
+                .setVersion(engagement.getVersion());
+            overallEvaluationMapper.insert(overallEvaluation);
+
+            evaluationMapperServiceImpl.update(new UpdateWrapper<Evaluation>()
+                    .set("overall", 1)
+                    .eq("student_id", studentId)
+                    .eq("assignment_id", assignmentId)
+                    .eq("version", engagement.getVersion()));
+
+            log.info("evaluation cost: {}", sw.formatTime());
+            return new AIEntry(role, retContent);
         } catch (Exception e) {
-            log.error("evaluation requestAI error: {}", response);
+            log.error("evaluation requestAI error: {}", e.getMessage());
             throw new MyException(HttpStatus.INTERNAL_SERVER_ERROR.value(), HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()+":"+"请求失败");
         }
 
-        OverallEvaluation overallEvaluation = new OverallEvaluation()
-            .setStudentId(studentId)
-            .setAssignmentId(assignmentId)
-            .setContent(engagement.getTextContent())
-            .setEvaluation(retContent)
-            .setVersion(engagement.getVersion());
-        overallEvaluationMapper.insert(overallEvaluation);
-
-        evaluationMapperServiceImpl.update(new UpdateWrapper<Evaluation>()
-                .set("overall", 1)
-                .eq("student_id", studentId)
-                .eq("assignment_id", assignmentId)
-                .eq("version", engagement.getVersion()));
-
-        log.info("evaluation cost: {}", sw.formatTime());
-        return new AIEntry(role, retContent);
-
     }
 
     private void initEvaluationRecord(Long assignmentId, Long studentId, Engagement engagement) {
@@ -201,13 +132,13 @@ public class AIEvaluationService {
         log.info("evaluation record: {}", evaluation);
     }
 
-    private AIRequestService.AIResponse requestAI(String prompt, int retryTime) {
+    private AIRequestService.AIResponse requestAIWithRetry(String prompt, int retryTime) {
         List<AIEntry> entry = new ArrayList<>();
         entry.add(new AIEntry("user", prompt));
         AIRequestService.AIResponse response = aiRequestService.requestChatGLM4(entry);
         if(response == null && retryTime < 3) {
             log.info("requestAI failed, retryTime: {}", retryTime + 1);
-            return requestAI(prompt, retryTime + 1);
+            return requestAIWithRetry(prompt, retryTime + 1);
         }
         return response;
     }
@@ -254,8 +185,7 @@ public class AIEvaluationService {
         }
 
         String content = engagement.getTextContent();
-        // TODO: 句子分割需要详细考虑和判断
-        String[] sentences = content.split("\\.");
+        String[] sentences = getSentenceList(content);
         List<SentenceEvaluation> sentenceEvaluationsList = new ArrayList<>();
         CompletableFuture<?>[] futures = new CompletableFuture[sentences.length];
 
@@ -298,6 +228,11 @@ public class AIEvaluationService {
         return sentenceEvaluationsList;
     }
 
+    private static String[] getSentenceList(String content) {
+        String[] sentences = content.split("\\.");
+        return sentences;
+    }
+
     private SentenceEvaluation processSingleSentenceWithRetry(Long assignmentId, Long studentId, String content, String description, String sentence, int sentenceNo, int version) {
         int retryTime = 0;
         while (retryTime < RETRY_COUNT) {
@@ -311,6 +246,18 @@ public class AIEvaluationService {
         return new SentenceEvaluation().setVersion(version).setNo(sentenceNo + 1).setType("error").setCategory("error").setEvaluation("error").setStudentId(studentId).setAssignmentId(assignmentId);
     }
 
+    /**
+     * 向Dify发起请求智评单个句子
+     * @param assignmentId
+     * @param studentId
+     * @param content
+     * @param assignmentDesc
+     * @param sentence
+     * @param sentenceNo
+     * @param version
+     * @param retryCount
+     * @return
+     */
     private SentenceEvaluation processSingleSentence(
             Long assignmentId, Long studentId,
             String content, String assignmentDesc,
@@ -389,4 +336,82 @@ public class AIEvaluationService {
         return evaluationMapper.selectList(queryWrapper);
     }
 
+    /**
+     * 对某一个句子进行智评
+     * @param assignmentId
+     * @param studentId
+     * @param version
+     * @return
+     */
+    public SentenceEvaluation evaluateSentenceByOne(Long assignmentId, Long studentId, Integer version, Integer sentenceNo) {
+        Engagement engagement = studentAssignmentMapper.findEngagementByStudentIdAndAssignmentId(studentId, assignmentId);
+        if(engagement == null) throw MyException.create(HttpStatus.BAD_REQUEST, "作业参与不存在");
+        Assignment assignment = assignmentMapper.selectById(assignmentId);
+        if (assignment == null) throw MyException.create(HttpStatus.BAD_REQUEST, "作业不存在");
+        Evaluation evaluationJudge = evaluationMapperServiceImpl.getByVersion(assignmentId, studentId, engagement.getVersion());
+        if(evaluationJudge == null) throw MyException.create(HttpStatus.BAD_REQUEST, "没有该版本的智评记录");
+
+        String content = engagement.getTextContent();
+        String[] sentences = getSentenceList(content);
+        SentenceEvaluation result = processSingleSentenceWithRetry(assignmentId, studentId, content, assignment.getDescription(), sentences[sentenceNo], sentenceNo, engagement.getVersion());
+        if(result == null || result.getType().equals("error")) {
+            throw MyException.create(HttpStatus.BAD_REQUEST, "智评失败,请重试");
+        }
+
+        sentenceEvaluationMapperServiceImpl.saveOrUpdateByCondition(result);
+        return result;
+    }
+
+    /**
+     * 双智评:同时触发整体智评和逐句智评
+     * 1. 创建智评记录
+     * 2. 同步执行整体智评(等待结果)
+     * 3. 异步执行逐句智评(不等待结果)
+     *
+     * @param assignmentId 作业ID
+     * @param studentId 学生ID
+     * @return 整体智评结果的CompletableFuture
+     */
+    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
+    public CompletableFuture<AIEntry> doubleEvaluationAsync(Long assignmentId, Long studentId) {
+        // 1. 基础校验和记录初始化
+        Engagement engagement = studentAssignmentMapper.findEngagementByStudentIdAndAssignmentId(studentId, assignmentId);
+        if (engagement == null) throw MyException.create(HttpStatus.BAD_REQUEST, "作业参与不存在");
+        Assignment assignment = assignmentMapper.selectById(assignmentId);
+        if (assignment == null) throw MyException.create(HttpStatus.BAD_REQUEST, "作业不存在");
+        if (evaluationMapperServiceImpl.getByVersion(assignmentId, studentId, engagement.getVersion()) != null) {
+            throw MyException.create(HttpStatus.BAD_REQUEST, "该版本作业已进行过AI分析");
+        }
+
+        // 2. 创建智评记录(原子操作)
+        initEvaluationRecord(assignmentId, studentId, engagement);
+        log.info("双智评记录已创建,开始并行处理");
+
+        // 3. 同步执行整体智评(阻塞等待结果)
+        CompletableFuture<AIEntry> overallFuture = CompletableFuture.supplyAsync(() -> {
+            try {
+                return evaluation(assignmentId, studentId);
+            } catch (Exception e) {
+                throw MyException.create(HttpStatus.BAD_REQUEST, "整体智评失败");
+            }
+        }, aiTaskExecutor);
+
+        // 4. 异步执行逐句智评(不阻塞主线程)
+        CompletableFuture.runAsync(() -> {
+            try {
+                rewritePerSentence(assignmentId, studentId);
+            } catch (Exception e) {
+                log.error("逐句智评异步执行失败: assignmentId={}, studentId={}, error={}",
+                        assignmentId, studentId, e.getMessage(), e);
+            }
+        }, aiTaskExecutor);
+
+        return overallFuture
+                .exceptionally(ex -> {
+                    log.error("双智评整体处理失败", ex);
+                    throw new MyException(HttpStatus.INTERNAL_SERVER_ERROR.value(), "双智评执行异常");
+                })
+                .orTimeout(120, TimeUnit.SECONDS); // 整体智评超时控制
+    }
+
 }