feat(trace): isolate aiops runs
This commit is contained in:
@@ -29,4 +29,13 @@ class ChatControllerTest {
|
||||
assertEquals("问题内容不能为空", body.getErrorMessage());
|
||||
verifyNoInteractions(chatService);
|
||||
}
|
||||
|
||||
@Test
|
||||
void aiOpsMetadataMessageCarriesSessionAndRunId() {
|
||||
ChatController.SseMessage message = ChatController.SseMessage.metadata("session-1", "run-1");
|
||||
|
||||
assertEquals("metadata", message.getType());
|
||||
assertEquals("session-1", message.getSessionId());
|
||||
assertEquals("run-1", message.getRunId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
package com.superbiz.agent.service;
|
||||
|
||||
import com.alibaba.cloud.ai.graph.agent.ReactAgent;
|
||||
import com.alibaba.cloud.ai.graph.agent.flow.agent.SupervisorAgent;
|
||||
import com.superbiz.agent.config.AiOpsPromptProperties;
|
||||
import com.superbiz.agent.domain.entity.AgentStep;
|
||||
import com.superbiz.agent.domain.entity.DiagnosisRun;
|
||||
import com.superbiz.agent.domain.entity.DiagnosisSession;
|
||||
import com.superbiz.agent.domain.entity.ToolInvocation;
|
||||
import com.superbiz.agent.dto.AIOpsRequest;
|
||||
import com.superbiz.agent.repository.AgentStepRepository;
|
||||
import com.superbiz.agent.repository.DiagnosisRunRepository;
|
||||
import com.superbiz.agent.repository.DiagnosisSessionRepository;
|
||||
import com.superbiz.agent.repository.ToolInvocationRepository;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.util.Optional;
|
||||
@@ -19,17 +26,22 @@ import static org.mockito.Mockito.*;
|
||||
class AiOpsServiceTest {
|
||||
|
||||
private final DiagnosisSessionRepository diagnosisSessionRepository = mock(DiagnosisSessionRepository.class);
|
||||
private final DiagnosisRunRepository diagnosisRunRepository = mock(DiagnosisRunRepository.class);
|
||||
private final AgentStepRepository agentStepRepository = mock(AgentStepRepository.class);
|
||||
private final ToolInvocationRepository toolInvocationRepository = mock(ToolInvocationRepository.class);
|
||||
private final AiOpsPromptProperties promptProperties = mock(AiOpsPromptProperties.class);
|
||||
private final AiOpsService service = new AiOpsService();
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
ReflectionTestUtils.setField(service, "diagnosisSessionRepository", diagnosisSessionRepository);
|
||||
ReflectionTestUtils.setField(service, "diagnosisRunRepository", diagnosisRunRepository);
|
||||
ReflectionTestUtils.setField(service, "agentStepRepository", agentStepRepository);
|
||||
ReflectionTestUtils.setField(service, "toolInvocationRepository", toolInvocationRepository);
|
||||
ReflectionTestUtils.setField(service, "aiOpsRuleEvaluationService", new AiOpsRuleEvaluationService());
|
||||
ReflectionTestUtils.setField(service, "selfEvaluationMergeService", new SelfEvaluationMergeService());
|
||||
ReflectionTestUtils.setField(service, "promptProperties", promptProperties);
|
||||
when(promptProperties.getSupervisor()).thenReturn("supervisor prompt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -139,19 +151,48 @@ class AiOpsServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void persistFinalReportUpdatesDiagnosisSessionAnswer() {
|
||||
DiagnosisSession session = DiagnosisSession.builder()
|
||||
void persistFinalReportUpdatesDiagnosisRunAnswerAndEvaluationByRun() {
|
||||
DiagnosisRun run = DiagnosisRun.builder()
|
||||
.sessionId("aiops-session-001")
|
||||
.runId("run-aiops-001")
|
||||
.query("AI Ops alert analysis")
|
||||
.status("SUCCESS")
|
||||
.agentFlow("AI_OPS")
|
||||
.build();
|
||||
when(diagnosisSessionRepository.findBySessionId("aiops-session-001")).thenReturn(Optional.of(session));
|
||||
when(toolInvocationRepository.findBySessionIdOrderByIdAsc("aiops-session-001")).thenReturn(List.of());
|
||||
ToolInvocation invocation = ToolInvocation.builder()
|
||||
.sessionId("aiops-session-001")
|
||||
.runId("run-aiops-001")
|
||||
.toolName("query_logs")
|
||||
.success(true)
|
||||
.build();
|
||||
when(diagnosisRunRepository.findBySessionIdAndRunId("aiops-session-001", "run-aiops-001"))
|
||||
.thenReturn(Optional.of(run));
|
||||
when(toolInvocationRepository.findByRunIdOrderByIdAsc("run-aiops-001")).thenReturn(List.of(invocation));
|
||||
|
||||
service.persistFinalReport("aiops-session-001", "# 告警分析报告\nHighCPUUsage payment-service analysis with evidence summary.");
|
||||
service.persistFinalReport("aiops-session-001", "run-aiops-001",
|
||||
"# 告警分析报告\nHighCPUUsage payment-service analysis with evidence summary.", null);
|
||||
|
||||
assertEquals("# 告警分析报告\nHighCPUUsage payment-service analysis with evidence summary.", session.getAnswer());
|
||||
assertEquals("# 告警分析报告\nHighCPUUsage payment-service analysis with evidence summary.", run.getAnswer());
|
||||
assertTrue(run.getSelfEvaluation().contains("aiops_rule_evaluation"));
|
||||
verify(toolInvocationRepository).findByRunIdOrderByIdAsc("run-aiops-001");
|
||||
verify(diagnosisRunRepository).save(run);
|
||||
verify(diagnosisSessionRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void legacyPersistFinalReportStillUpdatesHistoricalDiagnosisSession() {
|
||||
DiagnosisSession session = DiagnosisSession.builder()
|
||||
.sessionId("legacy-aiops-session")
|
||||
.query("AI Ops alert analysis")
|
||||
.status("SUCCESS")
|
||||
.agentFlow("AI_OPS")
|
||||
.build();
|
||||
when(diagnosisSessionRepository.findBySessionId("legacy-aiops-session")).thenReturn(Optional.of(session));
|
||||
when(toolInvocationRepository.findBySessionIdOrderByIdAsc("legacy-aiops-session")).thenReturn(List.of());
|
||||
|
||||
service.persistFinalReport("legacy-aiops-session", "# 告警分析报告\nLegacy analysis with evidence summary.");
|
||||
|
||||
assertEquals("# 告警分析报告\nLegacy analysis with evidence summary.", session.getAnswer());
|
||||
assertTrue(session.getSelfEvaluation().contains("aiops_rule_evaluation"));
|
||||
verify(diagnosisSessionRepository).save(session);
|
||||
}
|
||||
@@ -160,32 +201,68 @@ class AiOpsServiceTest {
|
||||
void persistFinalReportSkipsBlankInput() {
|
||||
service.persistFinalReport("aiops-session-001", " ");
|
||||
|
||||
verifyNoInteractions(diagnosisSessionRepository);
|
||||
verifyNoInteractions(diagnosisSessionRepository, diagnosisRunRepository);
|
||||
}
|
||||
|
||||
@Test
|
||||
void backfillSessionMetricsUsesRealToolInvocationCount() {
|
||||
DiagnosisSession session = DiagnosisSession.builder()
|
||||
void backfillRunMetricsUsesRunScopedRows() {
|
||||
DiagnosisRun run = DiagnosisRun.builder()
|
||||
.sessionId("aiops-session-002")
|
||||
.runId("run-aiops-002")
|
||||
.build();
|
||||
AgentStep stepWithTool = AgentStep.builder()
|
||||
.sessionId("aiops-session-002")
|
||||
.runId("run-aiops-002")
|
||||
.hasToolCall(true)
|
||||
.tokenCount(10)
|
||||
.build();
|
||||
AgentStep stepWithoutTool = AgentStep.builder()
|
||||
.sessionId("aiops-session-002")
|
||||
.runId("run-aiops-002")
|
||||
.hasToolCall(false)
|
||||
.tokenCount(20)
|
||||
.build();
|
||||
when(agentStepRepository.findBySessionIdOrderByStepIndex("aiops-session-002"))
|
||||
when(agentStepRepository.findByRunIdOrderByStepIndex("run-aiops-002"))
|
||||
.thenReturn(List.of(stepWithTool, stepWithoutTool));
|
||||
when(toolInvocationRepository.countBySessionId("aiops-session-002")).thenReturn(11L);
|
||||
when(toolInvocationRepository.countByRunId("run-aiops-002")).thenReturn(11L);
|
||||
|
||||
ReflectionTestUtils.invokeMethod(service, "backfillSessionMetrics", session);
|
||||
ReflectionTestUtils.invokeMethod(service, "backfillRunMetrics", run);
|
||||
|
||||
assertEquals(2, session.getStepCount());
|
||||
assertEquals(30, session.getTotalTokenCount());
|
||||
assertEquals(11, session.getToolCallCount());
|
||||
assertEquals(2, run.getStepCount());
|
||||
assertEquals(30, run.getTotalTokenCount());
|
||||
assertEquals(11, run.getToolCallCount());
|
||||
verify(agentStepRepository).findByRunIdOrderByStepIndex("run-aiops-002");
|
||||
verify(toolInvocationRepository).countByRunId("run-aiops-002");
|
||||
}
|
||||
|
||||
@Test
|
||||
void sameAiOpsSessionCanStartDistinctRuns() {
|
||||
AIOpsRequest request = new AIOpsRequest();
|
||||
request.setAlertName("HighCPUUsage");
|
||||
|
||||
ReflectionTestUtils.invokeMethod(service, "startDiagnosisRun", "same-session", "run-aiops-a", request);
|
||||
ReflectionTestUtils.invokeMethod(service, "startDiagnosisRun", "same-session", "run-aiops-b", request);
|
||||
|
||||
verify(diagnosisRunRepository).save(argThat(run ->
|
||||
"same-session".equals(run.getSessionId())
|
||||
&& "run-aiops-a".equals(run.getRunId())
|
||||
&& "AI_OPS".equals(run.getAgentFlow())
|
||||
&& "RUNNING".equals(run.getStatus())));
|
||||
verify(diagnosisRunRepository).save(argThat(run ->
|
||||
"same-session".equals(run.getSessionId())
|
||||
&& "run-aiops-b".equals(run.getRunId())
|
||||
&& "AI_OPS".equals(run.getAgentFlow())
|
||||
&& "RUNNING".equals(run.getStatus())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildSupervisorAgentSetsPlannerAsMainAgent() {
|
||||
ChatModel chatModel = mock(ChatModel.class);
|
||||
ReactAgent planner = mock(ReactAgent.class);
|
||||
ReactAgent executor = mock(ReactAgent.class);
|
||||
|
||||
SupervisorAgent supervisor = service.buildSupervisorAgent(chatModel, planner, executor);
|
||||
|
||||
assertSame(planner, supervisor.getMainAgent());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user