# ADR-044: OpenClaw + Nemotron 協作架構 > **狀態**: ✅ **已批准** > **決策日期**: 2026-03-31 > **批准日期**: 2026-03-31 18:30 (台北時區) > **決策者**: 首席架構師 + 統帥 > **提案者**: Claude Code > **相關**: ADR-036 Nemotron Tool Calling, Phase 18 自動修復 ## 背景 AWOOOI 目前有兩個 AI 能力: 1. **OpenClaw** - 主要大腦,負責 Root Cause Analysis、風險評估、決策推理 2. **Nemotron** - Tool Calling 專家,83.3% 精準度執行 K8s 操作 統帥需求:在同一個 Telegram 中同時看到兩者的分析結果。 ## 問題陳述 如何讓兩個 AI 在 Telegram 中協作,而不會: - 訊息混亂(誰說了什麼?) - 責任不清(誰做的決策?) - 無限迴圈(互相觸發) - 增加過多延遲 ## 決策 ### 採用「仲裁-執行分工」架構 ``` OpenClaw = 仲裁者 (Arbitrator) - 決定「為什麼」和「風險等級」 Nemotron = 執行者 (Executor) - 決定「怎麼做」和「具體指令」 ``` ### 職責分離 | 角色 | OpenClaw | Nemotron | |------|----------|----------| | **任務** | Root Cause Analysis | Tool Calling | | **輸出** | 風險等級 + 責任團隊 + 原因推理 | kubectl 指令 + 參數驗證 | | **模型** | Ollama/Gemini (RCA 任務) | Nemotron-mini (Tool 任務) | | **信心度** | 0-100% (AI 分析品質) | 驗證狀態 (✅/❌) | | **備援** | Expert System 規則 | Gemini Tool Calling | ### 流程設計 ``` 1. Incident 產生 ↓ 2. OpenClaw.generate_incident_proposal() → 輸出: risk_level, reasoning, primary_responsibility ↓ 3. 判斷是否需要 Nemotron ├─ LOW 風險 → 跳過 Nemotron └─ MEDIUM/HIGH/CRITICAL → 呼叫 Nemotron ↓ 4. NvidiaProvider.tool_call() → 輸出: tool_name, arguments, validation_status ↓ 5. 組合結果 → 推送 Telegram 卡片 ↓ 6. 用戶簽核 → 執行 ``` ### 觸發條件 | 風險等級 | OpenClaw | Nemotron | 原因 | |----------|----------|----------|------| | LOW | ✅ | ❌ | 低風險操作不需要 Tool 驗證 | | MEDIUM | ✅ | ✅ | 需要 Tool 驗證操作可行性 | | HIGH | ✅ | ✅ | 高風險必須雙重驗證 | | CRITICAL | ✅ | ✅ + HITL | 危險操作必須人工介入 | ## 實作規格 ### 1. 擴展 TelegramMessage ```python @dataclass class TelegramMessage: # 現有欄位... # 新增 Nemotron 結果欄位 nemotron_enabled: bool = False nemotron_tools: list[dict] | None = None # Tool Calling 結果 nemotron_validation: str = "" # "✅ 驗證通過" / "❌ 驗證失敗" nemotron_latency_ms: float = 0.0 ``` ### 2. 擴展 generate_incident_proposal ```python async def generate_incident_proposal_with_tools( self, incident_id: str, severity: str, signals: list[dict], affected_services: list[str], ) -> tuple[dict | None, str, bool]: """ Phase 22: OpenClaw + Nemotron 協作 Returns: (proposal_dict, provider, success) proposal_dict 新增: - nemotron_tools: Tool Calling 結果 - nemotron_validation: 驗證狀態 """ # Step 1: OpenClaw 仲裁 proposal, provider, success = await self.generate_incident_proposal( incident_id, severity, signals, affected_services ) if not success: return proposal, provider, success # Step 2: 判斷是否需要 Nemotron risk_level = proposal.get("risk_level", "low").lower() if risk_level == "low": proposal["nemotron_enabled"] = False return proposal, provider, True # Step 3: Nemotron Tool Calling from src.services.nvidia_provider import get_nvidia_provider nvidia = get_nvidia_provider() tool_result = await nvidia.tool_call( messages=[{ "role": "user", "content": f""" 根據以下分析,生成對應的 kubectl 操作: - Incident: {incident_id} - 原因: {proposal.get('reasoning', '')} - 目標資源: {proposal.get('target_resource', '')} - 建議操作: {proposal.get('action', '')} """ }], tools=K8S_OPERATION_TOOLS, ) # Step 4: 驗證 Tool Calling 結果 validation = await self._validate_tool_calls(tool_result.tool_calls) proposal["nemotron_enabled"] = True proposal["nemotron_tools"] = [ {"tool": tc.tool_name, "args": tc.arguments, "valid": tc.valid} for tc in tool_result.tool_calls ] proposal["nemotron_validation"] = validation proposal["nemotron_latency_ms"] = tool_result.latency_ms return proposal, provider, True ``` ### 3. Telegram 卡片格式 ```python def format_with_nemotron(self) -> str: """格式化含 Nemotron 結果的訊息""" # OpenClaw 區塊 openclaw_block = f""" 🤖 OpenClaw 仲裁 ├ 📊 信心: {self.confidence_emoji} {self.confidence_pct}% ├ 👥 責任: {self.primary_responsibility} └ 💡 原因: {self.root_cause[:50]} """ # Nemotron 區塊 (如果啟用) nemotron_block = "" if self.nemotron_enabled and self.nemotron_tools: tools_str = "\n".join([ f" {'✅' if t['valid'] else '❌'} {t['tool']}: {t['args'][:30]}" for t in self.nemotron_tools[:3] # 最多顯示 3 個 ]) nemotron_block = f""" ━━━━━━━━━━━━━━━━━━━ 🔧 Nemotron 執行方案 {tools_str} └ 驗證: {self.nemotron_validation} """ return f"{openclaw_block}{nemotron_block}" ``` ### 4. 異步執行 (非阻塞) ```python async def _push_decision_to_telegram_async( incident: Incident, proposal_data: dict, ) -> None: """ 異步推送,不阻塞主流程 Phase 22: 如果 Nemotron 延遲過長 (>10s),先推送 OpenClaw 結果, Nemotron 結果後續用 edit_message 更新 """ # 先推送 OpenClaw 結果 message_id = await gateway.send_approval_card( # ... OpenClaw 結果 ) # 如果需要 Nemotron,異步執行並更新 if proposal_data.get("risk_level") in ["medium", "high", "critical"]: asyncio.create_task( _update_with_nemotron_result(message_id, incident, proposal_data) ) ``` ## 後果 ### 正面 - **清晰分工**: OpenClaw 和 Nemotron 職責明確 - **可追蹤**: 每個 AI 的貢獻獨立顯示 - **容錯性**: 備援鏈清晰 (Nemotron → Gemini → Expert) - **效能**: 低風險操作不觸發 Nemotron,節省延遲 ### 負面 - **延遲增加**: 高風險操作需要兩輪 LLM - **複雜度**: 訊息格式需要擴展 ### 風險緩解 | 風險 | 緩解 | |------|------| | Nemotron 延遲 11-45s | 異步執行,先推送 OpenClaw 結果 | | Tool Calling 失敗 | Fallback 到 Gemini,再失敗則只顯示 OpenClaw | | 訊息超長 | 縮寫 Tool 參數,完整內容放 SignOz Link | ## 併發控制 (與 ADR-038 整合) > **首席架構師 P1 必修項** (2026-03-31) ### 雙 Semaphore 策略 ```python # apps/api/src/core/circuit_breaker.py 擴展 class OpenClawGuard: def __init__(self): self.openclaw_semaphore = asyncio.Semaphore(3) # 原有 self.nemotron_semaphore = asyncio.Semaphore(2) # 新增 (NVIDIA API 較慢) ``` **設計原因**: - Nemotron 併發限制為 2 (低於 OpenClaw 的 3) - NVIDIA NIM 免費 tier 有 RPM 限制 - Nemotron 延遲較高 (11-45s),過多並發無益 ### 並行執行優化 ```python # Step 3 優化: OpenClaw + Nemotron 並行而非串行 import asyncio async def generate_incident_proposal_with_tools(...): # 並行啟動 OpenClaw 和 Nemotron (減少延遲) openclaw_task = asyncio.create_task( self.generate_incident_proposal(incident_id, severity, signals, affected_services) ) # 先等待 OpenClaw 完成,判斷是否需要 Nemotron proposal, provider, success = await openclaw_task if not success or proposal.get("risk_level", "low").lower() == "low": return proposal, provider, success # 需要 Nemotron - 此時 OpenClaw 已完成,立即啟動 Nemotron nemotron_result = await self._call_nemotron_tools(proposal) # 組合結果 return self._combine_results(proposal, nemotron_result), provider, True ``` **延遲對比**: | 場景 | 串行 | 並行 | 改善 | |------|------|------|------| | MEDIUM 風險 | 3s + 15s = 18s | max(3s, 15s) = 15s | -3s | | HIGH 風險 | 5s + 30s = 35s | max(5s, 30s) = 30s | -5s | --- ## Circuit Breaker 整合 ### 雙層 Circuit Breaker 協調 ``` ┌─────────────────────────────────────────┐ │ OpenClawGuard (ADR-038) │ │ - 管理請求佇列 │ │ - 長期熔斷 (5 分鐘) │ └─────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────┐ │ NvidiaProvider.CircuitBreaker │ │ - NVIDIA API 短期熔斷 (60s) │ │ - 失敗 3 次後 OPEN │ └─────────────────────────────────────────┘ ``` ### 熔斷策略 | 層級 | 觸發條件 | 恢復時間 | 影響 | |------|---------|---------|------| | OpenClawGuard | 佇列滿 (>10) | 5 分鐘 | 停止新請求 | | NvidiaProvider | 連續 3 失敗 | 60 秒 | Fallback 到 Gemini | --- ## Feature Flag 支援 > **首席架構師 P1 必修項** ### 環境變數 ```bash # 啟用/停用 Nemotron 協作 (預設 true) ENABLE_NEMOTRON_COLLABORATION=true # Nemotron 呼叫超時 (預設 45s) NEMOTRON_TIMEOUT_SECONDS=45 # 強制使用異步更新 (先推 OpenClaw,後更新 Nemotron) NEMOTRON_ASYNC_UPDATE=true ``` ### 回滾計畫 ```python async def generate_incident_proposal_with_tools(...): # Feature Flag 檢查 if not settings.ENABLE_NEMOTRON_COLLABORATION: return await self.generate_incident_proposal(...) # 原流程 # ... 協作邏輯 ``` **回滾步驟**: 1. 設置 `ENABLE_NEMOTRON_COLLABORATION=false` 2. Rollout restart awoooi-api 3. 無需代碼回滾 --- ## DI 模式重構 > **首席架構師 P1 必修項** - 避免函數內 import ### 修改前 (❌ 違反 DI) ```python # Step 3: Nemotron Tool Calling from src.services.nvidia_provider import get_nvidia_provider # ❌ 函數內 import nvidia = get_nvidia_provider() ``` ### 修改後 (✅ DI 模式) ```python # apps/api/src/services/openclaw.py from src.services.nvidia_provider import INvidiaProvider class OpenClawService: def __init__( self, nvidia_provider: INvidiaProvider | None = None, # DI 注入 ): self._nvidia = nvidia_provider or get_nvidia_provider() async def generate_incident_proposal_with_tools( self, incident_id: str, severity: str, signals: list[dict], affected_services: list[str], ) -> tuple[dict | None, str, bool]: # ... 使用 self._nvidia 而非 import ``` --- ## 測試策略 ### E2E 測試案例 ```python # tests/test_openclaw_nemotron_collaboration.py @pytest.mark.asyncio async def test_low_risk_skips_nemotron(): """LOW 風險不觸發 Nemotron""" result = await openclaw.generate_incident_proposal_with_tools(...) assert result[0]["nemotron_enabled"] is False @pytest.mark.asyncio async def test_medium_risk_enables_nemotron(): """MEDIUM 風險啟用 Nemotron""" result = await openclaw.generate_incident_proposal_with_tools(...) assert result[0]["nemotron_enabled"] is True assert result[0]["nemotron_tools"] is not None @pytest.mark.asyncio async def test_nemotron_failure_fallback(): """Nemotron 失敗時 fallback 到 Gemini""" # Mock NVIDIA 失敗 with patch("nvidia_provider.tool_call", side_effect=Exception): result = await openclaw.generate_incident_proposal_with_tools(...) # 應該有結果 (來自 Gemini fallback) assert result[2] is True @pytest.mark.asyncio async def test_feature_flag_disabled(): """Feature Flag 停用時走原流程""" with patch.dict(os.environ, {"ENABLE_NEMOTRON_COLLABORATION": "false"}): result = await openclaw.generate_incident_proposal_with_tools(...) assert "nemotron_enabled" not in result[0] ``` ### 整合測試 ```python @pytest.mark.integration async def test_telegram_message_with_nemotron(): """Telegram 訊息包含 Nemotron 區塊""" msg = TelegramMessage( nemotron_enabled=True, nemotron_tools=[{"tool": "restart_deployment", "args": {...}, "valid": True}], ) formatted = msg.format_with_nemotron() assert "Nemotron 執行方案" in formatted assert "✅ restart_deployment" in formatted ``` --- ## 實作排程 (詳細) | 階段 | 內容 | 時間 | 檔案 | 依賴 | |------|------|------|------|------| | **22.1** | TelegramMessage 擴展 | 2h | `telegram_gateway.py` | 無 | | **22.2a** | OpenClawGuard 雙 Semaphore | 1h | `circuit_breaker.py` | 無 | | **22.2b** | DI 模式重構 | 1h | `openclaw.py` | 22.2a | | **22.2c** | `generate_incident_proposal_with_tools` | 2h | `openclaw.py` | 22.2a, 22.2b | | **22.3a** | Feature Flag 支援 | 1h | `config.py` | 無 | | **22.3b** | 異步推送邏輯 | 2h | `decision_manager.py` | 22.1, 22.2c | | **22.4a** | 單元測試 | 2h | `test_openclaw_nemotron*.py` | 22.2c | | **22.4b** | E2E 測試 | 2h | `test_e2e_collaboration.py` | 22.3b | | **總計** | | **13h (~1.5 天)** | | | --- ## 首席架構師審查結論 > **審查日期**: 2026-03-31 (台北時區) > **分數**: 83/100 → **條件通過** ### P1 必修項 (已補充) | 編號 | 項目 | 狀態 | |------|------|------| | P1-1 | 併發控制整合 | ✅ 已補充 | | P1-2 | DI 模式 | ✅ 已補充 | | P1-3 | Feature Flag | ✅ 已補充 | ### P2 建議項 (後續迭代) | 編號 | 項目 | 說明 | |------|------|------| | P2-1 | 並行優化 | 已納入設計 | | P2-2 | Pydantic Model | Phase 22.5 | | P2-3 | NemotronBlock | Phase 22.5 | --- ## 相關文件 - ADR-036: Nemotron Tool Calling 整合 - ADR-038: OpenClaw 併發治理 - Phase 18: 失敗自動修復閉環 - `feedback_ai_rate_limiter.md`: AI 用量控制 --- **Co-Authored-By: Claude Opus 4.5 **