iris-pressure-studio / iris /http_client.py
Codex
Deploy submission links to Hugging Face Space
200cb0b
Raw
History Blame Contribute Delete
2.72 kB
"""Small OpenAI-compatible chat completions client using the standard library."""
from __future__ import annotations
import json
from typing import Any
import urllib.error
import urllib.request
from iris.config import IrisConfig
from iris.errors import IrisAPIError
class ChatCompletionsClient:
"""Calls a /v1/chat/completions-compatible endpoint."""
def __init__(self, config: IrisConfig):
self.config = config
def complete(
self, messages: list[dict[str, str]], temperature: float | None = None
) -> str:
payload = {
"model": self.config.model,
"messages": messages,
"temperature": (
self.config.temperature if temperature is None else temperature
),
"max_tokens": self.config.max_tokens,
}
response = self._post_json("/chat/completions", payload)
try:
content = response["choices"][0]["message"]["content"]
except (KeyError, IndexError, TypeError) as exc:
raise IrisAPIError(f"Unexpected chat completion response: {response!r}") from exc
if not isinstance(content, str) or not content.strip():
raise IrisAPIError(
"Model endpoint returned no final message content. "
"Increase IRIS_MAX_TOKENS or use a non-thinking model."
)
return content
def _post_json(self, path: str, payload: dict[str, Any]) -> dict[str, Any]:
url = f"{self.config.api_base_url}{path}"
headers = {"Content-Type": "application/json"}
if self.config.api_key:
headers["Authorization"] = f"Bearer {self.config.api_key}"
request = urllib.request.Request(
url,
data=json.dumps(payload).encode("utf-8"),
headers=headers,
method="POST",
)
try:
with urllib.request.urlopen(
request, timeout=self.config.timeout_seconds
) as response:
body = response.read().decode("utf-8")
except urllib.error.HTTPError as exc:
detail = exc.read().decode("utf-8", errors="replace")[:1000]
raise IrisAPIError(f"HTTP {exc.code} from model endpoint: {detail}") from exc
except urllib.error.URLError as exc:
raise IrisAPIError(f"Could not reach model endpoint: {exc.reason}") from exc
except TimeoutError as exc:
raise IrisAPIError("Timed out waiting for model endpoint response") from exc
try:
return json.loads(body)
except json.JSONDecodeError as exc:
raise IrisAPIError(f"Endpoint returned non-JSON response: {body[:1000]}") from exc