| | |
| | | import com.zy.ai.service.AiAutoTuneMcpCallService; |
| | | import com.zy.ai.service.AutoTuneApplyService; |
| | | import com.zy.ai.service.AutoTuneSnapshotService; |
| | | import com.zy.ai.utils.AutoTuneWriteBehaviorUtils; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.ai.tool.annotation.Tool; |
| | | import org.springframework.ai.tool.annotation.ToolParam; |
| | |
| | | if (dryRun == null) { |
| | | throw new IllegalArgumentException("dryRun is required. Use dryRun=true first to create a preview token."); |
| | | } |
| | | String fingerprint = buildChangeFingerprint(changes); |
| | | if (Boolean.FALSE.equals(dryRun)) { |
| | | requireMatchingDryRunToken(dryRunToken, fingerprint); |
| | | } |
| | | |
| | | AutoTuneApplyRequest request = new AutoTuneApplyRequest(); |
| | | request.setReason(reason); |
| | |
| | | request.setTriggerType(triggerType); |
| | | request.setDryRun(dryRun); |
| | | request.setChanges(changes); |
| | | |
| | | String fingerprint = buildChangeFingerprint(changes); |
| | | if (Boolean.FALSE.equals(dryRun)) { |
| | | requireMatchingDryRunToken(dryRunToken, fingerprint); |
| | | } |
| | | |
| | | AutoTuneApplyResult result = autoTuneApplyService.apply(request); |
| | | if (Boolean.TRUE.equals(dryRun) && isSuccessful(result)) { |
| | | if (Boolean.TRUE.equals(dryRun) && hasApplicableDryRunChanges(result)) { |
| | | result.setDryRunToken(createDryRunToken(fingerprint)); |
| | | } |
| | | return result; |
| | |
| | | item.put("rejectCount", job.getRejectCount()); |
| | | item.put("errorMessage", job.getErrorMessage()); |
| | | List<AiAutoTuneMcpCall> mcpCalls = listMcpCalls(job.getId()); |
| | | List<Map<String, Object>> mcpCallSummaries = toMcpCallSummaries(mcpCalls); |
| | | List<Map<String, Object>> changeSummaries = listChangeSummaries(job, mcpCalls); |
| | | AutoTuneWriteBehaviorUtils.addWriteBehavior(item, |
| | | AutoTuneWriteBehaviorUtils.resolveJobWriteBehavior(job, mcpCallSummaries, changeSummaries)); |
| | | item.put("mcpCallCount", mcpCalls.size()); |
| | | item.put("mcpCalls", toMcpCallSummaries(mcpCalls)); |
| | | item.put("changes", listChangeSummaries(mcpCalls)); |
| | | item.put("mcpCalls", mcpCallSummaries); |
| | | item.put("changes", changeSummaries); |
| | | return item; |
| | | } |
| | | |
| | |
| | | item.put("successCount", mcpCall.getSuccessCount()); |
| | | item.put("rejectCount", mcpCall.getRejectCount()); |
| | | item.put("errorMessage", mcpCall.getErrorMessage()); |
| | | AutoTuneWriteBehaviorUtils.addWriteBehavior(item, |
| | | AutoTuneWriteBehaviorUtils.resolveMcpWriteBehavior(mcpCall)); |
| | | return item; |
| | | } |
| | | |
| | |
| | | return value == 1; |
| | | } |
| | | |
| | | private List<Map<String, Object>> listChangeSummaries(List<AiAutoTuneMcpCall> mcpCalls) { |
| | | List<Long> applyJobIds = collectApplyJobIds(mcpCalls); |
| | | private List<Map<String, Object>> listChangeSummaries(AiAutoTuneJob job, List<AiAutoTuneMcpCall> mcpCalls) { |
| | | Map<Long, String> ownerTriggerTypes = collectChangeOwnerTriggerTypes(job, mcpCalls); |
| | | List<Long> applyJobIds = new ArrayList<>(ownerTriggerTypes.keySet()); |
| | | if (applyJobIds.isEmpty()) { |
| | | return new ArrayList<>(); |
| | | } |
| | |
| | | |
| | | List<Map<String, Object>> result = new ArrayList<>(); |
| | | for (AiAutoTuneChange change : changes) { |
| | | result.add(toChangeSummary(change)); |
| | | result.add(toChangeSummary(change, ownerTriggerTypes.get(change.getJobId()))); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private List<Long> collectApplyJobIds(List<AiAutoTuneMcpCall> mcpCalls) { |
| | | List<Long> result = new ArrayList<>(); |
| | | private Map<Long, String> collectChangeOwnerTriggerTypes(AiAutoTuneJob job, List<AiAutoTuneMcpCall> mcpCalls) { |
| | | LinkedHashMap<Long, String> result = new LinkedHashMap<>(); |
| | | if (job != null && job.getId() != null) { |
| | | result.put(job.getId(), job.getTriggerType()); |
| | | } |
| | | if (mcpCalls == null || mcpCalls.isEmpty()) { |
| | | return result; |
| | | } |
| | | for (AiAutoTuneMcpCall mcpCall : mcpCalls) { |
| | | Long applyJobId = mcpCall.getApplyJobId(); |
| | | if (applyJobId != null && !result.contains(applyJobId)) { |
| | | result.add(applyJobId); |
| | | if (applyJobId == null || result.containsKey(applyJobId)) { |
| | | continue; |
| | | } |
| | | result.put(applyJobId, resolveMcpApplyJobTriggerType(mcpCall)); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private Map<String, Object> toChangeSummary(AiAutoTuneChange change) { |
| | | private String resolveMcpApplyJobTriggerType(AiAutoTuneMcpCall mcpCall) { |
| | | if (mcpCall == null || mcpCall.getToolName() == null) { |
| | | return null; |
| | | } |
| | | String toolName = mcpCall.getToolName().toLowerCase(Locale.ROOT); |
| | | return toolName.contains("revert_last_auto_tune_job") || toolName.contains("rollback") ? "rollback" : null; |
| | | } |
| | | |
| | | private Map<String, Object> toChangeSummary(AiAutoTuneChange change, String ownerTriggerType) { |
| | | LinkedHashMap<String, Object> item = new LinkedHashMap<>(); |
| | | item.put("jobId", change.getJobId()); |
| | | item.put("targetType", change.getTargetType()); |
| | | item.put("targetId", change.getTargetId()); |
| | | item.put("targetKey", change.getTargetKey()); |
| | |
| | | item.put("rejectReason", change.getRejectReason()); |
| | | item.put("cooldownExpireTime", change.getCooldownExpireTime()); |
| | | item.put("createTime", change.getCreateTime()); |
| | | AutoTuneWriteBehaviorUtils.addWriteBehavior(item, |
| | | AutoTuneWriteBehaviorUtils.resolveChangeWriteBehavior(change, ownerTriggerType)); |
| | | return item; |
| | | } |
| | | |
| | |
| | | return result != null && Boolean.TRUE.equals(result.getSuccess()); |
| | | } |
| | | |
| | | private boolean hasApplicableDryRunChanges(AutoTuneApplyResult result) { |
| | | if (!isSuccessful(result) || result.getChanges() == null || result.getChanges().isEmpty()) { |
| | | return false; |
| | | } |
| | | for (AiAutoTuneChange change : result.getChanges()) { |
| | | if (change != null && "dry_run".equals(normalizeLower(change.getResultStatus()))) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | private String buildChangeFingerprint(List<AutoTuneChangeCommand> changes) { |
| | | List<Map<String, String>> normalizedChanges = new ArrayList<>(); |
| | | if (changes != null) { |
| | |
| | | normalizedChanges.add(toNormalizedChange(change)); |
| | | } |
| | | } |
| | | validateUniqueChangeTargets(normalizedChanges); |
| | | normalizedChanges.sort(Comparator |
| | | .comparing((Map<String, String> item) -> item.get("targetType")) |
| | | .thenComparing(item -> item.get("targetId")) |
| | |
| | | return JSON.toJSONString(normalizedChanges); |
| | | } |
| | | |
| | | private void validateUniqueChangeTargets(List<Map<String, String>> normalizedChanges) { |
| | | Map<String, Map<String, String>> uniqueTargets = new LinkedHashMap<>(); |
| | | for (Map<String, String> change : normalizedChanges) { |
| | | String targetSignature = buildTargetSignature(change); |
| | | if (uniqueTargets.containsKey(targetSignature)) { |
| | | throw new IllegalArgumentException("Duplicate auto-tune change target in same request: " |
| | | + "targetType=" + change.get("targetType") |
| | | + ", targetId=" + change.get("targetId") |
| | | + ", targetKey=" + change.get("targetKey")); |
| | | } |
| | | uniqueTargets.put(targetSignature, change); |
| | | } |
| | | } |
| | | |
| | | private String buildTargetSignature(Map<String, String> change) { |
| | | return change.get("targetType") + "\n" |
| | | + change.get("targetId") + "\n" |
| | | + change.get("targetKey"); |
| | | } |
| | | |
| | | private Map<String, String> toNormalizedChange(AutoTuneChangeCommand change) { |
| | | LinkedHashMap<String, String> item = new LinkedHashMap<>(); |
| | | String targetType = normalizeLower(change == null ? null : change.getTargetType()); |