Selaa lähdekoodia

升级milvus版本,支持上传修改章节标题后的技术文件

lamphua 2 päivää sitten
vanhempi
commit
ca8d261ca8
20 muutettua tiedostoa jossa 911 lisäystä ja 407 poistoa
  1. 12
    0
      llm-back/pom.xml
  2. 2
    2
      llm-back/ruoyi-agent/pom.xml
  3. 1
    1
      llm-back/ruoyi-agent/src/main/java/com/ruoyi/agent/service/McpServerConfig.java
  4. 56
    62
      llm-back/ruoyi-agent/src/main/java/com/ruoyi/agent/service/impl/McpServiceImpl.java
  5. 2
    2
      llm-back/ruoyi-llm/pom.xml
  6. 10
    0
      llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/CmcAgentController.java
  7. 12
    23
      llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/KnowLedgeController.java
  8. 3
    10
      llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/McpController.java
  9. 2
    14
      llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/RagController.java
  10. 1
    5
      llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/SessionController.java
  11. 6
    9
      llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/service/ILangChainMilvusService.java
  12. 7
    10
      llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/service/IMilvusService.java
  13. 86
    82
      llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/service/impl/LangChainMilvusServiceImpl.java
  14. 112
    115
      llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/service/impl/MilvusServiceImpl.java
  15. 2
    2
      llm-back/ruoyi-system/pom.xml
  16. 8
    0
      llm-back/ruoyi-system/src/main/java/com/ruoyi/llm/service/ICmcAgentService.java
  17. 451
    63
      llm-back/ruoyi-system/src/main/java/com/ruoyi/llm/service/impl/CmcAgentServiceImpl.java
  18. 17
    2
      llm-ui/src/api/llm/agent.js
  19. 120
    4
      llm-ui/src/views/llm/agent/AgentDetail.vue
  20. 1
    1
      llm-ui/src/views/llm/knowledge/index.vue

+ 12
- 0
llm-back/pom.xml Näytä tiedosto

@@ -219,6 +219,18 @@
219 219
                 <version>${ruoyi.version}</version>
220 220
             </dependency>
221 221
 
222
+            <dependency>
223
+                <groupId>com.google.protobuf</groupId>
224
+                <artifactId>protobuf-java</artifactId>
225
+                <version>3.25.5</version>
226
+            </dependency>
227
+
228
+            <dependency>
229
+                <groupId>com.squareup.okhttp3</groupId>
230
+                <artifactId>okhttp</artifactId>
231
+                <version>4.12.0</version>
232
+            </dependency>
233
+
222 234
         </dependencies>
223 235
     </dependencyManagement>
224 236
 

+ 2
- 2
llm-back/ruoyi-agent/pom.xml Näytä tiedosto

@@ -17,7 +17,7 @@
17 17
     </description>
18 18
 
19 19
     <properties>
20
-        <solon.version>3.4.3</solon.version>
20
+        <solon.version>3.5.1</solon.version>
21 21
     </properties>
22 22
 
23 23
     <dependencies>
@@ -71,7 +71,7 @@
71 71
         <dependency>
72 72
             <groupId>io.milvus</groupId>
73 73
             <artifactId>milvus-sdk-java</artifactId>
74
-            <version>2.3.3</version>
74
+            <version>2.6.2</version>
75 75
         </dependency>
76 76
         <dependency>
77 77
             <groupId>dev.langchain4j</groupId>

+ 1
- 1
llm-back/ruoyi-agent/src/main/java/com/ruoyi/agent/service/McpServerConfig.java Näytä tiedosto

@@ -66,7 +66,7 @@ public class McpServerConfig {
66 66
     public FilterRegistrationBean mcpServerFilter() {
67 67
         FilterRegistrationBean<SolonServletFilter> filter = new FilterRegistrationBean<>();
68 68
         filter.setName("SolonFilter");
69
-        filter.addUrlPatterns("/llm/*");
69
+        filter.addUrlPatterns("/mcp/*");
70 70
         filter.setFilter(new SolonServletFilter());
71 71
         return filter;
72 72
     }

+ 56
- 62
llm-back/ruoyi-agent/src/main/java/com/ruoyi/agent/service/impl/McpServiceImpl.java Näytä tiedosto

@@ -13,17 +13,15 @@ import dev.langchain4j.model.embedding.onnx.bgesmallzhv15.BgeSmallZhV15Embedding
13 13
 import dev.langchain4j.store.embedding.EmbeddingMatch;
14 14
 import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
15 15
 import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
16
-import io.milvus.client.MilvusClient;
17
-import io.milvus.client.MilvusServiceClient;
18
-import io.milvus.grpc.SearchResults;
19
-import io.milvus.param.ConnectParam;
20
-import io.milvus.param.MetricType;
21
-import io.milvus.param.R;
22
-import io.milvus.param.RpcStatus;
23
-import io.milvus.param.collection.LoadCollectionParam;
24
-import io.milvus.param.collection.ReleaseCollectionParam;
25
-import io.milvus.param.dml.SearchParam;
26
-import io.milvus.response.SearchResultsWrapper;
16
+import io.milvus.v2.client.ConnectConfig;
17
+import io.milvus.v2.client.MilvusClientV2;
18
+import io.milvus.v2.common.IndexParam;
19
+import io.milvus.v2.service.collection.request.LoadCollectionReq;
20
+import io.milvus.v2.service.collection.request.ReleaseCollectionReq;
21
+import io.milvus.v2.service.vector.request.SearchReq;
22
+import io.milvus.v2.service.vector.request.data.BaseVector;
23
+import io.milvus.v2.service.vector.request.data.FloatVec;
24
+import io.milvus.v2.service.vector.response.SearchResp;
27 25
 import org.apache.poi.extractor.POITextExtractor;
28 26
 import org.apache.poi.extractor.ExtractorFactory;
29 27
 import org.apache.poi.xwpf.usermodel.*;
@@ -36,26 +34,25 @@ import org.noear.solon.ai.chat.ChatSession;
36 34
 import org.noear.solon.ai.chat.message.AssistantMessage;
37 35
 import org.noear.solon.ai.chat.message.ChatMessage;
38 36
 import org.noear.solon.ai.chat.session.InMemoryChatSession;
37
+import org.noear.solon.ai.mcp.McpChannel;
39 38
 import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint;
40 39
 import org.noear.solon.annotation.Param;
41 40
 import org.springframework.stereotype.Service;
42 41
 
43 42
 import java.io.*;
44 43
 import java.util.*;
45
-import java.util.stream.Collectors;
46 44
 
47 45
 @Service
48
-@McpServerEndpoint(sseEndpoint = "/llm/mcp/sse")
46
+@McpServerEndpoint(channel = McpChannel.SSE, mcpEndpoint = "/mcp/sse")
49 47
 public class McpServiceImpl implements IMcpService {
50 48
 
51 49
     private static final EmbeddingModel embeddingModel = new BgeSmallZhV15EmbeddingModel();
52 50
 
53 51
     private static final String llmServiceUrl = "http://192.168.28.188:8000/v1/chat/completions";
54 52
 
55
-    private static final MilvusServiceClient milvusClient = new MilvusServiceClient(
56
-            ConnectParam.newBuilder()
57
-                    .withHost("192.168.28.188")
58
-                    .withPort(19530)
53
+    private static final MilvusClientV2 milvusClient = new MilvusClientV2(
54
+            ConnectConfig.builder()
55
+                    .uri("http://192.168.28.188:19530")
59 56
                     .build());
60 57
 
61 58
     /**
@@ -70,9 +67,8 @@ public class McpServiceImpl implements IMcpService {
70 67
             try {
71 68
                 templatePath = templatePath.replace("/dev-api/profile", Solon.cfg().getProperty("cmc.profile"));
72 69
                 List<String> subTitles = extractSubTitles(templatePath, title);
73
-//                List<JSONObject> contexts = retrieveFromMilvus(milvusClient, embeddingModel, collectionName, title, 10);
74
-//                return generateAnswerWithDocumentAndCollection(embeddingModel, agentName, templatePath, title, contexts, llmServiceUrl);
75
-                return generateAnswerWithDocumentAndCollection(embeddingModel, agentName, templatePath, subTitles, new ArrayList<>());
70
+                List<JSONObject> contexts = retrieveFromMilvus(collectionName, title, 10);
71
+                return generateAnswerWithDocumentAndCollection(agentName, templatePath, subTitles, contexts);
76 72
             } catch (IOException e) {
77 73
                 throw new RuntimeException(e);
78 74
             }
@@ -82,22 +78,22 @@ public class McpServiceImpl implements IMcpService {
82 78
      * 从Milvus检索相关文档
83 79
      * @return
84 80
      */
85
-    public List<JSONObject> retrieveFromMilvus(MilvusClient milvusClient, EmbeddingModel embeddingModel, String collectionName, String query, int topK) {
86
-        SearchResultsWrapper wrapper = retrieve(milvusClient, embeddingModel, collectionName, query, topK);
87
-        return wrapper.getRowRecords(0).stream()
88
-                .map(record -> {
89
-                    JSONObject result = new JSONObject();
90
-                    result.put("file_name", record.get("file_name"));
91
-                    result.put("content", record.get("content"));
92
-                    return result;
93
-                })
94
-                .collect(Collectors.toList());
81
+    public List<JSONObject> retrieveFromMilvus(String collectionName, String query, int topK) {
82
+        List<JSONObject> resultList = new ArrayList<>();
83
+        List<List<SearchResp.SearchResult>> searchResultList = retrieve(collectionName, query, topK);
84
+        searchResultList.forEach(searchResult -> {
85
+            JSONObject result = new JSONObject();
86
+            result.put("file_name", searchResult.get(0).getEntity().get("file_name"));
87
+            result.put("content", searchResult.get(0).getEntity().get("content"));
88
+            resultList.add(result);
89
+        });
90
+        return resultList;
95 91
     }
96 92
 
97 93
     /**
98 94
      * 调用LLM生成回答
99 95
      */
100
-    public AssistantMessage generateAnswerWithDocumentAndCollection(EmbeddingModel embeddingModel, String agentName, String templatePath, List<String> titles, List<JSONObject> contexts) throws IOException {
96
+    public AssistantMessage generateAnswerWithDocumentAndCollection(String agentName, String templatePath, List<String> titles, List<JSONObject> contexts) throws IOException {
101 97
         StringBuilder sb = new StringBuilder("招标文件内容:\n\n");
102 98
         String filename = templatePath.replace("_" + agentName, "");
103 99
         File profilePath = new File(filename);
@@ -128,12 +124,12 @@ public class McpServiceImpl implements IMcpService {
128 124
         }
129 125
         String absolutePath = templatePath.replace("/dev-api/profile", Solon.cfg().getProperty("cmc.profile"));
130 126
         writeContent(content.toString(), titles, absolutePath);
131
-//        for (JSONObject context : contexts) {
132
-//            sb.append("文件").append(": ")
133
-//                    .append(context.getString("file_name")).append("\n\n")
134
-//                    .append("段落格式").append(": ")
135
-//                    .append(context.getString("content")).append("\n\n");
136
-//        }
127
+        for (JSONObject context : contexts) {
128
+            sb.append("文件").append(": ")
129
+                    .append(context.getString("file_name")).append("\n\n")
130
+                    .append("段落格式").append(": ")
131
+                    .append(context.getString("content")).append("\n\n");
132
+        }
137 133
         content.append( "招标文件分析完成,章节内容已写入【<a href='")
138 134
                 .append(templatePath.replace(Solon.cfg().getProperty("cmc.profile"), "/dev-api/profile"))
139 135
                 .append("'> 技术文件" + "</a>】,请查阅\n\n")
@@ -151,7 +147,6 @@ public class McpServiceImpl implements IMcpService {
151 147
                 .model("Qwen2.5-1.5B-Instruct")
152 148
                 .build();
153 149
 
154
-
155 150
         List<ChatMessage> messages = new ArrayList<>();
156 151
         messages.add(ChatMessage.ofUser(prompt));
157 152
         ChatSession chatSession =  InMemoryChatSession.builder().messages(messages).build();
@@ -280,6 +275,7 @@ public class McpServiceImpl implements IMcpService {
280 275
         return nextLevel <= level; // 存在更低级别的标题,不是叶子节点
281 276
 
282 277
     }
278
+
283 279
     /**
284 280
      * 获取二、三级标题列表
285 281
      */
@@ -302,42 +298,40 @@ public class McpServiceImpl implements IMcpService {
302 298
 
303 299
     /**
304 300
      * 检索知识库
301
+     * @return
305 302
      */
306
-    private SearchResultsWrapper retrieve(MilvusClient milvusClient, EmbeddingModel embeddingModel, String collectionName, String query, int topK) {
307
-        List<List<Float>> queryVector = Collections.singletonList(embeddingModel.embed(query).content().vectorAsList());
303
+    private List<List<SearchResp.SearchResult>> retrieve(String collectionName, String query, int topK) {
304
+        List<BaseVector> queryVector = Collections.singletonList(new FloatVec(embeddingModel.embed(query).content().vector()));
308 305
 
309 306
         //  加载集合
310
-        LoadCollectionParam loadParam = LoadCollectionParam.newBuilder()
311
-                .withCollectionName(collectionName)
307
+        LoadCollectionReq loadCollectionReq = LoadCollectionReq.builder()
308
+                .collectionName(collectionName)
312 309
                 .build();
313
-
314
-        R<RpcStatus> loadResponse = milvusClient.loadCollection(loadParam);
315
-        if (loadResponse.getStatus() != R.Status.Success.getCode()) {
316
-            System.err.println("加载Collection失败: " + loadResponse.getMessage());
317
-            milvusClient.close();
318
-        }
310
+        milvusClient.loadCollection(loadCollectionReq);
319 311
 
320 312
         // 构建SearchParam
321
-        SearchParam searchParam = SearchParam.newBuilder()
322
-                .withCollectionName(collectionName)
323
-                .withVectors(queryVector)
324
-                .withTopK(topK)
325
-                .withOutFields(Arrays.asList("file_name", "file_type", "content"))
326
-                .withVectorFieldName("embedding")
327
-                .withMetricType(MetricType.COSINE)
328
-                .withParams("{\"nprobe\": 8}")
313
+        Map<String, Object> searchParams = new HashMap<>();
314
+        searchParams.put("nprobe", 8);
315
+        SearchReq searchReq = SearchReq.builder()
316
+                .collectionName(collectionName)
317
+                .data(queryVector)
318
+                .topK(topK)
319
+                .outputFields(Arrays.asList("file_name", "file_type", "content"))
320
+                .annsField("embedding")
321
+                .metricType(IndexParam.MetricType.COSINE)
322
+                .searchParams(searchParams)
329 323
                 .build();
330 324
 
331
-        R<SearchResults> response = milvusClient.search(searchParam);
332
-        SearchResultsWrapper wrapper = new SearchResultsWrapper(response.getData().getResults());
325
+        SearchResp searchResp = milvusClient.search(searchReq);
326
+        List<List<SearchResp.SearchResult>> searchResultList = searchResp.getSearchResults();
333 327
 
334 328
         // 释放集合
335
-        ReleaseCollectionParam param = ReleaseCollectionParam.newBuilder()
336
-                .withCollectionName(collectionName)
329
+        ReleaseCollectionReq releaseCollectionReq = ReleaseCollectionReq.builder()
330
+                .collectionName(collectionName)
337 331
                 .build();
338
-        milvusClient.releaseCollection(param);
332
+        milvusClient.releaseCollection(releaseCollectionReq);
339 333
 
340
-        return wrapper;
334
+        return searchResultList;
341 335
     }
342 336
 
343 337
     /**

+ 2
- 2
llm-back/ruoyi-llm/pom.xml Näytä tiedosto

@@ -31,7 +31,7 @@
31 31
         <dependency>
32 32
             <groupId>io.milvus</groupId>
33 33
             <artifactId>milvus-sdk-java</artifactId>
34
-            <version>2.3.3</version>
34
+            <version>2.6.2</version>
35 35
         </dependency>
36 36
 
37 37
         <!-- LangChain4j -->
@@ -73,7 +73,7 @@
73 73
         <dependency>
74 74
             <groupId>org.noear</groupId>
75 75
             <artifactId>solon-ai-mcp</artifactId>
76
-            <version>3.4.3</version>
76
+            <version>3.5.1</version>
77 77
         </dependency>
78 78
 
79 79
     </dependencies>

+ 10
- 0
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/CmcAgentController.java Näytä tiedosto

@@ -89,6 +89,16 @@ public class CmcAgentController extends BaseController
89 89
         return success(cmcAgentService.uploadDocument(file, agentName));
90 90
     }
91 91
 
92
+    /**
93
+     * 上传修改文件
94
+     * @return
95
+     */
96
+    @PostMapping("/modifyFile")
97
+    public AjaxResult uploadModifyFile(MultipartFile file, String agentName) throws IOException
98
+    {
99
+        return success(cmcAgentService.uploadModifyFile(file, agentName));
100
+    }
101
+
92 102
     /**
93 103
      * 上传多文件
94 104
      * @return

+ 12
- 23
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/KnowLedgeController.java Näytä tiedosto

@@ -7,11 +7,6 @@ import com.ruoyi.common.core.controller.BaseController;
7 7
 import com.ruoyi.common.core.domain.AjaxResult;
8 8
 import dev.langchain4j.model.embedding.onnx.bgesmallzhv15.BgeSmallZhV15EmbeddingModel;
9 9
 import dev.langchain4j.model.embedding.EmbeddingModel;
10
-import io.milvus.client.MilvusServiceClient;
11
-import io.milvus.grpc.MutationResult;
12
-import io.milvus.param.ConnectParam;
13
-import io.milvus.param.R;
14
-import io.milvus.param.RpcStatus;
15 10
 import org.springframework.beans.factory.annotation.Autowired;
16 11
 import org.springframework.web.bind.annotation.*;
17 12
 import org.springframework.web.multipart.MultipartFile;
@@ -37,19 +32,13 @@ public class KnowLedgeController extends BaseController
37 32
 
38 33
     private static final EmbeddingModel embeddingModel = new BgeSmallZhV15EmbeddingModel();
39 34
 
40
-    private static final MilvusServiceClient milvusClient = new MilvusServiceClient(
41
-            ConnectParam.newBuilder()
42
-                    .withHost("192.168.28.188")
43
-                    .withPort(19530)
44
-                    .build());
45
-
46 35
     /**
47 36
      * 新建知识库
48 37
      */
49 38
     @GetMapping("/list")
50 39
     public AjaxResult listKnowLedgeCollection()
51 40
     {
52
-        JSONArray collectionNames = milvusService.getCollectionNames(milvusClient);
41
+        JSONArray collectionNames = milvusService.getCollectionNames();
53 42
         return success(collectionNames);
54 43
     }
55 44
 
@@ -59,8 +48,8 @@ public class KnowLedgeController extends BaseController
59 48
     @PostMapping("/create")
60 49
     public AjaxResult createKnowLedgeCollection(String collectionName, String description)
61 50
     {
62
-        R<RpcStatus> status = milvusService.createCollection(milvusClient, collectionName, description, 512);
63
-        return success(status);
51
+        milvusService.createCollection(collectionName, description, 512);
52
+        return success();
64 53
     }
65 54
 
66 55
     /**
@@ -69,7 +58,7 @@ public class KnowLedgeController extends BaseController
69 58
     @PostMapping("/modify")
70 59
     public AjaxResult modifyKnowLedgeCollection(String collectionName, String newCollectionName)
71 60
     {
72
-        milvusService.collectionRename(milvusClient, collectionName, newCollectionName);
61
+        milvusService.collectionRename(collectionName, newCollectionName);
73 62
         return success();
74 63
     }
75 64
 
@@ -79,8 +68,8 @@ public class KnowLedgeController extends BaseController
79 68
     @DeleteMapping("/remove")
80 69
     public AjaxResult removeKnowLedgeCollection(String collectionName)
81 70
     {
82
-        milvusService.removeAllDocument(milvusClient, collectionName);
83
-        milvusService.deleteCollectionName(milvusClient, collectionName);
71
+        milvusService.removeAllDocument(collectionName);
72
+        milvusService.deleteCollectionName(collectionName);
84 73
         return success();
85 74
     }
86 75
 
@@ -90,7 +79,7 @@ public class KnowLedgeController extends BaseController
90 79
     @GetMapping("/listDocument")
91 80
     public AjaxResult listKnowledgeDocument(String collectionName, String fileType)
92 81
     {
93
-        List<String> documentList = milvusService.listDocument(milvusClient, collectionName, fileType);
82
+        List<String> documentList = milvusService.listDocument(collectionName, fileType);
94 83
         return success(documentList);
95 84
     }
96 85
 
@@ -99,12 +88,12 @@ public class KnowLedgeController extends BaseController
99 88
      */
100 89
     @PostMapping("/insertDocument")
101 90
     public AjaxResult insertKnowledgeDocument(MultipartFile[] fileList, String collectionName) throws IOException {
102
-        R<MutationResult> insertResult = null;
91
+        long insertResult = 0;
103 92
         for (MultipartFile file : fileList)
104
-            insertResult = langChainMilvusService.insertLangchainEmbeddingDocument(milvusClient, file, collectionName, embeddingModel);
93
+            insertResult = langChainMilvusService.insertLangchainEmbeddingDocument(file, collectionName);
105 94
         String message = "文件导入成功";
106
-        if (insertResult != null && insertResult.getStatus() != R.Status.Success.getCode()) {
107
-            message = "文件导入失败" + insertResult.getMessage();
95
+        if (insertResult == 0) {
96
+            message = "文件导入失败";
108 97
             return error(message);
109 98
         }
110 99
         else
@@ -117,7 +106,7 @@ public class KnowLedgeController extends BaseController
117 106
     @DeleteMapping("/removeDocument")
118 107
     public AjaxResult deleteKnowledgeDocument(String fileName, String collectionName)
119 108
     {
120
-        milvusService.removeDocument(milvusClient, collectionName, fileName);
109
+        milvusService.removeDocument(collectionName, fileName);
121 110
         return success();
122 111
     }
123 112
 

+ 3
- 10
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/McpController.java Näytä tiedosto

@@ -1,7 +1,6 @@
1 1
 package com.ruoyi.web.llm.controller;
2 2
 
3 3
 import com.alibaba.fastjson2.JSONObject;
4
-import com.ruoyi.common.config.RuoYiConfig;
5 4
 import com.ruoyi.common.core.controller.BaseController;
6 5
 import com.ruoyi.llm.domain.CmcChat;
7 6
 import com.ruoyi.llm.service.ICmcAgentService;
@@ -18,13 +17,12 @@ import org.noear.solon.ai.chat.ChatSession;
18 17
 import org.noear.solon.ai.chat.message.AssistantMessage;
19 18
 import org.noear.solon.ai.chat.message.ChatMessage;
20 19
 import org.noear.solon.ai.chat.session.InMemoryChatSession;
20
+import org.noear.solon.ai.mcp.McpChannel;
21 21
 import org.noear.solon.ai.mcp.client.McpClientProvider;
22 22
 import org.springframework.beans.factory.annotation.Autowired;
23 23
 import org.springframework.web.bind.annotation.GetMapping;
24 24
 import org.springframework.web.bind.annotation.RequestMapping;
25 25
 import org.springframework.web.bind.annotation.RestController;
26
-import org.springframework.web.multipart.MultipartFile;
27
-import reactor.core.publisher.Flux;
28 26
 
29 27
 import java.io.IOException;
30 28
 import java.util.*;
@@ -69,7 +67,8 @@ public class McpController extends BaseController
69 67
     @GetMapping("/answer")
70 68
     public AssistantMessage answer(String topicId, String question) throws IOException {
71 69
         McpClientProvider clientProvider = McpClientProvider.builder()
72
-                .apiUrl("http://localhost:8080/llm/mcp/sse")
70
+                .channel(McpChannel.SSE)
71
+                .apiUrl("http://localhost:8080/mcp/sse")
73 72
                 .build();
74 73
         ChatModel chatModel = ChatModel.of(llmServiceUrl)
75 74
                 .model("Qwen2.5-1.5B-Instruct")
@@ -100,12 +99,6 @@ public class McpController extends BaseController
100 99
                 arguments.put("agentName", agentName);
101 100
                 arguments.put("title", question);
102 101
             }
103
-            if (arguments.getString("templatePath").contains("招标") || arguments.getString("templatePath").contains("询价")) {
104
-                arguments.put("collectionName", "technical");
105
-                String agentName = cmcAgentService.selectCmcAgentByAgentId(cmcTopicService.selectCmcTopicByTopicId(topicId).getAgentId()).getAgentName();
106
-                arguments.put("agentName", agentName);
107
-                arguments.put("title", question);
108
-            }
109 102
             resultContent = clientProvider.callToolAsText(name, arguments).getContent();
110 103
             assistantMessage = new AssistantMessage(resultContent);
111 104
         }

+ 2
- 14
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/RagController.java Näytä tiedosto

@@ -3,10 +3,6 @@ package com.ruoyi.web.llm.controller;
3 3
 import com.alibaba.fastjson2.JSONObject;
4 4
 import com.ruoyi.web.llm.service.ILangChainMilvusService;
5 5
 import com.ruoyi.common.core.controller.BaseController;
6
-import dev.langchain4j.model.embedding.EmbeddingModel;
7
-import dev.langchain4j.model.embedding.onnx.bgesmallzhv15.BgeSmallZhV15EmbeddingModel;
8
-import io.milvus.client.MilvusServiceClient;
9
-import io.milvus.param.ConnectParam;
10 6
 import org.noear.solon.ai.chat.message.AssistantMessage;
11 7
 import org.springframework.beans.factory.annotation.Autowired;
12 8
 import org.springframework.web.bind.annotation.GetMapping;
@@ -29,21 +25,13 @@ public class RagController extends BaseController
29 25
     @Autowired
30 26
     private ILangChainMilvusService langChainMilvusService;
31 27
 
32
-    private static final EmbeddingModel embeddingModel = new BgeSmallZhV15EmbeddingModel();
33
-
34
-    private static final MilvusServiceClient milvusClient = new MilvusServiceClient(
35
-            ConnectParam.newBuilder()
36
-                    .withHost("192.168.28.188")
37
-                    .withPort(19530)
38
-                    .build());
39
-
40 28
     /**
41 29
      * 调用LLM+RAG(知识库)生成回答
42 30
      */
43 31
     @GetMapping("/answer")
44 32
     public Flux<AssistantMessage> answerWithCollection(String collectionName, String topicId, String question)
45 33
     {
46
-        List<JSONObject> contexts = langChainMilvusService.retrieveFromMilvus(milvusClient, embeddingModel, collectionName, question, 10);
34
+        List<JSONObject> contexts = langChainMilvusService.retrieveFromMilvus(collectionName, question, 10);
47 35
         return langChainMilvusService.generateAnswerWithCollection(topicId, question, contexts, "http://192.168.28.188:8000/v1/chat/completions");
48 36
     }
49 37
 
@@ -53,7 +41,7 @@ public class RagController extends BaseController
53 41
     @GetMapping("/context")
54 42
     public List<JSONObject> context(String question, String collectionName)
55 43
     {
56
-        return langChainMilvusService.similarityFromMilvus(milvusClient, embeddingModel, collectionName, question, 10);
44
+        return langChainMilvusService.similarityFromMilvus(collectionName, question, 10);
57 45
     }
58 46
 
59 47
 }

+ 1
- 5
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/SessionController.java Näytä tiedosto

@@ -2,8 +2,6 @@ package com.ruoyi.web.llm.controller;
2 2
 
3 3
 import com.ruoyi.common.core.controller.BaseController;
4 4
 import com.ruoyi.web.llm.service.ILangChainMilvusService;
5
-import dev.langchain4j.model.embedding.EmbeddingModel;
6
-import dev.langchain4j.model.embedding.onnx.bgesmallzhv15.BgeSmallZhV15EmbeddingModel;
7 5
 import org.noear.solon.ai.chat.message.AssistantMessage;
8 6
 import org.springframework.beans.factory.annotation.Autowired;
9 7
 import org.springframework.web.bind.annotation.GetMapping;
@@ -26,8 +24,6 @@ public class SessionController extends BaseController
26 24
     @Autowired
27 25
     private ILangChainMilvusService langChainMilvusService;
28 26
 
29
-    private static final EmbeddingModel embeddingModel = new BgeSmallZhV15EmbeddingModel();
30
-
31 27
     /**
32 28
      * 生成回答
33 29
      */
@@ -42,7 +38,7 @@ public class SessionController extends BaseController
42 38
     @GetMapping("/answerWithDocument")
43 39
     public Flux<AssistantMessage> answerWithDocument(String topicId, String chatId, String question) throws IOException
44 40
     {
45
-        return langChainMilvusService.generateAnswerWithDocument(embeddingModel, topicId, chatId, question, "http://192.168.28.188:8000/v1/chat/completions");
41
+        return langChainMilvusService.generateAnswerWithDocument(topicId, chatId, question, "http://192.168.28.188:8000/v1/chat/completions");
46 42
     }
47 43
 
48 44
 }

+ 6
- 9
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/service/ILangChainMilvusService.java Näytä tiedosto

@@ -1,10 +1,6 @@
1 1
 package com.ruoyi.web.llm.service;
2 2
 
3 3
 import com.alibaba.fastjson2.JSONObject;
4
-import dev.langchain4j.model.embedding.EmbeddingModel;
5
-import io.milvus.client.MilvusClient;
6
-import io.milvus.grpc.MutationResult;
7
-import io.milvus.param.R;
8 4
 import org.noear.solon.ai.chat.message.AssistantMessage;
9 5
 import org.springframework.web.multipart.MultipartFile;
10 6
 import reactor.core.publisher.Flux;
@@ -16,20 +12,21 @@ public interface ILangChainMilvusService {
16 12
 
17 13
     /**
18 14
      * 导入知识库文件
15
+     * @return
19 16
      */
20
-    public R<MutationResult> insertLangchainEmbeddingDocument(MilvusClient milvusClient, MultipartFile file, String collectionName, EmbeddingModel embeddingModel) throws IOException;
17
+    public long insertLangchainEmbeddingDocument(MultipartFile file, String collectionName) throws IOException;
21 18
 
22 19
     /**
23 20
      * 从Milvus检索相关文档
24 21
      * @return
25 22
      */
26
-    public List<JSONObject> retrieveFromMilvus(MilvusClient milvusClient, EmbeddingModel embeddingModel, String collectionName, String query, int topK);
23
+    public List<JSONObject> retrieveFromMilvus(String collectionName, String query, int topK);
27 24
 
28 25
     /**
29 26
      * 从Milvus检索相关文档及相关度
30 27
      * @return
31 28
      */
32
-    public List<JSONObject> similarityFromMilvus(MilvusClient milvusClient, EmbeddingModel embeddingModel, String collectionName, String query, int topK);
29
+    public List<JSONObject> similarityFromMilvus(String collectionName, String query, int topK);
33 30
 
34 31
     /**
35 32
      * 调用LLM生成回答
@@ -47,13 +44,13 @@ public interface ILangChainMilvusService {
47 44
      * 调用LLM+RAG(外部文件)生成回答
48 45
      * @return
49 46
      */
50
-    public Flux<AssistantMessage> generateAnswerWithDocument(EmbeddingModel embeddingModel, String topicId, String chatId, String question, String llmServiceUrl) throws IOException;
47
+    public Flux<AssistantMessage> generateAnswerWithDocument(String topicId, String chatId, String question, String llmServiceUrl) throws IOException;
51 48
 
52 49
     /**
53 50
      * 调用LLM+RAG(外部文件+知识库)生成回答
54 51
      * @return
55 52
      */
56
-    public Flux<AssistantMessage> generateAnswerWithDocumentAndCollection(EmbeddingModel embeddingModel, String topicId, String question,  List<JSONObject> requests, String llmServiceUrl) throws IOException;
53
+    public Flux<AssistantMessage> generateAnswerWithDocumentAndCollection(String topicId, String question,  List<JSONObject> requests, String llmServiceUrl) throws IOException;
57 54
 
58 55
     /**
59 56
      * 获取二级标题下三级标题列表

+ 7
- 10
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/service/IMilvusService.java Näytä tiedosto

@@ -1,9 +1,6 @@
1 1
 package com.ruoyi.web.llm.service;
2 2
 
3 3
 import com.alibaba.fastjson2.JSONArray;
4
-import io.milvus.client.MilvusClient;
5
-import io.milvus.param.R;
6
-import io.milvus.param.RpcStatus;
7 4
 
8 5
 import java.util.List;
9 6
 
@@ -12,35 +9,35 @@ public interface IMilvusService {
12 9
     /**
13 10
      * 新建知识库Collection(含Schema、Field、Index)
14 11
      */
15
-    public R<RpcStatus> createCollection(MilvusClient milvusClient, String collectionName, String description, int dimension);
12
+    public void createCollection(String collectionName, String description, int dimension);
16 13
 
17 14
     /**
18 15
      * 查询知识库Collection
19 16
      */
20
-    public JSONArray getCollectionNames(MilvusClient milvusClient);
17
+    public JSONArray getCollectionNames();
21 18
 
22 19
     /**
23 20
      * 修改知识库Collection
24 21
      */
25
-    public void collectionRename(MilvusClient milvusClient, String collectionName, String newCollectionName);
22
+    public void collectionRename(String collectionName, String newCollectionName);
26 23
 
27 24
     /**
28 25
      * 删除知识库Collection
29 26
      */
30
-    public void deleteCollectionName(MilvusClient milvusClient, String collectionName);
27
+    public void deleteCollectionName(String collectionName);
31 28
 
32 29
     /**
33 30
      * 查询知识库文件
34 31
      */
35
-    public List<String> listDocument(MilvusClient milvusClient, String collectionName, String fileType);
32
+    public List<String> listDocument(String collectionName, String fileType);
36 33
 
37 34
     /**
38 35
      * 删除知识库文件
39 36
      */
40
-    public void removeDocument(MilvusClient milvusClient, String collectionName, String fileName);
37
+    public void removeDocument(String collectionName, String fileName);
41 38
 
42 39
     /**
43 40
      * 删除知识库所有文件
44 41
      */
45
-    public void removeAllDocument(MilvusClient milvusClient, String collectionName);
42
+    public void removeAllDocument(String collectionName);
46 43
 }

+ 86
- 82
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/service/impl/LangChainMilvusServiceImpl.java Näytä tiedosto

@@ -1,6 +1,8 @@
1 1
 package com.ruoyi.web.llm.service.impl;
2 2
 
3 3
 import com.alibaba.fastjson2.JSONObject;
4
+import com.google.gson.JsonObject;
5
+import com.google.gson.JsonParser;
4 6
 import com.ruoyi.common.config.RuoYiConfig;
5 7
 import com.ruoyi.llm.domain.CmcChat;
6 8
 import com.ruoyi.llm.domain.CmcDocument;
@@ -14,20 +16,21 @@ import dev.langchain4j.data.document.splitter.DocumentByParagraphSplitter;
14 16
 import dev.langchain4j.data.embedding.Embedding;
15 17
 import dev.langchain4j.data.segment.TextSegment;
16 18
 import dev.langchain4j.model.embedding.EmbeddingModel;
19
+import dev.langchain4j.model.embedding.onnx.bgesmallzhv15.BgeSmallZhV15EmbeddingModel;
17 20
 import dev.langchain4j.store.embedding.EmbeddingMatch;
18 21
 import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
19 22
 import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
20
-import io.milvus.client.MilvusClient;
21
-import io.milvus.grpc.MutationResult;
22
-import io.milvus.grpc.SearchResults;
23
-import io.milvus.param.MetricType;
24
-import io.milvus.param.R;
25
-import io.milvus.param.RpcStatus;
26
-import io.milvus.param.collection.LoadCollectionParam;
27
-import io.milvus.param.collection.ReleaseCollectionParam;
28
-import io.milvus.param.dml.InsertParam;
29
-import io.milvus.param.dml.SearchParam;
30
-import io.milvus.response.SearchResultsWrapper;
23
+import io.milvus.v2.client.ConnectConfig;
24
+import io.milvus.v2.client.MilvusClientV2;
25
+import io.milvus.v2.common.IndexParam;
26
+import io.milvus.v2.service.collection.request.LoadCollectionReq;
27
+import io.milvus.v2.service.collection.request.ReleaseCollectionReq;
28
+import io.milvus.v2.service.vector.request.InsertReq;
29
+import io.milvus.v2.service.vector.request.SearchReq;
30
+import io.milvus.v2.service.vector.request.data.BaseVector;
31
+import io.milvus.v2.service.vector.request.data.FloatVec;
32
+import io.milvus.v2.service.vector.response.InsertResp;
33
+import io.milvus.v2.service.vector.response.SearchResp;
31 34
 import org.apache.poi.extractor.POITextExtractor;
32 35
 import org.apache.poi.extractor.ExtractorFactory;
33 36
 import org.apache.poi.xwpf.usermodel.XWPFDocument;
@@ -46,7 +49,6 @@ import reactor.core.publisher.Flux;
46 49
 
47 50
 import java.io.*;
48 51
 import java.util.*;
49
-import java.util.stream.Collectors;
50 52
 
51 53
 @Service
52 54
 public class LangChainMilvusServiceImpl implements ILangChainMilvusService
@@ -57,11 +59,19 @@ public class LangChainMilvusServiceImpl implements ILangChainMilvusService
57 59
     @Autowired
58 60
     private ICmcDocumentService cmcDocumentService;
59 61
 
62
+    private static final MilvusClientV2 milvusClient = new MilvusClientV2(
63
+            ConnectConfig.builder()
64
+                    .uri("http://192.168.28.188:19530")
65
+                    .build());
66
+
67
+    private static final EmbeddingModel embeddingModel = new BgeSmallZhV15EmbeddingModel();
68
+
60 69
     /**
61 70
      * 导入知识库文件
71
+     * @return
62 72
      */
63 73
     @Override
64
-    public R<MutationResult> insertLangchainEmbeddingDocument(MilvusClient milvusClient, MultipartFile file, String collectionName, EmbeddingModel embeddingModel) throws IOException
74
+    public long insertLangchainEmbeddingDocument(MultipartFile file, String collectionName) throws IOException
65 75
     {
66 76
         File profilePath = new File( RuoYiConfig.getProfile() + "/upload/rag/knowledge/" + collectionName);
67 77
         if (!profilePath.exists())
@@ -71,37 +81,34 @@ public class LangChainMilvusServiceImpl implements ILangChainMilvusService
71 81
             file.transferTo(transferFile);
72 82
         List<TextSegment> segments = splitDocument(transferFile);
73 83
 
74
-        // 提取文本和生成嵌入
75
-        List<String> fileNames = new ArrayList<>();
76
-        List<String> fileTypes = new ArrayList<>();
77
-        List<String> texts = new ArrayList<>();
78
-        List<List<Float>> embeddings = new ArrayList<>();
84
+        // 准备导入数据
85
+        List<JsonObject> data = new ArrayList<>();
79 86
 
87
+        // 提取文本和生成嵌入
80 88
         for (TextSegment segment : segments) {
81 89
             String text = segment.text();
82 90
             if (text.trim().isEmpty())
83 91
                 continue;
84
-            fileNames.add(file.getOriginalFilename());
92
+
93
+            JSONObject fastjsonObj = new JSONObject();
94
+            fastjsonObj.put("file_name", file.getOriginalFilename());
85 95
             String[] fileName = file.getOriginalFilename().split("\\.");
86
-            fileTypes.add(fileName[fileName.length - 1]);
87
-            texts.add(text);
88
-            embeddings.add(embeddingModel.embed(text).content().vectorAsList());
96
+            fastjsonObj.put("file_type", fileName[fileName.length - 1]);
97
+            fastjsonObj.put("content", text);
98
+            fastjsonObj.put("embedding", embeddingModel.embed(text).content().vectorAsList());
99
+            String jsonString = fastjsonObj.toJSONString();
100
+            JsonObject jsonObject = JsonParser.parseString(jsonString).getAsJsonObject();
101
+            data.add(jsonObject);
89 102
         }
90 103
 
91
-        // 准备导入数据
92
-        List<InsertParam.Field> fields = new ArrayList<>();
93
-        fields.add(new InsertParam.Field("file_name", fileNames));
94
-        fields.add(new InsertParam.Field("file_type", fileTypes));
95
-        fields.add(new InsertParam.Field("content", texts));
96
-        fields.add(new InsertParam.Field("embedding", embeddings));
97
-
98
-        InsertParam insertParam = InsertParam.newBuilder()
99
-                .withCollectionName(collectionName)
100
-                .withFields(fields)
104
+        InsertReq insertReq = InsertReq.builder()
105
+                .collectionName(collectionName)
106
+                .data(data)
101 107
                 .build();
102 108
 
103 109
         // 执行导入
104
-        return milvusClient.insert(insertParam);
110
+        InsertResp insertResp = milvusClient.insert(insertReq);
111
+        return insertResp.getInsertCnt();
105 112
     }
106 113
 
107 114
     /**
@@ -109,16 +116,16 @@ public class LangChainMilvusServiceImpl implements ILangChainMilvusService
109 116
      * @return
110 117
      */
111 118
     @Override
112
-    public List<JSONObject> retrieveFromMilvus(MilvusClient milvusClient, EmbeddingModel embeddingModel, String collectionName, String query, int topK) {
113
-        SearchResultsWrapper wrapper = retrieve(milvusClient, embeddingModel, collectionName, query, topK);
114
-        return wrapper.getRowRecords(0).stream()
115
-                .map(record -> {
116
-                    JSONObject result = new JSONObject();
117
-                    result.put("file_name", record.get("file_name"));
118
-                    result.put("content", record.get("content"));
119
-                    return result;
120
-                })
121
-                .collect(Collectors.toList());
119
+    public List<JSONObject> retrieveFromMilvus(String collectionName, String query, int topK) {
120
+        List<JSONObject> resultList = new ArrayList<>();
121
+        List<List<SearchResp.SearchResult>> searchResultList = retrieve(collectionName, query, topK);
122
+        searchResultList.forEach(searchResult -> {
123
+            JSONObject result = new JSONObject();
124
+            result.put("file_name", searchResult.get(0).getEntity().get("file_name"));
125
+            result.put("content", searchResult.get(0).getEntity().get("content"));
126
+            resultList.add(result);
127
+        });
128
+        return resultList;
122 129
     }
123 130
 
124 131
     /**
@@ -126,19 +133,18 @@ public class LangChainMilvusServiceImpl implements ILangChainMilvusService
126 133
      * @return
127 134
      */
128 135
     @Override
129
-    public List<JSONObject> similarityFromMilvus(MilvusClient milvusClient, EmbeddingModel embeddingModel, String collectionName, String query, int topK) {
130
-        SearchResultsWrapper wrapper = retrieve(milvusClient, embeddingModel, collectionName, query, topK);
131
-        List<JSONObject> wrapperList =  wrapper.getRowRecords(0).stream()
132
-                .map(record -> {
133
-                    JSONObject result = new JSONObject();
134
-                    result.put("distance", record.get("distance"));
135
-                    result.put("file_name", record.get("file_name"));
136
-                    result.put("content", record.get("content"));
137
-                    return result;
138
-                })
139
-                .collect(Collectors.toList());
140
-        wrapperList.removeIf(jsonObject -> jsonObject.getDouble("distance") < 0.7);
141
-        return wrapperList;
136
+    public List<JSONObject> similarityFromMilvus(String collectionName, String query, int topK) {
137
+        List<JSONObject> resultList = new ArrayList<>();
138
+        List<List<SearchResp.SearchResult>> searchResultList = retrieve(collectionName, query, topK);
139
+        searchResultList.forEach(searchResult -> {
140
+            JSONObject result = new JSONObject();
141
+            result.put("score", searchResult.get(0).getScore());
142
+            result.put("file_name", searchResult.get(0).getEntity().get("file_name"));
143
+            result.put("content", searchResult.get(0).getEntity().get("content"));
144
+            resultList.add(result);
145
+        });
146
+        resultList.removeIf(jsonObject -> jsonObject.getDouble("score") < 0.7);
147
+        return resultList;
142 148
     }
143 149
 
144 150
     /**
@@ -193,7 +199,7 @@ public class LangChainMilvusServiceImpl implements ILangChainMilvusService
193 199
      * 调用LLM生成回答
194 200
      */
195 201
     @Override
196
-    public Flux<AssistantMessage> generateAnswerWithDocument(EmbeddingModel embeddingModel, String topicId, String chatId, String question, String llmServiceUrl) throws IOException {
202
+    public Flux<AssistantMessage> generateAnswerWithDocument(String topicId, String chatId, String question, String llmServiceUrl) throws IOException {
197 203
         CmcDocument cmcDocument = new CmcDocument();
198 204
         cmcDocument.setChatId(chatId);
199 205
         List<CmcDocument> documentList = cmcDocumentService.selectCmcDocumentList(cmcDocument);
@@ -226,7 +232,7 @@ public class LangChainMilvusServiceImpl implements ILangChainMilvusService
226 232
      * 调用LLM生成回答
227 233
      */
228 234
     @Override
229
-    public Flux<AssistantMessage> generateAnswerWithDocumentAndCollection(EmbeddingModel embeddingModel, String topicId, String question, List<JSONObject> contexts, String llmServiceUrl) throws IOException {
235
+    public Flux<AssistantMessage> generateAnswerWithDocumentAndCollection(String topicId, String question, List<JSONObject> contexts, String llmServiceUrl) throws IOException {
230 236
         StringBuilder sb = new StringBuilder("招标文件内容:\n\n");
231 237
         CmcChat cmcChat = new CmcChat();
232 238
         cmcChat.setTopicId(topicId);
@@ -306,42 +312,40 @@ public class LangChainMilvusServiceImpl implements ILangChainMilvusService
306 312
 
307 313
     /**
308 314
      * 检索知识库
315
+     * @return
309 316
      */
310
-    private SearchResultsWrapper retrieve(MilvusClient milvusClient, EmbeddingModel embeddingModel, String collectionName, String query, int topK) {
311
-        List<List<Float>> queryVector = Collections.singletonList(embeddingModel.embed(query).content().vectorAsList());
317
+    private List<List<SearchResp.SearchResult>> retrieve(String collectionName, String query, int topK) {
318
+        List<BaseVector> queryVector = Collections.singletonList(new FloatVec(embeddingModel.embed(query).content().vector()));
312 319
 
313 320
         //  加载集合
314
-        LoadCollectionParam loadParam = LoadCollectionParam.newBuilder()
315
-                .withCollectionName(collectionName)
321
+        LoadCollectionReq loadCollectionReq = LoadCollectionReq.builder()
322
+                .collectionName(collectionName)
316 323
                 .build();
317
-
318
-        R<RpcStatus> loadResponse = milvusClient.loadCollection(loadParam);
319
-        if (loadResponse.getStatus() != R.Status.Success.getCode()) {
320
-            System.err.println("加载Collection失败: " + loadResponse.getMessage());
321
-            milvusClient.close();
322
-        }
324
+        milvusClient.loadCollection(loadCollectionReq);
323 325
 
324 326
         // 构建SearchParam
325
-        SearchParam searchParam = SearchParam.newBuilder()
326
-                .withCollectionName(collectionName)
327
-                .withVectors(queryVector)
328
-                .withTopK(topK)
329
-                .withOutFields(Arrays.asList("file_name", "file_type", "content"))
330
-                .withVectorFieldName("embedding")
331
-                .withMetricType(MetricType.COSINE)
332
-                .withParams("{\"nprobe\": 8}")
327
+        Map<String, Object> searchParams = new HashMap<>();
328
+        searchParams.put("nprobe", 8);
329
+        SearchReq searchReq = SearchReq.builder()
330
+                .collectionName(collectionName)
331
+                .data(queryVector)
332
+                .topK(topK)
333
+                .outputFields(Arrays.asList("file_name", "file_type", "content"))
334
+                .annsField("embedding")
335
+                .metricType(IndexParam.MetricType.COSINE)
336
+                .searchParams(searchParams)
333 337
                 .build();
334 338
 
335
-        R<SearchResults> response = milvusClient.search(searchParam);
336
-        SearchResultsWrapper wrapper = new SearchResultsWrapper(response.getData().getResults());
339
+        SearchResp searchResp = milvusClient.search(searchReq);
340
+        List<List<SearchResp.SearchResult>> searchResultList = searchResp.getSearchResults();
337 341
 
338 342
         // 释放集合
339
-        ReleaseCollectionParam param = ReleaseCollectionParam.newBuilder()
340
-                .withCollectionName(collectionName)
343
+        ReleaseCollectionReq releaseCollectionReq = ReleaseCollectionReq.builder()
344
+                .collectionName(collectionName)
341 345
                 .build();
342
-        milvusClient.releaseCollection(param);
346
+        milvusClient.releaseCollection(releaseCollectionReq);
343 347
 
344
-        return wrapper;
348
+        return searchResultList;
345 349
     }
346 350
 
347 351
     /**

+ 112
- 115
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/service/impl/MilvusServiceImpl.java Näytä tiedosto

@@ -3,112 +3,109 @@ package com.ruoyi.web.llm.service.impl;
3 3
 import com.alibaba.fastjson2.JSONArray;
4 4
 import com.alibaba.fastjson2.JSONObject;
5 5
 import com.ruoyi.web.llm.service.IMilvusService;
6
-import io.milvus.client.MilvusClient;
7
-import io.milvus.grpc.*;
8
-import io.milvus.param.*;
9
-import io.milvus.param.collection.*;
10 6
 import io.milvus.param.dml.DeleteParam;
11
-import io.milvus.param.highlevel.collection.ListCollectionsParam;
12
-import io.milvus.param.highlevel.collection.response.ListCollectionsResponse;
13
-import io.milvus.param.highlevel.dml.QuerySimpleParam;
14
-import io.milvus.param.highlevel.dml.response.QueryResponse;
15
-import io.milvus.param.index.CreateIndexParam;
16
-import io.milvus.response.QueryResultsWrapper;
7
+import io.milvus.v2.client.ConnectConfig;
8
+import io.milvus.v2.client.MilvusClientV2;
9
+import io.milvus.v2.common.DataType;
10
+import io.milvus.v2.common.IndexParam;
11
+import io.milvus.v2.service.collection.request.*;
12
+import io.milvus.v2.service.collection.response.DescribeCollectionResp;
13
+import io.milvus.v2.service.collection.response.ListCollectionsResp;
14
+import io.milvus.v2.service.vector.request.QueryReq;
15
+import io.milvus.v2.service.vector.response.QueryResp;
17 16
 import org.springframework.stereotype.Service;
18 17
 
19 18
 import java.text.SimpleDateFormat;
20
-import java.util.ArrayList;
21
-import java.util.Arrays;
22
-import java.util.List;
23
-import java.util.TimeZone;
19
+import java.util.*;
24 20
 import java.util.stream.Collectors;
25 21
 
26 22
 @Service
27 23
 public class MilvusServiceImpl implements IMilvusService {
28 24
 
25
+    private static final MilvusClientV2 milvusClient = new MilvusClientV2(
26
+            ConnectConfig.builder()
27
+                    .uri("http://192.168.28.188:19530")
28
+                    .build());
29
+
29 30
     /**
30 31
      * 新建知识库Collection(含Schema、Field、Index)
31 32
      */
32 33
     @Override
33
-    public R<RpcStatus> createCollection(MilvusClient milvusClient, String collectionName, String description, int dimension) {
34
-        FieldType idField = FieldType.newBuilder()
35
-                .withName("id")
36
-                .withDataType(DataType.Int64)
37
-                .withPrimaryKey(true)
38
-                .withAutoID(true)
39
-                .build();
40
-
41
-        FieldType fileNameField = FieldType.newBuilder()
42
-                .withName("file_name")
43
-                .withDataType(DataType.VarChar)
44
-                .withMaxLength(256)
45
-                .build();
46
-
47
-        FieldType fieldTypeField = FieldType.newBuilder()
48
-                .withName("file_type")
49
-                .withDataType(DataType.VarChar)
50
-                .withMaxLength(256)
51
-                .build();
52
-
53
-        FieldType contentField = FieldType.newBuilder()
54
-                .withName("content")
55
-                .withDataType(DataType.VarChar)
56
-                .withMaxLength(65535)
57
-                .build();
58
-
59
-        FieldType vectorField = FieldType.newBuilder()
60
-                .withName("embedding")
61
-                .withDataType(DataType.FloatVector)
62
-                .withDimension(dimension)
63
-                .build();
34
+    public void createCollection(String collectionName, String description, int dimension) {
35
+        CreateCollectionReq.CollectionSchema schema = MilvusClientV2.CreateSchema();
36
+
37
+        schema.addField(AddFieldReq.builder()
38
+                .fieldName("id")
39
+                .dataType(DataType.Int64)
40
+                .isPrimaryKey(true)
41
+                .autoID(true)
42
+                .build());
43
+
44
+        schema.addField(AddFieldReq.builder()
45
+                .fieldName("file_name")
46
+                .dataType(DataType.VarChar)
47
+                .maxLength(256)
48
+                .build());
49
+
50
+        schema.addField(AddFieldReq.builder()
51
+                .fieldName("file_type")
52
+                .dataType(DataType.VarChar)
53
+                .maxLength(10)
54
+                .build());
55
+
56
+        schema.addField(AddFieldReq.builder()
57
+                .fieldName("content")
58
+                .dataType(DataType.VarChar)
59
+                .maxLength(65535)
60
+                .build());
61
+
62
+        schema.addField(AddFieldReq.builder()
63
+                .fieldName("embedding")
64
+                .dataType(DataType.FloatVector)
65
+                .dimension(5)
66
+                .build());
64 67
 
65
-        CreateCollectionParam createCollectionParam = CreateCollectionParam.newBuilder()
66
-                .withCollectionName(collectionName)
67
-                .withDescription(description)
68
-                .addFieldType(idField)
69
-                .addFieldType(fileNameField)
70
-                .addFieldType(fieldTypeField)
71
-                .addFieldType(contentField)
72
-                .addFieldType(vectorField)
68
+        // 创建索引
69
+        Map<String, Object> extraParams = new HashMap<>();
70
+        extraParams.put("nlist", 64);
71
+        IndexParam indexParam = IndexParam.builder()
72
+                .fieldName("embedding")
73
+                .indexType(IndexParam.IndexType.IVF_FLAT)
74
+                .metricType(IndexParam.MetricType.COSINE)
75
+                .extraParams(extraParams)
73 76
                 .build();
74 77
 
75
-        R<RpcStatus> createResult = milvusClient.createCollection(createCollectionParam);
76
-
77
-        // 创建索引
78
-        CreateIndexParam createIndexParam = CreateIndexParam.newBuilder()
79
-                .withCollectionName(collectionName)
80
-                .withFieldName("embedding")
81
-                .withIndexType(IndexType.IVF_FLAT)
82
-                .withMetricType(MetricType.COSINE)
83
-                .withExtraParam("{\"nlist\": 64}")
78
+        CreateCollectionReq createCollectionParam = CreateCollectionReq.builder()
79
+                .collectionName(collectionName)
80
+                .description(description)
81
+                .collectionSchema(schema)
82
+                .indexParam(indexParam)
84 83
                 .build();
85 84
 
86
-        milvusClient.createIndex(createIndexParam);
87
-        return createResult;
85
+        milvusClient.createCollection(createCollectionParam);
88 86
     }
89 87
 
90 88
     /**
91 89
      * 查询知识库Collection
92 90
      */
93
-    public JSONArray getCollectionNames(MilvusClient milvusClient) {
91
+    public JSONArray getCollectionNames() {
94 92
         JSONArray jsonArray = new JSONArray();
95
-        ListCollectionsParam listParam = ListCollectionsParam.newBuilder().build();
96
-        ListCollectionsResponse listResponse = milvusClient.listCollections(listParam).getData();
93
+        ListCollectionsResp listResponse = milvusClient.listCollections();
97 94
         if (listResponse != null) {
98
-            List<String> collectionNames = milvusClient.listCollections(listParam).getData().collectionNames;
95
+            List<String> collectionNames = listResponse.getCollectionNames();
99 96
             for (String collectionName : collectionNames) {
100 97
                 JSONObject jsonObject = new JSONObject();
101
-                DescribeCollectionParam describeParam = DescribeCollectionParam.newBuilder()
102
-                                                        .withCollectionName(collectionName)
103
-                                                        .build();
104
-                DescribeCollectionResponse describeResponse = milvusClient.describeCollection(describeParam).getData();
98
+                DescribeCollectionReq request = DescribeCollectionReq.builder()
99
+                        .collectionName(collectionName)
100
+                        .build();
101
+                DescribeCollectionResp describeResponse = milvusClient.describeCollection(request);
105 102
                 jsonObject.put("collectionId", describeResponse.getCollectionID());
106 103
                 jsonObject.put("collectionName", collectionName);
107 104
                 SimpleDateFormat beijingFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
108 105
                 beijingFormat.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
109
-                String beijingTime = beijingFormat.format(describeResponse.getCreatedUtcTimestamp());
106
+                String beijingTime = beijingFormat.format(describeResponse.getCreateUtcTime());
110 107
                 jsonObject.put("createdTime", beijingTime);
111
-                jsonObject.put("description", describeResponse.getSchema().getDescription());
108
+                jsonObject.put("description", describeResponse.getDescription());
112 109
                 jsonArray.add(jsonObject);
113 110
             }
114 111
         }
@@ -118,10 +115,10 @@ public class MilvusServiceImpl implements IMilvusService {
118 115
     /**
119 116
      * 修改知识库Collection
120 117
      */
121
-    public void collectionRename(MilvusClient milvusClient, String collectionName, String newCollectionName) {
122
-        RenameCollectionParam renameCollectionReq = RenameCollectionParam.newBuilder()
123
-                .withOldCollectionName(collectionName)
124
-                .withNewCollectionName(newCollectionName)
118
+    public void collectionRename(String collectionName, String newCollectionName) {
119
+        RenameCollectionReq renameCollectionReq = RenameCollectionReq.builder()
120
+                .collectionName(collectionName)
121
+                .newCollectionName(newCollectionName)
125 122
                 .build();
126 123
 
127 124
         milvusClient.renameCollection(renameCollectionReq);
@@ -130,86 +127,86 @@ public class MilvusServiceImpl implements IMilvusService {
130 127
     /**
131 128
      * 删除知识库Collection
132 129
      */
133
-    public void deleteCollectionName(MilvusClient milvusClient, String collectionName) {
134
-        DropCollectionParam param = DropCollectionParam.newBuilder()
135
-                .withCollectionName(collectionName)
130
+    public void deleteCollectionName(String collectionName) {
131
+        DropCollectionReq dropCollectionReq = DropCollectionReq.builder()
132
+                .collectionName(collectionName)
136 133
                 .build();
137
-        milvusClient.dropCollection(param);
134
+        milvusClient.dropCollection(dropCollectionReq);
138 135
     }
139 136
 
140 137
     /**
141 138
      * 查询知识库文件
142 139
      */
143
-    public List<String> listDocument(MilvusClient milvusClient, String collectionName, String fileType) {
140
+    public List<String> listDocument(String collectionName, String fileType) {
144 141
         List<String> documentList = new ArrayList<>();
145
-        loadCollectionName(milvusClient,collectionName);
146
-        QuerySimpleParam queryParam = QuerySimpleParam.newBuilder()        
147
-                .withCollectionName(collectionName)
148
-                .withFilter("id > 0")
149
-                .withOutputFields(Arrays.asList("file_type", "file_name"))
150
-                .withLimit(16384L)
142
+        loadCollectionName(collectionName);
143
+        QueryReq queryParam = QueryReq.builder()
144
+                .collectionName(collectionName)
145
+                .filter("id > 0")
146
+                .outputFields(Arrays.asList("file_type", "file_name"))
147
+                .limit(16384L)
151 148
                 .build();
152 149
         if (fileType != null && !fileType.equals(""))
153
-            queryParam = QuerySimpleParam.newBuilder()
154
-                .withCollectionName(collectionName)
155
-                .withFilter(String.format("file_type == \"%s\"", fileType))
156
-                .withOutputFields(Arrays.asList("file_type", "file_name"))
157
-                .withLimit(16384L)
150
+            queryParam = QueryReq.builder()
151
+                .collectionName(collectionName)
152
+                .filter(String.format("file_type == \"%s\"", fileType))
153
+                .outputFields(Arrays.asList("file_type", "file_name"))
154
+                .limit(16384L)
158 155
                 .build();
159
-        QueryResponse queryResults = milvusClient.query(queryParam).getData();
160
-        List<QueryResultsWrapper.RowRecord> rowRecordList = new ArrayList<>();
161
-        if (queryResults != null) {
162
-            rowRecordList = queryResults.getRowRecords();
163
-            for (QueryResultsWrapper.RowRecord rowRecord : rowRecordList) {
164
-                documentList.add(rowRecord.get("file_name").toString());
156
+        QueryResp queryResp = milvusClient.query(queryParam);
157
+        List<QueryResp.QueryResult> rowRecordList = new ArrayList<>();
158
+        if (queryResp != null) {
159
+            rowRecordList = queryResp.getQueryResults();
160
+            for (QueryResp.QueryResult rowRecord : rowRecordList) {
161
+                documentList.add(rowRecord.getEntity().get("file_name").toString());
165 162
             }
166 163
         }
167
-        releaseCollectionName(milvusClient, collectionName);
164
+        releaseCollectionName(collectionName);
168 165
         return documentList.stream().distinct().collect(Collectors.toList());
169 166
     }
170 167
 
171 168
     /**
172 169
      * 删除知识库文件
173 170
      */
174
-    public void removeDocument(MilvusClient milvusClient, String collectionName, String fileName) {
175
-        loadCollectionName(milvusClient, collectionName);
171
+    public void removeDocument(String collectionName, String fileName) {
172
+        loadCollectionName(collectionName);
176 173
         DeleteParam deleteParam = DeleteParam.newBuilder()
177 174
                 .withCollectionName(collectionName)
178 175
                 .withExpr(String.format("file_name == \"%s\"", fileName))
179 176
                 .build();
180
-        milvusClient.delete(deleteParam);
177
+//        milvusClient.delete(deleteParam);
181 178
     }
182 179
 
183 180
     /**
184 181
      * 删除知识库所有文件
185 182
      */
186
-    public void removeAllDocument(MilvusClient milvusClient, String collectionName) {
187
-        loadCollectionName(milvusClient, collectionName);
183
+    public void removeAllDocument(String collectionName) {
184
+        loadCollectionName(collectionName);
188 185
         DeleteParam deleteParam = DeleteParam.newBuilder()
189 186
                 .withCollectionName(collectionName)
190 187
                 .withExpr("id > 0")
191 188
                 .build();
192
-        milvusClient.delete(deleteParam);
189
+//        milvusClient.delete(deleteParam);
193 190
     }
194 191
 
195 192
     /**
196 193
      * 加载知识库Collection
197 194
      */
198
-    public void loadCollectionName(MilvusClient milvusClient, String collectionName) {
199
-        LoadCollectionParam param = LoadCollectionParam.newBuilder()
200
-                .withCollectionName(collectionName)
195
+    public void loadCollectionName(String collectionName) {
196
+        LoadCollectionReq loadCollectionReq = LoadCollectionReq.builder()
197
+                .collectionName(collectionName)
201 198
                 .build();
202
-        milvusClient.loadCollection(param);
199
+        milvusClient.loadCollection(loadCollectionReq);
203 200
     }
204 201
 
205 202
     /**
206 203
      * 释放知识库Collection
207 204
      */
208
-    public void releaseCollectionName(MilvusClient milvusClient, String collectionName) {
209
-        ReleaseCollectionParam param = ReleaseCollectionParam.newBuilder()
210
-                .withCollectionName(collectionName)
205
+    public void releaseCollectionName(String collectionName) {
206
+        ReleaseCollectionReq releaseCollectionReq = ReleaseCollectionReq.builder()
207
+                .collectionName(collectionName)
211 208
                 .build();
212
-        milvusClient.releaseCollection(param);
209
+        milvusClient.releaseCollection(releaseCollectionReq);
213 210
     }
214 211
 
215 212
 }

+ 2
- 2
llm-back/ruoyi-system/pom.xml Näytä tiedosto

@@ -50,13 +50,13 @@
50 50
         <dependency>
51 51
             <groupId>org.noear</groupId>
52 52
             <artifactId>solon-ai</artifactId>
53
-            <version>3.4.3</version>
53
+            <version>3.5.1</version>
54 54
         </dependency>
55 55
 
56 56
         <dependency>
57 57
             <groupId>io.milvus</groupId>
58 58
             <artifactId>milvus-sdk-java</artifactId>
59
-            <version>2.3.3</version>
59
+            <version>2.6.2</version>
60 60
         </dependency>
61 61
 
62 62
     </dependencies>

+ 8
- 0
llm-back/ruoyi-system/src/main/java/com/ruoyi/llm/service/ICmcAgentService.java Näytä tiedosto

@@ -47,6 +47,14 @@ public interface ICmcAgentService
47 47
      */
48 48
     public JSONObject uploadDocument(MultipartFile file, String agentName) throws IOException;
49 49
 
50
+    /**
51
+     * 上传修改文件
52
+     *
53
+     * @param file 文件
54
+     * @return 结果
55
+     */
56
+    public JSONObject uploadModifyFile(MultipartFile file, String agentName) throws IOException;
57
+
50 58
     /**
51 59
      * 上传多文件
52 60
      *

+ 451
- 63
llm-back/ruoyi-system/src/main/java/com/ruoyi/llm/service/impl/CmcAgentServiceImpl.java Näytä tiedosto

@@ -5,7 +5,6 @@ import java.nio.file.Files;
5 5
 import java.nio.file.Path;
6 6
 import java.nio.file.Paths;
7 7
 import java.util.*;
8
-import java.util.stream.Collectors;
9 8
 
10 9
 import com.alibaba.fastjson2.JSONObject;
11 10
 import com.ruoyi.common.config.RuoYiConfig;
@@ -27,22 +26,23 @@ import dev.langchain4j.model.embedding.onnx.bgesmallzhv15.BgeSmallZhV15Embedding
27 26
 import dev.langchain4j.store.embedding.EmbeddingMatch;
28 27
 import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
29 28
 import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
30
-import io.milvus.client.MilvusServiceClient;
31
-import io.milvus.grpc.SearchResults;
32
-import io.milvus.param.ConnectParam;
33
-import io.milvus.param.MetricType;
34
-import io.milvus.param.R;
35
-import io.milvus.param.RpcStatus;
36
-import io.milvus.param.collection.LoadCollectionParam;
37
-import io.milvus.param.collection.ReleaseCollectionParam;
38
-import io.milvus.param.dml.SearchParam;
39
-import io.milvus.response.SearchResultsWrapper;
29
+import io.milvus.v2.client.ConnectConfig;
30
+import io.milvus.v2.client.MilvusClientV2;
31
+import io.milvus.v2.common.IndexParam;
32
+import io.milvus.v2.service.collection.request.LoadCollectionReq;
33
+import io.milvus.v2.service.collection.request.ReleaseCollectionReq;
34
+import io.milvus.v2.service.vector.request.SearchReq;
35
+import io.milvus.v2.service.vector.request.data.BaseVector;
36
+import io.milvus.v2.service.vector.request.data.FloatVec;
37
+import io.milvus.v2.service.vector.response.SearchResp;
40 38
 import org.apache.poi.extractor.ExtractorFactory;
41 39
 import org.apache.poi.extractor.POITextExtractor;
42 40
 import org.apache.poi.xwpf.usermodel.BreakType;
43 41
 import org.apache.poi.xwpf.usermodel.XWPFDocument;
44 42
 import org.apache.poi.xwpf.usermodel.XWPFParagraph;
45 43
 import org.apache.poi.xwpf.usermodel.XWPFRun;
44
+import org.apache.xmlbeans.XmlCursor;
45
+import org.noear.solon.Solon;
46 46
 import org.noear.solon.ai.chat.ChatModel;
47 47
 import org.noear.solon.ai.chat.ChatResponse;
48 48
 import org.noear.solon.ai.chat.ChatSession;
@@ -57,12 +57,12 @@ import org.springframework.web.multipart.MultipartFile;
57 57
 
58 58
 /**
59 59
  * 智能体Service业务层处理
60
- * 
60
+ *
61 61
  * @author ruoyi
62 62
  * @date 2025-07-17
63 63
  */
64 64
 @Service
65
-public class CmcAgentServiceImpl implements ICmcAgentService 
65
+public class CmcAgentServiceImpl implements ICmcAgentService
66 66
 {
67 67
     @Autowired
68 68
     private CmcAgentMapper cmcAgentMapper;
@@ -79,15 +79,14 @@ public class CmcAgentServiceImpl implements ICmcAgentService
79 79
 
80 80
     private static final String llmServiceUrl = "http://192.168.28.188:8000/v1/chat/completions";
81 81
 
82
-    private static final MilvusServiceClient milvusClient = new MilvusServiceClient(
83
-            ConnectParam.newBuilder()
84
-                    .withHost("192.168.28.188")
85
-                    .withPort(19530)
82
+    private static final MilvusClientV2 milvusClient = new MilvusClientV2(
83
+            ConnectConfig.builder()
84
+                    .uri("http://192.168.28.188:19530")
86 85
                     .build());
87 86
 
88 87
     /**
89 88
      * 查询智能体
90
-     * 
89
+     *
91 90
      * @param agentId 智能体主键
92 91
      * @return 智能体
93 92
      */
@@ -99,7 +98,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService
99 98
 
100 99
     /**
101 100
      * 查询智能体列表
102
-     * 
101
+     *
103 102
      * @param cmcAgent 智能体
104 103
      * @return 智能体
105 104
      */
@@ -173,9 +172,58 @@ public class CmcAgentServiceImpl implements ICmcAgentService
173 172
                     RuoYiConfig.getProfile() + outputFilename, "工作大纲");
174 173
             message = "好的,我已经收到您上传的招标文件。\n\n"+ chapters + "\n\n" +
175 174
                     "若您对章节标题有异议,请打开" + "【<a href='/profile" + outputFilename + "'> 技术文件 " + "</a>】" + "进行修改,后续将根据修改后的章节标题,帮您生成对应章节内容。\n\n" +
176
-                    "请问您需要哪个章节撰写的帮助?\n" +
177
-                    "如需单个章节,您可以复制章节标题名称至对话输入框,发送给我;如需全部章节,请输入 技术文件 。\n\n" +
178
-                    "思考时间可能较长,请耐心等待,若提示未检测工具,麻烦重新发送,谢谢!\n";
175
+                    "思考时间可能较长,请耐心等待!\n";
176
+        }
177
+        jsonObject.put("assistantMessage", message);
178
+        return jsonObject;
179
+    }
180
+
181
+    /**
182
+     * 上传修改文件
183
+     *
184
+     * @param file 文件
185
+     * @return 结果
186
+     */
187
+    @Override
188
+    public JSONObject uploadModifyFile(MultipartFile file, String agentName) throws IOException {
189
+        String prefixPath = "/upload/agent/" + agentName;
190
+        File profilePath = new File( RuoYiConfig.getProfile() + prefixPath);
191
+        if (!profilePath.exists())
192
+            profilePath.mkdirs();
193
+        String chatId = new SnowFlake().generateId();
194
+        JSONObject jsonObject = new JSONObject();
195
+        jsonObject.put("chatId", chatId);
196
+        File transferFile = new File(profilePath + "/" + file.getOriginalFilename());
197
+        if (!transferFile.exists())
198
+            file.transferTo(transferFile);
199
+        else {
200
+            Path inputFilePath = Paths.get(profilePath + "/" + file.getOriginalFilename());
201
+            Files.deleteIfExists(inputFilePath);
202
+            file.transferTo(transferFile);
203
+        }
204
+        processValue = "上传完成:100%";
205
+        CmcDocument cmcDocument = new CmcDocument();
206
+        cmcDocument.setDocumentId(new SnowFlake().generateId());
207
+        cmcDocument.setChatId(chatId);
208
+        cmcDocument.setPath(prefixPath + "/" + file.getOriginalFilename());
209
+        cmcDocumentMapper.insertCmcDocument(cmcDocument);
210
+        String message = "";
211
+        if (agentName.contains("技术")) {
212
+            CmcChat cmcChat = new CmcChat();
213
+            cmcChat.setChatId(jsonObject.getString("chatId"));
214
+            cmcChat.setInputTime(new Date());
215
+            cmcChat.setInput("投标文件地址:" + prefixPath + "/" + file.getOriginalFilename());
216
+            cmcChat.setUserId(SecurityUtils.getUserId());
217
+            cmcChatMapper.insertCmcChat(cmcChat);
218
+            InputStream fileInputStream = new FileInputStream(profilePath + "/" + file.getOriginalFilename());
219
+            try (XWPFDocument doc = new XWPFDocument(fileInputStream)) {
220
+                // 保存文档到本地文件系统
221
+                try (FileOutputStream out = new FileOutputStream(profilePath + "/" + file.getOriginalFilename())) {
222
+                    doc.write(out);
223
+                }
224
+            }
225
+            List<String> subTitles = extractSubTitles(profilePath + "/" + file.getOriginalFilename(), "技术文件");
226
+            message = generateAnswerWithDocumentAndCollection(profilePath + "/" + file.getOriginalFilename(), subTitles);
179 227
         }
180 228
         jsonObject.put("assistantMessage", message);
181 229
         return jsonObject;
@@ -211,7 +259,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService
211 259
 
212 260
     /**
213 261
      * 新增智能体
214
-     * 
262
+     *
215 263
      * @param cmcAgent 智能体
216 264
      * @return 结果
217 265
      */
@@ -224,7 +272,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService
224 272
 
225 273
     /**
226 274
      * 修改智能体
227
-     * 
275
+     *
228 276
      * @param cmcAgent 智能体
229 277
      * @return 结果
230 278
      */
@@ -236,7 +284,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService
236 284
 
237 285
     /**
238 286
      * 批量删除智能体
239
-     * 
287
+     *
240 288
      * @param agentIds 需要删除的智能体主键
241 289
      * @return 结果
242 290
      */
@@ -248,7 +296,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService
248 296
 
249 297
     /**
250 298
      * 删除智能体信息
251
-     * 
299
+     *
252 300
      * @param agentId 智能体主键
253 301
      * @return 结果
254 302
      */
@@ -258,6 +306,209 @@ public class CmcAgentServiceImpl implements ICmcAgentService
258 306
         return cmcAgentMapper.deleteCmcAgentByAgentId(agentId);
259 307
     }
260 308
 
309
+    /**
310
+     * 调用LLM生成回答 - 针对技术文件章节内容生成
311
+     */
312
+    public String generateAnswerWithDocumentAndCollection(String templatePath, List<String> titles) throws IOException {
313
+        File profilePath = new File(templatePath);
314
+
315
+        // 构建招标文件内容的嵌入向量存储
316
+        InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
317
+        List<TextSegment> segments = splitDocument(profilePath);
318
+        List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
319
+        embeddingStore.addAll(embeddings, segments);
320
+
321
+        StringBuilder allGeneratedContent = new StringBuilder();
322
+        Map<String, String> titleContentMap = new HashMap<>();
323
+
324
+        List<String> successTitles = new ArrayList<>();
325
+        List<String> failureTitles = new ArrayList<>();
326
+
327
+        // 为每个章节标题生成内容
328
+        for (String title : titles) {
329
+            try {
330
+                // 1. 从招标文件中检索相关内容
331
+                List<String> tenderContent = retrieveFromTenderDocument(embeddingStore, title);
332
+
333
+                // 2. 从知识库中检索相似章节内容
334
+                List<JSONObject> knowledgeContexts = retrieveFromMilvus("technical", title, 10);
335
+
336
+                // 3. 构建针对该章节的提示语
337
+                String prompt = buildChapterPrompt(title, tenderContent, knowledgeContexts);
338
+
339
+                // 4. 生成章节内容
340
+                String chapterContent = generateAnswer(prompt);
341
+
342
+                // 5. 存储章节内容
343
+                titleContentMap.put(title, chapterContent);
344
+                allGeneratedContent.append(title).append("\n").append(chapterContent).append("\n\n");
345
+
346
+                // 更新进度
347
+                processValue = "生成章节内容中: " + Double.parseDouble(String.format("%.2f%n", (double) (titles.indexOf(title) + 1)  / titles.size() * 100)) + "%";
348
+                successTitles.add(title);
349
+
350
+            } catch (Exception e) {
351
+                failureTitles.add(title);
352
+                System.err.println("生成章节 " + title + " 内容时出错: " + e.getMessage());
353
+                titleContentMap.put(title, "该章节内容生成失败,请手动填写。");
354
+            }
355
+        }
356
+
357
+        // 将生成的内容写入技术文件
358
+        writeContentToDocument(titleContentMap, titles, templatePath);
359
+
360
+        // 构建返回消息
361
+        StringBuilder message = new StringBuilder();
362
+        message.append("技术文件章节内容生成完成!\n\n");
363
+        message.append("已为以下章节生成内容:\n");
364
+        for (String title : successTitles) {
365
+            message.append("• ").append(title).append("\n");
366
+        }
367
+        message.append("以下章节内容待生成:\n");
368
+        for (String title : failureTitles) {
369
+            message.append("× ").append(title).append("\n");
370
+        }
371
+        message.append("\n技术文件已更新,请查看:\n");
372
+        message.append("【<a href='/profile").append(templatePath.replace("\\", "/").replace(RuoYiConfig.getProfile(), ""))
373
+               .append("'>点击查看技术文件</a>】\n\n");
374
+        message.append("如需修改特定章节,请输入章节标题名称。\n").append("若提示未检测到工具,麻烦重新发送,谢谢");
375
+
376
+        return message.toString();
377
+    }
378
+
379
+    /**
380
+     * 从招标文件中检索与指定标题相关的内容
381
+     */
382
+    private List<String> retrieveFromTenderDocument(InMemoryEmbeddingStore<TextSegment> embeddingStore, String title) {
383
+        List<String> relevantContent = new ArrayList<>();
384
+
385
+        try {
386
+            Embedding queryEmbedding = embeddingModel.embed(title).content();
387
+            EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
388
+                    .queryEmbedding(queryEmbedding)
389
+                    .minScore(0.7) // 降低阈值以获取更多相关内容
390
+                    .maxResults(5) // 限制结果数量
391
+                    .build();
392
+
393
+            List<EmbeddingMatch<TextSegment>> results = embeddingStore.search(embeddingSearchRequest).matches();
394
+            results.sort(Comparator.comparingDouble(EmbeddingMatch<TextSegment>::score).reversed());
395
+
396
+            for (EmbeddingMatch<TextSegment> match : results) {
397
+                relevantContent.add(match.embedded().text());
398
+            }
399
+        } catch (Exception e) {
400
+            System.err.println("从招标文件检索内容时出错: " + e.getMessage());
401
+        }
402
+
403
+        return relevantContent;
404
+    }
405
+
406
+    /**
407
+     * 构建针对特定章节的提示语
408
+     */
409
+    private String buildChapterPrompt(String title, List<String> tenderContent, List<JSONObject> knowledgeContexts) {
410
+        StringBuilder prompt = new StringBuilder();
411
+
412
+        // 基础指令
413
+        prompt.append("你是一位专业的技术文件撰写专家。请根据以下信息为技术文件章节生成详细内容。\n\n");
414
+
415
+        // 章节信息
416
+        prompt.append("【目标章节】: ").append(title).append("\n\n");
417
+
418
+        // 招标文件相关内容
419
+        if (!tenderContent.isEmpty()) {
420
+            prompt.append("【招标文件要求】:\n");
421
+            for (int i = 0; i < tenderContent.size(); i++) {
422
+                prompt.append(i + 1).append(". ").append(tenderContent.get(i)).append("\n");
423
+            }
424
+            prompt.append("\n");
425
+        }
426
+
427
+        // 知识库参考内容
428
+        if (!knowledgeContexts.isEmpty()) {
429
+            prompt.append("【参考模板内容】:\n");
430
+            for (int i = 0; i < knowledgeContexts.size(); i++) {
431
+                JSONObject context = knowledgeContexts.get(i);
432
+                prompt.append("参考文件 ").append(i + 1).append(" (").append(context.getString("file_name")).append("):\n");
433
+                prompt.append(context.getString("content")).append("\n\n");
434
+            }
435
+        }
436
+
437
+        // 具体要求
438
+        prompt.append("【撰写要求】:\n");
439
+        prompt.append("1. 内容必须与招标文件要求高度匹配\n");
440
+        prompt.append("2. 参考模板内容的结构和表达方式\n");
441
+        prompt.append("3. 内容要专业、具体、可操作\n");
442
+        prompt.append("4. 语言要规范、准确,符合技术文件标准\n");
443
+        prompt.append("5. 内容要完整,包含必要的技术细节\n");
444
+        prompt.append("6. 避免空洞的套话,要有实质性的技术内容\n\n");
445
+
446
+        // 输出格式要求
447
+        prompt.append("【输出格式】:\n");
448
+        prompt.append("请直接输出章节的正文内容,不要包含标题。内容应该包括:\n");
449
+        prompt.append("- 技术方案概述\n");
450
+        prompt.append("- 具体实施方法\n");
451
+        prompt.append("- 技术参数和标准\n");
452
+        prompt.append("- 质量保证措施\n");
453
+        prompt.append("- 其他相关技术细节\n\n");
454
+
455
+        prompt.append("请开始生成 ").append(title).append(" 的详细内容:");
456
+
457
+        return prompt.toString();
458
+    }
459
+
460
+    /**
461
+     * 将生成的内容写入文档
462
+     */
463
+    private void writeContentToDocument(Map<String, String> titleContentMap, List<String> titles, String absolutePath) throws IOException {
464
+        File file = new File(absolutePath);
465
+        FileInputStream fileInputStream = new FileInputStream(file);
466
+
467
+        try (XWPFDocument document = new XWPFDocument(fileInputStream)) {
468
+            List<XWPFParagraph> paragraphs = document.getParagraphs();
469
+            List<Integer> titlePositions = new ArrayList<>();
470
+            List<String> contents = new ArrayList<>();
471
+
472
+            // 找到所有标题的位置
473
+            for (int i = 0; i < paragraphs.size(); i++) {
474
+                XWPFParagraph paragraph = paragraphs.get(i);
475
+                String paragraphText = paragraph.getText().trim();
476
+
477
+                for (String title : titles) {
478
+                    if (paragraphText.contains(title)) {
479
+                        titlePositions.add(i);
480
+                        contents.add(titleContentMap.getOrDefault(title, ""));
481
+                        break;
482
+                    }
483
+                }
484
+            }
485
+
486
+            // 从后往前插入内容,避免位置偏移
487
+            for (int i = titlePositions.size() - 1; i >= 0; i--) {
488
+                int insertPos = titlePositions.get(i) + 1;
489
+                if (insertPos < paragraphs.size()) {
490
+                    XmlCursor xmlCursor = paragraphs.get(insertPos).getCTP().newCursor();
491
+                    XWPFParagraph contentParagraph = document.insertNewParagraph(xmlCursor);
492
+                    contentParagraph.setStyle("1"); // 正文样式
493
+
494
+                    XWPFRun run = contentParagraph.createRun();
495
+                    String content = contents.get(i);
496
+                    if (content != null && !content.trim().isEmpty()) {
497
+                        run.setText(content);
498
+                    }
499
+                }
500
+
501
+                // 更新进度
502
+                processValue = "章节内容写入: " + Double.parseDouble(String.format("%.2f%n", (double) (i + 1)  / titles.size() * 100)) + "%";
503
+
504
+            }
505
+
506
+            // 保存文档
507
+            try (FileOutputStream out = new FileOutputStream(absolutePath)) {
508
+                document.write(out);
509
+            }
510
+        }
511
+    }
261 512
 
262 513
     /**
263 514
      * 调用LLM生成回答
@@ -281,7 +532,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService
281 532
             sb.append(requests).append("\n\n");
282 533
         }
283 534
         //根据招标文件工作大纲要求生成二级标题
284
-        sb.append("请根据上述招标文件内容,严格按以下格式列出").append(question).append(":\n")
535
+        sb.append("请基于上述招标文件中提到的工作大纲内容,先列出二级章节标题,严格按以下格式,仅输出标题列表:\n")
285 536
                 .append("6.1 XX\n" +
286 537
                 "6.2 XX\n" +
287 538
                 "6.3 XX");
@@ -306,7 +557,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService
306 557
         for (String chapter2 : chapter2List) {
307 558
             String orderNum = chapter2.split(" ")[0];
308 559
             StringBuilder sb = new StringBuilder("二级标题如下:\n").append(chapter2)
309
-                    .append("\n 请根据参考内容编排三级标题,严格按以下格式列出:\n")
560
+                    .append("\n 请以上述二级标题作为检索词,到技术知识库中检索最相似的片段,结合检索结果编排该二级标题下的三级标题,严格按以下格式,仅输出三级标题列表:\n")
310 561
                     .append(orderNum).append(".1 XX\n")
311 562
                     .append(orderNum).append(".2 XX\n")
312 563
                     .append(orderNum).append(".3 XX\n");
@@ -321,7 +572,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService
321 572
         }
322 573
         String sb = "二级标题如下:\n" + chapters2 +
323 574
                 "\n 三级标题如下:" + chapter3 +
324
-                "帮我合并三级标题,严格按以下格式列出:\n" +
575
+                "请将上述二级与对应的三级标题进行合并,严格按以下格式输出完整大纲,仅输出标题:\n" +
325 576
                 "6 技术文件\n" +
326 577
                 "6.1 XX\n" +
327 578
                 "6.1.1 XX\n" +
@@ -329,7 +580,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService
329 580
                 "6.2 XX\n" +
330 581
                 "6.2.1 XX";
331 582
         String content = generateAnswer(sb);
332
-        writeContent(content, templatePath);
583
+        writeTitles(content, templatePath);
333 584
         return content;
334 585
     }
335 586
 
@@ -355,61 +606,58 @@ public class CmcAgentServiceImpl implements ICmcAgentService
355 606
      * @return
356 607
      */
357 608
     public List<JSONObject> retrieveFromMilvus(String collectionName, String query, int topK) {
358
-        SearchResultsWrapper wrapper = retrieve(collectionName, query, topK);
359
-        return wrapper.getRowRecords(0).stream()
360
-                .map(record -> {
361
-                    JSONObject result = new JSONObject();
362
-                    result.put("content", record.get("content"));
363
-                    return result;
364
-                })
365
-                .collect(Collectors.toList());
609
+        List<JSONObject> resultList = new ArrayList<>();
610
+        List<List<SearchResp.SearchResult>> searchResultList = retrieve(collectionName, query, topK);
611
+        searchResultList.forEach(searchResult -> {
612
+            JSONObject result = new JSONObject();
613
+            result.put("content", searchResult.get(0).getEntity().get("content"));
614
+            resultList.add(result);
615
+        });
616
+        return resultList;
366 617
     }
367 618
 
368 619
     /**
369 620
      * 检索知识库
370 621
      */
371
-    private SearchResultsWrapper retrieve(String collectionName, String query, int topK) {
372
-        List<List<Float>> queryVector = Collections.singletonList(embeddingModel.embed(query).content().vectorAsList());
622
+    private List<List<SearchResp.SearchResult>> retrieve(String collectionName, String query, int topK) {
623
+        List<BaseVector> queryVector = Collections.singletonList(new FloatVec(embeddingModel.embed(query).content().vector()));
373 624
 
374 625
         //  加载集合
375
-        LoadCollectionParam loadParam = LoadCollectionParam.newBuilder()
376
-                .withCollectionName(collectionName)
626
+        LoadCollectionReq loadCollectionReq = LoadCollectionReq.builder()
627
+                .collectionName(collectionName)
377 628
                 .build();
378
-
379
-        R<RpcStatus> loadResponse = milvusClient.loadCollection(loadParam);
380
-        if (loadResponse.getStatus() != R.Status.Success.getCode()) {
381
-            System.err.println("加载Collection失败: " + loadResponse.getMessage());
382
-            milvusClient.close();
383
-        }
629
+        milvusClient.loadCollection(loadCollectionReq);
384 630
 
385 631
         // 构建SearchParam
386
-        SearchParam searchParam = SearchParam.newBuilder()
387
-                .withCollectionName(collectionName)
388
-                .withVectors(queryVector)
389
-                .withTopK(topK)
390
-                .withOutFields(Arrays.asList("content"))
391
-                .withVectorFieldName("embedding")
392
-                .withMetricType(MetricType.COSINE)
393
-                .withParams("{\"nprobe\": 8}")
632
+        Map<String, Object> searchParams = new HashMap<>();
633
+        searchParams.put("nprobe", 8);
634
+        SearchReq searchReq = SearchReq.builder()
635
+                .collectionName(collectionName)
636
+                .data(queryVector)
637
+                .topK(topK)
638
+                .outputFields(Arrays.asList("file_name", "file_type", "content"))
639
+                .annsField("embedding")
640
+                .metricType(IndexParam.MetricType.COSINE)
641
+                .searchParams(searchParams)
394 642
                 .build();
395 643
 
396
-        R<SearchResults> response = milvusClient.search(searchParam);
397
-        SearchResultsWrapper wrapper = new SearchResultsWrapper(response.getData().getResults());
644
+        SearchResp searchResp = milvusClient.search(searchReq);
645
+        List<List<SearchResp.SearchResult>> searchResultList = searchResp.getSearchResults();
398 646
 
399 647
         // 释放集合
400
-        ReleaseCollectionParam param = ReleaseCollectionParam.newBuilder()
401
-                .withCollectionName(collectionName)
648
+        ReleaseCollectionReq releaseCollectionReq = ReleaseCollectionReq.builder()
649
+                .collectionName(collectionName)
402 650
                 .build();
403
-        milvusClient.releaseCollection(param);
651
+        milvusClient.releaseCollection(releaseCollectionReq);
404 652
 
405
-        return wrapper;
653
+        return searchResultList;
406 654
     }
407 655
 
408 656
     /**
409 657
      * 写入章节大纲
410 658
      * @return
411 659
      */
412
-    public void writeContent(String content, String templatePath) throws IOException {
660
+    public void writeTitles(String content, String templatePath) throws IOException {
413 661
         List<String> chapters = new ArrayList<>();
414 662
         String[] contentLines = content.split("\n");
415 663
         for (String line : contentLines) {
@@ -439,6 +687,146 @@ public class CmcAgentServiceImpl implements ICmcAgentService
439 687
         }
440 688
     }
441 689
 
690
+    /**
691
+     * 写入章节内容
692
+     * @return
693
+     */
694
+    public void writeContent(String content, List<String> titles, String absolutePath) throws IOException {
695
+        String[] contentLines = content.split("\n");
696
+        Map<String, String> map = new HashMap<>();
697
+        File file = new File(absolutePath);
698
+        FileInputStream fileInputStream = new FileInputStream(file);
699
+        try (XWPFDocument document = new XWPFDocument(fileInputStream)) {
700
+            for (int i = 0; i < titles.size(); i++) {
701
+                int startIndex = Arrays.asList(contentLines).indexOf(titles.get(i));
702
+                StringBuilder text = new StringBuilder();
703
+                if (startIndex >= 0) {
704
+                    if (i < titles.size() - 1) {
705
+                        int endIndex = Arrays.asList(contentLines).indexOf(titles.get(i + 1));
706
+                        for (int c = startIndex + 1; c < endIndex; c++) {
707
+                            text.append(contentLines[c]).append("\n\n");
708
+                        }
709
+                    } else {
710
+                        if (startIndex + 1 < contentLines.length) {
711
+                            for (int c = startIndex + 1; c < contentLines.length; c++) {
712
+                                text.append(contentLines[c]).append("\n\n");
713
+                            }
714
+                        }
715
+                    }
716
+                }
717
+                else
718
+                    text.append(content);
719
+                map.put(titles.get(i), text.toString());
720
+            }
721
+
722
+            List<Integer> positions = new ArrayList<>();
723
+            List<String> contents = new ArrayList<>();
724
+            List<XWPFParagraph> paragraphs = document.getParagraphs();
725
+
726
+            for (int i = 0; i < paragraphs.size(); i++) {
727
+                XWPFParagraph paragraph = paragraphs.get(i);
728
+                for (String title : titles) {
729
+                    if (paragraph.getText().contains(title)) {
730
+                        positions.add(i);
731
+                        contents.add(map.get(title));
732
+                    }
733
+                }
734
+            }
735
+
736
+            for (int i = positions.size() - 1; i >= 0; i--) {
737
+                int insertPos = positions.get(i) + 1;
738
+                XmlCursor xmlCursor = paragraphs.get(insertPos).getCTP().newCursor();
739
+                XWPFParagraph contentParagraph = document.insertNewParagraph(xmlCursor);
740
+                contentParagraph.setStyle("1");
741
+                XWPFRun run = contentParagraph.createRun();
742
+                run.setText(contents.get(i));
743
+            }
744
+
745
+            try (FileOutputStream out = new FileOutputStream(absolutePath)) {
746
+                document.write(out);
747
+            }
748
+        }
749
+    }
750
+
751
+    /**
752
+     * 获取最低级别子标题列表
753
+     */
754
+    public List<String> extractSubTitles(String filename, String question) throws IOException {
755
+        List<String> subTitles = new ArrayList<>();
756
+        InputStream fileInputStream = new FileInputStream(filename);
757
+
758
+        boolean foundParent = false;
759
+        int parentLevel = -1;
760
+        // 用于跟踪当前路径
761
+        List<XWPFParagraph> currentPath = new ArrayList<>();
762
+
763
+        try (XWPFDocument document = new XWPFDocument(fileInputStream)) {
764
+
765
+            for (XWPFParagraph  paragraph : document.getParagraphs()) {
766
+                String text = paragraph.getText();
767
+                int level = Integer.parseInt(paragraph.getStyle());
768
+
769
+                // 维护当前路径
770
+                while (!currentPath.isEmpty() && Integer.parseInt(currentPath.get(currentPath.size() - 1).getStyle()) >= level) {
771
+                    currentPath.remove(currentPath.size() - 1);
772
+                }
773
+                currentPath.add(paragraph);
774
+                if (foundParent) {
775
+                    if (level > parentLevel) { // 是子标题
776
+                        if (isLeafNode(document, paragraph, level)) {
777
+                            subTitles.add(text.replace("\n", ""));
778
+                        }
779
+                    } else {
780
+                        // 遇到同级或更高级别的标题,停止搜索
781
+                        break;
782
+                    }
783
+                } else if (text.equals(question)) {
784
+                    foundParent = true;
785
+                    parentLevel = level;
786
+                }
787
+            }
788
+
789
+
790
+            return subTitles;
791
+
792
+        }
793
+    }
794
+
795
+    // 检查一个标题是否是叶子节点(没有更低级别的子标题)
796
+    private boolean isLeafNode(XWPFDocument doc, XWPFParagraph p, int level) {
797
+        int index = doc.getPosOfParagraph(p);
798
+        if (index == -1 || index >= doc.getParagraphs().size()-1) {
799
+            return true;
800
+        }
801
+
802
+        // 检查后续段落
803
+        XWPFParagraph nextP = doc.getParagraphs().get(index + 1);
804
+        int nextLevel = Integer.parseInt(nextP.getStyle());
805
+        // 遇到同级或更高级别标题,是叶子节点
806
+        return nextLevel <= level; // 存在更低级别的标题,不是叶子节点
807
+
808
+    }
809
+
810
+    /**
811
+     * 获取二、三级标题列表
812
+     */
813
+    public String extractTitles(String filename) throws IOException {
814
+        StringBuilder subTitles = new StringBuilder();
815
+        InputStream fileInputStream = new FileInputStream(filename);
816
+        try (XWPFDocument document = new XWPFDocument(fileInputStream)) {
817
+            for (XWPFParagraph paragraph : document.getParagraphs()) {
818
+                String text = paragraph.getText().trim();
819
+                if (paragraph.getStyle() != null) {
820
+                    // 判断主标题
821
+                    if (paragraph.getStyle().equals("3") || paragraph.getStyle().equals("4") ) {
822
+                        subTitles.append(text).append("\n");
823
+                    }
824
+                }
825
+            }
826
+        }
827
+        return subTitles.toString();
828
+    }
829
+
442 830
     /**
443 831
      * 分割文档
444 832
      */

+ 17
- 2
llm-ui/src/api/llm/agent.js Näytä tiedosto

@@ -1,8 +1,8 @@
1 1
 /*
2 2
  * @Author: wrh
3 3
  * @Date: 2025-07-17 18:06:24
4
- * @LastEditors: Please set LastEditors
5
- * @LastEditTime: 2025-07-25 14:51:40
4
+ * @LastEditors: wrh
5
+ * @LastEditTime: 2025-09-02 16:43:25
6 6
  */
7 7
 import request from '@/utils/request'
8 8
 
@@ -64,6 +64,21 @@ export function uploadFile(file, agentName) {
64 64
   })
65 65
 }
66 66
 
67
+// 上传单文件
68
+export function uploadModifyFile(file, agentName) {
69
+  const formData = new FormData()
70
+  formData.append('file', file)
71
+  formData.append('agentName', agentName)
72
+  return request({
73
+    url: '/llm/agent/modifyFile',
74
+    method: 'post',
75
+    data: formData,
76
+    headers: {
77
+      'Content-Type': 'multipart/form-data'
78
+    }
79
+  })
80
+}
81
+
67 82
 // 上传多文件
68 83
 export function uploadFileList(fileList, agentName) {
69 84
   const formData = new FormData()

+ 120
- 4
llm-ui/src/views/llm/agent/AgentDetail.vue Näytä tiedosto

@@ -2,7 +2,7 @@
2 2
  * @Author: wrh
3 3
  * @Date: 2025-01-01 00:00:00
4 4
  * @LastEditors: wrh
5
- * @LastEditTime: 2025-08-15 17:13:15
5
+ * @LastEditTime: 2025-09-11 15:58:35
6 6
 -->
7 7
 <template>
8 8
   <div class="agent-detail-container" v-loading="loading">
@@ -89,7 +89,7 @@
89 89
                       <el-icon>
90 90
                         <Upload />
91 91
                       </el-icon>
92
-                      选择文件
92
+                      选择招标文件
93 93
                     </el-button>
94 94
                   </el-upload>
95 95
                   <div v-if="chatFileList.length > 0" class="chat-upload-actions">
@@ -138,6 +138,41 @@
138 138
               </div>
139 139
             </div>
140 140
 
141
+            <!-- 文件上传提示消息 -->
142
+            <div v-if="techUploadDisplay" class="message-item assistant-message">
143
+              <div class="message-avatar">
144
+                <svg-icon icon-class="robot" />
145
+              </div>
146
+              <div class="message-content">
147
+                <div class="message-upload-area">
148
+                  <p class="upload-tip">请上传大纲修改后的技术文件:</p>
149
+                  <el-upload class="inline-upload" :action="uploadAction" :multiple="false" :auto-upload="false"
150
+                    :file-list="chatFileList" :on-change="handleChatFileChange" :before-upload="beforeUpload"
151
+                    :show-file-list="true" :limit="1">
152
+                    <el-button type="primary" size="small">
153
+                      <el-icon>
154
+                        <Upload />
155
+                      </el-icon>
156
+                      选择技术文件
157
+                    </el-button>
158
+                  </el-upload>
159
+                  <div v-if="chatFileList.length > 0" class="chat-upload-actions">
160
+                    <el-button size="small" type="success" @click="submitFileUpload">
161
+                      <el-icon>
162
+                        <Check />
163
+                      </el-icon>
164
+                      确认上传
165
+                    </el-button>
166
+                  </div>
167
+                  <div v-if="percentageDisplay">
168
+                    <el-progress :percentage="percentage" :stroke-width="25" :text-inside="true">
169
+                      <el-button text>{{ progress }}</el-button>
170
+                    </el-progress>
171
+                  </div>
172
+                </div>
173
+              </div>
174
+            </div>
175
+
141 176
             <!-- 正在输入指示器 -->
142 177
             <div v-if="isTyping" class="message-item assistant-message">
143 178
               <div class="message-avatar">
@@ -180,7 +215,7 @@
180 215
 import { ref, reactive, watch, nextTick, computed, getCurrentInstance } from 'vue';
181 216
 import { ElMessage, ElMessageBox } from 'element-plus';
182 217
 import { Delete, Refresh } from '@element-plus/icons-vue';
183
-import { getAgent, opening, uploadFile, getProcessValue } from '@/api/llm/agent';
218
+import { getAgent, opening, uploadFile, uploadModifyFile, getProcessValue } from '@/api/llm/agent';
184 219
 import { answer } from '@/api/llm/mcp';
185 220
 import { listTopic, getTopic, delTopic, addTopic, updateTopic } from "@/api/llm/topic";
186 221
 import { listChat, addChat, updateChat } from "@/api/llm/chat";
@@ -213,6 +248,7 @@ const chatTitle = ref('智能体新对话')
213 248
 const percentage = ref(0);
214 249
 const progress = ref("");
215 250
 const percentageDisplay = ref(false);
251
+const techUploadDisplay = ref(false)
216 252
 
217 253
 // 聊天内文件上传相关
218 254
 const chatFileList = ref([]) // 聊天内的文件列表
@@ -268,6 +304,10 @@ const loadTopic = async () => {
268 304
   if (res.rows.length > 0) {
269 305
     topicList.value = res.rows
270 306
   }
307
+  else {
308
+    topicList.value = res.rows
309
+    techUploadDisplay.value = false
310
+  }
271 311
 }
272 312
 
273 313
 // 加载指定话题的聊天记录(公共函数)
@@ -569,6 +609,7 @@ const submitChatUpload = async () => {
569 609
           isHtml: true // 标记这是HTML内容
570 610
         }
571 611
         chatMessages.value.push(uploadMessage);
612
+        techUploadDisplay.value = true;
572 613
         // ElMessage.success('文件上传成功');
573 614
         let topicRes = await addTopic({ agentId: props.agentId, topic: fileName });
574 615
         const topicId = topicRes.msg;
@@ -620,6 +661,80 @@ const submitChatUpload = async () => {
620 661
   }
621 662
 }
622 663
 
664
+// 聊天内文件上传提交
665
+const submitFileUpload = async () => {
666
+  if (chatFileList.value.length === 0) {
667
+    ElMessage.warning('请先选择文件')
668
+    return
669
+  }
670
+  try {
671
+    percentageDisplay.value = true;
672
+    var timer;
673
+    clearInterval(timer);
674
+    getProcess()
675
+    function getProcess() {
676
+      timer = setInterval(function () { //隔2000毫秒获取进度
677
+        getProcessValue().then(res => {
678
+          if (res.code == 200 & res.msg.includes(":")) {
679
+            progress.value = res.msg;
680
+            percentage.value = Number(res.msg.split(":")[1].replace("%", ""))
681
+          }
682
+        });
683
+      }, 2000)
684
+    }
685
+    const file = chatFileList.value[0].raw // 只取第一个文件
686
+    const fileName = file.name;
687
+    try {
688
+      const response = await uploadModifyFile(file, agentInfo.value.agentName)
689
+      const chatId = response.data.chatId; //获取保存后的chatId
690
+      // 解析返回的数据
691
+      if (response.data && response.data.assistantMessage) {
692
+        percentageDisplay.value = false;
693
+        // 格式化链接:在href前加上基础API地址
694
+        let assistantContent = formatContentLinks(response.data.assistantMessage)
695
+        clearInterval(timer);
696
+        // 添加上传成功的消息到聊天记录
697
+        const uploadMessage = {
698
+          role: 'assistant',
699
+          content: assistantContent,
700
+          timestamp: new Date().toLocaleTimeString(),
701
+          isHtml: true // 标记这是HTML内容
702
+        }
703
+        chatMessages.value.push(uploadMessage);
704
+        techUploadDisplay.value = false;
705
+        await updateChat({ userId: userStore.id, chatId, topicId: currentTopicId.value, output: assistantContent, outputTime: proxy.parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}') });
706
+
707
+        // 清空文件上传列表
708
+        chatFileList.value = [];
709
+
710
+        nextTick(() => {
711
+          scrollToBottom()
712
+        })
713
+      }
714
+    }
715
+    catch (error) {
716
+      clearInterval(timer);
717
+      console.error('文件上传失败:', error)
718
+    }
719
+
720
+  } catch (error) {
721
+    console.error('文件上传失败:', error)
722
+    ElMessage.error('文件上传失败')
723
+
724
+    // 添加上传失败的消息到聊天记录
725
+    const errorMessage = {
726
+      role: 'assistant',
727
+      content: '文件上传失败,请检查文件格式或网络连接后重试。',
728
+      timestamp: new Date().toLocaleTimeString(),
729
+      isHtml: false
730
+    }
731
+    chatMessages.value.push(errorMessage)
732
+
733
+    nextTick(() => {
734
+      scrollToBottom()
735
+    })
736
+  }
737
+}
623 738
 // 开始新对话
624 739
 const startNewChat = () => {
625 740
   currentTopicId.value = null
@@ -1192,4 +1307,5 @@ const formatContentLinks = (content) => {
1192 1307
     margin-top: 16px;
1193 1308
     font-size: 14px;
1194 1309
   }
1195
-}</style>
1310
+}
1311
+</style>

+ 1
- 1
llm-ui/src/views/llm/knowledge/index.vue Näytä tiedosto

@@ -635,7 +635,7 @@ function sendMessage() {
635 635
     if (response && Array.isArray(response)) {
636 636
       aiMessage.references = response.map(item => ({
637 637
         fileName: item.file_name,
638
-        similarity: item.distance,
638
+        similarity: item.score,
639 639
         content: item.content
640 640
       }));
641 641
     }

Loading…
Peruuta
Tallenna