作者 lixiang

智能助手增加流式返回接口

@@ -2,6 +2,7 @@ package org.jeecg.config.shiro; @@ -2,6 +2,7 @@ package org.jeecg.config.shiro;
2 2
3 import lombok.extern.slf4j.Slf4j; 3 import lombok.extern.slf4j.Slf4j;
4 import org.apache.commons.pool2.impl.GenericObjectPoolConfig; 4 import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
  5 +import org.apache.shiro.SecurityUtils;
5 import org.apache.shiro.mgt.DefaultSessionStorageEvaluator; 6 import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
6 import org.apache.shiro.mgt.DefaultSubjectDAO; 7 import org.apache.shiro.mgt.DefaultSubjectDAO;
7 import org.apache.shiro.mgt.SecurityManager; 8 import org.apache.shiro.mgt.SecurityManager;
@@ -238,7 +239,7 @@ public class ShiroConfig { @@ -238,7 +239,7 @@ public class ShiroConfig {
238 public DefaultWebSecurityManager securityManager(ShiroRealm myRealm) { 239 public DefaultWebSecurityManager securityManager(ShiroRealm myRealm) {
239 DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); 240 DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
240 securityManager.setRealm(myRealm); 241 securityManager.setRealm(myRealm);
241 - 242 + SecurityUtils.setSecurityManager(securityManager);
242 /* 243 /*
243 * 关闭shiro自带的session,详情见文档 244 * 关闭shiro自带的session,详情见文档
244 * http://shiro.apache.org/session-management.html#SessionManagement- 245 * http://shiro.apache.org/session-management.html#SessionManagement-
@@ -10,6 +10,8 @@ import io.swagger.v3.oas.annotations.Operation; @@ -10,6 +10,8 @@ import io.swagger.v3.oas.annotations.Operation;
10 import lombok.extern.slf4j.Slf4j; 10 import lombok.extern.slf4j.Slf4j;
11 import org.apache.commons.lang3.StringUtils; 11 import org.apache.commons.lang3.StringUtils;
12 import org.apache.shiro.SecurityUtils; 12 import org.apache.shiro.SecurityUtils;
  13 +import org.apache.shiro.subject.Subject;
  14 +import org.apache.shiro.util.ThreadContext;
13 import org.jeecg.ai.handler.AIParams; 15 import org.jeecg.ai.handler.AIParams;
14 import org.jeecg.ai.handler.LLMHandler; 16 import org.jeecg.ai.handler.LLMHandler;
15 import org.jeecg.common.api.vo.Result; 17 import org.jeecg.common.api.vo.Result;
@@ -28,6 +30,12 @@ import org.springframework.stereotype.Component; @@ -28,6 +30,12 @@ import org.springframework.stereotype.Component;
28 import org.springframework.web.bind.annotation.GetMapping; 30 import org.springframework.web.bind.annotation.GetMapping;
29 import org.springframework.web.bind.annotation.RequestMapping; 31 import org.springframework.web.bind.annotation.RequestMapping;
30 import org.springframework.web.bind.annotation.RestController; 32 import org.springframework.web.bind.annotation.RestController;
  33 +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
  34 +
  35 +import java.io.File;
  36 +import java.io.IOException;
  37 +import java.util.concurrent.ExecutorService;
  38 +import java.util.concurrent.Executors;
31 39
32 import java.util.*; 40 import java.util.*;
33 41
@@ -47,6 +55,168 @@ public class ZdyRagController { @@ -47,6 +55,168 @@ public class ZdyRagController {
47 private IAiragLogService airagLogService; 55 private IAiragLogService airagLogService;
48 56
49 57
  58 + // 用于异步处理的线程池
  59 + private final ExecutorService executor = Executors.newCachedThreadPool();
  60 +
  61 + @Operation(summary = "sendStream")
  62 + @GetMapping("sendStream")
  63 + public SseEmitter sendStream(String questionText) throws Exception {
  64 + SseEmitter emitter = new SseEmitter(300000L);
  65 +
  66 + // 创建日志对象
  67 + String modelId = "1926875898187878401";
  68 + AiragLog logRecord = new AiragLog()
  69 + .setQuestion(questionText)
  70 + .setModelId(modelId)
  71 + .setCreateTime(new Date());
  72 +
  73 + executor.execute(() -> {
  74 + try {
  75 + String knowId = "1926872137990148098";
  76 + List<QuestionEmbedding> questionEmbeddings = questionEmbeddingService.similaritySearchByQuestion(questionText, 1, 0.8);
  77 +
  78 + // 如果从问题库中找到匹配
  79 + if (!questionEmbeddings.isEmpty()) {
  80 + QuestionEmbedding questionEmbedding = questionEmbeddings.get(0);
  81 + Map<String, String> data = new HashMap<>();
  82 + data.put("token", questionEmbedding.getAnswer());
  83 + emitter.send(SseEmitter.event().data(new ObjectMapper().writeValueAsString(data)));
  84 +
  85 + // 解析元数据
  86 + ObjectMapper objectMapper = new ObjectMapper();
  87 + Map<String, String> metadata = objectMapper.readValue(questionEmbedding.getMetadata(), Map.class);
  88 +
  89 + // 准备END事件数据
  90 + Map<String, String> endData = new HashMap<>();
  91 + endData.put("event", "END");
  92 + endData.put("similarity", String.valueOf(questionEmbedding.getSimilarity()));
  93 + if (metadata != null) {
  94 + String docName = metadata.get("docName");
  95 + endData.put("fileName", docName);
  96 + String fileName = generateFilePath(questionEmbedding.getMetadata());
  97 + if (StringUtils.isNotBlank(fileName)) {
  98 + endData.put("fileBase64", FileToBase64Util.fileToBase64(uploadPath + fileName));
  99 + }
  100 + }
  101 + emitter.send(SseEmitter.event().data(objectMapper.writeValueAsString(endData)));
  102 +
  103 + // 记录日志 - 从问题库匹配
  104 + logRecord.setAnswer(questionEmbedding.getAnswer())
  105 + .setAnswerType(1);
  106 + airagLogService.save(logRecord);
  107 +
  108 + emitter.complete();
  109 + return;
  110 + }
  111 +
  112 + // 从知识库搜索
  113 + List<Map<String, Object>> maps = embeddingHandler.searchEmbedding(knowId, questionText, 3, 0.75);
  114 + if (CollectionUtil.isEmpty(maps)) {
  115 + Map<String, String> data = new HashMap<>();
  116 + data.put("token", "该问题未记录在知识库中");
  117 + emitter.send(SseEmitter.event().data(new ObjectMapper().writeValueAsString(data)));
  118 +
  119 + // 准备END事件数据
  120 + Map<String, String> endData = new HashMap<>();
  121 + endData.put("event", "END");
  122 + emitter.send(SseEmitter.event().data(new ObjectMapper().writeValueAsString(endData)));
  123 +
  124 + // 记录日志 - 未命中任何知识库
  125 + logRecord.setAnswer("该问题未记录在知识库中")
  126 + .setAnswerType(3)
  127 + .setIsStorage(0);
  128 + airagLogService.save(logRecord);
  129 +
  130 + emitter.complete();
  131 + return;
  132 + }
  133 +
  134 + // 构建知识库内容
  135 + StringBuilder content = new StringBuilder();
  136 + for (Map<String, Object> map : maps) {
  137 + if (Double.parseDouble(map.get("score").toString()) > 0.75) {
  138 + content.append(map.get("content").toString()).append("\n");
  139 + }
  140 + }
  141 +
  142 + // 获取第一个匹配的元数据用于日志和文件信息
  143 + Map<String, Object> firstMatch = maps.get(0);
  144 + String fileName = generateFileDocName(firstMatch.get("metadata").toString());
  145 + String storedFileName = generateFilePath(firstMatch.get("metadata").toString());
  146 +
  147 + // 构建问题提示
  148 + String questin = "你是一个严谨的信息处理助手,请严格按照以下要求处理用户问题:" + questionText + "\n\n" +
  149 + "处理步骤和要求:\n" +
  150 + "1. 严格基于参考内容回答,禁止任何超出参考内容的推断或想象\n" +
  151 + "2. 回答结构:\n" +
  152 + " - 首先用一句话直接回答问题核心(仅限参考内容中明确包含的信息)\n" +
  153 + " - 然后列出支持该答案的具体证据(可直接引用参考内容)\n" +
  154 + "3. 禁止以下行为:\n" +
  155 + " - 添加参考内容中不存在的信息\n" +
  156 + " - 进行任何推测性陈述\n" +
  157 + " - 使用模糊或不确定的表达\n" +
  158 + " - 参考内容为空时应该拒绝回答\n" +
  159 + "参考内容(请严格限制回答范围于此):\n" + content;
  160 +
  161 + List<ChatMessage> messages = new ArrayList<>();
  162 + messages.add(new UserMessage("user", questin));
  163 + StringBuilder answerBuilder = new StringBuilder();
  164 +
  165 + TokenStream tokenStream = aiChatHandler.chat(modelId, messages);
  166 + tokenStream.onNext(token -> {
  167 + try {
  168 + answerBuilder.append(token);
  169 + Map<String, String> data = new HashMap<>();
  170 + data.put("token", token);
  171 + emitter.send(SseEmitter.event().data(new ObjectMapper().writeValueAsString(data)));
  172 + } catch (Exception e) {
  173 + log.error("发送token失败", e);
  174 + }
  175 + });
  176 +
  177 + tokenStream.onComplete(response -> {
  178 + try {
  179 + // 准备END事件数据
  180 + Map<String, String> endData = new HashMap<>();
  181 + endData.put("event", "END");
  182 + endData.put("similarity", firstMatch.get("score").toString());
  183 + endData.put("fileName", fileName);
  184 + endData.put("fileBase64", FileToBase64Util.fileToBase64(uploadPath + storedFileName));
  185 + emitter.send(SseEmitter.event().data(new ObjectMapper().writeValueAsString(endData)));
  186 +
  187 + // 记录日志 - 从知识库生成回答
  188 + logRecord.setAnswer(answerBuilder.toString())
  189 + .setAnswerType(2);
  190 + airagLogService.save(logRecord);
  191 +
  192 + emitter.complete();
  193 + } catch (Exception e) {
  194 + log.error("流式响应结束时发生错误", e);
  195 + }
  196 + });
  197 +
  198 + tokenStream.onError(error -> {
  199 + log.error("生成答案失败", error);
  200 + // 记录日志 - 错误情况
  201 + logRecord.setAnswer("生成答案失败: " + error.getMessage())
  202 + .setAnswerType(4);
  203 + airagLogService.save(logRecord);
  204 + emitter.completeWithError(error);
  205 + });
  206 +
  207 + tokenStream.start();
  208 + } catch (Exception e) {
  209 + log.error("处理请求时发生异常", e);
  210 + // 记录日志 - 异常情况
  211 + logRecord.setAnswer("处理请求时发生异常: " + e.getMessage())
  212 + .setAnswerType(4);
  213 + airagLogService.save(logRecord);
  214 + emitter.completeWithError(e);
  215 + }
  216 + });
  217 + return emitter;
  218 + }
  219 +
50 @Operation(summary = "send") 220 @Operation(summary = "send")
51 @GetMapping("send") 221 @GetMapping("send")
52 public Result<Map<String, Object>> send(String questionText) throws Exception { 222 public Result<Map<String, Object>> send(String questionText) throws Exception {