| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219 |
- # -*- coding: utf-8 -*-
- """
- Ollama Backend für NLP.
- Verwendet Ollama für lokale LLM-Inferenz.
- Ollama verwaltet Modelle und bietet eine REST-API.
- """
- import asyncio
- import json
- from typing import Any
- from trixy_core.nlp import NLPConfig, NLPState
- from trixy_core.utils.debug import pinfo, pdebug, perror, pwarn
- from plugins.nlp_llm.provider import LLMNLPProvider
- class OllamaNLPBackend(LLMNLPProvider):
- """
- Ollama Backend.
- Kommuniziert mit dem Ollama-Server über die REST-API.
- Ollama muss separat installiert und gestartet sein.
- """
- DEFAULT_HOST = "http://localhost:11434"
- def __init__(self) -> None:
- super().__init__()
- self._host = self.DEFAULT_HOST
- self._model_name = ""
- self._session = None
- async def _initialize_backend(self, config: NLPConfig) -> bool:
- """
- Initialisiert das Ollama Backend.
- Prüft Verbindung zum Ollama-Server und ob das Modell verfügbar ist.
- Args:
- config: NLP-Konfiguration
- Returns:
- True bei Erfolg
- """
- self._host = config.extra.get("ollama_host", self.DEFAULT_HOST)
- self._model_name = config.model_name
- # HTTP-Client erstellen
- try:
- import aiohttp
- self._session = aiohttp.ClientSession()
- except ImportError:
- perror("aiohttp nicht installiert. Installieren mit: pip install aiohttp")
- return False
- # Verbindung testen
- if not await self._check_connection():
- perror(f"Keine Verbindung zum Ollama-Server: {self._host}")
- pwarn("Stellen Sie sicher, dass Ollama läuft: ollama serve")
- return False
- # Prüfen ob Modell verfügbar
- if not await self._check_model():
- pwarn(f"Modell '{self._model_name}' nicht gefunden, versuche Pull...")
- if not await self._pull_model():
- perror(f"Modell konnte nicht geladen werden: {self._model_name}")
- return False
- pinfo(f"Ollama Backend initialisiert: {self._model_name}")
- return True
- async def _check_connection(self) -> bool:
- """Prüft Verbindung zum Ollama-Server."""
- try:
- async with self._session.get(f"{self._host}/api/tags", timeout=5) as resp:
- return resp.status == 200
- except Exception:
- return False
- async def _check_model(self) -> bool:
- """Prüft ob das Modell verfügbar ist."""
- try:
- async with self._session.get(f"{self._host}/api/tags") as resp:
- if resp.status != 200:
- return False
- data = await resp.json()
- models = data.get("models", [])
- for model in models:
- name = model.get("name", "")
- # Prüfe exakten Namen oder ohne Tag
- if name == self._model_name or name.split(":")[0] == self._model_name:
- return True
- return False
- except Exception as e:
- pdebug(f"Fehler beim Prüfen des Modells: {e}")
- return False
- async def _pull_model(self) -> bool:
- """Lädt das Modell von Ollama herunter."""
- try:
- pinfo(f"Lade Modell: {self._model_name} (dies kann einige Zeit dauern)")
- async with self._session.post(
- f"{self._host}/api/pull",
- json={"name": self._model_name, "stream": False},
- timeout=None, # Kein Timeout für Download
- ) as resp:
- if resp.status == 200:
- pinfo(f"Modell geladen: {self._model_name}")
- return True
- error = await resp.text()
- perror(f"Fehler beim Laden des Modells: {error}")
- return False
- except Exception as e:
- perror(f"Fehler beim Laden des Modells: {e}")
- return False
- async def _generate(self, messages: list[dict[str, str]]) -> str:
- """
- Generiert eine Antwort über die Ollama API.
- Args:
- messages: Nachrichtenliste
- Returns:
- Generierte Antwort
- """
- if self._session is None:
- raise RuntimeError("Ollama-Session nicht initialisiert")
- try:
- payload = {
- "model": self._model_name,
- "messages": messages,
- "stream": False,
- "options": {
- "temperature": self._config.temperature if self._config else 0.1,
- "num_predict": self._config.max_tokens if self._config else 256,
- "num_ctx": self._config.context_window if self._config else 2048,
- "num_thread": self._config.num_threads if self._config else 4,
- },
- }
- async with self._session.post(
- f"{self._host}/api/chat",
- json=payload,
- timeout=60,
- ) as resp:
- if resp.status != 200:
- error = await resp.text()
- raise RuntimeError(f"Ollama API Fehler: {error}")
- data = await resp.json()
- message = data.get("message", {})
- return message.get("content", "").strip()
- except asyncio.TimeoutError:
- raise RuntimeError("Ollama Timeout - Anfrage dauerte zu lange")
- except Exception as e:
- raise RuntimeError(f"Ollama Fehler: {e}")
- async def _shutdown_backend(self) -> None:
- """Schließt die HTTP-Session."""
- if self._session is not None:
- await self._session.close()
- self._session = None
- pdebug("Ollama-Session geschlossen")
- async def health_check(self) -> bool:
- """Prüft ob Ollama erreichbar ist."""
- return await self._check_connection() and await self._check_model()
- def supports_streaming(self) -> bool:
- """Ollama unterstützt Streaming."""
- return True
- async def generate_stream(self, messages: list[dict[str, str]]):
- """
- Generiert eine Antwort mit Streaming.
- Args:
- messages: Nachrichtenliste
- Yields:
- Text-Chunks
- """
- if self._session is None:
- raise RuntimeError("Ollama-Session nicht initialisiert")
- payload = {
- "model": self._model_name,
- "messages": messages,
- "stream": True,
- "options": {
- "temperature": self._config.temperature if self._config else 0.1,
- "num_predict": self._config.max_tokens if self._config else 256,
- },
- }
- async with self._session.post(
- f"{self._host}/api/chat",
- json=payload,
- ) as resp:
- async for line in resp.content:
- if line:
- try:
- data = json.loads(line)
- content = data.get("message", {}).get("content", "")
- if content:
- yield content
- except json.JSONDecodeError:
- continue
|