#
Junjie
4 天以前 0a7091b19b9dffecca0e09cd8d30a6b12afa7fab
#
7个文件已修改
609 ■■■■ 已修改文件
src/main/java/com/zy/ai/controller/WcsDiagnosisController.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/entity/WcsDiagnosisRequest.java 44 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/mcp/service/impl/WcsDataFacadeImpl.java 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/mcp/tool/WcsMcpTools.java 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/service/WcsDiagnosisService.java 120 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/utils/AiPromptUtils.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/utils/AiUtils.java 274 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/controller/WcsDiagnosisController.java
@@ -47,8 +47,7 @@
        SseEmitter emitter = new SseEmitter(0L);
        new Thread(() -> {
            try {
                WcsDiagnosisRequest request = aiUtils.makeAiRequest(100, null);
                wcsDiagnosisService.askStream(request, prompt, chatId, reset, emitter);
                wcsDiagnosisService.askStream(prompt, chatId, reset, emitter);
            } catch (Exception e) {
                try { emitter.complete(); } catch (Exception ignore) {}
            }
src/main/java/com/zy/ai/entity/WcsDiagnosisRequest.java
@@ -1,63 +1,33 @@
package com.zy.ai.entity;
import com.zy.asrs.entity.WrkMast;
import com.zy.system.entity.Config;
import lombok.Data;
import java.util.List;
import java.util.Map;
/**
 * WCS AI 诊断请求
 * 支持:
 * - 任务信息
 * - 设备实时数据
 * - 设备配置信息
 * - 系统日志
 * - 额外上下文
 * WCS AI 诊断请求。
 * 仅承载诊断入口信息,实时数据统一由 Agent 通过 MCP 工具获取。
 */
@Data
public class WcsDiagnosisRequest {
    /**
     * 当前关注的设备号(可选,例如堆垛机号=1),如果是整体系统诊断可以不填
     * 当前关注的堆垛机编号(可选)。
     */
    private Integer craneNo;
    /**
     * 当前你观察到的现象/问题描述(可选)
     * 例如:系统不执行任务,不知道哪个设备没在运行
     * 当前观察到的现象/问题描述(可选)。
     */
    private String alarmMessage;
    /**
     * 系统日志(按时间顺序)
     * 建议 Agent 查询日志时优先使用的行数上限(可选)。
     */
    private List<String> logs;
    private Integer logLimit;
    /**
     * 任务信息列表(当前待执行/在执行/挂起任务)
     */
    private List<WrkMast> tasks;
    /**
     * 设备当前实时数据(状态位、运行模式、心跳时间等)
     */
    private List<DeviceRealTimeData> deviceRealtimeData;
    /**
     * 设备配置信息
     */
    private List<DeviceConfigsData> deviceConfigs;
    /**
     * 系统配置信息
     */
    private List<Config> systemConfigs;
    /**
     * 额外上下文,例如:
     *  warehouseCode, shift, wcsVersion, plcVersion 等
     * 额外上下文,例如 warehouseCode、shift、wcsVersion、plcVersion 等。
     */
    private Map<String, Object> extraContext;
}
src/main/java/com/zy/ai/mcp/service/impl/WcsDataFacadeImpl.java
@@ -124,26 +124,83 @@
    @Override
    public Object getTasks(JSONObject args) {
        int crnNo = optInt(args, "crnNo", -1);
        int dualCrnNo = optInt(args, "dualCrnNo", -1);
        int rgvNo = optInt(args, "rgvNo", -1);
        List<Integer> taskNos = optIntList(args, "taskNos");
        int sourceStaNo = optInt(args, "sourceStaNo", -1);
        int staNo = optInt(args, "staNo", -1);
        List<Integer> wrkNos = optIntList(args, "wrkNos");
        if (wrkNos.isEmpty()) {
            wrkNos = optIntList(args, "taskNos");
        }
        List<String> wmsWrkNos = optStrList(args, "wmsWrkNos");
        List<Long> wrkStsList = optLongList(args, "wrkStsList");
        List<Integer> ioTypeList = optIntList(args, "ioTypeList");
        String barcode = optStr(args, "barcode");
        String batch = optStr(args, "batch");
        String sourceLocNo = optStr(args, "sourceLocNo");
        String locNo = optStr(args, "locNo");
        int limit = optInt(args, "limit", 200);
        QueryWrapper<WrkMast> wrapper = new QueryWrapper<>();
        if (taskNos != null && taskNos.size() > 0) {
            wrapper.in("wrk_no", taskNos);
        if (!wrkNos.isEmpty()) {
            wrapper.in("wrk_no", wrkNos);
        }
        if (!wmsWrkNos.isEmpty()) {
            wrapper.in("wms_wrk_no", wmsWrkNos);
        }
        if (!wrkStsList.isEmpty()) {
            wrapper.in("wrk_sts", wrkStsList);
        }
        if (!ioTypeList.isEmpty()) {
            wrapper.in("io_type", ioTypeList);
        }
        if (crnNo != -1) {
            wrapper.eq("crn_no", crnNo);
        }
        if (dualCrnNo != -1) {
            wrapper.eq("dual_crn_no", dualCrnNo);
        }
        if (rgvNo != -1) {
            wrapper.eq("rgv_no", rgvNo);
        }
        if (sourceStaNo != -1) {
            wrapper.eq("source_sta_no", sourceStaNo);
        }
        if (staNo != -1) {
            wrapper.eq("sta_no", staNo);
        }
        if (barcode != null) {
            wrapper.like("barcode", barcode);
        }
        if (batch != null) {
            wrapper.like("batch", batch);
        }
        if (sourceLocNo != null) {
            wrapper.like("source_loc_no", sourceLocNo);
        }
        if (locNo != null) {
            wrapper.like("loc_no", locNo);
        }
        int safeLimit = Math.max(1, Math.min(limit, 500));
        wrapper.orderByDesc("io_time").orderByDesc("appe_time").last("limit " + safeLimit);
        List<WrkMast> tasks = wrkMastService.list(wrapper);
        JSONObject data = new JSONObject();
        data.put("tasks", tasks);
        data.put("count", tasks.size());
        return data;
    }
@@ -310,6 +367,25 @@
        return list;
    }
    private String optStr(JSONObject o, String key) {
        if (o == null || !o.containsKey(key)) return null;
        String value = o.getString(key);
        if (value == null || value.trim().isEmpty()) return null;
        return value.trim();
    }
    private List<Long> optLongList(JSONObject o, String key) {
        if (o == null || !o.containsKey(key)) return Collections.emptyList();
        JSONArray arr = o.getJSONArray(key);
        if (arr == null) return Collections.emptyList();
        List<Long> list = new ArrayList<>();
        for (int i = 0; i < arr.size(); i++) {
            String s = arr.getString(i);
            if (s != null && !s.trim().isEmpty()) list.add(Long.parseLong(s.trim()));
        }
        return list;
    }
    private List<Integer> optIntList(JSONObject o, String key) {
        if (o == null || !o.containsKey(key)) return Collections.emptyList();
        JSONArray arr = o.getJSONArray(key);
src/main/java/com/zy/ai/mcp/tool/WcsMcpTools.java
@@ -32,16 +32,36 @@
        return wcsDataFacade.getRgvDeviceStatus(json().fluentPut("rgvNos", rgvNos));
    }
    @Tool(name = "task_list", description = "通过筛选条件查询任务数据")
    public Object getTasks(
    @Tool(name = "task_query", description = "按任务号、状态、设备、条码、库位等条件查询任务数据")
    public Object queryTasks(
            @ToolParam(description = "内部工作号列表 wrkNos", required = false) List<Integer> wrkNos,
            @ToolParam(description = "WMS任务号列表 wmsWrkNos", required = false) List<String> wmsWrkNos,
            @ToolParam(description = "任务状态列表 wrkStsList", required = false) List<Long> wrkStsList,
            @ToolParam(description = "入出库类型列表 ioTypeList", required = false) List<Integer> ioTypeList,
            @ToolParam(description = "堆垛机编号", required = false) Integer crnNo,
            @ToolParam(description = "双工位堆垛机编号", required = false) Integer dualCrnNo,
            @ToolParam(description = "RGV编号", required = false) Integer rgvNo,
            @ToolParam(description = "任务单号列表", required = false) List<Integer> taskNos,
            @ToolParam(description = "返回条数上限,默认 200", required = false) Integer limit) {
            @ToolParam(description = "条码关键字", required = false) String barcode,
            @ToolParam(description = "批次号关键字", required = false) String batch,
            @ToolParam(description = "源库位关键字", required = false) String sourceLocNo,
            @ToolParam(description = "目标库位关键字", required = false) String locNo,
            @ToolParam(description = "源站号", required = false) Integer sourceStaNo,
            @ToolParam(description = "目标站号", required = false) Integer staNo,
            @ToolParam(description = "返回条数上限,默认 200,最大 500", required = false) Integer limit) {
        return wcsDataFacade.getTasks(json()
                .fluentPut("wrkNos", wrkNos)
                .fluentPut("wmsWrkNos", wmsWrkNos)
                .fluentPut("wrkStsList", wrkStsList)
                .fluentPut("ioTypeList", ioTypeList)
                .fluentPut("crnNo", crnNo)
                .fluentPut("dualCrnNo", dualCrnNo)
                .fluentPut("rgvNo", rgvNo)
                .fluentPut("taskNos", taskNos)
                .fluentPut("barcode", barcode)
                .fluentPut("batch", batch)
                .fluentPut("sourceLocNo", sourceLocNo)
                .fluentPut("locNo", locNo)
                .fluentPut("sourceStaNo", sourceStaNo)
                .fluentPut("staNo", staNo)
                .fluentPut("limit", limit));
    }
src/main/java/com/zy/ai/service/WcsDiagnosisService.java
@@ -35,7 +35,7 @@
    private AiPromptUtils aiPromptUtils;
    @Autowired
    private AiUtils aiUtils;
    @Autowired(required = false)
    @Autowired
    private SpringAiMcpToolManager mcpToolManager;
    public void diagnoseStream(WcsDiagnosisRequest request, SseEmitter emitter) {
@@ -49,44 +49,10 @@
        mcpUser.setRole("user");
        mcpUser.setContent(aiUtils.buildDiagnosisUserContentMcp(request));
        if (runMcpStreamingDiagnosis(messages, mcpSystem, mcpUser, 0.3, 2048, emitter, null)) {
            return;
        runMcpStreamingDiagnosis(messages, mcpSystem, mcpUser, 0.3, 2048, emitter, null);
        }
        messages = new ArrayList<>();
        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);
        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 {
                log.info("AI diagnose stream stopped: normal end");
                emitter.complete();
            } catch (Exception ignore) {}
        }, e -> {
            try {
                try { emitter.send(SseEmitter.event().data("【AI】运行已停止(异常)")); } catch (Exception ignore) {}
                log.error("AI diagnose stream stopped: error", e);
                emitter.complete();
            } catch (Exception ignore) {}
        });
    }
    public void askStream(WcsDiagnosisRequest request,
                          String prompt,
    public void askStream(String prompt,
                          String chatId,
                          boolean reset,
                          SseEmitter emitter) {
@@ -114,11 +80,7 @@
            }
        }
        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");
@@ -128,60 +90,7 @@
        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 {
                try { emitter.send(SseEmitter.event().data("【AI】运行已停止(异常)")); } catch (Exception ignore) {}
                emitter.complete();
            } catch (Exception ignore) {}
        });
        runMcpStreamingDiagnosis(messages, mcpSystem, mcpUser, 0.3, 2048, emitter, finalChatId);
    }
    public List<Map<String, Object>> listChats() {
@@ -249,7 +158,7 @@
        return p.length() > 20 ? p.substring(0, 20) : p;
    }
 
    private boolean runMcpStreamingDiagnosis(List<ChatCompletionRequest.Message> baseMessages,
    private void runMcpStreamingDiagnosis(List<ChatCompletionRequest.Message> baseMessages,
                                             ChatCompletionRequest.Message systemPrompt,
                                             ChatCompletionRequest.Message userQuestion,
                                             Double temperature,
@@ -257,9 +166,13 @@
                                             SseEmitter emitter,
                                             String chatId) {
        try {
            if (mcpToolManager == null) return false;
            if (mcpToolManager == null) {
                throw new IllegalStateException("Spring AI MCP tool manager is unavailable");
            }
            List<Object> tools = mcpToolManager.buildOpenAiTools();
            if (tools.isEmpty()) return false;
            if (tools.isEmpty()) {
                throw new IllegalStateException("No MCP tools registered");
            }
            baseMessages.add(systemPrompt);
            baseMessages.add(userQuestion);
@@ -275,8 +188,7 @@
                sse(emitter, "\\n正在分析(第" + (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;
                    throw new IllegalStateException("LLM returned empty response");
                }
                ChatCompletionRequest.Message assistant = resp.getChoices().get(0).getMessage();
@@ -366,16 +278,18 @@
                    }
                } catch (Exception ignore) {}
            }, e -> {
                sse(emitter, "\\n\\n【AI】分析出错,正在回退...\\n\\n");
                try {
                    sse(emitter, "\\n\\n【AI】分析出错,运行已停止(异常)\\n\\n");
                    log.error("AI MCP diagnose stopped: stream error", e);
                    emitter.complete();
                } catch (Exception ignore) {}
            });
            return true;
        } catch (Exception e) {
            try {
                sse(emitter, "\\n\\n【AI】运行已停止(异常)\\n\\n");
                log.error("AI MCP diagnose stopped: error", e);
                emitter.complete();
            } catch (Exception ignore) {}
            return true;
        }
    }
src/main/java/com/zy/ai/utils/AiPromptUtils.java
@@ -9,7 +9,7 @@
    public String getAiDiagnosePromptMcp() {
        String prompt = "你是一名资深 WCS(仓储控制系统)与自动化立库专家,熟悉:堆垛机、输送线、提升机、穿梭车等设备的任务分配和运行逻辑,也熟悉常见的系统卡死、任务不执行、设备空闲但无任务等问题模式。\n\n" +
                "你可以按需调用系统提供的工具以获取实时数据与上下文(工具返回 JSON):\n" +
                "- 任务:task_list\n" +
                "- 任务:task_query\n" +
                "- 设备实时状态:device_get_crn_status / device_get_station_status / device_get_rgv_status\n" +
                "- 日志:log_query\n" +
                "- 设备配置:config_get_device_config\n" +
@@ -18,13 +18,6 @@
                "1)避免臆测。如信息不足,先调用相应工具收集必要数据;可多轮调用。\n" +
                "2)对工具返回的 JSON 先进行结构化归纳,提炼关键字段,再做推理。\n" +
                "3)优先顺序:任务→设备状态→日志→配置;按需调整。\n\n" +
                "你将收到以下几类数据:\n" +
                "1)任务信息(tasks):当前待执行/在执行/挂起任务\n" +
                "2)设备实时数据(deviceRealtimeData):每台设备当前状态、是否在线、当前任务号等\n" +
                "3)设备配置信息(deviceConfigs):设备是否启用、服务区域、允许的任务类型等\n" +
                "4)系统日志(logs):按时间顺序的日志文本\n" +
                "5)额外上下文(extraContext):如仓库代码、WCS 版本等\n\n" +
                "6)系统配置信息(systemConfigs):系统的配置参数,数据库表名sys_config\n\n" +
                "你的目标是:帮助现场运维人员分析,为什么系统当前不执行任务,或者任务执行效率异常,指出可能是哪些设备导致的问题。\n\n" +
                "请按以下结构输出诊断结果(使用简体中文):\n" +
                "1. 问题概述(1-3 句话,概括当前系统状态)\n" +
@@ -38,16 +31,6 @@
    //WCS高级专家Prompt
    public String getWcsSensorPromptMcp() {
//        String prompt = "你是一名资深 WCS(仓储控制系统)与自动化立库专家,熟悉:堆垛机、输送线、提升机、穿梭车等设备的任务分配和运行逻辑,也熟悉常见的系统卡死、任务不执行、设备空闲但无任务等问题模式。\n\n" +
//                "你可以按需调用系统提供的工具以获取实时数据与上下文(工具返回 JSON):\n" +
//                "- 任务:task_list\n" +
//                "- 设备实时状态:device_get_crn_status / device_get_station_status / device_get_rgv_status\n" +
//                "- 日志:log_query\n" +
//                "- 设备配置:config_get_device_config\n" +
//                "- 系统配置:config_get_system_config\n\n" +
//                "请先用工具获取必要信息,再以简洁、明确的中文作答,并在需要时给出可执行的排查建议。" +
//                "如需要额外数据,请先调用合适的工具再继续回答。";
        String prompt = "你是一名资深 WCS(仓储控制系统)与自动化立库专家,\n" +
                "精通堆垛机、输送线、提升机、穿梭车、RGV、工位等设备的\n" +
                "任务分配、运行状态流转与异常处理。\n" +
@@ -60,10 +43,9 @@
                "   - 若问题涉及“当前状态 / 是否卡死 / 是否有任务 / 是否异常”,\n" +
                "     你必须先调用工具获取数据,再进行分析。\n" +
                "\n" +
                "2. **优先使用聚合式信息,其次再使用单项查询。**\n" +
                "   - 若需要整体判断系统状态,请优先调用:\n" +
                "     → build_diagnosis_snapshot(如可用)\n" +
                "   - 若只需要局部补充信息,再调用单项工具。\n" +
                "2. **优先使用最少且最相关的工具调用。**\n" +
                "   - 整体诊断时,先查任务与关键设备状态。\n" +
                "   - 需要补证据时,再查日志或配置。\n" +
                "\n" +
                "3. **当信息不足以判断时,不得猜测原因。**\n" +
                "   - 必须明确指出“缺少哪些数据”,并调用对应工具获取。\n" +
@@ -73,7 +55,7 @@
                "==================== 可用工具(返回 JSON) ====================\n" +
                "\n" +
                "【任务相关】\n" +
                "- task_list               —— 查询当前/历史任务及状态\n" +
                "- task_query              —— 按任务号、状态、设备、条码、库位等条件查询任务\n" +
                "\n" +
                "【设备实时状态】\n" +
                "- device_get_crn_status   —— 堆垛机实时状态\n" +
@@ -115,32 +97,4 @@
                "- 若需要进一步数据,请**先调用工具,再继续分析**\n";
        return prompt;
    }
    //AI诊断系统Prompt
    public String getAiDiagnosePrompt() {
        String prompt = "你是一名资深 WCS(仓储控制系统)与自动化立库专家,熟悉:堆垛机、输送线、提升机、穿梭车等设备的任务分配和运行逻辑,也熟悉常见的系统卡死、任务不执行、设备空闲但无任务等问题模式。\n\n" +
                "你将收到以下几类数据:\n" +
                "1)任务信息(tasks):当前待执行/在执行/挂起任务\n" +
                "2)设备实时数据(deviceRealtimeData):每台设备当前状态、是否在线、当前任务号等\n" +
                "3)设备配置信息(deviceConfigs):设备是否启用、服务区域、允许的任务类型等\n" +
                "4)系统日志(logs):按时间顺序的日志文本\n" +
                "5)额外上下文(extraContext):如仓库代码、WCS 版本等\n\n" +
                "6)系统配置信息(systemConfigs):系统的配置参数,数据库表名sys_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 getWcsSensorPrompt() {
        String prompt = "你是一名资深 WCS(仓储控制系统)与自动化立库专家,熟悉:堆垛机、输送线、提升机、穿梭车等设备的任务分配和运行逻辑,也熟悉常见的系统卡死、任务不执行、设备空闲但无任务等问题模式。\n\n" +
                "在回答用户问题时,需要结合下面给出的系统当前上下文信息(任务、设备实时状态、设备配置、系统日志、系统配置等),以简洁、明确的中文作答,并在需要时给出可执行的排查建议。";
        return prompt;
    }
}
src/main/java/com/zy/ai/utils/AiUtils.java
@@ -1,279 +1,47 @@
package com.zy.ai.utils;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zy.ai.entity.DeviceConfigsData;
import com.zy.ai.entity.DeviceRealTimeData;
import com.zy.ai.entity.WcsDiagnosisRequest;
import com.zy.ai.log.AiLogAppender;
import com.zy.asrs.entity.BasCrnp;
import com.zy.asrs.entity.BasDevp;
import com.zy.asrs.entity.WrkMast;
import com.zy.asrs.service.BasCrnpService;
import com.zy.asrs.service.BasDevpService;
import com.zy.asrs.service.WrkMastService;
import com.zy.common.utils.RedisUtil;
import com.zy.core.cache.SlaveConnection;
import com.zy.core.enums.SlaveType;
import com.zy.core.model.StationObjModel;
import com.zy.core.model.protocol.CrnProtocol;
import com.zy.core.model.protocol.StationProtocol;
import com.zy.core.thread.CrnThread;
import com.zy.core.thread.StationThread;
import com.zy.system.entity.Config;
import com.zy.system.service.ConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Component
public class AiUtils {
    @Autowired
    private WrkMastService wrkMastService;
    @Autowired
    private BasCrnpService basCrnpService;
    @Autowired
    private BasDevpService basDevpService;
    @Autowired
    private ConfigService configService;
    @Autowired
    private RedisUtil redisUtil;
    public WcsDiagnosisRequest makeAiRequest(int logLimit, String alarmMessage) {
        WcsDiagnosisRequest request = new WcsDiagnosisRequest();
        request.setAlarmMessage(alarmMessage);
        List<String> logs = AiLogAppender.getRecentLogs(logLimit);
        request.setLogs(logs);
        List<WrkMast> wrkMasts = wrkMastService.list(new QueryWrapper<>());
        request.setTasks(wrkMasts);
        List<DeviceRealTimeData> deviceRealTimeDataList = new ArrayList<>();
        List<DeviceConfigsData> deviceConfigsDataList = new ArrayList<>();
        List<BasCrnp> basCrnps = basCrnpService.list(new QueryWrapper<>());
        for (BasCrnp basCrnp : basCrnps) {
            CrnThread crnThread = (CrnThread) SlaveConnection.get(SlaveType.Crn, basCrnp.getCrnNo());
            if (crnThread == null) {
                continue;
            }
            CrnProtocol protocol = crnThread.getStatus();
            for (StationObjModel stationObjModel : basCrnp.getInStationList$()) {
                StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, stationObjModel.getDeviceNo());
                if (stationThread == null) {
                    continue;
                }
                Map<Integer, StationProtocol> map = stationThread.getStatusMap();
                StationProtocol stationProtocol = map.get(stationObjModel.getStationId());
                if (stationProtocol == null) {
                    continue;
                }
                DeviceRealTimeData stationData = new DeviceRealTimeData();
                stationData.setDeviceNo(stationObjModel.getDeviceNo());
                stationData.setDeviceType(String.valueOf(SlaveType.Devp));
                stationData.setDeviceData(stationProtocol);
                deviceRealTimeDataList.add(stationData);
            }
            DeviceRealTimeData deviceRealTimeData = new DeviceRealTimeData();
            deviceRealTimeData.setDeviceNo(basCrnp.getCrnNo());
            deviceRealTimeData.setDeviceType(String.valueOf(SlaveType.Crn));
            deviceRealTimeData.setDeviceData(protocol);
            deviceRealTimeDataList.add(deviceRealTimeData);
            DeviceConfigsData deviceConfigsData = new DeviceConfigsData();
            deviceConfigsData.setDeviceNo(basCrnp.getCrnNo());
            deviceConfigsData.setDeviceType(String.valueOf(SlaveType.Crn));
            deviceConfigsData.setDeviceData(basCrnp);
            deviceConfigsDataList.add(deviceConfigsData);
        }
        List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<>());
        for (BasDevp basDevp : basDevps) {
            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
            if (stationThread == null) {
                continue;
            }
            Map<Integer, StationProtocol> map = stationThread.getStatusMap();
            for (StationObjModel stationObjModel : basDevp.getInStationList$()) {
                StationProtocol stationProtocol = map.get(stationObjModel.getStationId());
                if (stationProtocol == null) {
                    continue;
                }
                DeviceRealTimeData stationData = new DeviceRealTimeData();
                stationData.setDeviceNo(stationObjModel.getDeviceNo());
                stationData.setDeviceType(String.valueOf(SlaveType.Devp));
                stationData.setDeviceData(stationProtocol);
                deviceRealTimeDataList.add(stationData);
            }
            for (StationObjModel stationObjModel : basDevp.getOutStationList$()) {
                StationProtocol stationProtocol = map.get(stationObjModel.getStationId());
                if (stationProtocol == null) {
                    continue;
                }
                DeviceRealTimeData stationData = new DeviceRealTimeData();
                stationData.setDeviceNo(stationObjModel.getDeviceNo());
                stationData.setDeviceType(String.valueOf(SlaveType.Devp));
                stationData.setDeviceData(stationProtocol);
                deviceRealTimeDataList.add(stationData);
            }
            //剔除全部输送站点信息,以免数据量过大,后期看实际情况是否打开
            basDevp.setStationList(null);
            DeviceConfigsData deviceConfigsData = new DeviceConfigsData();
            deviceConfigsData.setDeviceNo(basDevp.getDevpNo());
            deviceConfigsData.setDeviceType(String.valueOf(SlaveType.Devp));
            deviceConfigsData.setDeviceData(basDevp);
            deviceConfigsDataList.add(deviceConfigsData);
        }
        request.setDeviceRealtimeData(deviceRealTimeDataList);
        request.setDeviceConfigs(deviceConfigsDataList);
        List<Config> systemConfigList = configService.list(new QueryWrapper<Config>().ne("code", "dingdingReportUrl"));
        request.setSystemConfigs(systemConfigList);
        request.setLogLimit(logLimit);
        return request;
    }
    public String buildDiagnosisUserContent(WcsDiagnosisRequest request) {
        StringBuilder sb = new StringBuilder();
        if (request.getAlarmMessage() != null && !request.getAlarmMessage().isEmpty()) {
            sb.append("【问题描述】\n");
            sb.append(request.getAlarmMessage()).append("\n\n");
        }
        sb.append("【设备信息】\n");
        sb.append("关注设备(如果有指定): ")
                .append(request.getCraneNo() != null ? request.getCraneNo() : "未指定,需整体分析")
                .append("\n\n");
        Object pseudo = redisUtil.get(com.zy.core.enums.RedisKeyType.MAIN_PROCESS_PSEUDOCODE.key);
        if (pseudo != null) {
            sb.append("【主流程伪代码 mainProcessPseudo】\n");
            sb.append(String.valueOf(pseudo)).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");
        }
        if (request.getLogs() != null && !request.getLogs().isEmpty()) {
            sb.append("【系统日志 logs(按时间顺序)】\n");
            for (String logLine : request.getLogs()) {
                sb.append(logLine).append("\n");
            }
        } else {
            sb.append("【系统日志 logs(按时间顺序)】\n");
            sb.append("当前未提供日志信息。\n");
        }
        if (request.getSystemConfigs() != null && !request.getSystemConfigs().isEmpty()) {
            sb.append("【系统配置 sys_config】\n");
            sb.append("下面是各系统配置的 JSON 数据:\n");
            sb.append(JSON.toJSONString(request.getSystemConfigs(), true)).append("\n\n");
        }
        sb.append("\n请根据以上所有信息,结合你的经验进行分析诊断。");
        return sb.toString();
    }
    public String buildAskUserContent(WcsDiagnosisRequest request) {
        StringBuilder sb = new StringBuilder();
        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");
        }
        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");
        }
        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");
        }
        if (request.getLogs() != null && !request.getLogs().isEmpty()) {
            sb.append("【系统日志 logs(按时间顺序)】\n");
            for (String logLine : request.getLogs()) {
                sb.append(logLine).append("\n");
            }
        }
        if (request.getSystemConfigs() != null && !request.getSystemConfigs().isEmpty()) {
            sb.append("【系统配置 sys_config】\n");
            sb.append("下面是各系统配置的 JSON 数据:\n");
            sb.append(JSON.toJSONString(request.getSystemConfigs(), true)).append("\n\n");
        }
        return sb.toString();
    }
    public String buildDiagnosisUserContentMcp(WcsDiagnosisRequest request) {
        StringBuilder sb = new StringBuilder();
        if (request == null) {
            return "";
        }
        if (request.getAlarmMessage() != null && !request.getAlarmMessage().isEmpty()) {
            sb.append("【问题描述】\n");
            sb.append(request.getAlarmMessage()).append("\n\n");
        }
        return sb.toString();
        if (request.getCraneNo() != null) {
            sb.append("【诊断范围】\n");
            sb.append("当前重点关注堆垛机:").append(request.getCraneNo()).append("\n\n");
    }
        if (request.getLogLimit() != null && request.getLogLimit() > 0) {
            sb.append("【查询建议】\n");
            sb.append("如需日志,请优先查询最近 ").append(request.getLogLimit()).append(" 行。\n\n");
        }
        if (request.getExtraContext() != null && !request.getExtraContext().isEmpty()) {
            sb.append("【额外上下文 extraContext】\n");
            sb.append(JSON.toJSONString(request.getExtraContext(), true)).append("\n\n");
        }
        sb.append("请先通过 MCP 工具获取任务、设备、日志和配置等实时数据,再基于事实分析。\n");
        return sb.toString();
    }
}