소스 검색

获取二级标题下三级标题列表

lamphua 4 일 전
부모
커밋
2a981696ae

+ 2
- 2
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/CmcDocumentController.java 파일 보기

73
      * @return
73
      * @return
74
      */
74
      */
75
     @PostMapping("/upload")
75
     @PostMapping("/upload")
76
-    public AjaxResult upload(MultipartFile[] fileList) throws IOException
76
+    public AjaxResult upload(MultipartFile[] fileList, String agentName) throws IOException
77
     {
77
     {
78
-        return success(cmcDocumentService.uploadDocument(fileList));
78
+        return success(cmcDocumentService.uploadDocument(fileList, agentName));
79
     }
79
     }
80
 
80
 
81
     /**
81
     /**

+ 2
- 0
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/McpController.java 파일 보기

1
 package com.ruoyi.web.llm.controller;
1
 package com.ruoyi.web.llm.controller;
2
 
2
 
3
 import com.alibaba.fastjson2.JSONObject;
3
 import com.alibaba.fastjson2.JSONObject;
4
+import com.ruoyi.common.config.RuoYiConfig;
4
 import com.ruoyi.common.core.controller.BaseController;
5
 import com.ruoyi.common.core.controller.BaseController;
5
 import com.ruoyi.web.llm.service.ILangChainMilvusService;
6
 import com.ruoyi.web.llm.service.ILangChainMilvusService;
6
 import dev.langchain4j.model.embedding.EmbeddingModel;
7
 import dev.langchain4j.model.embedding.EmbeddingModel;
78
     @GetMapping("/answerWithDocument")
79
     @GetMapping("/answerWithDocument")
79
     public Flux<AssistantMessage> answerWithDocumentAndCollection(String collectionName, String topicId, String chatId, String question) throws IOException
80
     public Flux<AssistantMessage> answerWithDocumentAndCollection(String collectionName, String topicId, String chatId, String question) throws IOException
80
     {
81
     {
82
+        question = String.join(",", langChainMilvusService.extractSubTitles(RuoYiConfig.getProfile() + "/upload/agent/template/technical.docx", question));
81
         List<JSONObject> requests = langChainMilvusService.retrieveFromMilvus(milvusClient, embeddingModel, collectionName, question, 10);
83
         List<JSONObject> requests = langChainMilvusService.retrieveFromMilvus(milvusClient, embeddingModel, collectionName, question, 10);
82
         return langChainMilvusService.generateAnswerWithDocumentAndCollection(embeddingModel, topicId, chatId, question, requests, "http://192.168.28.188:8000/v1/chat/completions");
84
         return langChainMilvusService.generateAnswerWithDocumentAndCollection(embeddingModel, topicId, chatId, question, requests, "http://192.168.28.188:8000/v1/chat/completions");
83
     }
85
     }

+ 5
- 0
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/service/ILangChainMilvusService.java 파일 보기

55
      */
55
      */
56
     public Flux<AssistantMessage> generateAnswerWithDocumentAndCollection(EmbeddingModel embeddingModel, String topicId, String chatId, String question,  List<JSONObject> requests, String llmServiceUrl) throws IOException;
56
     public Flux<AssistantMessage> generateAnswerWithDocumentAndCollection(EmbeddingModel embeddingModel, String topicId, String chatId, String question,  List<JSONObject> requests, String llmServiceUrl) throws IOException;
57
 
57
 
58
+    /**
59
+     * 获取二级标题下三级标题列表
60
+     * @return
61
+     */
62
+    public List<String> extractSubTitles(String filename, String question) throws IOException;
58
 }
63
 }

+ 65
- 23
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/service/impl/LangChainMilvusServiceImpl.java 파일 보기

13
 import dev.langchain4j.data.embedding.Embedding;
13
 import dev.langchain4j.data.embedding.Embedding;
14
 import dev.langchain4j.data.segment.TextSegment;
14
 import dev.langchain4j.data.segment.TextSegment;
15
 import dev.langchain4j.model.embedding.EmbeddingModel;
15
 import dev.langchain4j.model.embedding.EmbeddingModel;
16
+import dev.langchain4j.store.embedding.EmbeddingMatch;
16
 import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
17
 import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
17
 import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
18
 import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
18
 import io.milvus.client.MilvusClient;
19
 import io.milvus.client.MilvusClient;
28
 import io.milvus.response.SearchResultsWrapper;
29
 import io.milvus.response.SearchResultsWrapper;
29
 import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
30
 import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
30
 import org.apache.poi.xwpf.usermodel.XWPFDocument;
31
 import org.apache.poi.xwpf.usermodel.XWPFDocument;
32
+import org.apache.poi.xwpf.usermodel.XWPFParagraph;
31
 import org.noear.solon.ai.chat.ChatModel;
33
 import org.noear.solon.ai.chat.ChatModel;
32
 import org.noear.solon.ai.chat.ChatResponse;
34
 import org.noear.solon.ai.chat.ChatResponse;
33
 import org.noear.solon.ai.chat.ChatSession;
35
 import org.noear.solon.ai.chat.ChatSession;
195
         List<CmcDocument> documentList = cmcDocumentService.selectCmcDocumentList(cmcDocument);
197
         List<CmcDocument> documentList = cmcDocumentService.selectCmcDocumentList(cmcDocument);
196
         StringBuilder sb = new StringBuilder("问题: " + question + "\n\n").append("根据以下上下文回答问题:\n\n");
198
         StringBuilder sb = new StringBuilder("问题: " + question + "\n\n").append("根据以下上下文回答问题:\n\n");
197
         for (CmcDocument document : documentList) {
199
         for (CmcDocument document : documentList) {
198
-            File profilePath = new File(RuoYiConfig.getProfile() + "/upload/rag/document/" + document.getPath());
200
+            File profilePath = new File(RuoYiConfig.getProfile() + document.getPath());
199
             List<TextSegment> segments = splitDocument(document.getPath(), profilePath);
201
             List<TextSegment> segments = splitDocument(document.getPath(), profilePath);
200
             List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
202
             List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
201
             InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
203
             InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
205
                     .queryEmbedding(queryEmbedding)
207
                     .queryEmbedding(queryEmbedding)
206
                     .maxResults(1)
208
                     .maxResults(1)
207
                     .build();
209
                     .build();
208
-            String contexts = embeddingStore.search(embeddingSearchRequest).matches().get(0).embedded().text();
209
-            sb.append("文件").append(": ")
210
-                    .append(document.getPath()).append("\n\n")
211
-                    .append("上下文").append(": ")
212
-                    .append(contexts).append("\n\n");
210
+            for (EmbeddingMatch embeddingMatch : embeddingStore.search(embeddingSearchRequest).matches()) {
211
+                String contexts = embeddingMatch.embedded().toString();
212
+                sb.append("文件").append(": ")
213
+                        .append(document.getPath()).append("\n\n")
214
+                        .append("上下文").append(": ")
215
+                        .append(contexts).append("\n\n");
216
+            }
213
         }
217
         }
214
         return generateAnswer(topicId, sb.toString(), llmServiceUrl);
218
         return generateAnswer(topicId, sb.toString(), llmServiceUrl);
215
     }
219
     }
218
      * 调用LLM生成回答
222
      * 调用LLM生成回答
219
      */
223
      */
220
     @Override
224
     @Override
221
-    public Flux<AssistantMessage> generateAnswerWithDocumentAndCollection(EmbeddingModel embeddingModel, String topicId, String chatId, String question, List<JSONObject> requests, String llmServiceUrl) throws IOException {
225
+    public Flux<AssistantMessage> generateAnswerWithDocumentAndCollection(EmbeddingModel embeddingModel, String topicId, String chatId, String question, List<JSONObject> contexts, String llmServiceUrl) throws IOException {
226
+        StringBuilder sb = new StringBuilder("招标文件内容:\n\n");
222
         CmcDocument cmcDocument = new CmcDocument();
227
         CmcDocument cmcDocument = new CmcDocument();
223
         cmcDocument.setChatId(chatId);
228
         cmcDocument.setChatId(chatId);
224
         List<CmcDocument> documentList = cmcDocumentService.selectCmcDocumentList(cmcDocument);
229
         List<CmcDocument> documentList = cmcDocumentService.selectCmcDocumentList(cmcDocument);
225
-        StringBuilder sb = new StringBuilder("问题: " + question + "\n\n").append("根据以下要求:\n\n");
226
-        for (JSONObject request : requests) {
227
-            sb.append("要求").append(": ")
228
-                    .append(request.getString("content")).append("\n\n");
229
-        }
230
-        sb.append("参考以下文件上下文回答问题:\n\n");
231
         for (CmcDocument document : documentList) {
230
         for (CmcDocument document : documentList) {
232
-            File profilePath = new File(RuoYiConfig.getProfile() + "/upload/rag/document/" + document.getPath());
231
+            File profilePath = new File(RuoYiConfig.getProfile() + document.getPath());
233
             List<TextSegment> segments = splitDocument(document.getPath(), profilePath);
232
             List<TextSegment> segments = splitDocument(document.getPath(), profilePath);
234
             List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
233
             List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
235
             InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
234
             InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
237
             Embedding queryEmbedding = embeddingModel.embed(question).content();
236
             Embedding queryEmbedding = embeddingModel.embed(question).content();
238
             EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
237
             EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
239
                     .queryEmbedding(queryEmbedding)
238
                     .queryEmbedding(queryEmbedding)
240
-                    .maxResults(1)
239
+                    .maxResults(3)
241
                     .build();
240
                     .build();
242
-            String contexts = embeddingStore.search(embeddingSearchRequest).matches().get(0).embedded().text();
243
-            sb.append("文件").append(": ")
244
-                    .append(document.getPath()).append("\n\n")
245
-                    .append("上下文").append(": ")
246
-                    .append(contexts).append("\n\n");
241
+            for (EmbeddingMatch embeddingMatch : embeddingStore.search(embeddingSearchRequest).matches()) {
242
+                String requests = embeddingMatch.embedded().toString();
243
+                sb.append(requests).append("\n\n");
244
+            }
247
         }
245
         }
246
+        sb.append("针对本项目招标文件内容,补全以下章节部分:\n\n").append(question);
247
+//        for (JSONObject context : contexts) {
248
+//            sb.append("文件").append(": ")
249
+//                    .append(context.getString("file_name")).append("\n\n")
250
+//                    .append("段落格式").append(": ")
251
+//                    .append(context.getString("content")).append("\n\n");
252
+//        }
248
         return generateAnswer(topicId, sb.toString(), llmServiceUrl);
253
         return generateAnswer(topicId, sb.toString(), llmServiceUrl);
249
     }
254
     }
250
 
255
 
256
+    /**
257
+     * 获取二级标题下三级标题列表
258
+     */
259
+    @Override
260
+    public List<String> extractSubTitles(String filename, String question) throws IOException {
261
+        List<String> subTitles = new ArrayList<>();
262
+        boolean inTargetSection = false;
263
+
264
+        InputStream fileInputStream = new FileInputStream(filename);
265
+        XWPFDocument document = new XWPFDocument(fileInputStream);
266
+        for (XWPFParagraph paragraph : document.getParagraphs()) {
267
+            String text = paragraph.getText().trim();
268
+            if (paragraph.getStyle() != null) {
269
+                // 判断主标题
270
+                if (paragraph.getStyle().equals("3") &&
271
+                        text.contains(question)) {
272
+                    inTargetSection = true;
273
+                    continue;
274
+                }
275
+
276
+                // 如果已经在目标节中,收集标题3级别的子标题
277
+                if (inTargetSection) {
278
+                    if (paragraph.getStyle().equals("4")) {
279
+                        subTitles.add(text);
280
+                    }
281
+                    // 遇到下一个Heading1则退出
282
+                    else if (paragraph.getStyle().equals("3")) {
283
+                        break;
284
+                    }
285
+                }
286
+            }
287
+        }
288
+        if (subTitles.size() == 0)
289
+            subTitles.add(question);
290
+        return subTitles;
291
+    }
292
+
251
     /**
293
     /**
252
      * 检索知识库
294
      * 检索知识库
253
      */
295
      */
288
         return wrapper;
330
         return wrapper;
289
     }
331
     }
290
 
332
 
291
-
292
     /**
333
     /**
293
      * 检索知识库
334
      * 检索知识库
294
      */
335
      */
295
-    private List<TextSegment> splitDocument(String filename, File profilePath) throws IOException {
336
+    private List<TextSegment> splitDocument(String filename, File transferFile) throws IOException {
296
         // 加载文档
337
         // 加载文档
297
         Document document;
338
         Document document;
298
-        InputStream fileInputStream = new FileInputStream(profilePath);
339
+        InputStream fileInputStream = new FileInputStream(transferFile);
299
         filename = filename.toLowerCase();
340
         filename = filename.toLowerCase();
300
         if (filename.endsWith(".docx")) {
341
         if (filename.endsWith(".docx")) {
301
             XWPFDocument docx = new XWPFDocument(fileInputStream);
342
             XWPFDocument docx = new XWPFDocument(fileInputStream);
312
         DocumentByParagraphSplitter splitter = new DocumentByParagraphSplitter(1000,200);
353
         DocumentByParagraphSplitter splitter = new DocumentByParagraphSplitter(1000,200);
313
         return splitter.split(document);
354
         return splitter.split(document);
314
     }
355
     }
356
+
315
 }
357
 }

+ 2
- 1
llm-back/ruoyi-system/src/main/java/com/ruoyi/llm/service/ICmcDocumentService.java 파일 보기

35
      * 上传外部文件
35
      * 上传外部文件
36
      *
36
      *
37
      * @param fileList 文件
37
      * @param fileList 文件
38
+     * @param agentName 智能体名称
38
      * @return 结果
39
      * @return 结果
39
      */
40
      */
40
-    public JSONObject uploadDocument(MultipartFile[] fileList) throws IOException;
41
+    public JSONObject uploadDocument(MultipartFile[] fileList, String agentName) throws IOException;
41
 
42
 
42
     /**
43
     /**
43
      * 新增cmc聊天附件
44
      * 新增cmc聊天附件

+ 10
- 4
llm-back/ruoyi-system/src/main/java/com/ruoyi/llm/service/impl/CmcDocumentServiceImpl.java 파일 보기

66
      * 上传外部文件
66
      * 上传外部文件
67
      *
67
      *
68
      * @param fileList 文件
68
      * @param fileList 文件
69
+     * @param agentName 智能体名称
69
      * @return 结果
70
      * @return 结果
70
      */
71
      */
71
     @Override
72
     @Override
72
-    public JSONObject uploadDocument(MultipartFile[] fileList) throws IOException {
73
-        File profilePath = new File( RuoYiConfig.getProfile() + "/upload/rag/document" );
73
+    public JSONObject uploadDocument(MultipartFile[] fileList, String agentName) throws IOException {
74
+        String prefixPath = "";
75
+        if (!agentName.equals(""))
76
+            prefixPath = "/upload/agent/" + agentName;
77
+        else
78
+            prefixPath = "/upload/rag/document";
79
+        File profilePath = new File( RuoYiConfig.getProfile() + prefixPath);
74
         if (!profilePath.exists())
80
         if (!profilePath.exists())
75
             profilePath.mkdirs();
81
             profilePath.mkdirs();
76
         String chatId = new SnowFlake().generateId();
82
         String chatId = new SnowFlake().generateId();
77
         JSONObject jsonObject = new JSONObject();
83
         JSONObject jsonObject = new JSONObject();
78
         jsonObject.put("chatId", chatId);
84
         jsonObject.put("chatId", chatId);
79
         for (MultipartFile file : fileList) {
85
         for (MultipartFile file : fileList) {
80
-            File transferFile = new File(profilePath + File.separator + file.getOriginalFilename());
86
+            File transferFile = new File(profilePath + "/" + file.getOriginalFilename());
81
             if (!transferFile.exists())
87
             if (!transferFile.exists())
82
                 file.transferTo(transferFile);
88
                 file.transferTo(transferFile);
83
             CmcDocument cmcDocument = new CmcDocument();
89
             CmcDocument cmcDocument = new CmcDocument();
84
             cmcDocument.setDocumentId(new SnowFlake().generateId());
90
             cmcDocument.setDocumentId(new SnowFlake().generateId());
85
             cmcDocument.setChatId(chatId);
91
             cmcDocument.setChatId(chatId);
86
-            cmcDocument.setPath(file.getOriginalFilename());
92
+            cmcDocument.setPath(prefixPath + "/" + file.getOriginalFilename());
87
             cmcDocumentMapper.insertCmcDocument(cmcDocument);
93
             cmcDocumentMapper.insertCmcDocument(cmcDocument);
88
         }
94
         }
89
         return jsonObject;
95
         return jsonObject;

+ 2
- 1
llm-ui/src/api/llm/document.js 파일 보기

51
 
51
 
52
 
52
 
53
 // 上传聊天外部文件
53
 // 上传聊天外部文件
54
-export function uploadDocument(fileList) {
54
+export function uploadDocument(fileList, agentName) {
55
   const formData = new FormData()
55
   const formData = new FormData()
56
   for (let f of fileList) {
56
   for (let f of fileList) {
57
     formData.append('fileList', f)
57
     formData.append('fileList', f)
58
   }
58
   }
59
+  formData.append('agentName', agentName)
59
   return request({
60
   return request({
60
     url: '/llm/document/upload',
61
     url: '/llm/document/upload',
61
     method: 'post',
62
     method: 'post',

+ 3
- 3
llm-ui/src/views/llm/chat/index.vue 파일 보기

1
 <!--
1
 <!--
2
  * @Author: ysh
2
  * @Author: ysh
3
  * @Date: 2025-04-07 14:14:05
3
  * @Date: 2025-04-07 14:14:05
4
- * @LastEditors: Please set LastEditors
5
- * @LastEditTime: 2025-07-29 15:25:17
4
+ * @LastEditors: wrh
5
+ * @LastEditTime: 2025-07-29 17:12:19
6
 -->
6
 -->
7
 <template>
7
 <template>
8
   <div class="app-container">
8
   <div class="app-container">
590
     // 立即上传文件
590
     // 立即上传文件
591
     try {
591
     try {
592
       isUploading.value = true;
592
       isUploading.value = true;
593
-      let res = await uploadDocument(files);
593
+      let res = await uploadDocument(files, '');
594
       documentChatId.value = res.data.chatId;
594
       documentChatId.value = res.data.chatId;
595
       // 上传成功后,将文件添加到已上传列表
595
       // 上传成功后,将文件添加到已上传列表
596
       selectedFiles.value = [...selectedFiles.value, ...files];
596
       selectedFiles.value = [...selectedFiles.value, ...files];

Loading…
취소
저장