|
@@ -3,6 +3,7 @@ package com.ruoyi.agent.service.impl;
|
3
|
3
|
import com.alibaba.fastjson2.JSONObject;
|
4
|
4
|
import com.ruoyi.agent.service.IMcpService;
|
5
|
5
|
import dev.langchain4j.data.document.Document;
|
|
6
|
+import dev.langchain4j.data.document.parser.TextDocumentParser;
|
6
|
7
|
import dev.langchain4j.data.document.parser.apache.pdfbox.ApachePdfBoxDocumentParser;
|
7
|
8
|
import dev.langchain4j.data.document.splitter.DocumentByParagraphSplitter;
|
8
|
9
|
import dev.langchain4j.data.embedding.Embedding;
|
|
@@ -23,11 +24,12 @@ import io.milvus.param.collection.LoadCollectionParam;
|
23
|
24
|
import io.milvus.param.collection.ReleaseCollectionParam;
|
24
|
25
|
import io.milvus.param.dml.SearchParam;
|
25
|
26
|
import io.milvus.response.SearchResultsWrapper;
|
|
27
|
+import org.apache.poi.hwpf.HWPFDocument;
|
26
|
28
|
import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
|
27
|
29
|
import org.apache.poi.xwpf.usermodel.XWPFDocument;
|
28
|
30
|
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
|
|
31
|
+import org.apache.poi.xwpf.usermodel.XWPFRun;
|
29
|
32
|
import org.noear.solon.Solon;
|
30
|
|
-import org.noear.solon.ai.annotation.PromptMapping;
|
31
|
33
|
import org.noear.solon.ai.annotation.ToolMapping;
|
32
|
34
|
import org.noear.solon.ai.chat.ChatModel;
|
33
|
35
|
import org.noear.solon.ai.chat.ChatResponse;
|
|
@@ -37,9 +39,7 @@ import org.noear.solon.ai.chat.message.AssistantMessage;
|
37
|
39
|
import org.noear.solon.ai.chat.message.ChatMessage;
|
38
|
40
|
import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint;
|
39
|
41
|
import org.noear.solon.annotation.Param;
|
40
|
|
-import org.reactivestreams.Publisher;
|
41
|
42
|
import org.springframework.stereotype.Service;
|
42
|
|
-import reactor.core.publisher.Flux;
|
43
|
43
|
|
44
|
44
|
import java.io.*;
|
45
|
45
|
import java.util.*;
|
|
@@ -60,13 +60,14 @@ public class McpServiceImpl implements IMcpService {
|
60
|
60
|
* 调用LLM+RAG(外部文件+知识库)生成回答
|
61
|
61
|
*/
|
62
|
62
|
@ToolMapping(description = "章节撰写")
|
63
|
|
- public AssistantMessage writeParagraph(@Param(description = "智能体名称") String collectionName,
|
64
|
|
- @Param(description = "章节名称") String title,
|
65
|
|
- @Param(description = "招标文件地址") String templatePath) throws IOException
|
|
63
|
+ public AssistantMessage writeParagraph(@Param(description = "知识库名称") String collectionName,
|
|
64
|
+ @Param(description = "智能体名称") String agentName,
|
|
65
|
+ @Param(description = "章节名称") String title,
|
|
66
|
+ @Param(description = "技术文件地址") String templatePath) throws IOException
|
66
|
67
|
{
|
67
|
68
|
title = String.join(",", extractSubTitles( "/upload/agent/template/technical.docx", title));
|
68
|
|
- List<JSONObject> requests = retrieveFromMilvus(milvusClient, embeddingModel, collectionName, title, 10);
|
69
|
|
- return generateAnswerWithDocumentAndCollection(embeddingModel, templatePath, title, requests, "http://192.168.28.188:8000/v1/chat/completions");
|
|
69
|
+ 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");
|
70
|
71
|
}
|
71
|
72
|
|
72
|
73
|
/**
|
|
@@ -88,9 +89,9 @@ public class McpServiceImpl implements IMcpService {
|
88
|
89
|
/**
|
89
|
90
|
* 调用LLM生成回答
|
90
|
91
|
*/
|
91
|
|
- public AssistantMessage generateAnswerWithDocumentAndCollection(EmbeddingModel embeddingModel, String templatePath, String question, List<JSONObject> contexts, String llmServiceUrl) throws IOException {
|
|
92
|
+ public AssistantMessage generateAnswerWithDocumentAndCollection(EmbeddingModel embeddingModel, String agentName, String templatePath, String question, List<JSONObject> contexts, String llmServiceUrl) throws IOException {
|
92
|
93
|
StringBuilder sb = new StringBuilder("招标文件内容:\n\n");
|
93
|
|
- File profilePath = new File(Solon.cfg().getProperty("cmc.profile") + templatePath);
|
|
94
|
+ File profilePath = new File(templatePath.replace("/dev-api/profile", Solon.cfg().getProperty("cmc.profile")).replace("_" + agentName, ""));
|
94
|
95
|
List<TextSegment> segments = splitDocument(profilePath);
|
95
|
96
|
List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
|
96
|
97
|
InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
|
|
@@ -112,14 +113,14 @@ public class McpServiceImpl implements IMcpService {
|
112
|
113
|
// .append("段落格式").append(": ")
|
113
|
114
|
// .append(context.getString("content")).append("\n\n");
|
114
|
115
|
// }
|
115
|
|
- return generateAnswer(sb.toString(), llmServiceUrl);
|
|
116
|
+ return generateAnswer(sb.toString(), question, templatePath, llmServiceUrl);
|
116
|
117
|
}
|
117
|
118
|
|
118
|
119
|
/**
|
119
|
120
|
* 调用LLM生成回答
|
120
|
121
|
* @return
|
121
|
122
|
*/
|
122
|
|
- public AssistantMessage generateAnswer(String prompt, String llmServiceUrl) throws IOException {
|
|
123
|
+ public AssistantMessage generateAnswer(String prompt, String question, String templatePath, String llmServiceUrl) throws IOException {
|
123
|
124
|
ChatSession chatSession = new ChatSessionDefault();
|
124
|
125
|
ChatModel chatModel = ChatModel.of(llmServiceUrl)
|
125
|
126
|
.provider("openai")
|
|
@@ -129,7 +130,61 @@ public class McpServiceImpl implements IMcpService {
|
129
|
130
|
chatSession.addMessage(ChatMessage.ofUser(prompt));
|
130
|
131
|
|
131
|
132
|
ChatResponse response = chatModel.prompt(chatSession).call();
|
132
|
|
- return response.lastChoice().getMessage();
|
|
133
|
+ String content = response.lastChoice().getMessage().getContent() + "\n\n" +
|
|
134
|
+ "招标文件分析完成,章节内容已写入【<a href='" + templatePath.replace("/dev-api", "") + "'> 技术文件" + "</a>】,请查阅";
|
|
135
|
+ String absolutePath = templatePath.replace("/dev-api/profile", Solon.cfg().getProperty("cmc.profile"));
|
|
136
|
+ writeContent(response.lastChoice().getMessage().getContent(), question, absolutePath);
|
|
137
|
+ return ChatMessage.ofAssistant(content);
|
|
138
|
+ }
|
|
139
|
+
|
|
140
|
+ /**
|
|
141
|
+ * 写入章节内容
|
|
142
|
+ * @return
|
|
143
|
+ */
|
|
144
|
+ public void writeContent(String content, String question, String absolutePath) throws IOException {
|
|
145
|
+ File file = new File(absolutePath);
|
|
146
|
+ FileInputStream fileInputStream = new FileInputStream(file);
|
|
147
|
+ XWPFDocument document = new XWPFDocument(fileInputStream);
|
|
148
|
+ String[] contentLines = content.split("\n");
|
|
149
|
+ Map<String, String> map = new HashMap<>();
|
|
150
|
+ String[] titles = question.split(",");
|
|
151
|
+ for (int i = 0; i < titles.length; i ++) {
|
|
152
|
+ int startIndex = Arrays.asList(contentLines).indexOf(titles[i]);
|
|
153
|
+ StringBuilder text = new StringBuilder("");
|
|
154
|
+ if (i < titles.length - 1) {
|
|
155
|
+ int endIndex = Arrays.asList(contentLines).indexOf(titles[i + 1]);
|
|
156
|
+ for (int c = startIndex + 1; c < endIndex; c++)
|
|
157
|
+ text.append(contentLines[c]);
|
|
158
|
+ }
|
|
159
|
+ else {
|
|
160
|
+ if (startIndex + 1 < contentLines.length)
|
|
161
|
+ for (int c = startIndex + 1; c < contentLines.length; c++)
|
|
162
|
+ text.append(contentLines[c]);
|
|
163
|
+ }
|
|
164
|
+ map.put(titles[i], text.toString());
|
|
165
|
+ }
|
|
166
|
+ int count = 0;
|
|
167
|
+ for (int i = 0; i < document.getParagraphs().size(); i++) {
|
|
168
|
+ XWPFParagraph paragraph = document.getParagraphs().get(i);
|
|
169
|
+ for (String title : titles) {
|
|
170
|
+ if (paragraph.getText().equals(title)) {
|
|
171
|
+ int pos = document.getBodyElements().indexOf(paragraph) + 1;
|
|
172
|
+ XWPFParagraph contentParagraph = document.createParagraph();
|
|
173
|
+ contentParagraph.setStyle("1");
|
|
174
|
+ XWPFRun run = contentParagraph.createRun();
|
|
175
|
+ run.setText(map.get(title));
|
|
176
|
+ document.setParagraph(contentParagraph, pos);
|
|
177
|
+ count++;
|
|
178
|
+ }
|
|
179
|
+ }
|
|
180
|
+ if (count == titles.length)
|
|
181
|
+ break;
|
|
182
|
+ }
|
|
183
|
+ FileOutputStream out = new FileOutputStream(absolutePath);
|
|
184
|
+ document.write(out);
|
|
185
|
+ // 关闭文档
|
|
186
|
+ out.close();
|
|
187
|
+ document.close();
|
133
|
188
|
}
|
134
|
189
|
|
135
|
190
|
/**
|
|
@@ -216,7 +271,11 @@ public class McpServiceImpl implements IMcpService {
|
216
|
271
|
Document document;
|
217
|
272
|
InputStream fileInputStream = new FileInputStream(transferFile);
|
218
|
273
|
String filename = transferFile.getName().toLowerCase();
|
219
|
|
- if (filename.endsWith(".docx")) {
|
|
274
|
+ if (filename.endsWith(".doc")) {
|
|
275
|
+ HWPFDocument doc = new HWPFDocument(fileInputStream);
|
|
276
|
+ document = Document.from(doc.getDocumentText());
|
|
277
|
+ }
|
|
278
|
+ else if (filename.endsWith(".docx")) {
|
220
|
279
|
XWPFDocument docx = new XWPFDocument(fileInputStream);
|
221
|
280
|
XWPFWordExtractor extractor = new XWPFWordExtractor(docx);
|
222
|
281
|
String text = extractor.getText();
|
|
@@ -225,6 +284,9 @@ public class McpServiceImpl implements IMcpService {
|
225
|
284
|
else if (filename.endsWith(".pdf")) {
|
226
|
285
|
document = new ApachePdfBoxDocumentParser().parse(fileInputStream);
|
227
|
286
|
}
|
|
287
|
+ else if (filename.endsWith(".txt")) {
|
|
288
|
+ document = new TextDocumentParser().parse(fileInputStream);
|
|
289
|
+ }
|
228
|
290
|
else {
|
229
|
291
|
throw new UnsupportedOperationException("不支持文件类型: " + filename);
|
230
|
292
|
}
|