Просмотр исходного кода

升级到solon版本,上传文件自动生成多级章节

lamphua 3 недель назад
Родитель
Сommit
e974288b87

+ 1
- 7
llm-back/ruoyi-agent/pom.xml Просмотреть файл

@@ -17,7 +17,7 @@
17 17
     </description>
18 18
 
19 19
     <properties>
20
-        <solon.version>3.3.3</solon.version>
20
+        <solon.version>3.4.3</solon.version>
21 21
     </properties>
22 22
 
23 23
     <dependencies>
@@ -42,12 +42,6 @@
42 42
         <dependency>
43 43
             <groupId>org.noear</groupId>
44 44
             <artifactId>solon-ai-mcp</artifactId>
45
-            <exclusions>
46
-                <exclusion>
47
-                    <groupId>org.slf4j</groupId>
48
-                    <artifactId>*</artifactId>
49
-                </exclusion>
50
-            </exclusions>
51 45
         </dependency>
52 46
 
53 47
         <dependency>

+ 40
- 15
llm-back/ruoyi-agent/src/main/java/com/ruoyi/agent/service/impl/McpServiceImpl.java Просмотреть файл

@@ -36,6 +36,7 @@ import org.noear.solon.ai.chat.ChatSession;
36 36
 import org.noear.solon.ai.chat.ChatSessionDefault;
37 37
 import org.noear.solon.ai.chat.message.AssistantMessage;
38 38
 import org.noear.solon.ai.chat.message.ChatMessage;
39
+import org.noear.solon.ai.chat.session.InMemoryChatSession;
39 40
 import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint;
40 41
 import org.noear.solon.annotation.Param;
41 42
 import org.springframework.stereotype.Service;
@@ -50,11 +51,14 @@ public class McpServiceImpl implements IMcpService {
50 51
 
51 52
     private static final EmbeddingModel embeddingModel = new BgeSmallZhV15EmbeddingModel();
52 53
 
54
+    private static final String llmServiceUrl = "http://192.168.28.188:8000/v1/chat/completions";
55
+
53 56
     private static final MilvusServiceClient milvusClient = new MilvusServiceClient(
54 57
             ConnectParam.newBuilder()
55 58
                     .withHost("192.168.28.188")
56 59
                     .withPort(19530)
57 60
                     .build());
61
+
58 62
     /**
59 63
      * 调用LLM+RAG(外部文件+知识库)生成回答
60 64
      */
@@ -65,10 +69,11 @@ public class McpServiceImpl implements IMcpService {
65 69
                                            @Param(description = "技术文件地址") String templatePath) throws IOException
66 70
     {
67 71
             try {
68
-                title = String.join(",", extractSubTitles( "/upload/agent/template/technical.docx", title));
72
+                templatePath = templatePath.replace("/dev-api/profile", Solon.cfg().getProperty("cmc.profile"));
73
+                title = String.join(",", extractSubTitles(templatePath, title));
69 74
 //                List<JSONObject> contexts = retrieveFromMilvus(milvusClient, embeddingModel, collectionName, title, 10);
70
-//                return generateAnswerWithDocumentAndCollection(embeddingModel, agentName, templatePath, title, contexts, "http://192.168.28.188:8000/v1/chat/completions");
71
-                return generateAnswerWithDocumentAndCollection(embeddingModel, agentName, templatePath, title, new ArrayList<>(), "http://192.168.28.188:8000/v1/chat/completions");
75
+//                return generateAnswerWithDocumentAndCollection(embeddingModel, agentName, templatePath, title, contexts, llmServiceUrl);
76
+                return generateAnswerWithDocumentAndCollection(embeddingModel, agentName, templatePath, title, new ArrayList<>(), llmServiceUrl);
72 77
             } catch (IOException e) {
73 78
                 throw new RuntimeException(e);
74 79
             }
@@ -95,15 +100,15 @@ public class McpServiceImpl implements IMcpService {
95 100
      */
96 101
     public AssistantMessage generateAnswerWithDocumentAndCollection(EmbeddingModel embeddingModel, String agentName, String templatePath, String question, List<JSONObject> contexts, String llmServiceUrl) throws IOException {
97 102
         StringBuilder sb = new StringBuilder("招标文件内容:\n\n");
98
-        String filename = templatePath.replace("/dev-api/profile", Solon.cfg().getProperty("cmc.profile")).replace("_" + agentName, "");
103
+        String filename = templatePath.replace("_" + agentName, "");
99 104
         File profilePath = new File(filename);
100 105
         if (!profilePath.exists()) {
101 106
             filename = filename.replace(".docx", ".doc");
102 107
             profilePath = new File(filename);
103 108
         }
109
+        InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
104 110
         List<TextSegment> segments = splitDocument(profilePath);
105 111
         List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
106
-        InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
107 112
         embeddingStore.addAll(embeddings, segments);
108 113
         Embedding queryEmbedding = embeddingModel.embed(question).content();
109 114
         EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
@@ -132,17 +137,19 @@ public class McpServiceImpl implements IMcpService {
132 137
      * @return
133 138
      */
134 139
     public AssistantMessage generateAnswer(String prompt, String question, String templatePath, String llmServiceUrl) throws IOException {
135
-        ChatSession chatSession = new ChatSessionDefault();
136 140
         ChatModel chatModel = ChatModel.of(llmServiceUrl)
137
-                .provider("openai")
141
+                
138 142
                 .model("Qwen2.5-1.5B-Instruct")
139 143
                 .build();
140 144
 
141
-        chatSession.addMessage(ChatMessage.ofUser(prompt));
142
-
145
+        List<ChatMessage> messages = new ArrayList<>();
146
+        messages.add(ChatMessage.ofUser(prompt));
147
+        ChatSession chatSession =  InMemoryChatSession.builder().messages(messages).build();
143 148
         ChatResponse response = chatModel.prompt(chatSession).call();
144 149
         String content = response.lastChoice().getMessage().getContent() + "\n\n" +
145
-                "招标文件分析完成,章节内容已写入【<a href='" + templatePath + "'> 技术文件" + "</a>】,请查阅";
150
+                "招标文件分析完成,章节内容已写入【<a href='" + templatePath.replace(Solon.cfg().getProperty("cmc.profile"), "/dev-api/profile") + "'> 技术文件" + "</a>】,请查阅\n\n" +
151
+                "如需修改,请输入技术文件已有内容的章节标题\n\n" +
152
+                extractTitles(templatePath);
146 153
         String absolutePath = templatePath.replace("/dev-api/profile", Solon.cfg().getProperty("cmc.profile"));
147 154
         writeContent(response.lastChoice().getMessage().getContent(), question, absolutePath);
148 155
         return ChatMessage.ofAssistant(content);
@@ -153,13 +160,12 @@ public class McpServiceImpl implements IMcpService {
153 160
      * @return
154 161
      */
155 162
     public void writeContent(String content, String question, String absolutePath) throws IOException {
163
+        String[] contentLines = content.split("\n");
164
+        Map<String, String> map = new HashMap<>();
165
+        String[] titles = question.split(",");
156 166
         File file = new File(absolutePath);
157 167
         FileInputStream fileInputStream = new FileInputStream(file);
158 168
         try (XWPFDocument document = new XWPFDocument(fileInputStream)) {
159
-            String[] contentLines = content.split("\n");
160
-            Map<String, String> map = new HashMap<>();
161
-            String[] titles = question.split(",");
162
-
163 169
             for (int i = 0; i < titles.length; i++) {
164 170
                 int startIndex = Arrays.asList(contentLines).indexOf(titles[i]);
165 171
                 StringBuilder text = new StringBuilder();
@@ -217,7 +223,6 @@ public class McpServiceImpl implements IMcpService {
217 223
     public List<String> extractSubTitles(String filename, String question) throws IOException {
218 224
         List<String> subTitles = new ArrayList<>();
219 225
         boolean inTargetSection = false;
220
-        filename = Solon.cfg().getProperty("cmc.profile") + filename;
221 226
         InputStream fileInputStream = new FileInputStream(filename);
222 227
         try (XWPFDocument document = new XWPFDocument(fileInputStream)) {
223 228
             for (XWPFParagraph paragraph : document.getParagraphs()) {
@@ -248,6 +253,26 @@ public class McpServiceImpl implements IMcpService {
248 253
         return subTitles;
249 254
     }
250 255
 
256
+    /**
257
+     * 获取二、三级标题列表
258
+     */
259
+    public String extractTitles(String filename) throws IOException {
260
+        StringBuilder subTitles = new StringBuilder();
261
+        InputStream fileInputStream = new FileInputStream(filename);
262
+        try (XWPFDocument document = new XWPFDocument(fileInputStream)) {
263
+            for (XWPFParagraph paragraph : document.getParagraphs()) {
264
+                String text = paragraph.getText().trim();
265
+                if (paragraph.getStyle() != null) {
266
+                    // 判断主标题
267
+                    if (paragraph.getStyle().equals("3") || paragraph.getStyle().equals("4") ) {
268
+                        subTitles.append(text).append("\n");
269
+                    }
270
+                }
271
+            }
272
+        }
273
+        return subTitles.toString();
274
+    }
275
+
251 276
     /**
252 277
      * 检索知识库
253 278
      */

+ 2
- 8
llm-back/ruoyi-llm/pom.xml Просмотреть файл

@@ -45,7 +45,7 @@
45 45
         <dependency>
46 46
             <groupId>dev.langchain4j</groupId>
47 47
             <artifactId>langchain4j-milvus</artifactId>
48
-            <version>0.35.0</version> <!-- 版本需与核心一致 -->
48
+            <version>0.35.0</version>
49 49
         </dependency>
50 50
 
51 51
         <!-- LangChain4j embedding 集成 -->
@@ -73,13 +73,7 @@
73 73
         <dependency>
74 74
             <groupId>org.noear</groupId>
75 75
             <artifactId>solon-ai-mcp</artifactId>
76
-            <version>3.3.1</version>
77
-            <exclusions>
78
-                <exclusion>
79
-                    <groupId>org.slf4j</groupId>
80
-                    <artifactId>*</artifactId>
81
-                </exclusion>
82
-            </exclusions>
76
+            <version>3.4.3</version>
83 77
         </dependency>
84 78
 
85 79
     </dependencies>

+ 4
- 2
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/CmcTopicController.java Просмотреть файл

@@ -113,7 +113,8 @@ public class CmcTopicController extends BaseController
113 113
             String[] chatIds = new String[cmcChatList.size()];
114 114
             for (int i = 0; i < cmcChatList.size(); i++)
115 115
                 chatIds[i] = cmcChatList.get(i).getChatId();
116
-            cmcChatService.deleteCmcChatByChatIds(chatIds);
116
+            if (chatIds.length > 0)
117
+                cmcChatService.deleteCmcChatByChatIds(chatIds);
117 118
             for (String chatId : chatIds) {
118 119
                 CmcDocument cmcDocument = new CmcDocument();
119 120
                 cmcDocument.setChatId(chatId);
@@ -121,7 +122,8 @@ public class CmcTopicController extends BaseController
121 122
                 String[] documentIds = new String[cmcDocumentList.size()];
122 123
                 for (int i = 0; i < cmcDocumentList.size(); i++)
123 124
                     documentIds[i] = cmcDocumentList.get(i).getDocumentId();
124
-                cmcDocumentService.deleteCmcDocumentByDocumentIds(documentIds);
125
+                if (documentIds.length > 0)
126
+                    cmcDocumentService.deleteCmcDocumentByDocumentIds(documentIds);
125 127
             }
126 128
         }
127 129
         return success(cmcTopicService.deleteCmcTopicByTopicIds(topicIds));

+ 13
- 13
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/McpController.java Просмотреть файл

@@ -15,9 +15,9 @@ import io.milvus.param.ConnectParam;
15 15
 import org.noear.solon.ai.chat.ChatModel;
16 16
 import org.noear.solon.ai.chat.ChatResponse;
17 17
 import org.noear.solon.ai.chat.ChatSession;
18
-import org.noear.solon.ai.chat.ChatSessionDefault;
19 18
 import org.noear.solon.ai.chat.message.AssistantMessage;
20 19
 import org.noear.solon.ai.chat.message.ChatMessage;
20
+import org.noear.solon.ai.chat.session.InMemoryChatSession;
21 21
 import org.noear.solon.ai.mcp.client.McpClientProvider;
22 22
 import org.springframework.beans.factory.annotation.Autowired;
23 23
 import org.springframework.web.bind.annotation.GetMapping;
@@ -54,6 +54,8 @@ public class McpController extends BaseController
54 54
 
55 55
     private static final EmbeddingModel embeddingModel = new BgeSmallZhV15EmbeddingModel();
56 56
 
57
+    private static final String llmServiceUrl = "http://192.168.28.188:8000/v1/chat/completions";
58
+
57 59
     private static final MilvusServiceClient milvusClient = new MilvusServiceClient(
58 60
             ConnectParam.newBuilder()
59 61
                     .withHost("192.168.28.188")
@@ -69,8 +71,7 @@ public class McpController extends BaseController
69 71
 //        McpClientProvider clientProvider = McpClientProvider.builder()
70 72
 //                .apiUrl("http://localhost:8080/llm/mcp/sse")
71 73
 //                .build();
72
-//        ChatModel chatModel = ChatModel.of("http://192.168.28.188:8000/v1/chat/completions")
73
-//                .provider("openai")
74
+//        ChatModel chatModel = ChatModel.of(llmServiceUrl)
74 75
 //                .model("Qwen2.5-1.5B-Instruct")
75 76
 //                .defaultToolsAdd(clientProvider)
76 77
 //                .build();
@@ -97,22 +98,21 @@ public class McpController extends BaseController
97 98
         McpClientProvider clientProvider = McpClientProvider.builder()
98 99
                 .apiUrl("http://localhost:8080/llm/mcp/sse")
99 100
                 .build();
100
-        ChatSession chatSession = new ChatSessionDefault(topicId);
101
-        ChatModel chatModel = ChatModel.of("http://192.168.28.188:8000/v1/chat/completions")
102
-                .provider("openai")
101
+        ChatModel chatModel = ChatModel.of(llmServiceUrl)
103 102
                 .model("Qwen2.5-1.5B-Instruct")
104 103
                 .defaultToolsAdd(clientProvider)
105 104
                 .build();
106 105
 
106
+        List<ChatMessage> messages = new ArrayList<>();
107 107
         CmcChat cmcChat = new CmcChat();
108 108
         cmcChat.setTopicId(topicId);
109 109
         List<CmcChat> cmcChatList = cmcChatService.selectCmcChatList(cmcChat);
110 110
         for (CmcChat chat : cmcChatList) {
111
-            chatSession.addMessage(ChatMessage.ofUser(chat.getInput()));
112
-            chatSession.addMessage(ChatMessage.ofAssistant(chat.getOutput()));
111
+            messages.add(ChatMessage.ofUser(chat.getInput()));
112
+            messages.add(ChatMessage.ofAssistant(chat.getOutput()));
113 113
         }
114
-
115
-        chatSession.addMessage(ChatMessage.ofUser(question));
114
+        messages.add(ChatMessage.ofUser(question));
115
+        ChatSession chatSession =  InMemoryChatSession.builder().messages(messages).build();
116 116
         ChatResponse response = chatModel.prompt(chatSession).call();
117 117
         String resultContent = response.lastChoice().getMessage().getResultContent();
118 118
         AssistantMessage assistantMessage;
@@ -142,7 +142,7 @@ public class McpController extends BaseController
142 142
     {
143 143
         question = String.join(",", langChainMilvusService.extractSubTitles(RuoYiConfig.getProfile() + "/upload/agent/template/technical.docx", question));
144 144
         List<JSONObject> requests = langChainMilvusService.retrieveFromMilvus(milvusClient, embeddingModel, collectionName, question, 10);
145
-        return langChainMilvusService.generateAnswerWithDocumentAndCollection(embeddingModel, topicId, question, requests, "http://192.168.28.188:8000/v1/chat/completions");
145
+        return langChainMilvusService.generateAnswerWithDocumentAndCollection(embeddingModel, topicId, question, requests, llmServiceUrl);
146 146
     }
147 147
 
148 148
     /**
@@ -154,8 +154,8 @@ public class McpController extends BaseController
154 154
         McpClientProvider clientProvider = McpClientProvider.builder()
155 155
                     .apiUrl("http://localhost:8080/llm/mcp/sse")
156 156
                     .build();
157
-        ChatModel chatModel = ChatModel.of("http://192.168.28.188:8000/v1/chat/completions")
158
-                .provider("openai")
157
+        ChatModel chatModel = ChatModel.of(llmServiceUrl)
158
+                
159 159
                 .model("DeepSeek-R1-Distill-Qwen-1.5B")
160 160
                 .defaultToolsAdd(clientProvider)
161 161
                 .build();

+ 14
- 12
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/service/impl/LangChainMilvusServiceImpl.java Просмотреть файл

@@ -35,9 +35,9 @@ import org.apache.poi.xwpf.usermodel.XWPFParagraph;
35 35
 import org.noear.solon.ai.chat.ChatModel;
36 36
 import org.noear.solon.ai.chat.ChatResponse;
37 37
 import org.noear.solon.ai.chat.ChatSession;
38
-import org.noear.solon.ai.chat.ChatSessionDefault;
39 38
 import org.noear.solon.ai.chat.message.AssistantMessage;
40 39
 import org.noear.solon.ai.chat.message.ChatMessage;
40
+import org.noear.solon.ai.chat.session.InMemoryChatSession;
41 41
 import org.reactivestreams.Publisher;
42 42
 import org.springframework.beans.factory.annotation.Autowired;
43 43
 import org.springframework.stereotype.Service;
@@ -147,23 +147,23 @@ public class LangChainMilvusServiceImpl implements ILangChainMilvusService
147 147
      */
148 148
     @Override
149 149
     public Flux<AssistantMessage> generateAnswer(String topicId, String prompt, String llmServiceUrl) {
150
-        ChatSession chatSession = new ChatSessionDefault(topicId);
151 150
         ChatModel chatModel = ChatModel.of(llmServiceUrl)
152
-                .provider("openai")
151
+                
153 152
                 .model("Qwen2.5-1.5B-Instruct")
154 153
                 .build();
155 154
 
155
+        List<ChatMessage> messages = new ArrayList<>();
156 156
         if (topicId != null) {
157 157
             CmcChat cmcChat = new CmcChat();
158 158
             cmcChat.setTopicId(topicId);
159 159
             List<CmcChat> cmcChatList = cmcChatService.selectCmcChatList(cmcChat);
160 160
             for (CmcChat chat : cmcChatList) {
161
-                chatSession.addMessage(ChatMessage.ofUser(chat.getInput()));
162
-                chatSession.addMessage(ChatMessage.ofAssistant(chat.getOutput()));
161
+                messages.add(ChatMessage.ofUser(chat.getInput()));
162
+                messages.add(ChatMessage.ofAssistant(chat.getOutput()));
163 163
             }
164 164
         }
165
-        chatSession.addMessage(ChatMessage.ofUser(prompt));
166
-
165
+        messages.add(ChatMessage.ofUser(prompt));
166
+        ChatSession chatSession =  InMemoryChatSession.builder().messages(messages).build();
167 167
         Publisher<ChatResponse> publisher = chatModel.prompt(chatSession).stream();
168 168
         return Flux.from(publisher)
169 169
                 .map(response -> {
@@ -200,9 +200,9 @@ public class LangChainMilvusServiceImpl implements ILangChainMilvusService
200 200
         StringBuilder sb = new StringBuilder("问题: " + question + "\n\n").append("根据以下上下文回答问题:\n\n");
201 201
         for (CmcDocument document : documentList) {
202 202
             File profilePath = new File(RuoYiConfig.getProfile() + document.getPath());
203
+            InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
203 204
             List<TextSegment> segments = splitDocument(profilePath);
204 205
             List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
205
-            InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
206 206
             embeddingStore.addAll(embeddings, segments);
207 207
             Embedding queryEmbedding = embeddingModel.embed(question).content();
208 208
             EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
@@ -238,16 +238,18 @@ public class LangChainMilvusServiceImpl implements ILangChainMilvusService
238 238
             if (documentList.size() == 1) {
239 239
                 for (CmcDocument document : documentList) {
240 240
                     File profilePath = new File(RuoYiConfig.getProfile() + document.getPath());
241
+                    InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
241 242
                     List<TextSegment> segments = splitDocument(profilePath);
242 243
                     List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
243
-                    InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
244 244
                     embeddingStore.addAll(embeddings, segments);
245 245
                     Embedding queryEmbedding = embeddingModel.embed(question).content();
246 246
                     EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
247 247
                             .queryEmbedding(queryEmbedding)
248
-                            .maxResults(3)
248
+                            .minScore(0.7)
249 249
                             .build();
250
-                    for (EmbeddingMatch embeddingMatch : embeddingStore.search(embeddingSearchRequest).matches()) {
250
+                    List<EmbeddingMatch<TextSegment>> results = embeddingStore.search(embeddingSearchRequest).matches();
251
+                    results.sort(Comparator.comparingDouble(EmbeddingMatch<TextSegment>::score).reversed());
252
+                    for (EmbeddingMatch<TextSegment> embeddingMatch : results) {
251 253
                         String requests = embeddingMatch.embedded().toString();
252 254
                         sb.append(requests).append("\n\n");
253 255
                     }
@@ -345,7 +347,7 @@ public class LangChainMilvusServiceImpl implements ILangChainMilvusService
345 347
     /**
346 348
      * 检索知识库
347 349
      */
348
-    private List<TextSegment> splitDocument(File transferFile) throws IOException {
350
+    private List<TextSegment> splitDocument(File transferFile) throws FileNotFoundException {
349 351
         // 加载文档
350 352
         Document document;
351 353
         InputStream fileInputStream = new FileInputStream(transferFile);

+ 8
- 10
llm-back/ruoyi-system/pom.xml Просмотреть файл

@@ -39,26 +39,24 @@
39 39
             <groupId>dev.langchain4j</groupId>
40 40
             <artifactId>langchain4j-document-parser-apache-pdfbox</artifactId>
41 41
             <version>0.35.0</version>
42
-            <scope>compile</scope>
43 42
         </dependency>
44 43
 
45 44
         <dependency>
46 45
             <groupId>dev.langchain4j</groupId>
47 46
             <artifactId>langchain4j-embeddings-bge-small-zh-v15</artifactId>
48 47
             <version>0.35.0</version>
49
-            <scope>compile</scope>
50 48
         </dependency>
51 49
 
52 50
         <dependency>
53 51
             <groupId>org.noear</groupId>
54
-            <artifactId>solon-ai-mcp</artifactId>
55
-            <version>3.3.1</version>
56
-            <exclusions>
57
-                <exclusion>
58
-                    <groupId>org.slf4j</groupId>
59
-                    <artifactId>*</artifactId>
60
-                </exclusion>
61
-            </exclusions>
52
+            <artifactId>solon-ai</artifactId>
53
+            <version>3.4.3</version>
54
+        </dependency>
55
+
56
+        <dependency>
57
+            <groupId>io.milvus</groupId>
58
+            <artifactId>milvus-sdk-java</artifactId>
59
+            <version>2.3.3</version>
62 60
         </dependency>
63 61
 
64 62
     </dependencies>

+ 127
- 26
llm-back/ruoyi-system/src/main/java/com/ruoyi/llm/service/impl/CmcAgentServiceImpl.java Просмотреть файл

@@ -2,6 +2,7 @@ package com.ruoyi.llm.service.impl;
2 2
 
3 3
 import java.io.*;
4 4
 import java.util.*;
5
+import java.util.stream.Collectors;
5 6
 
6 7
 import com.alibaba.fastjson2.JSONObject;
7 8
 import com.ruoyi.common.config.RuoYiConfig;
@@ -23,6 +24,16 @@ import dev.langchain4j.model.embedding.onnx.bgesmallzhv15.BgeSmallZhV15Embedding
23 24
 import dev.langchain4j.store.embedding.EmbeddingMatch;
24 25
 import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
25 26
 import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
27
+import io.milvus.client.MilvusServiceClient;
28
+import io.milvus.grpc.SearchResults;
29
+import io.milvus.param.ConnectParam;
30
+import io.milvus.param.MetricType;
31
+import io.milvus.param.R;
32
+import io.milvus.param.RpcStatus;
33
+import io.milvus.param.collection.LoadCollectionParam;
34
+import io.milvus.param.collection.ReleaseCollectionParam;
35
+import io.milvus.param.dml.SearchParam;
36
+import io.milvus.response.SearchResultsWrapper;
26 37
 import org.apache.poi.extractor.ExtractorFactory;
27 38
 import org.apache.poi.extractor.POITextExtractor;
28 39
 import org.apache.poi.xwpf.usermodel.BreakType;
@@ -32,8 +43,8 @@ import org.apache.poi.xwpf.usermodel.XWPFRun;
32 43
 import org.noear.solon.ai.chat.ChatModel;
33 44
 import org.noear.solon.ai.chat.ChatResponse;
34 45
 import org.noear.solon.ai.chat.ChatSession;
35
-import org.noear.solon.ai.chat.ChatSessionDefault;
36 46
 import org.noear.solon.ai.chat.message.ChatMessage;
47
+import org.noear.solon.ai.chat.session.InMemoryChatSession;
37 48
 import org.springframework.beans.factory.annotation.Autowired;
38 49
 import org.springframework.stereotype.Service;
39 50
 import com.ruoyi.llm.mapper.CmcAgentMapper;
@@ -63,6 +74,12 @@ public class CmcAgentServiceImpl implements ICmcAgentService
63 74
 
64 75
     private static final String llmServiceUrl = "http://192.168.28.188:8000/v1/chat/completions";
65 76
 
77
+    private static final MilvusServiceClient milvusClient = new MilvusServiceClient(
78
+            ConnectParam.newBuilder()
79
+                    .withHost("192.168.28.188")
80
+                    .withPort(19530)
81
+                    .build());
82
+
66 83
     /**
67 84
      * 查询智能体
68 85
      * 
@@ -144,8 +161,8 @@ public class CmcAgentServiceImpl implements ICmcAgentService
144 161
                     doc.write(out);
145 162
                 }
146 163
             }
147
-            String chapters = generateAnswerWithDocument(embeddingModel, profilePath + "/" + file.getOriginalFilename(),
148
-                    RuoYiConfig.getProfile() + outputFilename, "工作大纲", llmServiceUrl);
164
+            String chapters = generateAnswerWithDocument(profilePath + "/" + file.getOriginalFilename(),
165
+                    RuoYiConfig.getProfile() + outputFilename, "工作大纲");
149 166
             message = "好的,我已经收到您上传的招标文件,我将给您提供技术文件模板,您可点击进行预览:" +
150 167
                     "【<a href='/profile" + outputFilename + "'> 模版 " + "</a>】\n\n" +
151 168
                     chapters + "\n\n" +
@@ -227,12 +244,12 @@ public class CmcAgentServiceImpl implements ICmcAgentService
227 244
     /**
228 245
      * 调用LLM生成回答
229 246
      */
230
-    public String generateAnswerWithDocument(EmbeddingModel embeddingModel, String uploadFilePath, String templatePath, String question, String llmServiceUrl) throws IOException {
247
+    public String generateAnswerWithDocument(String uploadFilePath, String templatePath, String question) throws IOException {
231 248
         StringBuilder sb = new StringBuilder("招标文件内容:\n\n");
232 249
         File profilePath = new File(uploadFilePath);
250
+        InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
233 251
         List<TextSegment> segments = splitDocument(profilePath);
234 252
         List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
235
-        InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
236 253
         embeddingStore.addAll(embeddings, segments);
237 254
         Embedding queryEmbedding = embeddingModel.embed(question).content();
238 255
         EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
@@ -249,26 +266,105 @@ public class CmcAgentServiceImpl implements ICmcAgentService
249 266
                 .append("6.1 XX\n" +
250 267
                 "6.2 XX\n" +
251 268
                 "6.3 XX");
252
-        return generateAnswer(sb.toString(), question, templatePath, llmServiceUrl);
269
+        return writeChapters(sb.toString(), templatePath);
270
+    }
271
+
272
+    /**
273
+     * 编排章节
274
+     * @return
275
+     */
276
+    public String writeChapters(String prompt, String templatePath) throws IOException {
277
+        String chapter2 = generateAnswer(prompt);
278
+
279
+        StringBuilder sb = new StringBuilder("二级标题如下:\n" + chapter2 + "\n 请根据参考内容编排三级标题,严格按以下格式列出")
280
+                .append(":\n").append("6.1.1 XX\n" +
281
+                        "6.1.2 XX\n" +
282
+                        "6.1.3 XX");
283
+        List<JSONObject> contexts = retrieveFromMilvus("technical", chapter2, 10);
284
+        for (JSONObject context : contexts) {
285
+            sb.append("参考内容").append(": ")
286
+                    .append(context.getString("content")).append("\n\n");
287
+        }
288
+        String chapter3 = generateAnswer(sb.toString());
289
+        sb = new StringBuilder("二级标题如下:\n" + chapter2 + "\n 三级标题如下:" + chapter3 + "帮我合并二三级标题,严格按以下格式列出")
290
+                .append(":\n").append("6.1 XX\n" +
291
+                        "6.1.1 XX\n" +
292
+                        "6.1.2 XX\n"+
293
+                        "6.2 XX\n" +
294
+                        "6.3 XX");
295
+        String content = generateAnswer(sb.toString());
296
+        writeContent(content, templatePath);
297
+        return content;
253 298
     }
254 299
 
255 300
     /**
256 301
      * 调用LLM生成回答
257 302
      * @return
258 303
      */
259
-    public String generateAnswer(String prompt, String question, String templatePath, String llmServiceUrl) throws IOException {
260
-        ChatSession chatSession = new ChatSessionDefault();
261
-        ChatModel chatModel = ChatModel.of(llmServiceUrl)
262
-                .provider("openai")
263
-                .model("Qwen2.5-1.5B-Instruct")
304
+    public String generateAnswer(String prompt) throws IOException {
305
+        ChatModel chatModel = ChatModel.of(llmServiceUrl).model("Qwen2.5-1.5B-Instruct").build();
306
+
307
+        List<ChatMessage> messages = new ArrayList<>();
308
+        messages.add(ChatMessage.ofUser(prompt));
309
+        ChatSession chatSession =  InMemoryChatSession.builder().messages(messages).build();
310
+        ChatResponse response = chatModel.prompt(chatSession).call();
311
+
312
+        return response.lastChoice().getMessage().getContent();
313
+    }
314
+
315
+    /**
316
+     * 从Milvus检索相关文档
317
+     * @return
318
+     */
319
+    public List<JSONObject> retrieveFromMilvus(String collectionName, String query, int topK) {
320
+        SearchResultsWrapper wrapper = retrieve(collectionName, query, topK);
321
+        return wrapper.getRowRecords(0).stream()
322
+                .map(record -> {
323
+                    JSONObject result = new JSONObject();
324
+                    result.put("content", record.get("content"));
325
+                    return result;
326
+                })
327
+                .collect(Collectors.toList());
328
+    }
329
+
330
+    /**
331
+     * 检索知识库
332
+     */
333
+    private SearchResultsWrapper retrieve(String collectionName, String query, int topK) {
334
+        List<List<Float>> queryVector = Collections.singletonList(embeddingModel.embed(query).content().vectorAsList());
335
+
336
+        //  加载集合
337
+        LoadCollectionParam loadParam = LoadCollectionParam.newBuilder()
338
+                .withCollectionName(collectionName)
264 339
                 .build();
265 340
 
266
-        chatSession.addMessage(ChatMessage.ofUser(prompt));
341
+        R<RpcStatus> loadResponse = milvusClient.loadCollection(loadParam);
342
+        if (loadResponse.getStatus() != R.Status.Success.getCode()) {
343
+            System.err.println("加载Collection失败: " + loadResponse.getMessage());
344
+            milvusClient.close();
345
+        }
267 346
 
268
-        ChatResponse response = chatModel.prompt(chatSession).call();
269
-        String content = response.lastChoice().getMessage().getContent();
270
-        writeContent(content, templatePath);
271
-        return content;
347
+        // 构建SearchParam
348
+        SearchParam searchParam = SearchParam.newBuilder()
349
+                .withCollectionName(collectionName)
350
+                .withVectors(queryVector)
351
+                .withTopK(topK)
352
+                .withOutFields(Arrays.asList("content"))
353
+                .withVectorFieldName("embedding")
354
+                .withMetricType(MetricType.COSINE)
355
+                .withParams("{\"nprobe\": 8}")
356
+                .build();
357
+
358
+        R<SearchResults> response = milvusClient.search(searchParam);
359
+        SearchResultsWrapper wrapper = new SearchResultsWrapper(response.getData().getResults());
360
+
361
+        // 释放集合
362
+        ReleaseCollectionParam param = ReleaseCollectionParam.newBuilder()
363
+                .withCollectionName(collectionName)
364
+                .build();
365
+        milvusClient.releaseCollection(param);
366
+
367
+        return wrapper;
272 368
     }
273 369
 
274 370
     /**
@@ -280,20 +376,25 @@ public class CmcAgentServiceImpl implements ICmcAgentService
280 376
         String[] contentLines = content.split("\n");
281 377
         for (String line : contentLines) {
282 378
             if (line.contains("6."))
283
-                chapters.add(line.replace("*", "").replace("#", "").split(" ")[1]);
379
+                chapters.add(line.replace("*", "").replace("#", "").replace("-", ""));
284 380
         }
285 381
         File file = new File(templatePath);
286 382
         FileInputStream fileInputStream = new FileInputStream(file);
287 383
         try (XWPFDocument document = new XWPFDocument(fileInputStream)) {
288
-            for (int i = 0; i < chapters.size(); i++) {
289
-                XWPFParagraph contentParagraph = document.createParagraph();
290
-                contentParagraph.setStyle("3");
291
-                XWPFRun run = contentParagraph.createRun();
292
-                run.setText(chapters.get(i));
293
-                run.addBreak();
294
-                if (i < chapters.size() - 1)
295
-                    run.addBreak(BreakType.PAGE);
296
-            }
384
+           for (int i = 0; i < chapters.size(); i++) {
385
+               XWPFParagraph contentParagraph = document.createParagraph();
386
+               contentParagraph.setStyle("3");
387
+               boolean title2 = chapters.get(i).indexOf(".") == chapters.get(i).lastIndexOf(".");
388
+               if (!title2)
389
+                   contentParagraph.setStyle("4");
390
+
391
+               XWPFRun run = contentParagraph.createRun();
392
+               if (chapters.get(i).contains(" "))
393
+                   run.setText(chapters.get(i).split(" ")[1]);
394
+               run.addBreak();
395
+               if (i < chapters.size() - 1 && chapters.get(i + 1).indexOf(".") == chapters.get(i + 1).lastIndexOf("."))
396
+                   run.addBreak(BreakType.PAGE);
397
+           }
297 398
 
298 399
             try (FileOutputStream out = new FileOutputStream(templatePath)) {
299 400
                 document.write(out);

Загрузка…
Отмена
Сохранить