#AI
Junjie
15 小时以前 319da488ef058274f8bc6af77a3b398efacdd32b
#AI
11个文件已添加
4个文件已修改
583 ■■■■■ 已修改文件
pom.xml 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/config/LlmConfig.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/controller/WcsDiagnosisController.java 118 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/entity/ChatCompletionRequest.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/entity/ChatCompletionResponse.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/entity/DeviceConfigsData.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/entity/DeviceRealTimeData.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/entity/WcsDiagnosisRequest.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/entity/WcsDiagnosisResponse.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/log/AiLogAppender.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/service/LlmChatService.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/service/WcsDiagnosisService.java 117 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/utils/Utils.java 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application.yml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/logback-spring.xml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml
@@ -5,7 +5,7 @@
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
        <version>2.5.14</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.zy</groupId>
@@ -126,6 +126,11 @@
            <artifactId>truelicense-core</artifactId>
            <version>1.33</version>
        </dependency>
        <!-- WebClient -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
    </dependencies>
    <build>
src/main/java/com/zy/ai/config/LlmConfig.java
New file
@@ -0,0 +1,20 @@
package com.zy.ai.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
@Configuration
public class LlmConfig {
    @Value("${llm.base-url}")
    private String baseUrl;
    @Bean
    public WebClient llmWebClient() {
        return WebClient.builder()
                .baseUrl(baseUrl)
                .build();
    }
}
src/main/java/com/zy/ai/controller/WcsDiagnosisController.java
New file
@@ -0,0 +1,118 @@
package com.zy.ai.controller;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.zy.ai.entity.DeviceConfigsData;
import com.zy.ai.entity.DeviceRealTimeData;
import com.zy.ai.entity.WcsDiagnosisRequest;
import com.zy.ai.entity.WcsDiagnosisResponse;
import com.zy.ai.log.AiLogAppender;
import com.zy.ai.service.WcsDiagnosisService;
import com.zy.asrs.entity.BasCrnp;
import com.zy.asrs.entity.WrkMast;
import com.zy.asrs.service.BasCrnpService;
import com.zy.asrs.service.WrkMastService;
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 lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/ai/diagnose")
@RequiredArgsConstructor
public class WcsDiagnosisController {
    @Autowired
    private WcsDiagnosisService wcsDiagnosisService;
    @Autowired
    private WrkMastService wrkMastService;
    @Autowired
    private BasCrnpService basCrnpService;
    @GetMapping("/runAi")
    public WcsDiagnosisResponse runAi() {
        WcsDiagnosisRequest request = new WcsDiagnosisRequest();
        request.setAlarmMessage("系统不执行任务");
        List<String> logs = AiLogAppender.getRecentLogs(300);
        request.setLogs(logs);
        List<WrkMast> wrkMasts = wrkMastService.selectList(new EntityWrapper<>());
        request.setTasks(wrkMasts);
        List<DeviceRealTimeData> deviceRealTimeDataList = new ArrayList<>();
        List<DeviceConfigsData> deviceConfigsDataList = new ArrayList<>();
        List<BasCrnp> basCrnps = basCrnpService.selectList(new EntityWrapper<>());
        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);
        }
        request.setDeviceRealtimeData(deviceRealTimeDataList);
        WcsDiagnosisResponse response = diagnose(request);
        return response;
    }
    /**
     * 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;
    }
}
src/main/java/com/zy/ai/entity/ChatCompletionRequest.java
New file
@@ -0,0 +1,21 @@
package com.zy.ai.entity;
import lombok.Data;
import java.util.List;
@Data
public class ChatCompletionRequest {
    private String model;
    private List<Message> messages;
    // 可选参数
    private Double temperature;
    private Integer max_tokens;
    @Data
    public static class Message {
        private String role;    // "user" / "assistant" / "system"
        private String content;
    }
}
src/main/java/com/zy/ai/entity/ChatCompletionResponse.java
New file
@@ -0,0 +1,45 @@
package com.zy.ai.entity;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class ChatCompletionResponse {
    private String id;
    @JsonProperty("object")
    private String objectName;
    private Long created;
    private List<Choice> choices;
    private Usage usage;
    @Data
    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class Choice {
        private Integer index;
        private ChatCompletionRequest.Message message;
        @JsonProperty("finish_reason")
        private String finishReason;
    }
    @Data
    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class Usage {
        @JsonProperty("prompt_tokens")
        private Integer promptTokens;
        @JsonProperty("completion_tokens")
        private Integer completionTokens;
        @JsonProperty("total_tokens")
        private Integer totalTokens;
    }
}
src/main/java/com/zy/ai/entity/DeviceConfigsData.java
New file
@@ -0,0 +1,14 @@
package com.zy.ai.entity;
import lombok.Data;
@Data
public class DeviceConfigsData {
    private Integer deviceNo;
    private String deviceType;
    private Object deviceData;
}
src/main/java/com/zy/ai/entity/DeviceRealTimeData.java
New file
@@ -0,0 +1,14 @@
package com.zy.ai.entity;
import lombok.Data;
@Data
public class DeviceRealTimeData {
    private Integer deviceNo;
    private String deviceType;
    private Object deviceData;
}
src/main/java/com/zy/ai/entity/WcsDiagnosisRequest.java
New file
@@ -0,0 +1,57 @@
package com.zy.ai.entity;
import com.zy.asrs.entity.WrkMast;
import lombok.Data;
import java.util.List;
import java.util.Map;
/**
 * WCS AI 诊断请求
 * 支持:
 * - 任务信息
 * - 设备实时数据
 * - 设备配置信息
 * - 系统日志
 * - 额外上下文
 */
@Data
public class WcsDiagnosisRequest {
    /**
     * 当前关注的设备号(可选,例如堆垛机号=1),如果是整体系统诊断可以不填
     */
    private Integer craneNo;
    /**
     * 当前你观察到的现象/问题描述(可选)
     * 例如:系统不执行任务,不知道哪个设备没在运行
     */
    private String alarmMessage;
    /**
     * 系统日志(按时间顺序)
     */
    private List<String> logs;
    /**
     * 任务信息列表(当前待执行/在执行/挂起任务)
     */
    private List<WrkMast> tasks;
    /**
     * 设备当前实时数据(状态位、运行模式、心跳时间等)
     */
    private List<DeviceRealTimeData> deviceRealtimeData;
    /**
     * 设备配置信息
     */
    private List<DeviceConfigsData> deviceConfigs;
    /**
     * 额外上下文,例如:
     *  warehouseCode, shift, wcsVersion, plcVersion 等
     */
    private Map<String, Object> extraContext;
}
src/main/java/com/zy/ai/entity/WcsDiagnosisResponse.java
New file
@@ -0,0 +1,21 @@
package com.zy.ai.entity;
import lombok.Data;
/**
 * WCS AI 诊断响应
 */
@Data
public class WcsDiagnosisResponse {
    /**
     * AI 诊断完整文本(Markdown)
     */
    private String analysis;
    /**
     * 可选:保留原始请求,前端调试用
     */
    private Object originalRequest;
}
src/main/java/com/zy/ai/log/AiLogAppender.java
New file
@@ -0,0 +1,53 @@
package com.zy.ai.log;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.AppenderBase;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.stream.Collectors;
public class AiLogAppender extends AppenderBase<ILoggingEvent> {
    // 保存最近 2000 条日志
    private static final LinkedBlockingDeque<String> LOG_BUFFER = new LinkedBlockingDeque<>(2000);
    private static final DateTimeFormatter TIME_FORMATTER =
            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")
                    .withZone(ZoneId.systemDefault());
    @Override
    protected void append(ILoggingEvent event) {
        String time = TIME_FORMATTER.format(Instant.ofEpochMilli(event.getTimeStamp()));
        String thread = event.getThreadName();
        String level = event.getLevel().toString();
        String loggerName = event.getLoggerName();
        String message = event.getFormattedMessage();
        String logLine = String.format(
                "%s [%s] %-5s %s - %s",
                time,
                thread,
                level,
                loggerName,
                message
        );
        // 放进环形缓冲区
        if (LOG_BUFFER.remainingCapacity() == 0) {
            LOG_BUFFER.pollFirst(); // 移除最旧的
        }
        LOG_BUFFER.offerLast(logLine);
    }
    public static List<String> getRecentLogs(int limit) {
        int size = LOG_BUFFER.size();
        int skip = Math.max(0, size - limit);
        return LOG_BUFFER.stream()
                .skip(skip)
                .collect(Collectors.toList());
    }
}
src/main/java/com/zy/ai/service/LlmChatService.java
New file
@@ -0,0 +1,63 @@
package com.zy.ai.service;
import com.zy.ai.entity.ChatCompletionRequest;
import com.zy.ai.entity.ChatCompletionResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import java.util.List;
@Slf4j
@Service
@RequiredArgsConstructor
public class LlmChatService {
    private final WebClient llmWebClient;
    @Value("${llm.api-key}")
    private String apiKey;
    @Value("${llm.model}")
    private String model;
    /**
     * 通用对话方法:传入 messages,返回大模型文本回复
     */
    public String chat(List<ChatCompletionRequest.Message> messages,
                       Double temperature,
                       Integer maxTokens) {
        ChatCompletionRequest req = new ChatCompletionRequest();
        req.setModel(model);
        req.setMessages(messages);
        req.setTemperature(temperature != null ? temperature : 0.3);
        req.setMax_tokens(maxTokens != null ? maxTokens : 1024);
        ChatCompletionResponse response = llmWebClient.post()
                .uri("/chat/completions")
                .header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey)
                .contentType(MediaType.APPLICATION_JSON)
                .bodyValue(req)   // 2.5.14 已支持 bodyValue
                .retrieve()
                .bodyToMono(ChatCompletionResponse.class)
                .doOnError(ex -> log.error("调用 LLM 失败", ex))
                .onErrorResume(ex -> Mono.empty())
                .block();
        if (response == null ||
                response.getChoices() == null ||
                response.getChoices().isEmpty() ||
                response.getChoices().get(0).getMessage() == null) {
            return "AI 诊断失败:未获取到有效回复。";
        }
        return response.getChoices().get(0).getMessage().getContent();
    }
}
src/main/java/com/zy/ai/service/WcsDiagnosisService.java
New file
@@ -0,0 +1,117 @@
package com.zy.ai.service;
import com.alibaba.fastjson.JSON;
import com.zy.ai.entity.ChatCompletionRequest;
import com.zy.ai.entity.WcsDiagnosisRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
@RequiredArgsConstructor
public class WcsDiagnosisService {
    private final LlmChatService llmChatService;
    /**
     * 针对“系统不执行任务 / 不知道哪个设备没在运行”的通用 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)额外上下文(extraContext):如仓库代码、WCS 版本等\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:把具体的数据组织成文本(JSON 形式方便模型看结构)
        ChatCompletionRequest.Message user = new ChatCompletionRequest.Message();
        user.setRole("user");
        user.setContent(buildUserContent(request));
        messages.add(user);
        // 调用大模型
        return llmChatService.chat(messages, 0.2, 2048);
    }
    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");
            }
        } else {
            sb.append("当前未提供日志信息。\n");
        }
        sb.append("\n请根据以上所有信息,结合你的经验进行分析诊断。");
        return sb.toString();
    }
}
src/main/java/com/zy/asrs/utils/Utils.java
@@ -20,14 +20,9 @@
import com.zy.core.model.protocol.CrnProtocol;
import com.zy.core.thread.CrnThread;
import java.lang.reflect.Field;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Set;
import java.util.*;
public class Utils {
@@ -212,4 +207,20 @@
        }
        return list;
    }
    public static Map<String, Object> convertObjectToMap(Object obj) {
        Map<String, Object> map = new HashMap<>();
        Class<?> clazz = obj.getClass();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            try {
                map.put(field.getName(), field.get(obj));
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        return map;
    }
}
src/main/resources/application.yml
@@ -65,3 +65,8 @@
  loggingPath: ./stock/out/@pom.build.finalName@/deviceLogs
  # 日志过期时间 单位天
  expireDays: 7
llm:
  base-url: https://api.siliconflow.cn/v1
  api-key: sk-sxdtebtquwrugzrmaqqqkzdzmrgzhzmplwwuowysdasccent
  model: Qwen/Qwen3-VL-32B-Instruct
src/main/resources/logback-spring.xml
@@ -11,6 +11,8 @@
              value="%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(-%5p) ${PID:-} [%15.15t] %-40.40logger{39} : %m%n">
    </property>
    <appender name="AI_LOG" class="com.zy.ai.log.AiLogAppender"/>
    <!--控制台-->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
@@ -55,6 +57,7 @@
    </appender>
    <root level="INFO">
        <appender-ref ref="AI_LOG"/>
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="INFO_FILE"/>
        <appender-ref ref="ERROR_FILE"/>