| | |
| | | package com.zy.ai.service; |
| | | |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.baomidou.mybatisplus.core.conditions.AbstractWrapper; |
| | | import com.baomidou.mybatisplus.core.conditions.Wrapper; |
| | | import com.zy.ai.domain.autotune.AutoTuneApplyRequest; |
| | | import com.zy.ai.domain.autotune.AutoTuneApplyResult; |
| | | import com.zy.ai.domain.autotune.AutoTuneChangeCommand; |
| | | import com.zy.ai.domain.autotune.AutoTuneSnapshot; |
| | | import com.zy.ai.domain.autotune.AutoTuneJobStatus; |
| | | import com.zy.ai.domain.autotune.AutoTuneTriggerType; |
| | | import com.zy.ai.entity.AiAutoTuneChange; |
| | | import com.zy.ai.entity.AiAutoTuneJob; |
| | | 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.mcp.tool.AutoTuneMcpTools; |
| | | import com.zy.ai.service.impl.AutoTuneAgentServiceImpl; |
| | | import com.zy.ai.service.impl.AutoTuneControlModeServiceImpl; |
| | | import com.zy.ai.service.impl.AutoTuneCoordinatorServiceImpl; |
| | | import com.zy.asrs.service.WrkMastService; |
| | | import com.zy.common.utils.RedisUtil; |
| | | import com.zy.core.enums.RedisKeyType; |
| | | import com.zy.system.entity.OperateLog; |
| | | import com.zy.system.service.ConfigService; |
| | | import com.zy.system.service.OperateLogService; |
| | | import org.junit.jupiter.api.BeforeEach; |
| | |
| | | import static org.junit.jupiter.api.Assertions.assertEquals; |
| | | import static org.junit.jupiter.api.Assertions.assertFalse; |
| | | import static org.junit.jupiter.api.Assertions.assertNotNull; |
| | | import static org.junit.jupiter.api.Assertions.assertNull; |
| | | import static org.junit.jupiter.api.Assertions.assertSame; |
| | | import static org.junit.jupiter.api.Assertions.assertThrows; |
| | | import static org.junit.jupiter.api.Assertions.assertTrue; |
| | |
| | | import static org.mockito.ArgumentMatchers.anyString; |
| | | import static org.mockito.ArgumentMatchers.eq; |
| | | import static org.mockito.Mockito.doThrow; |
| | | import static org.mockito.Mockito.lenient; |
| | | import static org.mockito.Mockito.never; |
| | | import static org.mockito.Mockito.times; |
| | | import static org.mockito.Mockito.verify; |
| | |
| | | @Mock |
| | | private AiAutoTuneChangeService aiAutoTuneChangeService; |
| | | @Mock |
| | | private AiAutoTuneMcpCallService aiAutoTuneMcpCallService; |
| | | @Mock |
| | | private LlmChatService llmChatService; |
| | | @Mock |
| | | private SpringAiMcpToolManager mcpToolManager; |
| | |
| | | private AiPromptTemplateService aiPromptTemplateService; |
| | | @Mock |
| | | private ConfigService configService; |
| | | private AutoTuneControlModeService autoTuneControlModeService; |
| | | @Mock |
| | | private WrkMastService wrkMastService; |
| | | @Mock |
| | |
| | | |
| | | @BeforeEach |
| | | void setUp() { |
| | | autoTuneControlModeService = new AutoTuneControlModeServiceImpl(configService); |
| | | tools = new AutoTuneMcpTools( |
| | | autoTuneSnapshotService, |
| | | autoTuneApplyService, |
| | | aiAutoTuneJobService, |
| | | aiAutoTuneChangeService); |
| | | aiAutoTuneChangeService, |
| | | aiAutoTuneMcpCallService); |
| | | lenient().when(configService.getConfigValue("aiAutoTuneEnabled", "N")).thenReturn("Y"); |
| | | lenient().when(configService.getConfigValue("aiAutoTuneAnalysisOnly", "Y")).thenReturn("N"); |
| | | } |
| | | |
| | | @Test |
| | |
| | | job.setSummary("applied"); |
| | | job.setSuccessCount(1); |
| | | job.setRejectCount(0); |
| | | AiAutoTuneChange change = new AiAutoTuneChange(); |
| | | change.setJobId(7L); |
| | | change.setTargetType("sys_config"); |
| | | change.setTargetKey("conveyorStationTaskLimit"); |
| | | change.setRequestedValue("12"); |
| | | change.setResultStatus("success"); |
| | | AiAutoTuneChange agentChange = new AiAutoTuneChange(); |
| | | agentChange.setJobId(7L); |
| | | agentChange.setTargetType("station"); |
| | | agentChange.setTargetId("101"); |
| | | agentChange.setTargetKey("outTaskLimit"); |
| | | agentChange.setRequestedValue("3"); |
| | | agentChange.setResultStatus("success"); |
| | | AiAutoTuneChange applyChange = new AiAutoTuneChange(); |
| | | applyChange.setJobId(70L); |
| | | applyChange.setTargetType("sys_config"); |
| | | applyChange.setTargetKey("conveyorStationTaskLimit"); |
| | | applyChange.setRequestedValue("12"); |
| | | applyChange.setResultStatus("success"); |
| | | com.zy.ai.entity.AiAutoTuneMcpCall mcpCall = new com.zy.ai.entity.AiAutoTuneMcpCall(); |
| | | mcpCall.setAgentJobId(7L); |
| | | mcpCall.setCallSeq(1); |
| | | mcpCall.setToolName("wcs_local_dispatch_apply_auto_tune_changes"); |
| | | mcpCall.setStatus("success"); |
| | | mcpCall.setApplyJobId(70L); |
| | | |
| | | when(aiAutoTuneJobService.list(any(Wrapper.class))).thenReturn(Collections.singletonList(job)); |
| | | when(aiAutoTuneChangeService.list(any(Wrapper.class))).thenReturn(Collections.singletonList(change)); |
| | | when(aiAutoTuneMcpCallService.list(any(Wrapper.class))).thenReturn(Collections.singletonList(mcpCall)); |
| | | List<AiAutoTuneChange> auditChanges = new ArrayList<>(); |
| | | auditChanges.add(agentChange); |
| | | auditChanges.add(applyChange); |
| | | when(aiAutoTuneChangeService.list(any(Wrapper.class))).thenReturn(auditChanges); |
| | | |
| | | List<Map<String, Object>> result = tools.getRecentAutoTuneJobs(99); |
| | | |
| | | assertEquals(1, result.size()); |
| | | assertEquals(7L, result.get(0).get("id")); |
| | | assertFalse(result.get(0).containsKey("reasoningDigest")); |
| | | assertEquals(1, result.get(0).get("mcpCallCount")); |
| | | List<?> changes = (List<?>) result.get(0).get("changes"); |
| | | assertEquals(1, changes.size()); |
| | | assertEquals(2, changes.size()); |
| | | assertEquals(7L, ((Map<?, ?>) changes.get(0)).get("jobId")); |
| | | assertEquals(70L, ((Map<?, ?>) changes.get(1)).get("jobId")); |
| | | |
| | | ArgumentCaptor<Wrapper<AiAutoTuneJob>> wrapperCaptor = ArgumentCaptor.forClass(Wrapper.class); |
| | | verify(aiAutoTuneJobService).list(wrapperCaptor.capture()); |
| | | assertTrue(wrapperCaptor.getValue().getSqlSegment().contains("limit 20")); |
| | | |
| | | ArgumentCaptor<Wrapper<AiAutoTuneChange>> changeWrapperCaptor = ArgumentCaptor.forClass(Wrapper.class); |
| | | verify(aiAutoTuneChangeService).list(changeWrapperCaptor.capture()); |
| | | Wrapper<AiAutoTuneChange> changeWrapper = changeWrapperCaptor.getValue(); |
| | | assertTrue(changeWrapper.getSqlSegment().contains("job_id IN")); |
| | | List<Object> changeQueryParams = wrapperParamValues(changeWrapper); |
| | | assertTrue(changeQueryParams.contains(7L)); |
| | | assertTrue(changeQueryParams.contains(70L)); |
| | | } |
| | | |
| | | @Test |
| | | void recentJobsMarksRollbackMcpChangesAsRollback() { |
| | | AiAutoTuneJob job = new AiAutoTuneJob(); |
| | | job.setId(8L); |
| | | job.setTriggerType("manual"); |
| | | job.setStatus("success"); |
| | | job.setSuccessCount(1); |
| | | job.setRejectCount(0); |
| | | AiAutoTuneChange rollbackChange = new AiAutoTuneChange(); |
| | | rollbackChange.setJobId(80L); |
| | | rollbackChange.setTargetType("sys_config"); |
| | | rollbackChange.setTargetKey("conveyorStationTaskLimit"); |
| | | rollbackChange.setResultStatus("success"); |
| | | com.zy.ai.entity.AiAutoTuneMcpCall mcpCall = new com.zy.ai.entity.AiAutoTuneMcpCall(); |
| | | mcpCall.setAgentJobId(8L); |
| | | mcpCall.setCallSeq(1); |
| | | mcpCall.setToolName("wcs_local_dispatch_revert_last_auto_tune_job"); |
| | | mcpCall.setStatus("success"); |
| | | mcpCall.setApplyJobId(80L); |
| | | mcpCall.setSuccessCount(1); |
| | | |
| | | when(aiAutoTuneJobService.list(any(Wrapper.class))).thenReturn(Collections.singletonList(job)); |
| | | when(aiAutoTuneMcpCallService.list(any(Wrapper.class))).thenReturn(Collections.singletonList(mcpCall)); |
| | | when(aiAutoTuneChangeService.list(any(Wrapper.class))).thenReturn(Collections.singletonList(rollbackChange)); |
| | | |
| | | List<Map<String, Object>> result = tools.getRecentAutoTuneJobs(1); |
| | | |
| | | assertEquals("rollback", result.get(0).get("writeBehavior")); |
| | | List<?> changes = (List<?>) result.get(0).get("changes"); |
| | | assertEquals(1, changes.size()); |
| | | assertEquals("rollback", ((Map<?, ?>) changes.get(0)).get("writeBehavior")); |
| | | } |
| | | |
| | | @Test |
| | |
| | | AutoTuneApplyResult expected = new AutoTuneApplyResult(); |
| | | expected.setDryRun(true); |
| | | expected.setSuccess(true); |
| | | expected.setChanges(Collections.singletonList(applyResultChange("dry_run"))); |
| | | when(autoTuneApplyService.apply(any(AutoTuneApplyRequest.class))).thenReturn(expected); |
| | | |
| | | AutoTuneChangeCommand command = new AutoTuneChangeCommand(); |
| | |
| | | } |
| | | |
| | | @Test |
| | | void applyToolRejectsDuplicateDryRunTargetsBeforeServiceCall() { |
| | | List<AutoTuneChangeCommand> changes = new ArrayList<>(); |
| | | changes.add(change(" sys_config ", "ignored-first", " conveyorStationTaskLimit ", "12")); |
| | | changes.add(change("SYS_CONFIG", "ignored-second", "conveyorStationTaskLimit", "13")); |
| | | |
| | | IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, |
| | | () -> tools.applyAutoTuneChanges("duplicate target", 10, "agent", true, null, changes)); |
| | | |
| | | assertTrue(exception.getMessage().contains("Duplicate auto-tune change target")); |
| | | assertTrue(exception.getMessage().contains("targetType=sys_config")); |
| | | assertTrue(exception.getMessage().contains("targetKey=conveyorStationTaskLimit")); |
| | | verify(autoTuneApplyService, never()).apply(any(AutoTuneApplyRequest.class)); |
| | | } |
| | | |
| | | @Test |
| | | void applyToolRejectsRealApplyWithoutDryRunTokenInAnalysisOnlyMode() { |
| | | AutoTuneChangeCommand command = change("sys_config", null, "conveyorStationTaskLimit", "12"); |
| | | |
| | | IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, |
| | | () -> tools.applyAutoTuneChanges("direct apply", 10, "agent", false, null, |
| | | Collections.singletonList(command))); |
| | | |
| | | assertTrue(exception.getMessage().contains("dryRunToken is required")); |
| | | verify(autoTuneApplyService, never()).apply(any(AutoTuneApplyRequest.class)); |
| | | } |
| | | |
| | | @Test |
| | | void applyToolAllowsRealApplyOnlyWithMatchingDryRunToken() { |
| | | AutoTuneApplyResult dryRunResult = new AutoTuneApplyResult(); |
| | | dryRunResult.setDryRun(true); |
| | | dryRunResult.setSuccess(true); |
| | | dryRunResult.setChanges(Collections.singletonList(applyResultChange("dry_run"))); |
| | | AutoTuneApplyResult applyResult = new AutoTuneApplyResult(); |
| | | applyResult.setDryRun(false); |
| | | applyResult.setSuccess(true); |
| | |
| | | AutoTuneApplyResult dryRunResult = new AutoTuneApplyResult(); |
| | | dryRunResult.setDryRun(true); |
| | | dryRunResult.setSuccess(true); |
| | | dryRunResult.setChanges(Collections.singletonList(applyResultChange("dry_run"))); |
| | | when(autoTuneApplyService.apply(any(AutoTuneApplyRequest.class))).thenReturn(dryRunResult); |
| | | |
| | | AutoTuneApplyResult preview = tools.applyAutoTuneChanges("preview", 10, "agent", true, null, |
| | |
| | | AutoTuneApplyResult dryRunResult = new AutoTuneApplyResult(); |
| | | dryRunResult.setDryRun(true); |
| | | dryRunResult.setSuccess(true); |
| | | dryRunResult.setChanges(Collections.singletonList(applyResultChange("dry_run"))); |
| | | when(autoTuneApplyService.apply(any(AutoTuneApplyRequest.class))).thenReturn(dryRunResult); |
| | | List<AutoTuneChangeCommand> changes = Collections.singletonList( |
| | | change("sys_config", null, "conveyorStationTaskLimit", "12")); |
| | |
| | | } |
| | | |
| | | @Test |
| | | void applyToolDoesNotIssueDryRunTokenWhenAllChangesAreNoChange() { |
| | | AutoTuneApplyResult dryRunResult = new AutoTuneApplyResult(); |
| | | dryRunResult.setDryRun(true); |
| | | dryRunResult.setSuccess(true); |
| | | dryRunResult.setSuccessCount(0); |
| | | dryRunResult.setRejectCount(0); |
| | | dryRunResult.setChanges(Collections.singletonList(applyResultChange("no_change"))); |
| | | when(autoTuneApplyService.apply(any(AutoTuneApplyRequest.class))).thenReturn(dryRunResult); |
| | | |
| | | AutoTuneApplyResult preview = tools.applyAutoTuneChanges("preview", 10, "agent", true, null, |
| | | Collections.singletonList(change("sys_config", null, "conveyorStationTaskLimit", "12"))); |
| | | |
| | | assertNull(preview.getDryRunToken()); |
| | | verify(autoTuneApplyService, times(1)).apply(any(AutoTuneApplyRequest.class)); |
| | | } |
| | | |
| | | @Test |
| | | void rollbackToolDelegatesToApplyServiceRollback() { |
| | | AutoTuneApplyResult expected = new AutoTuneApplyResult(); |
| | | when(autoTuneApplyService.rollbackLastSuccessfulJob("bad result")).thenReturn(expected); |
| | | |
| | | AutoTuneApplyResult result = tools.revertLastAutoTuneJob("bad result"); |
| | | |
| | | assertSame(expected, result); |
| | | verify(autoTuneApplyService).rollbackLastSuccessfulJob("bad result"); |
| | | } |
| | | |
| | | @Test |
| | | void rollbackToolBlocksRollbackInAnalysisOnlyMode() { |
| | | AutoTuneApplyResult expected = new AutoTuneApplyResult(); |
| | | expected.setSuccess(false); |
| | | expected.setAnalysisOnly(true); |
| | | expected.setNoApply(true); |
| | | expected.setRejectCount(1); |
| | | when(autoTuneApplyService.rollbackLastSuccessfulJob("bad result")).thenReturn(expected); |
| | | |
| | | AutoTuneApplyResult result = tools.revertLastAutoTuneJob("bad result"); |
| | |
| | | } |
| | | |
| | | @Test |
| | | void coordinatorRunsAgentAndReleasesLockWhenGuardWriteFails() { |
| | | AutoTuneAgentService.AutoTuneAgentResult agentResult = successfulAgentResult(); |
| | | when(configService.getConfigValue("aiAutoTuneEnabled", "N")).thenReturn("Y"); |
| | | when(configService.getConfigValue("aiAutoTuneIntervalMinutes", "10")).thenReturn("10"); |
| | | when(wrkMastService.count(any(Wrapper.class))).thenReturn(1L); |
| | | when(redisUtil.get(RedisKeyType.AI_AUTO_TUNE_LAST_TRIGGER_GUARD.key)).thenReturn(null); |
| | | when(aiAutoTuneJobService.list(any(Wrapper.class))).thenReturn(Collections.emptyList()); |
| | | when(redisUtil.trySetStringIfAbsent(anyString(), anyString(), anyLong())).thenReturn(true); |
| | | doThrow(new RuntimeException("guard failed")) |
| | | .when(redisUtil) |
| | | .set(eq(RedisKeyType.AI_AUTO_TUNE_LAST_TRIGGER_GUARD.key), any(), eq(600L)); |
| | | when(autoTuneAgentService.runAutoTune(AutoTuneTriggerType.AUTO.getCode())).thenReturn(agentResult); |
| | | |
| | | AutoTuneCoordinatorService.AutoTuneCoordinatorResult result = coordinatorService().runAutoTuneIfEligible(); |
| | | |
| | | assertFalse(result.getSkipped()); |
| | | assertTrue(result.getTriggered()); |
| | | assertSame(agentResult, result.getAgentResult()); |
| | | verify(autoTuneAgentService).runAutoTune(AutoTuneTriggerType.AUTO.getCode()); |
| | | verify(redisUtil).compareAndDelete(anyString(), anyString()); |
| | | } |
| | | |
| | | @Test |
| | | void coordinatorSetsGuardWhenAgentReturnsFailure() { |
| | | AutoTuneAgentService.AutoTuneAgentResult agentResult = failedAgentResult(); |
| | | when(configService.getConfigValue("aiAutoTuneEnabled", "N")).thenReturn("Y"); |
| | |
| | | } |
| | | |
| | | @Test |
| | | void manualTriggerRunsAgentWithRunningLockAndDoesNotWriteSchedulerGuard() { |
| | | AutoTuneAgentService.AutoTuneAgentResult agentResult = successfulAgentResult(); |
| | | agentResult.setTriggerType(AutoTuneTriggerType.MANUAL.getCode()); |
| | | when(configService.getConfigValue("aiAutoTuneIntervalMinutes", "10")).thenReturn("10"); |
| | | when(wrkMastService.count(any(Wrapper.class))).thenReturn(1L); |
| | | when(redisUtil.trySetStringIfAbsent(anyString(), anyString(), anyLong())).thenReturn(true); |
| | | when(autoTuneAgentService.runAutoTune(AutoTuneTriggerType.MANUAL.getCode())).thenReturn(agentResult); |
| | | |
| | | AutoTuneCoordinatorService.AutoTuneCoordinatorResult result = coordinatorService().runManualAutoTune(); |
| | | |
| | | assertFalse(result.getSkipped()); |
| | | assertTrue(result.getTriggered()); |
| | | assertSame(agentResult, result.getAgentResult()); |
| | | verify(autoTuneAgentService).runAutoTune(AutoTuneTriggerType.MANUAL.getCode()); |
| | | verify(redisUtil, never()).set(eq(RedisKeyType.AI_AUTO_TUNE_LAST_TRIGGER_GUARD.key), any(), anyLong()); |
| | | verify(redisUtil).compareAndDelete(anyString(), anyString()); |
| | | |
| | | ArgumentCaptor<OperateLog> operateLogCaptor = ArgumentCaptor.forClass(OperateLog.class); |
| | | verify(operateLogService).save(operateLogCaptor.capture()); |
| | | assertEquals("ai_auto_tune_manual_trigger", operateLogCaptor.getValue().getAction()); |
| | | |
| | | ArgumentCaptor<AiAutoTuneJob> jobCaptor = ArgumentCaptor.forClass(AiAutoTuneJob.class); |
| | | verify(aiAutoTuneJobService).save(jobCaptor.capture()); |
| | | AiAutoTuneJob auditJob = jobCaptor.getValue(); |
| | | assertEquals(AutoTuneTriggerType.MANUAL.getCode(), auditJob.getTriggerType()); |
| | | assertEquals(AutoTuneJobStatus.NO_CHANGE.getCode(), auditJob.getStatus()); |
| | | assertNotNull(auditJob.getStartTime()); |
| | | assertNotNull(auditJob.getFinishTime()); |
| | | assertEquals(1, auditJob.getHasActiveTasks()); |
| | | assertEquals(AiPromptScene.AUTO_TUNE_DISPATCH.getCode(), auditJob.getPromptSceneCode()); |
| | | assertEquals("no changes needed", auditJob.getSummary()); |
| | | assertEquals(10, auditJob.getIntervalBefore()); |
| | | assertEquals(10, auditJob.getIntervalAfter()); |
| | | assertEquals(0, auditJob.getSuccessCount()); |
| | | assertEquals(0, auditJob.getRejectCount()); |
| | | assertEquals(1, auditJob.getLlmCallCount()); |
| | | assertEquals(10, auditJob.getPromptTokens()); |
| | | assertEquals(5, auditJob.getCompletionTokens()); |
| | | assertEquals(15, auditJob.getTotalTokens()); |
| | | } |
| | | |
| | | @Test |
| | | void manualTriggerAuditsSuccessOnlyWhenActualApplyHappened() { |
| | | AutoTuneAgentService.AutoTuneAgentResult agentResult = actualAppliedAgentResult(); |
| | | agentResult.setTriggerType(AutoTuneTriggerType.MANUAL.getCode()); |
| | | when(configService.getConfigValue("aiAutoTuneIntervalMinutes", "10")).thenReturn("10"); |
| | | when(wrkMastService.count(any(Wrapper.class))).thenReturn(1L); |
| | | when(redisUtil.trySetStringIfAbsent(anyString(), anyString(), anyLong())).thenReturn(true); |
| | | when(autoTuneAgentService.runAutoTune(AutoTuneTriggerType.MANUAL.getCode())).thenReturn(agentResult); |
| | | |
| | | AutoTuneCoordinatorService.AutoTuneCoordinatorResult result = coordinatorService().runManualAutoTune(); |
| | | |
| | | assertFalse(result.getSkipped()); |
| | | ArgumentCaptor<AiAutoTuneJob> jobCaptor = ArgumentCaptor.forClass(AiAutoTuneJob.class); |
| | | verify(aiAutoTuneJobService).save(jobCaptor.capture()); |
| | | AiAutoTuneJob auditJob = jobCaptor.getValue(); |
| | | assertEquals(AutoTuneJobStatus.SUCCESS.getCode(), auditJob.getStatus()); |
| | | assertEquals(3, auditJob.getSuccessCount()); |
| | | assertEquals(0, auditJob.getRejectCount()); |
| | | } |
| | | |
| | | @Test |
| | | void manualTriggerWritesMcpCallAuditUnderAgentJob() { |
| | | AutoTuneAgentService.AutoTuneAgentResult agentResult = actualAppliedAgentResult(); |
| | | agentResult.setTriggerType(AutoTuneTriggerType.MANUAL.getCode()); |
| | | agentResult.setMcpCalls(Collections.singletonList(mcpCallResult())); |
| | | when(configService.getConfigValue("aiAutoTuneIntervalMinutes", "10")).thenReturn("10"); |
| | | when(wrkMastService.count(any(Wrapper.class))).thenReturn(1L); |
| | | when(redisUtil.trySetStringIfAbsent(anyString(), anyString(), anyLong())).thenReturn(true); |
| | | when(autoTuneAgentService.runAutoTune(AutoTuneTriggerType.MANUAL.getCode())).thenReturn(agentResult); |
| | | when(aiAutoTuneJobService.save(any(AiAutoTuneJob.class))).thenAnswer(invocation -> { |
| | | AiAutoTuneJob job = invocation.getArgument(0); |
| | | job.setId(88L); |
| | | return true; |
| | | }); |
| | | when(aiAutoTuneMcpCallService.saveBatch(any())).thenReturn(true); |
| | | |
| | | coordinatorService().runManualAutoTune(); |
| | | |
| | | ArgumentCaptor<List> mcpCallsCaptor = ArgumentCaptor.forClass(List.class); |
| | | verify(aiAutoTuneMcpCallService).saveBatch(mcpCallsCaptor.capture()); |
| | | List<?> savedCalls = mcpCallsCaptor.getValue(); |
| | | assertEquals(1, savedCalls.size()); |
| | | com.zy.ai.entity.AiAutoTuneMcpCall savedCall = (com.zy.ai.entity.AiAutoTuneMcpCall) savedCalls.get(0); |
| | | assertEquals(88L, savedCall.getAgentJobId()); |
| | | assertEquals("wcs_local_dispatch_apply_auto_tune_changes", savedCall.getToolName()); |
| | | assertEquals(1, savedCall.getDryRun()); |
| | | assertEquals(77L, savedCall.getApplyJobId()); |
| | | } |
| | | |
| | | @Test |
| | | void manualTriggerPreservesFullAuditSummaryAndErrorMessage() { |
| | | String longSummary = "自动调参失败原因".repeat(120); |
| | | AutoTuneAgentService.AutoTuneAgentResult agentResult = failedAgentResult(); |
| | | agentResult.setTriggerType(AutoTuneTriggerType.MANUAL.getCode()); |
| | | agentResult.setSummary(longSummary); |
| | | when(configService.getConfigValue("aiAutoTuneIntervalMinutes", "10")).thenReturn("10"); |
| | | when(wrkMastService.count(any(Wrapper.class))).thenReturn(1L); |
| | | when(redisUtil.trySetStringIfAbsent(anyString(), anyString(), anyLong())).thenReturn(true); |
| | | when(autoTuneAgentService.runAutoTune(AutoTuneTriggerType.MANUAL.getCode())).thenReturn(agentResult); |
| | | |
| | | coordinatorService().runManualAutoTune(); |
| | | |
| | | ArgumentCaptor<AiAutoTuneJob> jobCaptor = ArgumentCaptor.forClass(AiAutoTuneJob.class); |
| | | verify(aiAutoTuneJobService).save(jobCaptor.capture()); |
| | | AiAutoTuneJob auditJob = jobCaptor.getValue(); |
| | | assertEquals(longSummary, auditJob.getSummary()); |
| | | assertEquals(longSummary, auditJob.getErrorMessage()); |
| | | } |
| | | |
| | | @Test |
| | | void manualTriggerSkipsWhenRunningLockIsNotAcquired() { |
| | | when(redisUtil.trySetStringIfAbsent(anyString(), anyString(), anyLong())).thenReturn(false); |
| | | |
| | | AutoTuneCoordinatorService.AutoTuneCoordinatorResult result = coordinatorService().runManualAutoTune(); |
| | | |
| | | assertTrue(result.getSkipped()); |
| | | assertEquals("running_lock_not_acquired", result.getReason()); |
| | | verify(autoTuneAgentService, never()).runAutoTune(anyString()); |
| | | verify(redisUtil, never()).compareAndDelete(anyString(), anyString()); |
| | | } |
| | | |
| | | @Test |
| | | void agentExecutesSnapshotDryRunAndRealApplyToolSequence() { |
| | | AutoTuneAgentServiceImpl service = new AutoTuneAgentServiceImpl( |
| | | llmChatService, |
| | | mcpToolManager, |
| | | aiPromptTemplateService); |
| | | aiPromptTemplateService, |
| | | autoTuneControlModeService); |
| | | AiPromptTemplate promptTemplate = new AiPromptTemplate(); |
| | | promptTemplate.setContent("system prompt"); |
| | | when(aiPromptTemplateService.resolvePublished("wcs_auto_tune_dispatch")).thenReturn(promptTemplate); |
| | |
| | | dryRunOutput.put("dryRunToken", "token-123"); |
| | | return dryRunOutput; |
| | | } |
| | | if ("wcs_local_dispatch_apply_auto_tune_changes".equals(toolName) |
| | | && Boolean.FALSE.equals(arguments.getBoolean("dryRun"))) { |
| | | LinkedHashMap<String, Object> applyOutput = new LinkedHashMap<>(); |
| | | applyOutput.put("success", true); |
| | | applyOutput.put("dryRun", false); |
| | | applyOutput.put("successCount", 1); |
| | | applyOutput.put("rejectCount", 0); |
| | | return applyOutput; |
| | | } |
| | | return Collections.singletonMap("ok", true); |
| | | }); |
| | | when(llmChatService.chatCompletion(any(), anyDouble(), anyInt(), any())) |
| | | when(llmChatService.chatCompletionOrThrow(any(), anyDouble(), anyInt(), any())) |
| | | .thenReturn( |
| | | response("read snapshot", toolCall("call_1", "wcs_local_dispatch_get_auto_tune_snapshot", "{}"), 10, 5), |
| | | response("dry-run first", toolCall("call_2", "wcs_local_dispatch_apply_auto_tune_changes", |
| | |
| | | assertEquals(26L, result.getCompletionTokens()); |
| | | assertEquals(72L, result.getTotalTokens()); |
| | | assertTrue(result.getSummary().contains("已完成自动调参")); |
| | | assertTrue(result.getActualApplyCalled()); |
| | | assertFalse(result.getRollbackCalled()); |
| | | assertEquals(1, result.getSuccessCount()); |
| | | assertEquals(0, result.getRejectCount()); |
| | | |
| | | ArgumentCaptor<String> toolNameCaptor = ArgumentCaptor.forClass(String.class); |
| | | ArgumentCaptor<JSONObject> argumentCaptor = ArgumentCaptor.forClass(JSONObject.class); |
| | |
| | | assertEquals("token-123", argumentCaptor.getAllValues().get(2).getString("dryRunToken")); |
| | | |
| | | ArgumentCaptor<List<Object>> toolsCaptor = ArgumentCaptor.forClass(List.class); |
| | | verify(llmChatService, times(4)).chatCompletion(any(), anyDouble(), anyInt(), toolsCaptor.capture()); |
| | | verify(llmChatService, times(4)).chatCompletionOrThrow(any(), anyDouble(), anyInt(), toolsCaptor.capture()); |
| | | List<String> visibleToolNames = toolNames(toolsCaptor.getAllValues().get(0)); |
| | | assertEquals(4, visibleToolNames.size()); |
| | | assertTrue(visibleToolNames.contains("wcs_local_dispatch_get_auto_tune_snapshot")); |
| | |
| | | AutoTuneAgentServiceImpl service = agentService(); |
| | | when(mcpToolManager.buildOpenAiTools()).thenReturn(allowedOpenAiTools()); |
| | | when(mcpToolManager.callTool(any(), any(JSONObject.class))).thenReturn(Collections.singletonMap("ok", true)); |
| | | when(llmChatService.chatCompletion(any(), anyDouble(), anyInt(), any())) |
| | | when(llmChatService.chatCompletionOrThrow(any(), anyDouble(), anyInt(), any())) |
| | | .thenReturn( |
| | | response("snapshot", toolCall("call_1", "wcs_local_dispatch_get_auto_tune_snapshot", |
| | | "{}"), 10, 5), |
| | |
| | | } |
| | | |
| | | @Test |
| | | void agentCallsMcpForRealApplyInAnalysisOnlyMode() { |
| | | when(configService.getConfigValue("aiAutoTuneAnalysisOnly", "Y")).thenReturn("Y"); |
| | | AutoTuneAgentServiceImpl service = agentService(); |
| | | when(mcpToolManager.buildOpenAiTools()).thenReturn(allowedOpenAiTools()); |
| | | when(mcpToolManager.callTool(any(), any(JSONObject.class))).thenAnswer(invocation -> { |
| | | String toolName = invocation.getArgument(0); |
| | | if ("wcs_local_dispatch_apply_auto_tune_changes".equals(toolName)) { |
| | | return rejectedAnalysisOnlyResult(false); |
| | | } |
| | | return Collections.singletonMap("ok", true); |
| | | }); |
| | | when(llmChatService.chatCompletionOrThrow(any(), anyDouble(), anyInt(), any())) |
| | | .thenReturn( |
| | | response("snapshot", toolCall("call_1", "wcs_local_dispatch_get_auto_tune_snapshot", |
| | | "{}"), 10, 5), |
| | | response("apply", toolCall("call_2", "wcs_local_dispatch_apply_auto_tune_changes", |
| | | "{\"dryRun\":false,\"changes\":[{\"targetType\":\"sys_config\",\"targetKey\":\"conveyorStationTaskLimit\",\"newValue\":\"12\"}]}"), 10, 5), |
| | | response("stop", null, 10, 5) |
| | | ); |
| | | |
| | | AutoTuneAgentService.AutoTuneAgentResult result = service.runAutoTune("manual"); |
| | | |
| | | assertFalse(result.getSuccess()); |
| | | assertTrue(result.getAnalysisOnly()); |
| | | assertFalse(result.getAllowApply()); |
| | | assertEquals("analysis_only", result.getExecutionMode()); |
| | | assertFalse(result.getActualApplyCalled()); |
| | | assertEquals(1, result.getRejectCount()); |
| | | assertTrue(result.getSummary().contains("仅分析模式禁止实际应用/回滚")); |
| | | verify(mcpToolManager).callTool(eq("wcs_local_dispatch_get_auto_tune_snapshot"), any(JSONObject.class)); |
| | | verify(mcpToolManager).callTool(eq("wcs_local_dispatch_apply_auto_tune_changes"), any(JSONObject.class)); |
| | | } |
| | | |
| | | @Test |
| | | void agentCallsMcpForRollbackInAnalysisOnlyMode() { |
| | | when(configService.getConfigValue("aiAutoTuneAnalysisOnly", "Y")).thenReturn("Y"); |
| | | AutoTuneAgentServiceImpl service = agentService(); |
| | | when(mcpToolManager.buildOpenAiTools()).thenReturn(allowedOpenAiTools()); |
| | | when(mcpToolManager.callTool(any(), any(JSONObject.class))).thenAnswer(invocation -> { |
| | | String toolName = invocation.getArgument(0); |
| | | if ("wcs_local_dispatch_revert_last_auto_tune_job".equals(toolName)) { |
| | | return rejectedAnalysisOnlyResult(false); |
| | | } |
| | | return Collections.singletonMap("ok", true); |
| | | }); |
| | | when(llmChatService.chatCompletionOrThrow(any(), anyDouble(), anyInt(), any())) |
| | | .thenReturn( |
| | | response("snapshot", toolCall("call_1", "wcs_local_dispatch_get_auto_tune_snapshot", |
| | | "{}"), 10, 5), |
| | | response("rollback", toolCall("call_2", "wcs_local_dispatch_revert_last_auto_tune_job", |
| | | "{\"reason\":\"bad result\"}"), 10, 5), |
| | | response("stop", null, 10, 5) |
| | | ); |
| | | |
| | | AutoTuneAgentService.AutoTuneAgentResult result = service.runAutoTune("manual"); |
| | | |
| | | assertFalse(result.getSuccess()); |
| | | assertTrue(result.getAnalysisOnly()); |
| | | assertTrue(result.getRollbackCalled()); |
| | | assertEquals(1, result.getRejectCount()); |
| | | assertTrue(result.getSummary().contains("仅分析模式禁止实际应用/回滚")); |
| | | verify(mcpToolManager).callTool(eq("wcs_local_dispatch_get_auto_tune_snapshot"), any(JSONObject.class)); |
| | | verify(mcpToolManager).callTool(eq("wcs_local_dispatch_revert_last_auto_tune_job"), any(JSONObject.class)); |
| | | } |
| | | |
| | | @Test |
| | | void agentMarksSnapshotOnlyRunAsNoActualMutationEvenIfAssistantClaimsApplied() { |
| | | AutoTuneAgentServiceImpl service = agentService(); |
| | | when(mcpToolManager.buildOpenAiTools()).thenReturn(allowedOpenAiTools()); |
| | | when(mcpToolManager.callTool(any(), any(JSONObject.class))).thenReturn(Collections.singletonMap("ok", true)); |
| | | when(llmChatService.chatCompletionOrThrow(any(), anyDouble(), anyInt(), any())) |
| | | .thenReturn( |
| | | response("snapshot", toolCall("call_1", "wcs_local_dispatch_get_auto_tune_snapshot", "{}"), 10, 5), |
| | | response("已执行 dry-run 并实际应用成功", null, 10, 5) |
| | | ); |
| | | |
| | | AutoTuneAgentService.AutoTuneAgentResult result = service.runAutoTune("manual"); |
| | | |
| | | assertTrue(result.getSuccess()); |
| | | assertFalse(result.getActualApplyCalled()); |
| | | assertFalse(result.getRollbackCalled()); |
| | | assertEquals(0, result.getSuccessCount()); |
| | | assertTrue(result.getSummary().contains("自动调参 Agent 未调用实际应用或回滚工具,未修改运行参数。")); |
| | | } |
| | | |
| | | @Test |
| | | void agentDoesNotMarkNoChangeRealApplyAsActualApplyWhenSuccessCountIsPositive() { |
| | | AutoTuneAgentServiceImpl service = agentService(); |
| | | when(mcpToolManager.buildOpenAiTools()).thenReturn(allowedOpenAiTools()); |
| | | when(mcpToolManager.callTool(any(), any(JSONObject.class))).thenAnswer(invocation -> { |
| | | String toolName = invocation.getArgument(0); |
| | | if ("wcs_local_dispatch_apply_auto_tune_changes".equals(toolName)) { |
| | | LinkedHashMap<String, Object> noChange = new LinkedHashMap<>(); |
| | | noChange.put("resultStatus", "no_change"); |
| | | |
| | | LinkedHashMap<String, Object> applyOutput = new LinkedHashMap<>(); |
| | | applyOutput.put("success", true); |
| | | applyOutput.put("dryRun", false); |
| | | applyOutput.put("successCount", 1); |
| | | applyOutput.put("rejectCount", 0); |
| | | applyOutput.put("changes", Collections.singletonList(noChange)); |
| | | return applyOutput; |
| | | } |
| | | return Collections.singletonMap("ok", true); |
| | | }); |
| | | when(llmChatService.chatCompletionOrThrow(any(), anyDouble(), anyInt(), any())) |
| | | .thenReturn( |
| | | response("snapshot", toolCall("call_1", "wcs_local_dispatch_get_auto_tune_snapshot", |
| | | "{}"), 10, 5), |
| | | response("apply no change", toolCall("call_2", "wcs_local_dispatch_apply_auto_tune_changes", |
| | | "{\"dryRun\":false,\"dryRunToken\":\"token-123\",\"changes\":[{\"targetType\":\"sys_config\",\"targetKey\":\"conveyorStationTaskLimit\",\"newValue\":\"12\"}]}"), 10, 5), |
| | | response("无变更", null, 10, 5) |
| | | ); |
| | | |
| | | AutoTuneAgentService.AutoTuneAgentResult result = service.runAutoTune("manual"); |
| | | |
| | | assertTrue(result.getSuccess()); |
| | | assertFalse(result.getActualApplyCalled()); |
| | | assertEquals(1, result.getSuccessCount()); |
| | | assertEquals(0, result.getRejectCount()); |
| | | } |
| | | |
| | | @Test |
| | | void agentMarksRejectedDryRunAsFailedAndFeedsBackGenericConstraints() { |
| | | AutoTuneAgentServiceImpl service = agentService(); |
| | | when(mcpToolManager.buildOpenAiTools()).thenReturn(allowedOpenAiTools()); |
| | | when(mcpToolManager.callTool(any(), any(JSONObject.class))).thenAnswer(invocation -> { |
| | | String toolName = invocation.getArgument(0); |
| | | if ("wcs_local_dispatch_get_auto_tune_snapshot".equals(toolName)) { |
| | | return Collections.singletonMap("ok", true); |
| | | } |
| | | LinkedHashMap<String, Object> rejectedChange = new LinkedHashMap<>(); |
| | | rejectedChange.put("targetType", "sys_config"); |
| | | rejectedChange.put("targetId", ""); |
| | | rejectedChange.put("targetKey", "crnOutBatchRunningLimit"); |
| | | rejectedChange.put("oldValue", "5"); |
| | | rejectedChange.put("requestedValue", "9"); |
| | | rejectedChange.put("resultStatus", "rejected"); |
| | | rejectedChange.put("rejectReason", "crnOutBatchRunningLimit 单次调整步长不能超过 3"); |
| | | |
| | | LinkedHashMap<String, Object> dryRunOutput = new LinkedHashMap<>(); |
| | | dryRunOutput.put("success", false); |
| | | dryRunOutput.put("dryRun", true); |
| | | dryRunOutput.put("rejectCount", 1); |
| | | dryRunOutput.put("changes", Collections.singletonList(rejectedChange)); |
| | | return dryRunOutput; |
| | | }); |
| | | when(llmChatService.chatCompletionOrThrow(any(), anyDouble(), anyInt(), any())) |
| | | .thenReturn( |
| | | response("snapshot", toolCall("call_1", "wcs_local_dispatch_get_auto_tune_snapshot", "{}"), 10, 5), |
| | | response("dry run rejected", toolCall("call_2", "wcs_local_dispatch_apply_auto_tune_changes", |
| | | "{\"dryRun\":true,\"changes\":[{\"targetType\":\"sys_config\",\"targetKey\":\"crnOutBatchRunningLimit\",\"newValue\":\"9\"}]}"), 10, 5), |
| | | response("停止实际应用", null, 10, 5) |
| | | ); |
| | | |
| | | AutoTuneAgentService.AutoTuneAgentResult result = service.runAutoTune("auto"); |
| | | |
| | | assertFalse(result.getSuccess()); |
| | | assertTrue(result.getSummary().contains("被拒绝的 dry-run/apply")); |
| | | assertEquals(0, result.getSuccessCount()); |
| | | assertEquals(1, result.getRejectCount()); |
| | | ArgumentCaptor<List<ChatCompletionRequest.Message>> messagesCaptor = ArgumentCaptor.forClass(List.class); |
| | | verify(llmChatService, times(3)).chatCompletionOrThrow(messagesCaptor.capture(), anyDouble(), anyInt(), any()); |
| | | String userInstruction = messagesCaptor.getAllValues().stream() |
| | | .flatMap(List::stream) |
| | | .filter(message -> "user".equals(message.getRole())) |
| | | .map(ChatCompletionRequest.Message::getContent) |
| | | .filter(content -> content != null && content.contains("ruleSnapshot")) |
| | | .findFirst() |
| | | .orElse(""); |
| | | assertTrue(userInstruction.contains("所有提交给")); |
| | | assertTrue(userInstruction.contains("minValue")); |
| | | assertTrue(userInstruction.contains("cooldownMinutes")); |
| | | String rejectedToolMessage = messagesCaptor.getAllValues().stream() |
| | | .flatMap(List::stream) |
| | | .filter(message -> "tool".equals(message.getRole())) |
| | | .map(ChatCompletionRequest.Message::getContent) |
| | | .filter(content -> content != null && content.contains("rejectCount")) |
| | | .findFirst() |
| | | .orElse(""); |
| | | assertTrue(rejectedToolMessage.contains("禁止继续实际应用")); |
| | | assertTrue(rejectedToolMessage.contains("ruleSnapshot")); |
| | | assertTrue(rejectedToolMessage.contains("maxStep")); |
| | | assertFalse(rejectedToolMessage.contains("outBufferCapacity")); |
| | | } |
| | | |
| | | @Test |
| | | void agentKeepsOriginalLlmFailureReason() { |
| | | AutoTuneAgentServiceImpl service = agentService(); |
| | | when(mcpToolManager.buildOpenAiTools()).thenReturn(allowedOpenAiTools()); |
| | | when(llmChatService.chatCompletionOrThrow(any(), anyDouble(), anyInt(), any())) |
| | | .thenThrow(new IllegalStateException("OpenAI Responses 调用失败: HTTP 502")); |
| | | |
| | | AutoTuneAgentService.AutoTuneAgentResult result = service.runAutoTune("auto"); |
| | | |
| | | assertFalse(result.getSuccess()); |
| | | assertTrue(result.getSummary().contains("OpenAI Responses 调用失败: HTTP 502")); |
| | | assertFalse(result.getSummary().contains("LLM returned empty response")); |
| | | } |
| | | |
| | | @Test |
| | | void agentFailsAndDoesNotExecuteDisallowedToolCall() { |
| | | AutoTuneAgentServiceImpl service = agentService(); |
| | | when(mcpToolManager.buildOpenAiTools()).thenReturn(allowedOpenAiTools()); |
| | | when(llmChatService.chatCompletion(any(), anyDouble(), anyInt(), any())) |
| | | when(llmChatService.chatCompletionOrThrow(any(), anyDouble(), anyInt(), any())) |
| | | .thenReturn(response("bad tool", toolCall("call_1", "wcs_local_device_get_crn_status", "{}"), 10, 5)); |
| | | |
| | | AutoTuneAgentService.AutoTuneAgentResult result = service.runAutoTune("scheduler"); |
| | |
| | | AutoTuneAgentServiceImpl service = agentService(); |
| | | when(mcpToolManager.buildOpenAiTools()).thenReturn(allowedOpenAiTools()); |
| | | when(mcpToolManager.callTool(any(), any(JSONObject.class))).thenThrow(new RuntimeException("boom")); |
| | | when(llmChatService.chatCompletion(any(), anyDouble(), anyInt(), any())) |
| | | when(llmChatService.chatCompletionOrThrow(any(), anyDouble(), anyInt(), any())) |
| | | .thenReturn(response("snapshot", toolCall("call_1", "wcs_local_dispatch_get_auto_tune_snapshot", "{}"), 10, 5)); |
| | | |
| | | AutoTuneAgentService.AutoTuneAgentResult result = service.runAutoTune("scheduler"); |
| | |
| | | void agentFailsWhenLlmReturnsNoToolCalls() { |
| | | AutoTuneAgentServiceImpl service = agentService(); |
| | | when(mcpToolManager.buildOpenAiTools()).thenReturn(allowedOpenAiTools()); |
| | | when(llmChatService.chatCompletion(any(), anyDouble(), anyInt(), any())) |
| | | when(llmChatService.chatCompletionOrThrow(any(), anyDouble(), anyInt(), any())) |
| | | .thenReturn(response("no changes needed", null, 10, 5)); |
| | | |
| | | AutoTuneAgentService.AutoTuneAgentResult result = service.runAutoTune("scheduler"); |
| | |
| | | AutoTuneAgentServiceImpl service = agentService(); |
| | | when(mcpToolManager.buildOpenAiTools()).thenReturn(allowedOpenAiTools()); |
| | | when(mcpToolManager.callTool(any(), any(JSONObject.class))).thenReturn(Collections.singletonMap("ok", true)); |
| | | when(llmChatService.chatCompletion(any(), anyDouble(), anyInt(), any())) |
| | | when(llmChatService.chatCompletionOrThrow(any(), anyDouble(), anyInt(), any())) |
| | | .thenReturn(response("keep going", toolCall("call_1", "wcs_local_dispatch_get_auto_tune_snapshot", "{}"), 10, 5)); |
| | | |
| | | AutoTuneAgentService.AutoTuneAgentResult result = service.runAutoTune("scheduler"); |
| | |
| | | AiPromptTemplate promptTemplate = new AiPromptTemplate(); |
| | | promptTemplate.setContent("system prompt"); |
| | | when(aiPromptTemplateService.resolvePublished("wcs_auto_tune_dispatch")).thenReturn(promptTemplate); |
| | | return new AutoTuneAgentServiceImpl(llmChatService, mcpToolManager, aiPromptTemplateService); |
| | | return new AutoTuneAgentServiceImpl( |
| | | llmChatService, |
| | | mcpToolManager, |
| | | aiPromptTemplateService, |
| | | autoTuneControlModeService); |
| | | } |
| | | |
| | | private AutoTuneCoordinatorServiceImpl coordinatorService() { |
| | | return new AutoTuneCoordinatorServiceImpl( |
| | | configService, |
| | | autoTuneControlModeService, |
| | | wrkMastService, |
| | | aiAutoTuneJobService, |
| | | aiAutoTuneMcpCallService, |
| | | autoTuneAgentService, |
| | | redisUtil, |
| | | operateLogService); |
| | |
| | | result.setCompletionTokens(5L); |
| | | result.setTotalTokens(15L); |
| | | result.setMaxRoundsReached(false); |
| | | result.setActualApplyCalled(false); |
| | | result.setRollbackCalled(false); |
| | | result.setSuccessCount(0); |
| | | result.setRejectCount(0); |
| | | return result; |
| | | } |
| | | |
| | | private AutoTuneAgentService.AutoTuneAgentResult actualAppliedAgentResult() { |
| | | AutoTuneAgentService.AutoTuneAgentResult result = successfulAgentResult(); |
| | | result.setSummary("applied"); |
| | | result.setActualApplyCalled(true); |
| | | result.setSuccessCount(3); |
| | | result.setRejectCount(0); |
| | | return result; |
| | | } |
| | | |
| | | private AutoTuneAgentService.McpCallResult mcpCallResult() { |
| | | AutoTuneAgentService.McpCallResult result = new AutoTuneAgentService.McpCallResult(); |
| | | result.setCallSeq(1); |
| | | result.setToolName("wcs_local_dispatch_apply_auto_tune_changes"); |
| | | result.setStatus("success"); |
| | | result.setDryRun(true); |
| | | result.setApplyJobId(77L); |
| | | result.setSuccessCount(1); |
| | | result.setRejectCount(0); |
| | | result.setDurationMs(12L); |
| | | result.setRequestJson("{\"dryRun\":true}"); |
| | | result.setResponseJson("{\"success\":true}"); |
| | | return result; |
| | | } |
| | | |
| | |
| | | return result; |
| | | } |
| | | |
| | | private AutoTuneApplyResult rejectedAnalysisOnlyResult(Boolean dryRun) { |
| | | AutoTuneApplyResult result = new AutoTuneApplyResult(); |
| | | result.setDryRun(dryRun); |
| | | result.setSuccess(false); |
| | | result.setAnalysisOnly(true); |
| | | result.setNoApply(true); |
| | | result.setSuccessCount(0); |
| | | result.setRejectCount(1); |
| | | result.setSummary("仅分析模式禁止实际应用/回滚,未修改运行参数"); |
| | | result.setChanges(new ArrayList<>()); |
| | | return result; |
| | | } |
| | | |
| | | private AutoTuneChangeCommand change(String targetType, String targetId, String targetKey, String newValue) { |
| | | AutoTuneChangeCommand command = new AutoTuneChangeCommand(); |
| | | command.setTargetType(targetType); |
| | |
| | | return command; |
| | | } |
| | | |
| | | private AiAutoTuneChange applyResultChange(String resultStatus) { |
| | | AiAutoTuneChange change = new AiAutoTuneChange(); |
| | | change.setResultStatus(resultStatus); |
| | | return change; |
| | | } |
| | | |
| | | private List<Object> wrapperParamValues(Wrapper<?> wrapper) { |
| | | if (!(wrapper instanceof AbstractWrapper<?, ?, ?> abstractWrapper)) { |
| | | return Collections.emptyList(); |
| | | } |
| | | return new ArrayList<>(abstractWrapper.getParamNameValuePairs().values()); |
| | | } |
| | | |
| | | private List<Object> allowedOpenAiTools() { |
| | | List<Object> tools = new ArrayList<>(); |
| | | tools.add(openAiTool("wcs_local_dispatch_get_auto_tune_snapshot")); |