81 lines
2.7 KiB
Python
81 lines
2.7 KiB
Python
import asyncio
|
|
from typing import Any, Mapping, Optional, Type, TypeVar
|
|
|
|
import httpx
|
|
from pydantic import BaseModel
|
|
|
|
from ..errors.remote_call_error import RemoteCallError
|
|
from ..views import Trace
|
|
|
|
T = TypeVar("T", bound=BaseModel)
|
|
|
|
|
|
async def call_remote_great_ai_async(
|
|
base_uri: str,
|
|
data: Mapping[str, Any],
|
|
retry_count: int = 4,
|
|
timeout_in_seconds: Optional[int] = 300,
|
|
model_class: Optional[Type[T]] = None,
|
|
) -> Trace[T]:
|
|
"""Communicate with a GreatAI object through an HTTP request.
|
|
|
|
Send a POST request using [httpx](https://www.python-httpx.org/) to implement a
|
|
remote call. Transport failures are retried up to `retry_count` times. A retry
|
|
may repeat a prediction when its previous response was lost.
|
|
|
|
The return value is inflated into a Trace. If `model_class` is specified, the
|
|
original output is deserialised.
|
|
|
|
Args:
|
|
base_uri: Address of the remote instance, example: 'http://localhost:6060'
|
|
data: The input sent as a json to the remote instance.
|
|
retry_count: Retry on any HTTP communication failure.
|
|
timeout_in_seconds: Overall permissible max length of the request. `None` means
|
|
no timeout.
|
|
model_class: A subtype of BaseModel to be used for deserialising the `.output`
|
|
of the trace.
|
|
"""
|
|
|
|
if base_uri.endswith("/"):
|
|
base_uri = base_uri[:-1]
|
|
|
|
if not base_uri.endswith("/predict"):
|
|
base_uri = f"{base_uri}/predict"
|
|
|
|
if retry_count < 0:
|
|
raise ValueError("retry_count must be non-negative")
|
|
transport = httpx.AsyncHTTPTransport(retries=0)
|
|
|
|
try:
|
|
async with httpx.AsyncClient(
|
|
transport=transport, timeout=timeout_in_seconds
|
|
) as client:
|
|
for attempt in range(retry_count + 1):
|
|
try:
|
|
response = await client.post(base_uri, json=data)
|
|
break
|
|
except httpx.TransportError:
|
|
if attempt == retry_count:
|
|
raise
|
|
await asyncio.sleep(min(0.1 * 2**attempt, 1))
|
|
try:
|
|
response.raise_for_status()
|
|
except Exception:
|
|
raise RemoteCallError(
|
|
f"Unexpected status code, reason: {response.text}"
|
|
)
|
|
except Exception as e:
|
|
raise RemoteCallError(str(e)) from e
|
|
|
|
try:
|
|
trace = response.json()
|
|
except Exception:
|
|
raise RemoteCallError(
|
|
f"JSON parsing failed {response.text}",
|
|
)
|
|
try:
|
|
if model_class is not None:
|
|
trace["output"] = model_class.model_validate(trace["output"])
|
|
return Trace.model_validate(trace)
|
|
except Exception:
|
|
raise RemoteCallError("Could not parse Trace")
|