LOGBOOK + ADR-092 附錄 C — 2026-04-21 修復紀錄 E2E 驗證: telegram_approval_card_sent message_id=25045 (SignOzDown) ✓ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
9.8 KiB
ADR-092: AI Decision Layer — LLM 擴展架構
狀態: Accepted 日期: 2026-04-19 (台北時區) 作者: ogt + Claude Opus 4.7 (1M context, 亞太) 關聯: ADR-090 盲區治理 / MASTER §3 D1-D6 / feedback_ai_autonomous_direction
背景
2026-04-19 session 完成 Phase 7 盲區治理後(14 個新 scanner,8 張 0 writer 表全活化),首席架構師 Review 發現嚴重 Gap:
- AI 層淺:14 個新 scanner 中 8 個純 threshold(rule-based),只 Hermes 1 個真用 LLM 做決策
- autonomy_score 實測:63/100(starter 起步級),
ai_diversity子項僅 6/20 - 統帥鐵律:「朝 AI 自主化方向」— infrastructure 不等於 AI
違反 feedback_ai_autonomous_direction.md 核心原則:「禁寫死規則做最終決策」。
決策
1. AI Decision 層的 4 個 LLM Service(本 session 完成)
| Service | LLM 判斷 required_key | 觸發條件 | 職責 |
|---|---|---|---|
| hermes_rule_quality | recommended_actions |
每日 04:00 Taipei | 分析 noise_rate > 0.5 rule 的假報真因 + 改進建議 |
| capacity_forecaster | priority_actions |
每日 05:00 Taipei(predict_linear 命中) | 分析 7d 容量預測高風險 host 的修復策略 |
| compliance_scanner | posture_grade |
每日 03:00 Taipei(有 warning/violation) | 產整體 compliance posture 評級 + top 3 action |
| coverage_evaluator | worst_dimension |
每 1h(red_ratio > 30% 且 scanned > 50) | 分析 7 維 red 缺口 + 補覆蓋建議 |
2. LLM 調用統一 pattern(services/llm_json_parser.py)
原問題:4 個 LLM scanner 各自重複 3-path JSON parse 邏輯(~80 行 × 4 = 320 行)
新 pattern:parse_llm_json_response(text, required_key, logger_context)
# 統一 3-path fallback
Path 1: 剝 markdown fence + 直接 JSON 含 required_key
Path 2: NemoTron wrapper — description/action_title/reasoning 內嵌 JSON
Path 3: 所有失敗 return None + logger.warning
所有 LLM service 都用此 helper,未來擴加 LLM service 直接呼叫。
3. LLM Service 架構約束(鐵律)
必須遵守:
- 失敗永不 raise:LLM 掛掉/parse 失敗 → return None → 呼叫者 fallback 硬編規則
- AI 只建議不動作:所有 LLM 輸出都寫
requires_human_decision=True,推 Telegram 等人工 - openclaw 統一入口:不直接呼叫 Ollama/NVIDIA/Gemini,走
get_openclaw().call(prompt)多 provider fallback - aol 留痕:LLM 結果進
automation_operation_log.output.llm_analysis - 繁中 + JSON schema:prompt 明確 required_key,system prompt 要求 LLM 產純 JSON
禁止:
- 硬編 threshold 做最終決策(只做「觸發討論」)
- LLM 結果直接執行破壞性動作(必過人工)
- 內嵌多 provider 判斷(統一走 openclaw)
4. 觸發條件設計原則
節流避免 token 成本爆炸:
- Daily cron:每日一次(compliance/forecaster/hermes)
- 事件觸發:coverage 只在
red_ratio > 30% 且 scanned >= 50才跑 LLM - 避免 bootstrap 首次必觸發的浪費
5. autonomy_score 量化追蹤
新增 GET /api/v1/aiops/kpi 回 5 子項 × 20 分總分(0-100):
asset_coverage: green 比例 × 20rule_quality: 1 - (noisy/total) × 20capacity_health: critical 扣 10 / warning 扣 3automation_flow: log10(24h_ops) 標準化 × 20ai_diversity: ai_generated rules + op_type 多樣性 × 20
Grade: mature(90+) / in_progress(70-90) / starter(50-70) / initial(<50)
實作成果
達成指標
- LLM decision service: 1/9 → 4/9
- autonomy_score: 未量化 → 63/100 (starter) 可持續追蹤
- LLM JSON parse 重複碼: 320 行 → 1 份共用 helper(90 行)
Commits
ba18ad2Hermes LLM 升級(Hermes 從 threshold 改 LLM)d6b854aGap 3.1 capacity_forecaster LLMf6cb938Gap 3.2 compliance_scanner LLM2f5cab2Gap 3.3 coverage_evaluator LLMfa643ebP1 重構:llm_json_parser helper + coverage 雙條件
相關檔案
apps/api/src/services/llm_json_parser.py(共用 helper)apps/api/src/services/openclaw.py(多 provider)apps/api/src/jobs/{hermes_rule_quality,capacity_forecaster,compliance_scanner,coverage_evaluator}_job.pyapps/api/src/services/aiops_kpi_service.py(autonomy_score 計算)apps/api/src/api/v1/aiops_kpi.py(KPI endpoint)
後續工作(下 session)
P1 剩餘優化(首席架構師 Review 指出)
- Prometheus HTTP helper 抽出 — 5 scanner 重複 httpx + 錯誤處理 pattern
- 14 scanner first_delay 錯開 — 避免並行啟動搶 DB pool(建議 60/90/120/180 階梯)
- LLM budget guard —
aiops_kpi_service加llm_call_count_24h指標超過閾值 Telegram 告警 - asset_scanner 918 行拆分 —
providers/+writers/兩層
可擴展 LLM 方向
rule_catalog_sync: 新規則 import 時 LLM 驗證 expr 合理性asset_change_tracker: 重大 lifecycle 變化時 LLM 評估爆炸半徑drift_interpreter(既有): 升級更精細 prompt
回滾計畫
若 LLM service 造成成本或穩定性問題,可分層關閉:
- 改 config
AIOPS_LLM_ENABLED=false(需新增,目前未實作) - 或 kubectl exec 進 Pod
kill單個 loop task - 各 LLM
try/except return None → fallback原設計保護主流程不受影響
相關 ADR
- ADR-067 Phase 30 AI 五大應用(OpenClaw + drift_narrator + runbook_generator 等原 LLM service)
- ADR-081 PreDecisionInvestigator + EvidenceSnapshot
- ADR-083 學習閉環重建
- ADR-090 監控盲區治理 + 自動化覆蓋矩陣(11 張 + 8 張新表)
附錄 A:2026-04-20 四修(B1-B4)— 全流程斷鏈緊急修復
觸發:統帥截圖 Telegram 顯示「採納失敗」+ 兩天無任何告警。 指令:全景、全流程、全節點、全架構比對,找到所有斷點再修。
Root Cause 鏈
| 症狀 | 斷點 | 根因 |
|---|---|---|
| Playbook 學習失敗 163次/48h | playbook_service.update_with_validation |
evolver 傳 .value(str)→ setattr 不觸發 Pydantic → .value 炸 |
| Telegram 永久靜默 2 天 | approval_db.find_by_fingerprint |
PENDING = 「已發 TG」謊言;TG 失敗後 approval 永 PENDING → 永遠收斂跳過 |
| 採納變更失敗 | telegram_gateway._handle_drift_action |
呼叫 DriftAdoptService.adopt_drift() 但方法不存在(AttributeError) |
| AI 無法感知自身故障 | main.py 無 background watchdog | MASTER §1.1 盲區:SLO 計算只是 API endpoint |
修復(6 檔 / 230 行 / commit 156a52f)
- B1
playbook_service.py—update_with_validationsetattr 前強制 enum 轉型 - B2a
approval_db.py—find_by_fingerprintdebounce 窗外 PENDING 必須 Redistg_sent:{fp}確認;新mark_telegram_confirmed() - B2b
webhooks.py—_push_to_telegram_background成功後setex tg_sent:{fp}24h;3 call site 傳 fingerprint - Drift
drift_adopt_service.py— 新adopt_drift(report_id)wrapper - B3
jobs/ai_slo_watchdog_job.py— 新建:每 15 分鐘 W-1/W-2/W-3 → TYPE-8M - B3
main.pylifespan 註冊 watchdog loop
附錄 B:2026-04-20 C1-C4 — Playbook 鏈路結構性斷點修復
觸發:統帥全景盤查後,發現 Playbook 自動修復鏈路有 3 個結構性斷點。
| # | 斷點 | 根因 | 修復 | commit |
|---|---|---|---|---|
| C1 | evolver 封存 yaml_rule playbooks | 無 YAML_RULE source guard |
playbook_evolver.py 兩處加 if source == YAML_RULE: continue |
de2d34d |
| C2 | seeder 不復活 DEPRECATED yaml_rule | idempotency SQL 包含 DEPRECATED | AND status != 'deprecated' |
de2d34d |
| C3 | AI 新規則不即時建立 Playbook | _append_rule_to_yaml 後無 seeder 呼叫 |
create_task(seed_playbooks_from_rules()) |
de2d34d |
| C4 | watchdog 不偵測鏈路斷裂 | W-4 缺失 | _count_approved_playbooks();為 0 → TYPE-8M |
de2d34d |
架構鐵律:PlaybookSource.YAML_RULE playbooks 是自動修復鏈路的「基礎設施」,evolver 的 trust-based 退場邏輯不得觸及此類 playbooks。
附錄 C:2026-04-21 — BUTTON_DATA_INVALID 根治 + Gitea Code Review 修復
觸發:Telegram 所有 devops_tool 類別告警卡片發送失敗(HTTP 400 BUTTON_DATA_INVALID)+ Gitea PR Code Review 顯示「AI 分析失敗」。
Root Cause 鏈
| 症狀 | 斷點 | 根因 |
|---|---|---|
| Telegram 400 BUTTON_DATA_INVALID | generate_callback_nonce |
UUID(36) + action(20) + ts(10) + rand(8) + colons = 77B > 64B Telegram 限制 |
| Gitea PR "AI 分析失敗" | _call_openclaw_code_review |
OpenClaw 只有 /analyze/incident 和 /analyze/error;/analyze/code-review 從未實作(404) |
| Push review AttributeError | _call_openclaw_push_review |
local_code_review_service.review_push() 回傳 dict,呼叫端對 dict 做屬性訪問(analysis.issues) |
修復
- nonce 壓縮
security_interceptor.py—generate_callback_nonce用 base64url encode UUID bytes(36→22 chars);parse_callback_data對應 decode;host_restart_servicenonce = 63B - code review 改 local
gitea_webhook_service.py—_call_openclaw_code_review改用local_code_review_service.review_pr()(Ollama + Gemini fallback) - push review dict→model
gitea_webhook_service.py—_call_openclaw_push_review加 dict→CodeReviewResult轉換
E2E 驗證(2026-04-21 21:57 台北)
host_restart_servicenonce = 63B ✓,所有 7 個 actions ≤ 64B ✓- UUID round-trip decode = True ✓
telegram_approval_card_sentmessage_id=25045(SignOzDown devops_tool)✓ 無 BUTTON_DATA_INVALID
Commits
acab1cdfix(gitea): code-review 改 local service + push review dict→CodeReviewResultbd73548fix(telegram): BUTTON_DATA_INVALID nonce 超 64B 根因修復8fd31ecfix(telegram): nonce UUID base64url 壓縮(徹底解決)