fix: clean up aiops mojibake text
This commit is contained in:
@@ -36,8 +36,8 @@ import java.util.Optional;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AI Ops 闂備礁鎼幊妯肩磽濮樿泛绀傛俊顖滅帛娴溿倝鏌熼柇锕€鏋熸俊顖氾躬閺岋繝宕煎┑鍩裤垹鈹?
|
* AI Ops 智能运维服务。
|
||||||
* 闂佽崵濮甸崝妤呭窗閺囥垺鍎楁俊銈勭缁?Agent 闂備礁鎲¢〃鍛崲鐎n剛绀婇柡鍐ㄧ墛閸庡秹鏌涢弴銊ヤ簼闁哥喓鍋ら幃褰掑焵椤掑嫭鏅濋柍褜鍓熷畷瑙勬償閵娿儱鍤戦梺褰掑亰閸橀箖濡堕敂鍓х<?
|
* 负责构建 Planner、Executor、Supervisor 多 Agent 编排流程,并持久化诊断会话与执行指标。
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class AiOpsService {
|
public class AiOpsService {
|
||||||
@@ -53,7 +53,7 @@ public class AiOpsService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private QueryMetricsTools queryMetricsTools;
|
private QueryMetricsTools queryMetricsTools;
|
||||||
|
|
||||||
@Autowired(required = false) // Mock 婵犵妲呴崹顏堝焵椤掆偓绾绢厾娑甸埀顒佺箾閹寸偞灏い鎴濇閺呭爼鎮╁ù瀣亙闂侀潧顭堥崕閬嶅棘閳?
|
@Autowired(required = false) // Mock 模式下才注册本地日志查询工具。
|
||||||
private QueryLogsTools queryLogsTools;
|
private QueryLogsTools queryLogsTools;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@@ -81,12 +81,12 @@ public class AiOpsService {
|
|||||||
private SkillRegistry skillRegistry;
|
private SkillRegistry skillRegistry;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 闂備礁婀遍悷鎶藉幢閳哄倹鏉?AI Ops 闂備礁鎲$粙鎴︽晝閵娾晩鏁嗛柣鏃傚帶缁€鍡涙煕閳╁喚鐒介柍褜鍓濆Λ鍕箒婵炶揪缍€閵嗏偓闁?
|
* 执行 AI Ops 告警分析流程。
|
||||||
*
|
*
|
||||||
* @param chatModel 濠电姰鍨归悥銏ゅ礋閳ь剚绗熼埀顒€鐣烽崷顓涘亾閿濆簼绨介柡澶庢閵?
|
* @param chatModel 大模型实例
|
||||||
* @param toolCallbacks 闁诲氦顫夐幃鍫曞磿闁秴鐭楅柛褎顨呴悙濠囨煟閹邦剙顣虫繛鍫濈埣閺屸剝鎷呴悷閭︽缂?
|
* @param toolCallbacks Spring AI 工具回调
|
||||||
* @return 闂備礁鎲$敮鎺懳涘▎鎾村€甸柣锝呯灱绾惧ジ鏌熼幆褜鍤熷ù婊庡灦閺岋絽螣閸喚鍘梺?
|
* @return 多 Agent 编排后的最终状态
|
||||||
* @throws GraphRunnerException 濠电姷顣介埀顒€鍟块埀顒€缍婇幃?Agent 闂備礁婀遍悷鎶藉幢閳哄倹鏉稿┑鐘灪閸庤偐鍒掗崜褎鍠?
|
* @throws GraphRunnerException Agent 图执行失败时抛出
|
||||||
*/
|
*/
|
||||||
public Optional<OverAllState> executeAiOpsAnalysis(ChatModel chatModel, ToolCallback[] toolCallbacks) throws GraphRunnerException {
|
public Optional<OverAllState> executeAiOpsAnalysis(ChatModel chatModel, ToolCallback[] toolCallbacks) throws GraphRunnerException {
|
||||||
return executeAiOpsAnalysis(chatModel, toolCallbacks, null, resolveSessionId(null));
|
return executeAiOpsAnalysis(chatModel, toolCallbacks, null, resolveSessionId(null));
|
||||||
@@ -99,19 +99,16 @@ public class AiOpsService {
|
|||||||
String resolvedSessionId = isBlank(sessionId) ? resolveSessionId(request) : sessionId.trim();
|
String resolvedSessionId = isBlank(sessionId) ? resolveSessionId(request) : sessionId.trim();
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
// 闂備礁鎲$敮妤冪矙閹寸姷纾介柟鎹愵嚙缁狅綁鏌″鍐ㄥ缂佺虎鍨堕弻锟犲磼濞戞﹩鈧粓鏌i敂鐣屽⒌鐎殿噮鍓熼、妯衡攽閸垻宕堕梺?
|
|
||||||
DiagnosisSession session = startDiagnosisSession(resolvedSessionId, request);
|
DiagnosisSession session = startDiagnosisSession(resolvedSessionId, request);
|
||||||
diagnosisSessionRepository.save(session);
|
diagnosisSessionRepository.save(session);
|
||||||
|
|
||||||
// 闂佽崵濮崇粈浣规櫠娴犲鍋?ThreadLocal 濠电偞鍨堕幐鎼佹晝閿濆洨绠旈柛娑欐綑濡﹢鏌涢妷鈺婃缂佲偓閸戯箰okupKnowledgeTool 闂傚倷绶¢崑鍛┍閾忚宕查柛鎰电厛濞间即鏌曢崼婵堝缂佺媭鍨堕弻?sessionId闂?
|
// 让工具调用、Hook 和知识库检索能够拿到当前诊断会话 ID。
|
||||||
SessionContextHolder.setSessionId(resolvedSessionId);
|
SessionContextHolder.setSessionId(resolvedSessionId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 闂備礁鎼鍛偓姘煎墰缁?Planner 闂?Executor Agent闂備焦瀵х粙鎴︽偋婵犲洦鍎婇柍鈺佸暟閳?Agent 闂備礁鎲¢懝鍓р偓姘煎灦瀹曢潧顭ㄩ崨顔芥?Hook闂?
|
|
||||||
ReactAgent plannerAgent = buildPlannerAgent(chatModel, toolCallbacks);
|
ReactAgent plannerAgent = buildPlannerAgent(chatModel, toolCallbacks);
|
||||||
ReactAgent executorAgent = buildExecutorAgent(chatModel, toolCallbacks);
|
ReactAgent executorAgent = buildExecutorAgent(chatModel, toolCallbacks);
|
||||||
|
|
||||||
// 闂備礁鎼鍛偓姘煎墰缁?Supervisor Agent闂備焦瀵х粙鎴︽偋閸涱垳绠斿鑸靛姇缁€?Hook闂?
|
|
||||||
SupervisorAgent supervisorAgent = SupervisorAgent.builder()
|
SupervisorAgent supervisorAgent = SupervisorAgent.builder()
|
||||||
.name("ai_ops_supervisor")
|
.name("ai_ops_supervisor")
|
||||||
.description("Coordinates Planner and Executor agents")
|
.description("Coordinates Planner and Executor agents")
|
||||||
@@ -122,19 +119,17 @@ public class AiOpsService {
|
|||||||
|
|
||||||
String taskPrompt = buildTaskPrompt(request);
|
String taskPrompt = buildTaskPrompt(request);
|
||||||
|
|
||||||
logger.info("闂佽崵濮撮鍛村疮娴兼潙鏋?Supervisor Agent 闁诲孩顔栭崰鎺楀磻閹炬枼鏀芥い鏃傗拡閸庢垹绱掓鏍﹂偗妤?..");
|
logger.info("Invoking AI Ops supervisor agent");
|
||||||
|
|
||||||
Optional<OverAllState> stateOptional = supervisorAgent.invoke(taskPrompt);
|
Optional<OverAllState> stateOptional = supervisorAgent.invoke(taskPrompt);
|
||||||
|
|
||||||
long duration = System.currentTimeMillis() - startTime;
|
long duration = System.currentTimeMillis() - startTime;
|
||||||
|
|
||||||
// 闂備礁鎼ú銈夋偤閵娾晛钃熷┑鐘插鐎氭艾鈹戦悩鎻掓殲闁绘帟妫勯湁闁稿繘妫挎禍銏ゆ煟?
|
|
||||||
session.setStatus(stateOptional.isPresent() ? "SUCCESS" : "FAILED");
|
session.setStatus(stateOptional.isPresent() ? "SUCCESS" : "FAILED");
|
||||||
session.setTotalDurationMs((int) duration);
|
session.setTotalDurationMs((int) duration);
|
||||||
backfillSessionMetrics(session);
|
backfillSessionMetrics(session);
|
||||||
diagnosisSessionRepository.save(session);
|
diagnosisSessionRepository.save(session);
|
||||||
|
|
||||||
// 婵犵數鍎戠紞鈧い鏇嗗嫭鍙忛柣鎰仛鐎氼剟鏌涢幇闈涘箻婵¤尙顭堥湁闁绘瑥鎳愰幃濂告煟?
|
|
||||||
if (stateOptional.isPresent()) {
|
if (stateOptional.isPresent()) {
|
||||||
OverAllState state = stateOptional.get();
|
OverAllState state = stateOptional.get();
|
||||||
logger.debug("Final State Keys: {}", state.data().keySet());
|
logger.debug("Final State Keys: {}", state.data().keySet());
|
||||||
@@ -153,22 +148,21 @@ public class AiOpsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 濠电偛顕慨瀵糕偓娑掓櫆閺呭爼鎮剧仦鎯т粧閻庡厜鍋撻柍褜鍓涢崚鎺楀Ω閳轰礁鍤戝┑鐘才堥崑鎾绘煠閸偄鐏存鐐存崌楠炲洭顢楅埀顒傚緤閸ф鐓涢柛顐h壘娴滃墽绱撻崒娆戭槮闁绘锕ラ幈銊╁Χ婢跺﹤绐涙繝鐢靛Т閸燁垶鎮楅鈧弻?
|
* 从多 Agent 执行状态中提取最终报告文本。
|
||||||
*
|
*
|
||||||
* @param state 闂備礁婀遍悷鎶藉幢閳哄倹鏉搁梻浣虹帛椤牓宕洪弽顓炵劦?
|
* @param state Agent 图执行状态
|
||||||
* @return 闂備胶顢婄紙浼村磿闁秴绠熼柨鐔哄Т濡﹢鏌涢妷锝呭闁圭兘浜堕弻銊モ槈濡厧顣哄銈傛暘閸パ冨殤濠电姴锕ら崯浼村箺閻樼粯鐓曢柨鏃囧吹閸樻粎绱?
|
* @return Planner 输出中的最终报告
|
||||||
*/
|
*/
|
||||||
public Optional<String> extractFinalReport(OverAllState state) {
|
public Optional<String> extractFinalReport(OverAllState state) {
|
||||||
logger.info("闁诲孩顔栭崰鎺楀磻閹炬枼鏀芥い鏃傗拡閸庢劗鎲告0浣虹獢鐎规洩缍佸浠嬪Ω閿旇法甯涚紓鍌氬€风粈渚€鎮ф繝鍐╁弿闁靛牆顦?..");
|
logger.info("Extracting final AI Ops report");
|
||||||
|
|
||||||
// 闂備礁婀辩划顖炲礉閺嚶颁汗?Planner 闂備礁鎼悧鍐磻閹惧墎纾藉ù锝呮憸婢э絿绱掓0婵嗗籍鐎规洘鐟╅幃顔锯偓闈涙憸椤︹晠姊洪崨濠勫ⅹ闁瑰啿閰i獮鍡涘醇閳垛晛浜鹃柣鐔哄濠€浼存煛閸☆厾绉柟顖氬暣瀹曠喖顢曢敐鍛畼闂佽崵濮崑鎾绘煥閺囨浜鹃梺鎼炲妼闁帮絽顕i幖浣哥疀妞ゆ挾鍊幘缁樼厱婵炴垶锕╅悡顓犵磼?
|
|
||||||
Optional<AssistantMessage> plannerFinalOutput = state.value("planner_plan")
|
Optional<AssistantMessage> plannerFinalOutput = state.value("planner_plan")
|
||||||
.filter(AssistantMessage.class::isInstance)
|
.filter(AssistantMessage.class::isInstance)
|
||||||
.map(AssistantMessage.class::cast);
|
.map(AssistantMessage.class::cast);
|
||||||
|
|
||||||
if (plannerFinalOutput.isPresent()) {
|
if (plannerFinalOutput.isPresent()) {
|
||||||
String reportText = plannerFinalOutput.get().getText();
|
String reportText = plannerFinalOutput.get().getText();
|
||||||
logger.info("闂備胶鎳撻悺銊╁礉閺囩喐鍙忔繛鎴欏灩缁犵敻鏌熼柇锕€澧紒鎻掓健閺?Planner 闂備礁鎼悧鍐磻閹惧墎纾藉ù锝呮憸婢ф稑鈹戦鍝勨偓婵嗙暦閵婏妇绡€闊洦娲滈ˇ顕€姊婚崒妤€浜鹃梺鍓茬厛閸犳牠顢? {}", reportText.length());
|
logger.info("Extracted Planner final report, length: {}", reportText.length());
|
||||||
return Optional.of(reportText);
|
return Optional.of(reportText);
|
||||||
} else {
|
} else {
|
||||||
logger.warn("Unable to extract Planner final report");
|
logger.warn("Unable to extract Planner final report");
|
||||||
@@ -285,7 +279,7 @@ public class AiOpsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 闂備礁鎼鍛偓姘煎墰缁?Planner Agent
|
* 构建 Planner Agent。
|
||||||
*/
|
*/
|
||||||
private ReactAgent buildPlannerAgent(ChatModel chatModel, ToolCallback[] toolCallbacks) {
|
private ReactAgent buildPlannerAgent(ChatModel chatModel, ToolCallback[] toolCallbacks) {
|
||||||
return ReactAgent.builder()
|
return ReactAgent.builder()
|
||||||
@@ -299,7 +293,7 @@ public class AiOpsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 闂備礁鎼鍛偓姘煎墰缁?Executor Agent
|
* 构建 Executor Agent。
|
||||||
*/
|
*/
|
||||||
private ReactAgent buildExecutorAgent(ChatModel chatModel, ToolCallback[] toolCallbacks) {
|
private ReactAgent buildExecutorAgent(ChatModel chatModel, ToolCallback[] toolCallbacks) {
|
||||||
return ReactAgent.builder()
|
return ReactAgent.builder()
|
||||||
@@ -315,16 +309,13 @@ public class AiOpsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 闂備礁鎲¢弻锝夊礉瀹ュ鐒垫い鎴f硶閸斿秹鏌f惔顔肩仩妞ゆ洘鐟╅幃婊兾熼懡銈呭箥婵犵數鍋涢ˇ鏉棵洪弽銊ヮ嚤闁圭増婢樼粈鍌炴⒑閸噮鍎愭繛鍫濆缁?
|
* 根据运行模式构建方法工具数组。
|
||||||
* 闂備礁鎼粔鐑斤綖婢跺﹦鏆?cls.mock-enabled 闂備礁鎲¢崝鏇㈠疮閸ф鍋╁Δ锝呭暙閸欏﹥銇勯弽銊ь暡闁稿骸锕弻娑㈠冀瑜庨崳褰掓煙?QueryLogsTools
|
* Mock 模式注入本地 QueryLogsTools;真实模式下日志查询由外部 MCP 工具提供。
|
||||||
* 闁诲氦顫夐幃鍫曞磿闁秴鐭楅柟绋跨昂娴滄粓鏌涘┑鍡楊伀缁炬澘绉归弻銊モ槈濞嗘劗娈ら梺缁樻惈缁辨洟骞忛悩璇插耿婵°倕鍟惃鎴︽⒑閸濆嫯顫﹂柛搴㈡尦椤㈡艾螖娴e壊鍤ゅ┑鈽嗗灠閹碱偆鏁妷鈺傜叆婵炴垶蓱濠€鐗堜繆椤愮喐娅堢紒鐘崇☉铻栧ù锝呮惈瀵劑鏌i悩鍙夊偍闁搞劍妞介、鏇㈠礂閼测斁鏋欓柣搴到婢у海绮堟径灞稿亾濞堝灝鏋涢柛鐔跺嵆瀵偊濡堕崪浣告櫊闂侀潧顦崕鍗烆嚗閺冨牊鐓涢柛顐h壘娴滈箖姊?
|
|
||||||
*/
|
*/
|
||||||
private Object[] buildMethodToolsArray() {
|
private Object[] buildMethodToolsArray() {
|
||||||
if (queryLogsTools != null) {
|
if (queryLogsTools != null) {
|
||||||
// Mock 婵犵妲呴崹顏堝焵椤掆偓绾绢厾娑甸埀顒勬⒑閹稿海鈯曢柤鐟板⒔閳ь剙鐏氶敃銏犵暦?QueryLogsTools
|
|
||||||
return new Object[]{dateTimeTools, lookupKnowledgeTool, queryMetricsTools, queryLogsTools};
|
return new Object[]{dateTimeTools, lookupKnowledgeTool, queryMetricsTools, queryLogsTools};
|
||||||
}
|
}
|
||||||
// Real mode excludes local QueryLogsTools because logs are provided by MCP.
|
|
||||||
return new Object[]{dateTimeTools, lookupKnowledgeTool, queryMetricsTools};
|
return new Object[]{dateTimeTools, lookupKnowledgeTool, queryMetricsTools};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,7 +338,9 @@ public class AiOpsService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 濠?agent_step 闂?tool_invocation 婵犳鍠氶幊鎾趁洪敃鍌氱劦妞ゆ帒鍊荤敮娑㈡倵閸偄鍝虹€殿喕绮欏畷鎯邦槼缂佲偓閳ь剚绻?diagnosis_session */
|
/**
|
||||||
|
* 从 agent_step 和 tool_invocation 回填 diagnosis_session 的汇总指标。
|
||||||
|
*/
|
||||||
private void backfillSessionMetrics(DiagnosisSession session) {
|
private void backfillSessionMetrics(DiagnosisSession session) {
|
||||||
try {
|
try {
|
||||||
List<AgentStep> steps = agentStepRepository.findBySessionIdOrderByStepIndex(session.getSessionId());
|
List<AgentStep> steps = agentStepRepository.findBySessionIdOrderByStepIndex(session.getSessionId());
|
||||||
@@ -363,7 +356,7 @@ public class AiOpsService {
|
|||||||
session.setStepCount(stepCount);
|
session.setStepCount(stepCount);
|
||||||
session.setToolCallCount(Math.toIntExact(toolCallCount));
|
session.setToolCallCount(Math.toIntExact(toolCallCount));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.warn("闂備焦鎮堕崕鎶藉磻濞戙垺鏅查柣鎰綑椤曡鲸鎱ㄥΟ铏癸紞婵☆垰鐗撻弻鐔虹矙閹稿骸顦╅梺缁樼壄缁叉儳顕ラ崟顒佺秶妞ゆ劑鍎? sessionId={}", session.getSessionId(), e);
|
logger.warn("Failed to backfill AI Ops session metrics, sessionId={}", session.getSessionId(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -149,9 +149,9 @@ class AiOpsServiceTest {
|
|||||||
when(diagnosisSessionRepository.findBySessionId("aiops-session-001")).thenReturn(Optional.of(session));
|
when(diagnosisSessionRepository.findBySessionId("aiops-session-001")).thenReturn(Optional.of(session));
|
||||||
when(toolInvocationRepository.findBySessionIdOrderByIdAsc("aiops-session-001")).thenReturn(List.of());
|
when(toolInvocationRepository.findBySessionIdOrderByIdAsc("aiops-session-001")).thenReturn(List.of());
|
||||||
|
|
||||||
service.persistFinalReport("aiops-session-001", "# 闁告稑锕ㄩ鐔煎礆閸℃鈧粙骞庨妷銉﹀暈\nHighCPUUsage payment-service analysis with evidence summary.");
|
service.persistFinalReport("aiops-session-001", "# 告警分析报告\nHighCPUUsage payment-service analysis with evidence summary.");
|
||||||
|
|
||||||
assertEquals("# 闁告稑锕ㄩ鐔煎礆閸℃鈧粙骞庨妷銉﹀暈\nHighCPUUsage payment-service analysis with evidence summary.", session.getAnswer());
|
assertEquals("# 告警分析报告\nHighCPUUsage payment-service analysis with evidence summary.", session.getAnswer());
|
||||||
assertTrue(session.getSelfEvaluation().contains("aiops_rule_evaluation"));
|
assertTrue(session.getSelfEvaluation().contains("aiops_rule_evaluation"));
|
||||||
verify(diagnosisSessionRepository).save(session);
|
verify(diagnosisSessionRepository).save(session);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user