作者 lixiang

智能助手修改

... ... @@ -39,6 +39,11 @@
<dependencies>
<dependency>
<groupId>com.hankcs</groupId>
<artifactId>hanlp</artifactId>
<version>portable-1.8.4</version> <!-- 推荐最新版 -->
</dependency>
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.27</version> <!-- 使用最新稳定版 -->
... ...
package org.jeecg.modules.airag.app.timer;
import com.hankcs.hanlp.HanLP;
import com.hankcs.hanlp.summary.TextRankKeyword;
import java.util.List;
import java.util.stream.Collectors;
public class HandLPChinese {
public static void main(String[] args) {
String text = "迎宾讲解机器人 语音问答:可主动迎宾,语音识别,进行咨询接待、业务引导、信息查询等操作。 宣传展示:可轮播宣传文字、照片、视频等内容,并根据设定路线进行自动宣传讲解。 自动巡检:可按照既定路线定时或随时对车站进行巡航检查,并对巡检情况进行拍照记录。 自动避障:可自动识别前方障碍物,灵活避让,重新规划路线前往目的地。 娱乐功能:可播放音乐或视频,音乐视频库可扩充更新 远程客服:音视频通话,拨打电话 IOT智能控制:梯控功能、边门联动\t";
List<String> words = HanLP.segment(text)
.stream()
.map(term -> term.word)
.collect(Collectors.toList());
System.out.println(words);
List<String> keywords1 = HanLP.extractKeyword(text, 5);
System.out.println("关键词1:" + keywords1);
TextRankKeyword textRank = new TextRankKeyword();
List<String> keywords2 = textRank.getKeywords(text, 5);
System.out.println("关键词2:" + keywords2);
}
}
... ...
... ... @@ -2,43 +2,221 @@ package org.jeecg.modules.airag.zdyrag.controller;
import cn.hutool.core.collection.CollectionUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hankcs.hanlp.summary.TextRankKeyword;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.service.TokenStream;
import dev.langchain4j.store.embedding.pgvector.PgVectorEmbeddingStore;
import io.swagger.v3.oas.annotations.Operation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ThreadContext;
import org.jeecg.ai.handler.AIParams;
import org.jeecg.ai.handler.LLMHandler;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.modules.airag.app.entity.AiragLog;
import org.jeecg.modules.airag.app.entity.QuestionEmbedding;
import org.jeecg.modules.airag.app.service.IAiragLogService;
import org.jeecg.modules.airag.app.service.IQuestionEmbeddingService;
import org.jeecg.modules.airag.app.utils.FileToBase64Util;
import org.jeecg.modules.airag.common.handler.IAIChatHandler;
import org.jeecg.modules.airag.llm.handler.EmbeddingHandler;
import org.jeecg.modules.airag.llm.service.IAiragKnowledgeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.util.*;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.*;
/**
* todo
* 访问知识库
* 甄选关键词
* 根据参考内容、问题和关键词进行回答
* 导入时是否应该使用ai进行关键词提取?
* 直接回答llm
*/
@RestController
@RequestMapping("/airag/zdyRag")
@Slf4j
public class KeyRagController {
@Autowired
private EmbeddingHandler embeddingHandler;
@Autowired
IAIChatHandler aiChatHandler;
@Autowired
private IQuestionEmbeddingService questionEmbeddingService;
@Value("${jeecg.upload.path}")
private String uploadPath;
@Autowired
private IAiragLogService airagLogService;
// 用于异步处理的线程池
private final ExecutorService executor = Executors.newCachedThreadPool();
@Operation(summary = "sendStream1")
@GetMapping("sendStream1")
public SseEmitter sendStream(String questionText) throws Exception {
SseEmitter emitter = new SseEmitter(300000L);
// 创建日志对象
String modelId = "1926875898187878401";
AiragLog logRecord = new AiragLog()
.setQuestion(questionText)
.setModelId(modelId)
.setCreateTime(new Date());
executor.execute(() -> {
String knowId = "1926872137990148098";
try {
List<Map<String, Object>> maps = embeddingHandler.searchEmbedding(knowId, questionText, 2, 0.78);
// 从知识库搜索
if (CollectionUtil.isEmpty(maps)) {
Map<String, String> data = new HashMap<>();
data.put("token", "该问题未记录在知识库中");
emitter.send(SseEmitter.event().data(new ObjectMapper().writeValueAsString(data)));
// 准备END事件数据
Map<String, String> endData = new HashMap<>();
endData.put("event", "END");
emitter.send(SseEmitter.event().data(new ObjectMapper().writeValueAsString(endData)));
// 记录日志 - 未命中任何知识库
logRecord.setAnswer("该问题未记录在知识库中")
.setAnswerType(3)
.setIsStorage(0);
airagLogService.save(logRecord);
emitter.complete();
return;
}
// 构建知识库内容
StringBuilder content = new StringBuilder();
for (Map<String, Object> map : maps) {
if (Double.parseDouble(map.get("score").toString()) > 0.78) {
content.append(map.get("content").toString()).append("\n");
}
}
TextRankKeyword textRank = new TextRankKeyword();
List<String> keyWords = textRank.getKeywords(questionText, 5);
System.out.println("关键词...:" + keyWords);
// 获取第一个匹配的元数据用于日志和文件信息
Map<String, Object> firstMatch = maps.get(0);
String fileName = generateFileDocName(firstMatch.get("metadata").toString());
String storedFileName = generateFilePath(firstMatch.get("metadata").toString());
// 构建更优化的prompt
String prompt = String.format("你是一个严谨的信息处理助手,请严格按照以下要求处理用户问题:" + questionText + "\n\n" +
"处理步骤和要求:\n" +
"1. 严格基于参考内容回答,禁止任何超出参考内容的推断或想象\n" +
"2. 严格基于参考内容回答,禁止使用参考内容中与问题无关的内容\n" +
"3. 回答结构:\n" +
" - 首先用一句话直接回答问题核心(仅限参考内容中明确包含的信息)\n" +
" - 然后列出支持该答案的具体内容(可直接引用参考内容)\n" +
"4. 禁止以下行为:\n" +
" - 添加参考内容中不存在的信息\n" +
" - 在回答中提及‘参考内容’等字样\n" +
" - 在回答中提及其他产品的功能\n" +
" - 进行任何推测性陈述\n" +
" - 使用模糊或不确定的表达\n" +
" - 参考内容为空时应该拒绝回答\n" +
"参考内容(请严格限制回答范围于此):\n" + content);
List<ChatMessage> messages = new ArrayList<>();
messages.add(new UserMessage("user", prompt));
StringBuilder answerBuilder = new StringBuilder();
TokenStream tokenStream = aiChatHandler.chat(modelId, messages);
tokenStream.onNext(token -> {
try {
answerBuilder.append(token);
Map<String, String> data = new HashMap<>();
data.put("token", token);
emitter.send(SseEmitter.event().data(new ObjectMapper().writeValueAsString(data)));
} catch (Exception e) {
log.error("发送token失败", e);
}
});
tokenStream.onComplete(response -> {
try {
// 准备END事件数据
Map<String, String> endData = new HashMap<>();
endData.put("event", "END");
endData.put("similarity", firstMatch.get("score").toString());
endData.put("fileName", fileName);
endData.put("fileBase64", FileToBase64Util.fileToBase64(uploadPath + storedFileName));
emitter.send(SseEmitter.event().data(new ObjectMapper().writeValueAsString(endData)));
// 记录日志 - 从知识库生成回答
logRecord.setAnswer(answerBuilder.toString())
.setAnswerType(2);
System.out.println("回答内容 = " + answerBuilder);
airagLogService.save(logRecord);
emitter.complete();
} catch (Exception e) {
log.error("流式响应结束时发生错误", e);
}
});
tokenStream.onError(error -> {
log.error("生成答案失败", error);
// 记录日志 - 错误情况
logRecord.setAnswer("生成答案失败: " + error.getMessage())
.setAnswerType(4);
airagLogService.save(logRecord);
emitter.completeWithError(error);
});
tokenStream.start();
} catch (Exception e) {
log.error("处理请求时发生异常", e);
// 记录日志 - 异常情况
logRecord.setAnswer("处理请求时发生异常: " + e.getMessage())
.setAnswerType(4);
airagLogService.save(logRecord);
emitter.completeWithError(e);
}
});
return emitter;
}
private String generateFilePath(String metadataJson) throws Exception {
if (StringUtils.isEmpty(metadataJson)) {
return "";
}
ObjectMapper objectMapper = new ObjectMapper();
// 解析JSON字符串
Map<String, String> metadata = objectMapper.readValue(metadataJson, Map.class);
// 获取docName和docId
return metadata.get("storedFileName");
}
private String generateFileDocName(String metadataJson) throws Exception {
if (StringUtils.isEmpty(metadataJson)) {
return "";
}
ObjectMapper objectMapper = new ObjectMapper();
// 解析JSON字符串
Map<String, String> metadata = objectMapper.readValue(metadataJson, Map.class);
return metadata.get("docName");
}
}
... ...
... ... @@ -29,6 +29,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
... ... @@ -63,13 +64,20 @@ public class ZdyRagController {
@Operation(summary = "sendStream")
@GetMapping("sendStream")
public SseEmitter sendStream(String questionText) throws Exception {
public SseEmitter sendStream(@RequestParam("questionText") String questionText,
@RequestParam("code") String code,
@RequestParam("codeType") Integer codeType,
@RequestParam("user") String user
) throws Exception {
SseEmitter emitter = new SseEmitter(300000L);
// 创建日志对象
String modelId = "1926875898187878401";
AiragLog logRecord = new AiragLog()
.setQuestion(questionText)
.setCode(code)
.setCreateBy(user)
.setCodeType(codeType)
.setModelId(modelId)
.setCreateTime(new Date());
... ... @@ -291,7 +299,7 @@ public class ZdyRagController {
StringBuilder content = new StringBuilder();
for (Map<String, Object> map : maps) {
if (Double.parseDouble(map.get("score").toString()) > 0.75){
if (Double.parseDouble(map.get("score").toString()) > 0.78){
log.info("score = " + map.get("score").toString());
log.info("content = " + map.get("content").toString());
content.append(map.get("content").toString()).append("\n");
... ...