| | |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.zy.ai.entity.ChatCompletionRequest; |
| | | import com.zy.ai.entity.WcsDiagnosisRequest; |
| | | import com.zy.common.utils.RedisUtil; |
| | | import com.zy.core.enums.RedisKeyType; |
| | | import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | @Service |
| | | @RequiredArgsConstructor |
| | | public class WcsDiagnosisService { |
| | | |
| | | private final LlmChatService llmChatService; |
| | | private final RedisUtil redisUtil; |
| | | private static final long CHAT_TTL_SECONDS = 7L * 24 * 3600; |
| | | |
| | | /** |
| | | * 针对“系统不执行任务 / 不知道哪个设备没在运行”的通用 AI 诊断 |
| | |
| | | }); |
| | | } |
| | | |
| | | public void askStream(WcsDiagnosisRequest request, |
| | | String prompt, |
| | | String chatId, |
| | | boolean reset, |
| | | SseEmitter emitter) { |
| | | List<ChatCompletionRequest.Message> base = new ArrayList<>(); |
| | | |
| | | ChatCompletionRequest.Message system = new ChatCompletionRequest.Message(); |
| | | system.setRole("system"); |
| | | system.setContent( |
| | | "你是一名资深 WCS(仓储控制系统)与自动化立库专家,熟悉:堆垛机、输送线、提升机、穿梭车等设备的任务分配和运行逻辑,也熟悉常见的系统卡死、任务不执行、设备空闲但无任务等问题模式。\n\n" + |
| | | "在回答用户问题时,需要结合下面给出的系统当前上下文信息(任务、设备实时状态、设备配置、系统日志等),以简洁、明确的中文作答,并在需要时给出可执行的排查建议。" |
| | | ); |
| | | base.add(system); |
| | | |
| | | 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()) base.addAll(history); |
| | | } else { |
| | | history = new ArrayList<>(); |
| | | } |
| | | } |
| | | |
| | | ChatCompletionRequest.Message contextMsg = new ChatCompletionRequest.Message(); |
| | | contextMsg.setRole("user"); |
| | | contextMsg.setContent(buildUserContent(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; |
| | | final String finalHistoryKey = historyKey; |
| | | final String finalMetaKey = metaKey; |
| | | final String finalPrompt = prompt; |
| | | |
| | | llmChatService.chatStream(base, 0.2, 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) {} |
| | | }); |
| | | } |
| | | |
| | | 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); |
| | | } |
| | | } |
| | | } |
| | | return resp; |
| | | } |
| | | |
| | | 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; |
| | | } |
| | | |
| | | 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 String buildUserContent(WcsDiagnosisRequest request) { |
| | | StringBuilder sb = new StringBuilder(); |
| | | |