core: Registry, Skills, Router, Executor, Verifier, Runner mit Tests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
"""Chat-Client für das OpenAI-kompatible API von LiteLLM."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Protocol
|
||||
|
||||
import httpx
|
||||
|
||||
from .problems import is_context_overflow_message
|
||||
|
||||
|
||||
class LLMError(Exception):
|
||||
"""Basisklasse; `infra=True` bedeutet: Endpunkt/Netz, nicht Modellqualität."""
|
||||
|
||||
infra = True
|
||||
|
||||
|
||||
class EndpointError(LLMError):
|
||||
pass
|
||||
|
||||
|
||||
class ContextOverflowError(LLMError):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass
|
||||
class ChatResult:
|
||||
content: str
|
||||
finish_reason: str | None = None
|
||||
usage: dict[str, Any] = field(default_factory=dict)
|
||||
model: str | None = None
|
||||
|
||||
|
||||
class ChatClient(Protocol):
|
||||
def chat(self, model: str, messages: list[dict[str, str]], *, max_tokens: int | None = None,
|
||||
temperature: float | None = None) -> ChatResult: ...
|
||||
|
||||
|
||||
class OpenAIChatClient:
|
||||
def __init__(self, base_url: str, api_key: str = "", timeout: float = 120.0,
|
||||
client: httpx.Client | None = None):
|
||||
self.base_url = base_url.rstrip("/")
|
||||
self.headers = {"Authorization": f"Bearer {api_key}"} if api_key else {}
|
||||
self.client = client or httpx.Client(timeout=timeout)
|
||||
|
||||
def chat(self, model: str, messages: list[dict[str, str]], *, max_tokens: int | None = None,
|
||||
temperature: float | None = None) -> ChatResult:
|
||||
body: dict[str, Any] = {"model": model, "messages": messages}
|
||||
if max_tokens is not None:
|
||||
body["max_tokens"] = max_tokens
|
||||
if temperature is not None:
|
||||
body["temperature"] = temperature
|
||||
try:
|
||||
resp = self.client.post(f"{self.base_url}/v1/chat/completions", json=body, headers=self.headers)
|
||||
except httpx.HTTPError as exc:
|
||||
raise EndpointError(f"{type(exc).__name__}: {exc}") from exc
|
||||
if resp.status_code == 400 and is_context_overflow_message(resp.text):
|
||||
raise ContextOverflowError(resp.text[:500])
|
||||
if resp.status_code == 429 or resp.status_code >= 500:
|
||||
raise EndpointError(f"HTTP {resp.status_code}: {resp.text[:300]}")
|
||||
if resp.status_code >= 400:
|
||||
raise EndpointError(f"HTTP {resp.status_code}: {resp.text[:300]}")
|
||||
data = resp.json()
|
||||
choice = data["choices"][0]
|
||||
return ChatResult(
|
||||
content=choice["message"].get("content") or "",
|
||||
finish_reason=choice.get("finish_reason"),
|
||||
usage=data.get("usage") or {},
|
||||
model=data.get("model"),
|
||||
)
|
||||
Reference in New Issue
Block a user