From 62bcc50770991f10db2e5ebf65f2e34c07c9881c Mon Sep 17 00:00:00 2001 From: OG T Date: Thu, 16 Apr 2026 02:46:06 +0800 Subject: [PATCH] =?UTF-8?q?fix(tg+km):=20=E8=A3=9C=E9=BD=8A=20Telegram=20?= =?UTF-8?q?=E6=93=8D=E4=BD=9C=E7=B4=80=E9=8C=84=E6=8F=AD=E9=9C=B2=E8=88=87?= =?UTF-8?q?=20KM=20=E5=88=86=E9=A1=9E=E4=BF=AE=E5=BE=A9=20(ADR-076)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Telegram 訊息新增欄位: - alert_category 分類標籤(🏷️ 主機/K8s/資料庫/服務等) - playbook_name 顯示匹配到的 Playbook 名稱 - 頻率統計從 count_24h>1 降至 >=1(初次告警也顯示) - TelegramMessage 新增 alert_category/playbook_name 欄位 - decision_manager → send_approval_card 穿透 playbook_name KM 修復: - EntryType.PLAYBOOK → EntryType.AUTO_RUNBOOK(前者不存在,會 AttributeError) - category "auto_generated" → "ai_system"(前端 i18n 有對應翻譯) - runbook_generator 同步修正 category - KM 建立後推 Telegram 通知(best-effort) DB decision_chain 補欄位: - 新增 playbook_id / playbook_name / alert_category 2026-04-16 ogt + Claude Sonnet 4.6 Co-Authored-By: Claude Sonnet 4.6 --- apps/api/src/services/decision_manager.py | 25 +++++++++- apps/api/src/services/runbook_generator.py | 2 +- apps/api/src/services/telegram_gateway.py | 55 +++++++++++++++++++++- 3 files changed, 77 insertions(+), 5 deletions(-) diff --git a/apps/api/src/services/decision_manager.py b/apps/api/src/services/decision_manager.py index 684ca81c4..5d2f2dc2a 100644 --- a/apps/api/src/services/decision_manager.py +++ b/apps/api/src/services/decision_manager.py @@ -215,6 +215,8 @@ async def _push_decision_to_telegram( # 2026-04-09 Claude Sonnet 4.6: Tool Calling 模型/後端 nemotron_tool_model = proposal_data.get("nemotron_tool_model", "") nemotron_tool_backend = proposal_data.get("nemotron_tool_backend", "") + # 2026-04-16 ogt + Claude Sonnet 4.6: Playbook 匹配名稱穿透到 TG 卡片 (ADR-076) + _playbook_name = proposal_data.get("playbook_name", "") # 建立 approval_id (使用 incident_id 作為追蹤) # 2026-03-27 ogt: 修復 INC-INC-INC- 重複前綴 bug @@ -331,6 +333,7 @@ async def _push_decision_to_telegram( incident_id=incident.incident_id, alert_category=_alert_category, notification_type=_notification_type, + playbook_name=_playbook_name, ) # 2026-04-09 Claude Sonnet 4.6: 存 message_id → 後續狀態更新在原訊息延續 @@ -511,8 +514,8 @@ async def _generate_playbook_draft_if_new(incident: "Incident") -> None: KnowledgeEntryCreate( title=f"[AI草稿] {alertname} Playbook", content=content, - entry_type=EntryType.PLAYBOOK, - category="auto_generated", + entry_type=EntryType.AUTO_RUNBOOK, + category="ai_system", tags=[alertname, severity, "ai_draft", "mcp_phase4c"], source=EntrySource.AI_EXTRACTED, status=EntryStatus.DRAFT, @@ -539,6 +542,20 @@ async def _generate_playbook_draft_if_new(incident: "Incident") -> None: entry_id=entry.entry_id, ) + # 2026-04-16 ogt + Claude Sonnet 4.6: KM 建立後推 Telegram 通知 (ADR-076) + # best-effort — 不阻塞主流程 + try: + from src.services.telegram_gateway import get_telegram_gateway as _get_tg + _tg = _get_tg() + await _tg.send_as_nemotron( + f"📚 KM 新條目{entry.entry_id}\n" + f"🏷️ 分類:auto_generated 狀態:DRAFT\n" + f"📋 告警:{alertname} 事件:{incident.incident_id}\n" + f"標題:{str(getattr(entry, 'title', alertname))[:60]}" + ) + except Exception as _tg_err: + _sl.get_logger(__name__).debug("km_tg_notify_failed", error=str(_tg_err)) + except Exception as e: import structlog as _sl _sl.get_logger(__name__).debug("playbook_draft_failed", error=str(e)) @@ -2314,6 +2331,7 @@ class DecisionManager: import json as _json from src.utils.timezone import now_taipei + # 2026-04-16 ogt + Claude Sonnet 4.6: 補入 playbook_id / alert_category (ADR-076) entry = { "ts": now_taipei().isoformat(), "confidence": proposal_data.get("confidence", 0.0), @@ -2321,6 +2339,9 @@ class DecisionManager: "provider": proposal_data.get("provider", proposal_data.get("source", "")), "source": proposal_data.get("source", ""), "diagnosis": str(proposal_data.get("diagnosis", proposal_data.get("description", "")))[:200], + "playbook_id": proposal_data.get("playbook_id", ""), + "playbook_name": proposal_data.get("playbook_name", ""), + "alert_category": proposal_data.get("alert_category", ""), } async with get_db_context() as db: diff --git a/apps/api/src/services/runbook_generator.py b/apps/api/src/services/runbook_generator.py index a70b1172e..6b06ecd7c 100644 --- a/apps/api/src/services/runbook_generator.py +++ b/apps/api/src/services/runbook_generator.py @@ -90,7 +90,7 @@ class NemotronRunbookGenerator: title=f"[AUTO] {incident.incident_id} — {playbook.name}", content=content, entry_type=EntryType.AUTO_RUNBOOK, - category="auto_generated", + category="ai_system", tags=list(incident.affected_services or []) + ["auto_runbook", "nemotron"], source=EntrySource.AI_EXTRACTED, status=EntryStatus.DRAFT, diff --git a/apps/api/src/services/telegram_gateway.py b/apps/api/src/services/telegram_gateway.py index b8730f8e2..4de6fa985 100644 --- a/apps/api/src/services/telegram_gateway.py +++ b/apps/api/src/services/telegram_gateway.py @@ -164,6 +164,9 @@ class TelegramMessage: ai_provider: str = "" # ollama/gemini/claude/expert_system/mock # 2026-04-04 ogt: 底層模型名稱 (e.g. qwen2.5:7b-instruct, nemotron-70b) ai_model: str = "" + # 2026-04-16 ogt + Claude Sonnet 4.6: 告警分類與修復鏈路顯示 (ADR-076) + alert_category: str = "" # host/k8s/database/service/external_site/secops 等 + playbook_name: str = "" # 匹配到的 Playbook 名稱(空字串=規則匹配) # ========================================================================== # Phase 22: Nemotron 協作欄位 (ADR-044) @@ -237,7 +240,7 @@ class TelegramMessage: # 2026-03-29 ogt: ADR-037 異常頻率顯示 frequency_block = "" - if self.anomaly_frequency and self.anomaly_frequency.get("count_24h", 0) > 1: + if self.anomaly_frequency and self.anomaly_frequency.get("count_24h", 0) >= 1: freq = self.anomaly_frequency escalation_emoji = { None: "", @@ -304,12 +307,37 @@ class TelegramMessage: "LOW": "低風險", }.get(self.risk_level.upper(), self.risk_level) + # ADR-076 分類標籤 (2026-04-16 ogt + Claude Sonnet 4.6) + _category_map = { + "host": "🖥️ 主機", "host_resource": "🖥️ 主機", + "k8s": "☸️ K8s", "kubernetes": "☸️ K8s", + "database": "🗄️ 資料庫", + "service": "⚙️ 服務", + "external_site": "🌐 外部網站", + "secops": "🔐 安全", + "auto_repair": "🔧 自動修復", + "alertchain_health": "📡 告警鏈路", + "flywheel_health": "🔄 飛輪健康", + "docker": "🐳 Docker", + "infrastructure": "🏗️ 基礎設施", + } + category_line = "" + if self.alert_category: + cat_display = html.escape(_category_map.get(self.alert_category, self.alert_category)) + category_line = f"🏷️ 分類:{cat_display}\n" + + # Playbook 顯示 + playbook_line = "" + if self.playbook_name: + playbook_line = f"📖 Playbook:{html.escape(self.playbook_name)}\n" + # ADR-075 TYPE-3 格式組裝 message = ( f"{self.status_emoji} ACTION REQUIRED | {html.escape(risk_zh)}\n" f"──────────────────────\n" f"📋 {html.escape(incident_id)}\n" f"🎯 資源:{safe_resource}\n" + f"{category_line}" f"\n" f"🧠 AI 深度診斷\n" f"├─ 分析:{safe_root_cause}\n" @@ -317,6 +345,7 @@ class TelegramMessage: f"└─ {ai_source}\n" f"\n" f"⚡ 建議修復動作\n" + f"{playbook_line}" f"{safe_action}\n" ) if ai_cost_display: @@ -448,10 +477,27 @@ class TelegramMessage: ) # 2026-04-05 Claude Code: 重設計訊息格式,提升易讀性 + # 2026-04-16 ogt + Claude Sonnet 4.6: 補 alert_category + playbook_name 顯示 + _category_map = { + "host": "🖥️ 主機", "host_resource": "🖥️ 主機", + "k8s": "☸️ K8s", "kubernetes": "☸️ K8s", + "database": "🗄️ 資料庫", "service": "⚙️ 服務", + "external_site": "🌐 外部網站", "secops": "🔐 安全", + "auto_repair": "🔧 自動修復", "docker": "🐳 Docker", + } + category_line = "" + if self.alert_category: + cat_display = html.escape(_category_map.get(self.alert_category, self.alert_category)) + category_line = f"🏷️ {cat_display}\n" + playbook_line = "" + if self.playbook_name: + playbook_line = f"📖 {html.escape(self.playbook_name)}\n" + # 組裝訊息 message = ( f"{self.status_emoji} {html.escape(self.risk_level)} {html.escape(incident_id)}\n" f"{safe_resource}\n" + f"{category_line}" f"\n" f"{conf_line}\n" f"👥 {resp_display}\n" @@ -460,7 +506,7 @@ class TelegramMessage: if nemotron_block: message += f"\n{nemotron_block}" message += ( - f"\n🔧 建議: {safe_action}\n" + f"\n🔧 建議: {playbook_line}{safe_action}\n" f"⏱️ 停機: {safe_downtime}" ) @@ -1488,6 +1534,8 @@ class TelegramGateway: # 2026-04-12 ogt: ADR-075 告警分類與通知類型(斷點 B 修復) alert_category: str = "", notification_type: str = "", + # 2026-04-16 ogt + Claude Sonnet 4.6: 修復鏈路顯示 (ADR-076) + playbook_name: str = "", ) -> dict: """ 推送待簽核卡片到 Telegram (v7.0 含 SignOz 整合) @@ -1561,6 +1609,9 @@ class TelegramGateway: # 2026-04-09 Claude Sonnet 4.6: Tool Calling 模型/後端 nemotron_tool_model=nemotron_tool_model, nemotron_tool_backend=nemotron_tool_backend, + # 2026-04-16 ogt + Claude Sonnet 4.6: 修復鏈路顯示 (ADR-076) + alert_category=alert_category, + playbook_name=playbook_name, ) # 格式化訊息 — Phase 22: 如果 Nemotron 啟用,使用雙軌格式