lamphua 1 день назад
Родитель
Сommit
5068b6a9f7

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

17
     </description>
17
     </description>
18
 
18
 
19
     <properties>
19
     <properties>
20
-        <solon.version>3.9.5</solon.version>
20
+        <solon.version>4.0.2</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
+            <version>4.0.2</version>
45
         </dependency>
46
         </dependency>
46
 
47
 
47
         <dependency>
48
         <dependency>
48
             <groupId>org.noear</groupId>
49
             <groupId>org.noear</groupId>
49
             <artifactId>solon-ai-agent</artifactId>
50
             <artifactId>solon-ai-agent</artifactId>
51
+            <version>4.0.2</version>
50
         </dependency>
52
         </dependency>
51
         <dependency>
53
         <dependency>
52
             <groupId>org.slf4j</groupId>
54
             <groupId>org.slf4j</groupId>

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

23
 import io.milvus.v2.service.vector.request.data.BaseVector;
23
 import io.milvus.v2.service.vector.request.data.BaseVector;
24
 import io.milvus.v2.service.vector.request.data.FloatVec;
24
 import io.milvus.v2.service.vector.request.data.FloatVec;
25
 import io.milvus.v2.service.vector.response.SearchResp;
25
 import io.milvus.v2.service.vector.response.SearchResp;
26
-import io.modelcontextprotocol.server.McpServer;
27
-import io.modelcontextprotocol.spec.McpServerSession;
28
 import org.apache.poi.hwpf.HWPFDocument;
26
 import org.apache.poi.hwpf.HWPFDocument;
29
 import org.apache.poi.hwpf.usermodel.Paragraph;
27
 import org.apache.poi.hwpf.usermodel.Paragraph;
30
 import org.apache.poi.hwpf.usermodel.Range;
28
 import org.apache.poi.hwpf.usermodel.Range;
32
 import org.apache.xmlbeans.XmlCursor;
30
 import org.apache.xmlbeans.XmlCursor;
33
 import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPPrGeneral;
31
 import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPPrGeneral;
34
 import org.noear.solon.Solon;
32
 import org.noear.solon.Solon;
33
+import org.noear.solon.ai.agent.simple.SimpleAgent;
35
 import org.noear.solon.ai.annotation.ToolMapping;
34
 import org.noear.solon.ai.annotation.ToolMapping;
36
 import org.noear.solon.ai.chat.ChatModel;
35
 import org.noear.solon.ai.chat.ChatModel;
37
-import org.noear.solon.ai.chat.ChatResponse;
38
 import org.noear.solon.ai.chat.ChatSession;
36
 import org.noear.solon.ai.chat.ChatSession;
39
 import org.noear.solon.ai.chat.message.AssistantMessage;
37
 import org.noear.solon.ai.chat.message.AssistantMessage;
40
 import org.noear.solon.ai.chat.message.ChatMessage;
38
 import org.noear.solon.ai.chat.message.ChatMessage;
39
+import org.noear.solon.ai.chat.prompt.Prompt;
41
 import org.noear.solon.ai.chat.session.InMemoryChatSession;
40
 import org.noear.solon.ai.chat.session.InMemoryChatSession;
42
 import org.noear.solon.ai.mcp.McpChannel;
41
 import org.noear.solon.ai.mcp.McpChannel;
43
 import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint;
42
 import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint;
52
 import java.util.*;
51
 import java.util.*;
53
 
52
 
54
 @Service
53
 @Service
55
-@McpServerEndpoint(channel = McpChannel.STREAMABLE_STATELESS , mcpEndpoint = "/mcp/sse")
54
+@McpServerEndpoint(channel = McpChannel.STREAMABLE_STATELESS, mcpEndpoint = "/mcp/sse")
56
 public class McpServiceImpl implements IMcpService {
55
 public class McpServiceImpl implements IMcpService {
57
 
56
 
58
     private static final EmbeddingModel embeddingModel = new BgeSmallZhV15EmbeddingModel();
57
     private static final EmbeddingModel embeddingModel = new BgeSmallZhV15EmbeddingModel();
76
         if (milvusServiceUrl == null || milvusServiceUrl.isEmpty()) {
75
         if (milvusServiceUrl == null || milvusServiceUrl.isEmpty()) {
77
             throw new IllegalStateException("Milvus service URL 未配置");
76
             throw new IllegalStateException("Milvus service URL 未配置");
78
         }
77
         }
79
-//        milvusClient = new MilvusClientV2(
80
-//                ConnectConfig.builder()
81
-//                        .uri(milvusServiceUrl)
82
-//                        .build());
78
+        // milvusClient = new MilvusClientV2(
79
+        // ConnectConfig.builder()
80
+        // .uri(milvusServiceUrl)
81
+        // .build());
83
     }
82
     }
84
 
83
 
85
     @PreDestroy
84
     @PreDestroy
96
         }
95
         }
97
     }
96
     }
98
 
97
 
98
+    /**
99
+     * 获取所有表名列表
100
+     */
101
+    @ToolMapping(description = "修改文档格式")
102
+    public JSONArray ModifyDocumentFormat(@Param(description = "文档路径") String documentPath) {
103
+        return getAllTableNames();
104
+    }
105
+
99
     /**
106
     /**
100
      * 调用LLM+RAG(外部文件+知识库)生成回答
107
      * 调用LLM+RAG(外部文件+知识库)生成回答
101
      */
108
      */
102
     @ToolMapping(description = "章节撰写")
109
     @ToolMapping(description = "章节撰写")
103
     public AssistantMessage writeParagraph(@Param(description = "知识库名称") String collectionName,
110
     public AssistantMessage writeParagraph(@Param(description = "知识库名称") String collectionName,
104
-                                           @Param(description = "智能体名称") String agentName,
105
-                                           @Param(description = "章节名称") String title,
106
-                                           @Param(description = "技术文件地址") String templatePath) throws IOException
107
-    {
111
+            @Param(description = "智能体名称") String agentName,
112
+            @Param(description = "章节名称") String title,
113
+            @Param(description = "技术文件地址") String templatePath) throws Throwable {
108
         try {
114
         try {
109
             if (templatePath.startsWith("/dev-api"))
115
             if (templatePath.startsWith("/dev-api"))
110
                 templatePath = templatePath.replace("/dev-api/profile", profile);
116
                 templatePath = templatePath.replace("/dev-api/profile", profile);
111
-            templatePath =profile + templatePath;
117
+            templatePath = profile + templatePath;
112
             List<String> subTitles = extractSubTitles(templatePath, title);
118
             List<String> subTitles = extractSubTitles(templatePath, title);
113
             List<JSONObject> contexts = retrieveFromMilvus(collectionName, title, 10);
119
             List<JSONObject> contexts = retrieveFromMilvus(collectionName, title, 10);
114
             return generateAnswerWithDocumentAndCollection(agentName, templatePath, subTitles, contexts);
120
             return generateAnswerWithDocumentAndCollection(agentName, templatePath, subTitles, contexts);
121
      * 查询OA数据生成回答
127
      * 查询OA数据生成回答
122
      */
128
      */
123
     @ToolMapping(description = "执行SQL查询")
129
     @ToolMapping(description = "执行SQL查询")
124
-    public JSONArray SQLQuery(@Param(description = "sql语句") String sqlString)
125
-    {
126
-        sqlString = sqlString.replace("[","").replace("]","");
130
+    public JSONArray SQLQuery(@Param(description = "sql语句") String sqlString) {
131
+        sqlString = sqlString.replace("[", "").replace("]", "");
127
         return executeQuery(sqlString);
132
         return executeQuery(sqlString);
128
     }
133
     }
129
 
134
 
131
      * 获取所有表名列表
136
      * 获取所有表名列表
132
      */
137
      */
133
     @ToolMapping(description = "获取数据库中所有表名列表")
138
     @ToolMapping(description = "获取数据库中所有表名列表")
134
-    public JSONArray GetAllTableNames()
135
-    {
139
+    public JSONArray GetAllTableNames() {
136
         return getAllTableNames();
140
         return getAllTableNames();
137
     }
141
     }
138
 
142
 
140
      * 获取指定表的结构信息
144
      * 获取指定表的结构信息
141
      */
145
      */
142
     @ToolMapping(description = "获取指定表的结构信息")
146
     @ToolMapping(description = "获取指定表的结构信息")
143
-    public JSONObject GetTableStructure(@Param(description = "表名") String tableName)
144
-    {
147
+    public JSONObject GetTableStructure(@Param(description = "表名") String tableName) {
145
         return getTableStructure(tableName);
148
         return getTableStructure(tableName);
146
     }
149
     }
147
 
150
 
155
             throw new RuntimeException("MySQL 驱动未找到", e);
158
             throw new RuntimeException("MySQL 驱动未找到", e);
156
         }
159
         }
157
 
160
 
158
-        try (Connection conn = DriverManager.getConnection(Solon.cfg().getProperty("cmc.mysqlService.jdbcUrl"),Solon.cfg().getProperty("cmc.mysqlService.username"), Solon.cfg().getProperty("cmc.mysqlService.password"));
159
-             PreparedStatement stmt = conn.prepareStatement(sql)) {
161
+        try (Connection conn = DriverManager.getConnection(Solon.cfg().getProperty("cmc.mysqlService.jdbcUrl"),
162
+                Solon.cfg().getProperty("cmc.mysqlService.username"),
163
+                Solon.cfg().getProperty("cmc.mysqlService.password"));
164
+                PreparedStatement stmt = conn.prepareStatement(sql)) {
160
 
165
 
161
             ResultSet rs = stmt.executeQuery();
166
             ResultSet rs = stmt.executeQuery();
162
             ResultSetMetaData metaData = rs.getMetaData();
167
             ResultSetMetaData metaData = rs.getMetaData();
181
 
186
 
182
     /**
187
     /**
183
      * 从Milvus检索相关文档
188
      * 从Milvus检索相关文档
189
+     * 
184
      * @return
190
      * @return
185
      */
191
      */
186
     public List<JSONObject> retrieveFromMilvus(String collectionName, String query, int topK) {
192
     public List<JSONObject> retrieveFromMilvus(String collectionName, String query, int topK) {
198
     /**
204
     /**
199
      * 调用LLM生成回答
205
      * 调用LLM生成回答
200
      */
206
      */
201
-    public AssistantMessage generateAnswerWithDocumentAndCollection(String agentName, String templatePath, List<String> titles, List<JSONObject> contexts) throws IOException {
207
+    public AssistantMessage generateAnswerWithDocumentAndCollection(String agentName, String templatePath,
208
+            List<String> titles, List<JSONObject> contexts) throws Throwable {
202
         StringBuilder sb = new StringBuilder("招标文件内容:\n\n");
209
         StringBuilder sb = new StringBuilder("招标文件内容:\n\n");
203
         String filename = templatePath.replace("_" + agentName, "");
210
         String filename = templatePath.replace("_" + agentName, "");
204
         File profilePath = new File(filename);
211
         File profilePath = new File(filename);
235
                     .append("段落格式").append(": ")
242
                     .append("段落格式").append(": ")
236
                     .append(context.getString("content")).append("\n\n");
243
                     .append(context.getString("content")).append("\n\n");
237
         }
244
         }
238
-        content.append( "招标文件分析完成,章节内容已写入【<a href='")
245
+        content.append("招标文件分析完成,章节内容已写入【<a href='")
239
                 .append(templatePath.replace(profile, "/dev-api/profile"))
246
                 .append(templatePath.replace(profile, "/dev-api/profile"))
240
                 .append("'> 技术文件" + "</a>】,请查阅\n\n")
247
                 .append("'> 技术文件" + "</a>】,请查阅\n\n")
241
                 .append("如需修改,请输入技术文件已有内容的章节标题\n\n")
248
                 .append("如需修改,请输入技术文件已有内容的章节标题\n\n")
245
 
252
 
246
     /**
253
     /**
247
      * 调用LLM生成回答
254
      * 调用LLM生成回答
255
+     * 
248
      * @return
256
      * @return
249
      */
257
      */
250
-    public String generateAnswer(String prompt) throws IOException {
258
+    public String generateAnswer(String prompt) throws Throwable {
251
         ChatModel chatModel = ChatModel.of(llmServiceUrl)
259
         ChatModel chatModel = ChatModel.of(llmServiceUrl)
252
                 .model("Qwen")
260
                 .model("Qwen")
253
                 .build();
261
                 .build();
254
 
262
 
255
         List<ChatMessage> messages = new ArrayList<>();
263
         List<ChatMessage> messages = new ArrayList<>();
256
         messages.add(ChatMessage.ofUser(prompt));
264
         messages.add(ChatMessage.ofUser(prompt));
257
-        ChatSession chatSession =  InMemoryChatSession.builder().messages(messages).build();
265
+        ChatSession chatSession = InMemoryChatSession.builder().messages(messages).build();
258
         chatSession.addMessage(messages);
266
         chatSession.addMessage(messages);
259
-        ChatResponse response = chatModel.prompt(chatSession).call();
267
+        SimpleAgent robot = SimpleAgent.of(chatModel)
268
+                .build();
269
+        Prompt prompt1 = Prompt.of(prompt).attrPut("session", chatSession);
260
 
270
 
261
-        return response.lastChoice().getMessage().getContent();
271
+        return robot.prompt(prompt1).call().getContent();
262
     }
272
     }
263
 
273
 
264
     /**
274
     /**
265
      * 写入章节内容
275
      * 写入章节内容
276
+     * 
266
      * @return
277
      * @return
267
      */
278
      */
268
     public void writeContent(String content, List<String> titles, String absolutePath) throws IOException {
279
     public void writeContent(String content, List<String> titles, String absolutePath) throws IOException {
287
                             }
298
                             }
288
                         }
299
                         }
289
                     }
300
                     }
290
-                }
291
-                else
301
+                } else
292
                     text.append(content);
302
                     text.append(content);
293
                 map.put(titles.get(i), text.toString());
303
                 map.put(titles.get(i), text.toString());
294
             }
304
             }
371
         StringBuilder currentLevel3Content = new StringBuilder();
381
         StringBuilder currentLevel3Content = new StringBuilder();
372
         String[] paragraphs = content.split("\n");
382
         String[] paragraphs = content.split("\n");
373
         for (String para : paragraphs) {
383
         for (String para : paragraphs) {
374
-            if (para.trim().isEmpty()) continue;
384
+            if (para.trim().isEmpty())
385
+                continue;
375
 
386
 
376
             if (isDocxLevel3Title(para.trim(), document)) {
387
             if (isDocxLevel3Title(para.trim(), document)) {
377
                 if (currentLevel3Content.length() != 0) {
388
                 if (currentLevel3Content.length() != 0) {
403
         for (int j = 0; j < level2Range.numParagraphs(); j++) {
414
         for (int j = 0; j < level2Range.numParagraphs(); j++) {
404
             Paragraph p = level2Range.getParagraph(j);
415
             Paragraph p = level2Range.getParagraph(j);
405
             String paraText = p.text().trim();
416
             String paraText = p.text().trim();
406
-            if (paraText.isEmpty()) continue;
417
+            if (paraText.isEmpty())
418
+                continue;
407
 
419
 
408
             int pLevel = getDocOutlineLevel(p);
420
             int pLevel = getDocOutlineLevel(p);
409
             if (pLevel == 2 && paraText.equals(content.split("\n")[0].trim())) {
421
             if (pLevel == 2 && paraText.equals(content.split("\n")[0].trim())) {
450
 
462
 
451
         try (XWPFDocument document = new XWPFDocument(fileInputStream)) {
463
         try (XWPFDocument document = new XWPFDocument(fileInputStream)) {
452
 
464
 
453
-            for (XWPFParagraph  paragraph : document.getParagraphs()) {
465
+            for (XWPFParagraph paragraph : document.getParagraphs()) {
454
                 String text = paragraph.getText();
466
                 String text = paragraph.getText();
455
                 int level = getDocxOutlineLevel(paragraph, document);
467
                 int level = getDocxOutlineLevel(paragraph, document);
456
                 if (level > 0) {
468
                 if (level > 0) {
485
                 }
497
                 }
486
             }
498
             }
487
 
499
 
488
-
489
             return subTitles;
500
             return subTitles;
490
 
501
 
491
         }
502
         }
494
     // 检查一个标题是否是叶子节点(没有更低级别的子标题)
505
     // 检查一个标题是否是叶子节点(没有更低级别的子标题)
495
     private boolean isLeafNode(XWPFDocument doc, XWPFParagraph p, int level) {
506
     private boolean isLeafNode(XWPFDocument doc, XWPFParagraph p, int level) {
496
         int index = doc.getPosOfParagraph(p);
507
         int index = doc.getPosOfParagraph(p);
497
-        if (index == -1 || index >= doc.getParagraphs().size()-1) {
508
+        if (index == -1 || index >= doc.getParagraphs().size() - 1) {
498
             return true;
509
             return true;
499
         }
510
         }
500
 
511
 
529
 
540
 
530
     /**
541
     /**
531
      * 检索知识库
542
      * 检索知识库
543
+     * 
532
      * @return
544
      * @return
533
      */
545
      */
534
     private List<List<SearchResp.SearchResult>> retrieve(String collectionName, String query, int topK) {
546
     private List<List<SearchResp.SearchResult>> retrieve(String collectionName, String query, int topK) {
535
-        List<BaseVector> queryVector = Collections.singletonList(new FloatVec(embeddingModel.embed(query).content().vector()));
547
+        List<BaseVector> queryVector = Collections
548
+                .singletonList(new FloatVec(embeddingModel.embed(query).content().vector()));
536
 
549
 
537
-        //  加载集合
550
+        // 加载集合
538
         LoadCollectionReq loadCollectionReq = LoadCollectionReq.builder()
551
         LoadCollectionReq loadCollectionReq = LoadCollectionReq.builder()
539
                 .collectionName(collectionName)
552
                 .collectionName(collectionName)
540
                 .build();
553
                 .build();
579
 
592
 
580
                 for (XWPFParagraph paragraph : xwpfDocument.getParagraphs()) {
593
                 for (XWPFParagraph paragraph : xwpfDocument.getParagraphs()) {
581
                     String text = paragraph.getText().trim();
594
                     String text = paragraph.getText().trim();
582
-                    if (text.isEmpty()) continue;
595
+                    if (text.isEmpty())
596
+                        continue;
583
 
597
 
584
                     int paraLevel = getDocxOutlineLevel(paragraph, xwpfDocument);
598
                     int paraLevel = getDocxOutlineLevel(paragraph, xwpfDocument);
585
 
599
 
614
                 }
628
                 }
615
             }
629
             }
616
             return segments;
630
             return segments;
617
-        }
618
-        else if (filename.endsWith(".doc")) {
631
+        } else if (filename.endsWith(".doc")) {
619
             List<TextSegment> segments = new ArrayList<>();
632
             List<TextSegment> segments = new ArrayList<>();
620
             try (HWPFDocument hwpfDocument = new HWPFDocument(fileInputStream)) {
633
             try (HWPFDocument hwpfDocument = new HWPFDocument(fileInputStream)) {
621
                 StringBuilder currentLevel2Content = new StringBuilder();
634
                 StringBuilder currentLevel2Content = new StringBuilder();
625
                 for (int i = 0; i < range.numParagraphs(); i++) {
638
                 for (int i = 0; i < range.numParagraphs(); i++) {
626
                     Paragraph paragraph = range.getParagraph(i);
639
                     Paragraph paragraph = range.getParagraph(i);
627
                     String text = paragraph.text().trim();
640
                     String text = paragraph.text().trim();
628
-                    if (text.isEmpty()) continue;
641
+                    if (text.isEmpty())
642
+                        continue;
629
 
643
 
630
                     int paraLevel = getDocOutlineLevel(paragraph);
644
                     int paraLevel = getDocOutlineLevel(paragraph);
631
 
645
 
664
 
678
 
665
         else if (filename.endsWith(".pdf")) {
679
         else if (filename.endsWith(".pdf")) {
666
             Document document = new ApachePdfBoxDocumentParser().parse(fileInputStream);
680
             Document document = new ApachePdfBoxDocumentParser().parse(fileInputStream);
667
-            DocumentByParagraphSplitter splitter = new DocumentByParagraphSplitter(300,50);
681
+            DocumentByParagraphSplitter splitter = new DocumentByParagraphSplitter(300, 50);
668
             return splitter.split(document);
682
             return splitter.split(document);
669
-        }
670
-        else if (filename.endsWith(".txt")) {
683
+        } else if (filename.endsWith(".txt")) {
671
             Document document = new TextDocumentParser().parse(fileInputStream);
684
             Document document = new TextDocumentParser().parse(fileInputStream);
672
-            DocumentByParagraphSplitter splitter = new DocumentByParagraphSplitter(300,50);
685
+            DocumentByParagraphSplitter splitter = new DocumentByParagraphSplitter(300, 50);
673
             return splitter.split(document);
686
             return splitter.split(document);
674
-        }
675
-        else {
687
+        } else {
676
             throw new UnsupportedOperationException("不支持文件类型: " + filename);
688
             throw new UnsupportedOperationException("不支持文件类型: " + filename);
677
         }
689
         }
678
     }
690
     }
689
             throw new RuntimeException("MySQL 驱动未找到", e);
701
             throw new RuntimeException("MySQL 驱动未找到", e);
690
         }
702
         }
691
 
703
 
692
-        try (Connection conn = DriverManager.getConnection(Solon.cfg().getProperty("cmc.mysqlService.jdbcUrl"),Solon.cfg().getProperty("cmc.mysqlService.username"), Solon.cfg().getProperty("cmc.mysqlService.password"))) {
704
+        try (Connection conn = DriverManager.getConnection(Solon.cfg().getProperty("cmc.mysqlService.jdbcUrl"),
705
+                Solon.cfg().getProperty("cmc.mysqlService.username"),
706
+                Solon.cfg().getProperty("cmc.mysqlService.password"))) {
693
 
707
 
694
             DatabaseMetaData metaData = conn.getMetaData();
708
             DatabaseMetaData metaData = conn.getMetaData();
695
 
709
 
738
             throw new RuntimeException("MySQL 驱动未找到", e);
752
             throw new RuntimeException("MySQL 驱动未找到", e);
739
         }
753
         }
740
 
754
 
741
-        try (Connection conn = DriverManager.getConnection(Solon.cfg().getProperty("cmc.mysqlService.jdbcUrl"),Solon.cfg().getProperty("cmc.mysqlService.username"), Solon.cfg().getProperty("cmc.mysqlService.password"))) {
755
+        try (Connection conn = DriverManager.getConnection(Solon.cfg().getProperty("cmc.mysqlService.jdbcUrl"),
756
+                Solon.cfg().getProperty("cmc.mysqlService.username"),
757
+                Solon.cfg().getProperty("cmc.mysqlService.password"))) {
742
 
758
 
743
             DatabaseMetaData metaData = conn.getMetaData();
759
             DatabaseMetaData metaData = conn.getMetaData();
744
-            ResultSet tables = metaData.getTables(null, null, null, new String[]{"TABLE"});
760
+            ResultSet tables = metaData.getTables(null, null, null, new String[] { "TABLE" });
745
 
761
 
746
             while (tables.next()) {
762
             while (tables.next()) {
747
                 String tableName = tables.getString("TABLE_NAME");
763
                 String tableName = tables.getString("TABLE_NAME");

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

32
             <groupId>org.springframework.boot</groupId>
32
             <groupId>org.springframework.boot</groupId>
33
             <artifactId>spring-boot-starter-web</artifactId>
33
             <artifactId>spring-boot-starter-web</artifactId>
34
         </dependency>
34
         </dependency>
35
+        
36
+        <dependency>
37
+            <groupId>org.noear</groupId>
38
+            <artifactId>solon-ai-mcp</artifactId>
39
+            <version>4.0.2</version>
40
+        </dependency>
35
 
41
 
36
     </dependencies>
42
     </dependencies>
37
 
43
 

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

12
 import com.ruoyi.llm.service.ICmcAgentService;
12
 import com.ruoyi.llm.service.ICmcAgentService;
13
 import com.ruoyi.llm.service.ICmcChatService;
13
 import com.ruoyi.llm.service.ICmcChatService;
14
 import com.ruoyi.llm.service.ICmcTopicService;
14
 import com.ruoyi.llm.service.ICmcTopicService;
15
+
16
+import org.noear.solon.ai.agent.simple.SimpleAgent;
15
 import org.noear.solon.ai.chat.ChatModel;
17
 import org.noear.solon.ai.chat.ChatModel;
16
-import org.noear.solon.ai.chat.ChatResponse;
17
 import org.noear.solon.ai.chat.ChatSession;
18
 import org.noear.solon.ai.chat.ChatSession;
18
 import org.noear.solon.ai.chat.message.AssistantMessage;
19
 import org.noear.solon.ai.chat.message.AssistantMessage;
19
 import org.noear.solon.ai.chat.message.ChatMessage;
20
 import org.noear.solon.ai.chat.message.ChatMessage;
21
+import org.noear.solon.ai.chat.prompt.Prompt;
20
 import org.noear.solon.ai.chat.session.InMemoryChatSession;
22
 import org.noear.solon.ai.chat.session.InMemoryChatSession;
21
 import org.noear.solon.ai.mcp.McpChannel;
23
 import org.noear.solon.ai.mcp.McpChannel;
22
 import org.noear.solon.ai.mcp.client.McpClientProvider;
24
 import org.noear.solon.ai.mcp.client.McpClientProvider;
44
 import java.util.ArrayList;
46
 import java.util.ArrayList;
45
 import java.util.List;
47
 import java.util.List;
46
 
48
 
47
-
48
 /**
49
 /**
49
  * mcp模型上下文协议Controller
50
  * mcp模型上下文协议Controller
50
  * 
51
  * 
53
  */
54
  */
54
 @RestController
55
 @RestController
55
 @RequestMapping("/llm/mcp")
56
 @RequestMapping("/llm/mcp")
56
-public class McpController extends BaseController
57
-{
57
+public class McpController extends BaseController {
58
     private static final Logger log = LoggerFactory.getLogger(McpController.class);
58
     private static final Logger log = LoggerFactory.getLogger(McpController.class);
59
 
59
 
60
     /** 仅允许上传 .docx 文件 */
60
     /** 仅允许上传 .docx 文件 */
61
-    private static final String[] DOCX_EXTENSION = {"docx"};
61
+    private static final String[] DOCX_EXTENSION = { "docx" };
62
 
62
 
63
     /** Python 脚本输出的文件名 */
63
     /** Python 脚本输出的文件名 */
64
     private static final String FORMATTED_OUTPUT_NAME = "formatted_output.docx";
64
     private static final String FORMATTED_OUTPUT_NAME = "formatted_output.docx";
89
 
89
 
90
     /**
90
     /**
91
      * 自动调用mcp工具问答
91
      * 自动调用mcp工具问答
92
+     * 
92
      * @return
93
      * @return
93
      */
94
      */
94
     @GetMapping("/answer")
95
     @GetMapping("/answer")
95
-    public AssistantMessage answer(String topicId, String question) throws IOException {
96
+    public AssistantMessage answer(String topicId, String question) throws Throwable {
96
         McpClientProvider clientProvider = McpClientProvider.builder()
97
         McpClientProvider clientProvider = McpClientProvider.builder()
97
-                .channel(McpChannel.STREAMABLE_STATELESS )
98
+                .channel(McpChannel.STREAMABLE_STATELESS)
98
                 .url("http://localhost:8087/mcp/sse")
99
                 .url("http://localhost:8087/mcp/sse")
99
                 .build();
100
                 .build();
100
         ChatModel chatModel = ChatModel.of(llmServiceUrl)
101
         ChatModel chatModel = ChatModel.of(llmServiceUrl)
111
             messages.add(ChatMessage.ofAssistant(chat.getOutput()));
112
             messages.add(ChatMessage.ofAssistant(chat.getOutput()));
112
         }
113
         }
113
         messages.add(ChatMessage.ofUser(question));
114
         messages.add(ChatMessage.ofUser(question));
114
-        ChatSession chatSession =  InMemoryChatSession.builder().messages(messages).build();
115
-        ChatResponse response = chatModel.prompt(chatSession).call();
116
-        String resultContent = response.lastChoice().getMessage().getResultContent();
115
+        ChatSession chatSession = InMemoryChatSession.builder().messages(messages).build();
116
+        SimpleAgent robot = SimpleAgent.of(chatModel)
117
+                .build();
118
+        Prompt prompt1 = Prompt.of(question).attrPut("session", chatSession);
119
+        String resultContent = robot.prompt(prompt1).call().getMessage().getResultContent();
117
         AssistantMessage assistantMessage;
120
         AssistantMessage assistantMessage;
118
         if (resultContent.startsWith("<tool_call>")) {
121
         if (resultContent.startsWith("<tool_call>")) {
119
             String content = resultContent.replace("<tool_call>\n", "").replace("\n</tool_call>", "");
122
             String content = resultContent.replace("<tool_call>\n", "").replace("\n</tool_call>", "");
120
             JSONObject jsonObject = JSONObject.parseObject(content);
123
             JSONObject jsonObject = JSONObject.parseObject(content);
121
             String name = jsonObject.getString("name");
124
             String name = jsonObject.getString("name");
122
             JSONObject arguments = jsonObject.getJSONObject("arguments");
125
             JSONObject arguments = jsonObject.getJSONObject("arguments");
123
-            if (arguments.getString("templatePath").contains("招标") || arguments.getString("templatePath").contains("询价")) {
126
+            if (arguments.getString("templatePath").contains("招标")
127
+                    || arguments.getString("templatePath").contains("询价")) {
124
                 arguments.put("collectionName", "technical");
128
                 arguments.put("collectionName", "technical");
125
-                String agentName = cmcAgentService.selectCmcAgentByAgentId(cmcTopicService.selectCmcTopicByTopicId(topicId).getAgentId()).getAgentName();
129
+                String agentName = cmcAgentService
130
+                        .selectCmcAgentByAgentId(cmcTopicService.selectCmcTopicByTopicId(topicId).getAgentId())
131
+                        .getAgentName();
126
                 arguments.put("agentName", agentName);
132
                 arguments.put("agentName", agentName);
127
                 arguments.put("title", question);
133
                 arguments.put("title", question);
128
             }
134
             }
129
             resultContent = clientProvider.callTool(name, arguments).getContent();
135
             resultContent = clientProvider.callTool(name, arguments).getContent();
130
             assistantMessage = ChatMessage.ofAssistant(resultContent);
136
             assistantMessage = ChatMessage.ofAssistant(resultContent);
131
-        }
132
-        else
137
+        } else
133
             throw new IOException("模型上下文工具未准备就绪,请重试");
138
             throw new IOException("模型上下文工具未准备就绪,请重试");
134
         return assistantMessage;
139
         return assistantMessage;
135
     }
140
     }
141
      * @return 格式化后的文件下载路径
146
      * @return 格式化后的文件下载路径
142
      */
147
      */
143
     @PostMapping("/formatDoc")
148
     @PostMapping("/formatDoc")
144
-    public AjaxResult formatDoc(MultipartFile file)
145
-    {
146
-        try
147
-        {
148
-            if (file == null || file.isEmpty())
149
-            {
149
+    public AjaxResult formatDoc(MultipartFile file) {
150
+        try {
151
+            if (file == null || file.isEmpty()) {
150
                 return AjaxResult.error("上传文件不能为空");
152
                 return AjaxResult.error("上传文件不能为空");
151
             }
153
             }
152
 
154
 
167
 
169
 
168
             // 4. 定位格式化后的输出文件(与上传文件同目录下的 formatted_output.docx)
170
             // 4. 定位格式化后的输出文件(与上传文件同目录下的 formatted_output.docx)
169
             File outputFile = new File(new File(uploadedAbsolutePath).getParentFile(), FORMATTED_OUTPUT_NAME);
171
             File outputFile = new File(new File(uploadedAbsolutePath).getParentFile(), FORMATTED_OUTPUT_NAME);
170
-            if (!outputFile.exists())
171
-            {
172
+            if (!outputFile.exists()) {
172
                 return AjaxResult.error("文档格式化失败,未找到输出文件");
173
                 return AjaxResult.error("文档格式化失败,未找到输出文件");
173
             }
174
             }
174
 
175
 
182
             ajax.put("newFileName", FORMATTED_OUTPUT_NAME);
183
             ajax.put("newFileName", FORMATTED_OUTPUT_NAME);
183
             ajax.put("originalFilename", file.getOriginalFilename());
184
             ajax.put("originalFilename", file.getOriginalFilename());
184
             return ajax;
185
             return ajax;
185
-        }
186
-        catch (Exception e)
187
-        {
186
+        } catch (Exception e) {
188
             log.error("Word 文档格式化失败", e);
187
             log.error("Word 文档格式化失败", e);
189
             return AjaxResult.error("文档格式化失败: " + e.getMessage());
188
             return AjaxResult.error("文档格式化失败: " + e.getMessage());
190
         }
189
         }
193
     /**
192
     /**
194
      * 将 /profile/... 形式的资源路径转为本地绝对路径
193
      * 将 /profile/... 形式的资源路径转为本地绝对路径
195
      */
194
      */
196
-    private String toAbsolutePath(String resourcePath)
197
-    {
195
+    private String toAbsolutePath(String resourcePath) {
198
         return RuoYiConfig.getProfile() + StringUtils.substringAfter(resourcePath, Constants.RESOURCE_PREFIX);
196
         return RuoYiConfig.getProfile() + StringUtils.substringAfter(resourcePath, Constants.RESOURCE_PREFIX);
199
     }
197
     }
200
 
198
 
201
     /**
199
     /**
202
      * 根据上传文件的资源路径,推导格式化输出文件的资源路径
200
      * 根据上传文件的资源路径,推导格式化输出文件的资源路径
203
      */
201
      */
204
-    private String buildOutputResourcePath(String uploadedResourcePath)
205
-    {
202
+    private String buildOutputResourcePath(String uploadedResourcePath) {
206
         int lastSlash = uploadedResourcePath.lastIndexOf('/');
203
         int lastSlash = uploadedResourcePath.lastIndexOf('/');
207
         return uploadedResourcePath.substring(0, lastSlash + 1) + FORMATTED_OUTPUT_NAME;
204
         return uploadedResourcePath.substring(0, lastSlash + 1) + FORMATTED_OUTPUT_NAME;
208
     }
205
     }
210
     /**
207
     /**
211
      * 校验格式化服务与模版文件是否可用
208
      * 校验格式化服务与模版文件是否可用
212
      */
209
      */
213
-    private void validateFormatterConfig() throws IOException
214
-    {
215
-        if (StringUtils.isEmpty(pythonServiceUrl))
216
-        {
210
+    private void validateFormatterConfig() throws IOException {
211
+        if (StringUtils.isEmpty(pythonServiceUrl)) {
217
             throw new IOException("Python 服务 URL 未配置: cmc.pythonService.url");
212
             throw new IOException("Python 服务 URL 未配置: cmc.pythonService.url");
218
         }
213
         }
219
-        if (StringUtils.isEmpty(docFormatterTemplatePath) || !new File(docFormatterTemplatePath).exists())
220
-        {
214
+        if (StringUtils.isEmpty(docFormatterTemplatePath) || !new File(docFormatterTemplatePath).exists()) {
221
             throw new IOException("Word 模版文件路径未配置或文件不存在: " + docFormatterTemplatePath);
215
             throw new IOException("Word 模版文件路径未配置或文件不存在: " + docFormatterTemplatePath);
222
         }
216
         }
223
 
217
 
225
                 ? pythonServiceUrl + "health"
219
                 ? pythonServiceUrl + "health"
226
                 : pythonServiceUrl + "/health";
220
                 : pythonServiceUrl + "/health";
227
         String healthResponse = HttpUtils.sendGet(healthUrl);
221
         String healthResponse = HttpUtils.sendGet(healthUrl);
228
-        if (StringUtils.isEmpty(healthResponse) || !healthResponse.contains("\"status\":\"ok\""))
229
-        {
222
+        if (StringUtils.isEmpty(healthResponse) || !healthResponse.contains("\"status\":\"ok\"")) {
230
             throw new IOException("Python 服务不可用,请确认已启动: " + pythonServiceUrl);
223
             throw new IOException("Python 服务不可用,请确认已启动: " + pythonServiceUrl);
231
         }
224
         }
232
     }
225
     }
238
      * @param templateAbsolutePath 模版文件的绝对路径
231
      * @param templateAbsolutePath 模版文件的绝对路径
239
      * @return 服务返回的消息内容
232
      * @return 服务返回的消息内容
240
      */
233
      */
241
-    private String callDocFormatterService(String uploadedAbsolutePath, String templateAbsolutePath) throws IOException
242
-    {
234
+    private String callDocFormatterService(String uploadedAbsolutePath, String templateAbsolutePath)
235
+            throws IOException {
243
         JSONObject body = new JSONObject();
236
         JSONObject body = new JSONObject();
244
         body.put("uploaded_file_path", uploadedAbsolutePath);
237
         body.put("uploaded_file_path", uploadedAbsolutePath);
245
         body.put("template_file_path", templateAbsolutePath);
238
         body.put("template_file_path", templateAbsolutePath);
256
         conn.setReadTimeout(300_000);
249
         conn.setReadTimeout(300_000);
257
         conn.setDoOutput(true);
250
         conn.setDoOutput(true);
258
 
251
 
259
-        try (OutputStream os = conn.getOutputStream())
260
-        {
252
+        try (OutputStream os = conn.getOutputStream()) {
261
             os.write(body.toJSONString().getBytes(StandardCharsets.UTF_8));
253
             os.write(body.toJSONString().getBytes(StandardCharsets.UTF_8));
262
         }
254
         }
263
 
255
 
264
         int status = conn.getResponseCode();
256
         int status = conn.getResponseCode();
265
         InputStream responseStream = status >= 400 ? conn.getErrorStream() : conn.getInputStream();
257
         InputStream responseStream = status >= 400 ? conn.getErrorStream() : conn.getInputStream();
266
-        if (responseStream == null)
267
-        {
258
+        if (responseStream == null) {
268
             throw new IOException("文档格式化服务无响应,HTTP 状态码: " + status);
259
             throw new IOException("文档格式化服务无响应,HTTP 状态码: " + status);
269
         }
260
         }
270
 
261
 
271
         StringBuilder responseBuilder = new StringBuilder();
262
         StringBuilder responseBuilder = new StringBuilder();
272
         try (BufferedReader reader = new BufferedReader(
263
         try (BufferedReader reader = new BufferedReader(
273
-                new InputStreamReader(responseStream, StandardCharsets.UTF_8)))
274
-        {
264
+                new InputStreamReader(responseStream, StandardCharsets.UTF_8))) {
275
             String line;
265
             String line;
276
-            while ((line = reader.readLine()) != null)
277
-            {
266
+            while ((line = reader.readLine()) != null) {
278
                 responseBuilder.append(line);
267
                 responseBuilder.append(line);
279
             }
268
             }
280
         }
269
         }
281
 
270
 
282
         String responseBody = responseBuilder.toString();
271
         String responseBody = responseBuilder.toString();
283
-        if (status >= 400)
284
-        {
272
+        if (status >= 400) {
285
             throw new IOException("文档格式化服务请求失败,HTTP " + status + ",响应: " + responseBody);
273
             throw new IOException("文档格式化服务请求失败,HTTP " + status + ",响应: " + responseBody);
286
         }
274
         }
287
 
275
 
288
         JSONObject result = JSONObject.parseObject(responseBody);
276
         JSONObject result = JSONObject.parseObject(responseBody);
289
-        if (!Boolean.TRUE.equals(result.getBoolean("success")))
290
-        {
277
+        if (!Boolean.TRUE.equals(result.getBoolean("success"))) {
291
             throw new IOException("文档格式化失败: " + result.getString("message"));
278
             throw new IOException("文档格式化失败: " + result.getString("message"));
292
         }
279
         }
293
         return result.getString("message");
280
         return result.getString("message");

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