|
|
@@ -0,0 +1,166 @@
|
|
|
+package com.njuzr.eaibackend.service;
|
|
|
+
|
|
|
+import com.njuzr.eaibackend.dto.AIDTO;
|
|
|
+import com.njuzr.eaibackend.exception.MyException;
|
|
|
+import com.njuzr.eaibackend.mapper.AIDialogueMapper;
|
|
|
+import com.njuzr.eaibackend.po.AIDialogue;
|
|
|
+import com.njuzr.eaibackend.po.AIEntry;
|
|
|
+import com.njuzr.eaibackend.utils.ModelMapperUtil;
|
|
|
+import com.njuzr.eaibackend.vo.AIDialogueVO;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.data.domain.*;
|
|
|
+import org.springframework.data.mongodb.core.MongoTemplate;
|
|
|
+import org.springframework.data.mongodb.core.aggregation.*;
|
|
|
+import org.springframework.data.mongodb.core.query.Criteria;
|
|
|
+import org.springframework.data.mongodb.core.query.Query;
|
|
|
+import org.springframework.data.mongodb.core.query.Update;
|
|
|
+import org.springframework.http.HttpStatus;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Objects;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+/**
|
|
|
+ * @author: Leonezhurui
|
|
|
+ * @Date: 2024/3/3 - 11:29
|
|
|
+ * @Package: EAI-Backend
|
|
|
+ */
|
|
|
+
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+public class AIDialogueService {
|
|
|
+ private final MongoTemplate mongoTemplate;
|
|
|
+
|
|
|
+ private final AIDialogueMapper aiDialogueMapper;
|
|
|
+
|
|
|
+ private final AIRequestService aiRequestService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ public AIDialogueService(MongoTemplate mongoTemplate, AIDialogueMapper aiDialogueMapper, AIRequestService aiRequestService) {
|
|
|
+ this.mongoTemplate = mongoTemplate;
|
|
|
+ this.aiDialogueMapper = aiDialogueMapper;
|
|
|
+ this.aiRequestService = aiRequestService;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建新的AI会话,在学生点击进行写作界面时会自动触发
|
|
|
+ * @param assignmentId
|
|
|
+ * @param userId
|
|
|
+ */
|
|
|
+ public void createAIDialogue(Long assignmentId, Long userId) {
|
|
|
+ if (aiDialogueMapper.findByAssignmentIdAndUserId(assignmentId, userId, PageRequest.of(0,1)).hasContent()) {
|
|
|
+ throw new MyException(HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase()+":"+"AI会话已存在");
|
|
|
+ }
|
|
|
+ AIDialogue aiDialogue = new AIDialogue();
|
|
|
+ aiDialogue.setAssignmentId(assignmentId);
|
|
|
+ aiDialogue.setUserId(userId);
|
|
|
+ aiDialogue.setDialogues(new ArrayList<>());
|
|
|
+ aiDialogueMapper.save(aiDialogue);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 分页返回查找内容
|
|
|
+ * @param assignmentId
|
|
|
+ * @param userId
|
|
|
+ * @param page
|
|
|
+ * @param size
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public AIDialogueVO getAIDialogue(Long assignmentId, Long userId, int page, int size) {
|
|
|
+
|
|
|
+ AIDialogue targetDialogue = mongoTemplate.findOne(
|
|
|
+ Query.query(Criteria.where("assignmentId").is(assignmentId).and("userId").is(userId)), AIDialogue.class
|
|
|
+ );
|
|
|
+
|
|
|
+ if (targetDialogue == null) {
|
|
|
+ throw new MyException(HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase()+":"+"找不到AI会话");
|
|
|
+ }
|
|
|
+
|
|
|
+ Pageable pageable = PageRequest.of(page, size);
|
|
|
+
|
|
|
+ List<AIDialogue.DialogueEntry> sortedDialogues = targetDialogue.getDialogues();
|
|
|
+ sortedDialogues.sort((d1, d2) -> d2.getTimestamp().compareTo(d1.getTimestamp()));
|
|
|
+
|
|
|
+ // 手动处理dialogues数组的分页逻辑
|
|
|
+ int start = (int) pageable.getOffset();
|
|
|
+ int end = Math.min((start + pageable.getPageSize()), sortedDialogues.size());
|
|
|
+
|
|
|
+ if (start > sortedDialogues.size()) {
|
|
|
+ start = end = sortedDialogues.size();
|
|
|
+ }
|
|
|
+
|
|
|
+ List<AIDialogue.DialogueEntry> pagedDialogues = sortedDialogues.subList(start, end);
|
|
|
+
|
|
|
+ List<AIEntry> entries = pagedDialogues.stream().map(entry -> ModelMapperUtil.map(entry, AIEntry.class)).collect(Collectors.toList());
|
|
|
+
|
|
|
+ AIDialogueVO aiDialogueVO = new AIDialogueVO();
|
|
|
+ aiDialogueVO.setDialogueId(targetDialogue.getId());
|
|
|
+
|
|
|
+ aiDialogueVO.setMessages(new PageImpl<>(entries, pageable, targetDialogue.getDialogues().size()));
|
|
|
+
|
|
|
+ // 返回包含分页dialogues和总条目数的PageImpl
|
|
|
+ return aiDialogueVO;
|
|
|
+ }
|
|
|
+
|
|
|
+ public AIEntry requestAI(String dialogueId, AIDTO aidto) {
|
|
|
+ // 1 使用WebClient请求数据
|
|
|
+ String modelType = aidto.getModel();
|
|
|
+ List<AIEntry> entries = aidto.getMessages();
|
|
|
+ if (entries.size() > 11) {
|
|
|
+ // TODO 限制上下文数量
|
|
|
+ }
|
|
|
+
|
|
|
+ AIRequestService.AIResponse res;
|
|
|
+ if (Objects.equals(modelType, "ChatGLM3")) {
|
|
|
+ res = aiRequestService.requestChatGLM(aidto.getMessages());
|
|
|
+ } else if (modelType.equals("QWen14B")) {
|
|
|
+ res = aiRequestService.requestQWen(aidto.getMessages());
|
|
|
+ } else {
|
|
|
+ throw new MyException(HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase()+":"+"模型不支持");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2 更新数据库
|
|
|
+ AIEntry lastEntry = aidto.getMessages().get(aidto.getMessages().size()-1);
|
|
|
+ AIDialogue.DialogueEntry userEntry = new AIDialogue.DialogueEntry(lastEntry.getRole(), lastEntry.getContent(), System.currentTimeMillis() / 1000);
|
|
|
+
|
|
|
+ String retContent;
|
|
|
+ String role;
|
|
|
+ Long timestamp;
|
|
|
+ try {
|
|
|
+ retContent = res.getChoices().get(0).getMessage().getContent();
|
|
|
+ role = res.getChoices().get(0).getMessage().getRole();
|
|
|
+ timestamp = res.getCreated();
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("AI请求返回的数据内容有误,数据为{}", res);
|
|
|
+ throw new MyException(HttpStatus.INTERNAL_SERVER_ERROR.value(), HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()+":"+"请求失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ AIDialogue.DialogueEntry aiEntry = new AIDialogue.DialogueEntry(role, retContent, timestamp);
|
|
|
+
|
|
|
+ addDialogueEntry(dialogueId, userEntry, aiEntry);
|
|
|
+
|
|
|
+ return new AIEntry(role, retContent);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 更新MongoDB数据;通过$push的方式,减少更新的开销;请求成功则会一次性更新两条数据,如果不成功,则两条都不插入。
|
|
|
+ * @param dialogueId
|
|
|
+ * @param userEntry
|
|
|
+ * @param aiEntry
|
|
|
+ */
|
|
|
+ private void addDialogueEntry(String dialogueId, AIDialogue.DialogueEntry userEntry, AIDialogue.DialogueEntry aiEntry) {
|
|
|
+ try {
|
|
|
+ Query query = new Query(Criteria.where("id").is(dialogueId));
|
|
|
+ Update update = new Update().push("dialogues").each(userEntry, aiEntry);
|
|
|
+ mongoTemplate.updateFirst(query, update, AIDialogue.class);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("MongoDB数据库更新出错");
|
|
|
+ throw new MyException(HttpStatus.INTERNAL_SERVER_ERROR.value(), HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()+":"+"数据库更新失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+}
|