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