package com.zy.ai.service.impl; import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.zy.ai.domain.autotune.AutoTuneJobStatus; import com.zy.ai.domain.autotune.AutoTuneTriggerType; import com.zy.ai.entity.AiAutoTuneJob; import com.zy.ai.service.AiAutoTuneJobService; import com.zy.ai.service.AutoTuneAgentService; import com.zy.ai.service.AutoTuneCoordinatorService; import com.zy.asrs.entity.WrkMast; import com.zy.asrs.service.WrkMastService; import com.zy.common.utils.RedisUtil; import com.zy.core.enums.RedisKeyType; import com.zy.core.enums.WrkStsType; import com.zy.system.entity.OperateLog; import com.zy.system.service.ConfigService; import com.zy.system.service.OperateLogService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.util.Arrays; import java.util.Date; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.UUID; @Slf4j @Service("autoTuneCoordinatorService") @RequiredArgsConstructor public class AutoTuneCoordinatorServiceImpl implements AutoTuneCoordinatorService { private static final String CONFIG_ENABLED = "aiAutoTuneEnabled"; private static final String CONFIG_INTERVAL_MINUTES = "aiAutoTuneIntervalMinutes"; private static final String DEFAULT_ENABLED = "N"; private static final int DEFAULT_INTERVAL_MINUTES = 10; private static final int MIN_INTERVAL_MINUTES = 5; private static final int MAX_INTERVAL_MINUTES = 60; private static final int RUNNING_LOCK_SECONDS = 20 * 60; private static final long SYSTEM_USER_ID = 9527L; private static final List FINAL_WRK_STS_LIST = Arrays.asList( WrkStsType.COMPLETE_INBOUND.sts, WrkStsType.SETTLE_INBOUND.sts, WrkStsType.COMPLETE_OUTBOUND.sts, WrkStsType.SETTLE_OUTBOUND.sts, WrkStsType.COMPLETE_LOC_MOVE.sts, WrkStsType.COMPLETE_CRN_MOVE.sts ); private final ConfigService configService; private final WrkMastService wrkMastService; private final AiAutoTuneJobService aiAutoTuneJobService; private final AutoTuneAgentService autoTuneAgentService; private final RedisUtil redisUtil; private final OperateLogService operateLogService; @Override public AutoTuneCoordinatorResult runAutoTuneIfEligible() { if (!isEnabled()) { return AutoTuneCoordinatorResult.skipped("disabled"); } int intervalMinutes = resolveIntervalMinutes(); if (!hasActiveTasks()) { return AutoTuneCoordinatorResult.skipped("no_active_tasks"); } if (isLastTriggerGuardActive()) { return AutoTuneCoordinatorResult.skipped("last_trigger_guard_active"); } AiAutoTuneJob latestSuccessfulJob = latestSuccessfulAutoJob(); if (!isIntervalReached(latestSuccessfulJob, intervalMinutes)) { return AutoTuneCoordinatorResult.skipped("interval_not_reached"); } String lockKey = RedisKeyType.AI_AUTO_TUNE_RUNNING_LOCK.key; String lockToken = UUID.randomUUID().toString(); if (!redisUtil.trySetStringIfAbsent(lockKey, lockToken, RUNNING_LOCK_SECONDS)) { return AutoTuneCoordinatorResult.skipped("running_lock_not_acquired"); } AutoTuneAgentService.AutoTuneAgentResult agentResult = null; try { agentResult = autoTuneAgentService.runAutoTune(AutoTuneTriggerType.AUTO.getCode()); writeOperateLog(agentResult); markNoChangeGuardIfNeeded(latestSuccessfulJob, agentResult, intervalMinutes); return AutoTuneCoordinatorResult.triggered(agentResult); } catch (Exception exception) { log.error("Auto tune coordinator failed to run agent", exception); agentResult = failedAgentResult(exception); writeOperateLog(agentResult); return AutoTuneCoordinatorResult.triggered(agentResult); } finally { redisUtil.compareAndDelete(lockKey, lockToken); } } private boolean isEnabled() { String enabled = configService.getConfigValue(CONFIG_ENABLED, DEFAULT_ENABLED); if (enabled == null) { return false; } String normalized = enabled.trim(); return "Y".equalsIgnoreCase(normalized) || "true".equalsIgnoreCase(normalized) || "1".equals(normalized); } private int resolveIntervalMinutes() { String value = configService.getConfigValue(CONFIG_INTERVAL_MINUTES, String.valueOf(DEFAULT_INTERVAL_MINUTES)); try { int intervalMinutes = Integer.parseInt(value.trim()); if (intervalMinutes < MIN_INTERVAL_MINUTES || intervalMinutes > MAX_INTERVAL_MINUTES) { return DEFAULT_INTERVAL_MINUTES; } return intervalMinutes; } catch (Exception exception) { return DEFAULT_INTERVAL_MINUTES; } } private boolean hasActiveTasks() { QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.and(wrapper -> wrapper.notIn("wrk_sts", FINAL_WRK_STS_LIST).or().isNull("wrk_sts")); return wrkMastService.count(queryWrapper) > 0; } private boolean isLastTriggerGuardActive() { Object guardValue = redisUtil.get(RedisKeyType.AI_AUTO_TUNE_LAST_TRIGGER_GUARD.key); return guardValue != null; } private AiAutoTuneJob latestSuccessfulAutoJob() { QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("status", AutoTuneJobStatus.SUCCESS.getCode()); queryWrapper.eq("trigger_type", AutoTuneTriggerType.AUTO.getCode()); queryWrapper.last("order by coalesce(finish_time, create_time) desc limit 1"); List jobs = aiAutoTuneJobService.list(queryWrapper); if (jobs == null || jobs.isEmpty()) { return null; } return jobs.get(0); } private boolean isIntervalReached(AiAutoTuneJob latestSuccessfulJob, int intervalMinutes) { if (latestSuccessfulJob == null) { return true; } Date latestFinishTime = latestSuccessfulJob.getFinishTime(); if (latestFinishTime == null) { latestFinishTime = latestSuccessfulJob.getCreateTime(); } if (latestFinishTime == null) { return true; } long intervalMillis = intervalMinutes * 60L * 1000L; return System.currentTimeMillis() - latestFinishTime.getTime() >= intervalMillis; } private void markNoChangeGuardIfNeeded(AiAutoTuneJob beforeJob, AutoTuneAgentService.AutoTuneAgentResult agentResult, int intervalMinutes) { if (agentResult == null || !Boolean.TRUE.equals(agentResult.getSuccess())) { return; } AiAutoTuneJob afterJob = latestSuccessfulAutoJob(); if (!isSameJob(beforeJob, afterJob)) { return; } long expireSeconds = intervalMinutes * 60L; redisUtil.set(RedisKeyType.AI_AUTO_TUNE_LAST_TRIGGER_GUARD.key, String.valueOf(System.currentTimeMillis()), expireSeconds); } private boolean isSameJob(AiAutoTuneJob beforeJob, AiAutoTuneJob afterJob) { if (beforeJob == null || afterJob == null) { return beforeJob == afterJob; } return beforeJob.getId() != null && beforeJob.getId().equals(afterJob.getId()); } private AutoTuneAgentService.AutoTuneAgentResult failedAgentResult(Exception exception) { AutoTuneAgentService.AutoTuneAgentResult result = new AutoTuneAgentService.AutoTuneAgentResult(); result.setSuccess(false); result.setTriggerType(AutoTuneTriggerType.AUTO.getCode()); result.setSummary("自动调参后台任务执行异常: " + exception.getMessage()); result.setToolCallCount(0); result.setLlmCallCount(0); result.setPromptTokens(0L); result.setCompletionTokens(0L); result.setTotalTokens(0L); result.setMaxRoundsReached(false); return result; } private void writeOperateLog(AutoTuneAgentService.AutoTuneAgentResult agentResult) { if (agentResult == null) { return; } OperateLog operateLog = new OperateLog(); operateLog.setAction("ai_auto_tune_background_scheduler"); operateLog.setUserId(SYSTEM_USER_ID); operateLog.setIp("system"); operateLog.setRequest(JSON.toJSONString(buildRequestSummary(agentResult))); operateLog.setResponse(JSON.toJSONString(buildResponseSummary(agentResult))); operateLog.setCreateTime(new Date()); operateLogService.save(operateLog); } private Map buildRequestSummary(AutoTuneAgentService.AutoTuneAgentResult agentResult) { Map request = new LinkedHashMap<>(); request.put("trigger", agentResult.getTriggerType()); return request; } private Map buildResponseSummary(AutoTuneAgentService.AutoTuneAgentResult agentResult) { Map response = new LinkedHashMap<>(); response.put("success", agentResult.getSuccess()); response.put("summary", agentResult.getSummary()); response.put("toolCallCount", agentResult.getToolCallCount()); response.put("llmCallCount", agentResult.getLlmCallCount()); response.put("promptTokens", agentResult.getPromptTokens()); response.put("completionTokens", agentResult.getCompletionTokens()); response.put("totalTokens", agentResult.getTotalTokens()); response.put("maxRoundsReached", agentResult.getMaxRoundsReached()); return response; } }