feat: add traceable scoped AIOps diagnosis
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
package com.superbiz.agent.service;
|
||||
|
||||
import com.superbiz.agent.domain.entity.AgentStep;
|
||||
import com.superbiz.agent.domain.entity.DiagnosisSession;
|
||||
import com.superbiz.agent.dto.AIOpsRequest;
|
||||
import com.superbiz.agent.repository.AgentStepRepository;
|
||||
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.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
class AiOpsServiceTest {
|
||||
|
||||
private final DiagnosisSessionRepository diagnosisSessionRepository = mock(DiagnosisSessionRepository.class);
|
||||
private final AgentStepRepository agentStepRepository = mock(AgentStepRepository.class);
|
||||
private final ToolInvocationRepository toolInvocationRepository = mock(ToolInvocationRepository.class);
|
||||
private final AiOpsService service = new AiOpsService();
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
ReflectionTestUtils.setField(service, "diagnosisSessionRepository", diagnosisSessionRepository);
|
||||
ReflectionTestUtils.setField(service, "agentStepRepository", agentStepRepository);
|
||||
ReflectionTestUtils.setField(service, "toolInvocationRepository", toolInvocationRepository);
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveSessionIdUsesRequestValueWhenPresent() {
|
||||
AIOpsRequest request = new AIOpsRequest();
|
||||
request.setSessionId(" aiops-demo-session ");
|
||||
|
||||
assertEquals("aiops-demo-session", service.resolveSessionId(request));
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveSessionIdGeneratesWhenMissing() {
|
||||
String sessionId = service.resolveSessionId(null);
|
||||
|
||||
assertNotNull(sessionId);
|
||||
assertFalse(sessionId.isBlank());
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildQuerySummaryUsesAlertFieldsAndUserRequestFallback() {
|
||||
AIOpsRequest request = new AIOpsRequest();
|
||||
request.setAlertName("payment-service-latency-high");
|
||||
request.setService("payment-service");
|
||||
request.setSeverity("P1");
|
||||
request.setTimeRange("last_15m");
|
||||
request.setDescription("P95 latency is high");
|
||||
request.setUserRequest("结合日志和指标排查支付超时");
|
||||
|
||||
String summary = service.buildQuerySummary(request);
|
||||
|
||||
assertTrue(summary.contains("AI Ops 告警分析"));
|
||||
assertTrue(summary.contains("告警: payment-service-latency-high"));
|
||||
assertTrue(summary.contains("服务: payment-service"));
|
||||
assertTrue(summary.contains("等级: P1"));
|
||||
assertTrue(summary.contains("时间范围: last_15m"));
|
||||
assertTrue(summary.contains("描述: P95 latency is high"));
|
||||
assertTrue(summary.contains("请求: 结合日志和指标排查支付超时"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasAlertPayloadIgnoresUserRequestOnly() {
|
||||
AIOpsRequest request = new AIOpsRequest();
|
||||
request.setUserRequest("please discover active alerts");
|
||||
|
||||
assertFalse(service.hasAlertPayload(request));
|
||||
|
||||
request.setAlertName("HighCPUUsage");
|
||||
|
||||
assertTrue(service.hasAlertPayload(request));
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildTaskPromptUsesPayloadTargetedModeWhenAlertFieldsExist() {
|
||||
AIOpsRequest request = new AIOpsRequest();
|
||||
request.setAlertName("HighCPUUsage");
|
||||
request.setService("payment-service");
|
||||
request.setSeverity("P1");
|
||||
request.setTimeRange("last_15m");
|
||||
request.setDescription("CPU usage is above 80%");
|
||||
|
||||
String prompt = service.buildTaskPrompt(request);
|
||||
|
||||
assertTrue(prompt.contains("AIOps scope mode: PAYLOAD_TARGETED"));
|
||||
assertTrue(prompt.contains("primary and only main diagnosis target"));
|
||||
assertTrue(prompt.contains("queryPrometheusAlerts only to verify"));
|
||||
assertTrue(prompt.contains("do not create full root-cause or remediation sections"));
|
||||
assertTrue(prompt.contains("Related Risk"));
|
||||
assertTrue(prompt.contains("告警: HighCPUUsage"));
|
||||
assertTrue(prompt.contains("服务: payment-service"));
|
||||
assertFalse(prompt.contains("AIOps scope mode: AUTO_DISCOVERY"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildTaskPromptUsesAutoDiscoveryModeWhenAlertPayloadIsMissing() {
|
||||
String nullRequestPrompt = service.buildTaskPrompt(null);
|
||||
|
||||
assertTrue(nullRequestPrompt.contains("AIOps scope mode: AUTO_DISCOVERY"));
|
||||
assertTrue(nullRequestPrompt.contains("First call queryPrometheusAlerts"));
|
||||
assertTrue(nullRequestPrompt.contains("current active/firing alerts"));
|
||||
assertFalse(nullRequestPrompt.contains("AIOps scope mode: PAYLOAD_TARGETED"));
|
||||
|
||||
AIOpsRequest userRequestOnly = new AIOpsRequest();
|
||||
userRequestOnly.setUserRequest("check what is firing now");
|
||||
|
||||
String userRequestOnlyPrompt = service.buildTaskPrompt(userRequestOnly);
|
||||
|
||||
assertTrue(userRequestOnlyPrompt.contains("AIOps scope mode: AUTO_DISCOVERY"));
|
||||
assertTrue(userRequestOnlyPrompt.contains("First call queryPrometheusAlerts"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void persistFinalReportUpdatesDiagnosisSessionAnswer() {
|
||||
DiagnosisSession session = DiagnosisSession.builder()
|
||||
.sessionId("aiops-session-001")
|
||||
.query("AI Ops 告警分析")
|
||||
.status("SUCCESS")
|
||||
.agentFlow("AI_OPS")
|
||||
.build();
|
||||
when(diagnosisSessionRepository.findBySessionId("aiops-session-001")).thenReturn(Optional.of(session));
|
||||
|
||||
service.persistFinalReport("aiops-session-001", "# 告警分析报告");
|
||||
|
||||
assertEquals("# 告警分析报告", session.getAnswer());
|
||||
verify(diagnosisSessionRepository).save(session);
|
||||
}
|
||||
|
||||
@Test
|
||||
void persistFinalReportSkipsBlankInput() {
|
||||
service.persistFinalReport("aiops-session-001", " ");
|
||||
|
||||
verifyNoInteractions(diagnosisSessionRepository);
|
||||
}
|
||||
|
||||
@Test
|
||||
void backfillSessionMetricsUsesRealToolInvocationCount() {
|
||||
DiagnosisSession session = DiagnosisSession.builder()
|
||||
.sessionId("aiops-session-002")
|
||||
.build();
|
||||
AgentStep stepWithTool = AgentStep.builder()
|
||||
.sessionId("aiops-session-002")
|
||||
.hasToolCall(true)
|
||||
.tokenCount(10)
|
||||
.build();
|
||||
AgentStep stepWithoutTool = AgentStep.builder()
|
||||
.sessionId("aiops-session-002")
|
||||
.hasToolCall(false)
|
||||
.tokenCount(20)
|
||||
.build();
|
||||
when(agentStepRepository.findBySessionIdOrderByStepIndex("aiops-session-002"))
|
||||
.thenReturn(List.of(stepWithTool, stepWithoutTool));
|
||||
when(toolInvocationRepository.countBySessionId("aiops-session-002")).thenReturn(11L);
|
||||
|
||||
ReflectionTestUtils.invokeMethod(service, "backfillSessionMetrics", session);
|
||||
|
||||
assertEquals(2, session.getStepCount());
|
||||
assertEquals(30, session.getTotalTokenCount());
|
||||
assertEquals(11, session.getToolCallCount());
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import com.superbiz.agent.domain.entity.AgentStep;
|
||||
import com.superbiz.agent.domain.entity.DiagnosisSession;
|
||||
import com.superbiz.agent.repository.AgentStepRepository;
|
||||
import com.superbiz.agent.repository.DiagnosisSessionRepository;
|
||||
import com.superbiz.agent.repository.ToolInvocationRepository;
|
||||
import com.superbiz.agent.tool.LookupKnowledgeTool;
|
||||
import com.superbiz.agent.tool.RetrievedDocTracker;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -143,6 +144,8 @@ class ChatServiceSequentialAgentTest {
|
||||
});
|
||||
when(agentStepRepository.findById(any())).thenReturn(Optional.of(new AgentStep()));
|
||||
when(agentStepRepository.findBySessionIdOrderByStepIndex(anyString())).thenReturn(List.of());
|
||||
ToolInvocationRepository toolInvocationRepository = mock(ToolInvocationRepository.class);
|
||||
when(toolInvocationRepository.countBySessionId(anyString())).thenReturn(0L);
|
||||
|
||||
EvaluationService evaluationService = mock(EvaluationService.class);
|
||||
RetrievedDocTracker retrievedDocTracker = mock(RetrievedDocTracker.class);
|
||||
@@ -158,6 +161,7 @@ class ChatServiceSequentialAgentTest {
|
||||
ReflectionTestUtils.setField(chatService, "queryLogsTools", new QueryLogsTools(mock(ToolInvocationRecorder.class)));
|
||||
ReflectionTestUtils.setField(chatService, "diagnosisSessionRepository", diagnosisSessionRepository);
|
||||
ReflectionTestUtils.setField(chatService, "agentStepRepository", agentStepRepository);
|
||||
ReflectionTestUtils.setField(chatService, "toolInvocationRepository", toolInvocationRepository);
|
||||
ReflectionTestUtils.setField(chatService, "evaluationService", evaluationService);
|
||||
ReflectionTestUtils.setField(chatService, "retrievedDocTracker", retrievedDocTracker);
|
||||
ReflectionTestUtils.setField(chatService, "knowledgeDomainService", knowledgeDomainService);
|
||||
|
||||
Reference in New Issue
Block a user