feat: add local chat provider and CLI
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
"""Ollama implementation of the local model provider contract."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import socket
|
||||
from urllib.error import HTTPError, URLError
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
from javis.providers.base import (
|
||||
ChatMessage,
|
||||
InvalidProviderResponseError,
|
||||
ModelNotInstalledError,
|
||||
ProviderTimeoutError,
|
||||
ProviderUnavailableError,
|
||||
ResponseAbortedError,
|
||||
)
|
||||
|
||||
|
||||
class OllamaProvider:
|
||||
name = "ollama"
|
||||
|
||||
def __init__(self, model: str, base_url: str, timeout_seconds: float) -> None:
|
||||
self.model = model
|
||||
self._endpoint = f"{base_url.rstrip('/')}/api/chat"
|
||||
self._timeout_seconds = timeout_seconds
|
||||
|
||||
def chat(self, messages: list[ChatMessage]) -> str:
|
||||
payload = {
|
||||
"model": self.model,
|
||||
"messages": [
|
||||
{"role": message.role, "content": message.content} for message in messages
|
||||
],
|
||||
"stream": False,
|
||||
"think": False,
|
||||
}
|
||||
request = Request(
|
||||
self._endpoint,
|
||||
data=json.dumps(payload).encode("utf-8"),
|
||||
headers={"Content-Type": "application/json"},
|
||||
method="POST",
|
||||
)
|
||||
|
||||
try:
|
||||
with urlopen(request, timeout=self._timeout_seconds) as response:
|
||||
raw_response = response.read()
|
||||
except HTTPError as exc:
|
||||
details = exc.read().decode("utf-8", errors="replace")
|
||||
if exc.code == 404 or "not found" in details.lower():
|
||||
raise ModelNotInstalledError(
|
||||
f"Das lokale Modell '{self.model}' ist nicht installiert."
|
||||
) from exc
|
||||
raise ProviderUnavailableError(f"Ollama meldet HTTP-Fehler {exc.code}.") from exc
|
||||
except TimeoutError as exc:
|
||||
raise ProviderTimeoutError(
|
||||
"Die Modellantwort hat das Zeitlimit überschritten."
|
||||
) from exc
|
||||
except URLError as exc:
|
||||
if isinstance(exc.reason, (TimeoutError, socket.timeout)):
|
||||
raise ProviderTimeoutError(
|
||||
"Die Modellantwort hat das Zeitlimit überschritten."
|
||||
) from exc
|
||||
raise ProviderUnavailableError(
|
||||
"Ollama ist unter der konfigurierten lokalen Adresse nicht erreichbar."
|
||||
) from exc
|
||||
|
||||
try:
|
||||
result = json.loads(raw_response)
|
||||
except (json.JSONDecodeError, UnicodeDecodeError) as exc:
|
||||
raise InvalidProviderResponseError(
|
||||
"Ollama hat keine gültige JSON-Antwort geliefert."
|
||||
) from exc
|
||||
|
||||
if result.get("done") is False:
|
||||
raise ResponseAbortedError("Die Modellantwort wurde vorzeitig abgebrochen.")
|
||||
|
||||
content = result.get("message", {}).get("content")
|
||||
if not isinstance(content, str) or not content.strip():
|
||||
raise InvalidProviderResponseError(
|
||||
"Ollama hat keine verwendbare Textantwort geliefert."
|
||||
)
|
||||
return content.strip()
|
||||
Reference in New Issue
Block a user