Add diagnosis playbook skills
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
package com.superbiz.agent.config;
|
||||
|
||||
import com.alibaba.cloud.ai.graph.skills.SkillMetadata;
|
||||
import com.alibaba.cloud.ai.graph.skills.registry.SkillRegistry;
|
||||
import com.alibaba.cloud.ai.graph.skills.registry.classpath.ClasspathSkillRegistry;
|
||||
import org.springframework.ai.chat.prompt.SystemPromptTemplate;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Configuration
|
||||
public class SkillConfig {
|
||||
|
||||
private static final String ACTIVE_SKILL_NAME = "diagnose-mysql-connection-pool";
|
||||
|
||||
@Bean
|
||||
public SkillRegistry skillRegistry() {
|
||||
SkillRegistry classpathRegistry = ClasspathSkillRegistry.builder()
|
||||
.classpathPath("skills")
|
||||
.basePath("target/skills-cache")
|
||||
.build();
|
||||
return new SingleSkillRegistry(classpathRegistry, ACTIVE_SKILL_NAME);
|
||||
}
|
||||
|
||||
private record SingleSkillRegistry(SkillRegistry delegate, String activeSkillName) implements SkillRegistry {
|
||||
|
||||
@Override
|
||||
public List<SkillMetadata> listAll() {
|
||||
return delegate.listAll().stream()
|
||||
.filter(skill -> activeSkillName.equals(skill.getName()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRegistryType() {
|
||||
return delegate.getRegistryType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readSkillContent(String skillName) throws IOException {
|
||||
if (!activeSkillName.equals(skillName)) {
|
||||
throw new IOException("Skill not found: " + skillName);
|
||||
}
|
||||
return delegate.readSkillContent(skillName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSkillLoadInstructions() {
|
||||
return delegate.getSkillLoadInstructions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SystemPromptTemplate getSystemPromptTemplate() {
|
||||
return delegate.getSystemPromptTemplate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<SkillMetadata> get(String skillName) {
|
||||
if (!activeSkillName.equals(skillName)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return delegate.get(skillName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return listAll().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(String skillName) {
|
||||
return activeSkillName.equals(skillName) && delegate.contains(skillName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reload() {
|
||||
delegate.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.superbiz.agent.hook;
|
||||
|
||||
import com.alibaba.cloud.ai.graph.RunnableConfig;
|
||||
import com.alibaba.cloud.ai.graph.agent.Prioritized;
|
||||
import com.alibaba.cloud.ai.graph.agent.hook.HookPosition;
|
||||
import com.alibaba.cloud.ai.graph.agent.hook.HookPositions;
|
||||
import com.alibaba.cloud.ai.graph.agent.hook.messages.AgentCommand;
|
||||
import com.alibaba.cloud.ai.graph.agent.hook.messages.MessagesModelHook;
|
||||
import com.alibaba.cloud.ai.graph.skills.SkillMetadata;
|
||||
import com.alibaba.cloud.ai.graph.skills.registry.SkillRegistry;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
import org.springframework.ai.chat.messages.SystemMessage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Injects planner-visible skill metadata without exposing the full skill loader tool.
|
||||
*/
|
||||
@Slf4j
|
||||
@HookPositions(HookPosition.BEFORE_MODEL)
|
||||
public class PlannerSkillMetadataHook extends MessagesModelHook {
|
||||
|
||||
private static final String CATALOG_MARKER = "\"skill_catalog\"";
|
||||
|
||||
private final SkillRegistry skillRegistry;
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
public PlannerSkillMetadataHook(SkillRegistry skillRegistry) {
|
||||
this.skillRegistry = skillRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "planner_skill_metadata_hook";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Prioritized.HIGHEST_PRECEDENCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AgentCommand beforeModel(List<Message> previousMessages, RunnableConfig config) {
|
||||
if (skillRegistry == null || skillRegistry.size() == 0 || hasCatalog(previousMessages)) {
|
||||
return new AgentCommand(previousMessages);
|
||||
}
|
||||
|
||||
try {
|
||||
List<Map<String, String>> skills = skillRegistry.listAll().stream()
|
||||
.map(this::toSkillSummary)
|
||||
.toList();
|
||||
if (skills.isEmpty()) {
|
||||
return new AgentCommand(previousMessages);
|
||||
}
|
||||
|
||||
Map<String, Object> catalog = new LinkedHashMap<>();
|
||||
catalog.put("purpose", "Planner-visible diagnosis skill metadata only.");
|
||||
catalog.put("rules", List.of(
|
||||
"Choose at most one primary skill.",
|
||||
"Do not load full skill instructions in Planner.",
|
||||
"Executor reads the selected skill before evidence collection.",
|
||||
"If no skill matches, set selected_skill to null."
|
||||
));
|
||||
catalog.put("skills", skills);
|
||||
catalog.put("required_planner_output", Map.of(
|
||||
"selected_skill", "skill name or null",
|
||||
"selection_reason", "short reason",
|
||||
"plan", "ordered execution step list"
|
||||
));
|
||||
|
||||
Map<String, Object> payload = Map.of("skill_catalog", catalog);
|
||||
String content = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(payload);
|
||||
|
||||
List<Message> updatedMessages = new ArrayList<>(previousMessages.size() + 1);
|
||||
updatedMessages.add(new SystemMessage(content));
|
||||
updatedMessages.addAll(previousMessages);
|
||||
return new AgentCommand(updatedMessages);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to inject planner skill metadata, fallback to original messages", e);
|
||||
return new AgentCommand(previousMessages);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> toSkillSummary(SkillMetadata skill) {
|
||||
Map<String, String> summary = new LinkedHashMap<>();
|
||||
summary.put("name", skill.getName());
|
||||
summary.put("description", skill.getDescription());
|
||||
return summary;
|
||||
}
|
||||
|
||||
private boolean hasCatalog(List<Message> messages) {
|
||||
return messages.stream()
|
||||
.map(Message::getText)
|
||||
.anyMatch(text -> text != null && text.contains(CATALOG_MARKER));
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,10 @@ 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.agent.hook.Hook;
|
||||
import com.alibaba.cloud.ai.graph.agent.hook.skills.SkillsAgentHook;
|
||||
import com.alibaba.cloud.ai.graph.exception.GraphRunnerException;
|
||||
import com.alibaba.cloud.ai.graph.skills.registry.SkillRegistry;
|
||||
import com.superbiz.agent.agent.tool.DateTimeTools;
|
||||
import com.superbiz.agent.agent.tool.InternalDocsTools;
|
||||
import com.superbiz.agent.agent.tool.QueryLogsTools;
|
||||
@@ -13,6 +16,7 @@ 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.hook.PlannerSkillMetadataHook;
|
||||
import com.superbiz.agent.repository.AgentStepRepository;
|
||||
import com.superbiz.agent.repository.DiagnosisSessionRepository;
|
||||
import com.superbiz.agent.repository.ToolInvocationRepository;
|
||||
@@ -32,8 +36,8 @@ import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* AI Ops 智能运维服务
|
||||
* 负责多 Agent 协作的告警分析流程
|
||||
* AI Ops 闂備礁鎼幊妯肩磽濮樿泛绀傛俊顖滅帛娴溿倝鏌熼柇锕€鏋熸俊顖氾躬閺岋繝宕煎┑鍩裤垹鈹?
|
||||
* 闂佽崵濮甸崝妤呭窗閺囥垺鍎楁俊銈勭缁?Agent 闂備礁鎲¢〃鍛崲鐎n剛绀婇柡鍐ㄧ墛閸庡秹鏌涢弴銊ヤ簼闁哥喓鍋ら幃褰掑焵椤掑嫭鏅濋柍褜鍓熷畷瑙勬償閵娿儱鍤戦梺褰掑亰閸橀箖濡堕敂鍓х<?
|
||||
*/
|
||||
@Service
|
||||
public class AiOpsService {
|
||||
@@ -49,7 +53,7 @@ public class AiOpsService {
|
||||
@Autowired
|
||||
private QueryMetricsTools queryMetricsTools;
|
||||
|
||||
@Autowired(required = false) // Mock 模式下才注册
|
||||
@Autowired(required = false) // Mock 婵犵妲呴崹顏堝焵椤掆偓绾绢厾娑甸埀顒佺箾閹寸偞灏い鎴濇閺呭爼鎮╁ù瀣亙闂侀潧顭堥崕閬嶅棘閳?
|
||||
private QueryLogsTools queryLogsTools;
|
||||
|
||||
@Autowired
|
||||
@@ -73,13 +77,16 @@ public class AiOpsService {
|
||||
@Autowired
|
||||
private SelfEvaluationMergeService selfEvaluationMergeService;
|
||||
|
||||
@Autowired(required = false)
|
||||
private SkillRegistry skillRegistry;
|
||||
|
||||
/**
|
||||
* 执行 AI Ops 告警分析流程
|
||||
* 闂備礁婀遍悷鎶藉幢閳哄倹鏉?AI Ops 闂備礁鎲$粙鎴︽晝閵娾晩鏁嗛柣鏃傚帶缁€鍡涙煕閳╁喚鐒介柍褜鍓濆Λ鍕箒婵炶揪缍€閵嗏偓闁?
|
||||
*
|
||||
* @param chatModel 大模型实例
|
||||
* @param toolCallbacks 工具回调数组
|
||||
* @return 分析结果状态
|
||||
* @throws GraphRunnerException 如果 Agent 执行失败
|
||||
* @param chatModel 濠电姰鍨归悥銏ゅ礋閳ь剚绗熼埀顒€鐣烽崷顓涘亾閿濆簼绨介柡澶庢閵?
|
||||
* @param toolCallbacks 闁诲氦顫夐幃鍫曞磿闁秴鐭楅柛褎顨呴悙濠囨煟閹邦剙顣虫繛鍫濈埣閺屸剝鎷呴悷閭︽缂?
|
||||
* @return 闂備礁鎲$敮鎺懳涘▎鎾村€甸柣锝呯灱绾惧ジ鏌熼幆褜鍤熷ù婊庡灦閺岋絽螣閸喚鍘梺?
|
||||
* @throws GraphRunnerException 濠电姷顣介埀顒€鍟块埀顒€缍婇幃?Agent 闂備礁婀遍悷鎶藉幢閳哄倹鏉稿┑鐘灪閸庤偐鍒掗崜褎鍠?
|
||||
*/
|
||||
public Optional<OverAllState> executeAiOpsAnalysis(ChatModel chatModel, ToolCallback[] toolCallbacks) throws GraphRunnerException {
|
||||
return executeAiOpsAnalysis(chatModel, toolCallbacks, null, resolveSessionId(null));
|
||||
@@ -87,27 +94,27 @@ public class AiOpsService {
|
||||
|
||||
public Optional<OverAllState> executeAiOpsAnalysis(ChatModel chatModel, ToolCallback[] toolCallbacks,
|
||||
AIOpsRequest request, String sessionId) throws GraphRunnerException {
|
||||
logger.info("开始执行 AI Ops 多 Agent 协作流程");
|
||||
logger.info("Starting AI Ops multi-agent analysis");
|
||||
|
||||
String resolvedSessionId = isBlank(sessionId) ? resolveSessionId(request) : sessionId.trim();
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
// 创建或更新诊断会话
|
||||
// 闂備礁鎲$敮妤冪矙閹寸姷纾介柟鎹愵嚙缁狅綁鏌″鍐ㄥ缂佺虎鍨堕弻锟犲磼濞戞﹩鈧粓鏌i敂鐣屽⒌鐎殿噮鍓熼、妯衡攽閸垻宕堕梺?
|
||||
DiagnosisSession session = startDiagnosisSession(resolvedSessionId, request);
|
||||
diagnosisSessionRepository.save(session);
|
||||
|
||||
// 设置 ThreadLocal 上下文(LookupKnowledgeTool 通过此获取 sessionId)
|
||||
// 闂佽崵濮崇粈浣规櫠娴犲鍋?ThreadLocal 濠电偞鍨堕幐鎼佹晝閿濆洨绠旈柛娑欐綑濡﹢鏌涢妷鈺婃缂佲偓閸戯箰okupKnowledgeTool 闂傚倷绶¢崑鍛┍閾忚宕查柛鎰电厛濞间即鏌曢崼婵堝缂佺媭鍨堕弻?sessionId闂?
|
||||
SessionContextHolder.setSessionId(resolvedSessionId);
|
||||
|
||||
try {
|
||||
// 构建 Planner 和 Executor Agent(每个 Agent 各自带 Hook)
|
||||
// 闂備礁鎼鍛偓姘煎墰缁?Planner 闂?Executor Agent闂備焦瀵х粙鎴︽偋婵犲洦鍎婇柍鈺佸暟閳?Agent 闂備礁鎲¢懝鍓р偓姘煎灦瀹曢潧顭ㄩ崨顔芥?Hook闂?
|
||||
ReactAgent plannerAgent = buildPlannerAgent(chatModel, toolCallbacks);
|
||||
ReactAgent executorAgent = buildExecutorAgent(chatModel, toolCallbacks);
|
||||
|
||||
// 构建 Supervisor Agent(不加 Hook)
|
||||
// 闂備礁鎼鍛偓姘煎墰缁?Supervisor Agent闂備焦瀵х粙鎴︽偋閸涱垳绠斿鑸靛姇缁€?Hook闂?
|
||||
SupervisorAgent supervisorAgent = SupervisorAgent.builder()
|
||||
.name("ai_ops_supervisor")
|
||||
.description("负责调度 Planner 与 Executor 的多 Agent 控制器")
|
||||
.description("Coordinates Planner and Executor agents")
|
||||
.model(chatModel)
|
||||
.systemPrompt(promptProperties.getSupervisor())
|
||||
.subAgents(List.of(plannerAgent, executorAgent))
|
||||
@@ -115,19 +122,19 @@ public class AiOpsService {
|
||||
|
||||
String taskPrompt = buildTaskPrompt(request);
|
||||
|
||||
logger.info("调用 Supervisor Agent 开始编排...");
|
||||
logger.info("闂佽崵濮撮鍛村疮娴兼潙鏋?Supervisor Agent 闁诲孩顔栭崰鎺楀磻閹炬枼鏀芥い鏃傗拡閸庢垹绱掓鏍﹂偗妤?..");
|
||||
|
||||
Optional<OverAllState> 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());
|
||||
@@ -146,25 +153,25 @@ public class AiOpsService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 从执行结果中提取最终报告文本
|
||||
* 濠电偛顕慨瀵糕偓娑掓櫆閺呭爼鎮剧仦鎯т粧閻庡厜鍋撻柍褜鍓涢崚鎺楀Ω閳轰礁鍤戝┑鐘才堥崑鎾绘煠閸偄鐏存鐐存崌楠炲洭顢楅埀顒傚緤閸ф鐓涢柛顐h壘娴滃墽绱撻崒娆戭槮闁绘锕ラ幈銊╁Χ婢跺﹤绐涙繝鐢靛Т閸燁垶鎮楅鈧弻?
|
||||
*
|
||||
* @param state 执行状态
|
||||
* @return 报告文本(如果存在)
|
||||
* @param state 闂備礁婀遍悷鎶藉幢閳哄倹鏉搁梻浣虹帛椤牓宕洪弽顓炵劦?
|
||||
* @return 闂備胶顢婄紙浼村磿闁秴绠熼柨鐔哄Т濡﹢鏌涢妷锝呭闁圭兘浜堕弻銊モ槈濡厧顣哄銈傛暘閸パ冨殤濠电姴锕ら崯浼村箺閻樼粯鐓曢柨鏃囧吹閸樻粎绱?
|
||||
*/
|
||||
public Optional<String> extractFinalReport(OverAllState state) {
|
||||
logger.info("开始提取最终报告...");
|
||||
logger.info("闁诲孩顔栭崰鎺楀磻閹炬枼鏀芥い鏃傗拡閸庢劗鎲告0浣虹獢鐎规洩缍佸浠嬪Ω閿旇法甯涚紓鍌氬€风粈渚€鎮ф繝鍐╁弿闁靛牆顦?..");
|
||||
|
||||
// 提取 Planner 最终输出(包含完整的告警分析报告)
|
||||
// 闂備礁婀辩划顖炲礉閺嚶颁汗?Planner 闂備礁鎼悧鍐磻閹惧墎纾藉ù锝呮憸婢э絿绱掓0婵嗗籍鐎规洘鐟╅幃顔锯偓闈涙憸椤︹晠姊洪崨濠勫ⅹ闁瑰啿閰i獮鍡涘醇閳垛晛浜鹃柣鐔哄濠€浼存煛閸☆厾绉柟顖氬暣瀹曠喖顢曢敐鍛畼闂佽崵濮崑鎾绘煥閺囨浜鹃梺鎼炲妼闁帮絽顕i幖浣哥疀妞ゆ挾鍊幘缁樼厱婵炴垶锕╅悡顓犵磼?
|
||||
Optional<AssistantMessage> 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());
|
||||
logger.info("闂備胶鎳撻悺銊╁礉閺囩喐鍙忔繛鎴欏灩缁犵敻鏌熼柇锕€澧紒鎻掓健閺?Planner 闂備礁鎼悧鍐磻閹惧墎纾藉ù锝呮憸婢ф稑鈹戦鍝勨偓婵嗙暦閵婏妇绡€闊洦娲滈ˇ顕€姊婚崒妤€浜鹃梺鍓茬厛閸犳牠顢? {}", reportText.length());
|
||||
return Optional.of(reportText);
|
||||
} else {
|
||||
logger.warn("未能提取到 Planner 最终报告");
|
||||
logger.warn("Unable to extract Planner final report");
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
@@ -197,16 +204,16 @@ public class AiOpsService {
|
||||
|
||||
String buildQuerySummary(AIOpsRequest request) {
|
||||
if (request == null) {
|
||||
return "AI Ops 告警分析";
|
||||
return "AI Ops alert analysis";
|
||||
}
|
||||
|
||||
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());
|
||||
StringBuilder summary = new StringBuilder("AI Ops alert analysis");
|
||||
appendField(summary, "alert", request.getAlertName());
|
||||
appendField(summary, "service", request.getService());
|
||||
appendField(summary, "severity", request.getSeverity());
|
||||
appendField(summary, "timeRange", request.getTimeRange());
|
||||
appendField(summary, "description", request.getDescription());
|
||||
appendField(summary, "request", request.getUserRequest());
|
||||
return summary.toString();
|
||||
}
|
||||
|
||||
@@ -238,8 +245,8 @@ public class AiOpsService {
|
||||
|
||||
String buildTaskPrompt(AIOpsRequest request) {
|
||||
StringBuilder prompt = new StringBuilder();
|
||||
prompt.append("你是企业级 SRE,接到了自动化告警排查任务。请结合工具调用,执行**规划→执行→再规划**的闭环,并最终按照固定模板输出《告警分析报告》。禁止编造虚假数据,如连续多次查询失败需诚实反馈无法完成的原因。");
|
||||
prompt.append("\n\n本次告警输入:\n");
|
||||
prompt.append("You are an enterprise SRE handling an automated alert diagnosis task. Combine tool evidence, run a plan-execute-replan loop, and output the final alert analysis report. Do not fabricate data; if repeated queries fail, clearly state why the task cannot be completed.");
|
||||
prompt.append("\n\nAlert input:\n");
|
||||
prompt.append(buildQuerySummary(request));
|
||||
if (hasAlertPayload(request)) {
|
||||
String knowledgeQuery = buildKnowledgeRetrievalQuery(request);
|
||||
@@ -278,53 +285,69 @@ public class AiOpsService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 Planner Agent
|
||||
* 闂備礁鎼鍛偓姘煎墰缁?Planner Agent
|
||||
*/
|
||||
private ReactAgent buildPlannerAgent(ChatModel chatModel, ToolCallback[] toolCallbacks) {
|
||||
return ReactAgent.builder()
|
||||
.name("planner_agent")
|
||||
.description("负责拆解告警、规划与再规划步骤")
|
||||
.description("Plans alert diagnosis steps")
|
||||
.model(chatModel)
|
||||
.systemPrompt(promptProperties.getPlanner())
|
||||
.methodTools(buildMethodToolsArray())
|
||||
.tools(toolCallbacks)
|
||||
.hooks(new AgentLoggingHook(agentStepRepository, "planner"))
|
||||
.hooks(buildHooks("planner"))
|
||||
.outputKey("planner_plan")
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 Executor Agent
|
||||
* 闂備礁鎼鍛偓姘煎墰缁?Executor Agent
|
||||
*/
|
||||
private ReactAgent buildExecutorAgent(ChatModel chatModel, ToolCallback[] toolCallbacks) {
|
||||
return ReactAgent.builder()
|
||||
.name("executor_agent")
|
||||
.description("负责执行 Planner 的首个步骤并及时反馈")
|
||||
.description("Executes the current Planner step and reports feedback")
|
||||
.model(chatModel)
|
||||
.systemPrompt(promptProperties.getExecutor())
|
||||
.methodTools(buildMethodToolsArray())
|
||||
.tools(toolCallbacks)
|
||||
.hooks(new AgentLoggingHook(agentStepRepository, "executor"))
|
||||
.hooks(buildHooks("executor"))
|
||||
.outputKey("executor_feedback")
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 动态构建方法工具数组
|
||||
* 根据 cls.mock-enabled 决定是否包含 QueryLogsTools
|
||||
* 工具顺序:知识库查询优先,日志查询次之,弃用工具最后
|
||||
* 闂備礁鎲¢弻锝夊礉瀹ュ鐒垫い鎴f硶閸斿秹鏌f惔顔肩仩妞ゆ洘鐟╅幃婊兾熼懡銈呭箥婵犵數鍋涢ˇ鏉棵洪弽銊ヮ嚤闁圭増婢樼粈鍌炴⒑閸噮鍎愭繛鍫濆缁?
|
||||
* 闂備礁鎼粔鐑斤綖婢跺﹦鏆?cls.mock-enabled 闂備礁鎲¢崝鏇㈠疮閸ф鍋╁Δ锝呭暙閸欏﹥銇勯弽銊ь暡闁稿骸锕弻娑㈠冀瑜庨崳褰掓煙?QueryLogsTools
|
||||
* 闁诲氦顫夐幃鍫曞磿闁秴鐭楅柟绋跨昂娴滄粓鏌涘┑鍡楊伀缁炬澘绉归弻銊モ槈濞嗘劗娈ら梺缁樻惈缁辨洟骞忛悩璇插耿婵°倕鍟惃鎴︽⒑閸濆嫯顫﹂柛搴㈡尦椤㈡艾螖娴e壊鍤ゅ┑鈽嗗灠閹碱偆鏁妷鈺傜叆婵炴垶蓱濠€鐗堜繆椤愮喐娅堢紒鐘崇☉铻栧ù锝呮惈瀵劑鏌i悩鍙夊偍闁搞劍妞介、鏇㈠礂閼测斁鏋欓柣搴到婢у海绮堟径灞稿亾濞堝灝鏋涢柛鐔跺嵆瀵偊濡堕崪浣告櫊闂侀潧顦崕鍗烆嚗閺冨牊鐓涢柛顐h壘娴滈箖姊?
|
||||
*/
|
||||
private Object[] buildMethodToolsArray() {
|
||||
if (queryLogsTools != null) {
|
||||
// Mock 模式:包含 QueryLogsTools
|
||||
// Mock 婵犵妲呴崹顏堝焵椤掆偓绾绢厾娑甸埀顒勬⒑閹稿海鈯曢柤鐟板⒔閳ь剙鐏氶敃銏犵暦?QueryLogsTools
|
||||
return new Object[]{dateTimeTools, lookupKnowledgeTool, queryMetricsTools, queryLogsTools};
|
||||
} else {
|
||||
// 真实模式:不包含 QueryLogsTools(由 MCP 提供日志查询功能)
|
||||
return new Object[]{dateTimeTools, lookupKnowledgeTool, queryMetricsTools};
|
||||
}
|
||||
// Real mode excludes local QueryLogsTools because logs are provided by MCP.
|
||||
return new Object[]{dateTimeTools, lookupKnowledgeTool, queryMetricsTools};
|
||||
}
|
||||
|
||||
/** 从 agent_step 和 tool_invocation 汇总指标回填 diagnosis_session */
|
||||
private Hook[] buildHooks(String agentName) {
|
||||
AgentLoggingHook loggingHook = new AgentLoggingHook(agentStepRepository, agentName);
|
||||
if (skillRegistry == null || skillRegistry.size() == 0) {
|
||||
return new Hook[]{loggingHook};
|
||||
}
|
||||
if ("planner".equals(agentName)) {
|
||||
return new Hook[]{
|
||||
new PlannerSkillMetadataHook(skillRegistry),
|
||||
loggingHook
|
||||
};
|
||||
}
|
||||
return new Hook[]{
|
||||
loggingHook,
|
||||
SkillsAgentHook.builder()
|
||||
.skillRegistry(skillRegistry)
|
||||
.build()
|
||||
};
|
||||
}
|
||||
|
||||
/** 濠?agent_step 闂?tool_invocation 婵犳鍠氶幊鎾趁洪敃鍌氱劦妞ゆ帒鍊荤敮娑㈡倵閸偄鍝虹€殿喕绮欏畷鎯邦槼缂佲偓閳ь剚绻?diagnosis_session */
|
||||
private void backfillSessionMetrics(DiagnosisSession session) {
|
||||
try {
|
||||
List<AgentStep> steps = agentStepRepository.findBySessionIdOrderByStepIndex(session.getSessionId());
|
||||
@@ -340,7 +363,7 @@ public class AiOpsService {
|
||||
session.setStepCount(stepCount);
|
||||
session.setToolCallCount(Math.toIntExact(toolCallCount));
|
||||
} catch (Exception e) {
|
||||
logger.warn("回填会话指标失败: sessionId={}", session.getSessionId(), e);
|
||||
logger.warn("闂備焦鎮堕崕鎶藉磻濞戙垺鏅查柣鎰綑椤曡鲸鎱ㄥΟ铏癸紞婵☆垰鐗撻弻鐔虹矙閹稿骸顦╅梺缁樼壄缁叉儳顕ラ崟顒佺秶妞ゆ劑鍎? sessionId={}", session.getSessionId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,10 @@ import com.alibaba.cloud.ai.graph.OverAllState;
|
||||
import com.alibaba.cloud.ai.graph.RunnableConfig;
|
||||
import com.alibaba.cloud.ai.graph.agent.ReactAgent;
|
||||
import com.alibaba.cloud.ai.graph.agent.flow.agent.SequentialAgent;
|
||||
import com.alibaba.cloud.ai.graph.agent.hook.Hook;
|
||||
import com.alibaba.cloud.ai.graph.agent.hook.skills.SkillsAgentHook;
|
||||
import com.alibaba.cloud.ai.graph.exception.GraphRunnerException;
|
||||
import com.alibaba.cloud.ai.graph.skills.registry.SkillRegistry;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.superbiz.agent.agent.tool.DateTimeTools;
|
||||
@@ -13,6 +16,7 @@ import com.superbiz.agent.agent.tool.QueryLogsTools;
|
||||
import com.superbiz.agent.agent.tool.QueryMetricsTools;
|
||||
import com.superbiz.agent.domain.entity.DiagnosisSession;
|
||||
import com.superbiz.agent.hook.AgentLoggingHook;
|
||||
import com.superbiz.agent.hook.PlannerSkillMetadataHook;
|
||||
import com.superbiz.agent.hook.TokenTrackingChatModel;
|
||||
import com.superbiz.agent.hook.TokenUsageHolder;
|
||||
import com.superbiz.agent.hook.VerifierInputHook;
|
||||
@@ -99,6 +103,9 @@ public class ChatService {
|
||||
@Autowired
|
||||
private KnowledgeDomainService knowledgeDomainService;
|
||||
|
||||
@Autowired(required = false)
|
||||
private SkillRegistry skillRegistry;
|
||||
|
||||
@Autowired
|
||||
private ToolTraceSummaryService toolTraceSummaryService;
|
||||
|
||||
@@ -160,6 +167,7 @@ public class ChatService {
|
||||
systemPromptBuilder.append("你是一个专业的智能助手,可以获取当前时间、查询天气信息、搜索内部文档知识库,以及查询 Prometheus 告警信息。\n");
|
||||
systemPromptBuilder.append("当用户询问时间相关问题时,**必须每次都调用 getCurrentDateTime 工具**,因为时间会不断变化。即使历史消息中有时间信息,也不要直接复用,必须重新查询最新时间。\n");
|
||||
systemPromptBuilder.append("当用户需要查询公司内部文档、流程、最佳实践或技术指南时,使用 lookupKnowledgeTool 工具。\n");
|
||||
systemPromptBuilder.append("当用户的问题匹配某个诊断 Skill 时,先调用 read_skill 读取对应流程,再按流程调用证据工具。\n");
|
||||
systemPromptBuilder.append("当用户需要查询 Prometheus 告警、监控指标或系统告警状态时,使用 queryPrometheusAlerts 工具。\n");
|
||||
systemPromptBuilder.append("当用户需要查询腾讯云日志时,请调用腾讯云mcp服务查询,默认查询地域ap-guangzhou,查询时间范围为近一个月。\n\n");
|
||||
|
||||
@@ -271,7 +279,7 @@ public class ChatService {
|
||||
.systemPrompt(systemPrompt)
|
||||
.methodTools(buildMethodToolsArray())
|
||||
.tools(getToolCallbacks())
|
||||
.hooks(new AgentLoggingHook(agentStepRepository, "intelligent_assistant"))
|
||||
.hooks(buildHooks("intelligent_assistant"))
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -516,7 +524,7 @@ public class ChatService {
|
||||
.description("负责拆解问题、规划步骤")
|
||||
.model(chatModel)
|
||||
.systemPrompt(prompt.toString())
|
||||
.hooks(new AgentLoggingHook(agentStepRepository, "planner"))
|
||||
.hooks(buildHooks("planner"))
|
||||
.outputKey("planner_plan")
|
||||
.build();
|
||||
}
|
||||
@@ -553,11 +561,30 @@ public class ChatService {
|
||||
.systemPrompt(prompt.toString())
|
||||
.methodTools(buildMethodToolsArray())
|
||||
.tools(toolCallbacks)
|
||||
.hooks(new AgentLoggingHook(agentStepRepository, "executor"))
|
||||
.hooks(buildHooks("executor"))
|
||||
.outputKey("executor_feedback")
|
||||
.build();
|
||||
}
|
||||
|
||||
private Hook[] buildHooks(String agentName) {
|
||||
AgentLoggingHook loggingHook = new AgentLoggingHook(agentStepRepository, agentName);
|
||||
if (skillRegistry == null || skillRegistry.size() == 0) {
|
||||
return new Hook[]{loggingHook};
|
||||
}
|
||||
if ("planner".equals(agentName)) {
|
||||
return new Hook[]{
|
||||
new PlannerSkillMetadataHook(skillRegistry),
|
||||
loggingHook
|
||||
};
|
||||
}
|
||||
return new Hook[]{
|
||||
loggingHook,
|
||||
SkillsAgentHook.builder()
|
||||
.skillRegistry(skillRegistry)
|
||||
.build()
|
||||
};
|
||||
}
|
||||
|
||||
private String resolveSessionId(String requestedSessionId) {
|
||||
if (requestedSessionId != null && !requestedSessionId.isBlank()) {
|
||||
return requestedSessionId;
|
||||
|
||||
Reference in New Issue
Block a user