소스 검색

feat:智能评价逐句并行进行,设置超时时间

baiqi 1 년 전
부모
커밋
f923838601

+ 12 - 0
src/main/java/com/njuzr/eaibackend/config/ThreadPoolConfig.java

@@ -52,6 +52,18 @@ public class ThreadPoolConfig {
         return executor;
     }
 
+    @Bean("aiPerSentenceExecutor")
+    public ThreadPoolTaskExecutor aiPerSentenceExecutor() {
+        ThreadPoolTaskExecutor executor = getExecutor();
+        executor.setCorePoolSize(100);
+        executor.setMaxPoolSize(150);
+        executor.setQueueCapacity(500);
+        executor.setThreadNamePrefix("ai-sentence-");
+        executor.initialize();
+        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+        return executor;
+    }
+
     private static ThreadPoolTaskExecutor getExecutor() {
         return new ThreadPoolTaskExecutor() {
             @Override

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

@@ -0,0 +1,10 @@
+package com.njuzr.eaibackend.mapper;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.njuzr.eaibackend.po.Evaluation;
+import com.njuzr.eaibackend.po.SentenceEvaluation;
+import org.springframework.stereotype.Service;
+
+@Service
+public class SentenceEvaluationMapperServiceImpl extends ServiceImpl<SentenceEvaluationMapper, SentenceEvaluation> {
+}

+ 2 - 0
src/main/java/com/njuzr/eaibackend/po/SentenceEvaluation.java

@@ -4,12 +4,14 @@ import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.Data;
+import lombok.experimental.Accessors;
 
 /**
  * @author BaiBai
  * @date 2025-01-17 18:41
  */
 @Data
+@Accessors(chain = true)
 @TableName("sentence_evaluation")
 public class SentenceEvaluation {
     @TableId(type = IdType.AUTO)

+ 102 - 92
src/main/java/com/njuzr/eaibackend/service/AIEvaluationService.java

@@ -15,11 +15,10 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
 
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.List;
+import java.util.*;
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 /**
  * @author BaiBai
@@ -117,6 +116,12 @@ public class AIEvaluationService {
     @Qualifier("aiTaskExecutor")
     private ThreadPoolTaskExecutor aiTaskExecutor;
 
+    @Autowired
+    @Qualifier("aiPerSentenceExecutor")
+    private ThreadPoolTaskExecutor aiPerSentenceExecutor;
+    @Autowired
+    private SentenceEvaluationMapperServiceImpl sentenceEvaluationMapperServiceImpl;
+
     @Autowired
     public AIEvaluationService(AIRequestService aiRequestService, StudentAssignmentMapper studentAssignmentMapper, AssignmentMapper assignmentMapper, OverallEvaluationMapper overallEvaluationMapper, SentenceEvaluationMapper sentenceEvaluationMapper, EvaluationMapper evaluationMapper, EvaluationMapperServiceImpl evaluationMapperServiceImpl) {
         this.aiRequestService = aiRequestService;
@@ -145,9 +150,9 @@ public class AIEvaluationService {
         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 && evaluationJudge.getOverall() != null) throw MyException.create(HttpStatus.BAD_REQUEST, "作业已进行过AI分析");
+        if(evaluationJudge != null) throw MyException.create(HttpStatus.BAD_REQUEST, "作业已进行过AI分析");
 
-        if(evaluationJudge == null) initEvaluationRecord(assignmentId, studentId, engagement);
+        initEvaluationRecord(assignmentId, studentId, engagement);
 
         String prompt = String.format(REWRITE_PROMPT_TEMPLATE, assignment.getDescription(), engagement.getTextContent());
         AIRequestService.AIResponse response = requestAI(prompt, 0);
@@ -210,101 +215,107 @@ public class AIEvaluationService {
      * @param studentId
      * @return
      */
+    @Transactional(rollbackFor = Exception.class)
     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, "作业参与不存在");
+        StopWatch sw = StopWatch.createStarted();
 
-        // 通过assignmentId找到作业,获取作业描述
+        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 (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 && evaluationJudge.getSentence() == 1){
-            throw MyException.create(HttpStatus.BAD_REQUEST, "作业已进行过AI分析");
+        // 没有智能评价记录时,进行重试,等待整体智能批改将记录写入数据库
+        int retryTime = 0;
+        while (retryTime < 3) {
+            Evaluation evaluationJudge = evaluationMapperServiceImpl.getByVersion(assignmentId, studentId, engagement.getVersion());
+            if (evaluationJudge == null){
+                try {
+                    log.info("rewritePerSentence find evaluation record failed, assignmentId: {}, studentId: {}, retryTime: {}", assignmentId, studentId, retryTime + 1);
+                    Thread.sleep(300);
+                    retryTime++;
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+            } else {
+                if(evaluationJudge.getSentence() != 0){
+                    throw MyException.create(HttpStatus.BAD_REQUEST, "作业已进行过逐句智能评价");
+                } else {
+                    break;
+                }
+            }
+        }
+        if(retryTime >= 3) {
+            log.info("rewritePerSentence failed, assignmentId: {}, studentId: {}, reason: over max retry times(3)", assignmentId, studentId);
+            throw MyException.create(HttpStatus.BAD_REQUEST, "逐句批改失败,超过重试次数,请重新发起请求");
         }
 
-        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.WorkflowRunResponse 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.requestPreSentenceWorkflow(content, assignmentDescription, res[i]);
-
-            // 3 获取返回数据
-            try {
-                sentenceEvaluation.setEvaluation(response.getData().getOutputs().getResult_content());
-                sentenceEvaluation.setType(response.getData().getOutputs().getResult_type());
-                sentenceEvaluation.setCategory(response.getData().getOutputs().getResult_category());
-                log.info("AI返回的数据为:"+ response);
-            } catch (Exception e) {
-                log.error("AI请求返回的数据内容有误,数据为{}", response);
-                throw new MyException(HttpStatus.INTERNAL_SERVER_ERROR.value(), HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()+":"+"请求失败");
-            }
+        String[] sentences = content.split("\\.");
+        List<SentenceEvaluation> sentenceEvaluationsList = new ArrayList<>();
+        CompletableFuture<?>[] futures = new CompletableFuture[sentences.length];
+
+        for (int i = 0; i < sentences.length; i++) {
+            final int sentenceNo = i;
+            final String sentence = sentences[i] + ".";
+            futures[i] = CompletableFuture.supplyAsync(() ->
+                    processSingleSentence(assignmentId, studentId, content, assignment.getDescription(), sentence, sentenceNo, engagement.getVersion()),
+                    aiPerSentenceExecutor)
+                    .thenAccept(sentenceEvaluationsList::add);
+        }
 
-            // 4 存储数据
-            sentenceEvaluation.setAssignmentId(assignmentId);
-            sentenceEvaluation.setStudentId(studentId);
-            sentenceEvaluation.setContent(content);
-            sentenceEvaluation.setNo(i+1);
-            sentenceEvaluation.setSentence(res[i] + ".");
-            sentenceEvaluation.setVersion(engagement.getVersion());
-            sentenceEvaluationMapper.insert(sentenceEvaluation);
-
-            // 5 填入返回信息
-            responses.add(sentenceEvaluation);
+        // 等待所有任务完成
+        try {
+            CompletableFuture.allOf(futures).get(180, TimeUnit.SECONDS);
+            log.info("rewritePerSentence allOf success, assignmentId: {}, studentId: {}, version: {}", assignmentId, studentId, engagement.getVersion());
+        } catch (TimeoutException e) {
+            log.warn("rewritePerSentence failed, assignmentId: {}, studentId: {}, reason: timeout", assignmentId, studentId);
+            Arrays.stream(futures).forEach(f -> f.cancel(true));
+            throw MyException.create(HttpStatus.BAD_REQUEST, "处理超时,请重试");
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw MyException.create(HttpStatus.BAD_REQUEST, "处理被中断: " + e.getMessage());
+        } catch (Exception e){
+            throw MyException.create(HttpStatus.BAD_REQUEST, "处理失败: " + e.getMessage());
         }
 
-        Evaluation evaluationJudge2 = evaluationMapper.selectOne(new QueryWrapper<Evaluation>()
-                .eq("assignment_id", assignmentId)
+        // 所有任务完成后批量插入数据库
+        if(!sentenceEvaluationsList.isEmpty()) {
+            sentenceEvaluationMapperServiceImpl.saveBatch(sentenceEvaluationsList);
+        }
+
+        evaluationMapperServiceImpl.update(new UpdateWrapper<Evaluation>()
+                .set("sentence", 1)
                 .eq("student_id", studentId)
+                .eq("assignment_id", assignmentId)
                 .eq("version", engagement.getVersion()));
-        if(evaluationJudge2 == null){
-            Evaluation evaluation = new Evaluation();
-            evaluation.setTime(new Date());
-            evaluation.setAssignmentId(assignmentId);
-            evaluation.setStudentId(studentId);
-            evaluation.setVersion(engagement.getVersion());
-            evaluation.setSentence(1);
-            evaluation.setOverall(0);
-            evaluationMapper.insert(evaluation);
-        } else {
-            evaluationJudge.setSentence(1);
-            evaluationMapper.updateById(evaluationJudge);
+
+        log.info("rewritePerSentence cost: {}", sw.formatTime());
+        return sentenceEvaluationsList;
+    }
+
+    private SentenceEvaluation processSingleSentence(
+            Long assignmentId, Long studentId,
+            String content, String assignmentDesc,
+            String sentence, int sentenceNo,
+            Integer version
+    ) {
+        AIRequestService.WorkflowRunResponse response = aiRequestService.requestPreSentenceWorkflow(content, assignmentDesc, sentence);
+        if(response == null || response.getData() == null){
+            log.error("processSingleSentence failed, assignmentId: {}, studentId: {}, sentence: {}, response: {}", assignmentId, studentId, sentence, response);
+            return new SentenceEvaluation().setVersion(version).setNo(sentenceNo + 1).setType("error").setCategory("error").setEvaluation("error");
         }
 
-        return responses;
+        return new SentenceEvaluation()
+                .setEvaluation(response.getData().getOutputs().getResult_content())
+                .setType(response.getData().getOutputs().getResult_type())
+                .setCategory(response.getData().getOutputs().getResult_category())
+                .setAssignmentId(assignmentId)
+                .setStudentId(studentId)
+                .setContent(content)
+                .setNo(sentenceNo + 1)
+                .setSentence(sentence)
+                .setVersion(version);
     }
 
     /**
@@ -334,16 +345,15 @@ public class AIEvaluationService {
      * @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);
+        List<SentenceEvaluation> sentenceEvaluations = sentenceEvaluationMapperServiceImpl.list(new QueryWrapper<SentenceEvaluation>()
+               .eq("assignment_id", assignmentId)
+               .eq("student_id", studentId)
+               .eq("version", version));
 
         if(sentenceEvaluations.isEmpty()){
             throw MyException.create(HttpStatus.BAD_REQUEST, "AI批改不存在");
         }
-        // 按no属性进行排序
+
         return sentenceEvaluations.stream().sorted(Comparator.comparing(SentenceEvaluation::getNo)).toList();
     }