| | |
| | | |
| | | 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; |
| | | import com.zy.ai.entity.ChatCompletionResponse; |
| | |
| | | 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; |
| | |
| | | private static final String TOOL_GET_RECENT_JOBS = "wcs_local_dispatch_get_recent_auto_tune_jobs"; |
| | | private static final String TOOL_APPLY_CHANGES = "wcs_local_dispatch_apply_auto_tune_changes"; |
| | | private static final String TOOL_REVERT_LAST_JOB = "wcs_local_dispatch_revert_last_auto_tune_job"; |
| | | private static final String MCP_STATUS_SUCCESS = "success"; |
| | | private static final String MCP_STATUS_REJECTED = "rejected"; |
| | | private static final String MCP_STATUS_FAILED = "failed"; |
| | | private static final Set<String> ALLOWED_TOOL_NAMES = Set.of( |
| | | TOOL_GET_SNAPSHOT, |
| | | TOOL_GET_RECENT_JOBS, |
| | |
| | | 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; |
| | |
| | | } |
| | | |
| | | 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.chatCompletion(messages, TEMPERATURE, MAX_TOKENS, tools); |
| | | ChatCompletionResponse response = llmChatService.chatCompletionOrThrow(messages, TEMPERATURE, MAX_TOKENS, tools); |
| | | ChatCompletionRequest.Message assistantMessage = extractAssistantMessage(response); |
| | | usageCounter.add(response.getUsage()); |
| | | messages.add(assistantMessage); |
| | |
| | | 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) { |
| | | Object toolOutput = callMountedTool(toolCall, runState); |
| | | Object toolOutput = callMountedTool(toolCall, runState, normalizedTriggerType); |
| | | messages.add(buildToolMessage(toolCall, toolOutput)); |
| | | } |
| | | } |
| | | 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(); |
| | |
| | | |
| | | 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。" |
| | | + "不要输出自由格式 JSON 供外层解析。"); |
| | | userMessage.setContent(AiPromptUtils.buildAutoTuneRuntimeGuard(triggerType, controlMode)); |
| | | messages.add(userMessage); |
| | | return messages; |
| | | } |
| | |
| | | return message; |
| | | } |
| | | |
| | | private Object callMountedTool(ChatCompletionRequest.ToolCall toolCall, RunState runState) { |
| | | 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); |
| | | } |
| | | JSONObject arguments = parseArguments(toolCall); |
| | | applySchedulerTriggerType(toolName, triggerType, arguments); |
| | | long startTimeMillis = System.currentTimeMillis(); |
| | | try { |
| | | Object output = mcpToolManager.callTool(toolName, arguments); |
| | | runState.markToolSuccess(toolName); |
| | | recordMutationResult(toolName, arguments, output, runState); |
| | | 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; |
| | | } catch (Exception exception) { |
| | | runState.addMcpCall(buildMcpCall(toolName, arguments, null, startTimeMillis, exception)); |
| | | throw new IllegalStateException("Auto-tune MCP tool failed: " + toolName + ", " + exception.getMessage(), |
| | | exception); |
| | | } |
| | | } |
| | | |
| | | private AutoTuneAgentService.McpCallResult buildMcpCall(String toolName, |
| | | JSONObject arguments, |
| | | Object output, |
| | | long startTimeMillis, |
| | | Exception exception) { |
| | | AutoTuneAgentService.McpCallResult call = new AutoTuneAgentService.McpCallResult(); |
| | | call.setToolName(toolName); |
| | | call.setDryRun(resolveDryRun(arguments)); |
| | | call.setDurationMs(Math.max(0L, System.currentTimeMillis() - startTimeMillis)); |
| | | call.setRequestJson(JSON.toJSONString(arguments == null ? new JSONObject() : arguments)); |
| | | if (exception != null) { |
| | | call.setStatus(MCP_STATUS_FAILED); |
| | | call.setErrorMessage(exception.getMessage()); |
| | | return call; |
| | | } |
| | | call.setStatus(resolveMcpStatus(output)); |
| | | call.setResponseJson(JSON.toJSONString(output)); |
| | | call.setApplyJobId(resolveApplyJobId(output)); |
| | | call.setSuccessCount(resolveSuccessCount(output)); |
| | | call.setRejectCount(resolveRejectCount(output)); |
| | | if (MCP_STATUS_REJECTED.equals(call.getStatus())) { |
| | | call.setErrorMessage(resolveApplyError(output)); |
| | | } |
| | | return call; |
| | | } |
| | | |
| | | private Boolean resolveDryRun(JSONObject arguments) { |
| | | if (arguments == null || !arguments.containsKey("dryRun")) { |
| | | return null; |
| | | } |
| | | return Boolean.TRUE.equals(arguments.getBoolean("dryRun")); |
| | | } |
| | | |
| | | private String resolveMcpStatus(Object output) { |
| | | if (isRejectedApplyResult(output)) { |
| | | return MCP_STATUS_REJECTED; |
| | | } |
| | | return MCP_STATUS_SUCCESS; |
| | | } |
| | | |
| | | private Long resolveApplyJobId(Object output) { |
| | | if (output instanceof AutoTuneApplyResult) { |
| | | return ((AutoTuneApplyResult) output).getJobId(); |
| | | } |
| | | if (output instanceof Map<?, ?>) { |
| | | Object jobId = ((Map<?, ?>) output).get("jobId"); |
| | | if (jobId instanceof Number) { |
| | | return ((Number) jobId).longValue(); |
| | | } |
| | | if (jobId != null) { |
| | | try { |
| | | return Long.parseLong(String.valueOf(jobId)); |
| | | } catch (NumberFormatException ignore) { |
| | | return null; |
| | | } |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private Integer resolveSuccessCount(Object output) { |
| | | if (output instanceof AutoTuneApplyResult) { |
| | | return safeCount(((AutoTuneApplyResult) output).getSuccessCount()); |
| | | } |
| | | if (output instanceof Map<?, ?>) { |
| | | return safeCount(((Map<?, ?>) output).get("successCount")); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private Integer resolveRejectCount(Object output) { |
| | | if (output instanceof AutoTuneApplyResult) { |
| | | return safeCount(((AutoTuneApplyResult) output).getRejectCount()); |
| | | } |
| | | if (output instanceof Map<?, ?>) { |
| | | return safeCount(((Map<?, ?>) output).get("rejectCount")); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private String resolveApplyError(Object output) { |
| | | if (output instanceof AutoTuneApplyResult) { |
| | | AutoTuneApplyResult result = (AutoTuneApplyResult) output; |
| | | return firstRejectReason(result.getChanges(), result.getSummary()); |
| | | } |
| | | if (output instanceof Map<?, ?>) { |
| | | Map<?, ?> result = (Map<?, ?>) output; |
| | | Object changes = result.get("changes"); |
| | | if (changes instanceof List<?>) { |
| | | for (Object change : (List<?>) changes) { |
| | | if (change instanceof Map<?, ?>) { |
| | | Object rejectReason = ((Map<?, ?>) change).get("rejectReason"); |
| | | if (!isBlank(rejectReason == null ? null : String.valueOf(rejectReason))) { |
| | | return String.valueOf(rejectReason); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | Object summary = result.get("summary"); |
| | | return summary == null ? null : String.valueOf(summary); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private String firstRejectReason(List<?> changes, String fallback) { |
| | | if (changes != null) { |
| | | for (Object change : changes) { |
| | | String rejectReason = null; |
| | | if (change instanceof com.zy.ai.entity.AiAutoTuneChange) { |
| | | rejectReason = ((com.zy.ai.entity.AiAutoTuneChange) change).getRejectReason(); |
| | | } else if (change instanceof Map<?, ?>) { |
| | | Object value = ((Map<?, ?>) change).get("rejectReason"); |
| | | rejectReason = value == null ? null : String.valueOf(value); |
| | | } |
| | | if (!isBlank(rejectReason)) { |
| | | return rejectReason; |
| | | } |
| | | } |
| | | } |
| | | return fallback; |
| | | } |
| | | |
| | | private void recordMutationResult(String toolName, JSONObject arguments, Object output, RunState runState) { |
| | | if (TOOL_APPLY_CHANGES.equals(toolName)) { |
| | | boolean dryRun = Boolean.TRUE.equals(arguments.getBoolean("dryRun")); |
| | | if (!dryRun) { |
| | | runState.addCounts(output); |
| | | if (outputHasSuccessfulChange(output)) { |
| | | runState.markActualApply(); |
| | | } |
| | | } else if (isRejectedApplyResult(output)) { |
| | | runState.addCounts(output); |
| | | } |
| | | return; |
| | | } |
| | | if (TOOL_REVERT_LAST_JOB.equals(toolName)) { |
| | | 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) { |
| | | if (output instanceof AutoTuneApplyResult) { |
| | | AutoTuneApplyResult result = (AutoTuneApplyResult) output; |
| | | return !Boolean.TRUE.equals(result.getSuccess()) || safeCount(result.getRejectCount()) > 0; |
| | | } |
| | | if (output instanceof Map<?, ?>) { |
| | | Map<?, ?> result = (Map<?, ?>) output; |
| | | if (!isApplyResultShape(result)) { |
| | | return false; |
| | | } |
| | | if (!Boolean.TRUE.equals(result.get("success"))) { |
| | | return true; |
| | | } |
| | | return safeCount(result.get("rejectCount")) > 0 || hasRejectedChange(result.get("changes")); |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | private boolean isApplyResultShape(Map<?, ?> result) { |
| | | return result.containsKey("success") |
| | | || result.containsKey("rejectCount") |
| | | || result.containsKey("changes") |
| | | || result.containsKey("dryRun") |
| | | || result.containsKey("dryRunToken"); |
| | | } |
| | | |
| | | private Object withRejectedApplyInstruction(Object output) { |
| | | JSONObject wrappedOutput = JSON.parseObject(JSON.toJSONString(output)); |
| | | wrappedOutput.put("agentInstruction", |
| | | "本次 dry-run/apply 未完全通过,禁止继续实际应用。必须读取 changes[].rejectReason," |
| | | + "并回到 snapshot.ruleSnapshot 按每个目标参数的 minValue、maxValue/dynamicMaxValue、maxStep、cooldownMinutes 和 note 重新校验。" |
| | | + "只有所有 changes 均满足规则并通过 dry-run 后,才允许实际应用。"); |
| | | return wrappedOutput; |
| | | } |
| | | |
| | | private boolean hasRejectedChange(Object changes) { |
| | | if (!(changes instanceof List<?>)) { |
| | | return false; |
| | | } |
| | | for (Object change : (List<?>) changes) { |
| | | if (!(change instanceof Map<?, ?>)) { |
| | | continue; |
| | | } |
| | | Object status = ((Map<?, ?>) change).get("resultStatus"); |
| | | if ("rejected".equals(status) || "failed".equals(status)) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | private static int safeCount(Object value) { |
| | | if (value instanceof Number) { |
| | | return ((Number) value).intValue(); |
| | | } |
| | | if (value == null) { |
| | | return 0; |
| | | } |
| | | try { |
| | | return Integer.parseInt(String.valueOf(value)); |
| | | } catch (NumberFormatException ignore) { |
| | | return 0; |
| | | } |
| | | } |
| | | |
| | | private void applySchedulerTriggerType(String toolName, String triggerType, JSONObject arguments) { |
| | | if (!TOOL_APPLY_CHANGES.equals(toolName)) { |
| | | return; |
| | | } |
| | | if (!AutoTuneTriggerType.AUTO.getCode().equals(triggerType)) { |
| | | return; |
| | | } |
| | | arguments.put("triggerType", AutoTuneTriggerType.AUTO.getCode()); |
| | | } |
| | | |
| | | private ChatCompletionRequest.Message buildToolMessage(ChatCompletionRequest.ToolCall toolCall, Object toolOutput) { |
| | |
| | | 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()); |
| | | result.setCompletionTokens(usageCounter.getCompletionTokens()); |
| | | result.setTotalTokens(usageCounter.getTotalTokens()); |
| | | result.setMaxRoundsReached(maxRoundsReached); |
| | | result.setActualApplyCalled(runState.isActualApplyCalled()); |
| | | result.setRollbackCalled(runState.isRollbackCalled()); |
| | | result.setSuccessCount(runState.getSuccessCount()); |
| | | result.setRejectCount(runState.getRejectCount()); |
| | | result.setMcpCalls(runState.getMcpCalls()); |
| | | |
| | | String summary = summaryBuffer == null ? "" : summaryBuffer.toString().trim(); |
| | | if (runState.getToolCallCount() <= 0) { |
| | | if (runState.getToolCallCount() <= 0 && runState.getMcpCallCount() <= 0) { |
| | | summary = "自动调参 Agent 未调用任何允许的 MCP 工具,未执行调参。" + (summary.isEmpty() ? "" : "\n" + summary); |
| | | } else if (!runState.isSnapshotCalled()) { |
| | | summary = summary + "\n自动调参 Agent 未调用快照工具,结果不完整。"; |
| | |
| | | if (runState.hasToolError()) { |
| | | summary = summary + "\n自动调参 Agent 存在工具调用错误,已标记为失败。"; |
| | | } |
| | | if (runState.hasApplyRejected()) { |
| | | summary = summary + "\n自动调参 Agent 存在被拒绝的 dry-run/apply 结果,未视为成功调参。"; |
| | | if (!isBlank(runState.getFirstRejectReason())) { |
| | | summary = summary + "拒绝原因: " + runState.getFirstRejectReason(); |
| | | } |
| | | } |
| | | if (success && !runState.hasActualMutation()) { |
| | | summary = "自动调参 Agent 未调用实际应用或回滚工具,未修改运行参数。" |
| | | + (summary.isEmpty() ? "" : "\n" + summary); |
| | | } |
| | | 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) { |
| | |
| | | return isBlank(triggerType) ? "agent" : triggerType.trim(); |
| | | } |
| | | |
| | | private boolean isBlank(String value) { |
| | | private static boolean isBlank(String value) { |
| | | return value == null || value.trim().isEmpty(); |
| | | } |
| | | |
| | |
| | | private int toolCallCount; |
| | | private boolean snapshotCalled; |
| | | private boolean toolError; |
| | | private boolean applyRejected; |
| | | private String firstRejectReason; |
| | | private boolean actualApplyCalled; |
| | | private boolean rollbackCalled; |
| | | private int successCount; |
| | | private int rejectCount; |
| | | private final List<AutoTuneAgentService.McpCallResult> mcpCalls = new ArrayList<>(); |
| | | |
| | | void markToolSuccess(String toolName) { |
| | | toolCallCount++; |
| | |
| | | toolError = true; |
| | | } |
| | | |
| | | void markApplyRejected(String rejectReason) { |
| | | applyRejected = true; |
| | | if (isBlank(firstRejectReason) && !isBlank(rejectReason)) { |
| | | firstRejectReason = rejectReason; |
| | | } |
| | | } |
| | | |
| | | void markActualApply() { |
| | | actualApplyCalled = true; |
| | | } |
| | | |
| | | void markRollback() { |
| | | rollbackCalled = true; |
| | | } |
| | | |
| | | void addCounts(Object output) { |
| | | if (output instanceof AutoTuneApplyResult) { |
| | | AutoTuneApplyResult result = (AutoTuneApplyResult) output; |
| | | successCount += safeCount(result.getSuccessCount()); |
| | | rejectCount += safeCount(result.getRejectCount()); |
| | | return; |
| | | } |
| | | if (output instanceof Map<?, ?>) { |
| | | Map<?, ?> result = (Map<?, ?>) output; |
| | | successCount += safeCount(result.get("successCount")); |
| | | rejectCount += safeCount(result.get("rejectCount")); |
| | | } |
| | | } |
| | | |
| | | void addMcpCall(AutoTuneAgentService.McpCallResult mcpCall) { |
| | | if (mcpCall == null) { |
| | | return; |
| | | } |
| | | mcpCall.setCallSeq(mcpCalls.size() + 1); |
| | | mcpCalls.add(mcpCall); |
| | | } |
| | | |
| | | boolean isSuccessful() { |
| | | return toolCallCount > 0 && snapshotCalled && !toolError; |
| | | return toolCallCount > 0 && snapshotCalled && !toolError && !applyRejected; |
| | | } |
| | | |
| | | int getToolCallCount() { |
| | | return toolCallCount; |
| | | } |
| | | |
| | | int getMcpCallCount() { |
| | | return mcpCalls.size(); |
| | | } |
| | | |
| | | boolean isSnapshotCalled() { |
| | |
| | | boolean hasToolError() { |
| | | return toolError; |
| | | } |
| | | |
| | | boolean hasApplyRejected() { |
| | | return applyRejected; |
| | | } |
| | | |
| | | String getFirstRejectReason() { |
| | | return firstRejectReason; |
| | | } |
| | | |
| | | boolean isActualApplyCalled() { |
| | | return actualApplyCalled; |
| | | } |
| | | |
| | | boolean isRollbackCalled() { |
| | | return rollbackCalled; |
| | | } |
| | | |
| | | boolean hasActualMutation() { |
| | | return actualApplyCalled || rollbackCalled; |
| | | } |
| | | |
| | | int getSuccessCount() { |
| | | return successCount; |
| | | } |
| | | |
| | | int getRejectCount() { |
| | | return rejectCount; |
| | | } |
| | | |
| | | List<AutoTuneAgentService.McpCallResult> getMcpCalls() { |
| | | return new ArrayList<>(mcpCalls); |
| | | } |
| | | } |
| | | } |