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