71 lines
2.6 KiB
Python
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)
|