From bfbb0cbe87bbacbaf3fdfb5fb3caa935ec91da1f Mon Sep 17 00:00:00 2001
From: dubin <bindu_bean@163.com>
Date: 星期六, 10 一月 2026 16:23:36 +0800
Subject: [PATCH] #1
---
src/main/java/com/zy/ai/service/WcsDiagnosisService.java | 622 ++++++++++++++++++++++++++++++++++++++++++++++----------
1 files changed, 507 insertions(+), 115 deletions(-)
diff --git a/src/main/java/com/zy/ai/service/WcsDiagnosisService.java b/src/main/java/com/zy/ai/service/WcsDiagnosisService.java
index 64ad536..189e57d 100644
--- a/src/main/java/com/zy/ai/service/WcsDiagnosisService.java
+++ b/src/main/java/com/zy/ai/service/WcsDiagnosisService.java
@@ -1,156 +1,548 @@
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;
import java.util.List;
+import java.util.Map;
@Service
@RequiredArgsConstructor
+@Slf4j
public class WcsDiagnosisService {
- private final LlmChatService llmChatService;
+ private static final long CHAT_TTL_SECONDS = 7L * 24 * 3600;
- /**
- * 閽堝鈥滅郴缁熶笉鎵ц浠诲姟 / 涓嶇煡閬撳摢涓澶囨病鍦ㄨ繍琛屸�濈殑閫氱敤 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(
- "浣犳槸涓�鍚嶈祫娣� WCS锛堜粨鍌ㄦ帶鍒剁郴缁燂級涓庤嚜鍔ㄥ寲绔嬪簱涓撳锛岀啛鎮夛細鍫嗗灈鏈恒�佽緭閫佺嚎銆佹彁鍗囨満銆佺┛姊溅绛夎澶囩殑浠诲姟鍒嗛厤鍜岃繍琛岄�昏緫锛�" +
- "涔熺啛鎮夊父瑙佺殑绯荤粺鍗℃銆佷换鍔′笉鎵ц銆佽澶囩┖闂蹭絾鏃犱换鍔$瓑闂妯″紡銆俓n\n" +
- "浣犲皢鏀跺埌浠ヤ笅鍑犵被鏁版嵁锛歕n" +
- "1锛変换鍔′俊鎭紙tasks锛夛細褰撳墠寰呮墽琛�/鍦ㄦ墽琛�/鎸傝捣浠诲姟\n" +
- "2锛夎澶囧疄鏃舵暟鎹紙deviceRealtimeData锛夛細姣忓彴璁惧褰撳墠鐘舵�併�佹槸鍚﹀湪绾裤�佸綋鍓嶄换鍔″彿绛塡n" +
- "3锛夎澶囬厤缃俊鎭紙deviceConfigs锛夛細璁惧鏄惁鍚敤銆佹湇鍔″尯鍩熴�佸厑璁哥殑浠诲姟绫诲瀷绛塡n" +
- "4锛夌郴缁熸棩蹇楋紙logs锛夛細鎸夋椂闂撮『搴忕殑鏃ュ織鏂囨湰\n" +
- "5锛夐澶栦笂涓嬫枃锛坋xtraContext锛夛細濡備粨搴撲唬鐮併�乄CS 鐗堟湰绛塡n\n" +
- "浣犵殑鐩爣鏄細甯姪鐜板満杩愮淮浜哄憳鍒嗘瀽锛屼负浠�涔堢郴缁熷綋鍓嶄笉鎵ц浠诲姟锛屾垨鑰呬换鍔℃墽琛屾晥鐜囧紓甯革紝鎸囧嚭鍙兘鏄摢浜涜澶囧鑷寸殑闂銆俓n\n" +
- "璇锋寜浠ヤ笅缁撴瀯杈撳嚭璇婃柇缁撴灉锛堜娇鐢ㄧ畝浣撲腑鏂囷級锛歕n" +
- "1. 闂姒傝堪锛�1-3 鍙ヨ瘽锛屾鎷綋鍓嶇郴缁熺姸鎬侊級\n" +
- "2. 鍙枒璁惧鍒楄〃锛堝垪鍑� 1-N 涓澶囩紪鍙凤紝骞惰鏄庢瘡涓澶囦负浠�涔堝彲鐤戯紝渚嬪锛氶厤缃鐢�/闀挎椂闂寸┖闂�/鐘舵�佸紓甯�/浠诲姟鍒嗛厤涓嶅埌瀹冪瓑锛塡n" +
- "3. 鍙兘鍘熷洜锛堜粠浠诲姟鍒嗛厤銆佽澶囩姸鎬併�侀厤缃敊璇�佹帴鍙�/閫氫俊寮傚父绛夎搴︼紝鍒楀嚭 3-7 鏉★級\n" +
- "4. 寤鸿鎺掓煡姝ラ锛堟楠� 1銆�2銆�3...锛屾瘡姝ヨ灏介噺鍏蜂綋銆佸彲鎿嶄綔锛屼緥濡傦細鍦ㄦ煇椤甸潰鏌ョ湅鏌愬瓧娈点�佹鏌ユ煇涓紑鍏炽�佸姣旀煇涓姸鎬佷綅绛夛級\n" +
- "5. 椋庨櫓璇勪及锛堣鏄庡綋鍓嶉棶棰樺涓氬姟褰卞搷绋嬪害锛氶珮/涓�/浣庯紝浠ュ強鏄惁闇�瑕佺珛鍗充汉宸ュ共棰勶級\n" +
- "6. WCS 閫昏緫浼樺寲寤鸿锛堝鏋滀粠鏃ュ織/鏁版嵁鐪嬪嚭鍙兘鐨勭郴缁熼�昏緫缂洪櫡锛岃缁欏嚭绠�瑕佸缓璁紝渚嬪澧炲姞鏌愪釜闃插憜鏍¢獙銆佸憡璀︺�佺洃鎺х瓑锛塡n"
- );
- messages.add(system);
-
- // 2. user锛氭妸鍏蜂綋鐨勬暟鎹粍缁囨垚鏂囨湰锛圝SON 褰㈠紡鏂逛究妯″瀷鐪嬬粨鏋勶級
- ChatCompletionRequest.Message user = new ChatCompletionRequest.Message();
- user.setRole("user");
- user.setContent(buildUserContent(request));
- messages.add(user);
-
- // 璋冪敤澶фā鍨�
- return llmChatService.chat(messages, 0.2, 2048);
- }
+ @Value("${llm.platform}")
+ private String platform;
+ @Autowired
+ private LlmChatService llmChatService;
+ @Autowired
+ private RedisUtil redisUtil;
+ @Autowired
+ private AiPromptUtils aiPromptUtils;
+ @Autowired
+ private AiUtils aiUtils;
+ @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(
- "浣犳槸涓�鍚嶈祫娣� WCS锛堜粨鍌ㄦ帶鍒剁郴缁燂級涓庤嚜鍔ㄥ寲绔嬪簱涓撳锛岀啛鎮夛細鍫嗗灈鏈恒�佽緭閫佺嚎銆佹彁鍗囨満銆佺┛姊溅绛夎澶囩殑浠诲姟鍒嗛厤鍜岃繍琛岄�昏緫锛屼篃鐔熸倝甯歌鐨勭郴缁熷崱姝汇�佷换鍔′笉鎵ц銆佽澶囩┖闂蹭絾鏃犱换鍔$瓑闂妯″紡銆俓n\n" +
- "浣犲皢鏀跺埌浠ヤ笅鍑犵被鏁版嵁锛歕n" +
- "1锛変换鍔′俊鎭紙tasks锛夛細褰撳墠寰呮墽琛�/鍦ㄦ墽琛�/鎸傝捣浠诲姟\n" +
- "2锛夎澶囧疄鏃舵暟鎹紙deviceRealtimeData锛夛細姣忓彴璁惧褰撳墠鐘舵�併�佹槸鍚﹀湪绾裤�佸綋鍓嶄换鍔″彿绛塡n" +
- "3锛夎澶囬厤缃俊鎭紙deviceConfigs锛夛細璁惧鏄惁鍚敤銆佹湇鍔″尯鍩熴�佸厑璁哥殑浠诲姟绫诲瀷绛塡n" +
- "4锛夌郴缁熸棩蹇楋紙logs锛夛細鎸夋椂闂撮『搴忕殑鏃ュ織鏂囨湰\n" +
- "5锛夐澶栦笂涓嬫枃锛坋xtraContext锛夛細濡備粨搴撲唬鐮併�乄CS 鐗堟湰绛塡n\n" +
- "浣犵殑鐩爣鏄細甯姪鐜板満杩愮淮浜哄憳鍒嗘瀽锛屼负浠�涔堢郴缁熷綋鍓嶄笉鎵ц浠诲姟锛屾垨鑰呬换鍔℃墽琛屾晥鐜囧紓甯革紝鎸囧嚭鍙兘鏄摢浜涜澶囧鑷寸殑闂銆俓n\n" +
- "璇锋寜浠ヤ笅缁撴瀯杈撳嚭璇婃柇缁撴灉锛堜娇鐢ㄧ畝浣撲腑鏂囷級锛歕n" +
- "1. 闂姒傝堪锛�1-3 鍙ヨ瘽锛屾鎷綋鍓嶇郴缁熺姸鎬侊級\n" +
- "2. 鍙枒璁惧鍒楄〃锛堝垪鍑� 1-N 涓澶囩紪鍙凤紝骞惰鏄庢瘡涓澶囦负浠�涔堝彲鐤戯紝渚嬪锛氶厤缃鐢�/闀挎椂闂寸┖闂�/鐘舵�佸紓甯�/浠诲姟鍒嗛厤涓嶅埌瀹冪瓑锛塡n" +
- "3. 鍙兘鍘熷洜锛堜粠浠诲姟鍒嗛厤銆佽澶囩姸鎬併�侀厤缃敊璇�佹帴鍙�/閫氫俊寮傚父绛夎搴︼紝鍒楀嚭 3-7 鏉★級\n" +
- "4. 寤鸿鎺掓煡姝ラ锛堟楠� 1銆�2銆�3...锛屾瘡姝ヨ灏介噺鍏蜂綋銆佸彲鎿嶄綔锛屼緥濡傦細鍦ㄦ煇椤甸潰鏌ョ湅鏌愬瓧娈点�佹鏌ユ煇涓紑鍏炽�佸姣旀煇涓姸鎬佷綅绛夛級\n" +
- "5. 椋庨櫓璇勪及锛堣鏄庡綋鍓嶉棶棰樺涓氬姟褰卞搷绋嬪害锛氶珮/涓�/浣庯紝浠ュ強鏄惁闇�瑕佺珛鍗充汉宸ュ共棰勶級\n" +
- "6. WCS 閫昏緫浼樺寲寤鸿锛堝鏋滀粠鏃ュ織/鏁版嵁鐪嬪嚭鍙兘鐨勭郴缁熼�昏緫缂洪櫡锛岃缁欏嚭绠�瑕佸缓璁紝渚嬪澧炲姞鏌愪釜闃插憜鏍¢獙銆佸憡璀︺�佺洃鎺х瓑锛塡n"
- );
+ system.setContent(aiPromptUtils.getAiDiagnosePrompt());
messages.add(system);
ChatCompletionRequest.Message user = new ChatCompletionRequest.Message();
user.setRole("user");
- user.setContent(buildUserContent(request));
+ user.setContent(aiUtils.buildDiagnosisUserContent(request));
messages.add(user);
- llmChatService.chatStream(messages, 0.2, 2048, s -> {
- try { emitter.send(SseEmitter.event().data(s)); } catch (Exception ignore) {}
+ llmChatService.chatStream(messages, 0.3, 2048, s -> {
+ try {
+ String safe = s == null ? "" : s.replace("\r", "").replace("\n", "\\n");
+ if (!safe.isEmpty()) {
+ emitter.send(SseEmitter.event().data(safe));
+ }
+ } 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 {
+ try { emitter.send(SseEmitter.event().data("銆怉I銆戣繍琛屽凡鍋滄锛堝紓甯革級")); } catch (Exception ignore) {}
+ log.error("AI diagnose stream stopped: error", e);
+ emitter.completeWithError(e);
+ } catch (Exception ignore) {}
+ });
+ }
+
+ public void askStream(WcsDiagnosisRequest request,
+ String prompt,
+ String chatId,
+ boolean reset,
+ SseEmitter emitter) {
+ if (platform.equals("python")) {
+ pythonService.runPython(prompt, chatId, emitter);
+ return;
+ }
+
+ List<ChatCompletionRequest.Message> messages = new ArrayList<>();
+
+ List<ChatCompletionRequest.Message> history = null;
+ String historyKey = null;
+ String metaKey = null;
+ if (chatId != null && !chatId.isEmpty()) {
+ historyKey = RedisKeyType.AI_CHAT_HISTORY.key + chatId;
+ metaKey = RedisKeyType.AI_CHAT_META.key + chatId;
+ if (reset) {
+ redisUtil.del(historyKey, metaKey);
+ }
+ List<Object> stored = redisUtil.lGet(historyKey, 0, -1);
+ if (stored != null && !stored.isEmpty()) {
+ history = new ArrayList<>(stored.size());
+ for (Object o : stored) {
+ ChatCompletionRequest.Message m = convertToMessage(o);
+ if (m != null) history.add(m);
+ }
+ if (!history.isEmpty()) messages.addAll(history);
+ } else {
+ history = new ArrayList<>();
+ }
+ }
+
+ StringBuilder assistantBuffer = new StringBuilder();
+ final String finalChatId = chatId;
+ final String finalHistoryKey = historyKey;
+ final String finalMetaKey = metaKey;
+ final String finalPrompt = prompt;
+
+ 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()) {
+ emitter.send(SseEmitter.event().data(safe));
+ assistantBuffer.append(s);
+ }
+ } catch (Exception ignore) {}
+ }, () -> {
+ try {
+ if (finalChatId != null && !finalChatId.isEmpty()) {
+ ChatCompletionRequest.Message q = new ChatCompletionRequest.Message();
+ q.setRole("user");
+ q.setContent(finalPrompt == null ? "" : finalPrompt);
+ ChatCompletionRequest.Message a = new ChatCompletionRequest.Message();
+ a.setRole("assistant");
+ a.setContent(assistantBuffer.toString());
+ redisUtil.lSet(finalHistoryKey, q);
+ redisUtil.lSet(finalHistoryKey, a);
+ redisUtil.expire(finalHistoryKey, CHAT_TTL_SECONDS);
+ Map<Object, Object> old = redisUtil.hmget(finalMetaKey);
+ 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", finalChatId);
+ meta.put("title", buildTitleFromPrompt(finalPrompt));
+ meta.put("createdAt", createdAt);
+ meta.put("updatedAt", System.currentTimeMillis());
+ redisUtil.hmset(finalMetaKey, meta, CHAT_TTL_SECONDS);
+ }
+ emitter.complete();
+ } catch (Exception ignore) {}
}, e -> {
try { emitter.completeWithError(e); } catch (Exception ignore) {}
});
}
- private String buildUserContent(WcsDiagnosisRequest request) {
- StringBuilder sb = new StringBuilder();
-
- sb.append("銆愰棶棰樻弿杩般�慭n");
- if (request.getAlarmMessage() != null && !request.getAlarmMessage().isEmpty()) {
- sb.append(request.getAlarmMessage()).append("\n\n");
- } else {
- sb.append("绯荤粺褰撳墠涓嶆墽琛屼换鍔★紝浣嗗叿浣撳師鍥犱笉鏄庯紝璇锋牴鎹互涓嬩俊鎭府鍔╁垽鏂�俓n\n");
- }
-
- sb.append("銆愯澶囦俊鎭�慭n");
- sb.append("鍏虫敞璁惧锛堝鏋滄湁鎸囧畾锛�: ")
- .append(request.getCraneNo() != null ? request.getCraneNo() : "鏈寚瀹氾紝闇�鏁翠綋鍒嗘瀽")
- .append("\n\n");
-
- if (request.getExtraContext() != null && !request.getExtraContext().isEmpty()) {
- sb.append("銆愰澶栦笂涓嬫枃 extraContext銆慭n");
- sb.append(JSON.toJSONString(request.getExtraContext(), true)).append("\n\n");
- }
-
- if (request.getTasks() != null && !request.getTasks().isEmpty()) {
- sb.append("銆愪换鍔′俊鎭� tasks銆慭n");
- sb.append("涓嬮潰鏄綋鍓嶇浉鍏充换鍔″垪琛ㄧ殑 JSON 鏁版嵁锛歕n");
- sb.append(JSON.toJSONString(request.getTasks(), true)).append("\n\n");
- } else {
- sb.append("銆愪换鍔′俊鎭� tasks銆慭n");
- sb.append("褰撳墠鏈彁渚涗换鍔′俊鎭�俓n\n");
- }
-
- if (request.getDeviceRealtimeData() != null && !request.getDeviceRealtimeData().isEmpty()) {
- sb.append("銆愯澶囧疄鏃舵暟鎹� deviceRealtimeData銆慭n");
- sb.append("涓嬮潰鏄悇璁惧褰撳墠瀹炴椂鐘舵�佺殑 JSON 鏁版嵁锛歕n");
- sb.append(JSON.toJSONString(request.getDeviceRealtimeData(), true)).append("\n\n");
- } else {
- sb.append("銆愯澶囧疄鏃舵暟鎹� deviceRealtimeData銆慭n");
- sb.append("褰撳墠鏈彁渚涜澶囧疄鏃舵暟鎹�俓n\n");
- }
-
- if (request.getDeviceConfigs() != null && !request.getDeviceConfigs().isEmpty()) {
- sb.append("銆愯澶囬厤缃俊鎭� deviceConfigs銆慭n");
- sb.append("涓嬮潰鏄悇璁惧閰嶇疆鐨� JSON 鏁版嵁锛歕n");
- sb.append(JSON.toJSONString(request.getDeviceConfigs(), true)).append("\n\n");
- } else {
- sb.append("銆愯澶囬厤缃俊鎭� deviceConfigs銆慭n");
- sb.append("褰撳墠鏈彁渚涜澶囬厤缃俊鎭�俓n\n");
- }
-
- sb.append("銆愮郴缁熸棩蹇� logs锛堟寜鏃堕棿椤哄簭锛夈�慭n");
- if (request.getLogs() != null && !request.getLogs().isEmpty()) {
- for (String logLine : request.getLogs()) {
- sb.append(logLine).append("\n");
+ public List<Map<String, Object>> listChats() {
+ java.util.Set<String> keys = redisUtil.scanKeys(RedisKeyType.AI_CHAT_META.key, 1000);
+ List<Map<String, Object>> resp = new ArrayList<>();
+ if (keys != null) {
+ for (String key : keys) {
+ Map<Object, Object> m = redisUtil.hmget(key);
+ if (m != null && !m.isEmpty()) {
+ java.util.HashMap<String, Object> item = new java.util.HashMap<>();
+ for (Map.Entry<Object, Object> e : m.entrySet()) {
+ item.put(String.valueOf(e.getKey()), e.getValue());
+ }
+ String chatId = String.valueOf(item.get("chatId"));
+ String historyKey = RedisKeyType.AI_CHAT_HISTORY.key + chatId;
+ item.put("size", redisUtil.lGetListSize(historyKey));
+ resp.add(item);
+ }
}
- } else {
- sb.append("褰撳墠鏈彁渚涙棩蹇椾俊鎭�俓n");
}
+ return resp;
+ }
- sb.append("\n璇锋牴鎹互涓婃墍鏈変俊鎭紝缁撳悎浣犵殑缁忛獙杩涜鍒嗘瀽璇婃柇銆�");
+ public boolean deleteChat(String chatId) {
+ if (chatId == null || chatId.isEmpty()) return false;
+ String historyKey = RedisKeyType.AI_CHAT_HISTORY.key + chatId;
+ String metaKey = RedisKeyType.AI_CHAT_META.key + chatId;
+ redisUtil.del(historyKey, metaKey);
+ return true;
+ }
- return sb.toString();
+ public List<ChatCompletionRequest.Message> getChatHistory(String chatId) {
+ if (chatId == null || chatId.isEmpty()) return java.util.Collections.emptyList();
+ String historyKey = RedisKeyType.AI_CHAT_HISTORY.key + chatId;
+ List<Object> stored = redisUtil.lGet(historyKey, 0, -1);
+ List<ChatCompletionRequest.Message> result = new ArrayList<>();
+ if (stored != null) {
+ for (Object o : stored) {
+ ChatCompletionRequest.Message m = convertToMessage(o);
+ if (m != null) result.add(m);
+ }
+ }
+ return result;
+ }
+
+ private ChatCompletionRequest.Message convertToMessage(Object o) {
+ if (o instanceof ChatCompletionRequest.Message) {
+ return (ChatCompletionRequest.Message) o;
+ }
+ if (o instanceof Map) {
+ Map<?, ?> map = (Map<?, ?>) o;
+ ChatCompletionRequest.Message m = new ChatCompletionRequest.Message();
+ Object role = map.get("role");
+ Object content = map.get("content");
+ m.setRole(role == null ? null : String.valueOf(role));
+ m.setContent(content == null ? null : String.valueOf(content));
+ return m;
+ }
+ return null;
+ }
+
+ private String buildTitleFromPrompt(String prompt) {
+ if (prompt == null || prompt.isEmpty()) return "鏈懡鍚嶄細璇�";
+ 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;
}
}
--
Gitblit v1.9.1