|
|
@@ -23,8 +23,6 @@ import io.milvus.v2.service.vector.request.SearchReq;
|
|
23
|
23
|
import io.milvus.v2.service.vector.request.data.BaseVector;
|
|
24
|
24
|
import io.milvus.v2.service.vector.request.data.FloatVec;
|
|
25
|
25
|
import io.milvus.v2.service.vector.response.SearchResp;
|
|
26
|
|
-import io.modelcontextprotocol.server.McpServer;
|
|
27
|
|
-import io.modelcontextprotocol.spec.McpServerSession;
|
|
28
|
26
|
import org.apache.poi.hwpf.HWPFDocument;
|
|
29
|
27
|
import org.apache.poi.hwpf.usermodel.Paragraph;
|
|
30
|
28
|
import org.apache.poi.hwpf.usermodel.Range;
|
|
|
@@ -32,12 +30,13 @@ import org.apache.poi.xwpf.usermodel.*;
|
|
32
|
30
|
import org.apache.xmlbeans.XmlCursor;
|
|
33
|
31
|
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPPrGeneral;
|
|
34
|
32
|
import org.noear.solon.Solon;
|
|
|
33
|
+import org.noear.solon.ai.agent.simple.SimpleAgent;
|
|
35
|
34
|
import org.noear.solon.ai.annotation.ToolMapping;
|
|
36
|
35
|
import org.noear.solon.ai.chat.ChatModel;
|
|
37
|
|
-import org.noear.solon.ai.chat.ChatResponse;
|
|
38
|
36
|
import org.noear.solon.ai.chat.ChatSession;
|
|
39
|
37
|
import org.noear.solon.ai.chat.message.AssistantMessage;
|
|
40
|
38
|
import org.noear.solon.ai.chat.message.ChatMessage;
|
|
|
39
|
+import org.noear.solon.ai.chat.prompt.Prompt;
|
|
41
|
40
|
import org.noear.solon.ai.chat.session.InMemoryChatSession;
|
|
42
|
41
|
import org.noear.solon.ai.mcp.McpChannel;
|
|
43
|
42
|
import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint;
|
|
|
@@ -52,7 +51,7 @@ import java.sql.*;
|
|
52
|
51
|
import java.util.*;
|
|
53
|
52
|
|
|
54
|
53
|
@Service
|
|
55
|
|
-@McpServerEndpoint(channel = McpChannel.STREAMABLE_STATELESS , mcpEndpoint = "/mcp/sse")
|
|
|
54
|
+@McpServerEndpoint(channel = McpChannel.STREAMABLE_STATELESS, mcpEndpoint = "/mcp/sse")
|
|
56
|
55
|
public class McpServiceImpl implements IMcpService {
|
|
57
|
56
|
|
|
58
|
57
|
private static final EmbeddingModel embeddingModel = new BgeSmallZhV15EmbeddingModel();
|
|
|
@@ -76,10 +75,10 @@ public class McpServiceImpl implements IMcpService {
|
|
76
|
75
|
if (milvusServiceUrl == null || milvusServiceUrl.isEmpty()) {
|
|
77
|
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
|
84
|
@PreDestroy
|
|
|
@@ -96,19 +95,26 @@ public class McpServiceImpl implements IMcpService {
|
|
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
|
107
|
* 调用LLM+RAG(外部文件+知识库)生成回答
|
|
101
|
108
|
*/
|
|
102
|
109
|
@ToolMapping(description = "章节撰写")
|
|
103
|
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
|
114
|
try {
|
|
109
|
115
|
if (templatePath.startsWith("/dev-api"))
|
|
110
|
116
|
templatePath = templatePath.replace("/dev-api/profile", profile);
|
|
111
|
|
- templatePath =profile + templatePath;
|
|
|
117
|
+ templatePath = profile + templatePath;
|
|
112
|
118
|
List<String> subTitles = extractSubTitles(templatePath, title);
|
|
113
|
119
|
List<JSONObject> contexts = retrieveFromMilvus(collectionName, title, 10);
|
|
114
|
120
|
return generateAnswerWithDocumentAndCollection(agentName, templatePath, subTitles, contexts);
|
|
|
@@ -121,9 +127,8 @@ public class McpServiceImpl implements IMcpService {
|
|
121
|
127
|
* 查询OA数据生成回答
|
|
122
|
128
|
*/
|
|
123
|
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
|
132
|
return executeQuery(sqlString);
|
|
128
|
133
|
}
|
|
129
|
134
|
|
|
|
@@ -131,8 +136,7 @@ public class McpServiceImpl implements IMcpService {
|
|
131
|
136
|
* 获取所有表名列表
|
|
132
|
137
|
*/
|
|
133
|
138
|
@ToolMapping(description = "获取数据库中所有表名列表")
|
|
134
|
|
- public JSONArray GetAllTableNames()
|
|
135
|
|
- {
|
|
|
139
|
+ public JSONArray GetAllTableNames() {
|
|
136
|
140
|
return getAllTableNames();
|
|
137
|
141
|
}
|
|
138
|
142
|
|
|
|
@@ -140,8 +144,7 @@ public class McpServiceImpl implements IMcpService {
|
|
140
|
144
|
* 获取指定表的结构信息
|
|
141
|
145
|
*/
|
|
142
|
146
|
@ToolMapping(description = "获取指定表的结构信息")
|
|
143
|
|
- public JSONObject GetTableStructure(@Param(description = "表名") String tableName)
|
|
144
|
|
- {
|
|
|
147
|
+ public JSONObject GetTableStructure(@Param(description = "表名") String tableName) {
|
|
145
|
148
|
return getTableStructure(tableName);
|
|
146
|
149
|
}
|
|
147
|
150
|
|
|
|
@@ -155,8 +158,10 @@ public class McpServiceImpl implements IMcpService {
|
|
155
|
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
|
166
|
ResultSet rs = stmt.executeQuery();
|
|
162
|
167
|
ResultSetMetaData metaData = rs.getMetaData();
|
|
|
@@ -181,6 +186,7 @@ public class McpServiceImpl implements IMcpService {
|
|
181
|
186
|
|
|
182
|
187
|
/**
|
|
183
|
188
|
* 从Milvus检索相关文档
|
|
|
189
|
+ *
|
|
184
|
190
|
* @return
|
|
185
|
191
|
*/
|
|
186
|
192
|
public List<JSONObject> retrieveFromMilvus(String collectionName, String query, int topK) {
|
|
|
@@ -198,7 +204,8 @@ public class McpServiceImpl implements IMcpService {
|
|
198
|
204
|
/**
|
|
199
|
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
|
209
|
StringBuilder sb = new StringBuilder("招标文件内容:\n\n");
|
|
203
|
210
|
String filename = templatePath.replace("_" + agentName, "");
|
|
204
|
211
|
File profilePath = new File(filename);
|
|
|
@@ -235,7 +242,7 @@ public class McpServiceImpl implements IMcpService {
|
|
235
|
242
|
.append("段落格式").append(": ")
|
|
236
|
243
|
.append(context.getString("content")).append("\n\n");
|
|
237
|
244
|
}
|
|
238
|
|
- content.append( "招标文件分析完成,章节内容已写入【<a href='")
|
|
|
245
|
+ content.append("招标文件分析完成,章节内容已写入【<a href='")
|
|
239
|
246
|
.append(templatePath.replace(profile, "/dev-api/profile"))
|
|
240
|
247
|
.append("'> 技术文件" + "</a>】,请查阅\n\n")
|
|
241
|
248
|
.append("如需修改,请输入技术文件已有内容的章节标题\n\n")
|
|
|
@@ -245,24 +252,28 @@ public class McpServiceImpl implements IMcpService {
|
|
245
|
252
|
|
|
246
|
253
|
/**
|
|
247
|
254
|
* 调用LLM生成回答
|
|
|
255
|
+ *
|
|
248
|
256
|
* @return
|
|
249
|
257
|
*/
|
|
250
|
|
- public String generateAnswer(String prompt) throws IOException {
|
|
|
258
|
+ public String generateAnswer(String prompt) throws Throwable {
|
|
251
|
259
|
ChatModel chatModel = ChatModel.of(llmServiceUrl)
|
|
252
|
260
|
.model("Qwen")
|
|
253
|
261
|
.build();
|
|
254
|
262
|
|
|
255
|
263
|
List<ChatMessage> messages = new ArrayList<>();
|
|
256
|
264
|
messages.add(ChatMessage.ofUser(prompt));
|
|
257
|
|
- ChatSession chatSession = InMemoryChatSession.builder().messages(messages).build();
|
|
|
265
|
+ ChatSession chatSession = InMemoryChatSession.builder().messages(messages).build();
|
|
258
|
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
|
277
|
* @return
|
|
267
|
278
|
*/
|
|
268
|
279
|
public void writeContent(String content, List<String> titles, String absolutePath) throws IOException {
|
|
|
@@ -287,8 +298,7 @@ public class McpServiceImpl implements IMcpService {
|
|
287
|
298
|
}
|
|
288
|
299
|
}
|
|
289
|
300
|
}
|
|
290
|
|
- }
|
|
291
|
|
- else
|
|
|
301
|
+ } else
|
|
292
|
302
|
text.append(content);
|
|
293
|
303
|
map.put(titles.get(i), text.toString());
|
|
294
|
304
|
}
|
|
|
@@ -371,7 +381,8 @@ public class McpServiceImpl implements IMcpService {
|
|
371
|
381
|
StringBuilder currentLevel3Content = new StringBuilder();
|
|
372
|
382
|
String[] paragraphs = content.split("\n");
|
|
373
|
383
|
for (String para : paragraphs) {
|
|
374
|
|
- if (para.trim().isEmpty()) continue;
|
|
|
384
|
+ if (para.trim().isEmpty())
|
|
|
385
|
+ continue;
|
|
375
|
386
|
|
|
376
|
387
|
if (isDocxLevel3Title(para.trim(), document)) {
|
|
377
|
388
|
if (currentLevel3Content.length() != 0) {
|
|
|
@@ -403,7 +414,8 @@ public class McpServiceImpl implements IMcpService {
|
|
403
|
414
|
for (int j = 0; j < level2Range.numParagraphs(); j++) {
|
|
404
|
415
|
Paragraph p = level2Range.getParagraph(j);
|
|
405
|
416
|
String paraText = p.text().trim();
|
|
406
|
|
- if (paraText.isEmpty()) continue;
|
|
|
417
|
+ if (paraText.isEmpty())
|
|
|
418
|
+ continue;
|
|
407
|
419
|
|
|
408
|
420
|
int pLevel = getDocOutlineLevel(p);
|
|
409
|
421
|
if (pLevel == 2 && paraText.equals(content.split("\n")[0].trim())) {
|
|
|
@@ -450,7 +462,7 @@ public class McpServiceImpl implements IMcpService {
|
|
450
|
462
|
|
|
451
|
463
|
try (XWPFDocument document = new XWPFDocument(fileInputStream)) {
|
|
452
|
464
|
|
|
453
|
|
- for (XWPFParagraph paragraph : document.getParagraphs()) {
|
|
|
465
|
+ for (XWPFParagraph paragraph : document.getParagraphs()) {
|
|
454
|
466
|
String text = paragraph.getText();
|
|
455
|
467
|
int level = getDocxOutlineLevel(paragraph, document);
|
|
456
|
468
|
if (level > 0) {
|
|
|
@@ -485,7 +497,6 @@ public class McpServiceImpl implements IMcpService {
|
|
485
|
497
|
}
|
|
486
|
498
|
}
|
|
487
|
499
|
|
|
488
|
|
-
|
|
489
|
500
|
return subTitles;
|
|
490
|
501
|
|
|
491
|
502
|
}
|
|
|
@@ -494,7 +505,7 @@ public class McpServiceImpl implements IMcpService {
|
|
494
|
505
|
// 检查一个标题是否是叶子节点(没有更低级别的子标题)
|
|
495
|
506
|
private boolean isLeafNode(XWPFDocument doc, XWPFParagraph p, int level) {
|
|
496
|
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
|
509
|
return true;
|
|
499
|
510
|
}
|
|
500
|
511
|
|
|
|
@@ -529,12 +540,14 @@ public class McpServiceImpl implements IMcpService {
|
|
529
|
540
|
|
|
530
|
541
|
/**
|
|
531
|
542
|
* 检索知识库
|
|
|
543
|
+ *
|
|
532
|
544
|
* @return
|
|
533
|
545
|
*/
|
|
534
|
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
|
551
|
LoadCollectionReq loadCollectionReq = LoadCollectionReq.builder()
|
|
539
|
552
|
.collectionName(collectionName)
|
|
540
|
553
|
.build();
|
|
|
@@ -579,7 +592,8 @@ public class McpServiceImpl implements IMcpService {
|
|
579
|
592
|
|
|
580
|
593
|
for (XWPFParagraph paragraph : xwpfDocument.getParagraphs()) {
|
|
581
|
594
|
String text = paragraph.getText().trim();
|
|
582
|
|
- if (text.isEmpty()) continue;
|
|
|
595
|
+ if (text.isEmpty())
|
|
|
596
|
+ continue;
|
|
583
|
597
|
|
|
584
|
598
|
int paraLevel = getDocxOutlineLevel(paragraph, xwpfDocument);
|
|
585
|
599
|
|
|
|
@@ -614,8 +628,7 @@ public class McpServiceImpl implements IMcpService {
|
|
614
|
628
|
}
|
|
615
|
629
|
}
|
|
616
|
630
|
return segments;
|
|
617
|
|
- }
|
|
618
|
|
- else if (filename.endsWith(".doc")) {
|
|
|
631
|
+ } else if (filename.endsWith(".doc")) {
|
|
619
|
632
|
List<TextSegment> segments = new ArrayList<>();
|
|
620
|
633
|
try (HWPFDocument hwpfDocument = new HWPFDocument(fileInputStream)) {
|
|
621
|
634
|
StringBuilder currentLevel2Content = new StringBuilder();
|
|
|
@@ -625,7 +638,8 @@ public class McpServiceImpl implements IMcpService {
|
|
625
|
638
|
for (int i = 0; i < range.numParagraphs(); i++) {
|
|
626
|
639
|
Paragraph paragraph = range.getParagraph(i);
|
|
627
|
640
|
String text = paragraph.text().trim();
|
|
628
|
|
- if (text.isEmpty()) continue;
|
|
|
641
|
+ if (text.isEmpty())
|
|
|
642
|
+ continue;
|
|
629
|
643
|
|
|
630
|
644
|
int paraLevel = getDocOutlineLevel(paragraph);
|
|
631
|
645
|
|
|
|
@@ -664,15 +678,13 @@ public class McpServiceImpl implements IMcpService {
|
|
664
|
678
|
|
|
665
|
679
|
else if (filename.endsWith(".pdf")) {
|
|
666
|
680
|
Document document = new ApachePdfBoxDocumentParser().parse(fileInputStream);
|
|
667
|
|
- DocumentByParagraphSplitter splitter = new DocumentByParagraphSplitter(300,50);
|
|
|
681
|
+ DocumentByParagraphSplitter splitter = new DocumentByParagraphSplitter(300, 50);
|
|
668
|
682
|
return splitter.split(document);
|
|
669
|
|
- }
|
|
670
|
|
- else if (filename.endsWith(".txt")) {
|
|
|
683
|
+ } else if (filename.endsWith(".txt")) {
|
|
671
|
684
|
Document document = new TextDocumentParser().parse(fileInputStream);
|
|
672
|
|
- DocumentByParagraphSplitter splitter = new DocumentByParagraphSplitter(300,50);
|
|
|
685
|
+ DocumentByParagraphSplitter splitter = new DocumentByParagraphSplitter(300, 50);
|
|
673
|
686
|
return splitter.split(document);
|
|
674
|
|
- }
|
|
675
|
|
- else {
|
|
|
687
|
+ } else {
|
|
676
|
688
|
throw new UnsupportedOperationException("不支持文件类型: " + filename);
|
|
677
|
689
|
}
|
|
678
|
690
|
}
|
|
|
@@ -689,7 +701,9 @@ public class McpServiceImpl implements IMcpService {
|
|
689
|
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
|
708
|
DatabaseMetaData metaData = conn.getMetaData();
|
|
695
|
709
|
|
|
|
@@ -738,10 +752,12 @@ public class McpServiceImpl implements IMcpService {
|
|
738
|
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
|
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
|
762
|
while (tables.next()) {
|
|
747
|
763
|
String tableName = tables.getString("TABLE_NAME");
|