# 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)` ```python # 統一 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 架構約束(鐵律) **必須遵守**: 1. **失敗永不 raise**:LLM 掛掉/parse 失敗 → return None → 呼叫者 fallback 硬編規則 2. **AI 只建議不動作**:所有 LLM 輸出都寫 `requires_human_decision=True`,推 Telegram 等人工 3. **openclaw 統一入口**:不直接呼叫 Ollama/NVIDIA/Gemini,走 `get_openclaw().call(prompt)` 多 provider fallback 4. **aol 留痕**:LLM 結果進 `automation_operation_log.output.llm_analysis` 5. **繁中 + 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 比例 × 20 - `rule_quality`: 1 - (noisy/total) × 20 - `capacity_health`: critical 扣 10 / warning 扣 3 - `automation_flow`: log10(24h_ops) 標準化 × 20 - `ai_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 - `ba18ad2` Hermes LLM 升級(Hermes 從 threshold 改 LLM) - `d6b854a` Gap 3.1 capacity_forecaster LLM - `f6cb938` Gap 3.2 compliance_scanner LLM - `2f5cab2` Gap 3.3 coverage_evaluator LLM - `fa643eb` P1 重構: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.py` - `apps/api/src/services/aiops_kpi_service.py`(autonomy_score 計算) - `apps/api/src/api/v1/aiops_kpi.py`(KPI endpoint) --- ## 後續工作(下 session) ### P1 剩餘優化(首席架構師 Review 指出) 1. **Prometheus HTTP helper 抽出** — 5 scanner 重複 httpx + 錯誤處理 pattern 2. **14 scanner first_delay 錯開** — 避免並行啟動搶 DB pool(建議 60/90/120/180 階梯) 3. **LLM budget guard** — `aiops_kpi_service` 加 `llm_call_count_24h` 指標超過閾值 Telegram 告警 4. **asset_scanner 918 行拆分** — `providers/` + `writers/` 兩層 ### 可擴展 LLM 方向 - `rule_catalog_sync`: 新規則 import 時 LLM 驗證 expr 合理性 - `asset_change_tracker`: 重大 lifecycle 變化時 LLM 評估爆炸半徑 - `drift_interpreter` (既有): 升級更精細 prompt --- ## 回滾計畫 若 LLM service 造成成本或穩定性問題,可分層關閉: 1. 改 config `AIOPS_LLM_ENABLED=false`(需新增,目前未實作) 2. 或 kubectl exec 進 Pod `kill` 單個 loop task 3. 各 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) 1. **B1** `playbook_service.py` — `update_with_validation` setattr 前強制 enum 轉型 2. **B2a** `approval_db.py` — `find_by_fingerprint` debounce 窗外 PENDING 必須 Redis `tg_sent:{fp}` 確認;新 `mark_telegram_confirmed()` 3. **B2b** `webhooks.py` — `_push_to_telegram_background` 成功後 `setex tg_sent:{fp}` 24h;3 call site 傳 fingerprint 4. **Drift** `drift_adopt_service.py` — 新 `adopt_drift(report_id)` wrapper 5. **B3** `jobs/ai_slo_watchdog_job.py` — 新建:每 15 分鐘 W-1/W-2/W-3 → TYPE-8M 6. **B3** `main.py` lifespan 註冊 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`)| ### 修復 1. **nonce 壓縮** `security_interceptor.py` — `generate_callback_nonce` 用 base64url encode UUID bytes(36→22 chars);`parse_callback_data` 對應 decode;`host_restart_service` nonce = 63B 2. **code review 改 local** `gitea_webhook_service.py` — `_call_openclaw_code_review` 改用 `local_code_review_service.review_pr()`(Ollama + Gemini fallback) 3. **push review dict→model** `gitea_webhook_service.py` — `_call_openclaw_push_review` 加 dict→`CodeReviewResult` 轉換 ### E2E 驗證(2026-04-21 21:57 台北) - `host_restart_service` nonce = 63B ✓,所有 7 個 actions ≤ 64B ✓ - UUID round-trip decode = True ✓ - `telegram_approval_card_sent` message_id=25045(SignOzDown devops_tool)✓ 無 BUTTON_DATA_INVALID ### Commits - `acab1cd` fix(gitea): code-review 改 local service + push review dict→CodeReviewResult - `bd73548` fix(telegram): BUTTON_DATA_INVALID nonce 超 64B 根因修復 - `8fd31ec` fix(telegram): nonce UUID base64url 壓縮(徹底解決)