Files
checkbot/app/alerts/analyzer.py
T
patrickandClaude Sonnet 5.5 9c4f7366eb
test / pytest (3.11) (push) Waiting to run
test / pytest (3.13) (push) Waiting to run
feat: Phase 6 prompt versioning with test area, retention
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-06 02:17:01 +01:00

71 lines
2.6 KiB
Python

import asyncio
import logging
from dataclasses import dataclass
from typing import Callable
from ..constants import ALERTS_AI_PER_MINUTE, DEFAULT_AI_PER_MINUTE, DEFAULT_OLLAMA_MODEL, OLLAMA_MODEL_ALERT
from ..models import AI_DONE, AI_FAILED, AI_SKIPPED, Event
from ..ollama import OllamaClient
from ..poller.state import StateChange, RECENT_CHANGES_DEFAULT
from .formatter import clip_ai_text
from .prompts import build_messages
from .ratelimit import SlidingWindowLimiter
log = logging.getLogger(__name__)
ERROR_MAX = 200
PromptProvider = Callable[[], str | None]
def alert_model(store) -> str:
return store.get(OLLAMA_MODEL_ALERT) or DEFAULT_OLLAMA_MODEL
@dataclass(frozen=True)
class AnalysisResult:
status: str
text: str | None = None
class AlertAnalyzer:
def __init__(self, store, ollama_provider: Callable[[], OllamaClient | None],
history_provider: Callable[[str, str, int], list[StateChange]],
limiter: SlidingWindowLimiter, prompt_provider: PromptProvider | None = None):
self._prompt = prompt_provider or (lambda: None)
self._store = store
self._ollama = ollama_provider
self._history = history_provider
self._limiter = limiter
async def analyze(self, event: Event) -> AnalysisResult:
client = self._ollama()
if client is None:
return AnalysisResult(AI_SKIPPED)
try:
limit = self._store.get_int(ALERTS_AI_PER_MINUTE, DEFAULT_AI_PER_MINUTE)
if not self._limiter.try_acquire(limit):
return AnalysisResult(AI_SKIPPED)
return await self._ask(client, event)
finally:
await client.aclose()
def _editable_prompt(self) -> str | None:
try:
return self._prompt()
except Exception:
log.warning("Alarm-Prompt nicht lesbar, Standardtext wird verwendet", exc_info=True)
return None
async def _ask(self, client: OllamaClient, event: Event) -> AnalysisResult:
history = await asyncio.to_thread(self._history, event.host, event.service, RECENT_CHANGES_DEFAULT)
editable = await asyncio.to_thread(self._editable_prompt)
messages = build_messages(editable, event, history)
model = alert_model(self._store)
try:
reply = await client.chat(model, messages)
text = clip_ai_text(str(reply.get("content", "")))
except Exception as exc:
log.warning("KI-Analyse fehlgeschlagen: %s", f"{type(exc).__name__}: {exc}"[:ERROR_MAX])
return AnalysisResult(AI_FAILED)
return AnalysisResult(AI_DONE, text) if text else AnalysisResult(AI_FAILED)