feat: add aiops lightweight verifier
This commit is contained in:
@@ -248,7 +248,7 @@ public class ChatController {
|
||||
if (finalReportOptional.isPresent()) {
|
||||
String finalReportText = finalReportOptional.get();
|
||||
logger.info("提取到 Planner 最终报告,长度: {}", finalReportText.length());
|
||||
aiOpsService.persistFinalReport(sessionId, finalReportText);
|
||||
aiOpsService.persistFinalReport(sessionId, finalReportText, request);
|
||||
|
||||
// 发送分隔线
|
||||
emitter.send(SseEmitter.event().name("message")
|
||||
|
||||
@@ -97,6 +97,7 @@ public class DiagnosisTraceResponse {
|
||||
private int persistedToolCallCount;
|
||||
private int returnedToolCallCount;
|
||||
private boolean hasVerifierEvaluation;
|
||||
private boolean hasAiOpsRuleEvaluation;
|
||||
private boolean hasFeedback;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
package com.superbiz.agent.service;
|
||||
|
||||
import com.superbiz.agent.domain.entity.ToolInvocation;
|
||||
import com.superbiz.agent.dto.AIOpsRequest;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class AiOpsRuleEvaluationService {
|
||||
|
||||
public static final String PASS = "PASS";
|
||||
public static final String WARN = "WARN";
|
||||
public static final String FAIL = "FAIL";
|
||||
|
||||
public Map<String, Object> evaluate(AIOpsRequest request,
|
||||
String finalReport,
|
||||
List<ToolInvocation> toolInvocations) {
|
||||
List<Map<String, Object>> checks = new ArrayList<>();
|
||||
checks.add(checkReportPresent(finalReport));
|
||||
checks.add(checkPayloadFocus(request, finalReport));
|
||||
checks.add(checkEvidenceCoverage(toolInvocations));
|
||||
|
||||
String verdict = aggregateVerdict(checks);
|
||||
Map<String, Object> evaluation = new LinkedHashMap<>();
|
||||
evaluation.put("verdict", verdict);
|
||||
evaluation.put("checks", checks);
|
||||
evaluation.put("rationale", buildRationale(verdict, checks));
|
||||
evaluation.put("traceability_version", "aiops-rule-v1");
|
||||
return evaluation;
|
||||
}
|
||||
|
||||
private Map<String, Object> checkReportPresent(String finalReport) {
|
||||
boolean passed = finalReport != null && finalReport.trim().length() >= 40;
|
||||
return check(
|
||||
"final_report_present",
|
||||
passed ? PASS : FAIL,
|
||||
passed ? "Final report is present." : "Final report is missing or too short."
|
||||
);
|
||||
}
|
||||
|
||||
private Map<String, Object> checkPayloadFocus(AIOpsRequest request, String finalReport) {
|
||||
if (!hasAlertPayload(request)) {
|
||||
return check("payload_focus", PASS, "No alert payload was supplied; payload focus is not required.");
|
||||
}
|
||||
|
||||
String report = lower(finalReport);
|
||||
List<String> missing = new ArrayList<>();
|
||||
if (!contains(report, request.getAlertName())) {
|
||||
missing.add("alertName");
|
||||
}
|
||||
if (!contains(report, request.getService())) {
|
||||
missing.add("service");
|
||||
}
|
||||
|
||||
if (missing.isEmpty()) {
|
||||
return check("payload_focus", PASS, "Final report mentions the supplied alert and service.");
|
||||
}
|
||||
return check(
|
||||
"payload_focus",
|
||||
WARN,
|
||||
"Final report is missing payload focus terms: " + String.join(", ", missing)
|
||||
);
|
||||
}
|
||||
|
||||
private Map<String, Object> checkEvidenceCoverage(List<ToolInvocation> toolInvocations) {
|
||||
List<String> evidenceTools = safeTools(toolInvocations).stream()
|
||||
.filter(tool -> tool.equals("lookup_knowledge")
|
||||
|| tool.equals("query_metrics")
|
||||
|| tool.equals("query_logs"))
|
||||
.distinct()
|
||||
.toList();
|
||||
|
||||
if (evidenceTools.isEmpty()) {
|
||||
return check("evidence_tool_coverage", WARN, "No persisted AIOps evidence tool calls were found.");
|
||||
}
|
||||
return check(
|
||||
"evidence_tool_coverage",
|
||||
PASS,
|
||||
"Persisted evidence tools: " + String.join(", ", evidenceTools)
|
||||
);
|
||||
}
|
||||
|
||||
private List<String> safeTools(List<ToolInvocation> toolInvocations) {
|
||||
if (toolInvocations == null) {
|
||||
return List.of();
|
||||
}
|
||||
return toolInvocations.stream()
|
||||
.map(ToolInvocation::getToolName)
|
||||
.filter(name -> name != null && !name.isBlank())
|
||||
.map(name -> name.trim().toLowerCase(Locale.ROOT))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private String aggregateVerdict(List<Map<String, Object>> checks) {
|
||||
boolean hasFail = checks.stream().anyMatch(check -> FAIL.equals(check.get("verdict")));
|
||||
if (hasFail) {
|
||||
return FAIL;
|
||||
}
|
||||
boolean hasWarn = checks.stream().anyMatch(check -> WARN.equals(check.get("verdict")));
|
||||
return hasWarn ? WARN : PASS;
|
||||
}
|
||||
|
||||
private String buildRationale(String verdict, List<Map<String, Object>> checks) {
|
||||
long passCount = checks.stream().filter(check -> PASS.equals(check.get("verdict"))).count();
|
||||
long warnCount = checks.stream().filter(check -> WARN.equals(check.get("verdict"))).count();
|
||||
long failCount = checks.stream().filter(check -> FAIL.equals(check.get("verdict"))).count();
|
||||
return "AIOps rule evaluation %s: pass=%d, warn=%d, fail=%d"
|
||||
.formatted(verdict, passCount, warnCount, failCount);
|
||||
}
|
||||
|
||||
private Map<String, Object> check(String name, String verdict, String detail) {
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("name", name);
|
||||
result.put("verdict", verdict);
|
||||
result.put("detail", detail);
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean hasAlertPayload(AIOpsRequest request) {
|
||||
if (request == null) {
|
||||
return false;
|
||||
}
|
||||
return !isBlank(request.getAlertName())
|
||||
|| !isBlank(request.getService())
|
||||
|| !isBlank(request.getSeverity())
|
||||
|| !isBlank(request.getDescription())
|
||||
|| !isBlank(request.getTimeRange());
|
||||
}
|
||||
|
||||
private boolean contains(String lowerText, String value) {
|
||||
return isBlank(value) || lowerText.contains(value.trim().toLowerCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
private String lower(String value) {
|
||||
return value == null ? "" : value.toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
private boolean isBlank(String value) {
|
||||
return value == null || value.trim().isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import com.superbiz.agent.config.AiOpsPromptProperties;
|
||||
import com.superbiz.agent.tool.LookupKnowledgeTool;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -66,6 +67,12 @@ public class AiOpsService {
|
||||
@Autowired
|
||||
private ToolInvocationRepository toolInvocationRepository;
|
||||
|
||||
@Autowired
|
||||
private AiOpsRuleEvaluationService aiOpsRuleEvaluationService;
|
||||
|
||||
@Autowired
|
||||
private SelfEvaluationMergeService selfEvaluationMergeService;
|
||||
|
||||
/**
|
||||
* 执行 AI Ops 告警分析流程
|
||||
*
|
||||
@@ -170,11 +177,20 @@ public class AiOpsService {
|
||||
}
|
||||
|
||||
public void persistFinalReport(String sessionId, String finalReport) {
|
||||
persistFinalReport(sessionId, finalReport, null);
|
||||
}
|
||||
|
||||
public void persistFinalReport(String sessionId, String finalReport, AIOpsRequest request) {
|
||||
if (isBlank(sessionId) || isBlank(finalReport)) {
|
||||
return;
|
||||
}
|
||||
diagnosisSessionRepository.findBySessionId(sessionId.trim()).ifPresent(session -> {
|
||||
session.setAnswer(finalReport);
|
||||
List<com.superbiz.agent.domain.entity.ToolInvocation> invocations =
|
||||
toolInvocationRepository.findBySessionIdOrderByIdAsc(session.getSessionId());
|
||||
Map<String, Object> evaluation = aiOpsRuleEvaluationService.evaluate(request, finalReport, invocations);
|
||||
session.setSelfEvaluation(selfEvaluationMergeService.mergeAiOpsRuleEvaluation(
|
||||
session.getSelfEvaluation(), evaluation));
|
||||
diagnosisSessionRepository.save(session);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -115,6 +115,7 @@ public class DiagnosisTraceService {
|
||||
.persistedToolCallCount(defaultInt(session.getToolCallCount()))
|
||||
.returnedToolCallCount(toolInvocations.size())
|
||||
.hasVerifierEvaluation(selfEvaluation != null && selfEvaluation.containsKey("verifier_evaluation"))
|
||||
.hasAiOpsRuleEvaluation(selfEvaluation != null && selfEvaluation.containsKey("aiops_rule_evaluation"))
|
||||
.hasFeedback(session.getFeedback() != null && !session.getFeedback().isBlank())
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -27,6 +27,10 @@ public class SelfEvaluationMergeService {
|
||||
return merge(existingJson, "verifier_evaluation", verifierEvaluation);
|
||||
}
|
||||
|
||||
public String mergeAiOpsRuleEvaluation(String existingJson, Map<String, Object> aiOpsRuleEvaluation) {
|
||||
return merge(existingJson, "aiops_rule_evaluation", aiOpsRuleEvaluation);
|
||||
}
|
||||
|
||||
private String merge(String existingJson, String key, Map<String, Object> value) {
|
||||
try {
|
||||
Map<String, Object> root = parseRoot(existingJson);
|
||||
@@ -44,7 +48,9 @@ public class SelfEvaluationMergeService {
|
||||
}
|
||||
|
||||
Map<String, Object> parsed = objectMapper.readValue(existingJson, MAP_TYPE);
|
||||
if (parsed.containsKey("rule_evaluation") || parsed.containsKey("verifier_evaluation")) {
|
||||
if (parsed.containsKey("rule_evaluation")
|
||||
|| parsed.containsKey("verifier_evaluation")
|
||||
|| parsed.containsKey("aiops_rule_evaluation")) {
|
||||
return new LinkedHashMap<>(parsed);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user