From 63b01db83d9aad8a15276b4236a9a22e4aeef065 Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期二, 05 五月 2026 12:30:59 +0800
Subject: [PATCH] # Agent数据分析V3.0.1.7
---
src/main/java/com/zy/ai/service/impl/AutoTuneAgentServiceImpl.java | 425 +++++++++++++++++++++++++++++++++++++++++++++++++++--
1 files changed, 409 insertions(+), 16 deletions(-)
diff --git a/src/main/java/com/zy/ai/service/impl/AutoTuneAgentServiceImpl.java b/src/main/java/com/zy/ai/service/impl/AutoTuneAgentServiceImpl.java
index 7af6821..cdfe44f 100644
--- a/src/main/java/com/zy/ai/service/impl/AutoTuneAgentServiceImpl.java
+++ b/src/main/java/com/zy/ai/service/impl/AutoTuneAgentServiceImpl.java
@@ -2,6 +2,9 @@
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;
@@ -9,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;
@@ -31,6 +36,9 @@
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,
@@ -41,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;
@@ -57,10 +67,10 @@
}
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);
@@ -69,25 +79,29 @@
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();
@@ -97,10 +111,7 @@
ChatCompletionRequest.Message userMessage = new ChatCompletionRequest.Message();
userMessage.setRole("user");
- userMessage.setContent("璇锋墽琛屼竴娆″悗鍙� WCS 鑷姩璋冨弬銆倀riggerType=" + 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;
}
@@ -116,20 +127,291 @@
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銆乵axValue/dynamicMaxValue銆乵axStep銆乧ooldownMinutes 鍜� 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) {
@@ -168,19 +450,28 @@
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 鏈皟鐢ㄥ揩鐓у伐鍏凤紝缁撴灉涓嶅畬鏁淬��";
@@ -188,11 +479,33 @@
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()
+ + "锛宎nalysisOnly=" + controlMode.getAnalysisOnly()
+ + "锛宎llowApply=" + controlMode.getAllowApply()
+ + "锛宮odeLabel=" + controlMode.getModeLabel();
+ }
+
+ private AutoTuneControlModeSnapshot buildControlModeSnapshot() {
+ return autoTuneControlModeService.currentMode();
}
private List<Object> filterAllowedTools(List<Object> tools) {
@@ -235,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();
}
@@ -276,6 +589,13 @@
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++;
@@ -288,12 +608,53 @@
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() {
@@ -303,5 +664,37 @@
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);
+ }
}
}
--
Gitblit v1.9.1