fix(chat): OpenClaw 改走 Gemini Flash,移除 Ollama 依賴
All checks were successful
CD Pipeline / build-and-deploy (push) Successful in 7m18s
All checks were successful
CD Pipeline / build-and-deploy (push) Successful in 7m18s
Ollama 188 完全卡死 (0 bytes/30s timeout),無法作為對話後端。 雙 AI 皆使用 Gemini Flash,靠不同 persona 和 temperature 區分: - OpenClaw: temperature=0.5 (精準果斷) - NemoClaw: temperature=0.9 (分析發散) 同時 kubectl set env ENABLE_NEMOTRON_COLLABORATION=false 停止每個 incident 白白等待 30s Nemotron timeout Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,13 +5,13 @@ Phase 21.5 初版: 2026-03-31 ogt
|
||||
Phase 22.6 重寫: 2026-04-03 ogt (統帥需求: 雙 AI 互動對話)
|
||||
|
||||
功能:
|
||||
1. @openclaw / @nemo 路由 — 指定 AI 回應
|
||||
2. 無前綴 — 兩個 AI 輪流回應,並互相評論
|
||||
3. AI 互相對話 — NemoClaw 看到 OpenClaw 的回應後可補充/反駁
|
||||
1. @openclaw <msg> → 只有 OpenClaw 回應
|
||||
2. @nemo <msg> → 只有 NemoClaw 回應
|
||||
3. 無前綴 → OpenClaw 先答,NemoClaw 評論/反駁
|
||||
|
||||
架構:
|
||||
- OpenClaw: 用 Ollama qwen2.5:7b-instruct (本地, 快)
|
||||
- NemoClaw: 用 Gemini Flash (雲端, 快) — NIM nemotron-mini 太慢 (15s+)
|
||||
後端:
|
||||
- 雙 AI 皆用 Gemini Flash,靠不同 persona 區分人格
|
||||
- Ollama 188 目前卡死 (0 bytes/30s),待主機重啟後可切換回來
|
||||
"""
|
||||
|
||||
import structlog
|
||||
@@ -21,7 +21,6 @@ from src.repositories.incident_repository import get_incident_repository
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
# 人格設定
|
||||
OPENCLAW_PERSONA = """你是 OpenClaw,AWOOOI 平台的 SRE AI 主帥。
|
||||
個性: 精準、果斷、專業,像老將一樣直接給出建議。
|
||||
語氣: 簡短有力,不廢話。繁體中文回應。
|
||||
@@ -65,43 +64,26 @@ class ChatManager:
|
||||
except Exception:
|
||||
incident_summary = "無法取得告警"
|
||||
|
||||
return f"""## 系統狀態 ({now.strftime('%Y-%m-%d %H:%M')} 台北)
|
||||
- {cluster_info}
|
||||
- 活躍告警: {incident_summary}
|
||||
"""
|
||||
return (
|
||||
f"## 系統狀態 ({now.strftime('%Y-%m-%d %H:%M')} 台北)\n"
|
||||
f"- {cluster_info}\n"
|
||||
f"- 活躍告警: {incident_summary}\n"
|
||||
)
|
||||
|
||||
async def _call_ollama(self, system_prompt: str, user_message: str) -> str:
|
||||
"""呼叫 Ollama (OpenClaw 用)"""
|
||||
import httpx
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=30.0) as client:
|
||||
resp = await client.post(
|
||||
"http://192.168.0.188:11434/api/chat",
|
||||
json={
|
||||
"model": "qwen2.5:7b-instruct",
|
||||
"messages": [
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": user_message},
|
||||
],
|
||||
"stream": False,
|
||||
"options": {"temperature": 0.7, "num_predict": 512},
|
||||
},
|
||||
)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
return data.get("message", {}).get("content", "").strip()
|
||||
except Exception as e:
|
||||
logger.warning("ollama_chat_failed", error=str(e))
|
||||
return None
|
||||
async def _call_gemini(self, system_prompt: str, user_message: str, temperature: float = 0.7) -> str | None:
|
||||
"""
|
||||
呼叫 Gemini Flash
|
||||
|
||||
async def _call_gemini(self, system_prompt: str, user_message: str) -> str:
|
||||
"""呼叫 Gemini Flash (NemoClaw 用)"""
|
||||
2026-04-03 ogt: 雙 AI 皆走 Gemini,用不同 persona 區分
|
||||
OpenClaw temperature=0.5 (精準), NemoClaw temperature=0.9 (發散)
|
||||
"""
|
||||
import httpx
|
||||
from src.core.config import get_settings
|
||||
settings = get_settings()
|
||||
|
||||
api_key = settings.GEMINI_API_KEY if hasattr(settings, 'GEMINI_API_KEY') else None
|
||||
api_key = getattr(settings, 'GEMINI_API_KEY', None)
|
||||
if not api_key:
|
||||
logger.warning("gemini_api_key_not_set")
|
||||
return None
|
||||
|
||||
try:
|
||||
@@ -111,7 +93,7 @@ class ChatManager:
|
||||
f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key={api_key}",
|
||||
json={
|
||||
"contents": [{"role": "user", "parts": [{"text": full_prompt}]}],
|
||||
"generationConfig": {"temperature": 0.8, "maxOutputTokens": 512},
|
||||
"generationConfig": {"temperature": temperature, "maxOutputTokens": 512},
|
||||
},
|
||||
)
|
||||
resp.raise_for_status()
|
||||
@@ -122,37 +104,27 @@ class ChatManager:
|
||||
return None
|
||||
|
||||
async def _openclaw_respond(self, context: str, message: str) -> str:
|
||||
"""OpenClaw 回應"""
|
||||
system = f"{OPENCLAW_PERSONA}\n{context}"
|
||||
result = await self._call_ollama(system, message)
|
||||
"""OpenClaw 回應 (Gemini + OpenClaw persona, temperature=0.5)"""
|
||||
result = await self._call_gemini(f"{OPENCLAW_PERSONA}\n{context}", message, temperature=0.5)
|
||||
if not result:
|
||||
result = "🔴 OpenClaw 暫時離線,Ollama 無響應。"
|
||||
result = "🔴 OpenClaw 暫時無法回應。"
|
||||
return f"🦞 <b>OpenClaw:</b>\n{result}"
|
||||
|
||||
async def _nemoclaw_respond(self, context: str, message: str) -> str:
|
||||
"""NemoClaw 回應"""
|
||||
system = f"{NEMOCLAW_PERSONA}\n{context}"
|
||||
result = await self._call_gemini(system, message)
|
||||
"""NemoClaw 回應 (Gemini + NemoClaw persona, temperature=0.9)"""
|
||||
result = await self._call_gemini(f"{NEMOCLAW_PERSONA}\n{context}", message, temperature=0.9)
|
||||
if not result:
|
||||
# Gemini 失敗時 fallback 到 Ollama
|
||||
result = await self._call_ollama(system, message)
|
||||
if not result:
|
||||
result = "🔴 NemoClaw 暫時離線。"
|
||||
result = "🔴 NemoClaw 暫時無法回應。"
|
||||
return f"🤖 <b>NemoClaw:</b>\n{result}"
|
||||
|
||||
async def _nemoclaw_comment_on(self, context: str, openclaw_response: str, original_msg: str) -> str:
|
||||
async def _nemoclaw_comment_on(self, context: str, openclaw_response: str, original_msg: str) -> str | None:
|
||||
"""NemoClaw 評論 OpenClaw 的回應"""
|
||||
message = f"""統帥問了: {original_msg}
|
||||
|
||||
OpenClaw 的回應是:
|
||||
{openclaw_response}
|
||||
|
||||
請你從 NemoClaw 的角度評論上面的回應。可以補充、反駁、或提出不同觀點。"""
|
||||
|
||||
system = f"{NEMOCLAW_PERSONA}\n{context}"
|
||||
result = await self._call_gemini(system, message)
|
||||
if not result:
|
||||
result = await self._call_ollama(system, message)
|
||||
message = (
|
||||
f"統帥問了: {original_msg}\n\n"
|
||||
f"OpenClaw 剛才回應:\n{openclaw_response}\n\n"
|
||||
f"請從 NemoClaw 角度評論。可以補充、反駁、或提出不同觀點。簡短有力。"
|
||||
)
|
||||
result = await self._call_gemini(f"{NEMOCLAW_PERSONA}\n{context}", message, temperature=0.9)
|
||||
if not result:
|
||||
return None
|
||||
return f"🤖 <b>NemoClaw 補充:</b>\n{result}"
|
||||
@@ -168,7 +140,7 @@ OpenClaw 的回應是:
|
||||
|
||||
@openclaw <msg> → 只有 OpenClaw 回應
|
||||
@nemo <msg> → 只有 NemoClaw 回應
|
||||
其他 → OpenClaw 先回,NemoClaw 評論
|
||||
其他 → OpenClaw 先回,NemoClaw 補充/反駁
|
||||
"""
|
||||
context = await self.get_system_context()
|
||||
text = message_text.strip()
|
||||
@@ -183,27 +155,24 @@ OpenClaw 的回應是:
|
||||
msg = text[5:].strip() or text
|
||||
return await self._nemoclaw_respond(context, msg)
|
||||
|
||||
# 模式 3: 雙 AI 對話
|
||||
# Step 1: OpenClaw 先回
|
||||
openclaw_raw = await self._call_ollama(
|
||||
f"{OPENCLAW_PERSONA}\n{context}", text
|
||||
# 模式 3: 雙 AI 對話 — OpenClaw 先,NemoClaw 評論
|
||||
openclaw_raw = await self._call_gemini(
|
||||
f"{OPENCLAW_PERSONA}\n{context}", text, temperature=0.5
|
||||
)
|
||||
if not openclaw_raw:
|
||||
openclaw_raw = "Ollama 無響應,OpenClaw 暫時離線。"
|
||||
openclaw_raw = "Gemini 無響應,OpenClaw 暫時離線。"
|
||||
|
||||
openclaw_block = f"🦞 <b>OpenClaw:</b>\n{openclaw_raw}"
|
||||
|
||||
# Step 2: NemoClaw 評論 OpenClaw 的回應
|
||||
nemo_block = await self._nemoclaw_comment_on(context, openclaw_raw, text)
|
||||
|
||||
if nemo_block:
|
||||
return f"{openclaw_block}\n\n{nemo_block}"
|
||||
else:
|
||||
return openclaw_block
|
||||
return openclaw_block
|
||||
|
||||
|
||||
# Singleton
|
||||
_chat_manager = None
|
||||
_chat_manager: ChatManager | None = None
|
||||
|
||||
|
||||
def get_chat_manager() -> ChatManager:
|
||||
|
||||
Reference in New Issue
Block a user