feat(ai): Phase 22 OpenClaw + Nemotron 協作架構 (ADR-044)
All checks were successful
E2E Health Check / e2e-health (push) Successful in 17s

統帥批准實作「仲裁-執行分工」架構:
- OpenClaw = 仲裁者 (Why + Risk Level)
- Nemotron = 執行者 (How + kubectl Command)

新增功能:
- config.py: ENABLE_NEMOTRON_COLLABORATION Feature Flag
- openclaw.py: generate_incident_proposal_with_tools()
- openclaw.py: _call_nemotron_tools() Nemotron 呼叫
- telegram_gateway.py: TelegramMessage Nemotron 欄位
- telegram_gateway.py: format_with_nemotron() 雙區塊格式
- decision_manager.py: 整合協作方法
- proposal_service.py: 整合協作方法

觸發條件:
- LOW 風險 → 僅 OpenClaw
- MEDIUM/HIGH/CRITICAL → OpenClaw + Nemotron 雙軌

首席架構師審查: 83/100 條件通過

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
OG T
2026-03-31 18:52:53 +08:00
parent e7e3fc8e00
commit dd526684ab
9 changed files with 1080 additions and 9 deletions

View File

@@ -1383,6 +1383,282 @@ Focus on:
)
return None, provider, False
# =========================================================================
# Phase 22: OpenClaw + Nemotron 協作 (ADR-044)
# 2026-03-31 Claude Code: 統帥批准實作
# =========================================================================
async def generate_incident_proposal_with_tools(
self,
incident_id: str,
severity: str,
signals: list[dict],
affected_services: list[str],
expert_context: dict | None = None,
) -> tuple[dict | None, str, bool]:
"""
Phase 22: OpenClaw + Nemotron 協作生成修復提案
架構:
- OpenClaw = 仲裁者 (Arbitrator) - 決定「為什麼」和「風險等級」
- Nemotron = 執行者 (Executor) - 決定「怎麼做」和「具體指令」
觸發條件:
- LOW 風險 → 僅 OpenClaw跳過 Nemotron
- MEDIUM/HIGH/CRITICAL → OpenClaw + Nemotron 雙軌
Args:
incident_id: Incident ID
severity: 嚴重度 (P0/P1/P2/P3)
signals: 關聯的告警訊號
affected_services: 受影響服務
expert_context: Expert System 初步診斷 (可選)
Returns:
(proposal_dict, provider, success)
proposal_dict 新增:
- nemotron_enabled: bool
- nemotron_tools: list[dict] (如果啟用)
- nemotron_validation: str
- nemotron_latency_ms: float
"""
# Feature Flag 檢查
if not settings.ENABLE_NEMOTRON_COLLABORATION:
logger.info(
"nemotron_collaboration_disabled",
incident_id=incident_id,
reason="Feature flag disabled",
)
return await self.generate_incident_proposal(
incident_id, severity, signals, affected_services, expert_context
)
# Step 1: OpenClaw 仲裁
proposal, provider, success = await self.generate_incident_proposal(
incident_id, severity, signals, affected_services, expert_context
)
if not success or proposal is None:
return proposal, provider, success
# Step 2: 判斷是否需要 Nemotron
risk_level = proposal.get("risk_level", "low").lower()
if risk_level == "low":
proposal["nemotron_enabled"] = False
logger.info(
"nemotron_skipped_low_risk",
incident_id=incident_id,
risk_level=risk_level,
)
return proposal, provider, True
# Step 3: 呼叫 Nemotron Tool Calling
logger.info(
"nemotron_collaboration_start",
incident_id=incident_id,
risk_level=risk_level,
)
try:
nemotron_result = await self._call_nemotron_tools(
incident_id=incident_id,
reasoning=proposal.get("reasoning", ""),
target_resource=proposal.get("target_resource", ""),
suggested_action=proposal.get("action", ""),
namespace=proposal.get("namespace", "awoooi-prod"),
)
proposal["nemotron_enabled"] = True
proposal["nemotron_tools"] = nemotron_result.get("tools", [])
proposal["nemotron_validation"] = nemotron_result.get("validation", "⏳ 驗證中")
proposal["nemotron_latency_ms"] = nemotron_result.get("latency_ms", 0.0)
logger.info(
"nemotron_collaboration_complete",
incident_id=incident_id,
tools_count=len(proposal["nemotron_tools"]),
validation=proposal["nemotron_validation"],
latency_ms=proposal["nemotron_latency_ms"],
)
except Exception as e:
# Nemotron 失敗不阻塞主流程,降級為純 OpenClaw
logger.warning(
"nemotron_collaboration_failed",
incident_id=incident_id,
error=str(e),
)
proposal["nemotron_enabled"] = False
proposal["nemotron_tools"] = None
proposal["nemotron_validation"] = "❌ 呼叫失敗"
proposal["nemotron_latency_ms"] = 0.0
return proposal, provider, True
async def _call_nemotron_tools(
self,
incident_id: str,
reasoning: str,
target_resource: str,
suggested_action: str,
namespace: str = "awoooi-prod",
) -> dict:
"""
呼叫 Nemotron 執行 Tool Calling
Args:
incident_id: Incident ID
reasoning: OpenClaw 推理結果
target_resource: 目標資源名稱
suggested_action: OpenClaw 建議的操作
namespace: K8s namespace
Returns:
{
"tools": [{"tool": str, "args": dict, "valid": bool}],
"validation": str,
"latency_ms": float
}
"""
import asyncio
from src.services.nvidia_provider import get_nvidia_provider
nvidia = get_nvidia_provider()
start_time = time.time()
# 建構 Tool Calling prompt
tool_prompt = f"""根據以下 AI 分析結果,生成對應的 kubectl 操作指令:
## Incident 上下文
- Incident ID: {incident_id}
- 目標資源: {target_resource}
- Namespace: {namespace}
## OpenClaw 分析
- 建議操作: {suggested_action}
- 推理過程: {reasoning[:500]}
## 你的任務
生成最適合的 kubectl 操作。如果操作有風險,請標註驗證步驟。
"""
# 定義可用 Tools (K8s 操作)
k8s_tools = [
{
"type": "function",
"function": {
"name": "restart_deployment",
"description": "重啟 Deployment (rollout restart)",
"parameters": {
"type": "object",
"properties": {
"deployment_name": {"type": "string"},
"namespace": {"type": "string", "default": "awoooi-prod"},
},
"required": ["deployment_name"],
},
},
},
{
"type": "function",
"function": {
"name": "scale_deployment",
"description": "調整 Deployment 副本數",
"parameters": {
"type": "object",
"properties": {
"deployment_name": {"type": "string"},
"replicas": {"type": "integer"},
"namespace": {"type": "string", "default": "awoooi-prod"},
},
"required": ["deployment_name", "replicas"],
},
},
},
{
"type": "function",
"function": {
"name": "delete_pod",
"description": "刪除 Pod (強制重建)",
"parameters": {
"type": "object",
"properties": {
"pod_name": {"type": "string"},
"namespace": {"type": "string", "default": "awoooi-prod"},
},
"required": ["pod_name"],
},
},
},
]
try:
# 設置超時
timeout = settings.NEMOTRON_TIMEOUT_SECONDS
result = await asyncio.wait_for(
nvidia.tool_call(
messages=[{"role": "user", "content": tool_prompt}],
tools=k8s_tools,
),
timeout=timeout,
)
latency_ms = (time.time() - start_time) * 1000
# 解析 Tool Calling 結果
tools = []
validation_passed = True
if result and hasattr(result, "tool_calls") and result.tool_calls:
for tc in result.tool_calls:
tool_entry = {
"tool": tc.tool_name if hasattr(tc, "tool_name") else str(tc.get("name", "unknown")),
"args": tc.arguments if hasattr(tc, "arguments") else tc.get("arguments", {}),
"valid": tc.valid if hasattr(tc, "valid") else True,
}
tools.append(tool_entry)
if not tool_entry["valid"]:
validation_passed = False
elif result and isinstance(result, dict) and result.get("tool_calls"):
for tc in result["tool_calls"]:
tool_entry = {
"tool": tc.get("name", "unknown"),
"args": tc.get("arguments", {}),
"valid": True,
}
tools.append(tool_entry)
validation_status = "✅ 驗證通過" if validation_passed and tools else "❌ 驗證失敗"
return {
"tools": tools,
"validation": validation_status,
"latency_ms": latency_ms,
}
except asyncio.TimeoutError:
latency_ms = (time.time() - start_time) * 1000
logger.warning(
"nemotron_tool_call_timeout",
incident_id=incident_id,
timeout_seconds=settings.NEMOTRON_TIMEOUT_SECONDS,
)
return {
"tools": [],
"validation": "⏳ 呼叫超時",
"latency_ms": latency_ms,
}
except Exception as e:
latency_ms = (time.time() - start_time) * 1000
logger.error(
"nemotron_tool_call_error",
incident_id=incident_id,
error=str(e),
)
raise
# =========================================================================
# Shadow Mode Auto-Tuning
# =========================================================================