Browse Source

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

lamphua 2 days ago
parent
commit
ca8d261ca8
20 changed files with 911 additions and 407 deletions
  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 View File

219
                 <version>${ruoyi.version}</version>
219
                 <version>${ruoyi.version}</version>
220
             </dependency>
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
         </dependencies>
234
         </dependencies>
223
     </dependencyManagement>
235
     </dependencyManagement>
224
 
236
 

+ 2
- 2
llm-back/ruoyi-agent/pom.xml View File

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

+ 1
- 1
llm-back/ruoyi-agent/src/main/java/com/ruoyi/agent/service/McpServerConfig.java View File

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

+ 56
- 62
llm-back/ruoyi-agent/src/main/java/com/ruoyi/agent/service/impl/McpServiceImpl.java View File

13
 import dev.langchain4j.store.embedding.EmbeddingMatch;
13
 import dev.langchain4j.store.embedding.EmbeddingMatch;
14
 import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
14
 import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
15
 import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
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
 import org.apache.poi.extractor.POITextExtractor;
25
 import org.apache.poi.extractor.POITextExtractor;
28
 import org.apache.poi.extractor.ExtractorFactory;
26
 import org.apache.poi.extractor.ExtractorFactory;
29
 import org.apache.poi.xwpf.usermodel.*;
27
 import org.apache.poi.xwpf.usermodel.*;
36
 import org.noear.solon.ai.chat.message.AssistantMessage;
34
 import org.noear.solon.ai.chat.message.AssistantMessage;
37
 import org.noear.solon.ai.chat.message.ChatMessage;
35
 import org.noear.solon.ai.chat.message.ChatMessage;
38
 import org.noear.solon.ai.chat.session.InMemoryChatSession;
36
 import org.noear.solon.ai.chat.session.InMemoryChatSession;
37
+import org.noear.solon.ai.mcp.McpChannel;
39
 import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint;
38
 import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint;
40
 import org.noear.solon.annotation.Param;
39
 import org.noear.solon.annotation.Param;
41
 import org.springframework.stereotype.Service;
40
 import org.springframework.stereotype.Service;
42
 
41
 
43
 import java.io.*;
42
 import java.io.*;
44
 import java.util.*;
43
 import java.util.*;
45
-import java.util.stream.Collectors;
46
 
44
 
47
 @Service
45
 @Service
48
-@McpServerEndpoint(sseEndpoint = "/llm/mcp/sse")
46
+@McpServerEndpoint(channel = McpChannel.SSE, mcpEndpoint = "/mcp/sse")
49
 public class McpServiceImpl implements IMcpService {
47
 public class McpServiceImpl implements IMcpService {
50
 
48
 
51
     private static final EmbeddingModel embeddingModel = new BgeSmallZhV15EmbeddingModel();
49
     private static final EmbeddingModel embeddingModel = new BgeSmallZhV15EmbeddingModel();
52
 
50
 
53
     private static final String llmServiceUrl = "http://192.168.28.188:8000/v1/chat/completions";
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
                     .build());
56
                     .build());
60
 
57
 
61
     /**
58
     /**
70
             try {
67
             try {
71
                 templatePath = templatePath.replace("/dev-api/profile", Solon.cfg().getProperty("cmc.profile"));
68
                 templatePath = templatePath.replace("/dev-api/profile", Solon.cfg().getProperty("cmc.profile"));
72
                 List<String> subTitles = extractSubTitles(templatePath, title);
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
             } catch (IOException e) {
72
             } catch (IOException e) {
77
                 throw new RuntimeException(e);
73
                 throw new RuntimeException(e);
78
             }
74
             }
82
      * 从Milvus检索相关文档
78
      * 从Milvus检索相关文档
83
      * @return
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
      * 调用LLM生成回答
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
         StringBuilder sb = new StringBuilder("招标文件内容:\n\n");
97
         StringBuilder sb = new StringBuilder("招标文件内容:\n\n");
102
         String filename = templatePath.replace("_" + agentName, "");
98
         String filename = templatePath.replace("_" + agentName, "");
103
         File profilePath = new File(filename);
99
         File profilePath = new File(filename);
128
         }
124
         }
129
         String absolutePath = templatePath.replace("/dev-api/profile", Solon.cfg().getProperty("cmc.profile"));
125
         String absolutePath = templatePath.replace("/dev-api/profile", Solon.cfg().getProperty("cmc.profile"));
130
         writeContent(content.toString(), titles, absolutePath);
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
         content.append( "招标文件分析完成,章节内容已写入【<a href='")
133
         content.append( "招标文件分析完成,章节内容已写入【<a href='")
138
                 .append(templatePath.replace(Solon.cfg().getProperty("cmc.profile"), "/dev-api/profile"))
134
                 .append(templatePath.replace(Solon.cfg().getProperty("cmc.profile"), "/dev-api/profile"))
139
                 .append("'> 技术文件" + "</a>】,请查阅\n\n")
135
                 .append("'> 技术文件" + "</a>】,请查阅\n\n")
151
                 .model("Qwen2.5-1.5B-Instruct")
147
                 .model("Qwen2.5-1.5B-Instruct")
152
                 .build();
148
                 .build();
153
 
149
 
154
-
155
         List<ChatMessage> messages = new ArrayList<>();
150
         List<ChatMessage> messages = new ArrayList<>();
156
         messages.add(ChatMessage.ofUser(prompt));
151
         messages.add(ChatMessage.ofUser(prompt));
157
         ChatSession chatSession =  InMemoryChatSession.builder().messages(messages).build();
152
         ChatSession chatSession =  InMemoryChatSession.builder().messages(messages).build();
280
         return nextLevel <= level; // 存在更低级别的标题,不是叶子节点
275
         return nextLevel <= level; // 存在更低级别的标题,不是叶子节点
281
 
276
 
282
     }
277
     }
278
+
283
     /**
279
     /**
284
      * 获取二、三级标题列表
280
      * 获取二、三级标题列表
285
      */
281
      */
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
                 .build();
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
         // 构建SearchParam
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
                 .build();
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
                 .build();
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 View File

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

+ 10
- 0
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/CmcAgentController.java View File

89
         return success(cmcAgentService.uploadDocument(file, agentName));
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
      * @return
104
      * @return

+ 12
- 23
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/KnowLedgeController.java View File

7
 import com.ruoyi.common.core.domain.AjaxResult;
7
 import com.ruoyi.common.core.domain.AjaxResult;
8
 import dev.langchain4j.model.embedding.onnx.bgesmallzhv15.BgeSmallZhV15EmbeddingModel;
8
 import dev.langchain4j.model.embedding.onnx.bgesmallzhv15.BgeSmallZhV15EmbeddingModel;
9
 import dev.langchain4j.model.embedding.EmbeddingModel;
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
 import org.springframework.beans.factory.annotation.Autowired;
10
 import org.springframework.beans.factory.annotation.Autowired;
16
 import org.springframework.web.bind.annotation.*;
11
 import org.springframework.web.bind.annotation.*;
17
 import org.springframework.web.multipart.MultipartFile;
12
 import org.springframework.web.multipart.MultipartFile;
37
 
32
 
38
     private static final EmbeddingModel embeddingModel = new BgeSmallZhV15EmbeddingModel();
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
     @GetMapping("/list")
38
     @GetMapping("/list")
50
     public AjaxResult listKnowLedgeCollection()
39
     public AjaxResult listKnowLedgeCollection()
51
     {
40
     {
52
-        JSONArray collectionNames = milvusService.getCollectionNames(milvusClient);
41
+        JSONArray collectionNames = milvusService.getCollectionNames();
53
         return success(collectionNames);
42
         return success(collectionNames);
54
     }
43
     }
55
 
44
 
59
     @PostMapping("/create")
48
     @PostMapping("/create")
60
     public AjaxResult createKnowLedgeCollection(String collectionName, String description)
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
     @PostMapping("/modify")
58
     @PostMapping("/modify")
70
     public AjaxResult modifyKnowLedgeCollection(String collectionName, String newCollectionName)
59
     public AjaxResult modifyKnowLedgeCollection(String collectionName, String newCollectionName)
71
     {
60
     {
72
-        milvusService.collectionRename(milvusClient, collectionName, newCollectionName);
61
+        milvusService.collectionRename(collectionName, newCollectionName);
73
         return success();
62
         return success();
74
     }
63
     }
75
 
64
 
79
     @DeleteMapping("/remove")
68
     @DeleteMapping("/remove")
80
     public AjaxResult removeKnowLedgeCollection(String collectionName)
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
         return success();
73
         return success();
85
     }
74
     }
86
 
75
 
90
     @GetMapping("/listDocument")
79
     @GetMapping("/listDocument")
91
     public AjaxResult listKnowledgeDocument(String collectionName, String fileType)
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
         return success(documentList);
83
         return success(documentList);
95
     }
84
     }
96
 
85
 
99
      */
88
      */
100
     @PostMapping("/insertDocument")
89
     @PostMapping("/insertDocument")
101
     public AjaxResult insertKnowledgeDocument(MultipartFile[] fileList, String collectionName) throws IOException {
90
     public AjaxResult insertKnowledgeDocument(MultipartFile[] fileList, String collectionName) throws IOException {
102
-        R<MutationResult> insertResult = null;
91
+        long insertResult = 0;
103
         for (MultipartFile file : fileList)
92
         for (MultipartFile file : fileList)
104
-            insertResult = langChainMilvusService.insertLangchainEmbeddingDocument(milvusClient, file, collectionName, embeddingModel);
93
+            insertResult = langChainMilvusService.insertLangchainEmbeddingDocument(file, collectionName);
105
         String message = "文件导入成功";
94
         String message = "文件导入成功";
106
-        if (insertResult != null && insertResult.getStatus() != R.Status.Success.getCode()) {
107
-            message = "文件导入失败" + insertResult.getMessage();
95
+        if (insertResult == 0) {
96
+            message = "文件导入失败";
108
             return error(message);
97
             return error(message);
109
         }
98
         }
110
         else
99
         else
117
     @DeleteMapping("/removeDocument")
106
     @DeleteMapping("/removeDocument")
118
     public AjaxResult deleteKnowledgeDocument(String fileName, String collectionName)
107
     public AjaxResult deleteKnowledgeDocument(String fileName, String collectionName)
119
     {
108
     {
120
-        milvusService.removeDocument(milvusClient, collectionName, fileName);
109
+        milvusService.removeDocument(collectionName, fileName);
121
         return success();
110
         return success();
122
     }
111
     }
123
 
112
 

+ 3
- 10
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/McpController.java View File

1
 package com.ruoyi.web.llm.controller;
1
 package com.ruoyi.web.llm.controller;
2
 
2
 
3
 import com.alibaba.fastjson2.JSONObject;
3
 import com.alibaba.fastjson2.JSONObject;
4
-import com.ruoyi.common.config.RuoYiConfig;
5
 import com.ruoyi.common.core.controller.BaseController;
4
 import com.ruoyi.common.core.controller.BaseController;
6
 import com.ruoyi.llm.domain.CmcChat;
5
 import com.ruoyi.llm.domain.CmcChat;
7
 import com.ruoyi.llm.service.ICmcAgentService;
6
 import com.ruoyi.llm.service.ICmcAgentService;
18
 import org.noear.solon.ai.chat.message.AssistantMessage;
17
 import org.noear.solon.ai.chat.message.AssistantMessage;
19
 import org.noear.solon.ai.chat.message.ChatMessage;
18
 import org.noear.solon.ai.chat.message.ChatMessage;
20
 import org.noear.solon.ai.chat.session.InMemoryChatSession;
19
 import org.noear.solon.ai.chat.session.InMemoryChatSession;
20
+import org.noear.solon.ai.mcp.McpChannel;
21
 import org.noear.solon.ai.mcp.client.McpClientProvider;
21
 import org.noear.solon.ai.mcp.client.McpClientProvider;
22
 import org.springframework.beans.factory.annotation.Autowired;
22
 import org.springframework.beans.factory.annotation.Autowired;
23
 import org.springframework.web.bind.annotation.GetMapping;
23
 import org.springframework.web.bind.annotation.GetMapping;
24
 import org.springframework.web.bind.annotation.RequestMapping;
24
 import org.springframework.web.bind.annotation.RequestMapping;
25
 import org.springframework.web.bind.annotation.RestController;
25
 import org.springframework.web.bind.annotation.RestController;
26
-import org.springframework.web.multipart.MultipartFile;
27
-import reactor.core.publisher.Flux;
28
 
26
 
29
 import java.io.IOException;
27
 import java.io.IOException;
30
 import java.util.*;
28
 import java.util.*;
69
     @GetMapping("/answer")
67
     @GetMapping("/answer")
70
     public AssistantMessage answer(String topicId, String question) throws IOException {
68
     public AssistantMessage answer(String topicId, String question) throws IOException {
71
         McpClientProvider clientProvider = McpClientProvider.builder()
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
                 .build();
72
                 .build();
74
         ChatModel chatModel = ChatModel.of(llmServiceUrl)
73
         ChatModel chatModel = ChatModel.of(llmServiceUrl)
75
                 .model("Qwen2.5-1.5B-Instruct")
74
                 .model("Qwen2.5-1.5B-Instruct")
100
                 arguments.put("agentName", agentName);
99
                 arguments.put("agentName", agentName);
101
                 arguments.put("title", question);
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
             resultContent = clientProvider.callToolAsText(name, arguments).getContent();
102
             resultContent = clientProvider.callToolAsText(name, arguments).getContent();
110
             assistantMessage = new AssistantMessage(resultContent);
103
             assistantMessage = new AssistantMessage(resultContent);
111
         }
104
         }

+ 2
- 14
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/RagController.java View File

3
 import com.alibaba.fastjson2.JSONObject;
3
 import com.alibaba.fastjson2.JSONObject;
4
 import com.ruoyi.web.llm.service.ILangChainMilvusService;
4
 import com.ruoyi.web.llm.service.ILangChainMilvusService;
5
 import com.ruoyi.common.core.controller.BaseController;
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
 import org.noear.solon.ai.chat.message.AssistantMessage;
6
 import org.noear.solon.ai.chat.message.AssistantMessage;
11
 import org.springframework.beans.factory.annotation.Autowired;
7
 import org.springframework.beans.factory.annotation.Autowired;
12
 import org.springframework.web.bind.annotation.GetMapping;
8
 import org.springframework.web.bind.annotation.GetMapping;
29
     @Autowired
25
     @Autowired
30
     private ILangChainMilvusService langChainMilvusService;
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
      * 调用LLM+RAG(知识库)生成回答
29
      * 调用LLM+RAG(知识库)生成回答
42
      */
30
      */
43
     @GetMapping("/answer")
31
     @GetMapping("/answer")
44
     public Flux<AssistantMessage> answerWithCollection(String collectionName, String topicId, String question)
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
         return langChainMilvusService.generateAnswerWithCollection(topicId, question, contexts, "http://192.168.28.188:8000/v1/chat/completions");
35
         return langChainMilvusService.generateAnswerWithCollection(topicId, question, contexts, "http://192.168.28.188:8000/v1/chat/completions");
48
     }
36
     }
49
 
37
 
53
     @GetMapping("/context")
41
     @GetMapping("/context")
54
     public List<JSONObject> context(String question, String collectionName)
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 View File

2
 
2
 
3
 import com.ruoyi.common.core.controller.BaseController;
3
 import com.ruoyi.common.core.controller.BaseController;
4
 import com.ruoyi.web.llm.service.ILangChainMilvusService;
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
 import org.noear.solon.ai.chat.message.AssistantMessage;
5
 import org.noear.solon.ai.chat.message.AssistantMessage;
8
 import org.springframework.beans.factory.annotation.Autowired;
6
 import org.springframework.beans.factory.annotation.Autowired;
9
 import org.springframework.web.bind.annotation.GetMapping;
7
 import org.springframework.web.bind.annotation.GetMapping;
26
     @Autowired
24
     @Autowired
27
     private ILangChainMilvusService langChainMilvusService;
25
     private ILangChainMilvusService langChainMilvusService;
28
 
26
 
29
-    private static final EmbeddingModel embeddingModel = new BgeSmallZhV15EmbeddingModel();
30
-
31
     /**
27
     /**
32
      * 生成回答
28
      * 生成回答
33
      */
29
      */
42
     @GetMapping("/answerWithDocument")
38
     @GetMapping("/answerWithDocument")
43
     public Flux<AssistantMessage> answerWithDocument(String topicId, String chatId, String question) throws IOException
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 View File

1
 package com.ruoyi.web.llm.service;
1
 package com.ruoyi.web.llm.service;
2
 
2
 
3
 import com.alibaba.fastjson2.JSONObject;
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
 import org.noear.solon.ai.chat.message.AssistantMessage;
4
 import org.noear.solon.ai.chat.message.AssistantMessage;
9
 import org.springframework.web.multipart.MultipartFile;
5
 import org.springframework.web.multipart.MultipartFile;
10
 import reactor.core.publisher.Flux;
6
 import reactor.core.publisher.Flux;
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
      * 从Milvus检索相关文档
20
      * 从Milvus检索相关文档
24
      * @return
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
      * 从Milvus检索相关文档及相关度
26
      * 从Milvus检索相关文档及相关度
30
      * @return
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
      * 调用LLM生成回答
32
      * 调用LLM生成回答
47
      * 调用LLM+RAG(外部文件)生成回答
44
      * 调用LLM+RAG(外部文件)生成回答
48
      * @return
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
      * 调用LLM+RAG(外部文件+知识库)生成回答
50
      * 调用LLM+RAG(外部文件+知识库)生成回答
54
      * @return
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 View File

1
 package com.ruoyi.web.llm.service;
1
 package com.ruoyi.web.llm.service;
2
 
2
 
3
 import com.alibaba.fastjson2.JSONArray;
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
 import java.util.List;
5
 import java.util.List;
9
 
6
 
12
     /**
9
     /**
13
      * 新建知识库Collection(含Schema、Field、Index)
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
      * 查询知识库Collection
15
      * 查询知识库Collection
19
      */
16
      */
20
-    public JSONArray getCollectionNames(MilvusClient milvusClient);
17
+    public JSONArray getCollectionNames();
21
 
18
 
22
     /**
19
     /**
23
      * 修改知识库Collection
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
      * 删除知识库Collection
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 View File

1
 package com.ruoyi.web.llm.service.impl;
1
 package com.ruoyi.web.llm.service.impl;
2
 
2
 
3
 import com.alibaba.fastjson2.JSONObject;
3
 import com.alibaba.fastjson2.JSONObject;
4
+import com.google.gson.JsonObject;
5
+import com.google.gson.JsonParser;
4
 import com.ruoyi.common.config.RuoYiConfig;
6
 import com.ruoyi.common.config.RuoYiConfig;
5
 import com.ruoyi.llm.domain.CmcChat;
7
 import com.ruoyi.llm.domain.CmcChat;
6
 import com.ruoyi.llm.domain.CmcDocument;
8
 import com.ruoyi.llm.domain.CmcDocument;
14
 import dev.langchain4j.data.embedding.Embedding;
16
 import dev.langchain4j.data.embedding.Embedding;
15
 import dev.langchain4j.data.segment.TextSegment;
17
 import dev.langchain4j.data.segment.TextSegment;
16
 import dev.langchain4j.model.embedding.EmbeddingModel;
18
 import dev.langchain4j.model.embedding.EmbeddingModel;
19
+import dev.langchain4j.model.embedding.onnx.bgesmallzhv15.BgeSmallZhV15EmbeddingModel;
17
 import dev.langchain4j.store.embedding.EmbeddingMatch;
20
 import dev.langchain4j.store.embedding.EmbeddingMatch;
18
 import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
21
 import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
19
 import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
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
 import org.apache.poi.extractor.POITextExtractor;
34
 import org.apache.poi.extractor.POITextExtractor;
32
 import org.apache.poi.extractor.ExtractorFactory;
35
 import org.apache.poi.extractor.ExtractorFactory;
33
 import org.apache.poi.xwpf.usermodel.XWPFDocument;
36
 import org.apache.poi.xwpf.usermodel.XWPFDocument;
46
 
49
 
47
 import java.io.*;
50
 import java.io.*;
48
 import java.util.*;
51
 import java.util.*;
49
-import java.util.stream.Collectors;
50
 
52
 
51
 @Service
53
 @Service
52
 public class LangChainMilvusServiceImpl implements ILangChainMilvusService
54
 public class LangChainMilvusServiceImpl implements ILangChainMilvusService
57
     @Autowired
59
     @Autowired
58
     private ICmcDocumentService cmcDocumentService;
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
     @Override
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
         File profilePath = new File( RuoYiConfig.getProfile() + "/upload/rag/knowledge/" + collectionName);
76
         File profilePath = new File( RuoYiConfig.getProfile() + "/upload/rag/knowledge/" + collectionName);
67
         if (!profilePath.exists())
77
         if (!profilePath.exists())
71
             file.transferTo(transferFile);
81
             file.transferTo(transferFile);
72
         List<TextSegment> segments = splitDocument(transferFile);
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
         for (TextSegment segment : segments) {
88
         for (TextSegment segment : segments) {
81
             String text = segment.text();
89
             String text = segment.text();
82
             if (text.trim().isEmpty())
90
             if (text.trim().isEmpty())
83
                 continue;
91
                 continue;
84
-            fileNames.add(file.getOriginalFilename());
92
+
93
+            JSONObject fastjsonObj = new JSONObject();
94
+            fastjsonObj.put("file_name", file.getOriginalFilename());
85
             String[] fileName = file.getOriginalFilename().split("\\.");
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
                 .build();
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
      * @return
116
      * @return
110
      */
117
      */
111
     @Override
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
      * @return
133
      * @return
127
      */
134
      */
128
     @Override
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
      * 调用LLM生成回答
199
      * 调用LLM生成回答
194
      */
200
      */
195
     @Override
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
         CmcDocument cmcDocument = new CmcDocument();
203
         CmcDocument cmcDocument = new CmcDocument();
198
         cmcDocument.setChatId(chatId);
204
         cmcDocument.setChatId(chatId);
199
         List<CmcDocument> documentList = cmcDocumentService.selectCmcDocumentList(cmcDocument);
205
         List<CmcDocument> documentList = cmcDocumentService.selectCmcDocumentList(cmcDocument);
226
      * 调用LLM生成回答
232
      * 调用LLM生成回答
227
      */
233
      */
228
     @Override
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
         StringBuilder sb = new StringBuilder("招标文件内容:\n\n");
236
         StringBuilder sb = new StringBuilder("招标文件内容:\n\n");
231
         CmcChat cmcChat = new CmcChat();
237
         CmcChat cmcChat = new CmcChat();
232
         cmcChat.setTopicId(topicId);
238
         cmcChat.setTopicId(topicId);
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
                 .build();
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
         // 构建SearchParam
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
                 .build();
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
                 .build();
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 View File

3
 import com.alibaba.fastjson2.JSONArray;
3
 import com.alibaba.fastjson2.JSONArray;
4
 import com.alibaba.fastjson2.JSONObject;
4
 import com.alibaba.fastjson2.JSONObject;
5
 import com.ruoyi.web.llm.service.IMilvusService;
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
 import io.milvus.param.dml.DeleteParam;
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
 import org.springframework.stereotype.Service;
16
 import org.springframework.stereotype.Service;
18
 
17
 
19
 import java.text.SimpleDateFormat;
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
 import java.util.stream.Collectors;
20
 import java.util.stream.Collectors;
25
 
21
 
26
 @Service
22
 @Service
27
 public class MilvusServiceImpl implements IMilvusService {
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
      * 新建知识库Collection(含Schema、Field、Index)
31
      * 新建知识库Collection(含Schema、Field、Index)
31
      */
32
      */
32
     @Override
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
                 .build();
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
                 .build();
83
                 .build();
85
 
84
 
86
-        milvusClient.createIndex(createIndexParam);
87
-        return createResult;
85
+        milvusClient.createCollection(createCollectionParam);
88
     }
86
     }
89
 
87
 
90
     /**
88
     /**
91
      * 查询知识库Collection
89
      * 查询知识库Collection
92
      */
90
      */
93
-    public JSONArray getCollectionNames(MilvusClient milvusClient) {
91
+    public JSONArray getCollectionNames() {
94
         JSONArray jsonArray = new JSONArray();
92
         JSONArray jsonArray = new JSONArray();
95
-        ListCollectionsParam listParam = ListCollectionsParam.newBuilder().build();
96
-        ListCollectionsResponse listResponse = milvusClient.listCollections(listParam).getData();
93
+        ListCollectionsResp listResponse = milvusClient.listCollections();
97
         if (listResponse != null) {
94
         if (listResponse != null) {
98
-            List<String> collectionNames = milvusClient.listCollections(listParam).getData().collectionNames;
95
+            List<String> collectionNames = listResponse.getCollectionNames();
99
             for (String collectionName : collectionNames) {
96
             for (String collectionName : collectionNames) {
100
                 JSONObject jsonObject = new JSONObject();
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
                 jsonObject.put("collectionId", describeResponse.getCollectionID());
102
                 jsonObject.put("collectionId", describeResponse.getCollectionID());
106
                 jsonObject.put("collectionName", collectionName);
103
                 jsonObject.put("collectionName", collectionName);
107
                 SimpleDateFormat beijingFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
104
                 SimpleDateFormat beijingFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
108
                 beijingFormat.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
105
                 beijingFormat.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
109
-                String beijingTime = beijingFormat.format(describeResponse.getCreatedUtcTimestamp());
106
+                String beijingTime = beijingFormat.format(describeResponse.getCreateUtcTime());
110
                 jsonObject.put("createdTime", beijingTime);
107
                 jsonObject.put("createdTime", beijingTime);
111
-                jsonObject.put("description", describeResponse.getSchema().getDescription());
108
+                jsonObject.put("description", describeResponse.getDescription());
112
                 jsonArray.add(jsonObject);
109
                 jsonArray.add(jsonObject);
113
             }
110
             }
114
         }
111
         }
118
     /**
115
     /**
119
      * 修改知识库Collection
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
                 .build();
122
                 .build();
126
 
123
 
127
         milvusClient.renameCollection(renameCollectionReq);
124
         milvusClient.renameCollection(renameCollectionReq);
130
     /**
127
     /**
131
      * 删除知识库Collection
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
                 .build();
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
         List<String> documentList = new ArrayList<>();
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
                 .build();
148
                 .build();
152
         if (fileType != null && !fileType.equals(""))
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
                 .build();
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
         return documentList.stream().distinct().collect(Collectors.toList());
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
         DeleteParam deleteParam = DeleteParam.newBuilder()
173
         DeleteParam deleteParam = DeleteParam.newBuilder()
177
                 .withCollectionName(collectionName)
174
                 .withCollectionName(collectionName)
178
                 .withExpr(String.format("file_name == \"%s\"", fileName))
175
                 .withExpr(String.format("file_name == \"%s\"", fileName))
179
                 .build();
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
         DeleteParam deleteParam = DeleteParam.newBuilder()
185
         DeleteParam deleteParam = DeleteParam.newBuilder()
189
                 .withCollectionName(collectionName)
186
                 .withCollectionName(collectionName)
190
                 .withExpr("id > 0")
187
                 .withExpr("id > 0")
191
                 .build();
188
                 .build();
192
-        milvusClient.delete(deleteParam);
189
+//        milvusClient.delete(deleteParam);
193
     }
190
     }
194
 
191
 
195
     /**
192
     /**
196
      * 加载知识库Collection
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
                 .build();
198
                 .build();
202
-        milvusClient.loadCollection(param);
199
+        milvusClient.loadCollection(loadCollectionReq);
203
     }
200
     }
204
 
201
 
205
     /**
202
     /**
206
      * 释放知识库Collection
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
                 .build();
208
                 .build();
212
-        milvusClient.releaseCollection(param);
209
+        milvusClient.releaseCollection(releaseCollectionReq);
213
     }
210
     }
214
 
211
 
215
 }
212
 }

+ 2
- 2
llm-back/ruoyi-system/pom.xml View File

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

+ 8
- 0
llm-back/ruoyi-system/src/main/java/com/ruoyi/llm/service/ICmcAgentService.java View File

47
      */
47
      */
48
     public JSONObject uploadDocument(MultipartFile file, String agentName) throws IOException;
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 View File

5
 import java.nio.file.Path;
5
 import java.nio.file.Path;
6
 import java.nio.file.Paths;
6
 import java.nio.file.Paths;
7
 import java.util.*;
7
 import java.util.*;
8
-import java.util.stream.Collectors;
9
 
8
 
10
 import com.alibaba.fastjson2.JSONObject;
9
 import com.alibaba.fastjson2.JSONObject;
11
 import com.ruoyi.common.config.RuoYiConfig;
10
 import com.ruoyi.common.config.RuoYiConfig;
27
 import dev.langchain4j.store.embedding.EmbeddingMatch;
26
 import dev.langchain4j.store.embedding.EmbeddingMatch;
28
 import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
27
 import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
29
 import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
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
 import org.apache.poi.extractor.ExtractorFactory;
38
 import org.apache.poi.extractor.ExtractorFactory;
41
 import org.apache.poi.extractor.POITextExtractor;
39
 import org.apache.poi.extractor.POITextExtractor;
42
 import org.apache.poi.xwpf.usermodel.BreakType;
40
 import org.apache.poi.xwpf.usermodel.BreakType;
43
 import org.apache.poi.xwpf.usermodel.XWPFDocument;
41
 import org.apache.poi.xwpf.usermodel.XWPFDocument;
44
 import org.apache.poi.xwpf.usermodel.XWPFParagraph;
42
 import org.apache.poi.xwpf.usermodel.XWPFParagraph;
45
 import org.apache.poi.xwpf.usermodel.XWPFRun;
43
 import org.apache.poi.xwpf.usermodel.XWPFRun;
44
+import org.apache.xmlbeans.XmlCursor;
45
+import org.noear.solon.Solon;
46
 import org.noear.solon.ai.chat.ChatModel;
46
 import org.noear.solon.ai.chat.ChatModel;
47
 import org.noear.solon.ai.chat.ChatResponse;
47
 import org.noear.solon.ai.chat.ChatResponse;
48
 import org.noear.solon.ai.chat.ChatSession;
48
 import org.noear.solon.ai.chat.ChatSession;
57
 
57
 
58
 /**
58
 /**
59
  * 智能体Service业务层处理
59
  * 智能体Service业务层处理
60
- * 
60
+ *
61
  * @author ruoyi
61
  * @author ruoyi
62
  * @date 2025-07-17
62
  * @date 2025-07-17
63
  */
63
  */
64
 @Service
64
 @Service
65
-public class CmcAgentServiceImpl implements ICmcAgentService 
65
+public class CmcAgentServiceImpl implements ICmcAgentService
66
 {
66
 {
67
     @Autowired
67
     @Autowired
68
     private CmcAgentMapper cmcAgentMapper;
68
     private CmcAgentMapper cmcAgentMapper;
79
 
79
 
80
     private static final String llmServiceUrl = "http://192.168.28.188:8000/v1/chat/completions";
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
                     .build());
85
                     .build());
87
 
86
 
88
     /**
87
     /**
89
      * 查询智能体
88
      * 查询智能体
90
-     * 
89
+     *
91
      * @param agentId 智能体主键
90
      * @param agentId 智能体主键
92
      * @return 智能体
91
      * @return 智能体
93
      */
92
      */
99
 
98
 
100
     /**
99
     /**
101
      * 查询智能体列表
100
      * 查询智能体列表
102
-     * 
101
+     *
103
      * @param cmcAgent 智能体
102
      * @param cmcAgent 智能体
104
      * @return 智能体
103
      * @return 智能体
105
      */
104
      */
173
                     RuoYiConfig.getProfile() + outputFilename, "工作大纲");
172
                     RuoYiConfig.getProfile() + outputFilename, "工作大纲");
174
             message = "好的,我已经收到您上传的招标文件。\n\n"+ chapters + "\n\n" +
173
             message = "好的,我已经收到您上传的招标文件。\n\n"+ chapters + "\n\n" +
175
                     "若您对章节标题有异议,请打开" + "【<a href='/profile" + outputFilename + "'> 技术文件 " + "</a>】" + "进行修改,后续将根据修改后的章节标题,帮您生成对应章节内容。\n\n" +
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
         jsonObject.put("assistantMessage", message);
228
         jsonObject.put("assistantMessage", message);
181
         return jsonObject;
229
         return jsonObject;
211
 
259
 
212
     /**
260
     /**
213
      * 新增智能体
261
      * 新增智能体
214
-     * 
262
+     *
215
      * @param cmcAgent 智能体
263
      * @param cmcAgent 智能体
216
      * @return 结果
264
      * @return 结果
217
      */
265
      */
224
 
272
 
225
     /**
273
     /**
226
      * 修改智能体
274
      * 修改智能体
227
-     * 
275
+     *
228
      * @param cmcAgent 智能体
276
      * @param cmcAgent 智能体
229
      * @return 结果
277
      * @return 结果
230
      */
278
      */
236
 
284
 
237
     /**
285
     /**
238
      * 批量删除智能体
286
      * 批量删除智能体
239
-     * 
287
+     *
240
      * @param agentIds 需要删除的智能体主键
288
      * @param agentIds 需要删除的智能体主键
241
      * @return 结果
289
      * @return 结果
242
      */
290
      */
248
 
296
 
249
     /**
297
     /**
250
      * 删除智能体信息
298
      * 删除智能体信息
251
-     * 
299
+     *
252
      * @param agentId 智能体主键
300
      * @param agentId 智能体主键
253
      * @return 结果
301
      * @return 结果
254
      */
302
      */
258
         return cmcAgentMapper.deleteCmcAgentByAgentId(agentId);
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
      * 调用LLM生成回答
514
      * 调用LLM生成回答
281
             sb.append(requests).append("\n\n");
532
             sb.append(requests).append("\n\n");
282
         }
533
         }
283
         //根据招标文件工作大纲要求生成二级标题
534
         //根据招标文件工作大纲要求生成二级标题
284
-        sb.append("请根据上述招标文件内容,严格按以下格式列出").append(question).append(":\n")
535
+        sb.append("请基于上述招标文件中提到的工作大纲内容,先列出二级章节标题,严格按以下格式,仅输出标题列表:\n")
285
                 .append("6.1 XX\n" +
536
                 .append("6.1 XX\n" +
286
                 "6.2 XX\n" +
537
                 "6.2 XX\n" +
287
                 "6.3 XX");
538
                 "6.3 XX");
306
         for (String chapter2 : chapter2List) {
557
         for (String chapter2 : chapter2List) {
307
             String orderNum = chapter2.split(" ")[0];
558
             String orderNum = chapter2.split(" ")[0];
308
             StringBuilder sb = new StringBuilder("二级标题如下:\n").append(chapter2)
559
             StringBuilder sb = new StringBuilder("二级标题如下:\n").append(chapter2)
309
-                    .append("\n 请根据参考内容编排三级标题,严格按以下格式列出:\n")
560
+                    .append("\n 请以上述二级标题作为检索词,到技术知识库中检索最相似的片段,结合检索结果编排该二级标题下的三级标题,严格按以下格式,仅输出三级标题列表:\n")
310
                     .append(orderNum).append(".1 XX\n")
561
                     .append(orderNum).append(".1 XX\n")
311
                     .append(orderNum).append(".2 XX\n")
562
                     .append(orderNum).append(".2 XX\n")
312
                     .append(orderNum).append(".3 XX\n");
563
                     .append(orderNum).append(".3 XX\n");
321
         }
572
         }
322
         String sb = "二级标题如下:\n" + chapters2 +
573
         String sb = "二级标题如下:\n" + chapters2 +
323
                 "\n 三级标题如下:" + chapter3 +
574
                 "\n 三级标题如下:" + chapter3 +
324
-                "帮我合并三级标题,严格按以下格式列出:\n" +
575
+                "请将上述二级与对应的三级标题进行合并,严格按以下格式输出完整大纲,仅输出标题:\n" +
325
                 "6 技术文件\n" +
576
                 "6 技术文件\n" +
326
                 "6.1 XX\n" +
577
                 "6.1 XX\n" +
327
                 "6.1.1 XX\n" +
578
                 "6.1.1 XX\n" +
329
                 "6.2 XX\n" +
580
                 "6.2 XX\n" +
330
                 "6.2.1 XX";
581
                 "6.2.1 XX";
331
         String content = generateAnswer(sb);
582
         String content = generateAnswer(sb);
332
-        writeContent(content, templatePath);
583
+        writeTitles(content, templatePath);
333
         return content;
584
         return content;
334
     }
585
     }
335
 
586
 
355
      * @return
606
      * @return
356
      */
607
      */
357
     public List<JSONObject> retrieveFromMilvus(String collectionName, String query, int topK) {
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
                 .build();
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
         // 构建SearchParam
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
                 .build();
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
                 .build();
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
      * @return
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
         List<String> chapters = new ArrayList<>();
661
         List<String> chapters = new ArrayList<>();
414
         String[] contentLines = content.split("\n");
662
         String[] contentLines = content.split("\n");
415
         for (String line : contentLines) {
663
         for (String line : contentLines) {
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 View File

1
 /*
1
 /*
2
  * @Author: wrh
2
  * @Author: wrh
3
  * @Date: 2025-07-17 18:06:24
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
 import request from '@/utils/request'
7
 import request from '@/utils/request'
8
 
8
 
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
 export function uploadFileList(fileList, agentName) {
83
 export function uploadFileList(fileList, agentName) {
69
   const formData = new FormData()
84
   const formData = new FormData()

+ 120
- 4
llm-ui/src/views/llm/agent/AgentDetail.vue View File

2
  * @Author: wrh
2
  * @Author: wrh
3
  * @Date: 2025-01-01 00:00:00
3
  * @Date: 2025-01-01 00:00:00
4
  * @LastEditors: wrh
4
  * @LastEditors: wrh
5
- * @LastEditTime: 2025-08-15 17:13:15
5
+ * @LastEditTime: 2025-09-11 15:58:35
6
 -->
6
 -->
7
 <template>
7
 <template>
8
   <div class="agent-detail-container" v-loading="loading">
8
   <div class="agent-detail-container" v-loading="loading">
89
                       <el-icon>
89
                       <el-icon>
90
                         <Upload />
90
                         <Upload />
91
                       </el-icon>
91
                       </el-icon>
92
-                      选择文件
92
+                      选择招标文件
93
                     </el-button>
93
                     </el-button>
94
                   </el-upload>
94
                   </el-upload>
95
                   <div v-if="chatFileList.length > 0" class="chat-upload-actions">
95
                   <div v-if="chatFileList.length > 0" class="chat-upload-actions">
138
               </div>
138
               </div>
139
             </div>
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
             <div v-if="isTyping" class="message-item assistant-message">
177
             <div v-if="isTyping" class="message-item assistant-message">
143
               <div class="message-avatar">
178
               <div class="message-avatar">
180
 import { ref, reactive, watch, nextTick, computed, getCurrentInstance } from 'vue';
215
 import { ref, reactive, watch, nextTick, computed, getCurrentInstance } from 'vue';
181
 import { ElMessage, ElMessageBox } from 'element-plus';
216
 import { ElMessage, ElMessageBox } from 'element-plus';
182
 import { Delete, Refresh } from '@element-plus/icons-vue';
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
 import { answer } from '@/api/llm/mcp';
219
 import { answer } from '@/api/llm/mcp';
185
 import { listTopic, getTopic, delTopic, addTopic, updateTopic } from "@/api/llm/topic";
220
 import { listTopic, getTopic, delTopic, addTopic, updateTopic } from "@/api/llm/topic";
186
 import { listChat, addChat, updateChat } from "@/api/llm/chat";
221
 import { listChat, addChat, updateChat } from "@/api/llm/chat";
213
 const percentage = ref(0);
248
 const percentage = ref(0);
214
 const progress = ref("");
249
 const progress = ref("");
215
 const percentageDisplay = ref(false);
250
 const percentageDisplay = ref(false);
251
+const techUploadDisplay = ref(false)
216
 
252
 
217
 // 聊天内文件上传相关
253
 // 聊天内文件上传相关
218
 const chatFileList = ref([]) // 聊天内的文件列表
254
 const chatFileList = ref([]) // 聊天内的文件列表
268
   if (res.rows.length > 0) {
304
   if (res.rows.length > 0) {
269
     topicList.value = res.rows
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
           isHtml: true // 标记这是HTML内容
609
           isHtml: true // 标记这是HTML内容
570
         }
610
         }
571
         chatMessages.value.push(uploadMessage);
611
         chatMessages.value.push(uploadMessage);
612
+        techUploadDisplay.value = true;
572
         // ElMessage.success('文件上传成功');
613
         // ElMessage.success('文件上传成功');
573
         let topicRes = await addTopic({ agentId: props.agentId, topic: fileName });
614
         let topicRes = await addTopic({ agentId: props.agentId, topic: fileName });
574
         const topicId = topicRes.msg;
615
         const topicId = topicRes.msg;
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
 const startNewChat = () => {
739
 const startNewChat = () => {
625
   currentTopicId.value = null
740
   currentTopicId.value = null
1192
     margin-top: 16px;
1307
     margin-top: 16px;
1193
     font-size: 14px;
1308
     font-size: 14px;
1194
   }
1309
   }
1195
-}</style>
1310
+}
1311
+</style>

+ 1
- 1
llm-ui/src/views/llm/knowledge/index.vue View File

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

Loading…
Cancel
Save