fix(ai): 移除假信心分數 + 顯示 AI 模型來源

問題: AI 仲裁顯示硬編碼信心分數 (0.75/0.88/0.92/0.70)

修復:
- decision_manager: 預設 confidence 0.75 → 0.0
- decision_manager: Expert System confidence=0.0 + is_rule_based
- openclaw: 所有 Mock Response confidence → 0.0
- telegram_gateway: 新增 ai_provider 欄位
- telegram_gateway: 動態來源標籤 (Ollama/Gemini/Claude/規則匹配)

Telegram 卡片顯示:
- confidence > 0 + provider=ollama → 🤖 Ollama 仲裁
- confidence > 0 + provider=gemini → 🤖 Gemini 仲裁
- confidence > 0 + provider=claude → 🤖 Claude 仲裁
- confidence == 0 → ⚙️ 規則匹配

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
OG T
2026-03-29 15:19:51 +08:00
parent 12e49d844a
commit 5f9a6a7e55
4 changed files with 119 additions and 23 deletions

View File

@@ -102,8 +102,9 @@ async def _push_decision_to_telegram(
action = proposal_data.get("action", proposal_data.get("kubectl_command", ""))
description = proposal_data.get("description", "")
reasoning = proposal_data.get("reasoning", "")
confidence = proposal_data.get("confidence", 0.75)
confidence = proposal_data.get("confidence", 0.0) # 🔴 預設 0.0 表示未經 AI 分析
source = proposal_data.get("source", "unknown")
ai_provider = proposal_data.get("provider", "") # 2026-03-29 ogt: AI 模型來源
# 建立 approval_id (使用 incident_id 作為追蹤)
# 2026-03-27 ogt: 修復 INC-INC-INC- 重複前綴 bug
@@ -119,6 +120,7 @@ async def _push_decision_to_telegram(
primary_responsibility="INFRA",
confidence=confidence,
namespace=incident.signals[0].labels.get("namespace", "default") if incident.signals else "default",
ai_provider=ai_provider, # 2026-03-29 ogt: 顯示 AI 模型來源
)
# 🔴 發送成功後設置去重 key (TTL 10 分鐘)
@@ -222,16 +224,19 @@ def expert_analyze(incident: Incident) -> dict[str, Any]:
rule = EXPERT_RULES[matched_rule]
# 2026-03-29 ogt: Expert System 不應該假裝有高信心分數
# 設為 0.0 強制標記為規則匹配,而非 AI 仲裁
return {
"source": "expert_system",
"action": rule["action"].format(target=target),
"description": rule["description"],
"risk_level": rule["risk_level"],
"reasoning": rule["reasoning"],
"confidence": 0.75, # Expert System 固定信心分數
"reasoning": f"[規則匹配] {rule['reasoning']}", # 明確標示來源
"confidence": 0.0, # 🔴 規則匹配不是 AI 仲裁,信心度設 0
"kubectl_command": rule["action"].format(target=target),
"matched_rule": matched_rule,
"from_cache": False,
"is_rule_based": True, # 新增標記
}

View File

@@ -520,10 +520,11 @@ class OpenClawService:
)
# 根據告警類型生成專業 RCA + 仲裁
# 🔴 2026-03-29 ogt: Mock 響應必須標示為規則匹配,不是 AI 仲裁
if "oom" in message.lower() or "memory" in alert_type.lower():
mock_response = {
"action_title": f"刪除異常 Pod {target} (OOMKilled)",
"description": f"🤖 AI 仲裁: {target} 發生 OOMKilled根因為 JVM Heap 配置與 K8s memory limit 不匹配或存在記憶體洩漏。{signoz_summary}",
"description": f"⚙️ 規則匹配: {target} 發生 OOMKilled根因為 JVM Heap 配置與 K8s memory limit 不匹配或存在記憶體洩漏。{signoz_summary}",
"suggested_action": "DELETE_POD",
"kubectl_command": f"kubectl delete pod {target} -n {namespace}",
"target_resource": target,
@@ -550,9 +551,9 @@ class OpenClawService:
"kubectl_or_config": f"kubectl autoscale deployment {target.rsplit('-', 2)[0]} --memory-percent=80 --min=2 --max=5 -n {namespace}"
}
],
"reasoning": f"🤖 Pod OOMKilled 後 ReplicaSet 將自動重建,但需同步修正資源配置防止復發。{signoz_correlation}",
"reasoning": f"⚙️ Pod OOMKilled 後 ReplicaSet 將自動重建,但需同步修正資源配置防止復發。{signoz_correlation}",
"deviation_analysis": f"Memory 使用率 {metrics.get('memory_percent', 99)}%,超出基準線 60% 達 +6.5σ",
"confidence": 0.88,
"confidence": 0.0, # 🔴 規則匹配,非 AI 仲裁
"affected_services": [target, "api-gateway"],
"signoz_correlation": signoz_correlation,
}
@@ -564,7 +565,7 @@ class OpenClawService:
mock_response = {
"action_title": f"擴展 {target} 副本數 + 啟用 HPA",
"description": f"🤖 AI 仲裁: {target} CPU 使用率過高,根因為流量突增或計算密集任務未配置自動擴展。{rps_context}",
"description": f"⚙️ 規則匹配: {target} CPU 使用率過高,根因為流量突增或計算密集任務未配置自動擴展。{rps_context}",
"suggested_action": "SCALE_DEPLOYMENT",
"kubectl_command": tuning["command"],
"target_resource": target,
@@ -591,16 +592,16 @@ class OpenClawService:
"kubectl_or_config": f"kubectl set resources deployment/{target} --requests=cpu=500m --limits=cpu=2000m -n {namespace}"
}
],
"reasoning": f"🤖 水平擴展可即時分散負載,同時建議配置 HPA 防止復發。{signoz_correlation}",
"reasoning": f"[規則匹配] 水平擴展可即時分散負載,同時建議配置 HPA 防止復發。{signoz_correlation}",
"deviation_analysis": f"CPU 使用率 {metrics.get('cpu_percent', 95)}%,超出基準線 50% 達 +4.5σ",
"confidence": 0.92,
"confidence": 0.0, # 🔴 規則匹配不是 AI 仲裁,信心度設 0
"affected_services": [target],
"signoz_correlation": signoz_correlation,
}
elif "http" in alert_type.lower() or "5xx" in message.lower() or "502" in message.lower():
mock_response = {
"action_title": f"重啟 {target} + 檢查上游服務",
"description": f"🤖 AI 仲裁: {target} 產生 HTTP 5xx 錯誤,可能為應用程式例外或上游服務不可達。{signoz_summary}",
"description": f"⚙️ 規則匹配: {target} 產生 HTTP 5xx 錯誤,可能為應用程式例外或上游服務不可達。{signoz_summary}",
"suggested_action": "RESTART_DEPLOYMENT",
"kubectl_command": f"kubectl rollout restart deployment/{target} -n {namespace}",
"target_resource": target,
@@ -627,9 +628,9 @@ class OpenClawService:
"kubectl_or_config": "# 檢查 Redis 連線池配置,建議 maxTotal=50"
}
],
"reasoning": f"🤖 HTTP 錯誤需協同排查,先重啟恢復服務同時通知相關團隊。{signoz_correlation}",
"reasoning": f"[規則匹配] HTTP 錯誤需協同排查,先重啟恢復服務同時通知相關團隊。{signoz_correlation}",
"deviation_analysis": "錯誤率 5%,超出基準線 0.1% 達 +50σ",
"confidence": 0.65,
"confidence": 0.0, # 🔴 規則匹配不是 AI 仲裁,信心度設 0
"affected_services": [target, "nginx-ingress", "upstream-api"],
"signoz_correlation": signoz_correlation,
}
@@ -637,7 +638,7 @@ class OpenClawService:
# 通用異常處理
mock_response = {
"action_title": f"重新啟動 {target} 服務",
"description": f"🤖 AI 仲裁: {target} 發生異常: {message[:80]}。需進一步診斷確認根因。{signoz_summary}",
"description": f"⚙️ 規則匹配: {target} 發生異常: {message[:80]}。需進一步診斷確認根因。{signoz_summary}",
"suggested_action": "RESTART_DEPLOYMENT",
"kubectl_command": f"kubectl rollout restart deployment/{target} -n {namespace}",
"target_resource": target,
@@ -659,9 +660,9 @@ class OpenClawService:
"kubectl_or_config": tuning["command"],
}
],
"reasoning": f"🤖 根據告警 {alert_type} 先重啟恢復服務,同時安排深入診斷。{signoz_correlation}",
"reasoning": f"[規則匹配] 根據告警 {alert_type} 先重啟恢復服務,同時安排深入診斷。{signoz_correlation}",
"deviation_analysis": "監控指標顯示異常偏離基準線",
"confidence": 0.70,
"confidence": 0.0, # 🔴 規則匹配不是 AI 仲裁,信心度設 0
"affected_services": [target],
"signoz_correlation": signoz_correlation,
}

View File

@@ -142,6 +142,10 @@ class TelegramMessage:
# 2026-03-29 ogt: AI Token/Cost 追蹤
ai_tokens: int = 0 # LLM Token 使用量
ai_cost: float = 0.0 # LLM 成本 (USD)
# 2026-03-29 ogt: ADR-037 異常頻率統計
anomaly_frequency: dict | None = None # AnomalyCounter 統計
# 2026-03-29 ogt: AI Provider 來源顯示
ai_provider: str = "" # ollama/gemini/claude/expert_system/mock
def format(self) -> str:
"""
@@ -198,6 +202,43 @@ class TelegramMessage:
if self.ai_tokens > 0 or self.ai_cost > 0:
ai_cost_display = f"💰 Tokens: {self.ai_tokens:,} / ${self.ai_cost:.4f}\n"
# 2026-03-29 ogt: ADR-037 異常頻率顯示
frequency_block = ""
if self.anomaly_frequency and self.anomaly_frequency.get("count_24h", 0) > 1:
freq = self.anomaly_frequency
escalation_emoji = {
None: "",
"REPEAT": "⚠️",
"ESCALATE": "🔴",
"PERMANENT_FIX": "🚨",
}.get(freq.get("escalation_level"), "")
frequency_block = (
f"━━━━━━━━━━━━━━━━━━━\n"
f"📊 <b>頻率統計</b> {escalation_emoji}\n"
f"├ 1h: <code>{freq.get('count_1h', 0)}</code> 次\n"
f"├ 24h: <code>{freq.get('count_24h', 0)}</code> 次\n"
f"└ 修復: <code>{freq.get('auto_repair_count', 0)}</code> 次\n"
)
if freq.get("escalation_level"):
frequency_block += f"🔺 升級: <b>{freq['escalation_level']}</b>\n"
# 2026-03-29 ogt: 根據 confidence + ai_provider 動態顯示來源
# confidence > 0 = 真實 AI 分析, confidence == 0 = 規則匹配/降級
if self.confidence > 0 and self.ai_provider:
# 顯示具體 AI 模型
provider_names = {
"ollama": "Ollama",
"gemini": "Gemini",
"claude": "Claude",
}
provider_display = provider_names.get(self.ai_provider.lower(), self.ai_provider.upper())
source_label = f"🤖 <b>{provider_display} 仲裁</b>"
elif self.confidence > 0:
source_label = "🤖 <b>AI 仲裁判定</b>"
else:
source_label = "⚙️ <b>規則匹配</b>"
# 組裝訊息
message = (
f"═══════════════════════════\n"
@@ -206,11 +247,12 @@ class TelegramMessage:
f"📋 <code>{html.escape(incident_id)}</code>\n"
f"🎯 資源: <code>{safe_resource}</code>\n"
f"━━━━━━━━━━━━━━━━━━━\n"
f"🤖 <b>AI 仲裁判定</b>\n"
f"{source_label}\n"
f"👥 責任: {resp_display}\n"
f"📊 信心: {conf_emoji} {confidence_pct}%\n"
f"{ai_cost_display}"
f"💡 原因: {safe_root_cause}\n"
f"{frequency_block}"
f"{signoz_block}"
f"━━━━━━━━━━━━━━━━━━━\n"
f"🔧 建議: {safe_action}\n"
@@ -437,11 +479,16 @@ class TelegramGateway:
# 2026-03-29 ogt: AI Token/Cost 追蹤
ai_tokens: int = 0,
ai_cost: float = 0.0,
# 2026-03-29 ogt: ADR-037 異常頻率統計
anomaly_frequency: dict | None = None,
# 2026-03-29 ogt: AI Provider 來源顯示
ai_provider: str = "",
) -> dict:
"""
推送待簽核卡片到 Telegram (v7.0 含 SignOz 整合)
SOUL.md 4.1 + AI 仲裁 + SignOz 訊息格式
Phase 21 (ADR-037): 含異常頻率統計
Args:
approval_id: 簽核單 ID
@@ -456,6 +503,7 @@ class TelegramGateway:
signoz_*: SignOz Gold Metrics
signoz_trace_url: 動態時間參數的 Trace URL
auto_tuning_command: kubectl 調優指令
anomaly_frequency: 異常頻率統計 (ADR-037)
Returns:
dict: Telegram API 回應
@@ -475,7 +523,7 @@ class TelegramGateway:
trace_url=signoz_trace_url,
)
# 建立訊息結構 (含 AI 仲裁 + SignOz + Token/Cost)
# 建立訊息結構 (含 AI 仲裁 + SignOz + Token/Cost + 頻率統計)
message = TelegramMessage(
status_emoji=emoji,
risk_level=risk_level.upper(),
@@ -493,6 +541,10 @@ class TelegramGateway:
# 2026-03-29 ogt: AI Token/Cost 追蹤
ai_tokens=ai_tokens,
ai_cost=ai_cost,
# 2026-03-29 ogt: ADR-037 異常頻率統計
anomaly_frequency=anomaly_frequency,
# 2026-03-29 ogt: AI Provider 來源顯示
ai_provider=ai_provider,
)
# 格式化訊息