diff --git a/apps/api/src/services/openclaw.py b/apps/api/src/services/openclaw.py index c82ac436f..411583774 100644 --- a/apps/api/src/services/openclaw.py +++ b/apps/api/src/services/openclaw.py @@ -461,6 +461,65 @@ class OpenClawService: logger.warning("claude_call_failed", error=str(e)) return str(e), False + async def _call_nvidia(self, prompt: str) -> tuple[str, bool, int, float]: + """ + 呼叫 NVIDIA Nemotron (OpenAI 相容格式) + + 2026-03-29 ogt: 新增 Nemotron 一般告警支援 (非 Tool Calling) + + Returns: + tuple: (response_text, success, total_tokens, cost_usd) + """ + if not settings.NVIDIA_API_KEY: + return "NVIDIA_API_KEY not configured", False, 0, 0.0 + + try: + client = await self._get_client() + + # Nemotron 模型 + model_name = "nvidia/llama-3.1-nemotron-70b-instruct" + + response = await client.post( + "https://integrate.api.nvidia.com/v1/chat/completions", + headers={ + "Authorization": f"Bearer {settings.NVIDIA_API_KEY}", + "Content-Type": "application/json", + }, + json={ + "model": model_name, + "messages": [{"role": "user", "content": prompt}], + "temperature": 0.1, + "max_tokens": 2048, + "response_format": {"type": "json_object"}, # 強制 JSON + }, + timeout=60.0, + ) + response.raise_for_status() + data = response.json() + + text = data["choices"][0]["message"]["content"] + + # Token 用量 + usage = data.get("usage", {}) + prompt_tokens = usage.get("prompt_tokens", 0) + completion_tokens = usage.get("completion_tokens", 0) + total_tokens = usage.get("total_tokens", prompt_tokens + completion_tokens) + + # NVIDIA NIM 免費 tier = $0 + cost_usd = 0.0 + + logger.info( + "nvidia_response_received", + response_length=len(text), + total_tokens=total_tokens, + cost_usd=f"${cost_usd:.6f}", + ) + return text, True, total_tokens, cost_usd + + except Exception as e: + logger.warning("nvidia_call_failed", error=str(e)) + return str(e), False, 0, 0.0 + # ========================================================================= # Mock LLM - Intelligent Fallback with SignOz Data # ========================================================================= @@ -876,6 +935,9 @@ class OpenClawService: response, success = await self._call_ollama(prompt) elif provider == "gemini": response, success, total_tokens, cost_usd = await self._call_gemini(prompt) + elif provider == "nvidia": + # 2026-03-29 ogt: Nemotron 一般告警支援 + response, success, total_tokens, cost_usd = await self._call_nvidia(prompt) elif provider == "claude": response, success = await self._call_claude(prompt) else: diff --git a/k8s/awoooi-prod/04-configmap.yaml b/k8s/awoooi-prod/04-configmap.yaml index d828d061a..6890c92d1 100644 --- a/k8s/awoooi-prod/04-configmap.yaml +++ b/k8s/awoooi-prod/04-configmap.yaml @@ -1,8 +1,8 @@ # AWOOOI 正式環境 ConfigMap # 負責人: CIO # 版本: v1.1 -# 日期: 2026-03-27 (台北時區) -# 變更: Gemini 優先 (臨時,待 Ollama CPU 問題修復後切回) +# 日期: 2026-03-29 (台北時區) +# 變更: Gemini 優先 + Token/Cost 追蹤 apiVersion: v1 kind: ConfigMap @@ -34,10 +34,10 @@ data: CORS_ORIGINS: '["https://awoooi.wooo.work","http://localhost:3000","http://localhost:3001"]' # AI 配置 (JSON array 格式 for pydantic-settings) - # 2026-03-29 ogt: 切換 Gemini 優先 (確認 AI 仲裁運作正常) - # 備援順序: Gemini(~$0.001) → Ollama($0) → Claude(~$0.008) + # 2026-03-29 ogt: Gemini 優先 + Nvidia 備援 + Token/Cost 追蹤 + # 備援順序: Gemini(~$0.001) → Nvidia($0 免費tier) → Ollama($0) → Claude(~$0.008) # Rate Limiter: RPM=10, 每日 500 次, Token 100K - AI_FALLBACK_ORDER: '["gemini","ollama","claude"]' + AI_FALLBACK_ORDER: '["gemini","nvidia","ollama","claude"]' AI_CACHE_TTL: "3600" # 快取 TTL (秒)