From 4898d942bd6e3c1119493cf0314b15f2bd54daf3 Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期六, 03 一月 2026 22:06:22 +0800
Subject: [PATCH] #mcp

---
 src/main/java/com/zy/ai/utils/AiUtils.java                     |   11 
 src/main/webapp/views/ai/diagnosis.html                        |   39 ++
 src/main/java/com/zy/ai/service/LlmChatService.java            |  246 ++++++++++++++
 src/main/java/com/zy/ai/entity/ChatCompletionRequest.java      |   18 +
 src/main/java/com/zy/ai/controller/WcsDiagnosisController.java |   21 -
 src/main/java/com/zy/ai/utils/AiPromptUtils.java               |  111 ++++++
 src/main/java/com/zy/ai/service/WcsDiagnosisService.java       |  395 ++++++++++++++++++++--
 src/main/java/com/zy/ai/service/PythonService.java             |   55 +++
 src/main/java/com/zy/ai/mcp/controller/McpController.java      |   17 
 src/main/java/com/zy/ai/mcp/config/McpToolsBootstrap.java      |   34 +-
 src/main/resources/application.yml                             |   14 
 11 files changed, 873 insertions(+), 88 deletions(-)

diff --git a/src/main/java/com/zy/ai/controller/WcsDiagnosisController.java b/src/main/java/com/zy/ai/controller/WcsDiagnosisController.java
index 9d8df7a..15857a0 100644
--- a/src/main/java/com/zy/ai/controller/WcsDiagnosisController.java
+++ b/src/main/java/com/zy/ai/controller/WcsDiagnosisController.java
@@ -2,7 +2,6 @@
 
 import com.zy.ai.entity.ChatCompletionRequest;
 import com.zy.ai.entity.WcsDiagnosisRequest;
-import com.zy.ai.entity.WcsDiagnosisResponse;
 import com.zy.ai.service.WcsDiagnosisService;
 import com.zy.ai.utils.AiUtils;
 import com.zy.common.web.BaseController;
@@ -24,13 +23,6 @@
     private WcsDiagnosisService wcsDiagnosisService;
     @Autowired
     private AiUtils aiUtils;
-
-    @GetMapping("/runAi")
-    public WcsDiagnosisResponse runAi() {
-        WcsDiagnosisRequest request = aiUtils.makeAiRequest(1000, "绯荤粺褰撳墠涓嶆墽琛屼换鍔★紝浣嗗叿浣撳師鍥犱笉鏄庯紝璇锋牴鎹互涓嬩俊鎭府鍔╁垽鏂�俓n\n");
-        WcsDiagnosisResponse response = diagnose(request);
-        return response;
-    }
 
     @GetMapping("/runAiStream")
     public SseEmitter runAiStream() {
@@ -77,18 +69,5 @@
     @GetMapping("/chats/{chatId}/history")
     public List<ChatCompletionRequest.Message> getChatHistory(@PathVariable("chatId") String chatId) {
         return wcsDiagnosisService.getChatHistory(chatId);
-    }
-
-    /**
-     * POST /api/ai/diagnose/wcs
-     */
-    @PostMapping("/wcs")
-    public WcsDiagnosisResponse diagnose(@RequestBody WcsDiagnosisRequest request) {
-        String analysis = wcsDiagnosisService.diagnose(request);
-
-        WcsDiagnosisResponse resp = new WcsDiagnosisResponse();
-        resp.setAnalysis(analysis);
-        resp.setOriginalRequest(request);
-        return resp;
     }
 }
diff --git a/src/main/java/com/zy/ai/entity/ChatCompletionRequest.java b/src/main/java/com/zy/ai/entity/ChatCompletionRequest.java
index 690cdb5..c000f7e 100644
--- a/src/main/java/com/zy/ai/entity/ChatCompletionRequest.java
+++ b/src/main/java/com/zy/ai/entity/ChatCompletionRequest.java
@@ -13,10 +13,28 @@
     private Double temperature;
     private Integer max_tokens;
     private Boolean stream;
+    private List<Object> tools;
+    private Object tool_choice;
 
     @Data
     public static class Message {
         private String role;    // "user" / "assistant" / "system"
         private String content;
+        private String name;
+        private String tool_call_id;
+        private List<ToolCall> tool_calls;
+    }
+
+    @Data
+    public static class ToolCall {
+        private String id;
+        private String type;
+        private Function function;
+    }
+
+    @Data
+    public static class Function {
+        private String name;
+        private String arguments;
     }
 }
diff --git a/src/main/java/com/zy/ai/mcp/config/McpToolsBootstrap.java b/src/main/java/com/zy/ai/mcp/config/McpToolsBootstrap.java
index b15119f..c3ce29e 100644
--- a/src/main/java/com/zy/ai/mcp/config/McpToolsBootstrap.java
+++ b/src/main/java/com/zy/ai/mcp/config/McpToolsBootstrap.java
@@ -13,10 +13,10 @@
     public static void registerAll(ToolRegistry registry, final WcsDataFacade facade) {
 
         registry.register(tool(
-                "device.get_crn_status",
-                "Query realtime status of a crn device by deviceNo.",
+                "device_get_crn_status",
+                "閫氳繃鍫嗗灈鏈虹紪鍙锋煡璇㈠爢鍨涙満璁惧瀹炴椂鏁版嵁",
                 schemaObj(
-                        propInt("crnNos", true)
+                        propArr("crnNos", true, "integer")
                 ),
                 schemaObj(
                         propObj("devices", true)
@@ -29,8 +29,8 @@
         ));
 
         registry.register(tool(
-                "device.get_station_status",
-                "Query realtime status of a station device",
+                "device_get_station_status",
+                "鏌ヨ杈撻�佺嚎绔欑偣璁惧瀹炴椂鏁版嵁",
                 schemaObj(
 
                 ),
@@ -45,10 +45,10 @@
         ));
 
         registry.register(tool(
-                "device.get_rgv_status",
-                "Query realtime status of a rgv device by deviceNo.",
+                "device_get_rgv_status",
+                "閫氳繃RGV缂栧彿鏌ヨRGV璁惧瀹炴椂鏁版嵁",
                 schemaObj(
-                        propInt("rgvNos", true)
+                        propArr("rgvNos", true, "integer")
                 ),
                 schemaObj(
                         propObj("devices", true)
@@ -61,8 +61,8 @@
         ));
 
         registry.register(tool(
-                "task.list",
-                "List tasks by filters (status/CrnDevice/RgvDevice//time window).",
+                "task_list",
+                "閫氳繃绛涢�夋潯浠舵煡璇换鍔℃暟鎹�",
                 schemaObj(
                         propInt("crnNo", false),
                         propInt("rgvNo", false),
@@ -78,8 +78,8 @@
         ));
 
         registry.register(tool(
-                "log.query",
-                "Query logs by keyword/level/time window/device/task. Return clipped log lines.",
+                "log_query",
+                "閫氳繃绛涢�夋潯浠舵煡璇㈡棩蹇楁暟鎹�",
                 schemaObj(
                         propInt("limit", false)
                 ),
@@ -92,8 +92,8 @@
         ));
 
         registry.register(tool(
-                "config.get_device_config",
-                "Get device config by deviceCode.",
+                "config_get_device_config",
+                "閫氳繃璁惧缂栧彿鏌ヨ璁惧閰嶇疆鏁版嵁",
                 schemaObj(
                         propArr("crnNos", false, "integer"),
                         propArr("rgvNos", false, "integer"),
@@ -108,8 +108,8 @@
         ));
 
         registry.register(tool(
-                "config.get_system_config",
-                "Get key system configs for diagnosis.",
+                "config_get_system_config",
+                "鏌ヨ绯荤粺閰嶇疆鏁版嵁",
                 schemaObj(
 
                 ),
@@ -229,4 +229,4 @@
         m.put("items", items);
         return m;
     }
-}
\ No newline at end of file
+}
diff --git a/src/main/java/com/zy/ai/mcp/controller/McpController.java b/src/main/java/com/zy/ai/mcp/controller/McpController.java
index bf53c2e..fad580d 100644
--- a/src/main/java/com/zy/ai/mcp/controller/McpController.java
+++ b/src/main/java/com/zy/ai/mcp/controller/McpController.java
@@ -87,4 +87,19 @@
             return JsonRpcResponse.err(id, -32000, "Server error", e.getMessage());
         }
     }
-}
\ No newline at end of file
+
+    public List<Map<String, Object>> listTools() {
+        return registry.listTools();
+    }
+
+    public Object callTool(String toolName, JSONObject arguments) throws Exception {
+        if (toolName == null || toolName.trim().isEmpty()) {
+            throw new IllegalArgumentException("missing tool name");
+        }
+        ToolDefinition def = registry.get(toolName);
+        if (def == null) {
+            throw new IllegalArgumentException("tool not found: " + toolName);
+        }
+        return def.getHandler().handle(arguments == null ? new JSONObject() : arguments);
+    }
+}
diff --git a/src/main/java/com/zy/ai/service/LlmChatService.java b/src/main/java/com/zy/ai/service/LlmChatService.java
index d599775..431896c 100644
--- a/src/main/java/com/zy/ai/service/LlmChatService.java
+++ b/src/main/java/com/zy/ai/service/LlmChatService.java
@@ -12,6 +12,7 @@
 import reactor.core.publisher.Mono;
 import reactor.core.publisher.Flux;
 
+import java.util.HashMap;
 import java.util.List;
 import java.util.function.Consumer;
 import java.util.concurrent.LinkedBlockingQueue;
@@ -33,6 +34,9 @@
 
     @Value("${llm.model}")
     private String model;
+
+    @Value("${llm.pythonPlatformUrl}")
+    private String pythonPlatformUrl;
 
     /**
      * 閫氱敤瀵硅瘽鏂规硶锛氫紶鍏� messages锛岃繑鍥炲ぇ妯″瀷鏂囨湰鍥炲
@@ -74,6 +78,47 @@
         }
 
         return response.getChoices().get(0).getMessage().getContent();
+    }
+
+    public ChatCompletionResponse chatCompletion(List<ChatCompletionRequest.Message> messages,
+                                                 Double temperature,
+                                                 Integer maxTokens,
+                                                 List<Object> tools) {
+
+        ChatCompletionRequest req = new ChatCompletionRequest();
+        req.setModel(model);
+        req.setMessages(messages);
+        req.setTemperature(temperature != null ? temperature : 0.3);
+        req.setMax_tokens(maxTokens != null ? maxTokens : 1024);
+        req.setStream(false);
+        if (tools != null && !tools.isEmpty()) {
+            req.setTools(tools);
+            req.setTool_choice("auto");
+        }
+        return complete(req);
+    }
+
+    public ChatCompletionResponse complete(ChatCompletionRequest req) {
+        try {
+            return llmWebClient.post()
+                    .uri("/chat/completions")
+                    .header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey)
+                    .contentType(MediaType.APPLICATION_JSON)
+                    .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_EVENT_STREAM)
+                    .bodyValue(req)
+                    .exchangeToMono(resp -> resp.bodyToFlux(String.class)
+                            .collectList()
+                            .map(list -> {
+                                String payload = String.join("\n\n", list);
+                                return parseCompletion(payload);
+                            }))
+                    .doOnError(ex -> log.error("璋冪敤 LLM 澶辫触", ex))
+                    .onErrorResume(ex -> Mono.empty())
+                    .block();
+        } catch (Exception e) {
+            log.error("璋冪敤 LLM 澶辫触", e);
+            return null;
+        }
     }
 
     public void chatStream(List<ChatCompletionRequest.Message> messages,
@@ -172,6 +217,207 @@
         });
     }
 
+    public void chatStreamWithTools(List<ChatCompletionRequest.Message> messages,
+                                    Double temperature,
+                                    Integer maxTokens,
+                                    List<Object> tools,
+                                    Consumer<String> onChunk,
+                                    Runnable onComplete,
+                                    Consumer<Throwable> onError) {
+
+        ChatCompletionRequest req = new ChatCompletionRequest();
+        req.setModel(model);
+        req.setMessages(messages);
+        req.setTemperature(temperature != null ? temperature : 0.3);
+        req.setMax_tokens(maxTokens != null ? maxTokens : 1024);
+        req.setStream(true);
+        if (tools != null && !tools.isEmpty()) {
+            req.setTools(tools);
+            req.setTool_choice("auto");
+        }
+
+        Flux<String> flux = llmWebClient.post()
+                .uri("/chat/completions")
+                .header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey)
+                .contentType(MediaType.APPLICATION_JSON)
+                .accept(MediaType.TEXT_EVENT_STREAM)
+                .bodyValue(req)
+                .retrieve()
+                .bodyToFlux(String.class)
+                .doOnError(ex -> log.error("璋冪敤 LLM 娴佸紡澶辫触", ex));
+
+        AtomicBoolean doneSeen = new AtomicBoolean(false);
+        AtomicBoolean errorSeen = new AtomicBoolean(false);
+        LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<>();
+
+        Thread drain = new Thread(() -> {
+            try {
+                while (true) {
+                    String s = queue.poll(5, TimeUnit.SECONDS);
+                    if (s != null) {
+                        try { onChunk.accept(s); } catch (Exception ignore) {}
+                    }
+                    if (doneSeen.get() && queue.isEmpty()) {
+                        if (!errorSeen.get()) {
+                            try { if (onComplete != null) onComplete.run(); } catch (Exception ignore) {}
+                        }
+                        break;
+                    }
+                }
+            } catch (InterruptedException ignore) {
+                ignore.printStackTrace();
+            }
+        });
+        drain.setDaemon(true);
+        drain.start();
+
+        flux.subscribe(payload -> {
+            if (payload == null || payload.isEmpty()) return;
+            String[] events = payload.split("\\r?\\n\\r?\\n");
+            for (String part : events) {
+                String s = part;
+                if (s == null || s.isEmpty()) continue;
+                if (s.startsWith("data:")) {
+                    s = s.substring(5);
+                    if (s.startsWith(" ")) s = s.substring(1);
+                }
+                if ("[DONE]".equals(s.trim())) {
+                    doneSeen.set(true);
+                    continue;
+                }
+                try {
+                    JSONObject obj = JSON.parseObject(s);
+                    JSONArray choices = obj.getJSONArray("choices");
+                    if (choices != null && !choices.isEmpty()) {
+                        JSONObject c0 = choices.getJSONObject(0);
+                        JSONObject delta = c0.getJSONObject("delta");
+                        if (delta != null) {
+                            String content = delta.getString("content");
+                            if (content != null) {
+                                try { queue.offer(content); } catch (Exception ignore) {}
+                            }
+                        }
+                    }
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        }, err -> {
+            errorSeen.set(true);
+            doneSeen.set(true);
+            if (onError != null) onError.accept(err);
+        }, () -> {
+            if (!doneSeen.get()) {
+                errorSeen.set(true);
+                doneSeen.set(true);
+                if (onError != null) onError.accept(new RuntimeException("LLM 娴佹剰澶栧畬鎴�"));
+            } else {
+                doneSeen.set(true);
+            }
+        });
+    }
+
+    public void chatStreamRunPython(String prompt, String chatId, Consumer<String> onChunk,
+                                    Runnable onComplete,
+                                    Consumer<Throwable> onError) {
+        HashMap<String, Object> req = new HashMap<>();
+        req.put("prompt", prompt);
+        req.put("chatId", chatId);
+
+        Flux<String> flux = llmWebClient.post()
+                .uri(pythonPlatformUrl)
+                .header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey)
+                .contentType(MediaType.APPLICATION_JSON)
+                .accept(MediaType.TEXT_EVENT_STREAM)
+                .bodyValue(req)
+                .retrieve()
+                .bodyToFlux(String.class)
+                .doOnError(ex -> log.error("璋冪敤 LLM 娴佸紡澶辫触", ex));
+
+        AtomicBoolean doneSeen = new AtomicBoolean(false);
+        AtomicBoolean errorSeen = new AtomicBoolean(false);
+        LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<>();
+
+        Thread drain = new Thread(() -> {
+            try {
+                while (true) {
+                    String s = queue.poll(2, TimeUnit.SECONDS);
+                    if (s != null) {
+                        try {
+                            onChunk.accept(s);
+                        } catch (Exception ignore) {
+                        }
+                    }
+                    if (doneSeen.get() && queue.isEmpty()) {
+                        if (!errorSeen.get()) {
+                            try {
+                                if (onComplete != null) onComplete.run();
+                            } catch (Exception ignore) {
+                            }
+                        }
+                        break;
+                    }
+                }
+            } catch (InterruptedException ignore) {
+                ignore.printStackTrace();
+            }
+        });
+        drain.setDaemon(true);
+        drain.start();
+
+        flux.subscribe(payload -> {
+            if (payload == null || payload.isEmpty()) return;
+            String[] events = payload.split("\\r?\\n\\r?\\n");
+            for (String part : events) {
+                String s = part;
+                if (s == null || s.isEmpty()) continue;
+                if (s.startsWith("data:")) {
+                    s = s.substring(5);
+                    if (s.startsWith(" ")) s = s.substring(1);
+                }
+                if ("[DONE]".equals(s.trim())) {
+                    doneSeen.set(true);
+                    continue;
+                }
+                if("<think>".equals(s.trim()) || "</think>".equals(s.trim())) {
+                    queue.offer(s.trim());
+                    continue;
+                }
+                try {
+                    JSONObject obj = JSON.parseObject(s);
+                    JSONArray choices = obj.getJSONArray("choices");
+                    if (choices != null && !choices.isEmpty()) {
+                        JSONObject c0 = choices.getJSONObject(0);
+                        JSONObject delta = c0.getJSONObject("delta");
+                        if (delta != null) {
+                            String content = delta.getString("content");
+                            if (content != null) {
+                                try {
+                                    queue.offer(content);
+                                } catch (Exception ignore) {
+                                }
+                            }
+                        }
+                    }
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        }, err -> {
+            errorSeen.set(true);
+            doneSeen.set(true);
+            if (onError != null) onError.accept(err);
+        }, () -> {
+            if (!doneSeen.get()) {
+                errorSeen.set(true);
+                doneSeen.set(true);
+                if (onError != null) onError.accept(new RuntimeException("LLM 娴佹剰澶栧畬鎴�"));
+            } else {
+                doneSeen.set(true);
+            }
+        });
+    }
+
     private ChatCompletionResponse mergeSseChunk(ChatCompletionResponse acc, String payload) {
         if (payload == null || payload.isEmpty()) return acc;
         String[] events = payload.split("\\r?\\n\\r?\\n");
diff --git a/src/main/java/com/zy/ai/service/PythonService.java b/src/main/java/com/zy/ai/service/PythonService.java
new file mode 100644
index 0000000..adc712b
--- /dev/null
+++ b/src/main/java/com/zy/ai/service/PythonService.java
@@ -0,0 +1,55 @@
+package com.zy.ai.service;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
+
+@Slf4j
+@Component
+public class PythonService {
+
+    @Autowired
+    private LlmChatService llmChatService;
+
+    public boolean runPython(String prompt, String chatId, SseEmitter emitter) {
+        try {
+            llmChatService.chatStreamRunPython(prompt, chatId, s -> {
+                try {
+                    String safe = s == null ? "" : s.replace("\r", "").replace("\n", "\\n");
+                    if (!safe.isEmpty()) {
+                        sse(emitter, safe);
+                    }
+                } catch (Exception ignore) {
+                }
+            }, () -> {
+                try {
+                    sse(emitter, "\\n\\n銆怉I銆戣繍琛屽凡鍋滄锛堟甯哥粨鏉燂級\\n\\n");
+                    log.info("AI MCP diagnose stopped: final end");
+                    emitter.complete();
+                } catch (Exception ignore) {
+                }
+            }, e -> {
+                sse(emitter, "\\n\\n銆怉I銆戝垎鏋愬嚭閿欙紝姝e湪鍥為��...\\n\\n");
+            });
+            return true;
+        } catch (Exception e) {
+            try {
+                sse(emitter, "\\n\\n銆怉I銆戣繍琛屽凡鍋滄锛堝紓甯革級\\n\\n");
+                log.error("AI MCP diagnose stopped: error", e);
+                emitter.completeWithError(e);
+            } catch (Exception ignore) {}
+            return true;
+        }
+    }
+
+    private void sse(SseEmitter emitter, String data) {
+        if (data == null) return;
+        try {
+            emitter.send(SseEmitter.event().data(data));
+        } catch (Exception e) {
+            log.warn("SSE send failed", e);
+        }
+    }
+
+}
diff --git a/src/main/java/com/zy/ai/service/WcsDiagnosisService.java b/src/main/java/com/zy/ai/service/WcsDiagnosisService.java
index 1272f6a..189e57d 100644
--- a/src/main/java/com/zy/ai/service/WcsDiagnosisService.java
+++ b/src/main/java/com/zy/ai/service/WcsDiagnosisService.java
@@ -1,15 +1,20 @@
 package com.zy.ai.service;
 
 import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
 import com.zy.ai.entity.ChatCompletionRequest;
+import com.zy.ai.entity.ChatCompletionResponse;
 import com.zy.ai.entity.WcsDiagnosisRequest;
+import com.zy.ai.mcp.controller.McpController;
 import com.zy.ai.utils.AiPromptUtils;
 import com.zy.ai.utils.AiUtils;
 import com.zy.common.utils.RedisUtil;
 import com.zy.core.enums.RedisKeyType;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
 import java.util.ArrayList;
@@ -18,10 +23,13 @@
 
 @Service
 @RequiredArgsConstructor
+@Slf4j
 public class WcsDiagnosisService {
 
     private static final long CHAT_TTL_SECONDS = 7L * 24 * 3600;
 
+    @Value("${llm.platform}")
+    private String platform;
     @Autowired
     private LlmChatService llmChatService;
     @Autowired
@@ -30,31 +38,27 @@
     private AiPromptUtils aiPromptUtils;
     @Autowired
     private AiUtils aiUtils;
-
-    /**
-     * 閽堝鈥滅郴缁熶笉鎵ц浠诲姟 / 涓嶇煡閬撳摢涓澶囨病鍦ㄨ繍琛屸�濈殑閫氱敤 AI 璇婃柇
-     */
-    public String diagnose(WcsDiagnosisRequest request) {
-        List<ChatCompletionRequest.Message> messages = new ArrayList<>();
-
-        // 1. system锛氬畾涔変笓瀹惰韩浠� + 杈撳嚭缁撴瀯
-        ChatCompletionRequest.Message system = new ChatCompletionRequest.Message();
-        system.setRole("system");
-        system.setContent(aiPromptUtils.getAiDiagnosePrompt());
-        messages.add(system);
-
-        ChatCompletionRequest.Message user = new ChatCompletionRequest.Message();
-        user.setRole("user");
-        user.setContent(aiUtils.buildDiagnosisUserContent(request));
-        messages.add(user);
-
-        // 璋冪敤澶фā鍨�
-        return llmChatService.chat(messages, 0.2, 2048);
-    }
+    @Autowired(required = false)
+    private McpController mcpController;
+    @Autowired
+    private PythonService pythonService;
 
     public void diagnoseStream(WcsDiagnosisRequest request, SseEmitter emitter) {
         List<ChatCompletionRequest.Message> messages = new ArrayList<>();
 
+        ChatCompletionRequest.Message mcpSystem = new ChatCompletionRequest.Message();
+        mcpSystem.setRole("system");
+        mcpSystem.setContent(aiPromptUtils.getAiDiagnosePromptMcp());
+
+        ChatCompletionRequest.Message mcpUser = new ChatCompletionRequest.Message();
+        mcpUser.setRole("user");
+        mcpUser.setContent(aiUtils.buildDiagnosisUserContentMcp(request));
+
+        if (runMcpStreamingDiagnosis(messages, mcpSystem, mcpUser, 0.3, 2048, emitter, null)) {
+            return;
+        }
+
+        messages = new ArrayList<>();
         ChatCompletionRequest.Message system = new ChatCompletionRequest.Message();
         system.setRole("system");
         system.setContent(aiPromptUtils.getAiDiagnosePrompt());
@@ -65,7 +69,7 @@
         user.setContent(aiUtils.buildDiagnosisUserContent(request));
         messages.add(user);
 
-        llmChatService.chatStream(messages, 0.2, 2048, s -> {
+        llmChatService.chatStream(messages, 0.3, 2048, s -> {
             try {
                 String safe = s == null ? "" : s.replace("\r", "").replace("\n", "\\n");
                 if (!safe.isEmpty()) {
@@ -73,9 +77,16 @@
                 }
             } catch (Exception ignore) {}
         }, () -> {
-            try { emitter.complete(); } catch (Exception ignore) {}
+            try {
+                log.info("AI diagnose stream stopped: normal end");
+                emitter.complete();
+            } catch (Exception ignore) {}
         }, e -> {
-            try { emitter.completeWithError(e); } catch (Exception ignore) {}
+            try {
+                try { emitter.send(SseEmitter.event().data("銆怉I銆戣繍琛屽凡鍋滄锛堝紓甯革級")); } catch (Exception ignore) {}
+                log.error("AI diagnose stream stopped: error", e);
+                emitter.completeWithError(e);
+            } catch (Exception ignore) {}
         });
     }
 
@@ -84,12 +95,12 @@
                           String chatId,
                           boolean reset,
                           SseEmitter emitter) {
-        List<ChatCompletionRequest.Message> base = new ArrayList<>();
+        if (platform.equals("python")) {
+            pythonService.runPython(prompt, chatId, emitter);
+            return;
+        }
 
-        ChatCompletionRequest.Message system = new ChatCompletionRequest.Message();
-        system.setRole("system");
-        system.setContent(aiPromptUtils.getWcsSensorPrompt());
-        base.add(system);
+        List<ChatCompletionRequest.Message> messages = new ArrayList<>();
 
         List<ChatCompletionRequest.Message> history = null;
         String historyKey = null;
@@ -107,21 +118,11 @@
                     ChatCompletionRequest.Message m = convertToMessage(o);
                     if (m != null) history.add(m);
                 }
-                if (!history.isEmpty()) base.addAll(history);
+                if (!history.isEmpty()) messages.addAll(history);
             } else {
                 history = new ArrayList<>();
             }
         }
-
-        ChatCompletionRequest.Message contextMsg = new ChatCompletionRequest.Message();
-        contextMsg.setRole("user");
-        contextMsg.setContent(aiUtils.buildAskUserContent(request));
-        base.add(contextMsg);
-
-        ChatCompletionRequest.Message questionMsg = new ChatCompletionRequest.Message();
-        questionMsg.setRole("user");
-        questionMsg.setContent("銆愮敤鎴锋彁闂�慭n" + (prompt == null ? "" : prompt));
-        base.add(questionMsg);
 
         StringBuilder assistantBuffer = new StringBuilder();
         final String finalChatId = chatId;
@@ -129,7 +130,30 @@
         final String finalMetaKey = metaKey;
         final String finalPrompt = prompt;
 
-        llmChatService.chatStream(base, 0.2, 2048, s -> {
+        ChatCompletionRequest.Message mcpSystem = new ChatCompletionRequest.Message();
+        mcpSystem.setRole("system");
+        mcpSystem.setContent(aiPromptUtils.getWcsSensorPromptMcp());
+
+        ChatCompletionRequest.Message mcpUser = new ChatCompletionRequest.Message();
+        mcpUser.setRole("user");
+        mcpUser.setContent("銆愮敤鎴锋彁闂�慭n" + (prompt == null ? "" : prompt));
+
+        if (runMcpStreamingDiagnosis(messages, mcpSystem, mcpUser, 0.3, 2048, emitter, finalChatId)) {
+            return;
+        }
+
+        messages = new ArrayList<>();
+        ChatCompletionRequest.Message system = new ChatCompletionRequest.Message();
+        system.setRole("system");
+        system.setContent(aiPromptUtils.getWcsSensorPrompt());
+        messages.add(system);
+
+        ChatCompletionRequest.Message questionMsg = new ChatCompletionRequest.Message();
+        questionMsg.setRole("user");
+        questionMsg.setContent("銆愮敤鎴锋彁闂�慭n" + (prompt == null ? "" : prompt));
+        messages.add(questionMsg);
+
+        llmChatService.chatStream(messages, 0.3, 2048, s -> {
             try {
                 String safe = s == null ? "" : s.replace("\r", "").replace("\n", "\\n");
                 if (!safe.isEmpty()) {
@@ -231,5 +255,294 @@
         String p = prompt.replaceAll("\n", " ").trim();
         return p.length() > 20 ? p.substring(0, 20) : p;
     }
+ 
+    private boolean runMcpStreamingDiagnosis(List<ChatCompletionRequest.Message> baseMessages,
+                                             ChatCompletionRequest.Message systemPrompt,
+                                             ChatCompletionRequest.Message userQuestion,
+                                             Double temperature,
+                                             Integer maxTokens,
+                                             SseEmitter emitter,
+                                             String chatId) {
+        try {
+            if (mcpController == null) return false;
+            List<Object> tools = buildOpenAiTools();
+            if (tools.isEmpty()) return false;
 
+            baseMessages.add(systemPrompt);
+            baseMessages.add(userQuestion);
+ 
+            List<ChatCompletionRequest.Message> messages = new ArrayList<>(baseMessages.size() + 8);
+            messages.addAll(baseMessages);
+ 
+            sse(emitter, "<think>\\n姝e湪鍒濆鍖栬瘖鏂笌宸ュ叿鐜...\\n");
+
+            int maxRound = 10;
+            int i = 0;
+            while(true) {
+                sse(emitter, "\\n姝e湪鍒嗘瀽锛堢" + (i + 1) + "杞級...\\n");
+                ChatCompletionResponse resp = llmChatService.chatCompletion(messages, temperature, maxTokens, tools);
+                if (resp == null || resp.getChoices() == null || resp.getChoices().isEmpty() || resp.getChoices().get(0).getMessage() == null) {
+                    sse(emitter, "\\n鍒嗘瀽鍑洪敊锛屾鍦ㄥ洖閫�...\\n");
+                    return false;
+                }
+
+                ChatCompletionRequest.Message assistant = resp.getChoices().get(0).getMessage();
+                messages.add(assistant);
+                sse(emitter, assistant.getContent());
+
+                List<ChatCompletionRequest.ToolCall> toolCalls = assistant.getTool_calls();
+                if (toolCalls == null || toolCalls.isEmpty()) {
+                    break;
+                }
+
+                for (ChatCompletionRequest.ToolCall tc : toolCalls) {
+                    String toolName = tc != null && tc.getFunction() != null ? tc.getFunction().getName() : null;
+                    if (toolName == null || toolName.trim().isEmpty()) continue;
+                    sse(emitter, "\\n鍑嗗璋冪敤宸ュ叿锛�" + toolName + "\\n");
+                    JSONObject args = new JSONObject();
+                    if (tc.getFunction() != null && tc.getFunction().getArguments() != null && !tc.getFunction().getArguments().trim().isEmpty()) {
+                        try {
+                            args = JSON.parseObject(tc.getFunction().getArguments());
+                        } catch (Exception ignore) {
+                            args = new JSONObject();
+                            args.put("_raw", tc.getFunction().getArguments());
+                        }
+                    }
+                    Object output;
+                    try {
+                        output = mcpController.callTool(toolName, args);
+                    } catch (Exception e) {
+                        java.util.LinkedHashMap<String, Object> err = new java.util.LinkedHashMap<String, Object>();
+                        err.put("tool", toolName);
+                        err.put("error", e.getMessage());
+                        output = err;
+                    }
+                    sse(emitter, "\\n宸ュ叿杩斿洖锛屾鍦ㄧ户缁帹鐞�...\\n");
+                    ChatCompletionRequest.Message toolMsg = new ChatCompletionRequest.Message();
+                    toolMsg.setRole("tool");
+                    toolMsg.setTool_call_id(tc == null ? null : tc.getId());
+                    toolMsg.setContent(JSON.toJSONString(output));
+                    messages.add(toolMsg);
+                }
+
+                if(i++ >= maxRound) break;
+            }
+
+            sse(emitter, "\\n姝e湪鏍规嵁鏁版嵁杩涜鍒嗘瀽...\\n</think>\\n\\n");
+
+            ChatCompletionRequest.Message diagnosisMessage = new ChatCompletionRequest.Message();
+            diagnosisMessage.setRole("system");
+            diagnosisMessage.setContent("鏍规嵁浠ヤ笂淇℃伅杩涜鍒嗘瀽锛屽苟缁欏嚭瀹屾暣鐨勮瘖鏂粨璁恒��");
+            messages.add(diagnosisMessage);
+
+            StringBuilder assistantBuffer = new StringBuilder();
+            llmChatService.chatStreamWithTools(messages, temperature, maxTokens, tools, s -> {
+                try {
+                    String safe = s == null ? "" : s.replace("\r", "").replace("\n", "\\n");
+                    if (!safe.isEmpty()) {
+                        sse(emitter, safe);
+                        assistantBuffer.append(safe);
+                    }
+                } catch (Exception ignore) {}
+            }, () -> {
+                try {
+                    sse(emitter, "\\n\\n銆怉I銆戣繍琛屽凡鍋滄锛堟甯哥粨鏉燂級\\n\\n");
+                    log.info("AI MCP diagnose stopped: final end");
+                    emitter.complete();
+
+                    if (chatId != null) {
+                        String historyKey = RedisKeyType.AI_CHAT_HISTORY.key + chatId;
+                        String metaKey = RedisKeyType.AI_CHAT_META.key + chatId;
+
+                        ChatCompletionRequest.Message a = new ChatCompletionRequest.Message();
+                        a.setRole("assistant");
+                        a.setContent(assistantBuffer.toString());
+                        redisUtil.lSet(historyKey, userQuestion);
+                        redisUtil.lSet(historyKey, a);
+                        redisUtil.expire(historyKey, CHAT_TTL_SECONDS);
+                        Map<Object, Object> old = redisUtil.hmget(metaKey);
+                        Long createdAt = old != null && old.get("createdAt") != null ?
+                                (old.get("createdAt") instanceof Number ? ((Number) old.get("createdAt")).longValue() : Long.valueOf(String.valueOf(old.get("createdAt"))))
+                                : System.currentTimeMillis();
+                        Map<String, Object> meta = new java.util.HashMap<>();
+                        meta.put("chatId", chatId);
+                        meta.put("title", buildTitleFromPrompt(userQuestion.getContent()));
+                        meta.put("createdAt", createdAt);
+                        meta.put("updatedAt", System.currentTimeMillis());
+                        redisUtil.hmset(metaKey, meta, CHAT_TTL_SECONDS);
+                    }
+                } catch (Exception ignore) {}
+            }, e -> {
+                sse(emitter, "\\n\\n銆怉I銆戝垎鏋愬嚭閿欙紝姝e湪鍥為��...\\n\\n");
+            });
+            return true;
+        } catch (Exception e) {
+            try {
+                sse(emitter, "\\n\\n銆怉I銆戣繍琛屽凡鍋滄锛堝紓甯革級\\n\\n");
+                log.error("AI MCP diagnose stopped: error", e);
+                emitter.completeWithError(e);
+            } catch (Exception ignore) {}
+            return true;
+        }
+    }
+
+    private void sse(SseEmitter emitter, String data) {
+        if (data == null) return;
+        try {
+            emitter.send(SseEmitter.event().data(data));
+        } catch (Exception e) {
+            log.warn("SSE send failed", e);
+        }
+    }
+
+    private List<Object> buildOpenAiTools() {
+        if (mcpController == null) return java.util.Collections.emptyList();
+        List<Map<String, Object>> mcpTools = mcpController.listTools();
+        if (mcpTools == null || mcpTools.isEmpty()) return java.util.Collections.emptyList();
+
+        List<Object> tools = new ArrayList<>();
+        for (Map<String, Object> t : mcpTools) {
+            if (t == null) continue;
+            Object name = t.get("name");
+            if (name == null) continue;
+            Object inputSchema = t.get("inputSchema");
+            java.util.LinkedHashMap<String, Object> function = new java.util.LinkedHashMap<String, Object>();
+            function.put("name", String.valueOf(name));
+            Object desc = t.get("description");
+            if (desc != null) function.put("description", String.valueOf(desc));
+            function.put("parameters", inputSchema == null ? new java.util.LinkedHashMap<String, Object>() : inputSchema);
+
+            java.util.LinkedHashMap<String, Object> tool = new java.util.LinkedHashMap<String, Object>();
+            tool.put("type", "function");
+            tool.put("function", function);
+            tools.add(tool);
+        }
+        return tools;
+    }
+
+    private void sendLargeText(SseEmitter emitter, String text) {
+        if (text == null) return;
+        String safe = text.replace("\r", "").replace("\n", "\\n");
+        int chunkSize = 256;
+        int i = 0;
+        while (i < safe.length()) {
+            int end = Math.min(i + chunkSize, safe.length());
+            String part = safe.substring(i, end);
+            if (!part.isEmpty()) {
+                try { emitter.send(SseEmitter.event().data(part)); } catch (Exception ignore) {}
+            }
+            i = end;
+        }
+    }
+
+    private static final java.util.regex.Pattern DSML_INVOKE_PATTERN =
+            java.util.regex.Pattern.compile("<\\uFF5CDSML\\uFF5Cinvoke\\s+name=\\\"([^\\\"]+)\\\"[^>]*>([\\s\\S]*?)</\\uFF5CDSML\\uFF5Cinvoke>", java.util.regex.Pattern.MULTILINE);
+    private static final java.util.regex.Pattern JSON_OBJECT_PATTERN =
+            java.util.regex.Pattern.compile("\\{[\\s\\S]*\\}");
+    private static final java.util.regex.Pattern DSML_PARAM_PATTERN =
+            java.util.regex.Pattern.compile("<\\uFF5CDSML\\uFF5Cparameter\\s+name=\\\"([^\\\"]+)\\\"\\s*([^>]*)>([\\s\\S]*?)</\\uFF5CDSML\\uFF5Cparameter>", java.util.regex.Pattern.MULTILINE);
+
+    private java.util.List<DsmlInvocation> parseDsmlInvocations(String content) {
+        java.util.List<DsmlInvocation> list = new java.util.ArrayList<>();
+        if (content == null || content.isEmpty()) return list;
+        java.util.regex.Matcher m = DSML_INVOKE_PATTERN.matcher(content);
+        while (m.find()) {
+            String name = m.group(1);
+            String inner = m.group(2);
+            com.alibaba.fastjson.JSONObject args = null;
+            if (inner != null) {
+                java.util.regex.Matcher jm = JSON_OBJECT_PATTERN.matcher(inner);
+                if (jm.find()) {
+                    String json = jm.group();
+                    try { args = com.alibaba.fastjson.JSON.parseObject(json); } catch (Exception ignore) {}
+                }
+                java.util.regex.Matcher pm = DSML_PARAM_PATTERN.matcher(inner);
+                while (pm.find()) {
+                    if (args == null) args = new com.alibaba.fastjson.JSONObject();
+                    String pName = pm.group(1);
+                    String attr = pm.group(2);
+                    String valText = pm.group(3);
+                    boolean isString = attr != null && attr.toLowerCase().contains("string=\"true\"");
+                    String t = valText == null ? "" : valText.trim();
+                    if (isString) {
+                        args.put(pName, t);
+                    } else {
+                        if ("true".equalsIgnoreCase(t) || "false".equalsIgnoreCase(t)) {
+                            args.put(pName, Boolean.valueOf(t));
+                        } else {
+                            try {
+                                if (t.contains(".")) {
+                                    args.put(pName, Double.valueOf(t));
+                                } else {
+                                    args.put(pName, Long.valueOf(t));
+                                }
+                            } catch (Exception ex) {
+                                args.put(pName, t);
+                            }
+                        }
+                    }
+                }
+            }
+            DsmlInvocation inv = new DsmlInvocation();
+            inv.name = name;
+            inv.arguments = args;
+            list.add(inv);
+        }
+        return list;
+    }
+
+    private static class DsmlInvocation {
+        String name;
+        com.alibaba.fastjson.JSONObject arguments;
+    }
+
+    private List<DsmlInvocation> buildDefaultStatusInvocations() {
+        List<DsmlInvocation> list = new ArrayList<>();
+        DsmlInvocation crn = new DsmlInvocation();
+        crn.name = "device.get_crn_status";
+        com.alibaba.fastjson.JSONObject a1 = new com.alibaba.fastjson.JSONObject();
+        a1.put("limit", 20);
+        crn.arguments = a1;
+        list.add(crn);
+
+        DsmlInvocation st = new DsmlInvocation();
+        st.name = "device.get_station_status";
+        com.alibaba.fastjson.JSONObject a2 = new com.alibaba.fastjson.JSONObject();
+        a2.put("limit", 20);
+        st.arguments = a2;
+        list.add(st);
+
+        DsmlInvocation rgv = new DsmlInvocation();
+        rgv.name = "device.get_rgv_status";
+        com.alibaba.fastjson.JSONObject a3 = new com.alibaba.fastjson.JSONObject();
+        a3.put("limit", 20);
+        rgv.arguments = a3;
+        list.add(rgv);
+
+        return list;
+    }
+
+    private void ensureStatusCoverage(List<DsmlInvocation> invs) {
+        if (invs == null) return;
+        java.util.Set<String> names = new java.util.HashSet<String>();
+        for (DsmlInvocation d : invs) {
+            if (d != null && d.name != null) names.add(d.name);
+        }
+        if (!names.contains("device.get_crn_status") || !names.contains("device.get_station_status") || !names.contains("device.get_rgv_status")) {
+            List<DsmlInvocation> defaults = buildDefaultStatusInvocations();
+            for (DsmlInvocation d : defaults) {
+                if (!names.contains(d.name)) invs.add(d);
+            }
+        }
+    }
+
+    private boolean isConclusionText(String content) {
+        if (content == null) return false;
+        String c = content;
+        int len = c.length();
+        boolean longEnough = len >= 200;
+        boolean hasAllSections = c.contains("闂姒傝堪") && c.contains("鍙枒璁惧鍒楄〃") && c.contains("鍙兘鍘熷洜") && c.contains("寤鸿鎺掓煡姝ラ") && c.contains("椋庨櫓璇勪及");
+        boolean hasExplicitConclusion = (c.contains("缁撹") || c.contains("璇婃柇缁撴灉")) && longEnough;
+        return hasAllSections || hasExplicitConclusion;
+    }
 }
diff --git a/src/main/java/com/zy/ai/utils/AiPromptUtils.java b/src/main/java/com/zy/ai/utils/AiPromptUtils.java
index 98aae55..3f82e66 100644
--- a/src/main/java/com/zy/ai/utils/AiPromptUtils.java
+++ b/src/main/java/com/zy/ai/utils/AiPromptUtils.java
@@ -6,6 +6,117 @@
 public class AiPromptUtils {
 
     //AI璇婃柇绯荤粺Prompt
+    public String getAiDiagnosePromptMcp() {
+        String prompt = "浣犳槸涓�鍚嶈祫娣� WCS锛堜粨鍌ㄦ帶鍒剁郴缁燂級涓庤嚜鍔ㄥ寲绔嬪簱涓撳锛岀啛鎮夛細鍫嗗灈鏈恒�佽緭閫佺嚎銆佹彁鍗囨満銆佺┛姊溅绛夎澶囩殑浠诲姟鍒嗛厤鍜岃繍琛岄�昏緫锛屼篃鐔熸倝甯歌鐨勭郴缁熷崱姝汇�佷换鍔′笉鎵ц銆佽澶囩┖闂蹭絾鏃犱换鍔$瓑闂妯″紡銆俓n\n" +
+                "浣犲彲浠ユ寜闇�璋冪敤绯荤粺鎻愪緵鐨勫伐鍏蜂互鑾峰彇瀹炴椂鏁版嵁涓庝笂涓嬫枃锛堝伐鍏疯繑鍥� JSON锛夛細\n" +
+                "- 浠诲姟锛歵ask_list\n" +
+                "- 璁惧瀹炴椂鐘舵�侊細device_get_crn_status / device_get_station_status / device_get_rgv_status\n" +
+                "- 鏃ュ織锛歭og_query\n" +
+                "- 璁惧閰嶇疆锛歝onfig_get_device_config\n" +
+                "- 绯荤粺閰嶇疆锛歝onfig_get_system_config\n\n" +
+                "浣跨敤绛栫暐锛歕n" +
+                "1锛夐伩鍏嶈噯娴嬨�傚淇℃伅涓嶈冻锛屽厛璋冪敤鐩稿簲宸ュ叿鏀堕泦蹇呰鏁版嵁锛涘彲澶氳疆璋冪敤銆俓n" +
+                "2锛夊宸ュ叿杩斿洖鐨� JSON 鍏堣繘琛岀粨鏋勫寲褰掔撼锛屾彁鐐煎叧閿瓧娈碉紝鍐嶅仛鎺ㄧ悊銆俓n" +
+                "3锛変紭鍏堥『搴忥細浠诲姟鈫掕澶囩姸鎬佲啋鏃ュ織鈫掗厤缃紱鎸夐渶璋冩暣銆俓n\n" +
+                "浣犲皢鏀跺埌浠ヤ笅鍑犵被鏁版嵁锛歕n" +
+                "1锛変换鍔′俊鎭紙tasks锛夛細褰撳墠寰呮墽琛�/鍦ㄦ墽琛�/鎸傝捣浠诲姟\n" +
+                "2锛夎澶囧疄鏃舵暟鎹紙deviceRealtimeData锛夛細姣忓彴璁惧褰撳墠鐘舵�併�佹槸鍚﹀湪绾裤�佸綋鍓嶄换鍔″彿绛塡n" +
+                "3锛夎澶囬厤缃俊鎭紙deviceConfigs锛夛細璁惧鏄惁鍚敤銆佹湇鍔″尯鍩熴�佸厑璁哥殑浠诲姟绫诲瀷绛塡n" +
+                "4锛夌郴缁熸棩蹇楋紙logs锛夛細鎸夋椂闂撮『搴忕殑鏃ュ織鏂囨湰\n" +
+                "5锛夐澶栦笂涓嬫枃锛坋xtraContext锛夛細濡備粨搴撲唬鐮併�乄CS 鐗堟湰绛塡n\n" +
+                "6锛夌郴缁熼厤缃俊鎭紙systemConfigs锛夛細绯荤粺鐨勯厤缃弬鏁帮紝鏁版嵁搴撹〃鍚峴ys_config\n\n" +
+                "浣犵殑鐩爣鏄細甯姪鐜板満杩愮淮浜哄憳鍒嗘瀽锛屼负浠�涔堢郴缁熷綋鍓嶄笉鎵ц浠诲姟锛屾垨鑰呬换鍔℃墽琛屾晥鐜囧紓甯革紝鎸囧嚭鍙兘鏄摢浜涜澶囧鑷寸殑闂銆俓n\n" +
+                "璇锋寜浠ヤ笅缁撴瀯杈撳嚭璇婃柇缁撴灉锛堜娇鐢ㄧ畝浣撲腑鏂囷級锛歕n" +
+                "1. 闂姒傝堪锛�1-3 鍙ヨ瘽锛屾鎷綋鍓嶇郴缁熺姸鎬侊級\n" +
+                "2. 鍙枒璁惧鍒楄〃锛堝垪鍑� 1-N 涓澶囩紪鍙凤紝骞惰鏄庢瘡涓澶囦负浠�涔堝彲鐤戯紝渚嬪锛氶厤缃鐢�/闀挎椂闂寸┖闂�/鐘舵�佸紓甯�/浠诲姟鍒嗛厤涓嶅埌瀹冪瓑锛塡n" +
+                "3. 鍙兘鍘熷洜锛堜粠浠诲姟鍒嗛厤銆佽澶囩姸鎬併�侀厤缃敊璇�佹帴鍙�/閫氫俊寮傚父绛夎搴︼紝鍒楀嚭 3-7 鏉★級\n" +
+                "4. 寤鸿鎺掓煡姝ラ锛堟楠� 1銆�2銆�3...锛屾瘡姝ヨ灏介噺鍏蜂綋銆佸彲鎿嶄綔锛屼緥濡傦細鍦ㄦ煇椤甸潰鏌ョ湅鏌愬瓧娈点�佹鏌ユ煇涓紑鍏炽�佸姣旀煇涓姸鎬佷綅绛夛級\n" +
+                "5. 椋庨櫓璇勪及锛堣鏄庡綋鍓嶉棶棰樺涓氬姟褰卞搷绋嬪害锛氶珮/涓�/浣庯紝浠ュ強鏄惁闇�瑕佺珛鍗充汉宸ュ共棰勶級\n" +
+                "濡傞渶瑕侀澶栨暟鎹紝璇峰厛璋冪敤鍚堥�傜殑宸ュ叿鍐嶇户缁洖绛斻��";
+        return prompt;
+    }
+
+    //WCS楂樼骇涓撳Prompt
+    public String getWcsSensorPromptMcp() {
+//        String prompt = "浣犳槸涓�鍚嶈祫娣� WCS锛堜粨鍌ㄦ帶鍒剁郴缁燂級涓庤嚜鍔ㄥ寲绔嬪簱涓撳锛岀啛鎮夛細鍫嗗灈鏈恒�佽緭閫佺嚎銆佹彁鍗囨満銆佺┛姊溅绛夎澶囩殑浠诲姟鍒嗛厤鍜岃繍琛岄�昏緫锛屼篃鐔熸倝甯歌鐨勭郴缁熷崱姝汇�佷换鍔′笉鎵ц銆佽澶囩┖闂蹭絾鏃犱换鍔$瓑闂妯″紡銆俓n\n" +
+//                "浣犲彲浠ユ寜闇�璋冪敤绯荤粺鎻愪緵鐨勫伐鍏蜂互鑾峰彇瀹炴椂鏁版嵁涓庝笂涓嬫枃锛堝伐鍏疯繑鍥� JSON锛夛細\n" +
+//                "- 浠诲姟锛歵ask_list\n" +
+//                "- 璁惧瀹炴椂鐘舵�侊細device_get_crn_status / device_get_station_status / device_get_rgv_status\n" +
+//                "- 鏃ュ織锛歭og_query\n" +
+//                "- 璁惧閰嶇疆锛歝onfig_get_device_config\n" +
+//                "- 绯荤粺閰嶇疆锛歝onfig_get_system_config\n\n" +
+//                "璇峰厛鐢ㄥ伐鍏疯幏鍙栧繀瑕佷俊鎭紝鍐嶄互绠�娲併�佹槑纭殑涓枃浣滅瓟锛屽苟鍦ㄩ渶瑕佹椂缁欏嚭鍙墽琛岀殑鎺掓煡寤鸿銆�" +
+//                "濡傞渶瑕侀澶栨暟鎹紝璇峰厛璋冪敤鍚堥�傜殑宸ュ叿鍐嶇户缁洖绛斻��";
+
+        String prompt = "浣犳槸涓�鍚嶈祫娣� WCS锛堜粨鍌ㄦ帶鍒剁郴缁燂級涓庤嚜鍔ㄥ寲绔嬪簱涓撳锛孿n" +
+                "绮鹃�氬爢鍨涙満銆佽緭閫佺嚎銆佹彁鍗囨満銆佺┛姊溅銆丷GV銆佸伐浣嶇瓑璁惧鐨刓n" +
+                "浠诲姟鍒嗛厤銆佽繍琛岀姸鎬佹祦杞笌寮傚父澶勭悊銆俓n" +
+                "\n" +
+                "浣犵殑鑱岃矗鏄細**鍩轰簬瀹炴椂鏁版嵁杩涜宸ョ▼绾ц瘖鏂紝鑰屼笉鏄嚟缁忛獙鐚滄祴銆�**\n" +
+                "\n" +
+                "==================== 宸ヤ綔瑙勫垯锛堥潪甯搁噸瑕侊級 ====================\n" +
+                "\n" +
+                "1. **绂佹鍦ㄦ湭鑾峰彇瀹炴椂鏁版嵁鐨勬儏鍐典笅鐩存帴涓嬬粨璁恒��**\n" +
+                "   - 鑻ラ棶棰樻秹鍙娾�滃綋鍓嶇姸鎬� / 鏄惁鍗℃ / 鏄惁鏈変换鍔� / 鏄惁寮傚父鈥濓紝\n" +
+                "     浣犲繀椤诲厛璋冪敤宸ュ叿鑾峰彇鏁版嵁锛屽啀杩涜鍒嗘瀽銆俓n" +
+                "\n" +
+                "2. **浼樺厛浣跨敤鑱氬悎寮忎俊鎭紝鍏舵鍐嶄娇鐢ㄥ崟椤规煡璇€��**\n" +
+                "   - 鑻ラ渶瑕佹暣浣撳垽鏂郴缁熺姸鎬侊紝璇蜂紭鍏堣皟鐢細\n" +
+                "     鈫� build_diagnosis_snapshot锛堝鍙敤锛塡n" +
+                "   - 鑻ュ彧闇�瑕佸眬閮ㄨˉ鍏呬俊鎭紝鍐嶈皟鐢ㄥ崟椤瑰伐鍏枫�俓n" +
+                "\n" +
+                "3. **褰撲俊鎭笉瓒充互鍒ゆ柇鏃讹紝涓嶅緱鐚滄祴鍘熷洜銆�**\n" +
+                "   - 蹇呴』鏄庣‘鎸囧嚭鈥滅己灏戝摢浜涙暟鎹�濓紝骞惰皟鐢ㄥ搴斿伐鍏疯幏鍙栥�俓n" +
+                "\n" +
+                "4. **宸ュ叿杩斿洖鐨勬暟鎹槸浜嬪疄渚濇嵁锛屽繀椤诲紩鐢ㄥ叾鍏抽敭淇℃伅杩涜鎺ㄧ悊銆�**\n" +
+                "\n" +
+                "==================== 鍙敤宸ュ叿锛堣繑鍥� JSON锛� ====================\n" +
+                "\n" +
+                "銆愪换鍔$浉鍏炽�慭n" +
+                "- task_list               鈥斺�� 鏌ヨ褰撳墠/鍘嗗彶浠诲姟鍙婄姸鎬乗n" +
+                "\n" +
+                "銆愯澶囧疄鏃剁姸鎬併�慭n" +
+                "- device_get_crn_status   鈥斺�� 鍫嗗灈鏈哄疄鏃剁姸鎬乗n" +
+                "- device_get_station_status 鈥斺�� 宸ヤ綅瀹炴椂鐘舵�乗n" +
+                "- device_get_rgv_status   鈥斺�� RGV / 绌挎杞﹀疄鏃剁姸鎬乗n" +
+                "\n" +
+                "銆愭棩蹇椼�慭n" +
+                "- log_query               鈥斺�� 鏌ヨ绯荤粺/璁惧鏃ュ織锛堟寜鏃堕棿/鍏抽敭瀛楋級\n" +
+                "\n" +
+                "銆愰厤缃�慭n" +
+                "- config_get_device_config 鈥斺�� 璁惧閰嶇疆锛堝惎鐢ㄣ�佹ā寮忋�佺瓥鐣ワ級\n" +
+                "- config_get_system_config 鈥斺�� 绯荤粺绾ц皟搴�/绛栫暐閰嶇疆\n" +
+                "\n" +
+                "==================== 鎺ㄨ崘璇婃柇娴佺▼ ====================\n" +
+                "\n" +
+                "褰撴帴鍒拌瘖鏂姹傛椂锛岃閬靛惊浠ヤ笅姝ラ锛歕n" +
+                "\n" +
+                "Step 1\uFE0F鈨� 鏄庣‘璇婃柇鐩爣  \n" +
+                "- 褰撳墠瑕佸垽鏂殑鏄細璁惧鏄惁寮傚父锛熶换鍔℃槸鍚﹀崱姝伙紵璋冨害鏄惁闃诲锛焅n" +
+                "\n" +
+                "Step 2\uFE0F鈨� 璋冪敤蹇呰宸ュ叿鑾峰彇浜嬪疄鏁版嵁  \n" +
+                "- 璁惧鐘舵�� 鈫� 鏄惁鍦ㄧ嚎 / 鏄惁绌洪棽 / 褰撳墠浠诲姟\n" +
+                "- 浠诲姟鐘舵�� 鈫� 鏄惁瀛樺湪寰呮墽琛�/鎸傝捣浠诲姟\n" +
+                "- 鏃ュ織 鈫� 鏄惁瀛樺湪鍏抽敭寮傚父銆佺瓑寰呯‘璁ゃ�佸懡浠ゆ湭鍝嶅簲绛変俊鎭痋n" +
+                "\n" +
+                "Step 3\uFE0F鈨� 鍩轰簬鏁版嵁杩涜閫昏緫鍒嗘瀽  \n" +
+                "- 浣跨敤 WCS 涓撲笟鐭ヨ瘑杩涜鍥犳灉鍒ゆ柇锛堣�岄潪鐚滄祴锛塡n" +
+                "\n" +
+                "Step 4\uFE0F鈨� 杈撳嚭缁撴瀯鍖栫粨璁�  \n" +
+                "- 銆愮幇璞℃�荤粨銆慭n" +
+                "- 銆愬叧閿瘉鎹紙鏉ヨ嚜宸ュ叿杩斿洖锛夈�慭n" +
+                "- 銆愬彲鑳藉師鍥狅紙鎸変紭鍏堢骇锛夈�慭n" +
+                "- 銆愬彲鎵ц鐨勬帓鏌� / 澶勭悊寤鸿銆慭n" +
+                "\n" +
+                "==================== 杈撳嚭瑕佹眰 ====================\n" +
+                "\n" +
+                "- 浣跨敤**绠�娲併�佹槑纭殑涓枃**\n" +
+                "- 閬垮厤娉涙硾鑰岃皥銆侀伩鍏嶁�滃彲鑳�/涔熻鈥濆紡绌烘硾鎻忚堪\n" +
+                "- 鑻ラ渶瑕佽繘涓�姝ユ暟鎹紝璇�**鍏堣皟鐢ㄥ伐鍏凤紝鍐嶇户缁垎鏋�**\n";
+        return prompt;
+    }
+
+    //AI璇婃柇绯荤粺Prompt
     public String getAiDiagnosePrompt() {
         String prompt = "浣犳槸涓�鍚嶈祫娣� WCS锛堜粨鍌ㄦ帶鍒剁郴缁燂級涓庤嚜鍔ㄥ寲绔嬪簱涓撳锛岀啛鎮夛細鍫嗗灈鏈恒�佽緭閫佺嚎銆佹彁鍗囨満銆佺┛姊溅绛夎澶囩殑浠诲姟鍒嗛厤鍜岃繍琛岄�昏緫锛屼篃鐔熸倝甯歌鐨勭郴缁熷崱姝汇�佷换鍔′笉鎵ц銆佽澶囩┖闂蹭絾鏃犱换鍔$瓑闂妯″紡銆俓n\n" +
                 "浣犲皢鏀跺埌浠ヤ笅鍑犵被鏁版嵁锛歕n" +
diff --git a/src/main/java/com/zy/ai/utils/AiUtils.java b/src/main/java/com/zy/ai/utils/AiUtils.java
index a7cdeb9..5c5c83b 100644
--- a/src/main/java/com/zy/ai/utils/AiUtils.java
+++ b/src/main/java/com/zy/ai/utils/AiUtils.java
@@ -265,4 +265,15 @@
         return sb.toString();
     }
 
+    public String buildDiagnosisUserContentMcp(WcsDiagnosisRequest request) {
+        StringBuilder sb = new StringBuilder();
+
+        if (request.getAlarmMessage() != null && !request.getAlarmMessage().isEmpty()) {
+            sb.append("銆愰棶棰樻弿杩般�慭n");
+            sb.append(request.getAlarmMessage()).append("\n\n");
+        }
+
+        return sb.toString();
+    }
+
 }
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 41d4c4c..cc1b962 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -78,15 +78,23 @@
   expireDays: 7
 
 llm:
+  platform: python
+  pythonPlatformUrl: http://127.0.0.1:9000/ai/diagnose/askStream
 #  base-url: https://api.siliconflow.cn/v1
 #  api-key: sk-sxdtebtquwrugzrmaqqqkzdzmrgzhzmplwwuowysdasccent
 #  model: deepseek-ai/DeepSeek-V3.2
-  base-url: http://47.76.147.249:9998/e/7g7kqxxt1ei2un71
-  api-key: app-mP0O6aY5WpbfaHs7BNnjVkli
-  model: deepseek-ai/DeepSeek-V3.2
+#  base-url: http://47.76.147.249:9998/e/7g7kqxxt1ei2un71
+#  api-key: app-mP0O6aY5WpbfaHs7BNnjVkli
+#  model: deepseek-ai/DeepSeek-V3.2
 #  base-url: http://34.2.134.223:3000/v1
 #  api-key: sk-WabrmtOezCFwVo7XvVOrO3QkmfcKG7T7jy0BaVnmQTWm5GXh
 #  model: gemini-3-pro-preview
+#  base-url: http://127.0.0.1:8317/v1
+#  api-key: WznOjAGJNVFKSe9kBZTr
+#  model: gpt-5
+  base-url: https://api.xiaomimimo.com/v1
+  api-key: sk-cw7e4se9cal8cxdgjml8dmtn4pdmqtvfccg5fcermt0ddtys
+  model: mimo-v2-flash
 
 perf:
   methodTiming:
diff --git a/src/main/webapp/views/ai/diagnosis.html b/src/main/webapp/views/ai/diagnosis.html
index d705e67..b424504 100644
--- a/src/main/webapp/views/ai/diagnosis.html
+++ b/src/main/webapp/views/ai/diagnosis.html
@@ -28,6 +28,26 @@
     .time { font-size: 12px; color: #909399; text-align: right; margin-top: 6px; }
     .output .el-card__body { height: 100%; padding: 0; }
     .assistant-running { display: flex; align-items: center; gap: 8px; color: #909399; }
+    details.think-block {
+      border: 1px solid #e4e7ed;
+      border-radius: 4px;
+      padding: 8px;
+      margin: 8px 0;
+      background-color: #fcfcfc;
+    }
+    details.think-block summary {
+      cursor: pointer;
+      color: #909399;
+      font-size: 13px;
+      font-weight: bold;
+      outline: none;
+    }
+    details.think-block .content {
+      margin-top: 8px;
+      color: #606266;
+      font-size: 13px;
+      white-space: pre-wrap;
+    }
   </style>
 </head>
 <body>
@@ -130,6 +150,17 @@
         }
       },
       methods: {
+        renderMarkdown: function(md, streaming) {
+          if (!md) return '';
+          var src = md.replace(/\\n/g, '\n');
+          var openAttr = streaming ? ' open' : '';
+          src = src.replace(/<think>/g, '<details class="think-block"' + openAttr + '><summary>AI娣卞害鎬濊��</summary><div class="content">');
+          src = src.replace(/<\/think>/g, '</div></details>');
+          if (streaming && src.indexOf('<details class="think-block"') >= 0 && src.indexOf('</div></details>') < 0) {
+             src += '</div></details>';
+          }
+          return DOMPurify.sanitize(marked.parse(src));
+        },
         loadChats: function() {
           var self = this;
           fetch(baseUrl + '/ai/diagnose/chats', { headers: { 'token': localStorage.getItem('token') } })
@@ -146,7 +177,7 @@
               var msgs = [];
               for (var i=0;i<arr.length;i++) {
                 var m = arr[i];
-                if (m.role === 'assistant') msgs.push({ role: 'assistant', md: m.content || '', html: DOMPurify.sanitize(marked.parse((m.content||'').replace(/\\n/g,'\n'))), ts: self.nowStr() });
+                if (m.role === 'assistant') msgs.push({ role: 'assistant', md: m.content || '', html: self.renderMarkdown(m.content || '', false), ts: self.nowStr() });
                 else msgs.push({ role: 'user', text: m.content || '', ts: self.nowStr() });
               }
               self.messages = msgs;
@@ -274,8 +305,7 @@
               self.lastRenderTs = now;
               var last = self.messages.length > 0 ? self.messages[self.messages.length - 1] : null;
               if (last && last.role === 'assistant') {
-                var renderSource = (last.md || '').replace(/\\n/g, '\n');
-                last.html = DOMPurify.sanitize(marked.parse(renderSource));
+                last.html = self.renderMarkdown(last.md || '', true);
                 self.$nextTick(function() { self.scrollToBottom(true); });
               }
             }
@@ -298,8 +328,7 @@
             this.typingTimer = null;
           }
           if (last && last.role === 'assistant') {
-            var renderSource = (last.md || '').replace(/\n/g, '\n');
-            last.html = DOMPurify.sanitize(marked.parse(renderSource));
+            last.html = this.renderMarkdown(last.md || '', false);
           }
           this.$nextTick(function() { this.scrollToBottom(true); }.bind(this));
           this.loadChats();

--
Gitblit v1.9.1