作者 lixiang

智能助手修改

@@ -39,6 +39,11 @@ @@ -39,6 +39,11 @@
39 39
40 <dependencies> 40 <dependencies>
41 <dependency> 41 <dependency>
  42 + <groupId>com.hankcs</groupId>
  43 + <artifactId>hanlp</artifactId>
  44 + <version>portable-1.8.4</version> <!-- 推荐最新版 -->
  45 + </dependency>
  46 + <dependency>
42 <groupId>org.apache.pdfbox</groupId> 47 <groupId>org.apache.pdfbox</groupId>
43 <artifactId>pdfbox</artifactId> 48 <artifactId>pdfbox</artifactId>
44 <version>2.0.27</version> <!-- 使用最新稳定版 --> 49 <version>2.0.27</version> <!-- 使用最新稳定版 -->
  1 +package org.jeecg.modules.airag.app.timer;
  2 +
  3 +import com.hankcs.hanlp.HanLP;
  4 +import com.hankcs.hanlp.summary.TextRankKeyword;
  5 +
  6 +import java.util.List;
  7 +import java.util.stream.Collectors;
  8 +
  9 +
  10 +public class HandLPChinese {
  11 + public static void main(String[] args) {
  12 + String text = "迎宾讲解机器人 语音问答:可主动迎宾,语音识别,进行咨询接待、业务引导、信息查询等操作。 宣传展示:可轮播宣传文字、照片、视频等内容,并根据设定路线进行自动宣传讲解。 自动巡检:可按照既定路线定时或随时对车站进行巡航检查,并对巡检情况进行拍照记录。 自动避障:可自动识别前方障碍物,灵活避让,重新规划路线前往目的地。 娱乐功能:可播放音乐或视频,音乐视频库可扩充更新 远程客服:音视频通话,拨打电话 IOT智能控制:梯控功能、边门联动\t";
  13 + List<String> words = HanLP.segment(text)
  14 + .stream()
  15 + .map(term -> term.word)
  16 + .collect(Collectors.toList());
  17 + System.out.println(words);
  18 +
  19 +
  20 + List<String> keywords1 = HanLP.extractKeyword(text, 5);
  21 + System.out.println("关键词1:" + keywords1);
  22 + TextRankKeyword textRank = new TextRankKeyword();
  23 + List<String> keywords2 = textRank.getKeywords(text, 5);
  24 + System.out.println("关键词2:" + keywords2);
  25 +
  26 + }
  27 +}
@@ -2,43 +2,221 @@ package org.jeecg.modules.airag.zdyrag.controller; @@ -2,43 +2,221 @@ package org.jeecg.modules.airag.zdyrag.controller;
2 2
3 import cn.hutool.core.collection.CollectionUtil; 3 import cn.hutool.core.collection.CollectionUtil;
4 import com.fasterxml.jackson.databind.ObjectMapper; 4 import com.fasterxml.jackson.databind.ObjectMapper;
  5 +import com.hankcs.hanlp.summary.TextRankKeyword;
5 import dev.langchain4j.data.message.ChatMessage; 6 import dev.langchain4j.data.message.ChatMessage;
6 import dev.langchain4j.data.message.UserMessage; 7 import dev.langchain4j.data.message.UserMessage;
7 import dev.langchain4j.service.TokenStream; 8 import dev.langchain4j.service.TokenStream;
  9 +import dev.langchain4j.store.embedding.pgvector.PgVectorEmbeddingStore;
8 import io.swagger.v3.oas.annotations.Operation; 10 import io.swagger.v3.oas.annotations.Operation;
9 import lombok.extern.slf4j.Slf4j; 11 import lombok.extern.slf4j.Slf4j;
10 import org.apache.commons.lang3.StringUtils; 12 import org.apache.commons.lang3.StringUtils;
  13 +import org.apache.shiro.SecurityUtils;
  14 +import org.apache.shiro.subject.Subject;
  15 +import org.apache.shiro.util.ThreadContext;
  16 +import org.jeecg.ai.handler.AIParams;
  17 +import org.jeecg.ai.handler.LLMHandler;
  18 +import org.jeecg.common.api.vo.Result;
  19 +import org.jeecg.common.system.vo.LoginUser;
11 import org.jeecg.modules.airag.app.entity.AiragLog; 20 import org.jeecg.modules.airag.app.entity.AiragLog;
  21 +import org.jeecg.modules.airag.app.entity.QuestionEmbedding;
12 import org.jeecg.modules.airag.app.service.IAiragLogService; 22 import org.jeecg.modules.airag.app.service.IAiragLogService;
  23 +import org.jeecg.modules.airag.app.service.IQuestionEmbeddingService;
13 import org.jeecg.modules.airag.app.utils.FileToBase64Util; 24 import org.jeecg.modules.airag.app.utils.FileToBase64Util;
14 import org.jeecg.modules.airag.common.handler.IAIChatHandler; 25 import org.jeecg.modules.airag.common.handler.IAIChatHandler;
15 import org.jeecg.modules.airag.llm.handler.EmbeddingHandler; 26 import org.jeecg.modules.airag.llm.handler.EmbeddingHandler;
  27 +import org.jeecg.modules.airag.llm.service.IAiragKnowledgeService;
16 import org.springframework.beans.factory.annotation.Autowired; 28 import org.springframework.beans.factory.annotation.Autowired;
17 import org.springframework.beans.factory.annotation.Value; 29 import org.springframework.beans.factory.annotation.Value;
18 -import org.springframework.data.redis.core.RedisTemplate; 30 +import org.springframework.stereotype.Component;
19 import org.springframework.web.bind.annotation.GetMapping; 31 import org.springframework.web.bind.annotation.GetMapping;
20 import org.springframework.web.bind.annotation.RequestMapping; 32 import org.springframework.web.bind.annotation.RequestMapping;
21 -import org.springframework.web.bind.annotation.RequestParam;  
22 import org.springframework.web.bind.annotation.RestController; 33 import org.springframework.web.bind.annotation.RestController;
23 import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; 34 import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
24 35
25 -import java.util.*; 36 +import java.io.File;
  37 +import java.io.IOException;
26 import java.util.concurrent.ExecutorService; 38 import java.util.concurrent.ExecutorService;
27 import java.util.concurrent.Executors; 39 import java.util.concurrent.Executors;
28 -import java.util.concurrent.Future;  
29 -import java.util.concurrent.TimeUnit; 40 +
  41 +import java.util.*;
30 42
31 /** 43 /**
32 - * todo  
33 - * 访问知识库  
34 - * 甄选关键词  
35 - * 根据参考内容、问题和关键词进行回答  
36 - * 导入时是否应该使用ai进行关键词提取? 44 + * 直接回答llm
37 */ 45 */
38 @RestController 46 @RestController
39 @RequestMapping("/airag/zdyRag") 47 @RequestMapping("/airag/zdyRag")
40 @Slf4j 48 @Slf4j
41 public class KeyRagController { 49 public class KeyRagController {
  50 + @Autowired
  51 + private EmbeddingHandler embeddingHandler;
  52 + @Autowired
  53 + IAIChatHandler aiChatHandler;
  54 + @Autowired
  55 + private IQuestionEmbeddingService questionEmbeddingService;
  56 + @Value("${jeecg.upload.path}")
  57 + private String uploadPath;
  58 + @Autowired
  59 + private IAiragLogService airagLogService;
  60 +
  61 +
  62 + // 用于异步处理的线程池
  63 + private final ExecutorService executor = Executors.newCachedThreadPool();
  64 +
  65 + @Operation(summary = "sendStream1")
  66 + @GetMapping("sendStream1")
  67 + public SseEmitter sendStream(String questionText) throws Exception {
  68 + SseEmitter emitter = new SseEmitter(300000L);
  69 +
  70 + // 创建日志对象
  71 + String modelId = "1926875898187878401";
  72 + AiragLog logRecord = new AiragLog()
  73 + .setQuestion(questionText)
  74 + .setModelId(modelId)
  75 + .setCreateTime(new Date());
  76 +
  77 + executor.execute(() -> {
  78 + String knowId = "1926872137990148098";
  79 + try {
  80 + List<Map<String, Object>> maps = embeddingHandler.searchEmbedding(knowId, questionText, 2, 0.78);
  81 + // 从知识库搜索
  82 + if (CollectionUtil.isEmpty(maps)) {
  83 + Map<String, String> data = new HashMap<>();
  84 + data.put("token", "该问题未记录在知识库中");
  85 + emitter.send(SseEmitter.event().data(new ObjectMapper().writeValueAsString(data)));
  86 +
  87 + // 准备END事件数据
  88 + Map<String, String> endData = new HashMap<>();
  89 + endData.put("event", "END");
  90 + emitter.send(SseEmitter.event().data(new ObjectMapper().writeValueAsString(endData)));
  91 +
  92 + // 记录日志 - 未命中任何知识库
  93 + logRecord.setAnswer("该问题未记录在知识库中")
  94 + .setAnswerType(3)
  95 + .setIsStorage(0);
  96 + airagLogService.save(logRecord);
  97 +
  98 + emitter.complete();
  99 + return;
  100 + }
  101 +
  102 + // 构建知识库内容
  103 + StringBuilder content = new StringBuilder();
  104 + for (Map<String, Object> map : maps) {
  105 + if (Double.parseDouble(map.get("score").toString()) > 0.78) {
  106 + content.append(map.get("content").toString()).append("\n");
  107 + }
  108 + }
  109 + TextRankKeyword textRank = new TextRankKeyword();
  110 +
  111 + List<String> keyWords = textRank.getKeywords(questionText, 5);
  112 + System.out.println("关键词...:" + keyWords);
  113 + // 获取第一个匹配的元数据用于日志和文件信息
  114 + Map<String, Object> firstMatch = maps.get(0);
  115 + String fileName = generateFileDocName(firstMatch.get("metadata").toString());
  116 + String storedFileName = generateFilePath(firstMatch.get("metadata").toString());
  117 + // 构建更优化的prompt
  118 + String prompt = String.format("你是一个严谨的信息处理助手,请严格按照以下要求处理用户问题:" + questionText + "\n\n" +
  119 + "处理步骤和要求:\n" +
  120 + "1. 严格基于参考内容回答,禁止任何超出参考内容的推断或想象\n" +
  121 + "2. 严格基于参考内容回答,禁止使用参考内容中与问题无关的内容\n" +
  122 + "3. 回答结构:\n" +
  123 + " - 首先用一句话直接回答问题核心(仅限参考内容中明确包含的信息)\n" +
  124 + " - 然后列出支持该答案的具体内容(可直接引用参考内容)\n" +
  125 + "4. 禁止以下行为:\n" +
  126 + " - 添加参考内容中不存在的信息\n" +
  127 + " - 在回答中提及‘参考内容’等字样\n" +
  128 + " - 在回答中提及其他产品的功能\n" +
  129 + " - 进行任何推测性陈述\n" +
  130 + " - 使用模糊或不确定的表达\n" +
  131 + " - 参考内容为空时应该拒绝回答\n" +
  132 + "参考内容(请严格限制回答范围于此):\n" + content);
  133 +
  134 + List<ChatMessage> messages = new ArrayList<>();
  135 + messages.add(new UserMessage("user", prompt));
  136 + StringBuilder answerBuilder = new StringBuilder();
  137 +
  138 + TokenStream tokenStream = aiChatHandler.chat(modelId, messages);
  139 + tokenStream.onNext(token -> {
  140 + try {
  141 + answerBuilder.append(token);
  142 + Map<String, String> data = new HashMap<>();
  143 + data.put("token", token);
  144 + emitter.send(SseEmitter.event().data(new ObjectMapper().writeValueAsString(data)));
  145 + } catch (Exception e) {
  146 + log.error("发送token失败", e);
  147 + }
  148 + });
  149 +
  150 + tokenStream.onComplete(response -> {
  151 + try {
  152 + // 准备END事件数据
  153 + Map<String, String> endData = new HashMap<>();
  154 + endData.put("event", "END");
  155 + endData.put("similarity", firstMatch.get("score").toString());
  156 + endData.put("fileName", fileName);
  157 + endData.put("fileBase64", FileToBase64Util.fileToBase64(uploadPath + storedFileName));
  158 + emitter.send(SseEmitter.event().data(new ObjectMapper().writeValueAsString(endData)));
  159 +
  160 + // 记录日志 - 从知识库生成回答
  161 + logRecord.setAnswer(answerBuilder.toString())
  162 + .setAnswerType(2);
  163 +
  164 + System.out.println("回答内容 = " + answerBuilder);
  165 + airagLogService.save(logRecord);
  166 +
  167 + emitter.complete();
  168 + } catch (Exception e) {
  169 + log.error("流式响应结束时发生错误", e);
  170 + }
  171 + });
  172 +
  173 + tokenStream.onError(error -> {
  174 + log.error("生成答案失败", error);
  175 + // 记录日志 - 错误情况
  176 + logRecord.setAnswer("生成答案失败: " + error.getMessage())
  177 + .setAnswerType(4);
  178 + airagLogService.save(logRecord);
  179 + emitter.completeWithError(error);
  180 + });
  181 +
  182 + tokenStream.start();
  183 + } catch (Exception e) {
  184 + log.error("处理请求时发生异常", e);
  185 + // 记录日志 - 异常情况
  186 + logRecord.setAnswer("处理请求时发生异常: " + e.getMessage())
  187 + .setAnswerType(4);
  188 + airagLogService.save(logRecord);
  189 + emitter.completeWithError(e);
  190 + }
  191 + });
  192 + return emitter;
  193 + }
  194 +
  195 +
  196 + private String generateFilePath(String metadataJson) throws Exception {
  197 + if (StringUtils.isEmpty(metadataJson)) {
  198 + return "";
  199 + }
  200 + ObjectMapper objectMapper = new ObjectMapper();
  201 + // 解析JSON字符串
  202 + Map<String, String> metadata = objectMapper.readValue(metadataJson, Map.class);
  203 +
  204 + // 获取docName和docId
  205 + return metadata.get("storedFileName");
  206 +
  207 + }
  208 + private String generateFileDocName(String metadataJson) throws Exception {
  209 + if (StringUtils.isEmpty(metadataJson)) {
  210 + return "";
  211 + }
  212 + ObjectMapper objectMapper = new ObjectMapper();
  213 + // 解析JSON字符串
  214 + Map<String, String> metadata = objectMapper.readValue(metadataJson, Map.class);
  215 +
  216 + return metadata.get("docName");
  217 +
  218 + }
  219 +
42 220
43 221
44 } 222 }
@@ -29,6 +29,7 @@ import org.springframework.beans.factory.annotation.Value; @@ -29,6 +29,7 @@ import org.springframework.beans.factory.annotation.Value;
29 import org.springframework.stereotype.Component; 29 import org.springframework.stereotype.Component;
30 import org.springframework.web.bind.annotation.GetMapping; 30 import org.springframework.web.bind.annotation.GetMapping;
31 import org.springframework.web.bind.annotation.RequestMapping; 31 import org.springframework.web.bind.annotation.RequestMapping;
  32 +import org.springframework.web.bind.annotation.RequestParam;
32 import org.springframework.web.bind.annotation.RestController; 33 import org.springframework.web.bind.annotation.RestController;
33 import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; 34 import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
34 35
@@ -63,13 +64,20 @@ public class ZdyRagController { @@ -63,13 +64,20 @@ public class ZdyRagController {
63 64
64 @Operation(summary = "sendStream") 65 @Operation(summary = "sendStream")
65 @GetMapping("sendStream") 66 @GetMapping("sendStream")
66 - public SseEmitter sendStream(String questionText) throws Exception { 67 + public SseEmitter sendStream(@RequestParam("questionText") String questionText,
  68 + @RequestParam("code") String code,
  69 + @RequestParam("codeType") Integer codeType,
  70 + @RequestParam("user") String user
  71 + ) throws Exception {
67 SseEmitter emitter = new SseEmitter(300000L); 72 SseEmitter emitter = new SseEmitter(300000L);
68 73
69 // 创建日志对象 74 // 创建日志对象
70 String modelId = "1926875898187878401"; 75 String modelId = "1926875898187878401";
71 AiragLog logRecord = new AiragLog() 76 AiragLog logRecord = new AiragLog()
72 .setQuestion(questionText) 77 .setQuestion(questionText)
  78 + .setCode(code)
  79 + .setCreateBy(user)
  80 + .setCodeType(codeType)
73 .setModelId(modelId) 81 .setModelId(modelId)
74 .setCreateTime(new Date()); 82 .setCreateTime(new Date());
75 83
@@ -291,7 +299,7 @@ public class ZdyRagController { @@ -291,7 +299,7 @@ public class ZdyRagController {
291 299
292 StringBuilder content = new StringBuilder(); 300 StringBuilder content = new StringBuilder();
293 for (Map<String, Object> map : maps) { 301 for (Map<String, Object> map : maps) {
294 - if (Double.parseDouble(map.get("score").toString()) > 0.75){ 302 + if (Double.parseDouble(map.get("score").toString()) > 0.78){
295 log.info("score = " + map.get("score").toString()); 303 log.info("score = " + map.get("score").toString());
296 log.info("content = " + map.get("content").toString()); 304 log.info("content = " + map.get("content").toString());
297 content.append(map.get("content").toString()).append("\n"); 305 content.append(map.get("content").toString()).append("\n");