package com.zy.ai.service.impl;
|
|
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.enums.AiPromptScene;
|
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;
|
|
import java.util.ArrayList;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Set;
|
|
@Slf4j
|
@Service
|
@RequiredArgsConstructor
|
public class AutoTuneAgentServiceImpl implements AutoTuneAgentService {
|
|
private static final int MAX_TOOL_ROUNDS = 10;
|
private static final double TEMPERATURE = 0.2D;
|
private static final int MAX_TOKENS = 2048;
|
private static final String TOOL_GET_SNAPSHOT = "wcs_local_dispatch_get_auto_tune_snapshot";
|
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,
|
TOOL_APPLY_CHANGES,
|
TOOL_REVERT_LAST_JOB
|
);
|
|
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;
|
StringBuilder summaryBuffer = new StringBuilder();
|
|
try {
|
List<Object> tools = filterAllowedTools(mcpToolManager.buildOpenAiTools());
|
if (tools == null || tools.isEmpty()) {
|
throw new IllegalStateException("No auto-tune MCP tools registered");
|
}
|
|
AiPromptTemplate promptTemplate = aiPromptTemplateService.resolvePublished(AiPromptScene.AUTO_TUNE_DISPATCH.getCode());
|
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);
|
ChatCompletionRequest.Message assistantMessage = extractAssistantMessage(response);
|
usageCounter.add(response.getUsage());
|
messages.add(assistantMessage);
|
appendSummary(summaryBuffer, assistantMessage.getContent());
|
|
List<ChatCompletionRequest.ToolCall> toolCalls = assistantMessage.getTool_calls();
|
if (toolCalls == null || toolCalls.isEmpty()) {
|
return buildResult(runState.isSuccessful(), normalizedTriggerType, summaryBuffer, runState,
|
usageCounter, false, controlMode);
|
}
|
|
for (ChatCompletionRequest.ToolCall toolCall : toolCalls) {
|
Object toolOutput = callMountedTool(toolCall, runState, normalizedTriggerType);
|
messages.add(buildToolMessage(toolCall, toolOutput));
|
}
|
}
|
maxRoundsReached = true;
|
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,
|
controlMode);
|
}
|
}
|
|
private List<ChatCompletionRequest.Message> buildMessages(AiPromptTemplate promptTemplate,
|
String triggerType,
|
AutoTuneControlModeSnapshot controlMode) {
|
List<ChatCompletionRequest.Message> messages = new ArrayList<>();
|
|
ChatCompletionRequest.Message systemMessage = new ChatCompletionRequest.Message();
|
systemMessage.setRole("system");
|
systemMessage.setContent(promptTemplate == null ? "" : promptTemplate.getContent());
|
messages.add(systemMessage);
|
|
ChatCompletionRequest.Message userMessage = new ChatCompletionRequest.Message();
|
userMessage.setRole("user");
|
userMessage.setContent(AiPromptUtils.buildAutoTuneRuntimeGuard(triggerType, controlMode));
|
messages.add(userMessage);
|
return messages;
|
}
|
|
private ChatCompletionRequest.Message extractAssistantMessage(ChatCompletionResponse response) {
|
if (response == null || response.getChoices() == null || response.getChoices().isEmpty()) {
|
throw new IllegalStateException("LLM returned empty response");
|
}
|
ChatCompletionRequest.Message message = response.getChoices().get(0).getMessage();
|
if (message == null) {
|
throw new IllegalStateException("LLM returned empty message");
|
}
|
return message;
|
}
|
|
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) {
|
ChatCompletionRequest.Message toolMessage = new ChatCompletionRequest.Message();
|
toolMessage.setRole("tool");
|
toolMessage.setTool_call_id(toolCall == null ? null : toolCall.getId());
|
toolMessage.setContent(JSON.toJSONString(toolOutput));
|
return toolMessage;
|
}
|
|
private String resolveToolName(ChatCompletionRequest.ToolCall toolCall) {
|
if (toolCall == null || toolCall.getFunction() == null || isBlank(toolCall.getFunction().getName())) {
|
throw new IllegalArgumentException("missing tool name");
|
}
|
return toolCall.getFunction().getName();
|
}
|
|
private JSONObject parseArguments(ChatCompletionRequest.ToolCall toolCall) {
|
String rawArguments = toolCall == null || toolCall.getFunction() == null
|
? null
|
: toolCall.getFunction().getArguments();
|
if (isBlank(rawArguments)) {
|
return new JSONObject();
|
}
|
try {
|
return JSON.parseObject(rawArguments);
|
} catch (Exception exception) {
|
JSONObject arguments = new JSONObject();
|
arguments.put("_raw", rawArguments);
|
return arguments;
|
}
|
}
|
|
private AutoTuneAgentResult buildResult(boolean success,
|
String triggerType,
|
StringBuilder summaryBuffer,
|
RunState runState,
|
UsageCounter usageCounter,
|
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 && 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) {
|
List<Object> allowedTools = new ArrayList<>();
|
if (tools == null || tools.isEmpty()) {
|
return allowedTools;
|
}
|
for (Object tool : tools) {
|
String toolName = resolveOpenAiToolName(tool);
|
if (ALLOWED_TOOL_NAMES.contains(toolName)) {
|
allowedTools.add(tool);
|
}
|
}
|
return allowedTools;
|
}
|
|
private String resolveOpenAiToolName(Object tool) {
|
if (!(tool instanceof Map<?, ?> toolMap)) {
|
return null;
|
}
|
Object function = toolMap.get("function");
|
if (!(function instanceof Map<?, ?> functionMap)) {
|
return null;
|
}
|
Object name = functionMap.get("name");
|
return name == null ? null : String.valueOf(name);
|
}
|
|
private void appendSummary(StringBuilder summaryBuffer, String content) {
|
if (summaryBuffer == null || isBlank(content)) {
|
return;
|
}
|
if (summaryBuffer.length() > 0) {
|
summaryBuffer.append('\n');
|
}
|
summaryBuffer.append(content.trim());
|
}
|
|
private String normalizeTriggerType(String triggerType) {
|
return isBlank(triggerType) ? "agent" : triggerType.trim();
|
}
|
|
private static boolean isBlank(String value) {
|
return value == null || value.trim().isEmpty();
|
}
|
|
private static class UsageCounter {
|
private long promptTokens;
|
private long completionTokens;
|
private long totalTokens;
|
private int llmCallCount;
|
|
void add(ChatCompletionResponse.Usage usage) {
|
llmCallCount++;
|
if (usage == null) {
|
return;
|
}
|
promptTokens += usage.getPromptTokens() == null ? 0L : usage.getPromptTokens();
|
completionTokens += usage.getCompletionTokens() == null ? 0L : usage.getCompletionTokens();
|
totalTokens += usage.getTotalTokens() == null ? 0L : usage.getTotalTokens();
|
}
|
|
long getPromptTokens() {
|
return promptTokens;
|
}
|
|
long getCompletionTokens() {
|
return completionTokens;
|
}
|
|
long getTotalTokens() {
|
return totalTokens;
|
}
|
|
int getLlmCallCount() {
|
return llmCallCount;
|
}
|
}
|
|
private static class RunState {
|
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++;
|
if (TOOL_GET_SNAPSHOT.equals(toolName)) {
|
snapshotCalled = true;
|
}
|
}
|
|
void markToolError() {
|
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 && !applyRejected;
|
}
|
|
int getToolCallCount() {
|
return toolCallCount;
|
}
|
|
int getMcpCallCount() {
|
return mcpCalls.size();
|
}
|
|
boolean isSnapshotCalled() {
|
return snapshotCalled;
|
}
|
|
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);
|
}
|
}
|
}
|