fix(tg+km): 補齊 Telegram 操作紀錄揭露與 KM 分類修復 (ADR-076)
Some checks failed
CD Pipeline / build-and-deploy (push) Has been cancelled

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 <noreply@anthropic.com>
This commit is contained in:
OG T
2026-04-16 02:46:06 +08:00
parent 44ecf609e0
commit 62bcc50770
3 changed files with 77 additions and 5 deletions

View File

@@ -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"📚 <b>KM 新條目</b> — <code>{entry.entry_id}</code>\n"
f"🏷️ 分類:<b>auto_generated</b> 狀態DRAFT\n"
f"📋 告警:<code>{alertname}</code> 事件:<code>{incident.incident_id}</code>\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:

View File

@@ -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,

View File

@@ -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"🏷️ 分類:<b>{cat_display}</b>\n"
# Playbook 顯示
playbook_line = ""
if self.playbook_name:
playbook_line = f"📖 Playbook<code>{html.escape(self.playbook_name)}</code>\n"
# ADR-075 TYPE-3 格式組裝
message = (
f"{self.status_emoji} ACTION REQUIRED | <b>{html.escape(risk_zh)}</b>\n"
f"──────────────────────\n"
f"📋 <code>{html.escape(incident_id)}</code>\n"
f"🎯 資源:<code>{safe_resource}</code>\n"
f"{category_line}"
f"\n"
f"🧠 <b>AI 深度診斷</b>\n"
f"├─ 分析:{safe_root_cause}\n"
@@ -317,6 +345,7 @@ class TelegramMessage:
f"└─ {ai_source}\n"
f"\n"
f"⚡ <b>建議修復動作</b>\n"
f"{playbook_line}"
f"<code>{safe_action}</code>\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"📖 <code>{html.escape(self.playbook_name)}</code>\n"
# 組裝訊息
message = (
f"{self.status_emoji} <b>{html.escape(self.risk_level)}</b> <code>{html.escape(incident_id)}</code>\n"
f"<b>{safe_resource}</b>\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🔧 <b>建議:</b> {safe_action}\n"
f"\n🔧 <b>建議:</b> {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 啟用,使用雙軌格式