package com.superbiz.agent.service; import org.springframework.ai.chat.model.ChatModel; import com.alibaba.cloud.ai.graph.OverAllState; import com.alibaba.cloud.ai.graph.agent.ReactAgent; import com.alibaba.cloud.ai.graph.agent.flow.agent.SupervisorAgent; import com.alibaba.cloud.ai.graph.exception.GraphRunnerException; import com.superbiz.agent.agent.tool.DateTimeTools; import com.superbiz.agent.agent.tool.InternalDocsTools; import com.superbiz.agent.agent.tool.QueryLogsTools; import com.superbiz.agent.agent.tool.QueryMetricsTools; import com.superbiz.agent.domain.entity.AgentStep; import com.superbiz.agent.domain.entity.DiagnosisSession; import com.superbiz.agent.dto.AIOpsRequest; import com.superbiz.agent.hook.AgentLoggingHook; import com.superbiz.agent.repository.AgentStepRepository; import com.superbiz.agent.repository.DiagnosisSessionRepository; import com.superbiz.agent.repository.ToolInvocationRepository; import com.superbiz.agent.util.SessionContextHolder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.tool.ToolCallback; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.superbiz.agent.config.AiOpsPromptProperties; import com.superbiz.agent.tool.LookupKnowledgeTool; import java.util.List; import java.util.Optional; import java.util.UUID; /** * AI Ops 智能运维服务 * 负责多 Agent 协作的告警分析流程 */ @Service public class AiOpsService { private static final Logger logger = LoggerFactory.getLogger(AiOpsService.class); @Autowired private DateTimeTools dateTimeTools; @Autowired private InternalDocsTools internalDocsTools; @Autowired private QueryMetricsTools queryMetricsTools; @Autowired(required = false) // Mock 模式下才注册 private QueryLogsTools queryLogsTools; @Autowired private LookupKnowledgeTool lookupKnowledgeTool; @Autowired private AiOpsPromptProperties promptProperties; @Autowired private DiagnosisSessionRepository diagnosisSessionRepository; @Autowired private AgentStepRepository agentStepRepository; @Autowired private ToolInvocationRepository toolInvocationRepository; /** * 执行 AI Ops 告警分析流程 * * @param chatModel 大模型实例 * @param toolCallbacks 工具回调数组 * @return 分析结果状态 * @throws GraphRunnerException 如果 Agent 执行失败 */ public Optional executeAiOpsAnalysis(ChatModel chatModel, ToolCallback[] toolCallbacks) throws GraphRunnerException { return executeAiOpsAnalysis(chatModel, toolCallbacks, null, resolveSessionId(null)); } public Optional executeAiOpsAnalysis(ChatModel chatModel, ToolCallback[] toolCallbacks, AIOpsRequest request, String sessionId) throws GraphRunnerException { logger.info("开始执行 AI Ops 多 Agent 协作流程"); String resolvedSessionId = isBlank(sessionId) ? resolveSessionId(request) : sessionId.trim(); long startTime = System.currentTimeMillis(); // 创建或更新诊断会话 DiagnosisSession session = startDiagnosisSession(resolvedSessionId, request); diagnosisSessionRepository.save(session); // 设置 ThreadLocal 上下文(LookupKnowledgeTool 通过此获取 sessionId) SessionContextHolder.setSessionId(resolvedSessionId); try { // 构建 Planner 和 Executor Agent(每个 Agent 各自带 Hook) ReactAgent plannerAgent = buildPlannerAgent(chatModel, toolCallbacks); ReactAgent executorAgent = buildExecutorAgent(chatModel, toolCallbacks); // 构建 Supervisor Agent(不加 Hook) SupervisorAgent supervisorAgent = SupervisorAgent.builder() .name("ai_ops_supervisor") .description("负责调度 Planner 与 Executor 的多 Agent 控制器") .model(chatModel) .systemPrompt(promptProperties.getSupervisor()) .subAgents(List.of(plannerAgent, executorAgent)) .build(); String taskPrompt = buildTaskPrompt(request); logger.info("调用 Supervisor Agent 开始编排..."); Optional stateOptional = supervisorAgent.invoke(taskPrompt); long duration = System.currentTimeMillis() - startTime; // 更新诊断会话 session.setStatus(stateOptional.isPresent() ? "SUCCESS" : "FAILED"); session.setTotalDurationMs((int) duration); backfillSessionMetrics(session); diagnosisSessionRepository.save(session); // 添加调试代码 if (stateOptional.isPresent()) { OverAllState state = stateOptional.get(); logger.debug("Final State Keys: {}", state.data().keySet()); logger.debug("Planner Plan: {}", state.value("planner_plan")); logger.debug("Executor Feedback: {}", state.value("executor_feedback")); } return stateOptional; } catch (Exception e) { session.setStatus("FAILED"); diagnosisSessionRepository.save(session); throw e; } finally { SessionContextHolder.clear(); } } /** * 从执行结果中提取最终报告文本 * * @param state 执行状态 * @return 报告文本(如果存在) */ public Optional extractFinalReport(OverAllState state) { logger.info("开始提取最终报告..."); // 提取 Planner 最终输出(包含完整的告警分析报告) Optional plannerFinalOutput = state.value("planner_plan") .filter(AssistantMessage.class::isInstance) .map(AssistantMessage.class::cast); if (plannerFinalOutput.isPresent()) { String reportText = plannerFinalOutput.get().getText(); logger.info("成功提取到 Planner 最终报告,长度: {}", reportText.length()); return Optional.of(reportText); } else { logger.warn("未能提取到 Planner 最终报告"); return Optional.empty(); } } public String resolveSessionId(AIOpsRequest request) { if (request != null && !isBlank(request.getSessionId())) { return request.getSessionId().trim(); } return UUID.randomUUID().toString(); } public void persistFinalReport(String sessionId, String finalReport) { if (isBlank(sessionId) || isBlank(finalReport)) { return; } diagnosisSessionRepository.findBySessionId(sessionId.trim()).ifPresent(session -> { session.setAnswer(finalReport); diagnosisSessionRepository.save(session); }); } String buildQuerySummary(AIOpsRequest request) { if (request == null) { return "AI Ops 告警分析"; } StringBuilder summary = new StringBuilder("AI Ops 告警分析"); appendField(summary, "告警", request.getAlertName()); appendField(summary, "服务", request.getService()); appendField(summary, "等级", request.getSeverity()); appendField(summary, "时间范围", request.getTimeRange()); appendField(summary, "描述", request.getDescription()); appendField(summary, "请求", request.getUserRequest()); return summary.toString(); } 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()); } String buildTaskPrompt(AIOpsRequest request) { StringBuilder prompt = new StringBuilder(); prompt.append("你是企业级 SRE,接到了自动化告警排查任务。请结合工具调用,执行**规划→执行→再规划**的闭环,并最终按照固定模板输出《告警分析报告》。禁止编造虚假数据,如连续多次查询失败需诚实反馈无法完成的原因。"); prompt.append("\n\n本次告警输入:\n"); prompt.append(buildQuerySummary(request)); if (hasAlertPayload(request)) { prompt.append("\n\nAIOps scope mode: PAYLOAD_TARGETED\n"); prompt.append("- The request includes an alert payload. Treat the supplied alert payload as the primary and only main diagnosis target.\n"); prompt.append("- The final report must focus on the supplied alert fields such as alertName, service, severity, description, and timeRange.\n"); prompt.append("- You may call queryPrometheusAlerts only to verify whether the supplied alert is still active or to identify related risk/context.\n"); prompt.append("- If queryPrometheusAlerts returns unrelated active alerts, do not create full root-cause or remediation sections for them.\n"); prompt.append("- Mention unrelated active alerts only briefly in a Related Risk section when they help explain the supplied alert.\n"); } else { prompt.append("\n\nAIOps scope mode: AUTO_DISCOVERY\n"); prompt.append("- The request does not include alert payload fields. First call queryPrometheusAlerts to discover current active/firing alerts.\n"); prompt.append("- Prefer P0/P1 alerts or the longest-running firing alerts, then diagnose one or more alerts based on severity and evidence.\n"); prompt.append("- Use metrics, logs, and knowledge-base evidence before producing the final alert analysis report.\n"); } return prompt.toString(); } private DiagnosisSession startDiagnosisSession(String sessionId, AIOpsRequest request) { DiagnosisSession session = diagnosisSessionRepository.findBySessionId(sessionId) .orElseGet(() -> DiagnosisSession.builder() .sessionId(sessionId) .agentFlow("AI_OPS") .build()); session.setQuery(buildQuerySummary(request)); session.setStatus("RUNNING"); session.setAgentFlow("AI_OPS"); session.setAnswer(null); session.setTotalDurationMs(null); session.setTotalTokenCount(null); session.setStepCount(null); session.setToolCallCount(null); return session; } /** * 构建 Planner Agent */ private ReactAgent buildPlannerAgent(ChatModel chatModel, ToolCallback[] toolCallbacks) { return ReactAgent.builder() .name("planner_agent") .description("负责拆解告警、规划与再规划步骤") .model(chatModel) .systemPrompt(promptProperties.getPlanner()) .methodTools(buildMethodToolsArray()) .tools(toolCallbacks) .hooks(new AgentLoggingHook(agentStepRepository, "planner")) .outputKey("planner_plan") .build(); } /** * 构建 Executor Agent */ private ReactAgent buildExecutorAgent(ChatModel chatModel, ToolCallback[] toolCallbacks) { return ReactAgent.builder() .name("executor_agent") .description("负责执行 Planner 的首个步骤并及时反馈") .model(chatModel) .systemPrompt(promptProperties.getExecutor()) .methodTools(buildMethodToolsArray()) .tools(toolCallbacks) .hooks(new AgentLoggingHook(agentStepRepository, "executor")) .outputKey("executor_feedback") .build(); } /** * 动态构建方法工具数组 * 根据 cls.mock-enabled 决定是否包含 QueryLogsTools * 工具顺序:知识库查询优先,日志查询次之,弃用工具最后 */ private Object[] buildMethodToolsArray() { if (queryLogsTools != null) { // Mock 模式:包含 QueryLogsTools return new Object[]{dateTimeTools, lookupKnowledgeTool, queryMetricsTools, queryLogsTools}; } else { // 真实模式:不包含 QueryLogsTools(由 MCP 提供日志查询功能) return new Object[]{dateTimeTools, lookupKnowledgeTool, queryMetricsTools}; } } /** 从 agent_step 和 tool_invocation 汇总指标回填 diagnosis_session */ private void backfillSessionMetrics(DiagnosisSession session) { try { List steps = agentStepRepository.findBySessionIdOrderByStepIndex(session.getSessionId()); int totalTokens = 0; int stepCount = 0; for (AgentStep s : steps) { stepCount++; if (s.getTokenCount() != null) totalTokens += s.getTokenCount(); } long toolCallCount = toolInvocationRepository.countBySessionId(session.getSessionId()); session.setTotalTokenCount(totalTokens); session.setStepCount(stepCount); session.setToolCallCount(Math.toIntExact(toolCallCount)); } catch (Exception e) { logger.warn("回填会话指标失败: sessionId={}", session.getSessionId(), e); } } private void appendField(StringBuilder builder, String label, String value) { if (!isBlank(value)) { builder.append("\n- ").append(label).append(": ").append(value.trim()); } } private boolean isBlank(String value) { return value == null || value.trim().isEmpty(); } }