Files
awoooi/docs/adr/ADR-092-ai-decision-llm-expansion-layer.md
Your Name e2742ce9f3 docs: BUTTON_DATA_INVALID 根治 + Gitea Code Review 修復 記錄
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>
2026-04-21 21:59:00 +08:00

205 lines
9.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 個新 scanner8 張 0 writer 表全活化),首席架構師 Review 發現嚴重 Gap
- **AI 層淺**14 個新 scanner 中 **8 個純 thresholdrule-based**,只 Hermes 1 個真用 LLM 做決策
- **autonomy_score 實測**63/100starter 起步級),`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 Taipeipredict_linear 命中)| 分析 7d 容量預測高風險 host 的修復策略 |
| **compliance_scanner** | `posture_grade` | 每日 03:00 Taipei有 warning/violation| 產整體 compliance posture 評級 + top 3 action |
| **coverage_evaluator** | `worst_dimension` | 每 1hred_ratio > 30% 且 scanned > 50| 分析 7 維 red 缺口 + 補覆蓋建議 |
### 2. LLM 調用統一 patternservices/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_keysystem 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 張新表)
---
## 附錄 A2026-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}` 24h3 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
---
## 附錄 B2026-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。
---
## 附錄 C2026-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 bytes36→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=25045SignOzDown 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 壓縮(徹底解決)