瀏覽代碼

自动获取mcp工具撰写技术标书文档内容

lamphua 2 天之前
父節點
當前提交
b57fb1986a

+ 28
- 1
llm-back/ruoyi-agent/pom.xml 查看文件

54
             <groupId>org.apache.poi</groupId>
54
             <groupId>org.apache.poi</groupId>
55
             <artifactId>poi-ooxml</artifactId>
55
             <artifactId>poi-ooxml</artifactId>
56
         </dependency>
56
         </dependency>
57
-
57
+        <dependency>
58
+            <groupId>com.alibaba.fastjson2</groupId>
59
+            <artifactId>fastjson2</artifactId>
60
+        </dependency>
61
+        <dependency>
62
+            <groupId>dev.langchain4j</groupId>
63
+            <artifactId>langchain4j-embeddings-bge-small-zh-v15</artifactId>
64
+            <version>0.35.0</version>
65
+            <scope>compile</scope>
66
+        </dependency>
67
+        <dependency>
68
+            <groupId>io.milvus</groupId>
69
+            <artifactId>milvus-sdk-java</artifactId>
70
+            <version>2.3.3</version>
71
+            <scope>compile</scope>
72
+        </dependency>
73
+        <dependency>
74
+            <groupId>dev.langchain4j</groupId>
75
+            <artifactId>langchain4j</artifactId>
76
+            <version>0.35.0</version>
77
+            <scope>compile</scope>
78
+        </dependency>
79
+        <dependency>
80
+            <groupId>dev.langchain4j</groupId>
81
+            <artifactId>langchain4j-document-parser-apache-pdfbox</artifactId>
82
+            <version>0.35.0</version>
83
+            <scope>compile</scope>
84
+        </dependency>
58
     </dependencies>
85
     </dependencies>
59
 
86
 
60
     <dependencyManagement>
87
     <dependencyManagement>

+ 1
- 1
llm-back/ruoyi-agent/src/main/java/com/ruoyi/agent/service/McpServerConfig.java 查看文件

24
 public class McpServerConfig {
24
 public class McpServerConfig {
25
     @PostConstruct
25
     @PostConstruct
26
     public void start() {
26
     public void start() {
27
-        Solon.start(McpServerConfig.class, new String[]{});
27
+        Solon.start(McpServerConfig.class, new String[]{"--cfg=application.yml"});
28
     }
28
     }
29
 
29
 
30
     @PreDestroy
30
     @PreDestroy

+ 218
- 6
llm-back/ruoyi-agent/src/main/java/com/ruoyi/agent/service/impl/McpServiceImpl.java 查看文件

1
 package com.ruoyi.agent.service.impl;
1
 package com.ruoyi.agent.service.impl;
2
 
2
 
3
+import com.alibaba.fastjson2.JSONObject;
3
 import com.ruoyi.agent.service.IMcpService;
4
 import com.ruoyi.agent.service.IMcpService;
5
+import dev.langchain4j.data.document.Document;
6
+import dev.langchain4j.data.document.parser.apache.pdfbox.ApachePdfBoxDocumentParser;
7
+import dev.langchain4j.data.document.splitter.DocumentByParagraphSplitter;
8
+import dev.langchain4j.data.embedding.Embedding;
9
+import dev.langchain4j.data.segment.TextSegment;
10
+import dev.langchain4j.model.embedding.EmbeddingModel;
11
+import dev.langchain4j.model.embedding.onnx.bgesmallzhv15.BgeSmallZhV15EmbeddingModel;
12
+import dev.langchain4j.store.embedding.EmbeddingMatch;
13
+import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
14
+import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
15
+import io.milvus.client.MilvusClient;
16
+import io.milvus.client.MilvusServiceClient;
17
+import io.milvus.grpc.SearchResults;
18
+import io.milvus.param.ConnectParam;
19
+import io.milvus.param.MetricType;
20
+import io.milvus.param.R;
21
+import io.milvus.param.RpcStatus;
22
+import io.milvus.param.collection.LoadCollectionParam;
23
+import io.milvus.param.collection.ReleaseCollectionParam;
24
+import io.milvus.param.dml.SearchParam;
25
+import io.milvus.response.SearchResultsWrapper;
26
+import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
4
 import org.apache.poi.xwpf.usermodel.XWPFDocument;
27
 import org.apache.poi.xwpf.usermodel.XWPFDocument;
5
 import org.apache.poi.xwpf.usermodel.XWPFParagraph;
28
 import org.apache.poi.xwpf.usermodel.XWPFParagraph;
29
+import org.noear.solon.Solon;
6
 import org.noear.solon.ai.annotation.PromptMapping;
30
 import org.noear.solon.ai.annotation.PromptMapping;
7
-import org.noear.solon.ai.annotation.ResourceMapping;
8
 import org.noear.solon.ai.annotation.ToolMapping;
31
 import org.noear.solon.ai.annotation.ToolMapping;
32
+import org.noear.solon.ai.chat.ChatModel;
33
+import org.noear.solon.ai.chat.ChatResponse;
34
+import org.noear.solon.ai.chat.ChatSession;
35
+import org.noear.solon.ai.chat.ChatSessionDefault;
36
+import org.noear.solon.ai.chat.message.AssistantMessage;
9
 import org.noear.solon.ai.chat.message.ChatMessage;
37
 import org.noear.solon.ai.chat.message.ChatMessage;
10
 import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint;
38
 import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint;
11
 import org.noear.solon.annotation.Param;
39
 import org.noear.solon.annotation.Param;
40
+import org.reactivestreams.Publisher;
12
 import org.springframework.stereotype.Service;
41
 import org.springframework.stereotype.Service;
42
+import reactor.core.publisher.Flux;
13
 
43
 
14
-import java.io.FileInputStream;
15
-import java.io.FileOutputStream;
16
-import java.io.IOException;
17
-import java.util.Collection;
18
-import java.util.Collections;
44
+import java.io.*;
45
+import java.util.*;
46
+import java.util.stream.Collectors;
19
 
47
 
20
 @Service
48
 @Service
21
 @McpServerEndpoint(sseEndpoint = "/llm/mcp/sse")
49
 @McpServerEndpoint(sseEndpoint = "/llm/mcp/sse")
22
 public class McpServiceImpl implements IMcpService {
50
 public class McpServiceImpl implements IMcpService {
23
 
51
 
52
+    private static final EmbeddingModel embeddingModel = new BgeSmallZhV15EmbeddingModel();
53
+
54
+    private static final MilvusServiceClient milvusClient = new MilvusServiceClient(
55
+            ConnectParam.newBuilder()
56
+                    .withHost("192.168.28.188")
57
+                    .withPort(19530)
58
+                    .build());
59
+    /**
60
+     * 调用LLM+RAG(外部文件+知识库)生成回答
61
+     */
62
+    @ToolMapping(description = "章节撰写")
63
+    public AssistantMessage writeParagraph(@Param(description = "智能体名称") String collectionName,
64
+                                                 @Param(description = "章节名称") String title,
65
+                                                 @Param(description = "招标文件地址") String templatePath) throws IOException
66
+    {
67
+        title = String.join(",", extractSubTitles( "/upload/agent/template/technical.docx", title));
68
+        List<JSONObject> requests = retrieveFromMilvus(milvusClient, embeddingModel, collectionName, title, 10);
69
+        return generateAnswerWithDocumentAndCollection(embeddingModel, templatePath, title, requests, "http://192.168.28.188:8000/v1/chat/completions");
70
+    }
71
+
24
     @ToolMapping(description = "梳理招标文件服务要求")
72
     @ToolMapping(description = "梳理招标文件服务要求")
25
     public String getBidRequest(@Param(description = "文件路径") String document) throws IOException {
73
     public String getBidRequest(@Param(description = "文件路径") String document) throws IOException {
26
         try (XWPFDocument doc = new XWPFDocument(new FileInputStream(document))) {
74
         try (XWPFDocument doc = new XWPFDocument(new FileInputStream(document))) {
66
                         "回答以下问题:'" + part + "\n")
114
                         "回答以下问题:'" + part + "\n")
67
         );
115
         );
68
     }
116
     }
117
+
118
+    /**
119
+     * 从Milvus检索相关文档
120
+     * @return
121
+     */
122
+    public List<JSONObject> retrieveFromMilvus(MilvusClient milvusClient, EmbeddingModel embeddingModel, String collectionName, String query, int topK) {
123
+        SearchResultsWrapper wrapper = retrieve(milvusClient, embeddingModel, collectionName, query, topK);
124
+        return wrapper.getRowRecords(0).stream()
125
+                .map(record -> {
126
+                    JSONObject result = new JSONObject();
127
+                    result.put("file_name", record.get("file_name"));
128
+                    result.put("content", record.get("content"));
129
+                    return result;
130
+                })
131
+                .collect(Collectors.toList());
132
+    }
133
+
134
+    /**
135
+     * 调用LLM生成回答
136
+     */
137
+    public AssistantMessage generateAnswerWithDocumentAndCollection(EmbeddingModel embeddingModel, String templatePath, String question, List<JSONObject> contexts, String llmServiceUrl) throws IOException {
138
+        StringBuilder sb = new StringBuilder("招标文件内容:\n\n");
139
+        File profilePath = new File(Solon.cfg().getProperty("cmc.profile") + templatePath);
140
+        List<TextSegment> segments = splitDocument(profilePath);
141
+        List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
142
+        InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
143
+        embeddingStore.addAll(embeddings, segments);
144
+        Embedding queryEmbedding = embeddingModel.embed(question).content();
145
+        EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
146
+                .queryEmbedding(queryEmbedding)
147
+                .maxResults(3)
148
+                .build();
149
+        for (EmbeddingMatch embeddingMatch : embeddingStore.search(embeddingSearchRequest).matches()) {
150
+            String requests = embeddingMatch.embedded().toString();
151
+            sb.append(requests).append("\n\n");
152
+
153
+        }
154
+        sb.append("针对本项目招标文件内容,补全以下章节部分:\n\n").append(question);
155
+//        for (JSONObject context : contexts) {
156
+//            sb.append("文件").append(": ")
157
+//                    .append(context.getString("file_name")).append("\n\n")
158
+//                    .append("段落格式").append(": ")
159
+//                    .append(context.getString("content")).append("\n\n");
160
+//        }
161
+        return generateAnswer(sb.toString(), llmServiceUrl);
162
+    }
163
+
164
+    /**
165
+     * 调用LLM生成回答
166
+     * @return
167
+     */
168
+    public AssistantMessage generateAnswer(String prompt, String llmServiceUrl) throws IOException {
169
+        ChatSession chatSession = new ChatSessionDefault();
170
+        ChatModel chatModel = ChatModel.of(llmServiceUrl)
171
+                .provider("openai")
172
+                .model("Qwen2.5-1.5B-Instruct")
173
+                .build();
174
+
175
+        chatSession.addMessage(ChatMessage.ofUser(prompt));
176
+
177
+        ChatResponse response = chatModel.prompt(chatSession).call();
178
+        return response.lastChoice().getMessage();
179
+    }
180
+
181
+    /**
182
+     * 获取二级标题下三级标题列表
183
+     */
184
+    public List<String> extractSubTitles(String filename, String question) throws IOException {
185
+        List<String> subTitles = new ArrayList<>();
186
+        boolean inTargetSection = false;
187
+        filename = Solon.cfg().getProperty("cmc.profile") + filename;
188
+        InputStream fileInputStream = new FileInputStream(filename);
189
+        XWPFDocument document = new XWPFDocument(fileInputStream);
190
+        for (XWPFParagraph paragraph : document.getParagraphs()) {
191
+            String text = paragraph.getText().trim();
192
+            if (paragraph.getStyle() != null) {
193
+                // 判断主标题
194
+                if (paragraph.getStyle().equals("3") &&
195
+                        text.contains(question)) {
196
+                    inTargetSection = true;
197
+                    continue;
198
+                }
199
+
200
+                // 如果已经在目标节中,收集标题3级别的子标题
201
+                if (inTargetSection) {
202
+                    if (paragraph.getStyle().equals("4")) {
203
+                        subTitles.add(text);
204
+                    }
205
+                    // 遇到下一个Heading1则退出
206
+                    else if (paragraph.getStyle().equals("3")) {
207
+                        break;
208
+                    }
209
+                }
210
+            }
211
+        }
212
+        if (subTitles.size() == 0)
213
+            subTitles.add(question);
214
+        return subTitles;
215
+    }
216
+
217
+    /**
218
+     * 检索知识库
219
+     */
220
+    private SearchResultsWrapper retrieve(MilvusClient milvusClient, EmbeddingModel embeddingModel, String collectionName, String query, int topK) {
221
+        List<List<Float>> queryVector = Collections.singletonList(embeddingModel.embed(query).content().vectorAsList());
222
+
223
+        //  加载集合
224
+        LoadCollectionParam loadParam = LoadCollectionParam.newBuilder()
225
+                .withCollectionName(collectionName)
226
+                .build();
227
+
228
+        R<RpcStatus> loadResponse = milvusClient.loadCollection(loadParam);
229
+        if (loadResponse.getStatus() != R.Status.Success.getCode()) {
230
+            System.err.println("加载Collection失败: " + loadResponse.getMessage());
231
+            milvusClient.close();
232
+        }
233
+
234
+        // 构建SearchParam
235
+        SearchParam searchParam = SearchParam.newBuilder()
236
+                .withCollectionName(collectionName)
237
+                .withVectors(queryVector)
238
+                .withTopK(topK)
239
+                .withOutFields(Arrays.asList("file_name", "file_type", "content"))
240
+                .withVectorFieldName("embedding")
241
+                .withMetricType(MetricType.COSINE)
242
+                .withParams("{\"nprobe\": 8}")
243
+                .build();
244
+
245
+        R<SearchResults> response = milvusClient.search(searchParam);
246
+        SearchResultsWrapper wrapper = new SearchResultsWrapper(response.getData().getResults());
247
+
248
+        // 释放集合
249
+        ReleaseCollectionParam param = ReleaseCollectionParam.newBuilder()
250
+                .withCollectionName(collectionName)
251
+                .build();
252
+        milvusClient.releaseCollection(param);
253
+
254
+        return wrapper;
255
+    }
256
+
257
+    /**
258
+     * 检索知识库
259
+     */
260
+    private List<TextSegment> splitDocument(File transferFile) throws IOException {
261
+        // 加载文档
262
+        Document document;
263
+        InputStream fileInputStream = new FileInputStream(transferFile);
264
+        String filename = transferFile.getName().toLowerCase();
265
+        if (filename.endsWith(".docx")) {
266
+            XWPFDocument docx = new XWPFDocument(fileInputStream);
267
+            XWPFWordExtractor extractor = new XWPFWordExtractor(docx);
268
+            String text = extractor.getText();
269
+            document = Document.from(text);
270
+        }
271
+        else if (filename.endsWith(".pdf")) {
272
+            document = new ApachePdfBoxDocumentParser().parse(fileInputStream);
273
+        }
274
+        else {
275
+            throw new UnsupportedOperationException("不支持文件类型: " + filename);
276
+        }
277
+        DocumentByParagraphSplitter splitter = new DocumentByParagraphSplitter(1000,200);
278
+        return splitter.split(document);
279
+    }
280
+
69
 }
281
 }

+ 5
- 0
llm-back/ruoyi-agent/src/main/resources/application.yml 查看文件

1
+# 项目相关配置
2
+cmc:
3
+  # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
4
+  #  profile: /home/cmc/projects/cmc-llm
5
+  profile: E:/home/cmc/projects/cmc-llm

+ 28
- 0
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/CmcTopicController.java 查看文件

4
 import javax.servlet.http.HttpServletResponse;
4
 import javax.servlet.http.HttpServletResponse;
5
 
5
 
6
 import com.ruoyi.common.utils.SnowFlake;
6
 import com.ruoyi.common.utils.SnowFlake;
7
+import com.ruoyi.llm.domain.CmcChat;
8
+import com.ruoyi.llm.domain.CmcDocument;
9
+import com.ruoyi.llm.service.ICmcChatService;
10
+import com.ruoyi.llm.service.ICmcDocumentService;
7
 import org.springframework.beans.factory.annotation.Autowired;
11
 import org.springframework.beans.factory.annotation.Autowired;
8
 import org.springframework.web.bind.annotation.GetMapping;
12
 import org.springframework.web.bind.annotation.GetMapping;
9
 import org.springframework.web.bind.annotation.PostMapping;
13
 import org.springframework.web.bind.annotation.PostMapping;
35
     @Autowired
39
     @Autowired
36
     private ICmcTopicService cmcTopicService;
40
     private ICmcTopicService cmcTopicService;
37
 
41
 
42
+    @Autowired
43
+    private ICmcChatService cmcChatService;
44
+
45
+    @Autowired
46
+    private ICmcDocumentService cmcDocumentService;
47
+
38
     /**
48
     /**
39
      * 查询cmc聊天主题列表
49
      * 查询cmc聊天主题列表
40
      */
50
      */
96
 	@DeleteMapping("/{topicIds}")
106
 	@DeleteMapping("/{topicIds}")
97
     public AjaxResult remove(@PathVariable String[] topicIds)
107
     public AjaxResult remove(@PathVariable String[] topicIds)
98
     {
108
     {
109
+        for (String topicId : topicIds) {
110
+            CmcChat cmcChat = new CmcChat();
111
+            cmcChat.setTopicId(topicId);
112
+            List<CmcChat> cmcChatList = cmcChatService.selectCmcChatList(cmcChat);
113
+            String[] chatIds = new String[cmcChatList.size()];
114
+            for (int i = 0; i < cmcChatList.size(); i++)
115
+                chatIds[i] = cmcChatList.get(i).getChatId();
116
+            cmcChatService.deleteCmcChatByChatIds(chatIds);
117
+            for (String chatId : chatIds) {
118
+                CmcDocument cmcDocument = new CmcDocument();
119
+                cmcDocument.setChatId(chatId);
120
+                List<CmcDocument> cmcDocumentList = cmcDocumentService.selectCmcDocumentList(cmcDocument);
121
+                String[] documentIds = new String[cmcDocumentList.size()];
122
+                for (int i = 0; i < cmcDocumentList.size(); i++)
123
+                    documentIds[i] = cmcDocumentList.get(i).getDocumentId();
124
+                cmcChatService.deleteCmcChatByChatIds(documentIds);
125
+            }
126
+        }
99
         return success(cmcTopicService.deleteCmcTopicByTopicIds(topicIds));
127
         return success(cmcTopicService.deleteCmcTopicByTopicIds(topicIds));
100
     }
128
     }
101
 }
129
 }

+ 58
- 8
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/McpController.java 查看文件

3
 import com.alibaba.fastjson2.JSONObject;
3
 import com.alibaba.fastjson2.JSONObject;
4
 import com.ruoyi.common.config.RuoYiConfig;
4
 import com.ruoyi.common.config.RuoYiConfig;
5
 import com.ruoyi.common.core.controller.BaseController;
5
 import com.ruoyi.common.core.controller.BaseController;
6
+import com.ruoyi.llm.domain.CmcChat;
7
+import com.ruoyi.llm.service.ICmcChatService;
6
 import com.ruoyi.web.llm.service.ILangChainMilvusService;
8
 import com.ruoyi.web.llm.service.ILangChainMilvusService;
7
 import dev.langchain4j.model.embedding.EmbeddingModel;
9
 import dev.langchain4j.model.embedding.EmbeddingModel;
8
 import dev.langchain4j.model.embedding.onnx.bgesmallzhv15.BgeSmallZhV15EmbeddingModel;
10
 import dev.langchain4j.model.embedding.onnx.bgesmallzhv15.BgeSmallZhV15EmbeddingModel;
10
 import io.milvus.param.ConnectParam;
12
 import io.milvus.param.ConnectParam;
11
 import org.noear.solon.ai.chat.ChatModel;
13
 import org.noear.solon.ai.chat.ChatModel;
12
 import org.noear.solon.ai.chat.ChatResponse;
14
 import org.noear.solon.ai.chat.ChatResponse;
15
+import org.noear.solon.ai.chat.ChatSession;
16
+import org.noear.solon.ai.chat.ChatSessionDefault;
13
 import org.noear.solon.ai.chat.message.AssistantMessage;
17
 import org.noear.solon.ai.chat.message.AssistantMessage;
14
 import org.noear.solon.ai.chat.message.ChatMessage;
18
 import org.noear.solon.ai.chat.message.ChatMessage;
15
 import org.noear.solon.ai.mcp.client.McpClientProvider;
19
 import org.noear.solon.ai.mcp.client.McpClientProvider;
20
+import org.reactivestreams.Publisher;
16
 import org.springframework.beans.factory.annotation.Autowired;
21
 import org.springframework.beans.factory.annotation.Autowired;
17
 import org.springframework.web.bind.annotation.GetMapping;
22
 import org.springframework.web.bind.annotation.GetMapping;
18
 import org.springframework.web.bind.annotation.RequestMapping;
23
 import org.springframework.web.bind.annotation.RequestMapping;
37
     @Autowired
42
     @Autowired
38
     private ILangChainMilvusService langChainMilvusService;
43
     private ILangChainMilvusService langChainMilvusService;
39
 
44
 
45
+    @Autowired
46
+    private ICmcChatService cmcChatService;
47
+
40
     private static final EmbeddingModel embeddingModel = new BgeSmallZhV15EmbeddingModel();
48
     private static final EmbeddingModel embeddingModel = new BgeSmallZhV15EmbeddingModel();
41
 
49
 
42
     private static final MilvusServiceClient milvusClient = new MilvusServiceClient(
50
     private static final MilvusServiceClient milvusClient = new MilvusServiceClient(
44
                     .withHost("192.168.28.188")
52
                     .withHost("192.168.28.188")
45
                     .withPort(19530)
53
                     .withPort(19530)
46
                     .build());
54
                     .build());
55
+//
56
+//    /**
57
+//     * 同步问答
58
+//     * @return
59
+//     */
60
+//    @GetMapping("/answer")
61
+//    public AssistantMessage answer(String question) throws IOException {
62
+//        McpClientProvider clientProvider = McpClientProvider.builder()
63
+//                .apiUrl("http://localhost:8080/llm/mcp/sse")
64
+//                .build();
65
+//        ChatModel chatModel = ChatModel.of("http://192.168.28.188:8000/v1/chat/completions")
66
+//                .provider("openai")
67
+//                .model("Qwen2.5-1.5B-Instruct")
68
+//                .defaultToolsAdd(clientProvider)
69
+//                .build();
70
+//        ChatResponse response = chatModel.prompt(question).call();
71
+//        String resultContent = response.lastChoice().getMessage().getResultContent();
72
+//        AssistantMessage assistantMessage = new AssistantMessage(resultContent);
73
+//        if (resultContent.startsWith("<tool_call>")) {
74
+//            String content = resultContent.replace("<tool_call>\n", "").replace("\n</tool_call>", "");
75
+//            JSONObject jsonObject = JSONObject.parseObject(content);
76
+//            String name = jsonObject.getString("name");
77
+//            JSONObject arguments = jsonObject.getJSONObject("arguments");
78
+//            resultContent = clientProvider.callToolAsText(name, arguments).getContent();
79
+//            assistantMessage = new AssistantMessage(resultContent);
80
+//        }
81
+//        return assistantMessage;
82
+//    }
47
 
83
 
48
     /**
84
     /**
49
-     * 同步问答
85
+     * 自动调用mcp工具问答
50
      * @return
86
      * @return
51
      */
87
      */
52
     @GetMapping("/answer")
88
     @GetMapping("/answer")
53
-    public AssistantMessage answer(String question) throws IOException {
89
+    public AssistantMessage answer(String topicId, String question) throws IOException {
54
         McpClientProvider clientProvider = McpClientProvider.builder()
90
         McpClientProvider clientProvider = McpClientProvider.builder()
55
                 .apiUrl("http://localhost:8080/llm/mcp/sse")
91
                 .apiUrl("http://localhost:8080/llm/mcp/sse")
56
                 .build();
92
                 .build();
93
+        ChatSession chatSession = new ChatSessionDefault(topicId);
57
         ChatModel chatModel = ChatModel.of("http://192.168.28.188:8000/v1/chat/completions")
94
         ChatModel chatModel = ChatModel.of("http://192.168.28.188:8000/v1/chat/completions")
58
                 .provider("openai")
95
                 .provider("openai")
59
                 .model("Qwen2.5-1.5B-Instruct")
96
                 .model("Qwen2.5-1.5B-Instruct")
60
                 .defaultToolsAdd(clientProvider)
97
                 .defaultToolsAdd(clientProvider)
61
                 .build();
98
                 .build();
62
-        ChatResponse response = chatModel.prompt(question).call();
99
+
100
+        CmcChat cmcChat = new CmcChat();
101
+        cmcChat.setTopicId(topicId);
102
+        List<CmcChat> cmcChatList = cmcChatService.selectCmcChatList(cmcChat);
103
+        for (CmcChat chat : cmcChatList) {
104
+            chatSession.addMessage(ChatMessage.ofUser(chat.getInput()));
105
+            chatSession.addMessage(ChatMessage.ofAssistant(chat.getOutput()));
106
+        }
107
+
108
+        chatSession.addMessage(ChatMessage.ofUser(question));
109
+        ChatResponse response = chatModel.prompt(chatSession).call();
63
         String resultContent = response.lastChoice().getMessage().getResultContent();
110
         String resultContent = response.lastChoice().getMessage().getResultContent();
64
-        AssistantMessage assistantMessage = new AssistantMessage(resultContent);
111
+        AssistantMessage assistantMessage;
65
         if (resultContent.startsWith("<tool_call>")) {
112
         if (resultContent.startsWith("<tool_call>")) {
66
             String content = resultContent.replace("<tool_call>\n", "").replace("\n</tool_call>", "");
113
             String content = resultContent.replace("<tool_call>\n", "").replace("\n</tool_call>", "");
67
             JSONObject jsonObject = JSONObject.parseObject(content);
114
             JSONObject jsonObject = JSONObject.parseObject(content);
68
             String name = jsonObject.getString("name");
115
             String name = jsonObject.getString("name");
69
             JSONObject arguments = jsonObject.getJSONObject("arguments");
116
             JSONObject arguments = jsonObject.getJSONObject("arguments");
117
+            if (arguments.getString("templatePath").contains("技术"))
118
+                arguments.put("collectionName", "technical");
70
             resultContent = clientProvider.callToolAsText(name, arguments).getContent();
119
             resultContent = clientProvider.callToolAsText(name, arguments).getContent();
71
             assistantMessage = new AssistantMessage(resultContent);
120
             assistantMessage = new AssistantMessage(resultContent);
72
         }
121
         }
122
+        else
123
+            throw new IOException("模型上下文工具未准备就绪,请重试");
73
         return assistantMessage;
124
         return assistantMessage;
74
     }
125
     }
75
 
126
 
76
     /**
127
     /**
77
-     * 调用LLM+RAG(外部文件)生成回答
128
+     * 调用LLM+RAG(外部文件+知识库)生成回答
78
      */
129
      */
79
     @GetMapping("/answerWithDocument")
130
     @GetMapping("/answerWithDocument")
80
-    public Flux<AssistantMessage> answerWithDocumentAndCollection(String collectionName, String topicId, String chatId, String question) throws IOException
131
+    public Flux<AssistantMessage> answerWithDocumentAndCollection(String collectionName, String topicId, String question) throws IOException
81
     {
132
     {
82
         question = String.join(",", langChainMilvusService.extractSubTitles(RuoYiConfig.getProfile() + "/upload/agent/template/technical.docx", question));
133
         question = String.join(",", langChainMilvusService.extractSubTitles(RuoYiConfig.getProfile() + "/upload/agent/template/technical.docx", question));
83
         List<JSONObject> requests = langChainMilvusService.retrieveFromMilvus(milvusClient, embeddingModel, collectionName, question, 10);
134
         List<JSONObject> requests = langChainMilvusService.retrieveFromMilvus(milvusClient, embeddingModel, collectionName, question, 10);
84
-        return langChainMilvusService.generateAnswerWithDocumentAndCollection(embeddingModel, topicId, chatId, question, requests, "http://192.168.28.188:8000/v1/chat/completions");
135
+        return langChainMilvusService.generateAnswerWithDocumentAndCollection(embeddingModel, topicId, question, requests, "http://192.168.28.188:8000/v1/chat/completions");
85
     }
136
     }
86
 
137
 
87
-
88
     /**
138
     /**
89
      * 根据招标文件要求编写投标文件技术方案
139
      * 根据招标文件要求编写投标文件技术方案
90
      * @return
140
      * @return

+ 1
- 1
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/service/ILangChainMilvusService.java 查看文件

53
      * 调用LLM+RAG(外部文件+知识库)生成回答
53
      * 调用LLM+RAG(外部文件+知识库)生成回答
54
      * @return
54
      * @return
55
      */
55
      */
56
-    public Flux<AssistantMessage> generateAnswerWithDocumentAndCollection(EmbeddingModel embeddingModel, String topicId, String chatId, String question,  List<JSONObject> requests, String llmServiceUrl) throws IOException;
56
+    public Flux<AssistantMessage> generateAnswerWithDocumentAndCollection(EmbeddingModel embeddingModel, String topicId, String question,  List<JSONObject> requests, String llmServiceUrl) throws IOException;
57
 
57
 
58
     /**
58
     /**
59
      * 获取二级标题下三级标题列表
59
      * 获取二级标题下三级标题列表

+ 27
- 18
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/service/impl/LangChainMilvusServiceImpl.java 查看文件

4
 import com.ruoyi.common.config.RuoYiConfig;
4
 import com.ruoyi.common.config.RuoYiConfig;
5
 import com.ruoyi.llm.domain.CmcChat;
5
 import com.ruoyi.llm.domain.CmcChat;
6
 import com.ruoyi.llm.domain.CmcDocument;
6
 import com.ruoyi.llm.domain.CmcDocument;
7
+import com.ruoyi.llm.domain.CmcTopic;
7
 import com.ruoyi.llm.service.ICmcChatService;
8
 import com.ruoyi.llm.service.ICmcChatService;
8
 import com.ruoyi.llm.service.ICmcDocumentService;
9
 import com.ruoyi.llm.service.ICmcDocumentService;
9
 import com.ruoyi.web.llm.service.ILangChainMilvusService;
10
 import com.ruoyi.web.llm.service.ILangChainMilvusService;
30
 import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
31
 import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
31
 import org.apache.poi.xwpf.usermodel.XWPFDocument;
32
 import org.apache.poi.xwpf.usermodel.XWPFDocument;
32
 import org.apache.poi.xwpf.usermodel.XWPFParagraph;
33
 import org.apache.poi.xwpf.usermodel.XWPFParagraph;
34
+import org.checkerframework.checker.units.qual.C;
33
 import org.noear.solon.ai.chat.ChatModel;
35
 import org.noear.solon.ai.chat.ChatModel;
34
 import org.noear.solon.ai.chat.ChatResponse;
36
 import org.noear.solon.ai.chat.ChatResponse;
35
 import org.noear.solon.ai.chat.ChatSession;
37
 import org.noear.solon.ai.chat.ChatSession;
222
      * 调用LLM生成回答
224
      * 调用LLM生成回答
223
      */
225
      */
224
     @Override
226
     @Override
225
-    public Flux<AssistantMessage> generateAnswerWithDocumentAndCollection(EmbeddingModel embeddingModel, String topicId, String chatId, String question, List<JSONObject> contexts, String llmServiceUrl) throws IOException {
227
+    public Flux<AssistantMessage> generateAnswerWithDocumentAndCollection(EmbeddingModel embeddingModel, String topicId, String question, List<JSONObject> contexts, String llmServiceUrl) throws IOException {
226
         StringBuilder sb = new StringBuilder("招标文件内容:\n\n");
228
         StringBuilder sb = new StringBuilder("招标文件内容:\n\n");
227
-        CmcDocument cmcDocument = new CmcDocument();
228
-        cmcDocument.setChatId(chatId);
229
-        List<CmcDocument> documentList = cmcDocumentService.selectCmcDocumentList(cmcDocument);
230
-        for (CmcDocument document : documentList) {
231
-            File profilePath = new File(RuoYiConfig.getProfile() + document.getPath());
232
-            List<TextSegment> segments = splitDocument(document.getPath(), profilePath);
233
-            List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
234
-            InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
235
-            embeddingStore.addAll(embeddings, segments);
236
-            Embedding queryEmbedding = embeddingModel.embed(question).content();
237
-            EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
238
-                    .queryEmbedding(queryEmbedding)
239
-                    .maxResults(3)
240
-                    .build();
241
-            for (EmbeddingMatch embeddingMatch : embeddingStore.search(embeddingSearchRequest).matches()) {
242
-                String requests = embeddingMatch.embedded().toString();
243
-                sb.append(requests).append("\n\n");
229
+        CmcChat cmcChat = new CmcChat();
230
+        cmcChat.setTopicId(topicId);
231
+        for (CmcChat chat : cmcChatService.selectCmcChatList(cmcChat)) {
232
+            CmcDocument cmcDocument = new CmcDocument();
233
+            cmcDocument.setChatId(chat.getChatId());
234
+            List<CmcDocument> documentList = cmcDocumentService.selectCmcDocumentList(cmcDocument);
235
+            //技术标书撰写智能体一个话题只会上传一个招标文件
236
+            if (documentList.size() == 1) {
237
+                for (CmcDocument document : documentList) {
238
+                    File profilePath = new File(RuoYiConfig.getProfile() + document.getPath());
239
+                    List<TextSegment> segments = splitDocument(document.getPath(), profilePath);
240
+                    List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
241
+                    InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
242
+                    embeddingStore.addAll(embeddings, segments);
243
+                    Embedding queryEmbedding = embeddingModel.embed(question).content();
244
+                    EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
245
+                            .queryEmbedding(queryEmbedding)
246
+                            .maxResults(3)
247
+                            .build();
248
+                    for (EmbeddingMatch embeddingMatch : embeddingStore.search(embeddingSearchRequest).matches()) {
249
+                        String requests = embeddingMatch.embedded().toString();
250
+                        sb.append(requests).append("\n\n");
251
+                    }
252
+                }
244
             }
253
             }
245
         }
254
         }
246
         sb.append("针对本项目招标文件内容,补全以下章节部分:\n\n").append(question);
255
         sb.append("针对本项目招标文件内容,补全以下章节部分:\n\n").append(question);

+ 14
- 0
llm-back/ruoyi-system/src/main/java/com/ruoyi/llm/service/impl/CmcAgentServiceImpl.java 查看文件

4
 import java.io.FileInputStream;
4
 import java.io.FileInputStream;
5
 import java.io.FileOutputStream;
5
 import java.io.FileOutputStream;
6
 import java.io.IOException;
6
 import java.io.IOException;
7
+import java.util.Date;
7
 import java.util.List;
8
 import java.util.List;
8
 
9
 
9
 import com.alibaba.fastjson2.JSONObject;
10
 import com.alibaba.fastjson2.JSONObject;
10
 import com.ruoyi.common.config.RuoYiConfig;
11
 import com.ruoyi.common.config.RuoYiConfig;
11
 import com.ruoyi.common.utils.DateUtils;
12
 import com.ruoyi.common.utils.DateUtils;
13
+import com.ruoyi.common.utils.SecurityUtils;
12
 import com.ruoyi.common.utils.SnowFlake;
14
 import com.ruoyi.common.utils.SnowFlake;
15
+import com.ruoyi.llm.domain.CmcChat;
13
 import com.ruoyi.llm.domain.CmcDocument;
16
 import com.ruoyi.llm.domain.CmcDocument;
17
+import com.ruoyi.llm.mapper.CmcChatMapper;
14
 import com.ruoyi.llm.mapper.CmcDocumentMapper;
18
 import com.ruoyi.llm.mapper.CmcDocumentMapper;
19
+import com.ruoyi.llm.service.ICmcChatService;
15
 import org.apache.poi.xwpf.usermodel.XWPFDocument;
20
 import org.apache.poi.xwpf.usermodel.XWPFDocument;
16
 import org.springframework.beans.factory.annotation.Autowired;
21
 import org.springframework.beans.factory.annotation.Autowired;
17
 import org.springframework.stereotype.Service;
22
 import org.springframework.stereotype.Service;
35
     @Autowired
40
     @Autowired
36
     private CmcDocumentMapper cmcDocumentMapper;
41
     private CmcDocumentMapper cmcDocumentMapper;
37
 
42
 
43
+    @Autowired
44
+    private CmcChatMapper cmcChatMapper;
45
+
38
     /**
46
     /**
39
      * 查询智能体
47
      * 查询智能体
40
      * 
48
      * 
99
         cmcDocumentMapper.insertCmcDocument(cmcDocument);
107
         cmcDocumentMapper.insertCmcDocument(cmcDocument);
100
         String message = "";
108
         String message = "";
101
         if (agentName.contains("技术")) {
109
         if (agentName.contains("技术")) {
110
+            CmcChat cmcChat = new CmcChat();
111
+            cmcChat.setChatId(jsonObject.getString("chatId"));
112
+            cmcChat.setInputTime(new Date());
113
+            cmcChat.setInput("招标文件地址:" + outputFilename);
114
+            cmcChat.setUserId(SecurityUtils.getUserId());
115
+            cmcChatMapper.insertCmcChat(cmcChat);
102
             XWPFDocument doc = new XWPFDocument(new FileInputStream(RuoYiConfig.getProfile() + "/upload/agent/template/technical.docx"));
116
             XWPFDocument doc = new XWPFDocument(new FileInputStream(RuoYiConfig.getProfile() + "/upload/agent/template/technical.docx"));
103
             // 保存文档到本地文件系统
117
             // 保存文档到本地文件系统
104
             FileOutputStream out = new FileOutputStream(RuoYiConfig.getProfile() + outputFilename);
118
             FileOutputStream out = new FileOutputStream(RuoYiConfig.getProfile() + outputFilename);

Loading…
取消
儲存