From 319da488ef058274f8bc6af77a3b398efacdd32b Mon Sep 17 00:00:00 2001
From: Junjie <DELL@qq.com>
Date: 星期四, 11 十二月 2025 16:51:16 +0800
Subject: [PATCH] #AI

---
 src/main/java/com/zy/ai/entity/WcsDiagnosisResponse.java       |   21 ++
 src/main/java/com/zy/ai/entity/DeviceRealTimeData.java         |   14 +
 src/main/java/com/zy/ai/entity/ChatCompletionRequest.java      |   21 ++
 src/main/java/com/zy/ai/controller/WcsDiagnosisController.java |  118 +++++++++++
 src/main/resources/logback-spring.xml                          |    3 
 pom.xml                                                        |    7 
 src/main/java/com/zy/ai/entity/DeviceConfigsData.java          |   14 +
 src/main/java/com/zy/ai/config/LlmConfig.java                  |   20 ++
 src/main/java/com/zy/ai/service/LlmChatService.java            |   63 ++++++
 src/main/java/com/zy/asrs/utils/Utils.java                     |   25 +
 src/main/java/com/zy/ai/entity/ChatCompletionResponse.java     |   45 ++++
 src/main/java/com/zy/ai/log/AiLogAppender.java                 |   53 +++++
 src/main/java/com/zy/ai/service/WcsDiagnosisService.java       |  117 +++++++++++
 src/main/resources/application.yml                             |    5 
 src/main/java/com/zy/ai/entity/WcsDiagnosisRequest.java        |   57 +++++
 15 files changed, 575 insertions(+), 8 deletions(-)

diff --git a/pom.xml b/pom.xml
index 49d2ff1..38b5e6a 100644
--- a/pom.xml
+++ b/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>
diff --git a/src/main/java/com/zy/ai/config/LlmConfig.java b/src/main/java/com/zy/ai/config/LlmConfig.java
new file mode 100644
index 0000000..385286d
--- /dev/null
+++ b/src/main/java/com/zy/ai/config/LlmConfig.java
@@ -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();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/zy/ai/controller/WcsDiagnosisController.java b/src/main/java/com/zy/ai/controller/WcsDiagnosisController.java
new file mode 100644
index 0000000..34167fa
--- /dev/null
+++ b/src/main/java/com/zy/ai/controller/WcsDiagnosisController.java
@@ -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;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/zy/ai/entity/ChatCompletionRequest.java b/src/main/java/com/zy/ai/entity/ChatCompletionRequest.java
new file mode 100644
index 0000000..599bee0
--- /dev/null
+++ b/src/main/java/com/zy/ai/entity/ChatCompletionRequest.java
@@ -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;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/zy/ai/entity/ChatCompletionResponse.java b/src/main/java/com/zy/ai/entity/ChatCompletionResponse.java
new file mode 100644
index 0000000..8f2c167
--- /dev/null
+++ b/src/main/java/com/zy/ai/entity/ChatCompletionResponse.java
@@ -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;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/zy/ai/entity/DeviceConfigsData.java b/src/main/java/com/zy/ai/entity/DeviceConfigsData.java
new file mode 100644
index 0000000..e802b0d
--- /dev/null
+++ b/src/main/java/com/zy/ai/entity/DeviceConfigsData.java
@@ -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;
+
+}
diff --git a/src/main/java/com/zy/ai/entity/DeviceRealTimeData.java b/src/main/java/com/zy/ai/entity/DeviceRealTimeData.java
new file mode 100644
index 0000000..2de1824
--- /dev/null
+++ b/src/main/java/com/zy/ai/entity/DeviceRealTimeData.java
@@ -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;
+
+}
diff --git a/src/main/java/com/zy/ai/entity/WcsDiagnosisRequest.java b/src/main/java/com/zy/ai/entity/WcsDiagnosisRequest.java
new file mode 100644
index 0000000..b49f84b
--- /dev/null
+++ b/src/main/java/com/zy/ai/entity/WcsDiagnosisRequest.java
@@ -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;
+}
\ No newline at end of file
diff --git a/src/main/java/com/zy/ai/entity/WcsDiagnosisResponse.java b/src/main/java/com/zy/ai/entity/WcsDiagnosisResponse.java
new file mode 100644
index 0000000..817ceab
--- /dev/null
+++ b/src/main/java/com/zy/ai/entity/WcsDiagnosisResponse.java
@@ -0,0 +1,21 @@
+package com.zy.ai.entity;
+
+
+import lombok.Data;
+
+/**
+ * WCS AI 璇婃柇鍝嶅簲
+ */
+@Data
+public class WcsDiagnosisResponse {
+
+    /**
+     * AI 璇婃柇瀹屾暣鏂囨湰锛圡arkdown锛�
+     */
+    private String analysis;
+
+    /**
+     * 鍙�夛細淇濈暀鍘熷璇锋眰锛屽墠绔皟璇曠敤
+     */
+    private Object originalRequest;
+}
\ No newline at end of file
diff --git a/src/main/java/com/zy/ai/log/AiLogAppender.java b/src/main/java/com/zy/ai/log/AiLogAppender.java
new file mode 100644
index 0000000..9a25289
--- /dev/null
+++ b/src/main/java/com/zy/ai/log/AiLogAppender.java
@@ -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());
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/zy/ai/service/LlmChatService.java b/src/main/java/com/zy/ai/service/LlmChatService.java
new file mode 100644
index 0000000..b001d0e
--- /dev/null
+++ b/src/main/java/com/zy/ai/service/LlmChatService.java
@@ -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();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/zy/ai/service/WcsDiagnosisService.java b/src/main/java/com/zy/ai/service/WcsDiagnosisService.java
new file mode 100644
index 0000000..9f90c1d
--- /dev/null
+++ b/src/main/java/com/zy/ai/service/WcsDiagnosisService.java
@@ -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锛夐澶栦笂涓嬫枃锛坋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);
+    }
+
+    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();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/zy/asrs/utils/Utils.java b/src/main/java/com/zy/asrs/utils/Utils.java
index f84672c..185f27f 100644
--- a/src/main/java/com/zy/asrs/utils/Utils.java
+++ b/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;
+    }
 }
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 52a49a3..da30edb 100644
--- a/src/main/resources/application.yml
+++ b/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
\ No newline at end of file
diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml
index f72c762..6fa80d8 100644
--- a/src/main/resources/logback-spring.xml
+++ b/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"/>

--
Gitblit v1.9.1