Browse Source

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

lamphua 3 weeks ago
parent
commit
e974288b87

+ 1
- 7
llm-back/ruoyi-agent/pom.xml View File

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

+ 40
- 15
llm-back/ruoyi-agent/src/main/java/com/ruoyi/agent/service/impl/McpServiceImpl.java View File

36
 import org.noear.solon.ai.chat.ChatSessionDefault;
36
 import org.noear.solon.ai.chat.ChatSessionDefault;
37
 import org.noear.solon.ai.chat.message.AssistantMessage;
37
 import org.noear.solon.ai.chat.message.AssistantMessage;
38
 import org.noear.solon.ai.chat.message.ChatMessage;
38
 import org.noear.solon.ai.chat.message.ChatMessage;
39
+import org.noear.solon.ai.chat.session.InMemoryChatSession;
39
 import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint;
40
 import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint;
40
 import org.noear.solon.annotation.Param;
41
 import org.noear.solon.annotation.Param;
41
 import org.springframework.stereotype.Service;
42
 import org.springframework.stereotype.Service;
50
 
51
 
51
     private static final EmbeddingModel embeddingModel = new BgeSmallZhV15EmbeddingModel();
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
     private static final MilvusServiceClient milvusClient = new MilvusServiceClient(
56
     private static final MilvusServiceClient milvusClient = new MilvusServiceClient(
54
             ConnectParam.newBuilder()
57
             ConnectParam.newBuilder()
55
                     .withHost("192.168.28.188")
58
                     .withHost("192.168.28.188")
56
                     .withPort(19530)
59
                     .withPort(19530)
57
                     .build());
60
                     .build());
61
+
58
     /**
62
     /**
59
      * 调用LLM+RAG(外部文件+知识库)生成回答
63
      * 调用LLM+RAG(外部文件+知识库)生成回答
60
      */
64
      */
65
                                            @Param(description = "技术文件地址") String templatePath) throws IOException
69
                                            @Param(description = "技术文件地址") String templatePath) throws IOException
66
     {
70
     {
67
             try {
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
 //                List<JSONObject> contexts = retrieveFromMilvus(milvusClient, embeddingModel, collectionName, title, 10);
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
             } catch (IOException e) {
77
             } catch (IOException e) {
73
                 throw new RuntimeException(e);
78
                 throw new RuntimeException(e);
74
             }
79
             }
95
      */
100
      */
96
     public AssistantMessage generateAnswerWithDocumentAndCollection(EmbeddingModel embeddingModel, String agentName, String templatePath, String question, List<JSONObject> contexts, String llmServiceUrl) throws IOException {
101
     public AssistantMessage generateAnswerWithDocumentAndCollection(EmbeddingModel embeddingModel, String agentName, String templatePath, String question, List<JSONObject> contexts, String llmServiceUrl) throws IOException {
97
         StringBuilder sb = new StringBuilder("招标文件内容:\n\n");
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
         File profilePath = new File(filename);
104
         File profilePath = new File(filename);
100
         if (!profilePath.exists()) {
105
         if (!profilePath.exists()) {
101
             filename = filename.replace(".docx", ".doc");
106
             filename = filename.replace(".docx", ".doc");
102
             profilePath = new File(filename);
107
             profilePath = new File(filename);
103
         }
108
         }
109
+        InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
104
         List<TextSegment> segments = splitDocument(profilePath);
110
         List<TextSegment> segments = splitDocument(profilePath);
105
         List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
111
         List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
106
-        InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
107
         embeddingStore.addAll(embeddings, segments);
112
         embeddingStore.addAll(embeddings, segments);
108
         Embedding queryEmbedding = embeddingModel.embed(question).content();
113
         Embedding queryEmbedding = embeddingModel.embed(question).content();
109
         EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
114
         EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
132
      * @return
137
      * @return
133
      */
138
      */
134
     public AssistantMessage generateAnswer(String prompt, String question, String templatePath, String llmServiceUrl) throws IOException {
139
     public AssistantMessage generateAnswer(String prompt, String question, String templatePath, String llmServiceUrl) throws IOException {
135
-        ChatSession chatSession = new ChatSessionDefault();
136
         ChatModel chatModel = ChatModel.of(llmServiceUrl)
140
         ChatModel chatModel = ChatModel.of(llmServiceUrl)
137
-                .provider("openai")
141
+                
138
                 .model("Qwen2.5-1.5B-Instruct")
142
                 .model("Qwen2.5-1.5B-Instruct")
139
                 .build();
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
         ChatResponse response = chatModel.prompt(chatSession).call();
148
         ChatResponse response = chatModel.prompt(chatSession).call();
144
         String content = response.lastChoice().getMessage().getContent() + "\n\n" +
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
         String absolutePath = templatePath.replace("/dev-api/profile", Solon.cfg().getProperty("cmc.profile"));
153
         String absolutePath = templatePath.replace("/dev-api/profile", Solon.cfg().getProperty("cmc.profile"));
147
         writeContent(response.lastChoice().getMessage().getContent(), question, absolutePath);
154
         writeContent(response.lastChoice().getMessage().getContent(), question, absolutePath);
148
         return ChatMessage.ofAssistant(content);
155
         return ChatMessage.ofAssistant(content);
153
      * @return
160
      * @return
154
      */
161
      */
155
     public void writeContent(String content, String question, String absolutePath) throws IOException {
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
         File file = new File(absolutePath);
166
         File file = new File(absolutePath);
157
         FileInputStream fileInputStream = new FileInputStream(file);
167
         FileInputStream fileInputStream = new FileInputStream(file);
158
         try (XWPFDocument document = new XWPFDocument(fileInputStream)) {
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
             for (int i = 0; i < titles.length; i++) {
169
             for (int i = 0; i < titles.length; i++) {
164
                 int startIndex = Arrays.asList(contentLines).indexOf(titles[i]);
170
                 int startIndex = Arrays.asList(contentLines).indexOf(titles[i]);
165
                 StringBuilder text = new StringBuilder();
171
                 StringBuilder text = new StringBuilder();
217
     public List<String> extractSubTitles(String filename, String question) throws IOException {
223
     public List<String> extractSubTitles(String filename, String question) throws IOException {
218
         List<String> subTitles = new ArrayList<>();
224
         List<String> subTitles = new ArrayList<>();
219
         boolean inTargetSection = false;
225
         boolean inTargetSection = false;
220
-        filename = Solon.cfg().getProperty("cmc.profile") + filename;
221
         InputStream fileInputStream = new FileInputStream(filename);
226
         InputStream fileInputStream = new FileInputStream(filename);
222
         try (XWPFDocument document = new XWPFDocument(fileInputStream)) {
227
         try (XWPFDocument document = new XWPFDocument(fileInputStream)) {
223
             for (XWPFParagraph paragraph : document.getParagraphs()) {
228
             for (XWPFParagraph paragraph : document.getParagraphs()) {
248
         return subTitles;
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 View File

45
         <dependency>
45
         <dependency>
46
             <groupId>dev.langchain4j</groupId>
46
             <groupId>dev.langchain4j</groupId>
47
             <artifactId>langchain4j-milvus</artifactId>
47
             <artifactId>langchain4j-milvus</artifactId>
48
-            <version>0.35.0</version> <!-- 版本需与核心一致 -->
48
+            <version>0.35.0</version>
49
         </dependency>
49
         </dependency>
50
 
50
 
51
         <!-- LangChain4j embedding 集成 -->
51
         <!-- LangChain4j embedding 集成 -->
73
         <dependency>
73
         <dependency>
74
             <groupId>org.noear</groupId>
74
             <groupId>org.noear</groupId>
75
             <artifactId>solon-ai-mcp</artifactId>
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
         </dependency>
77
         </dependency>
84
 
78
 
85
     </dependencies>
79
     </dependencies>

+ 4
- 2
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/CmcTopicController.java View File

113
             String[] chatIds = new String[cmcChatList.size()];
113
             String[] chatIds = new String[cmcChatList.size()];
114
             for (int i = 0; i < cmcChatList.size(); i++)
114
             for (int i = 0; i < cmcChatList.size(); i++)
115
                 chatIds[i] = cmcChatList.get(i).getChatId();
115
                 chatIds[i] = cmcChatList.get(i).getChatId();
116
-            cmcChatService.deleteCmcChatByChatIds(chatIds);
116
+            if (chatIds.length > 0)
117
+                cmcChatService.deleteCmcChatByChatIds(chatIds);
117
             for (String chatId : chatIds) {
118
             for (String chatId : chatIds) {
118
                 CmcDocument cmcDocument = new CmcDocument();
119
                 CmcDocument cmcDocument = new CmcDocument();
119
                 cmcDocument.setChatId(chatId);
120
                 cmcDocument.setChatId(chatId);
121
                 String[] documentIds = new String[cmcDocumentList.size()];
122
                 String[] documentIds = new String[cmcDocumentList.size()];
122
                 for (int i = 0; i < cmcDocumentList.size(); i++)
123
                 for (int i = 0; i < cmcDocumentList.size(); i++)
123
                     documentIds[i] = cmcDocumentList.get(i).getDocumentId();
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
         return success(cmcTopicService.deleteCmcTopicByTopicIds(topicIds));
129
         return success(cmcTopicService.deleteCmcTopicByTopicIds(topicIds));

+ 13
- 13
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/McpController.java View File

15
 import org.noear.solon.ai.chat.ChatModel;
15
 import org.noear.solon.ai.chat.ChatModel;
16
 import org.noear.solon.ai.chat.ChatResponse;
16
 import org.noear.solon.ai.chat.ChatResponse;
17
 import org.noear.solon.ai.chat.ChatSession;
17
 import org.noear.solon.ai.chat.ChatSession;
18
-import org.noear.solon.ai.chat.ChatSessionDefault;
19
 import org.noear.solon.ai.chat.message.AssistantMessage;
18
 import org.noear.solon.ai.chat.message.AssistantMessage;
20
 import org.noear.solon.ai.chat.message.ChatMessage;
19
 import org.noear.solon.ai.chat.message.ChatMessage;
20
+import org.noear.solon.ai.chat.session.InMemoryChatSession;
21
 import org.noear.solon.ai.mcp.client.McpClientProvider;
21
 import org.noear.solon.ai.mcp.client.McpClientProvider;
22
 import org.springframework.beans.factory.annotation.Autowired;
22
 import org.springframework.beans.factory.annotation.Autowired;
23
 import org.springframework.web.bind.annotation.GetMapping;
23
 import org.springframework.web.bind.annotation.GetMapping;
54
 
54
 
55
     private static final EmbeddingModel embeddingModel = new BgeSmallZhV15EmbeddingModel();
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
     private static final MilvusServiceClient milvusClient = new MilvusServiceClient(
59
     private static final MilvusServiceClient milvusClient = new MilvusServiceClient(
58
             ConnectParam.newBuilder()
60
             ConnectParam.newBuilder()
59
                     .withHost("192.168.28.188")
61
                     .withHost("192.168.28.188")
69
 //        McpClientProvider clientProvider = McpClientProvider.builder()
71
 //        McpClientProvider clientProvider = McpClientProvider.builder()
70
 //                .apiUrl("http://localhost:8080/llm/mcp/sse")
72
 //                .apiUrl("http://localhost:8080/llm/mcp/sse")
71
 //                .build();
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
 //                .model("Qwen2.5-1.5B-Instruct")
75
 //                .model("Qwen2.5-1.5B-Instruct")
75
 //                .defaultToolsAdd(clientProvider)
76
 //                .defaultToolsAdd(clientProvider)
76
 //                .build();
77
 //                .build();
97
         McpClientProvider clientProvider = McpClientProvider.builder()
98
         McpClientProvider clientProvider = McpClientProvider.builder()
98
                 .apiUrl("http://localhost:8080/llm/mcp/sse")
99
                 .apiUrl("http://localhost:8080/llm/mcp/sse")
99
                 .build();
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
                 .model("Qwen2.5-1.5B-Instruct")
102
                 .model("Qwen2.5-1.5B-Instruct")
104
                 .defaultToolsAdd(clientProvider)
103
                 .defaultToolsAdd(clientProvider)
105
                 .build();
104
                 .build();
106
 
105
 
106
+        List<ChatMessage> messages = new ArrayList<>();
107
         CmcChat cmcChat = new CmcChat();
107
         CmcChat cmcChat = new CmcChat();
108
         cmcChat.setTopicId(topicId);
108
         cmcChat.setTopicId(topicId);
109
         List<CmcChat> cmcChatList = cmcChatService.selectCmcChatList(cmcChat);
109
         List<CmcChat> cmcChatList = cmcChatService.selectCmcChatList(cmcChat);
110
         for (CmcChat chat : cmcChatList) {
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
         ChatResponse response = chatModel.prompt(chatSession).call();
116
         ChatResponse response = chatModel.prompt(chatSession).call();
117
         String resultContent = response.lastChoice().getMessage().getResultContent();
117
         String resultContent = response.lastChoice().getMessage().getResultContent();
118
         AssistantMessage assistantMessage;
118
         AssistantMessage assistantMessage;
142
     {
142
     {
143
         question = String.join(",", langChainMilvusService.extractSubTitles(RuoYiConfig.getProfile() + "/upload/agent/template/technical.docx", question));
143
         question = String.join(",", langChainMilvusService.extractSubTitles(RuoYiConfig.getProfile() + "/upload/agent/template/technical.docx", question));
144
         List<JSONObject> requests = langChainMilvusService.retrieveFromMilvus(milvusClient, embeddingModel, collectionName, question, 10);
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
         McpClientProvider clientProvider = McpClientProvider.builder()
154
         McpClientProvider clientProvider = McpClientProvider.builder()
155
                     .apiUrl("http://localhost:8080/llm/mcp/sse")
155
                     .apiUrl("http://localhost:8080/llm/mcp/sse")
156
                     .build();
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
                 .model("DeepSeek-R1-Distill-Qwen-1.5B")
159
                 .model("DeepSeek-R1-Distill-Qwen-1.5B")
160
                 .defaultToolsAdd(clientProvider)
160
                 .defaultToolsAdd(clientProvider)
161
                 .build();
161
                 .build();

+ 14
- 12
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/service/impl/LangChainMilvusServiceImpl.java View File

35
 import org.noear.solon.ai.chat.ChatModel;
35
 import org.noear.solon.ai.chat.ChatModel;
36
 import org.noear.solon.ai.chat.ChatResponse;
36
 import org.noear.solon.ai.chat.ChatResponse;
37
 import org.noear.solon.ai.chat.ChatSession;
37
 import org.noear.solon.ai.chat.ChatSession;
38
-import org.noear.solon.ai.chat.ChatSessionDefault;
39
 import org.noear.solon.ai.chat.message.AssistantMessage;
38
 import org.noear.solon.ai.chat.message.AssistantMessage;
40
 import org.noear.solon.ai.chat.message.ChatMessage;
39
 import org.noear.solon.ai.chat.message.ChatMessage;
40
+import org.noear.solon.ai.chat.session.InMemoryChatSession;
41
 import org.reactivestreams.Publisher;
41
 import org.reactivestreams.Publisher;
42
 import org.springframework.beans.factory.annotation.Autowired;
42
 import org.springframework.beans.factory.annotation.Autowired;
43
 import org.springframework.stereotype.Service;
43
 import org.springframework.stereotype.Service;
147
      */
147
      */
148
     @Override
148
     @Override
149
     public Flux<AssistantMessage> generateAnswer(String topicId, String prompt, String llmServiceUrl) {
149
     public Flux<AssistantMessage> generateAnswer(String topicId, String prompt, String llmServiceUrl) {
150
-        ChatSession chatSession = new ChatSessionDefault(topicId);
151
         ChatModel chatModel = ChatModel.of(llmServiceUrl)
150
         ChatModel chatModel = ChatModel.of(llmServiceUrl)
152
-                .provider("openai")
151
+                
153
                 .model("Qwen2.5-1.5B-Instruct")
152
                 .model("Qwen2.5-1.5B-Instruct")
154
                 .build();
153
                 .build();
155
 
154
 
155
+        List<ChatMessage> messages = new ArrayList<>();
156
         if (topicId != null) {
156
         if (topicId != null) {
157
             CmcChat cmcChat = new CmcChat();
157
             CmcChat cmcChat = new CmcChat();
158
             cmcChat.setTopicId(topicId);
158
             cmcChat.setTopicId(topicId);
159
             List<CmcChat> cmcChatList = cmcChatService.selectCmcChatList(cmcChat);
159
             List<CmcChat> cmcChatList = cmcChatService.selectCmcChatList(cmcChat);
160
             for (CmcChat chat : cmcChatList) {
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
         Publisher<ChatResponse> publisher = chatModel.prompt(chatSession).stream();
167
         Publisher<ChatResponse> publisher = chatModel.prompt(chatSession).stream();
168
         return Flux.from(publisher)
168
         return Flux.from(publisher)
169
                 .map(response -> {
169
                 .map(response -> {
200
         StringBuilder sb = new StringBuilder("问题: " + question + "\n\n").append("根据以下上下文回答问题:\n\n");
200
         StringBuilder sb = new StringBuilder("问题: " + question + "\n\n").append("根据以下上下文回答问题:\n\n");
201
         for (CmcDocument document : documentList) {
201
         for (CmcDocument document : documentList) {
202
             File profilePath = new File(RuoYiConfig.getProfile() + document.getPath());
202
             File profilePath = new File(RuoYiConfig.getProfile() + document.getPath());
203
+            InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
203
             List<TextSegment> segments = splitDocument(profilePath);
204
             List<TextSegment> segments = splitDocument(profilePath);
204
             List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
205
             List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
205
-            InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
206
             embeddingStore.addAll(embeddings, segments);
206
             embeddingStore.addAll(embeddings, segments);
207
             Embedding queryEmbedding = embeddingModel.embed(question).content();
207
             Embedding queryEmbedding = embeddingModel.embed(question).content();
208
             EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
208
             EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
238
             if (documentList.size() == 1) {
238
             if (documentList.size() == 1) {
239
                 for (CmcDocument document : documentList) {
239
                 for (CmcDocument document : documentList) {
240
                     File profilePath = new File(RuoYiConfig.getProfile() + document.getPath());
240
                     File profilePath = new File(RuoYiConfig.getProfile() + document.getPath());
241
+                    InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
241
                     List<TextSegment> segments = splitDocument(profilePath);
242
                     List<TextSegment> segments = splitDocument(profilePath);
242
                     List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
243
                     List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
243
-                    InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
244
                     embeddingStore.addAll(embeddings, segments);
244
                     embeddingStore.addAll(embeddings, segments);
245
                     Embedding queryEmbedding = embeddingModel.embed(question).content();
245
                     Embedding queryEmbedding = embeddingModel.embed(question).content();
246
                     EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
246
                     EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
247
                             .queryEmbedding(queryEmbedding)
247
                             .queryEmbedding(queryEmbedding)
248
-                            .maxResults(3)
248
+                            .minScore(0.7)
249
                             .build();
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
                         String requests = embeddingMatch.embedded().toString();
253
                         String requests = embeddingMatch.embedded().toString();
252
                         sb.append(requests).append("\n\n");
254
                         sb.append(requests).append("\n\n");
253
                     }
255
                     }
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
         Document document;
352
         Document document;
351
         InputStream fileInputStream = new FileInputStream(transferFile);
353
         InputStream fileInputStream = new FileInputStream(transferFile);

+ 8
- 10
llm-back/ruoyi-system/pom.xml View File

39
             <groupId>dev.langchain4j</groupId>
39
             <groupId>dev.langchain4j</groupId>
40
             <artifactId>langchain4j-document-parser-apache-pdfbox</artifactId>
40
             <artifactId>langchain4j-document-parser-apache-pdfbox</artifactId>
41
             <version>0.35.0</version>
41
             <version>0.35.0</version>
42
-            <scope>compile</scope>
43
         </dependency>
42
         </dependency>
44
 
43
 
45
         <dependency>
44
         <dependency>
46
             <groupId>dev.langchain4j</groupId>
45
             <groupId>dev.langchain4j</groupId>
47
             <artifactId>langchain4j-embeddings-bge-small-zh-v15</artifactId>
46
             <artifactId>langchain4j-embeddings-bge-small-zh-v15</artifactId>
48
             <version>0.35.0</version>
47
             <version>0.35.0</version>
49
-            <scope>compile</scope>
50
         </dependency>
48
         </dependency>
51
 
49
 
52
         <dependency>
50
         <dependency>
53
             <groupId>org.noear</groupId>
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
         </dependency>
60
         </dependency>
63
 
61
 
64
     </dependencies>
62
     </dependencies>

+ 127
- 26
llm-back/ruoyi-system/src/main/java/com/ruoyi/llm/service/impl/CmcAgentServiceImpl.java View File

2
 
2
 
3
 import java.io.*;
3
 import java.io.*;
4
 import java.util.*;
4
 import java.util.*;
5
+import java.util.stream.Collectors;
5
 
6
 
6
 import com.alibaba.fastjson2.JSONObject;
7
 import com.alibaba.fastjson2.JSONObject;
7
 import com.ruoyi.common.config.RuoYiConfig;
8
 import com.ruoyi.common.config.RuoYiConfig;
23
 import dev.langchain4j.store.embedding.EmbeddingMatch;
24
 import dev.langchain4j.store.embedding.EmbeddingMatch;
24
 import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
25
 import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
25
 import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
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
 import org.apache.poi.extractor.ExtractorFactory;
37
 import org.apache.poi.extractor.ExtractorFactory;
27
 import org.apache.poi.extractor.POITextExtractor;
38
 import org.apache.poi.extractor.POITextExtractor;
28
 import org.apache.poi.xwpf.usermodel.BreakType;
39
 import org.apache.poi.xwpf.usermodel.BreakType;
32
 import org.noear.solon.ai.chat.ChatModel;
43
 import org.noear.solon.ai.chat.ChatModel;
33
 import org.noear.solon.ai.chat.ChatResponse;
44
 import org.noear.solon.ai.chat.ChatResponse;
34
 import org.noear.solon.ai.chat.ChatSession;
45
 import org.noear.solon.ai.chat.ChatSession;
35
-import org.noear.solon.ai.chat.ChatSessionDefault;
36
 import org.noear.solon.ai.chat.message.ChatMessage;
46
 import org.noear.solon.ai.chat.message.ChatMessage;
47
+import org.noear.solon.ai.chat.session.InMemoryChatSession;
37
 import org.springframework.beans.factory.annotation.Autowired;
48
 import org.springframework.beans.factory.annotation.Autowired;
38
 import org.springframework.stereotype.Service;
49
 import org.springframework.stereotype.Service;
39
 import com.ruoyi.llm.mapper.CmcAgentMapper;
50
 import com.ruoyi.llm.mapper.CmcAgentMapper;
63
 
74
 
64
     private static final String llmServiceUrl = "http://192.168.28.188:8000/v1/chat/completions";
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
                     doc.write(out);
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
             message = "好的,我已经收到您上传的招标文件,我将给您提供技术文件模板,您可点击进行预览:" +
166
             message = "好的,我已经收到您上传的招标文件,我将给您提供技术文件模板,您可点击进行预览:" +
150
                     "【<a href='/profile" + outputFilename + "'> 模版 " + "</a>】\n\n" +
167
                     "【<a href='/profile" + outputFilename + "'> 模版 " + "</a>】\n\n" +
151
                     chapters + "\n\n" +
168
                     chapters + "\n\n" +
227
     /**
244
     /**
228
      * 调用LLM生成回答
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
         StringBuilder sb = new StringBuilder("招标文件内容:\n\n");
248
         StringBuilder sb = new StringBuilder("招标文件内容:\n\n");
232
         File profilePath = new File(uploadFilePath);
249
         File profilePath = new File(uploadFilePath);
250
+        InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
233
         List<TextSegment> segments = splitDocument(profilePath);
251
         List<TextSegment> segments = splitDocument(profilePath);
234
         List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
252
         List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
235
-        InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
236
         embeddingStore.addAll(embeddings, segments);
253
         embeddingStore.addAll(embeddings, segments);
237
         Embedding queryEmbedding = embeddingModel.embed(question).content();
254
         Embedding queryEmbedding = embeddingModel.embed(question).content();
238
         EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
255
         EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
249
                 .append("6.1 XX\n" +
266
                 .append("6.1 XX\n" +
250
                 "6.2 XX\n" +
267
                 "6.2 XX\n" +
251
                 "6.3 XX");
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
      * 调用LLM生成回答
301
      * 调用LLM生成回答
257
      * @return
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
                 .build();
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
         String[] contentLines = content.split("\n");
376
         String[] contentLines = content.split("\n");
281
         for (String line : contentLines) {
377
         for (String line : contentLines) {
282
             if (line.contains("6."))
378
             if (line.contains("6."))
283
-                chapters.add(line.replace("*", "").replace("#", "").split(" ")[1]);
379
+                chapters.add(line.replace("*", "").replace("#", "").replace("-", ""));
284
         }
380
         }
285
         File file = new File(templatePath);
381
         File file = new File(templatePath);
286
         FileInputStream fileInputStream = new FileInputStream(file);
382
         FileInputStream fileInputStream = new FileInputStream(file);
287
         try (XWPFDocument document = new XWPFDocument(fileInputStream)) {
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
             try (FileOutputStream out = new FileOutputStream(templatePath)) {
399
             try (FileOutputStream out = new FileOutputStream(templatePath)) {
299
                 document.write(out);
400
                 document.write(out);

Loading…
Cancel
Save