浏览代码

结合mcp工具返回值流式输出

lamphua 2 天前
父节点
当前提交
9c401b261b

+ 16
- 0
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.ruoyi.agent.service.IMcpService;
3
 import com.ruoyi.agent.service.IMcpService;
4
+import org.apache.poi.xwpf.usermodel.XWPFDocument;
5
+import org.apache.poi.xwpf.usermodel.XWPFParagraph;
4
 import org.noear.solon.ai.annotation.PromptMapping;
6
 import org.noear.solon.ai.annotation.PromptMapping;
5
 import org.noear.solon.ai.annotation.ResourceMapping;
7
 import org.noear.solon.ai.annotation.ResourceMapping;
6
 import org.noear.solon.ai.annotation.ToolMapping;
8
 import org.noear.solon.ai.annotation.ToolMapping;
9
 import org.noear.solon.annotation.Param;
11
 import org.noear.solon.annotation.Param;
10
 import org.springframework.stereotype.Service;
12
 import org.springframework.stereotype.Service;
11
 
13
 
14
+import java.io.FileInputStream;
15
+import java.io.IOException;
12
 import java.util.Arrays;
16
 import java.util.Arrays;
13
 import java.util.Collection;
17
 import java.util.Collection;
14
 
18
 
21
         return "晴,14度";
25
         return "晴,14度";
22
     }
26
     }
23
 
27
 
28
+    @ToolMapping(description = "查看word文档")
29
+    public String openDocument() throws IOException {
30
+        String filePath = "C:\\Users\\lamphua\\Desktop\\智慧水利\\李家岩水库数字孪生招标文件.DOCX";
31
+        try (XWPFDocument doc = new XWPFDocument(new FileInputStream(filePath))) {
32
+            StringBuilder content = new StringBuilder();
33
+            for (XWPFParagraph paragraph : doc.getParagraphs()) {
34
+                content.append(paragraph.getText()).append("\n");
35
+            }
36
+            return content.toString();
37
+        }
38
+    }
39
+
24
     @ResourceMapping(uri = "config://app-version", description = "获取应用版本号")
40
     @ResourceMapping(uri = "config://app-version", description = "获取应用版本号")
25
     public String getAppVersion() {
41
     public String getAppVersion() {
26
         return "v3.2.0";
42
         return "v3.2.0";

+ 6
- 5
llm-back/ruoyi-llm/pom.xml 查看文件

67
             <groupId>org.noear</groupId>
67
             <groupId>org.noear</groupId>
68
             <artifactId>solon-ai-mcp</artifactId>
68
             <artifactId>solon-ai-mcp</artifactId>
69
             <version>3.3.1</version>
69
             <version>3.3.1</version>
70
-        </dependency>
71
-
72
-        <dependency>
73
-            <groupId>com.squareup.okhttp3</groupId>
74
-            <artifactId>okhttp</artifactId>
70
+            <exclusions>
71
+                <exclusion>
72
+                    <groupId>org.slf4j</groupId>
73
+                    <artifactId>*</artifactId>
74
+                </exclusion>
75
+            </exclusions>
75
         </dependency>
76
         </dependency>
76
 
77
 
77
     </dependencies>
78
     </dependencies>

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

1
 package com.ruoyi.web.llm.controller;
1
 package com.ruoyi.web.llm.controller;
2
 
2
 
3
 import com.ruoyi.common.core.controller.BaseController;
3
 import com.ruoyi.common.core.controller.BaseController;
4
-import com.ruoyi.common.core.domain.AjaxResult;
4
+import com.ruoyi.web.llm.service.ILangChainMilvusService;
5
+import org.noear.solon.ai.chat.ChatModel;
6
+import org.noear.solon.ai.chat.ChatResponse;
7
+import org.noear.solon.ai.chat.message.AssistantMessage;
5
 import org.noear.solon.ai.mcp.client.McpClientProvider;
8
 import org.noear.solon.ai.mcp.client.McpClientProvider;
9
+import org.reactivestreams.Publisher;
10
+import org.springframework.beans.factory.annotation.Autowired;
6
 import org.springframework.web.bind.annotation.GetMapping;
11
 import org.springframework.web.bind.annotation.GetMapping;
7
 import org.springframework.web.bind.annotation.RequestMapping;
12
 import org.springframework.web.bind.annotation.RequestMapping;
8
 import org.springframework.web.bind.annotation.RestController;
13
 import org.springframework.web.bind.annotation.RestController;
14
+import reactor.core.publisher.Flux;
9
 
15
 
10
-import java.util.Collections;
11
-import java.util.Map;
12
 
16
 
13
 /**
17
 /**
14
  * mcp模型上下文协议Controller
18
  * mcp模型上下文协议Controller
20
 @RequestMapping("/llm/mcp")
24
 @RequestMapping("/llm/mcp")
21
 public class McpController extends BaseController
25
 public class McpController extends BaseController
22
 {
26
 {
27
+    @Autowired
28
+    private ILangChainMilvusService langChainMilvusService;
29
+
23
     /**
30
     /**
24
-     * 生成回答
31
+     * 打开word文档
32
+     * @return
25
      */
33
      */
26
     @GetMapping("/answer")
34
     @GetMapping("/answer")
27
-    public AjaxResult answer() {
35
+    public Flux<AssistantMessage> answer(String question)
36
+    {
28
         McpClientProvider clientProvider = McpClientProvider.builder()
37
         McpClientProvider clientProvider = McpClientProvider.builder()
29
-                .apiUrl("http://localhost:52548/llm/mcp/sse")
38
+                    .apiUrl("http://localhost:8080/llm/mcp/sse")
39
+                    .build();
40
+
41
+        ChatModel chatModel = ChatModel.of("http://192.168.28.188:8000/v1/chat/completions")
42
+                .provider("openai")
43
+                .model("DeepSeek-R1-Distill-Qwen-1.5B")
44
+                .apiKey("1")
45
+//                .defaultToolsAdd(clientProvider)
30
                 .build();
46
                 .build();
47
+        Publisher<ChatResponse> publisher = chatModel.prompt(question).stream();
31
 
48
 
32
-        Map<String, Object> map = Collections.singletonMap("location", "杭州");
33
-        String rst = clientProvider.callToolAsText("getWeather", map).getContent();
34
-        // 1. 调用本地LLM或HTTP服务
35
-        return success(rst);
49
+        return Flux.from(publisher)
50
+                .map(response -> {
51
+                    return response.getChoices().get(0).getMessage();
52
+                });
36
     }
53
     }
37
 
54
 
38
-
39
 }
55
 }

+ 4
- 3
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/RagController.java 查看文件

13
 import dev.langchain4j.model.embedding.onnx.bgesmallzhv15.BgeSmallZhV15EmbeddingModel;
13
 import dev.langchain4j.model.embedding.onnx.bgesmallzhv15.BgeSmallZhV15EmbeddingModel;
14
 import io.milvus.client.MilvusServiceClient;
14
 import io.milvus.client.MilvusServiceClient;
15
 import io.milvus.param.ConnectParam;
15
 import io.milvus.param.ConnectParam;
16
+import org.noear.solon.ai.chat.message.AssistantMessage;
16
 import org.springframework.beans.factory.annotation.Autowired;
17
 import org.springframework.beans.factory.annotation.Autowired;
17
 import org.springframework.web.bind.annotation.GetMapping;
18
 import org.springframework.web.bind.annotation.GetMapping;
18
 import org.springframework.web.bind.annotation.RequestMapping;
19
 import org.springframework.web.bind.annotation.RequestMapping;
19
 import org.springframework.web.bind.annotation.RestController;
20
 import org.springframework.web.bind.annotation.RestController;
21
+import reactor.core.publisher.Flux;
20
 
22
 
21
 import java.io.IOException;
23
 import java.io.IOException;
22
 import java.util.List;
24
 import java.util.List;
46
      * 增强检索生成回答
48
      * 增强检索生成回答
47
      */
49
      */
48
     @GetMapping("/answer")
50
     @GetMapping("/answer")
49
-    public AjaxResult answer(String question, String collectionName) throws IOException {
51
+    public Flux<AssistantMessage> answer(String question, String collectionName) throws IOException {
50
 
52
 
51
         List<String> contexts = langChainMilvusService.retrieveFromMilvus(milvusClient, embeddingModel, collectionName, question, 1);
53
         List<String> contexts = langChainMilvusService.retrieveFromMilvus(milvusClient, embeddingModel, collectionName, question, 1);
52
-        String result = langChainMilvusService.generateAnswerWithRag(question, contexts, "http://192.168.28.188:8000/generate");
53
-        return success(result);
54
+        return langChainMilvusService.generateAnswerWithRag(question, contexts, "http://192.168.28.188:8000/v1/chat/completions");
54
     }
55
     }
55
 
56
 
56
 }
57
 }

+ 4
- 2
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/SessionController.java 查看文件

3
 import com.ruoyi.common.core.controller.BaseController;
3
 import com.ruoyi.common.core.controller.BaseController;
4
 import com.ruoyi.common.core.domain.AjaxResult;
4
 import com.ruoyi.common.core.domain.AjaxResult;
5
 import com.ruoyi.web.llm.service.ILangChainMilvusService;
5
 import com.ruoyi.web.llm.service.ILangChainMilvusService;
6
+import org.noear.solon.ai.chat.message.AssistantMessage;
6
 import org.springframework.beans.factory.annotation.Autowired;
7
 import org.springframework.beans.factory.annotation.Autowired;
7
 import org.springframework.web.bind.annotation.GetMapping;
8
 import org.springframework.web.bind.annotation.GetMapping;
8
 import org.springframework.web.bind.annotation.RequestMapping;
9
 import org.springframework.web.bind.annotation.RequestMapping;
9
 import org.springframework.web.bind.annotation.RestController;
10
 import org.springframework.web.bind.annotation.RestController;
11
+import reactor.core.publisher.Flux;
10
 
12
 
11
 import java.io.IOException;
13
 import java.io.IOException;
12
 
14
 
27
      * 生成回答
29
      * 生成回答
28
      */
30
      */
29
     @GetMapping("/answer")
31
     @GetMapping("/answer")
30
-    public AjaxResult answer(String question) throws IOException {
31
-        return success(langChainMilvusService.generateAnswer(question, "http://192.168.28.188:8000/generate"));
32
+    public Flux<AssistantMessage> answer(String question) {
33
+        return langChainMilvusService.generateAnswer(question, "http://192.168.28.188:8000/v1/chat/completions");
32
     }
34
     }
33
 
35
 
34
 }
36
 }

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

4
 import io.milvus.client.MilvusClient;
4
 import io.milvus.client.MilvusClient;
5
 import io.milvus.grpc.MutationResult;
5
 import io.milvus.grpc.MutationResult;
6
 import io.milvus.param.R;
6
 import io.milvus.param.R;
7
+import org.noear.solon.ai.chat.message.AssistantMessage;
7
 import org.springframework.web.multipart.MultipartFile;
8
 import org.springframework.web.multipart.MultipartFile;
9
+import reactor.core.publisher.Flux;
8
 
10
 
9
 import java.io.IOException;
11
 import java.io.IOException;
10
 import java.util.List;
12
 import java.util.List;
23
 
25
 
24
     /**
26
     /**
25
      * 调用LLM+RAG生成回答
27
      * 调用LLM+RAG生成回答
28
+     * @return
26
      */
29
      */
27
-    public String generateAnswerWithRag(String question, List<String> contexts, String llmServiceUrl) throws IOException;
30
+    public Flux<AssistantMessage> generateAnswerWithRag(String question, List<String> contexts, String llmServiceUrl);
28
 
31
 
29
     /**
32
     /**
30
      * 调用LLM生成回答
33
      * 调用LLM生成回答
34
+     * @return
31
      */
35
      */
32
-    public String generateAnswer(String question, String llmServiceUrl) throws IOException;
36
+    public Flux<AssistantMessage> generateAnswer(String question, String llmServiceUrl);
33
 }
37
 }

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

6
  */
6
  */
7
 package com.ruoyi.web.llm.service.impl;
7
 package com.ruoyi.web.llm.service.impl;
8
 
8
 
9
-import com.alibaba.fastjson2.JSONObject;
10
 import com.ruoyi.common.config.RuoYiConfig;
9
 import com.ruoyi.common.config.RuoYiConfig;
11
 import com.ruoyi.web.llm.service.ILangChainMilvusService;
10
 import com.ruoyi.web.llm.service.ILangChainMilvusService;
12
 import dev.langchain4j.data.document.Document;
11
 import dev.langchain4j.data.document.Document;
26
 import io.milvus.param.dml.InsertParam;
25
 import io.milvus.param.dml.InsertParam;
27
 import io.milvus.param.dml.SearchParam;
26
 import io.milvus.param.dml.SearchParam;
28
 import io.milvus.response.SearchResultsWrapper;
27
 import io.milvus.response.SearchResultsWrapper;
29
-import okhttp3.*;
30
 import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
28
 import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
31
 import org.apache.poi.xwpf.usermodel.XWPFDocument;
29
 import org.apache.poi.xwpf.usermodel.XWPFDocument;
30
+import org.noear.solon.ai.chat.ChatModel;
31
+import org.noear.solon.ai.chat.ChatResponse;
32
+import org.noear.solon.ai.chat.message.AssistantMessage;
33
+import org.reactivestreams.Publisher;
32
 import org.springframework.stereotype.Service;
34
 import org.springframework.stereotype.Service;
33
 import org.springframework.web.multipart.MultipartFile;
35
 import org.springframework.web.multipart.MultipartFile;
36
+import reactor.core.publisher.Flux;
34
 
37
 
35
 import java.io.File;
38
 import java.io.File;
36
 import java.io.FileInputStream;
39
 import java.io.FileInputStream;
37
 import java.io.IOException;
40
 import java.io.IOException;
38
 import java.io.InputStream;
41
 import java.io.InputStream;
39
 import java.util.*;
42
 import java.util.*;
40
-import java.util.concurrent.TimeUnit;
41
 import java.util.stream.Collectors;
43
 import java.util.stream.Collectors;
42
 
44
 
43
 @Service
45
 @Service
153
 
155
 
154
     /**
156
     /**
155
      * 调用LLM+RAG生成回答
157
      * 调用LLM+RAG生成回答
158
+     * @return
156
      */
159
      */
157
     @Override
160
     @Override
158
-    public String generateAnswerWithRag(String question, List<String> contexts, String llmServiceUrl) throws IOException {
161
+    public Flux<AssistantMessage> generateAnswerWithRag(String question, List<String> contexts, String llmServiceUrl) {
159
         StringBuilder sb = new StringBuilder();
162
         StringBuilder sb = new StringBuilder();
160
         sb.append("根据以下上下文回答问题:\n\n");
163
         sb.append("根据以下上下文回答问题:\n\n");
161
         for (int i = 0; i < contexts.size(); i++) {
164
         for (int i = 0; i < contexts.size(); i++) {
168
 
171
 
169
     /**
172
     /**
170
      * 调用LLM生成回答
173
      * 调用LLM生成回答
174
+     * @return
171
      */
175
      */
172
     @Override
176
     @Override
173
-    public String generateAnswer(String prompt, String llmServiceUrl) throws IOException {
174
-        HttpUrl url = HttpUrl.parse(llmServiceUrl)
175
-                .newBuilder()
176
-                .addQueryParameter("prompt", prompt)
177
-//                .addQueryParameter("max_token", String.valueOf(1024))
177
+    public Flux<AssistantMessage> generateAnswer(String prompt, String llmServiceUrl) {
178
+        ChatModel chatModel = ChatModel.of(llmServiceUrl)
179
+                .provider("openai")
180
+                .model("DeepSeek-R1-Distill-Qwen-1.5B")
181
+                .apiKey("1")
178
                 .build();
182
                 .build();
183
+        Publisher<ChatResponse> publisher = chatModel.prompt(prompt).stream();
179
 
184
 
180
-        Request request = new Request.Builder()
181
-                .url(url)
182
-                .build();
183
-
184
-        OkHttpClient client = new OkHttpClient.Builder()
185
-                .readTimeout(60, TimeUnit.SECONDS)      // 读取响应超时
186
-                .build();
187
-
188
-        try (Response response = client.newCall(request).execute()) {
189
-            if (response.body() != null) {
190
-                String responseBody = response.body().string();
191
-                return JSONObject.parseObject(responseBody).getString("generated_text");
192
-            }
193
-            else return null;
194
-        }
185
+        return Flux.from(publisher)
186
+                .map(response -> {
187
+                    return response.getChoices().get(0).getMessage();
188
+                });
195
     }
189
     }
196
 
190
 
197
 }
191
 }

+ 1
- 1
llm-ui/src/views/llm/chat/index.vue 查看文件

426
       // 使用Vue.set或直接赋值来确保响应式更新
426
       // 使用Vue.set或直接赋值来确保响应式更新
427
       chatMessages.value[messageIndex] = {
427
       chatMessages.value[messageIndex] = {
428
         ...chatMessages.value[messageIndex],
428
         ...chatMessages.value[messageIndex],
429
-        output: answer.msg,
429
+        output: answer.resultContent,
430
         outputTime: proxy.parseTime(new Date(), '{y}-{m}-{d}')
430
         outputTime: proxy.parseTime(new Date(), '{y}-{m}-{d}')
431
       };
431
       };
432
     }
432
     }

+ 1
- 1
llm-ui/src/views/llm/knowledge/index.vue 查看文件

440
       console.log(response);
440
       console.log(response);
441
       const aiMessage = {
441
       const aiMessage = {
442
         type: 'ai',
442
         type: 'ai',
443
-        content: response.msg,
443
+        content: response.resultContent,
444
         time: new Date()
444
         time: new Date()
445
       };
445
       };
446
       chatMessages.value.push(aiMessage);
446
       chatMessages.value.push(aiMessage);

正在加载...
取消
保存