Junjie
2 天以前 63b01db83d9aad8a15276b4236a9a22e4aeef065
src/main/java/com/zy/ai/service/impl/AutoTuneAgentServiceImpl.java
@@ -3,6 +3,7 @@
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.zy.ai.domain.autotune.AutoTuneApplyResult;
import com.zy.ai.domain.autotune.AutoTuneControlModeSnapshot;
import com.zy.ai.domain.autotune.AutoTuneTriggerType;
import com.zy.ai.entity.AiPromptTemplate;
import com.zy.ai.entity.ChatCompletionRequest;
@@ -11,7 +12,9 @@
import com.zy.ai.mcp.service.SpringAiMcpToolManager;
import com.zy.ai.service.AiPromptTemplateService;
import com.zy.ai.service.AutoTuneAgentService;
import com.zy.ai.service.AutoTuneControlModeService;
import com.zy.ai.service.LlmChatService;
import com.zy.ai.utils.AiPromptUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -46,10 +49,12 @@
    private final LlmChatService llmChatService;
    private final SpringAiMcpToolManager mcpToolManager;
    private final AiPromptTemplateService aiPromptTemplateService;
    private final AutoTuneControlModeService autoTuneControlModeService;
    @Override
    public AutoTuneAgentResult runAutoTune(String triggerType) {
        String normalizedTriggerType = normalizeTriggerType(triggerType);
        AutoTuneControlModeSnapshot controlMode = buildControlModeSnapshot();
        UsageCounter usageCounter = new UsageCounter();
        RunState runState = new RunState();
        boolean maxRoundsReached = false;
@@ -62,7 +67,7 @@
            }
            AiPromptTemplate promptTemplate = aiPromptTemplateService.resolvePublished(AiPromptScene.AUTO_TUNE_DISPATCH.getCode());
            List<ChatCompletionRequest.Message> messages = buildMessages(promptTemplate, normalizedTriggerType);
            List<ChatCompletionRequest.Message> messages = buildMessages(promptTemplate, normalizedTriggerType, controlMode);
            for (int round = 0; round < MAX_TOOL_ROUNDS; round++) {
                ChatCompletionResponse response = llmChatService.chatCompletionOrThrow(messages, TEMPERATURE, MAX_TOKENS, tools);
@@ -74,7 +79,7 @@
                List<ChatCompletionRequest.ToolCall> toolCalls = assistantMessage.getTool_calls();
                if (toolCalls == null || toolCalls.isEmpty()) {
                    return buildResult(runState.isSuccessful(), normalizedTriggerType, summaryBuffer, runState,
                            usageCounter, false);
                            usageCounter, false, controlMode);
                }
                for (ChatCompletionRequest.ToolCall toolCall : toolCalls) {
@@ -83,16 +88,20 @@
                }
            }
            maxRoundsReached = true;
            return buildResult(false, normalizedTriggerType, summaryBuffer, runState, usageCounter, maxRoundsReached);
            return buildResult(false, normalizedTriggerType, summaryBuffer, runState, usageCounter, maxRoundsReached,
                    controlMode);
        } catch (Exception exception) {
            log.error("Auto tune agent stopped with error", exception);
            appendSummary(summaryBuffer, "自动调参 Agent 执行异常: " + exception.getMessage());
            runState.markToolError();
            return buildResult(false, normalizedTriggerType, summaryBuffer, runState, usageCounter, maxRoundsReached);
            return buildResult(false, normalizedTriggerType, summaryBuffer, runState, usageCounter, maxRoundsReached,
                    controlMode);
        }
    }
    private List<ChatCompletionRequest.Message> buildMessages(AiPromptTemplate promptTemplate, String triggerType) {
    private List<ChatCompletionRequest.Message> buildMessages(AiPromptTemplate promptTemplate,
                                                              String triggerType,
                                                              AutoTuneControlModeSnapshot controlMode) {
        List<ChatCompletionRequest.Message> messages = new ArrayList<>();
        ChatCompletionRequest.Message systemMessage = new ChatCompletionRequest.Message();
@@ -102,15 +111,7 @@
        ChatCompletionRequest.Message userMessage = new ChatCompletionRequest.Message();
        userMessage.setRole("user");
        userMessage.setContent("请执行一次后台 WCS 自动调参。triggerType=" + triggerType
                + "。必须先调用 wcs_local_dispatch_get_auto_tune_snapshot 获取事实;如需提交变更,"
                + "必须先 dry-run,再根据 dry-run 结果决定是否实际应用;实际应用时必须带上 dry-run 返回的 dryRunToken。"
                + "禁止在没有收到 wcs_local_dispatch_apply_auto_tune_changes 或 wcs_local_dispatch_revert_last_auto_tune_job 工具返回结果时声称已试算、已应用或已回滚。"
                + "必须检查 taskSnapshot.stationLimitBlockedTasks 和 taskSnapshot.outboundTaskSamples 中的 systemMsg、wrkSts、batchSeq,判断是否存在被上限挡住的早序出库任务。"
                + "所有提交给 wcs_local_dispatch_apply_auto_tune_changes 的 changes 都必须先匹配 snapshot.ruleSnapshot 中对应 targetType/targetKey 的规则。"
                + "每个参数都必须满足 minValue、maxValue 或 dynamicMaxValue、maxStep、cooldownMinutes 和规则 note;找不到规则或无法证明动态上限时禁止提交。"
                + "dry-run 返回 success=false 或 rejectCount>0 时,必须停止实际应用并说明拒绝原因,或重新提交完全合法的 dry-run。"
                + "不要输出自由格式 JSON 供外层解析。");
        userMessage.setContent(AiPromptUtils.buildAutoTuneRuntimeGuard(triggerType, controlMode));
        messages.add(userMessage);
        return messages;
    }
@@ -126,7 +127,9 @@
        return message;
    }
    private Object callMountedTool(ChatCompletionRequest.ToolCall toolCall, RunState runState, String triggerType) {
    private Object callMountedTool(ChatCompletionRequest.ToolCall toolCall,
                                   RunState runState,
                                   String triggerType) {
        String toolName = resolveToolName(toolCall);
        if (!ALLOWED_TOOL_NAMES.contains(toolName)) {
            throw new IllegalArgumentException("Disallowed auto-tune MCP tool: " + toolName);
@@ -138,11 +141,13 @@
            Object output = mcpToolManager.callTool(toolName, arguments);
            runState.markToolSuccess(toolName);
            recordMutationResult(toolName, arguments, output, runState);
            if (TOOL_APPLY_CHANGES.equals(toolName) && isRejectedApplyResult(output)) {
                runState.markApplyRejected();
                Object wrappedOutput = withRejectedApplyInstruction(output);
                runState.addMcpCall(buildMcpCall(toolName, arguments, wrappedOutput, startTimeMillis, null));
                return wrappedOutput;
            if (isRejectedApplyResult(output)) {
                runState.markApplyRejected(resolveApplyError(output));
                if (TOOL_APPLY_CHANGES.equals(toolName)) {
                    Object wrappedOutput = withRejectedApplyInstruction(output);
                    runState.addMcpCall(buildMcpCall(toolName, arguments, wrappedOutput, startTimeMillis, null));
                    return wrappedOutput;
                }
            }
            runState.addMcpCall(buildMcpCall(toolName, arguments, output, startTimeMillis, null));
            return output;
@@ -279,8 +284,10 @@
        if (TOOL_APPLY_CHANGES.equals(toolName)) {
            boolean dryRun = Boolean.TRUE.equals(arguments.getBoolean("dryRun"));
            if (!dryRun) {
                runState.markActualApply();
                runState.addCounts(output);
                if (outputHasSuccessfulChange(output)) {
                    runState.markActualApply();
                }
            } else if (isRejectedApplyResult(output)) {
                runState.addCounts(output);
            }
@@ -290,6 +297,46 @@
            runState.markRollback();
            runState.addCounts(output);
        }
    }
    private boolean outputHasSuccessfulChange(Object output) {
        if (output instanceof AutoTuneApplyResult) {
            AutoTuneApplyResult result = (AutoTuneApplyResult) output;
            if (result.getChanges() != null) {
                return hasSuccessfulChange(result.getChanges());
            }
            return safeCount(result.getSuccessCount()) > 0;
        }
        if (output instanceof Map<?, ?>) {
            Map<?, ?> result = (Map<?, ?>) output;
            if (!isApplyResultShape(result)) {
                return false;
            }
            if (result.containsKey("changes")) {
                return hasSuccessfulChange(result.get("changes"));
            }
            return safeCount(result.get("successCount")) > 0;
        }
        return false;
    }
    private boolean hasSuccessfulChange(Object changes) {
        if (!(changes instanceof List<?>)) {
            return false;
        }
        for (Object change : (List<?>) changes) {
            String resultStatus = null;
            if (change instanceof com.zy.ai.entity.AiAutoTuneChange) {
                resultStatus = ((com.zy.ai.entity.AiAutoTuneChange) change).getResultStatus();
            } else if (change instanceof Map<?, ?>) {
                Object status = ((Map<?, ?>) change).get("resultStatus");
                resultStatus = status == null ? null : String.valueOf(status);
            }
            if (resultStatus != null && "success".equalsIgnoreCase(resultStatus.trim())) {
                return true;
            }
        }
        return false;
    }
    private boolean isRejectedApplyResult(Object output) {
@@ -403,10 +450,14 @@
                                            StringBuilder summaryBuffer,
                                            RunState runState,
                                            UsageCounter usageCounter,
                                            boolean maxRoundsReached) {
                                            boolean maxRoundsReached,
                                            AutoTuneControlModeSnapshot controlMode) {
        AutoTuneAgentResult result = new AutoTuneAgentResult();
        result.setSuccess(success);
        result.setTriggerType(triggerType);
        result.setAnalysisOnly(controlMode.getAnalysisOnly());
        result.setAllowApply(controlMode.getAllowApply());
        result.setExecutionMode(controlMode.getModeCode());
        result.setToolCallCount(runState.getToolCallCount());
        result.setLlmCallCount(usageCounter.getLlmCallCount());
        result.setPromptTokens(usageCounter.getPromptTokens());
@@ -430,6 +481,9 @@
        }
        if (runState.hasApplyRejected()) {
            summary = summary + "\n自动调参 Agent 存在被拒绝的 dry-run/apply 结果,未视为成功调参。";
            if (!isBlank(runState.getFirstRejectReason())) {
                summary = summary + "拒绝原因: " + runState.getFirstRejectReason();
            }
        }
        if (success && !runState.hasActualMutation()) {
            summary = "自动调参 Agent 未调用实际应用或回滚工具,未修改运行参数。"
@@ -438,8 +492,20 @@
        if (maxRoundsReached) {
            summary = summary + "\n自动调参 Agent 达到最大工具调用轮次,已停止。";
        }
        summary = buildModeSummary(controlMode) + (summary.isEmpty() ? "" : "\n" + summary);
        result.setSummary(summary);
        return result;
    }
    private String buildModeSummary(AutoTuneControlModeSnapshot controlMode) {
        return "执行模式: " + controlMode.getModeCode()
                + ",analysisOnly=" + controlMode.getAnalysisOnly()
                + ",allowApply=" + controlMode.getAllowApply()
                + ",modeLabel=" + controlMode.getModeLabel();
    }
    private AutoTuneControlModeSnapshot buildControlModeSnapshot() {
        return autoTuneControlModeService.currentMode();
    }
    private List<Object> filterAllowedTools(List<Object> tools) {
@@ -482,7 +548,7 @@
        return isBlank(triggerType) ? "agent" : triggerType.trim();
    }
    private boolean isBlank(String value) {
    private static boolean isBlank(String value) {
        return value == null || value.trim().isEmpty();
    }
@@ -524,6 +590,7 @@
        private boolean snapshotCalled;
        private boolean toolError;
        private boolean applyRejected;
        private String firstRejectReason;
        private boolean actualApplyCalled;
        private boolean rollbackCalled;
        private int successCount;
@@ -541,8 +608,11 @@
            toolError = true;
        }
        void markApplyRejected() {
        void markApplyRejected(String rejectReason) {
            applyRejected = true;
            if (isBlank(firstRejectReason) && !isBlank(rejectReason)) {
                firstRejectReason = rejectReason;
            }
        }
        void markActualApply() {
@@ -599,6 +669,10 @@
            return applyRejected;
        }
        String getFirstRejectReason() {
            return firstRejectReason;
        }
        boolean isActualApplyCalled() {
            return actualApplyCalled;
        }