feat(infra): Phase 2.1 Gemma 4 알림 자연어 설명
이상 감지 시 Gemma 4(MLX localhost:8801)로 원인 분석 + 권장 조치 생성. Gemma 실패해도 rule 결과만으로 알림 전송 (graceful degradation). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+49
-6
@@ -3,10 +3,9 @@
|
|||||||
5분마다 실행되어:
|
5분마다 실행되어:
|
||||||
1. core/ 함수로 상태 수집
|
1. core/ 함수로 상태 수집
|
||||||
2. Rule 기반 1차 판정 (threshold/패턴)
|
2. Rule 기반 1차 판정 (threshold/패턴)
|
||||||
3. 이상 감지 시 → 시놀로지 Chat 알림
|
3. 이상 감지 시 → Gemma 4가 자연어 설명 생성
|
||||||
4. 로그 → stdout (launchd가 캡처)
|
4. 시놀로지 Chat 알림 (rule 요약 + Gemma 설명)
|
||||||
|
5. 로그 → stdout (launchd가 캡처)
|
||||||
Gemma 4는 Phase 2.1에서 추가 (알림 메시지 자연어 생성).
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@@ -54,6 +53,40 @@ IGNORED_CONTAINERS = {
|
|||||||
# Services to health-check
|
# Services to health-check
|
||||||
HEALTH_SERVICES = ["document-server", "mlx", "ollama-gpu"]
|
HEALTH_SERVICES = ["document-server", "mlx", "ollama-gpu"]
|
||||||
|
|
||||||
|
# Gemma 4 — Mac mini MLX proxy (localhost on Mac mini)
|
||||||
|
GEMMA_URL = "http://localhost:8801/v1/chat/completions"
|
||||||
|
GEMMA_MODEL = "mlx-community/gemma-4-26b-a4b-it-8bit"
|
||||||
|
GEMMA_TIMEOUT = 30 # seconds
|
||||||
|
|
||||||
|
|
||||||
|
# --- Gemma ---
|
||||||
|
|
||||||
|
async def generate_explanation(alerts: list[str]) -> str:
|
||||||
|
"""Ask Gemma 4 to explain alerts and suggest actions. Returns empty on failure."""
|
||||||
|
prompt = (
|
||||||
|
"당신은 서버 인프라 모니터링 AI입니다. "
|
||||||
|
"아래 이상 항목들을 분석해서 간결하게 설명하고 권장 조치를 알려주세요.\n\n"
|
||||||
|
"이상 항목:\n" + "\n".join(f"- {a}" for a in alerts) + "\n\n"
|
||||||
|
"형식: 1~3문장으로 원인 분석 + 권장 조치. 한국어로."
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient(timeout=GEMMA_TIMEOUT) as client:
|
||||||
|
resp = await client.post(
|
||||||
|
GEMMA_URL,
|
||||||
|
json={
|
||||||
|
"model": GEMMA_MODEL,
|
||||||
|
"messages": [{"role": "user", "content": prompt}],
|
||||||
|
"max_tokens": 200,
|
||||||
|
"temperature": 0.3,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if resp.status_code == 200:
|
||||||
|
data = resp.json()
|
||||||
|
return data["choices"][0]["message"]["content"].strip()
|
||||||
|
except Exception:
|
||||||
|
log.debug("Gemma 설명 생성 실패 — rule 결과만 전송", exc_info=True)
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
# --- Alert ---
|
# --- Alert ---
|
||||||
|
|
||||||
@@ -179,8 +212,18 @@ async def run_checks() -> None:
|
|||||||
for a in all_alerts:
|
for a in all_alerts:
|
||||||
log.warning(" - %s", a)
|
log.warning(" - %s", a)
|
||||||
|
|
||||||
# Send combined alert
|
# Gemma 2nd: generate explanation
|
||||||
message = f"이상 감지 {len(all_alerts)}건:\n" + "\n".join(f"- {a}" for a in all_alerts)
|
explanation = await generate_explanation(all_alerts)
|
||||||
|
if explanation:
|
||||||
|
log.info("Gemma 설명: %s", explanation[:100])
|
||||||
|
|
||||||
|
# Build alert message
|
||||||
|
lines = [f"이상 감지 {len(all_alerts)}건:"]
|
||||||
|
lines.extend(f"- {a}" for a in all_alerts)
|
||||||
|
if explanation:
|
||||||
|
lines.append(f"\n분석: {explanation}")
|
||||||
|
message = "\n".join(lines)
|
||||||
|
|
||||||
await send_alert(message)
|
await send_alert(message)
|
||||||
else:
|
else:
|
||||||
log.info("전체 정상")
|
log.info("전체 정상")
|
||||||
|
|||||||
Reference in New Issue
Block a user