generated from coulomb/repo-seed
Implement llm-connect ADHOC diagnostics
This commit is contained in:
@@ -1,86 +1,101 @@
|
||||
"""
|
||||
Thin synchronous HTTP helper built on :mod:`urllib.request`.
|
||||
|
||||
Translates HTTP errors into typed :mod:`markitect.llm.exceptions`.
|
||||
"""
|
||||
|
||||
import json
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
from llm_connect.exceptions import (
|
||||
LLMAPIError,
|
||||
LLMRateLimitError,
|
||||
LLMTimeoutError,
|
||||
)
|
||||
|
||||
|
||||
def post_json(
|
||||
url: str,
|
||||
payload: Dict[str, Any],
|
||||
headers: Optional[Dict[str, str]] = None,
|
||||
timeout: int = 300,
|
||||
) -> Dict[str, Any]:
|
||||
"""POST *payload* as JSON and return the parsed response body.
|
||||
|
||||
Raises:
|
||||
LLMRateLimitError: on HTTP 429
|
||||
LLMAPIError: on other non-2xx responses
|
||||
LLMTimeoutError: on socket / read timeout
|
||||
"""
|
||||
data = json.dumps(payload).encode()
|
||||
req = urllib.request.Request(
|
||||
url,
|
||||
data=data,
|
||||
headers={"Content-Type": "application/json", **(headers or {})},
|
||||
method="POST",
|
||||
)
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
body = resp.read().decode()
|
||||
try:
|
||||
return json.loads(body)
|
||||
except json.JSONDecodeError as exc:
|
||||
preview = body[:300].replace("\n", "\\n")
|
||||
raise LLMAPIError(
|
||||
f"Invalid JSON response from {url}: {exc} — body preview: {preview!r}",
|
||||
cause=exc,
|
||||
) from exc
|
||||
except urllib.error.HTTPError as exc:
|
||||
body = ""
|
||||
try:
|
||||
body = exc.read().decode()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if exc.code == 429:
|
||||
raise LLMRateLimitError(
|
||||
f"Rate limited (429) from {url}",
|
||||
status_code=429,
|
||||
response_body=body,
|
||||
cause=exc,
|
||||
) from exc
|
||||
|
||||
raise LLMAPIError(
|
||||
f"HTTP {exc.code} from {url}",
|
||||
status_code=exc.code,
|
||||
response_body=body,
|
||||
cause=exc,
|
||||
) from exc
|
||||
except urllib.error.URLError as exc:
|
||||
if "timed out" in str(exc.reason):
|
||||
raise LLMTimeoutError(
|
||||
f"Request to {url} timed out after {timeout}s",
|
||||
cause=exc,
|
||||
) from exc
|
||||
raise LLMAPIError(
|
||||
f"URL error for {url}: {exc.reason}",
|
||||
cause=exc,
|
||||
) from exc
|
||||
except TimeoutError as exc:
|
||||
raise LLMTimeoutError(
|
||||
f"Request to {url} timed out after {timeout}s",
|
||||
cause=exc,
|
||||
) from exc
|
||||
"""
|
||||
Thin synchronous HTTP helper built on :mod:`urllib.request`.
|
||||
|
||||
Translates HTTP errors into typed :mod:`markitect.llm.exceptions`.
|
||||
"""
|
||||
|
||||
import json
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from llm_connect._diagnostics import record_provider_request, record_provider_response
|
||||
from llm_connect.exceptions import (
|
||||
LLMAPIError,
|
||||
LLMRateLimitError,
|
||||
LLMTimeoutError,
|
||||
)
|
||||
|
||||
|
||||
def post_json(
|
||||
url: str,
|
||||
payload: Dict[str, Any],
|
||||
headers: Optional[Dict[str, str]] = None,
|
||||
timeout: int = 300,
|
||||
) -> Dict[str, Any]:
|
||||
"""POST *payload* as JSON and return the parsed response body.
|
||||
|
||||
Raises:
|
||||
LLMRateLimitError: on HTTP 429
|
||||
LLMAPIError: on other non-2xx responses
|
||||
LLMTimeoutError: on socket / read timeout
|
||||
"""
|
||||
record_provider_request(url=url, payload=payload, headers=headers or {})
|
||||
data = json.dumps(payload).encode()
|
||||
req = urllib.request.Request(
|
||||
url,
|
||||
data=data,
|
||||
headers={"Content-Type": "application/json", **(headers or {})},
|
||||
method="POST",
|
||||
)
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
body = resp.read().decode()
|
||||
try:
|
||||
parsed = json.loads(body)
|
||||
record_provider_response(status=resp.status, body=parsed)
|
||||
return parsed
|
||||
except json.JSONDecodeError as exc:
|
||||
record_provider_response(status=resp.status, body=body)
|
||||
preview = body[:300].replace("\n", "\\n")
|
||||
raise LLMAPIError(
|
||||
f"Invalid JSON response from {url}: {exc} - body preview: {preview!r}",
|
||||
cause=exc,
|
||||
) from exc
|
||||
except urllib.error.HTTPError as exc:
|
||||
body = ""
|
||||
try:
|
||||
body = exc.read().decode()
|
||||
except Exception:
|
||||
pass
|
||||
record_provider_response(status=exc.code, body=_json_or_text(body))
|
||||
|
||||
if exc.code == 429:
|
||||
raise LLMRateLimitError(
|
||||
f"Rate limited (429) from {url}",
|
||||
status_code=429,
|
||||
response_body=body,
|
||||
cause=exc,
|
||||
) from exc
|
||||
|
||||
raise LLMAPIError(
|
||||
f"HTTP {exc.code} from {url}",
|
||||
status_code=exc.code,
|
||||
response_body=body,
|
||||
cause=exc,
|
||||
) from exc
|
||||
except urllib.error.URLError as exc:
|
||||
record_provider_response(body={"error": str(exc.reason)})
|
||||
if "timed out" in str(exc.reason):
|
||||
raise LLMTimeoutError(
|
||||
f"Request to {url} timed out after {timeout}s",
|
||||
cause=exc,
|
||||
) from exc
|
||||
raise LLMAPIError(
|
||||
f"URL error for {url}: {exc.reason}",
|
||||
cause=exc,
|
||||
) from exc
|
||||
except TimeoutError as exc:
|
||||
record_provider_response(body={"error": "timeout"})
|
||||
raise LLMTimeoutError(
|
||||
f"Request to {url} timed out after {timeout}s",
|
||||
cause=exc,
|
||||
) from exc
|
||||
|
||||
|
||||
def _json_or_text(body: str) -> Any:
|
||||
try:
|
||||
return json.loads(body)
|
||||
except (TypeError, ValueError):
|
||||
return body
|
||||
|
||||
Reference in New Issue
Block a user