|
@@ -0,0 +1,144 @@
|
|
|
+package cn.jlsxwkj.moudles.chat;
|
|
|
+
|
|
|
+import cn.hutool.crypto.digest.MD5;
|
|
|
+import cn.jlsxwkj.common.reader.ParagraphDocReader;
|
|
|
+import cn.jlsxwkj.common.reader.ParagraphTextReader;
|
|
|
+import cn.jlsxwkj.common.utils.Log;
|
|
|
+import cn.jlsxwkj.common.utils.MergeDocuments;
|
|
|
+import jakarta.annotation.Resource;
|
|
|
+import org.springframework.ai.document.Document;
|
|
|
+import org.springframework.ai.document.DocumentReader;
|
|
|
+import org.springframework.ai.ollama.OllamaChatModel;
|
|
|
+import org.springframework.ai.vectorstore.SearchRequest;
|
|
|
+import org.springframework.ai.vectorstore.VectorStore;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.web.multipart.MultipartFile;
|
|
|
+import reactor.core.publisher.Flux;
|
|
|
+
|
|
|
+import java.io.File;
|
|
|
+import java.io.IOException;
|
|
|
+import java.util.Locale;
|
|
|
+import java.util.Objects;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+
|
|
|
+@Service
|
|
|
+public class DocumentService {
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private VectorStore vectorStore;
|
|
|
+ @Resource
|
|
|
+ private OllamaChatModel ollamaChatModel;
|
|
|
+ private static final MD5 md5 = MD5.create();
|
|
|
+ private static final String PATH = Objects.requireNonNull(
|
|
|
+ DocumentService.class.getClassLoader().getResource("")
|
|
|
+ ).getPath() + "\\ai_doc\\";
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 使用spring ai解析txt文档并保存至 pg
|
|
|
+ *
|
|
|
+ * @param file 文档
|
|
|
+ * @throws IOException 如果解析文档异常删除已保存文件
|
|
|
+ */
|
|
|
+ public String uploadDocument(MultipartFile file) throws IOException {
|
|
|
+ var split = Objects.requireNonNull(file.getOriginalFilename()).split("\\.");
|
|
|
+ var fileType = split[split.length - 1].toLowerCase(Locale.ROOT);
|
|
|
+ var savePath = new File(PATH);
|
|
|
+ var saveFile = new File(PATH + DocumentService.md5.digestHex(file.getInputStream()) + "." + fileType);
|
|
|
+ var fileUrl = saveFile.toURI().toURL().toString();
|
|
|
+ if (!savePath.exists()) {
|
|
|
+ if (!savePath.mkdirs()) {
|
|
|
+ Log.warn(this.getClass(), "创建文件夹失败: " + PATH);
|
|
|
+ return "创建文件夹失败: " + PATH;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ DocumentReader reader;
|
|
|
+ switch (fileType) {
|
|
|
+ case "txt" -> reader = new ParagraphTextReader(fileUrl, 5);
|
|
|
+ case "doc", "docx" -> reader = new ParagraphDocReader(fileUrl, 5);
|
|
|
+ default -> {
|
|
|
+ Log.warn(this.getClass(), "暂不支持的文件类型: " + fileType);
|
|
|
+ return "暂不支持的文件类型: " + fileType;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 判断文件是否存在
|
|
|
+ if (!saveFile.exists()) {
|
|
|
+ try {
|
|
|
+ file.transferTo(saveFile);
|
|
|
+ vectorStore.add(reader.get());
|
|
|
+ } catch (Exception e){
|
|
|
+ boolean delete = saveFile.delete();
|
|
|
+ if (!delete) {
|
|
|
+ Log.warn(this.getClass(), "删除文件失败: ", fileUrl, e);
|
|
|
+ return "删除文件失败: " + fileUrl;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return "未知错误";
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据关键词搜索向量库
|
|
|
+ *
|
|
|
+ * @param keyword 关键词
|
|
|
+ * @return 文本内容
|
|
|
+ */
|
|
|
+ public String search(String keyword) {
|
|
|
+ //提取文本内容
|
|
|
+ return MergeDocuments.mergeDocuments(
|
|
|
+ vectorStore.similaritySearch(SearchRequest.query(keyword).withSimilarityThreshold(0.5))
|
|
|
+ ).stream().map(Document::getContent).collect(Collectors.joining("\n"));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 问答流,根据输入内容回答
|
|
|
+ *
|
|
|
+ * @param message 输入内容
|
|
|
+ * @return 回答内容
|
|
|
+ */
|
|
|
+ public Flux<String> chatStream(String message) {
|
|
|
+ //查询获取文档信息
|
|
|
+ var content = search(message);
|
|
|
+ //封装prompt并调用大模型
|
|
|
+ return ollamaChatModel.stream(getChatPrompt2String(message, content));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 问答,根据输入内容回答
|
|
|
+ *
|
|
|
+ * @param message 输入内容
|
|
|
+ * @return 回答内容
|
|
|
+ */
|
|
|
+ public String chat(String message) {
|
|
|
+ //查询获取文档信息
|
|
|
+ var content = search(message);
|
|
|
+
|
|
|
+ //封装prompt并调用大模型
|
|
|
+ return ollamaChatModel.call(
|
|
|
+ getChatPrompt2String(message, content)
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取prompt
|
|
|
+ *
|
|
|
+ * @param message 提问内容
|
|
|
+ * @param context 上下文
|
|
|
+ * @return 提示词
|
|
|
+ */
|
|
|
+ private String getChatPrompt2String(String message,
|
|
|
+ String context) {
|
|
|
+ var promptText = """
|
|
|
+ "%s"
|
|
|
+ 请参考以上内容回答问题 "%s" ,
|
|
|
+ 如内容不符可忽略
|
|
|
+ """;
|
|
|
+ if (context.isEmpty()) {
|
|
|
+ promptText = """
|
|
|
+ %s
|
|
|
+ """;
|
|
|
+ }
|
|
|
+
|
|
|
+ return String.format(promptText, message, context);
|
|
|
+ }
|
|
|
+}
|