大活
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
from importlib.metadata import version
|
||||
|
||||
__version__ = version(__package__ if __package__ is not None else "replicate")
|
||||
@@ -0,0 +1,23 @@
|
||||
from replicate.client import Client
|
||||
from replicate.pagination import async_paginate as _async_paginate
|
||||
from replicate.pagination import paginate as _paginate
|
||||
|
||||
default_client = Client()
|
||||
|
||||
run = default_client.run
|
||||
async_run = default_client.async_run
|
||||
|
||||
stream = default_client.stream
|
||||
async_stream = default_client.async_stream
|
||||
|
||||
paginate = _paginate
|
||||
async_paginate = _async_paginate
|
||||
|
||||
collections = default_client.collections
|
||||
deployments = default_client.deployments
|
||||
files = default_client.files
|
||||
hardware = default_client.hardware
|
||||
models = default_client.models
|
||||
predictions = default_client.predictions
|
||||
trainings = default_client.trainings
|
||||
webhooks = default_client.webhooks
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,57 @@
|
||||
from typing import Any, Dict, Literal, Optional
|
||||
|
||||
from replicate.resource import Namespace, Resource
|
||||
|
||||
|
||||
class Account(Resource):
|
||||
"""
|
||||
A user or organization account on Replicate.
|
||||
"""
|
||||
|
||||
type: Literal["user", "organization"]
|
||||
"""The type of account."""
|
||||
|
||||
username: str
|
||||
"""The username of the account."""
|
||||
|
||||
name: str
|
||||
"""The name of the account."""
|
||||
|
||||
github_url: Optional[str]
|
||||
"""The GitHub URL of the account."""
|
||||
|
||||
|
||||
class Accounts(Namespace):
|
||||
"""
|
||||
Namespace for operations related to accounts.
|
||||
"""
|
||||
|
||||
def current(self) -> Account:
|
||||
"""
|
||||
Get the current account.
|
||||
|
||||
Returns:
|
||||
Account: The current account.
|
||||
"""
|
||||
|
||||
resp = self._client._request("GET", "/v1/account")
|
||||
obj = resp.json()
|
||||
|
||||
return _json_to_account(obj)
|
||||
|
||||
async def async_current(self) -> Account:
|
||||
"""
|
||||
Get the current account.
|
||||
|
||||
Returns:
|
||||
Account: The current account.
|
||||
"""
|
||||
|
||||
resp = await self._client._async_request("GET", "/v1/account")
|
||||
obj = resp.json()
|
||||
|
||||
return _json_to_account(obj)
|
||||
|
||||
|
||||
def _json_to_account(json: Dict[str, Any]) -> Account:
|
||||
return Account(**json)
|
||||
407
art-agent/backend/venv/Lib/site-packages/replicate/client.py
Normal file
407
art-agent/backend/venv/Lib/site-packages/replicate/client.py
Normal file
@@ -0,0 +1,407 @@
|
||||
import asyncio
|
||||
import os
|
||||
import random
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
AsyncIterator,
|
||||
Dict,
|
||||
Iterable,
|
||||
Iterator,
|
||||
Mapping,
|
||||
Optional,
|
||||
Type,
|
||||
Union,
|
||||
)
|
||||
|
||||
import httpx
|
||||
from typing_extensions import Unpack
|
||||
|
||||
from replicate.__about__ import __version__
|
||||
from replicate.account import Accounts
|
||||
from replicate.collection import Collections
|
||||
from replicate.deployment import Deployments
|
||||
from replicate.exceptions import ReplicateError
|
||||
from replicate.file import Files
|
||||
from replicate.hardware import HardwareNamespace as Hardware
|
||||
from replicate.model import Models
|
||||
from replicate.prediction import Predictions
|
||||
from replicate.run import async_run, run
|
||||
from replicate.stream import async_stream, stream
|
||||
from replicate.training import Trainings
|
||||
from replicate.webhook import Webhooks
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from replicate.stream import ServerSentEvent
|
||||
|
||||
|
||||
class Client:
|
||||
"""A Replicate API client library"""
|
||||
|
||||
__client: Optional[httpx.Client] = None
|
||||
__async_client: Optional[httpx.AsyncClient] = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
api_token: Optional[str] = None,
|
||||
*,
|
||||
base_url: Optional[str] = None,
|
||||
timeout: Optional[httpx.Timeout] = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
|
||||
self._api_token = api_token
|
||||
self._base_url = base_url
|
||||
self._timeout = timeout
|
||||
self._client_kwargs = kwargs
|
||||
|
||||
self.poll_interval = float(os.environ.get("REPLICATE_POLL_INTERVAL", "0.5"))
|
||||
|
||||
@property
|
||||
def _client(self) -> httpx.Client:
|
||||
if not self.__client:
|
||||
self.__client = _build_httpx_client(
|
||||
httpx.Client,
|
||||
self._api_token,
|
||||
self._base_url,
|
||||
self._timeout,
|
||||
**self._client_kwargs,
|
||||
) # type: ignore[assignment]
|
||||
return self.__client # type: ignore[return-value]
|
||||
|
||||
@property
|
||||
def _async_client(self) -> httpx.AsyncClient:
|
||||
if not self.__async_client:
|
||||
self.__async_client = _build_httpx_client(
|
||||
httpx.AsyncClient,
|
||||
self._api_token,
|
||||
self._base_url,
|
||||
self._timeout,
|
||||
**self._client_kwargs,
|
||||
) # type: ignore[assignment]
|
||||
return self.__async_client # type: ignore[return-value]
|
||||
|
||||
def _request(self, method: str, path: str, **kwargs) -> httpx.Response:
|
||||
resp = self._client.request(method, path, **kwargs)
|
||||
_raise_for_status(resp)
|
||||
|
||||
return resp
|
||||
|
||||
async def _async_request(self, method: str, path: str, **kwargs) -> httpx.Response:
|
||||
resp = await self._async_client.request(method, path, **kwargs)
|
||||
_raise_for_status(resp)
|
||||
|
||||
return resp
|
||||
|
||||
@property
|
||||
def accounts(self) -> Accounts:
|
||||
"""
|
||||
Namespace for operations related to accounts.
|
||||
"""
|
||||
|
||||
return Accounts(client=self)
|
||||
|
||||
@property
|
||||
def collections(self) -> Collections:
|
||||
"""
|
||||
Namespace for operations related to collections of models.
|
||||
"""
|
||||
return Collections(client=self)
|
||||
|
||||
@property
|
||||
def deployments(self) -> Deployments:
|
||||
"""
|
||||
Namespace for operations related to deployments.
|
||||
"""
|
||||
return Deployments(client=self)
|
||||
|
||||
@property
|
||||
def files(self) -> Files:
|
||||
"""
|
||||
Namespace for operations related to files.
|
||||
"""
|
||||
return Files(client=self)
|
||||
|
||||
@property
|
||||
def hardware(self) -> Hardware:
|
||||
"""
|
||||
Namespace for operations related to hardware.
|
||||
"""
|
||||
return Hardware(client=self)
|
||||
|
||||
@property
|
||||
def models(self) -> Models:
|
||||
"""
|
||||
Namespace for operations related to models.
|
||||
"""
|
||||
return Models(client=self)
|
||||
|
||||
@property
|
||||
def predictions(self) -> Predictions:
|
||||
"""
|
||||
Namespace for operations related to predictions.
|
||||
"""
|
||||
return Predictions(client=self)
|
||||
|
||||
@property
|
||||
def trainings(self) -> Trainings:
|
||||
"""
|
||||
Namespace for operations related to trainings.
|
||||
"""
|
||||
return Trainings(client=self)
|
||||
|
||||
@property
|
||||
def webhooks(self) -> Webhooks:
|
||||
"""
|
||||
Namespace for operations related to webhooks.
|
||||
"""
|
||||
return Webhooks(client=self)
|
||||
|
||||
def run(
|
||||
self,
|
||||
ref: str,
|
||||
input: Optional[Dict[str, Any]] = None,
|
||||
*,
|
||||
use_file_output: Optional[bool] = True,
|
||||
**params: Unpack["Predictions.CreatePredictionParams"],
|
||||
) -> Union[Any, Iterator[Any]]: # noqa: ANN401
|
||||
"""
|
||||
Run a model and wait for its output.
|
||||
"""
|
||||
|
||||
return run(self, ref, input, use_file_output=use_file_output, **params)
|
||||
|
||||
async def async_run(
|
||||
self,
|
||||
ref: str,
|
||||
input: Optional[Dict[str, Any]] = None,
|
||||
*,
|
||||
use_file_output: Optional[bool] = True,
|
||||
**params: Unpack["Predictions.CreatePredictionParams"],
|
||||
) -> Union[Any, AsyncIterator[Any]]: # noqa: ANN401
|
||||
"""
|
||||
Run a model and wait for its output asynchronously.
|
||||
"""
|
||||
|
||||
return await async_run(
|
||||
self, ref, input, use_file_output=use_file_output, **params
|
||||
)
|
||||
|
||||
def stream(
|
||||
self,
|
||||
ref: str,
|
||||
*,
|
||||
input: Optional[Dict[str, Any]] = None,
|
||||
use_file_output: Optional[bool] = True,
|
||||
**params: Unpack["Predictions.CreatePredictionParams"],
|
||||
) -> Iterator["ServerSentEvent"]:
|
||||
"""
|
||||
Stream a model's output.
|
||||
"""
|
||||
|
||||
return stream(self, ref, input, use_file_output=use_file_output, **params)
|
||||
|
||||
async def async_stream(
|
||||
self,
|
||||
ref: str,
|
||||
input: Optional[Dict[str, Any]] = None,
|
||||
*,
|
||||
use_file_output: Optional[bool] = True,
|
||||
**params: Unpack["Predictions.CreatePredictionParams"],
|
||||
) -> AsyncIterator["ServerSentEvent"]:
|
||||
"""
|
||||
Stream a model's output asynchronously.
|
||||
"""
|
||||
|
||||
return async_stream(self, ref, input, use_file_output=use_file_output, **params)
|
||||
|
||||
|
||||
# Adapted from https://github.com/encode/httpx/issues/108#issuecomment-1132753155
|
||||
class RetryTransport(httpx.AsyncBaseTransport, httpx.BaseTransport):
|
||||
"""A custom HTTP transport that automatically retries requests using an exponential backoff strategy
|
||||
for specific HTTP status codes and request methods.
|
||||
"""
|
||||
|
||||
RETRYABLE_METHODS = frozenset(["HEAD", "GET", "PUT", "DELETE", "OPTIONS", "TRACE"])
|
||||
RETRYABLE_STATUS_CODES = frozenset(
|
||||
[
|
||||
429, # Too Many Requests
|
||||
503, # Service Unavailable
|
||||
504, # Gateway Timeout
|
||||
]
|
||||
)
|
||||
MAX_BACKOFF_WAIT = 60
|
||||
|
||||
def __init__( # pylint: disable=too-many-arguments
|
||||
self,
|
||||
wrapped_transport: Union[httpx.BaseTransport, httpx.AsyncBaseTransport],
|
||||
*,
|
||||
max_attempts: int = 10,
|
||||
max_backoff_wait: float = MAX_BACKOFF_WAIT,
|
||||
backoff_factor: float = 0.1,
|
||||
jitter_ratio: float = 0.1,
|
||||
retryable_methods: Optional[Iterable[str]] = None,
|
||||
retry_status_codes: Optional[Iterable[int]] = None,
|
||||
) -> None:
|
||||
self._wrapped_transport = wrapped_transport
|
||||
|
||||
if jitter_ratio < 0 or jitter_ratio > 0.5:
|
||||
raise ValueError(
|
||||
f"jitter ratio should be between 0 and 0.5, actual {jitter_ratio}"
|
||||
)
|
||||
|
||||
self.max_attempts = max_attempts
|
||||
self.backoff_factor = backoff_factor
|
||||
self.retryable_methods = (
|
||||
frozenset(retryable_methods)
|
||||
if retryable_methods
|
||||
else self.RETRYABLE_METHODS
|
||||
)
|
||||
self.retry_status_codes = (
|
||||
frozenset(retry_status_codes)
|
||||
if retry_status_codes
|
||||
else self.RETRYABLE_STATUS_CODES
|
||||
)
|
||||
self.jitter_ratio = jitter_ratio
|
||||
self.max_backoff_wait = max_backoff_wait
|
||||
|
||||
def _calculate_sleep(
|
||||
self, attempts_made: int, headers: Union[httpx.Headers, Mapping[str, str]]
|
||||
) -> float:
|
||||
retry_after_header = (headers.get("Retry-After") or "").strip()
|
||||
if retry_after_header:
|
||||
if retry_after_header.isdigit():
|
||||
return float(retry_after_header)
|
||||
|
||||
try:
|
||||
parsed_date = datetime.fromisoformat(retry_after_header).astimezone()
|
||||
diff = (parsed_date - datetime.now().astimezone()).total_seconds()
|
||||
if diff > 0:
|
||||
return min(diff, self.max_backoff_wait)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
backoff = self.backoff_factor * (2 ** (attempts_made - 1))
|
||||
jitter = (backoff * self.jitter_ratio) * random.choice([1, -1]) # noqa: S311
|
||||
total_backoff = backoff + jitter
|
||||
return min(total_backoff, self.max_backoff_wait)
|
||||
|
||||
def handle_request(self, request: httpx.Request) -> httpx.Response:
|
||||
response = self._wrapped_transport.handle_request(request) # type: ignore
|
||||
|
||||
if request.method not in self.retryable_methods:
|
||||
return response
|
||||
|
||||
remaining_attempts = self.max_attempts - 1
|
||||
attempts_made = 1
|
||||
|
||||
while True:
|
||||
if (
|
||||
remaining_attempts < 1
|
||||
or response.status_code not in self.retry_status_codes
|
||||
):
|
||||
return response
|
||||
|
||||
response.close()
|
||||
|
||||
sleep_for = self._calculate_sleep(attempts_made, response.headers)
|
||||
time.sleep(sleep_for)
|
||||
|
||||
response = self._wrapped_transport.handle_request(request) # type: ignore
|
||||
|
||||
attempts_made += 1
|
||||
remaining_attempts -= 1
|
||||
|
||||
async def handle_async_request(self, request: httpx.Request) -> httpx.Response:
|
||||
response = await self._wrapped_transport.handle_async_request(request) # type: ignore
|
||||
|
||||
if request.method not in self.retryable_methods:
|
||||
return response
|
||||
|
||||
remaining_attempts = self.max_attempts - 1
|
||||
attempts_made = 1
|
||||
|
||||
while True:
|
||||
if (
|
||||
remaining_attempts < 1
|
||||
or response.status_code not in self.retry_status_codes
|
||||
):
|
||||
return response
|
||||
|
||||
await response.aclose()
|
||||
|
||||
sleep_for = self._calculate_sleep(attempts_made, response.headers)
|
||||
await asyncio.sleep(sleep_for)
|
||||
|
||||
response = await self._wrapped_transport.handle_async_request(request) # type: ignore
|
||||
|
||||
attempts_made += 1
|
||||
remaining_attempts -= 1
|
||||
|
||||
async def aclose(self) -> None:
|
||||
await self._wrapped_transport.aclose() # type: ignore
|
||||
|
||||
def close(self) -> None:
|
||||
self._wrapped_transport.close() # type: ignore
|
||||
|
||||
|
||||
def _get_api_token_from_environment() -> Optional[str]:
|
||||
"""Get API token from cog current scope if available, otherwise from environment."""
|
||||
try:
|
||||
import cog # noqa: I001 # pyright: ignore [reportMissingImports]
|
||||
|
||||
for key, value in cog.current_scope().context.items():
|
||||
if key.upper() == "REPLICATE_API_TOKEN":
|
||||
return value
|
||||
except: # noqa: S110,E722,BLE001 we don't want this code to cause clients to fail
|
||||
pass
|
||||
|
||||
return os.environ.get("REPLICATE_API_TOKEN")
|
||||
|
||||
|
||||
def _build_httpx_client(
|
||||
client_type: Type[Union[httpx.Client, httpx.AsyncClient]],
|
||||
api_token: Optional[str] = None,
|
||||
base_url: Optional[str] = None,
|
||||
timeout: Optional[httpx.Timeout] = None,
|
||||
**kwargs,
|
||||
) -> Union[httpx.Client, httpx.AsyncClient]:
|
||||
headers = kwargs.pop("headers", {})
|
||||
if "User-Agent" not in headers:
|
||||
headers["User-Agent"] = f"replicate-python/{__version__}"
|
||||
if "Authorization" not in headers and (
|
||||
api_token := api_token or _get_api_token_from_environment()
|
||||
):
|
||||
headers["Authorization"] = f"Bearer {api_token}"
|
||||
|
||||
base_url = (
|
||||
base_url or os.environ.get("REPLICATE_BASE_URL") or "https://api.replicate.com"
|
||||
)
|
||||
if base_url == "":
|
||||
base_url = "https://api.replicate.com"
|
||||
|
||||
timeout = timeout or httpx.Timeout(
|
||||
5.0, read=30.0, write=30.0, connect=5.0, pool=10.0
|
||||
)
|
||||
|
||||
transport = kwargs.pop("transport", None) or (
|
||||
httpx.HTTPTransport()
|
||||
if client_type is httpx.Client
|
||||
else httpx.AsyncHTTPTransport()
|
||||
)
|
||||
|
||||
return client_type(
|
||||
base_url=base_url,
|
||||
headers=headers,
|
||||
timeout=timeout,
|
||||
transport=RetryTransport(wrapped_transport=transport), # type: ignore[arg-type]
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
def _raise_for_status(resp: httpx.Response) -> None:
|
||||
if 400 <= resp.status_code < 600:
|
||||
raise ReplicateError.from_response(resp)
|
||||
147
art-agent/backend/venv/Lib/site-packages/replicate/collection.py
Normal file
147
art-agent/backend/venv/Lib/site-packages/replicate/collection.py
Normal file
@@ -0,0 +1,147 @@
|
||||
from typing import Any, Dict, Iterator, List, Optional, Union, overload
|
||||
|
||||
from typing_extensions import deprecated
|
||||
|
||||
from replicate.model import Model
|
||||
from replicate.pagination import Page
|
||||
from replicate.resource import Namespace, Resource
|
||||
|
||||
|
||||
class Collection(Resource):
|
||||
"""
|
||||
A collection of models on Replicate.
|
||||
"""
|
||||
|
||||
slug: str
|
||||
"""The slug used to identify the collection."""
|
||||
|
||||
name: str
|
||||
"""The name of the collection."""
|
||||
|
||||
description: str
|
||||
"""A description of the collection."""
|
||||
|
||||
models: Optional[List[Model]] = None
|
||||
"""The models in the collection."""
|
||||
|
||||
@property
|
||||
@deprecated("Use `slug` instead of `id`")
|
||||
def id(self) -> str:
|
||||
"""
|
||||
DEPRECATED: Use `slug` instead.
|
||||
"""
|
||||
return self.slug
|
||||
|
||||
def __iter__(self) -> Iterator[Model]:
|
||||
if self.models is not None:
|
||||
return iter(self.models)
|
||||
return iter([])
|
||||
|
||||
@overload
|
||||
def __getitem__(self, index: int) -> Optional[Model]: ...
|
||||
|
||||
@overload
|
||||
def __getitem__(self, index: slice) -> Optional[List[Model]]: ...
|
||||
|
||||
def __getitem__(
|
||||
self, index: Union[int, slice]
|
||||
) -> Union[Optional[Model], Optional[List[Model]]]:
|
||||
if self.models is not None:
|
||||
return self.models[index]
|
||||
return None
|
||||
|
||||
def __len__(self) -> int:
|
||||
if self.models is not None:
|
||||
return len(self.models)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
class Collections(Namespace):
|
||||
"""
|
||||
A namespace for operations related to collections of models.
|
||||
"""
|
||||
|
||||
def list(
|
||||
self,
|
||||
cursor: Union[str, "ellipsis", None] = ..., # noqa: F821
|
||||
) -> Page[Collection]:
|
||||
"""
|
||||
List collections of models.
|
||||
|
||||
Parameters:
|
||||
cursor: The cursor to use for pagination. Use the value of `Page.next` or `Page.previous`.
|
||||
Returns:
|
||||
Page[Collection]: A page of of model collections.
|
||||
Raises:
|
||||
ValueError: If `cursor` is `None`.
|
||||
"""
|
||||
|
||||
if cursor is None:
|
||||
raise ValueError("cursor cannot be None")
|
||||
|
||||
resp = self._client._request(
|
||||
"GET", "/v1/collections" if cursor is ... else cursor
|
||||
)
|
||||
|
||||
obj = resp.json()
|
||||
obj["results"] = [_json_to_collection(result) for result in obj["results"]]
|
||||
|
||||
return Page[Collection](**obj)
|
||||
|
||||
async def async_list(
|
||||
self,
|
||||
cursor: Union[str, "ellipsis", None] = ..., # noqa: F821
|
||||
) -> Page[Collection]:
|
||||
"""
|
||||
List collections of models.
|
||||
|
||||
Parameters:
|
||||
cursor: The cursor to use for pagination. Use the value of `Page.next` or `Page.previous`.
|
||||
Returns:
|
||||
Page[Collection]: A page of of model collections.
|
||||
Raises:
|
||||
ValueError: If `cursor` is `None`.
|
||||
"""
|
||||
|
||||
if cursor is None:
|
||||
raise ValueError("cursor cannot be None")
|
||||
|
||||
resp = await self._client._async_request(
|
||||
"GET", "/v1/collections" if cursor is ... else cursor
|
||||
)
|
||||
|
||||
obj = resp.json()
|
||||
obj["results"] = [_json_to_collection(result) for result in obj["results"]]
|
||||
|
||||
return Page[Collection](**obj)
|
||||
|
||||
def get(self, slug: str) -> Collection:
|
||||
"""Get a model by name.
|
||||
|
||||
Args:
|
||||
name: The name of the model, in the format `owner/model-name`.
|
||||
Returns:
|
||||
The model.
|
||||
"""
|
||||
|
||||
resp = self._client._request("GET", f"/v1/collections/{slug}")
|
||||
|
||||
return _json_to_collection(resp.json())
|
||||
|
||||
async def async_get(self, slug: str) -> Collection:
|
||||
"""Get a model by name.
|
||||
|
||||
Args:
|
||||
name: The name of the model, in the format `owner/model-name`.
|
||||
Returns:
|
||||
The model.
|
||||
"""
|
||||
|
||||
resp = await self._client._async_request("GET", f"/v1/collections/{slug}")
|
||||
|
||||
return _json_to_collection(resp.json())
|
||||
|
||||
|
||||
def _json_to_collection(json: Dict[str, Any]) -> Collection:
|
||||
return Collection(**json)
|
||||
555
art-agent/backend/venv/Lib/site-packages/replicate/deployment.py
Normal file
555
art-agent/backend/venv/Lib/site-packages/replicate/deployment.py
Normal file
@@ -0,0 +1,555 @@
|
||||
from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, TypedDict, Union
|
||||
|
||||
from typing_extensions import Unpack, deprecated
|
||||
|
||||
from replicate.account import Account
|
||||
from replicate.helpers import async_encode_json, encode_json
|
||||
from replicate.pagination import Page
|
||||
from replicate.prediction import (
|
||||
Prediction,
|
||||
_create_prediction_body,
|
||||
_create_prediction_request_params,
|
||||
_json_to_prediction,
|
||||
)
|
||||
from replicate.resource import Namespace, Resource
|
||||
|
||||
try:
|
||||
from pydantic import v1 as pydantic # type: ignore
|
||||
except ImportError:
|
||||
import pydantic # type: ignore
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from replicate.client import Client
|
||||
from replicate.prediction import Predictions
|
||||
|
||||
|
||||
class Deployment(Resource):
|
||||
"""
|
||||
A deployment of a model hosted on Replicate.
|
||||
"""
|
||||
|
||||
_client: "Client" = pydantic.PrivateAttr()
|
||||
|
||||
owner: str
|
||||
"""
|
||||
The name of the user or organization that owns the deployment.
|
||||
"""
|
||||
|
||||
name: str
|
||||
"""
|
||||
The name of the deployment.
|
||||
"""
|
||||
|
||||
class Release(Resource):
|
||||
"""
|
||||
A release of a deployment.
|
||||
"""
|
||||
|
||||
number: int
|
||||
"""
|
||||
The release number.
|
||||
"""
|
||||
|
||||
model: str
|
||||
"""
|
||||
The model identifier string in the format of `{model_owner}/{model_name}`.
|
||||
"""
|
||||
|
||||
version: str
|
||||
"""
|
||||
The ID of the model version used in the release.
|
||||
"""
|
||||
|
||||
created_at: str
|
||||
"""
|
||||
The time the release was created.
|
||||
"""
|
||||
|
||||
created_by: Optional[Account]
|
||||
"""
|
||||
The account that created the release.
|
||||
"""
|
||||
|
||||
class Configuration(Resource):
|
||||
"""
|
||||
A configuration for a deployment.
|
||||
"""
|
||||
|
||||
hardware: str
|
||||
"""
|
||||
The SKU for the hardware used to run the model.
|
||||
"""
|
||||
|
||||
min_instances: int
|
||||
"""
|
||||
The minimum number of instances for scaling.
|
||||
"""
|
||||
|
||||
max_instances: int
|
||||
"""
|
||||
The maximum number of instances for scaling.
|
||||
"""
|
||||
|
||||
configuration: Configuration
|
||||
"""
|
||||
The deployment configuration.
|
||||
"""
|
||||
|
||||
current_release: Optional[Release]
|
||||
"""
|
||||
The current release of the deployment.
|
||||
"""
|
||||
|
||||
@property
|
||||
@deprecated("Use `deployment.owner` instead.")
|
||||
def username(self) -> str:
|
||||
"""
|
||||
The name of the user or organization that owns the deployment.
|
||||
This attribute is deprecated and will be removed in future versions.
|
||||
"""
|
||||
return self.owner
|
||||
|
||||
@property
|
||||
def id(self) -> str:
|
||||
"""
|
||||
Return the qualified deployment name, in the format `owner/name`.
|
||||
"""
|
||||
return f"{self.owner}/{self.name}"
|
||||
|
||||
@property
|
||||
def predictions(self) -> "DeploymentPredictions":
|
||||
"""
|
||||
Get the predictions for this deployment.
|
||||
"""
|
||||
|
||||
return DeploymentPredictions(client=self._client, deployment=self)
|
||||
|
||||
|
||||
class Deployments(Namespace):
|
||||
"""
|
||||
Namespace for operations related to deployments.
|
||||
"""
|
||||
|
||||
_client: "Client"
|
||||
|
||||
def list(
|
||||
self,
|
||||
cursor: Union[str, "ellipsis", None] = ..., # noqa: F821
|
||||
) -> Page[Deployment]:
|
||||
"""
|
||||
List all deployments.
|
||||
|
||||
Returns:
|
||||
A page of Deployments.
|
||||
"""
|
||||
|
||||
if cursor is None:
|
||||
raise ValueError("cursor cannot be None")
|
||||
|
||||
resp = self._client._request(
|
||||
"GET", "/v1/deployments" if cursor is ... else cursor
|
||||
)
|
||||
|
||||
obj = resp.json()
|
||||
obj["results"] = [
|
||||
_json_to_deployment(self._client, result) for result in obj["results"]
|
||||
]
|
||||
|
||||
return Page[Deployment](**obj)
|
||||
|
||||
async def async_list(
|
||||
self,
|
||||
cursor: Union[str, "ellipsis", None] = ..., # noqa: F821
|
||||
) -> Page[Deployment]:
|
||||
"""
|
||||
List all deployments.
|
||||
|
||||
Returns:
|
||||
A page of Deployments.
|
||||
"""
|
||||
if cursor is None:
|
||||
raise ValueError("cursor cannot be None")
|
||||
|
||||
resp = await self._client._async_request(
|
||||
"GET", "/v1/deployments" if cursor is ... else cursor
|
||||
)
|
||||
|
||||
obj = resp.json()
|
||||
obj["results"] = [
|
||||
_json_to_deployment(self._client, result) for result in obj["results"]
|
||||
]
|
||||
|
||||
return Page[Deployment](**obj)
|
||||
|
||||
def get(self, name: str) -> Deployment:
|
||||
"""
|
||||
Get a deployment by name.
|
||||
|
||||
Args:
|
||||
name: The name of the deployment, in the format `owner/model-name`.
|
||||
Returns:
|
||||
The model.
|
||||
"""
|
||||
|
||||
owner, name = name.split("/", 1)
|
||||
|
||||
resp = self._client._request(
|
||||
"GET",
|
||||
f"/v1/deployments/{owner}/{name}",
|
||||
)
|
||||
|
||||
return _json_to_deployment(self._client, resp.json())
|
||||
|
||||
async def async_get(self, name: str) -> Deployment:
|
||||
"""
|
||||
Get a deployment by name.
|
||||
|
||||
Args:
|
||||
name: The name of the deployment, in the format `owner/model-name`.
|
||||
Returns:
|
||||
The model.
|
||||
"""
|
||||
|
||||
owner, name = name.split("/", 1)
|
||||
|
||||
resp = await self._client._async_request(
|
||||
"GET",
|
||||
f"/v1/deployments/{owner}/{name}",
|
||||
)
|
||||
|
||||
return _json_to_deployment(self._client, resp.json())
|
||||
|
||||
class CreateDeploymentParams(TypedDict):
|
||||
"""
|
||||
Parameters for creating a new deployment.
|
||||
"""
|
||||
|
||||
name: str
|
||||
"""The name of the deployment."""
|
||||
|
||||
model: str
|
||||
"""The model identifier string in the format of `{model_owner}/{model_name}`."""
|
||||
|
||||
version: str
|
||||
"""The version of the model to deploy."""
|
||||
|
||||
hardware: str
|
||||
"""The SKU for the hardware used to run the model."""
|
||||
|
||||
min_instances: int
|
||||
"""The minimum number of instances for scaling."""
|
||||
|
||||
max_instances: int
|
||||
"""The maximum number of instances for scaling."""
|
||||
|
||||
def create(self, **params: Unpack[CreateDeploymentParams]) -> Deployment:
|
||||
"""
|
||||
Create a new deployment.
|
||||
|
||||
Args:
|
||||
params: Configuration for the new deployment.
|
||||
Returns:
|
||||
The newly created Deployment.
|
||||
"""
|
||||
|
||||
if name := params.get("name", None):
|
||||
if "/" in name:
|
||||
_, name = name.split("/", 1)
|
||||
params["name"] = name
|
||||
|
||||
resp = self._client._request(
|
||||
"POST",
|
||||
"/v1/deployments",
|
||||
json=params,
|
||||
)
|
||||
|
||||
return _json_to_deployment(self._client, resp.json())
|
||||
|
||||
async def async_create(
|
||||
self, **params: Unpack[CreateDeploymentParams]
|
||||
) -> Deployment:
|
||||
"""
|
||||
Create a new deployment.
|
||||
|
||||
Args:
|
||||
params: Configuration for the new deployment.
|
||||
Returns:
|
||||
The newly created Deployment.
|
||||
"""
|
||||
|
||||
if name := params.get("name", None):
|
||||
if "/" in name:
|
||||
_, name = name.split("/", 1)
|
||||
params["name"] = name
|
||||
|
||||
resp = await self._client._async_request(
|
||||
"POST",
|
||||
"/v1/deployments",
|
||||
json=params,
|
||||
)
|
||||
|
||||
return _json_to_deployment(self._client, resp.json())
|
||||
|
||||
class UpdateDeploymentParams(TypedDict, total=False):
|
||||
"""
|
||||
Parameters for updating an existing deployment.
|
||||
"""
|
||||
|
||||
version: str
|
||||
"""The version of the model to deploy."""
|
||||
|
||||
hardware: str
|
||||
"""The SKU for the hardware used to run the model."""
|
||||
|
||||
min_instances: int
|
||||
"""The minimum number of instances for scaling."""
|
||||
|
||||
max_instances: int
|
||||
"""The maximum number of instances for scaling."""
|
||||
|
||||
def update(
|
||||
self,
|
||||
deployment_owner: str,
|
||||
deployment_name: str,
|
||||
**params: Unpack[UpdateDeploymentParams],
|
||||
) -> Deployment:
|
||||
"""
|
||||
Update an existing deployment.
|
||||
|
||||
Args:
|
||||
deployment_owner: The owner of the deployment.
|
||||
deployment_name: The name of the deployment.
|
||||
params: Configuration updates for the deployment.
|
||||
Returns:
|
||||
The updated Deployment.
|
||||
"""
|
||||
|
||||
resp = self._client._request(
|
||||
"PATCH",
|
||||
f"/v1/deployments/{deployment_owner}/{deployment_name}",
|
||||
json=params,
|
||||
)
|
||||
|
||||
return _json_to_deployment(self._client, resp.json())
|
||||
|
||||
async def async_update(
|
||||
self,
|
||||
deployment_owner: str,
|
||||
deployment_name: str,
|
||||
**params: Unpack[UpdateDeploymentParams],
|
||||
) -> Deployment:
|
||||
"""
|
||||
Update an existing deployment.
|
||||
|
||||
Args:
|
||||
deployment_owner: The owner of the deployment.
|
||||
deployment_name: The name of the deployment.
|
||||
params: Configuration updates for the deployment.
|
||||
Returns:
|
||||
The updated Deployment.
|
||||
"""
|
||||
|
||||
resp = await self._client._async_request(
|
||||
"PATCH",
|
||||
f"/v1/deployments/{deployment_owner}/{deployment_name}",
|
||||
json=params,
|
||||
)
|
||||
|
||||
return _json_to_deployment(self._client, resp.json())
|
||||
|
||||
def delete(self, deployment_owner: str, deployment_name: str) -> bool:
|
||||
"""
|
||||
Delete an existing deployment.
|
||||
|
||||
Args:
|
||||
deployment_owner: The owner of the deployment.
|
||||
deployment_name: The name of the deployment.
|
||||
"""
|
||||
resp = self._client._request(
|
||||
"DELETE",
|
||||
f"/v1/deployments/{deployment_owner}/{deployment_name}",
|
||||
)
|
||||
return resp.status_code == 204
|
||||
|
||||
async def async_delete(self, deployment_owner: str, deployment_name: str) -> bool:
|
||||
"""
|
||||
Delete an existing deployment asynchronously.
|
||||
|
||||
Args:
|
||||
deployment_owner: The owner of the deployment.
|
||||
deployment_name: The name of the deployment.
|
||||
"""
|
||||
resp = await self._client._async_request(
|
||||
"DELETE",
|
||||
f"/v1/deployments/{deployment_owner}/{deployment_name}",
|
||||
)
|
||||
return resp.status_code == 204
|
||||
|
||||
@property
|
||||
def predictions(self) -> "DeploymentsPredictions":
|
||||
"""
|
||||
Get predictions for deployments.
|
||||
"""
|
||||
|
||||
return DeploymentsPredictions(client=self._client)
|
||||
|
||||
|
||||
def _json_to_deployment(client: "Client", json: Dict[str, Any]) -> Deployment:
|
||||
deployment = Deployment(**json)
|
||||
deployment._client = client
|
||||
return deployment
|
||||
|
||||
|
||||
class DeploymentPredictions(Namespace):
|
||||
"""
|
||||
Namespace for operations related to predictions in a deployment.
|
||||
"""
|
||||
|
||||
_deployment: Deployment
|
||||
|
||||
def __init__(self, client: "Client", deployment: Deployment) -> None:
|
||||
super().__init__(client=client)
|
||||
self._deployment = deployment
|
||||
|
||||
def create(
|
||||
self,
|
||||
input: Dict[str, Any],
|
||||
**params: Unpack["Predictions.CreatePredictionParams"],
|
||||
) -> Prediction:
|
||||
"""
|
||||
Create a new prediction with the deployment.
|
||||
"""
|
||||
|
||||
wait = params.pop("wait", None)
|
||||
file_encoding_strategy = params.pop("file_encoding_strategy", None)
|
||||
|
||||
if input is not None:
|
||||
input = encode_json(
|
||||
input,
|
||||
client=self._client,
|
||||
file_encoding_strategy=file_encoding_strategy,
|
||||
)
|
||||
|
||||
body = _create_prediction_body(version=None, input=input, **params)
|
||||
extras = _create_prediction_request_params(
|
||||
wait=wait,
|
||||
)
|
||||
resp = self._client._request(
|
||||
"POST",
|
||||
f"/v1/deployments/{self._deployment.owner}/{self._deployment.name}/predictions",
|
||||
json=body,
|
||||
**extras,
|
||||
)
|
||||
|
||||
return _json_to_prediction(self._client, resp.json())
|
||||
|
||||
async def async_create(
|
||||
self,
|
||||
input: Dict[str, Any],
|
||||
**params: Unpack["Predictions.CreatePredictionParams"],
|
||||
) -> Prediction:
|
||||
"""
|
||||
Create a new prediction with the deployment.
|
||||
"""
|
||||
|
||||
wait = params.pop("wait", None)
|
||||
file_encoding_strategy = params.pop("file_encoding_strategy", None)
|
||||
if input is not None:
|
||||
input = await async_encode_json(
|
||||
input,
|
||||
client=self._client,
|
||||
file_encoding_strategy=file_encoding_strategy,
|
||||
)
|
||||
|
||||
body = _create_prediction_body(version=None, input=input, **params)
|
||||
extras = _create_prediction_request_params(
|
||||
wait=wait,
|
||||
)
|
||||
resp = await self._client._async_request(
|
||||
"POST",
|
||||
f"/v1/deployments/{self._deployment.owner}/{self._deployment.name}/predictions",
|
||||
json=body,
|
||||
**extras,
|
||||
)
|
||||
|
||||
return _json_to_prediction(self._client, resp.json())
|
||||
|
||||
|
||||
class DeploymentsPredictions(Namespace):
|
||||
"""
|
||||
Namespace for operations related to predictions in deployments.
|
||||
"""
|
||||
|
||||
def create(
|
||||
self,
|
||||
deployment: Union[str, Tuple[str, str], Deployment],
|
||||
input: Dict[str, Any],
|
||||
**params: Unpack["Predictions.CreatePredictionParams"],
|
||||
) -> Prediction:
|
||||
"""
|
||||
Create a new prediction with the deployment.
|
||||
"""
|
||||
|
||||
wait = params.pop("wait", None)
|
||||
file_encoding_strategy = params.pop("file_encoding_strategy", None)
|
||||
|
||||
url = _create_prediction_url_from_deployment(deployment)
|
||||
if input is not None:
|
||||
input = encode_json(
|
||||
input,
|
||||
client=self._client,
|
||||
file_encoding_strategy=file_encoding_strategy,
|
||||
)
|
||||
|
||||
body = _create_prediction_body(version=None, input=input, **params)
|
||||
extras = _create_prediction_request_params(wait=wait)
|
||||
resp = self._client._request("POST", url, json=body, **extras)
|
||||
|
||||
return _json_to_prediction(self._client, resp.json())
|
||||
|
||||
async def async_create(
|
||||
self,
|
||||
deployment: Union[str, Tuple[str, str], Deployment],
|
||||
input: Dict[str, Any],
|
||||
**params: Unpack["Predictions.CreatePredictionParams"],
|
||||
) -> Prediction:
|
||||
"""
|
||||
Create a new prediction with the deployment.
|
||||
"""
|
||||
|
||||
wait = params.pop("wait", None)
|
||||
file_encoding_strategy = params.pop("file_encoding_strategy", None)
|
||||
|
||||
url = _create_prediction_url_from_deployment(deployment)
|
||||
if input is not None:
|
||||
input = await async_encode_json(
|
||||
input,
|
||||
client=self._client,
|
||||
file_encoding_strategy=file_encoding_strategy,
|
||||
)
|
||||
|
||||
body = _create_prediction_body(version=None, input=input, **params)
|
||||
extras = _create_prediction_request_params(wait=wait)
|
||||
resp = await self._client._async_request("POST", url, json=body, **extras)
|
||||
|
||||
return _json_to_prediction(self._client, resp.json())
|
||||
|
||||
|
||||
def _create_prediction_url_from_deployment(
|
||||
deployment: Union[str, Tuple[str, str], Deployment],
|
||||
) -> str:
|
||||
owner, name = None, None
|
||||
if isinstance(deployment, Deployment):
|
||||
owner, name = deployment.owner, deployment.name
|
||||
elif isinstance(deployment, tuple):
|
||||
owner, name = deployment[0], deployment[1]
|
||||
elif isinstance(deployment, str):
|
||||
owner, name = deployment.split("/", 1)
|
||||
|
||||
if owner is None or name is None:
|
||||
raise ValueError(
|
||||
"deployment must be a Deployment, a tuple of (owner, name), or a string in the format 'owner/name'"
|
||||
)
|
||||
|
||||
return f"/v1/deployments/{owner}/{name}/predictions"
|
||||
107
art-agent/backend/venv/Lib/site-packages/replicate/exceptions.py
Normal file
107
art-agent/backend/venv/Lib/site-packages/replicate/exceptions.py
Normal file
@@ -0,0 +1,107 @@
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
import httpx
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from replicate.prediction import Prediction
|
||||
|
||||
|
||||
class ReplicateException(Exception):
|
||||
"""A base class for all Replicate exceptions."""
|
||||
|
||||
|
||||
class ModelError(ReplicateException):
|
||||
"""An error from user's code in a model."""
|
||||
|
||||
prediction: "Prediction"
|
||||
|
||||
def __init__(self, prediction: "Prediction") -> None:
|
||||
self.prediction = prediction
|
||||
super().__init__(prediction.error)
|
||||
|
||||
|
||||
class ReplicateError(ReplicateException):
|
||||
"""
|
||||
An error from Replicate's API.
|
||||
|
||||
This class represents a problem details response as defined in RFC 7807.
|
||||
"""
|
||||
|
||||
type: Optional[str]
|
||||
"""A URI that identifies the error type."""
|
||||
|
||||
title: Optional[str]
|
||||
"""A short, human-readable summary of the error."""
|
||||
|
||||
status: Optional[int]
|
||||
"""The HTTP status code."""
|
||||
|
||||
detail: Optional[str]
|
||||
"""A human-readable explanation specific to this occurrence of the error."""
|
||||
|
||||
instance: Optional[str]
|
||||
"""A URI that identifies the specific occurrence of the error."""
|
||||
|
||||
def __init__( # pylint: disable=too-many-arguments
|
||||
self,
|
||||
type: Optional[str] = None,
|
||||
title: Optional[str] = None,
|
||||
status: Optional[int] = None,
|
||||
detail: Optional[str] = None,
|
||||
instance: Optional[str] = None,
|
||||
) -> None:
|
||||
self.type = type
|
||||
self.title = title
|
||||
self.status = status
|
||||
self.detail = detail
|
||||
self.instance = instance
|
||||
|
||||
@classmethod
|
||||
def from_response(cls, response: httpx.Response) -> "ReplicateError":
|
||||
"""Create a ReplicateError from an HTTP response."""
|
||||
|
||||
try:
|
||||
data = response.json()
|
||||
except ValueError:
|
||||
data = {}
|
||||
|
||||
return cls(
|
||||
type=data.get("type"),
|
||||
title=data.get("title"),
|
||||
detail=data.get("detail"),
|
||||
status=response.status_code,
|
||||
instance=data.get("instance"),
|
||||
)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""Get a dictionary representation of the error."""
|
||||
|
||||
return {
|
||||
key: value
|
||||
for key, value in {
|
||||
"type": self.type,
|
||||
"title": self.title,
|
||||
"status": self.status,
|
||||
"detail": self.detail,
|
||||
"instance": self.instance,
|
||||
}.items()
|
||||
if value is not None
|
||||
}
|
||||
|
||||
def __str__(self) -> str:
|
||||
return "ReplicateError Details:\n" + "\n".join(
|
||||
[f"{key}: {value}" for key, value in self.to_dict().items()]
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
class_name = self.__class__.__name__
|
||||
params = ", ".join(
|
||||
[
|
||||
f"type={repr(self.type)}",
|
||||
f"title={repr(self.title)}",
|
||||
f"status={repr(self.status)}",
|
||||
f"detail={repr(self.detail)}",
|
||||
f"instance={repr(self.instance)}",
|
||||
]
|
||||
)
|
||||
return f"{class_name}({params})"
|
||||
177
art-agent/backend/venv/Lib/site-packages/replicate/file.py
Normal file
177
art-agent/backend/venv/Lib/site-packages/replicate/file.py
Normal file
@@ -0,0 +1,177 @@
|
||||
import io
|
||||
import json
|
||||
import mimetypes
|
||||
import os
|
||||
import pathlib
|
||||
from typing import Any, BinaryIO, Dict, List, Optional, TypedDict, Union
|
||||
|
||||
from typing_extensions import Literal, NotRequired, Unpack
|
||||
|
||||
from replicate.resource import Namespace, Resource
|
||||
|
||||
FileEncodingStrategy = Literal["base64", "url"]
|
||||
|
||||
|
||||
class File(Resource):
|
||||
"""
|
||||
A file uploaded to Replicate that can be used as an input to a model.
|
||||
"""
|
||||
|
||||
id: str
|
||||
"""The ID of the file."""
|
||||
|
||||
name: str
|
||||
"""The name of the file."""
|
||||
|
||||
content_type: str
|
||||
"""The content type of the file."""
|
||||
|
||||
size: int
|
||||
"""The size of the file in bytes."""
|
||||
|
||||
etag: str
|
||||
"""The ETag of the file."""
|
||||
|
||||
checksums: Dict[str, str]
|
||||
"""The checksums of the file."""
|
||||
|
||||
metadata: Dict[str, Any]
|
||||
"""The metadata of the file."""
|
||||
|
||||
created_at: str
|
||||
"""The time the file was created."""
|
||||
|
||||
expires_at: Optional[str]
|
||||
"""The time the file will expire."""
|
||||
|
||||
urls: Dict[str, str]
|
||||
"""The URLs of the file."""
|
||||
|
||||
|
||||
class Files(Namespace):
|
||||
class CreateFileParams(TypedDict):
|
||||
"""Parameters for creating a file."""
|
||||
|
||||
filename: NotRequired[str]
|
||||
"""The name of the file."""
|
||||
|
||||
content_type: NotRequired[str]
|
||||
"""The content type of the file."""
|
||||
|
||||
metadata: NotRequired[Dict[str, Any]]
|
||||
"""The file metadata."""
|
||||
|
||||
def create(
|
||||
self,
|
||||
file: Union[str, pathlib.Path, BinaryIO, io.IOBase],
|
||||
**params: Unpack["Files.CreateFileParams"],
|
||||
) -> File:
|
||||
"""
|
||||
Upload a file that can be passed as an input when running a model.
|
||||
"""
|
||||
|
||||
if isinstance(file, (str, pathlib.Path)):
|
||||
file_path = pathlib.Path(file)
|
||||
params["filename"] = file_path.name
|
||||
with open(file, "rb") as f:
|
||||
return self.create(f, **params)
|
||||
elif not isinstance(file, (io.IOBase, BinaryIO)):
|
||||
raise ValueError(
|
||||
"Unsupported file type. Must be a file path or file-like object."
|
||||
)
|
||||
|
||||
resp = self._client._request(
|
||||
"POST", "/v1/files", timeout=None, **_create_file_params(file, **params)
|
||||
)
|
||||
|
||||
return _json_to_file(resp.json())
|
||||
|
||||
async def async_create(
|
||||
self,
|
||||
file: Union[str, pathlib.Path, BinaryIO, io.IOBase],
|
||||
**params: Unpack["Files.CreateFileParams"],
|
||||
) -> File:
|
||||
"""Upload a file asynchronously that can be passed as an input when running a model."""
|
||||
|
||||
if isinstance(file, (str, pathlib.Path)):
|
||||
file_path = pathlib.Path(file)
|
||||
params["filename"] = file_path.name
|
||||
with open(file_path, "rb") as f:
|
||||
return await self.async_create(f, **params)
|
||||
elif not isinstance(file, (io.IOBase, BinaryIO)):
|
||||
raise ValueError(
|
||||
"Unsupported file type. Must be a file path or file-like object."
|
||||
)
|
||||
|
||||
resp = await self._client._async_request(
|
||||
"POST", "/v1/files", timeout=None, **_create_file_params(file, **params)
|
||||
)
|
||||
|
||||
return _json_to_file(resp.json())
|
||||
|
||||
def get(self, file_id: str) -> File:
|
||||
"""Get an uploaded file by its ID."""
|
||||
|
||||
resp = self._client._request("GET", f"/v1/files/{file_id}")
|
||||
return _json_to_file(resp.json())
|
||||
|
||||
async def async_get(self, file_id: str) -> File:
|
||||
"""Get an uploaded file by its ID asynchronously."""
|
||||
|
||||
resp = await self._client._async_request("GET", f"/v1/files/{file_id}")
|
||||
return _json_to_file(resp.json())
|
||||
|
||||
def list(self) -> List[File]:
|
||||
"""List all uploaded files."""
|
||||
|
||||
resp = self._client._request("GET", "/v1/files")
|
||||
return [_json_to_file(obj) for obj in resp.json().get("results", [])]
|
||||
|
||||
async def async_list(self) -> List[File]:
|
||||
"""List all uploaded files asynchronously."""
|
||||
|
||||
resp = await self._client._async_request("GET", "/v1/files")
|
||||
return [_json_to_file(obj) for obj in resp.json().get("results", [])]
|
||||
|
||||
def delete(self, file_id: str) -> bool:
|
||||
"""Delete an uploaded file by its ID."""
|
||||
|
||||
resp = self._client._request("DELETE", f"/v1/files/{file_id}")
|
||||
return resp.status_code == 204
|
||||
|
||||
async def async_delete(self, file_id: str) -> bool:
|
||||
"""Delete an uploaded file by its ID asynchronously."""
|
||||
|
||||
resp = await self._client._async_request("DELETE", f"/v1/files/{file_id}")
|
||||
return resp.status_code == 204
|
||||
|
||||
|
||||
def _create_file_params(
|
||||
file: Union[BinaryIO, io.IOBase],
|
||||
**params: Unpack["Files.CreateFileParams"],
|
||||
) -> Dict[str, Any]:
|
||||
file.seek(0)
|
||||
|
||||
if params is None:
|
||||
params = {}
|
||||
|
||||
filename = params.get("filename", os.path.basename(getattr(file, "name", "file")))
|
||||
content_type = (
|
||||
params.get("content_type")
|
||||
or mimetypes.guess_type(filename)[0]
|
||||
or "application/octet-stream"
|
||||
)
|
||||
metadata = params.get("metadata")
|
||||
|
||||
data = {}
|
||||
if metadata:
|
||||
data["metadata"] = json.dumps(metadata)
|
||||
|
||||
return {
|
||||
"files": {"content": (filename, file, content_type)},
|
||||
"data": data,
|
||||
}
|
||||
|
||||
|
||||
def _json_to_file(json: Dict[str, Any]) -> File: # pylint: disable=redefined-outer-name
|
||||
return File(**json)
|
||||
@@ -0,0 +1,68 @@
|
||||
from typing import TYPE_CHECKING, Any, Dict, List
|
||||
|
||||
from typing_extensions import deprecated
|
||||
|
||||
from replicate.resource import Namespace, Resource
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
|
||||
class Hardware(Resource):
|
||||
"""
|
||||
Hardware for running a model on Replicate.
|
||||
"""
|
||||
|
||||
sku: str
|
||||
"""
|
||||
The SKU of the hardware.
|
||||
"""
|
||||
|
||||
name: str
|
||||
"""
|
||||
The name of the hardware.
|
||||
"""
|
||||
|
||||
@property
|
||||
@deprecated("Use `sku` instead of `id`")
|
||||
def id(self) -> str:
|
||||
"""
|
||||
DEPRECATED: Use `sku` instead.
|
||||
"""
|
||||
return self.sku
|
||||
|
||||
|
||||
class HardwareNamespace(Namespace):
|
||||
"""
|
||||
Namespace for operations related to hardware.
|
||||
"""
|
||||
|
||||
def list(self) -> List[Hardware]:
|
||||
"""
|
||||
List all hardware available for you to run models on Replicate.
|
||||
|
||||
Returns:
|
||||
List[Hardware]: A list of hardware.
|
||||
"""
|
||||
|
||||
resp = self._client._request("GET", "/v1/hardware")
|
||||
obj = resp.json()
|
||||
|
||||
return [_json_to_hardware(entry) for entry in obj]
|
||||
|
||||
async def async_list(self) -> List[Hardware]:
|
||||
"""
|
||||
List all hardware available for you to run models on Replicate.
|
||||
|
||||
Returns:
|
||||
List[Hardware]: A list of hardware.
|
||||
"""
|
||||
|
||||
resp = await self._client._async_request("GET", "/v1/hardware")
|
||||
obj = resp.json()
|
||||
|
||||
return [_json_to_hardware(entry) for entry in obj]
|
||||
|
||||
|
||||
def _json_to_hardware(json: Dict[str, Any]) -> Hardware:
|
||||
return Hardware(**json)
|
||||
192
art-agent/backend/venv/Lib/site-packages/replicate/helpers.py
Normal file
192
art-agent/backend/venv/Lib/site-packages/replicate/helpers.py
Normal file
@@ -0,0 +1,192 @@
|
||||
import base64
|
||||
import io
|
||||
import mimetypes
|
||||
from collections.abc import Mapping, Sequence
|
||||
from pathlib import Path
|
||||
from types import GeneratorType
|
||||
from typing import TYPE_CHECKING, Any, AsyncIterator, Iterator, Optional
|
||||
|
||||
import httpx
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from replicate.client import Client
|
||||
from replicate.file import FileEncodingStrategy
|
||||
|
||||
|
||||
try:
|
||||
import numpy as np # type: ignore
|
||||
|
||||
HAS_NUMPY = True
|
||||
except ImportError:
|
||||
HAS_NUMPY = False
|
||||
|
||||
|
||||
# pylint: disable=too-many-return-statements
|
||||
def encode_json(
|
||||
obj: Any, # noqa: ANN401
|
||||
client: "Client",
|
||||
file_encoding_strategy: Optional["FileEncodingStrategy"] = None,
|
||||
) -> Any: # noqa: ANN401
|
||||
"""
|
||||
Return a JSON-compatible version of the object.
|
||||
"""
|
||||
|
||||
if isinstance(obj, dict):
|
||||
return {
|
||||
key: encode_json(value, client, file_encoding_strategy)
|
||||
for key, value in obj.items()
|
||||
}
|
||||
if isinstance(obj, (list, set, frozenset, GeneratorType, tuple)):
|
||||
return [encode_json(value, client, file_encoding_strategy) for value in obj]
|
||||
if isinstance(obj, Path):
|
||||
with obj.open("rb") as file:
|
||||
return encode_json(file, client, file_encoding_strategy)
|
||||
if isinstance(obj, io.IOBase):
|
||||
if file_encoding_strategy == "base64":
|
||||
return base64_encode_file(obj)
|
||||
return client.files.create(obj).urls["get"]
|
||||
if HAS_NUMPY:
|
||||
if isinstance(obj, np.integer): # type: ignore
|
||||
return int(obj)
|
||||
if isinstance(obj, np.floating): # type: ignore
|
||||
return float(obj)
|
||||
if isinstance(obj, np.ndarray): # type: ignore
|
||||
return obj.tolist()
|
||||
return obj
|
||||
|
||||
|
||||
async def async_encode_json(
|
||||
obj: Any, # noqa: ANN401
|
||||
client: "Client",
|
||||
file_encoding_strategy: Optional["FileEncodingStrategy"] = None,
|
||||
) -> Any: # noqa: ANN401
|
||||
"""
|
||||
Asynchronously return a JSON-compatible version of the object.
|
||||
"""
|
||||
|
||||
if isinstance(obj, dict):
|
||||
return {
|
||||
key: (await async_encode_json(value, client, file_encoding_strategy))
|
||||
for key, value in obj.items()
|
||||
}
|
||||
if isinstance(obj, (list, set, frozenset, GeneratorType, tuple)):
|
||||
return [
|
||||
(await async_encode_json(value, client, file_encoding_strategy))
|
||||
for value in obj
|
||||
]
|
||||
if isinstance(obj, Path):
|
||||
with obj.open("rb") as file:
|
||||
return await async_encode_json(file, client, file_encoding_strategy)
|
||||
if isinstance(obj, io.IOBase):
|
||||
if file_encoding_strategy == "base64":
|
||||
# TODO: This should ideally use an async based file reader path.
|
||||
return base64_encode_file(obj)
|
||||
return (await client.files.async_create(obj)).urls["get"]
|
||||
if HAS_NUMPY:
|
||||
if isinstance(obj, np.integer): # type: ignore
|
||||
return int(obj)
|
||||
if isinstance(obj, np.floating): # type: ignore
|
||||
return float(obj)
|
||||
if isinstance(obj, np.ndarray): # type: ignore
|
||||
return obj.tolist()
|
||||
return obj
|
||||
|
||||
|
||||
def base64_encode_file(file: io.IOBase) -> str:
|
||||
"""
|
||||
Base64 encode a file.
|
||||
|
||||
Args:
|
||||
file: A file handle to upload.
|
||||
Returns:
|
||||
str: A base64-encoded data URI.
|
||||
"""
|
||||
|
||||
file.seek(0)
|
||||
body = file.read()
|
||||
|
||||
# Ensure the file handle is in bytes
|
||||
body = body.encode("utf-8") if isinstance(body, str) else body
|
||||
encoded_body = base64.b64encode(body).decode("utf-8")
|
||||
|
||||
mime_type = (
|
||||
mimetypes.guess_type(getattr(file, "name", ""))[0] or "application/octet-stream"
|
||||
)
|
||||
return f"data:{mime_type};base64,{encoded_body}"
|
||||
|
||||
|
||||
class FileOutput(httpx.SyncByteStream, httpx.AsyncByteStream):
|
||||
"""
|
||||
An object that can be used to read the contents of an output file
|
||||
created by running a Replicate model.
|
||||
"""
|
||||
|
||||
url: str
|
||||
"""
|
||||
The file URL.
|
||||
"""
|
||||
|
||||
_client: "Client"
|
||||
|
||||
def __init__(self, url: str, client: "Client") -> None:
|
||||
self.url = url
|
||||
self._client = client
|
||||
|
||||
def read(self) -> bytes:
|
||||
if self.url.startswith("data:"):
|
||||
_, encoded = self.url.split(",", 1)
|
||||
return base64.b64decode(encoded)
|
||||
|
||||
with self._client._client.stream("GET", self.url) as response:
|
||||
response.raise_for_status()
|
||||
return response.read()
|
||||
|
||||
def __iter__(self) -> Iterator[bytes]:
|
||||
if self.url.startswith("data:"):
|
||||
yield self.read()
|
||||
return
|
||||
|
||||
with self._client._client.stream("GET", self.url) as response:
|
||||
response.raise_for_status()
|
||||
yield from response.iter_bytes()
|
||||
|
||||
async def aread(self) -> bytes:
|
||||
if self.url.startswith("data:"):
|
||||
_, encoded = self.url.split(",", 1)
|
||||
return base64.b64decode(encoded)
|
||||
|
||||
async with self._client._async_client.stream("GET", self.url) as response:
|
||||
response.raise_for_status()
|
||||
return await response.aread()
|
||||
|
||||
async def __aiter__(self) -> AsyncIterator[bytes]:
|
||||
if self.url.startswith("data:"):
|
||||
yield await self.aread()
|
||||
return
|
||||
|
||||
async with self._client._async_client.stream("GET", self.url) as response:
|
||||
response.raise_for_status()
|
||||
async for chunk in response.aiter_bytes():
|
||||
yield chunk
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.url
|
||||
|
||||
|
||||
def transform_output(value: Any, client: "Client") -> Any:
|
||||
"""
|
||||
Transform the output of a prediction to a `FileOutput` object if it's a URL.
|
||||
"""
|
||||
|
||||
def transform(obj: Any) -> Any:
|
||||
if isinstance(obj, Mapping):
|
||||
return {k: transform(v) for k, v in obj.items()}
|
||||
if isinstance(obj, Sequence) and not isinstance(obj, str):
|
||||
return [transform(item) for item in obj]
|
||||
if isinstance(obj, str) and (
|
||||
obj.startswith("https:") or obj.startswith("data:")
|
||||
):
|
||||
return FileOutput(obj, client)
|
||||
return obj
|
||||
|
||||
return transform(value)
|
||||
@@ -0,0 +1,50 @@
|
||||
import re
|
||||
from typing import TYPE_CHECKING, NamedTuple, Optional, Tuple, Union
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from replicate.model import Model
|
||||
from replicate.version import Version
|
||||
|
||||
|
||||
class ModelVersionIdentifier(NamedTuple):
|
||||
"""
|
||||
A reference to a model version in the format owner/name or owner/name:version.
|
||||
"""
|
||||
|
||||
owner: str
|
||||
name: str
|
||||
version: Optional[str] = None
|
||||
|
||||
@classmethod
|
||||
def parse(cls, ref: str) -> "ModelVersionIdentifier":
|
||||
"""
|
||||
Split a reference in the format owner/name:version into its components.
|
||||
"""
|
||||
|
||||
match = re.match(r"^(?P<owner>[^/]+)/(?P<name>[^/:]+)(:(?P<version>.+))?$", ref)
|
||||
if not match:
|
||||
raise ValueError(
|
||||
f"Invalid reference to model version: {ref}. Expected format: owner/name:version"
|
||||
)
|
||||
|
||||
return cls(match.group("owner"), match.group("name"), match.group("version"))
|
||||
|
||||
|
||||
def _resolve(
|
||||
ref: Union["Model", "Version", "ModelVersionIdentifier", str],
|
||||
) -> Tuple[Optional["Version"], Optional[str], Optional[str], Optional[str]]:
|
||||
from replicate.model import Model # pylint: disable=import-outside-toplevel
|
||||
from replicate.version import Version # pylint: disable=import-outside-toplevel
|
||||
|
||||
version = None
|
||||
owner, name, version_id = None, None, None
|
||||
if isinstance(ref, Model):
|
||||
owner, name = ref.owner, ref.name
|
||||
elif isinstance(ref, Version):
|
||||
version = ref
|
||||
version_id = ref.id
|
||||
elif isinstance(ref, ModelVersionIdentifier):
|
||||
owner, name, version_id = ref
|
||||
elif isinstance(ref, str):
|
||||
owner, name, version_id = ModelVersionIdentifier.parse(ref)
|
||||
return version, owner, name, version_id
|
||||
538
art-agent/backend/venv/Lib/site-packages/replicate/model.py
Normal file
538
art-agent/backend/venv/Lib/site-packages/replicate/model.py
Normal file
@@ -0,0 +1,538 @@
|
||||
from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Tuple, Union, overload
|
||||
|
||||
from typing_extensions import NotRequired, TypedDict, Unpack, deprecated
|
||||
|
||||
from replicate.exceptions import ReplicateException
|
||||
from replicate.helpers import async_encode_json, encode_json
|
||||
from replicate.identifier import ModelVersionIdentifier
|
||||
from replicate.pagination import Page
|
||||
from replicate.prediction import (
|
||||
Prediction,
|
||||
_create_prediction_body,
|
||||
_create_prediction_request_params,
|
||||
_json_to_prediction,
|
||||
)
|
||||
from replicate.resource import Namespace, Resource
|
||||
from replicate.version import Version, Versions
|
||||
|
||||
try:
|
||||
from pydantic import v1 as pydantic # type: ignore
|
||||
except ImportError:
|
||||
import pydantic # type: ignore
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from replicate.client import Client
|
||||
from replicate.prediction import Predictions
|
||||
|
||||
|
||||
class Model(Resource):
|
||||
"""
|
||||
A machine learning model hosted on Replicate.
|
||||
"""
|
||||
|
||||
_client: "Client" = pydantic.PrivateAttr()
|
||||
|
||||
url: str
|
||||
"""
|
||||
The URL of the model.
|
||||
"""
|
||||
|
||||
owner: str
|
||||
"""
|
||||
The owner of the model.
|
||||
"""
|
||||
|
||||
name: str
|
||||
"""
|
||||
The name of the model.
|
||||
"""
|
||||
|
||||
description: Optional[str]
|
||||
"""
|
||||
The description of the model.
|
||||
"""
|
||||
|
||||
visibility: Literal["public", "private"]
|
||||
"""
|
||||
The visibility of the model. Can be 'public' or 'private'.
|
||||
"""
|
||||
|
||||
github_url: Optional[str]
|
||||
"""
|
||||
The GitHub URL of the model.
|
||||
"""
|
||||
|
||||
paper_url: Optional[str]
|
||||
"""
|
||||
The URL of the paper related to the model.
|
||||
"""
|
||||
|
||||
license_url: Optional[str]
|
||||
"""
|
||||
The URL of the license for the model.
|
||||
"""
|
||||
|
||||
run_count: int
|
||||
"""
|
||||
The number of runs of the model.
|
||||
"""
|
||||
|
||||
cover_image_url: Optional[str]
|
||||
"""
|
||||
The URL of the cover image for the model.
|
||||
"""
|
||||
|
||||
default_example: Optional[Prediction]
|
||||
"""
|
||||
The default example of the model.
|
||||
"""
|
||||
|
||||
latest_version: Optional[Version]
|
||||
"""
|
||||
The latest version of the model.
|
||||
"""
|
||||
|
||||
@property
|
||||
def id(self) -> str:
|
||||
"""
|
||||
Return the qualified model name, in the format `owner/name`.
|
||||
"""
|
||||
return f"{self.owner}/{self.name}"
|
||||
|
||||
@property
|
||||
@deprecated("Use `model.owner` instead.")
|
||||
def username(self) -> str:
|
||||
"""
|
||||
The name of the user or organization that owns the model.
|
||||
This attribute is deprecated and will be removed in future versions.
|
||||
"""
|
||||
return self.owner
|
||||
|
||||
@username.setter
|
||||
@deprecated("Use `model.owner` instead.")
|
||||
def username(self, value: str) -> None:
|
||||
self.owner = value
|
||||
|
||||
def predict(self, *args, **kwargs) -> None:
|
||||
"""
|
||||
DEPRECATED: Use `replicate.run()` instead.
|
||||
"""
|
||||
|
||||
raise ReplicateException(
|
||||
"The `model.predict()` method has been removed, because it's unstable: if a new version of the model you're using is pushed and its API has changed, your code may break. Use `replicate.run()` instead. See https://github.com/replicate/replicate-python#readme"
|
||||
)
|
||||
|
||||
@property
|
||||
def versions(self) -> Versions:
|
||||
"""
|
||||
Get the versions of this model.
|
||||
"""
|
||||
|
||||
return Versions(client=self._client, model=self)
|
||||
|
||||
def reload(self) -> None:
|
||||
"""
|
||||
Load this object from the server.
|
||||
"""
|
||||
|
||||
obj = self._client.models.get(f"{self.owner}/{self.name}")
|
||||
for name, value in obj.dict().items():
|
||||
setattr(self, name, value)
|
||||
|
||||
|
||||
class Models(Namespace):
|
||||
"""
|
||||
Namespace for operations related to models.
|
||||
"""
|
||||
|
||||
model = Model
|
||||
|
||||
@property
|
||||
def predictions(self) -> "ModelsPredictions":
|
||||
"""
|
||||
Get a namespace for operations related to predictions on a model.
|
||||
"""
|
||||
|
||||
return ModelsPredictions(client=self._client)
|
||||
|
||||
def list(self, cursor: Union[str, "ellipsis", None] = ...) -> Page[Model]: # noqa: F821
|
||||
"""
|
||||
List all public models.
|
||||
|
||||
Parameters:
|
||||
cursor: The cursor to use for pagination. Use the value of `Page.next` or `Page.previous`.
|
||||
Returns:
|
||||
Page[Model]: A page of of models.
|
||||
Raises:
|
||||
ValueError: If `cursor` is `None`.
|
||||
"""
|
||||
|
||||
if cursor is None:
|
||||
raise ValueError("cursor cannot be None")
|
||||
|
||||
resp = self._client._request("GET", "/v1/models" if cursor is ... else cursor)
|
||||
|
||||
obj = resp.json()
|
||||
obj["results"] = [
|
||||
_json_to_model(self._client, result) for result in obj["results"]
|
||||
]
|
||||
|
||||
return Page[Model](**obj)
|
||||
|
||||
async def async_list(
|
||||
self,
|
||||
cursor: Union[str, "ellipsis", None] = ..., # noqa: F821
|
||||
) -> Page[Model]:
|
||||
"""
|
||||
List all public models.
|
||||
|
||||
Parameters:
|
||||
cursor: The cursor to use for pagination. Use the value of `Page.next` or `Page.previous`.
|
||||
Returns:
|
||||
Page[Model]: A page of of models.
|
||||
Raises:
|
||||
ValueError: If `cursor` is `None`.
|
||||
"""
|
||||
|
||||
if cursor is None:
|
||||
raise ValueError("cursor cannot be None")
|
||||
|
||||
resp = await self._client._async_request(
|
||||
"GET", "/v1/models" if cursor is ... else cursor
|
||||
)
|
||||
|
||||
obj = resp.json()
|
||||
obj["results"] = [
|
||||
_json_to_model(self._client, result) for result in obj["results"]
|
||||
]
|
||||
|
||||
return Page[Model](**obj)
|
||||
|
||||
def search(self, query: str) -> Page[Model]:
|
||||
"""
|
||||
Search for public models.
|
||||
|
||||
Parameters:
|
||||
query: The search query.
|
||||
Returns:
|
||||
Page[Model]: A page of models matching the search query.
|
||||
"""
|
||||
resp = self._client._request(
|
||||
"QUERY", "/v1/models", content=query, headers={"Content-Type": "text/plain"}
|
||||
)
|
||||
|
||||
obj = resp.json()
|
||||
obj["results"] = [
|
||||
_json_to_model(self._client, result) for result in obj["results"]
|
||||
]
|
||||
|
||||
return Page[Model](**obj)
|
||||
|
||||
async def async_search(self, query: str) -> Page[Model]:
|
||||
"""
|
||||
Asynchronously search for public models.
|
||||
|
||||
Parameters:
|
||||
query: The search query.
|
||||
Returns:
|
||||
Page[Model]: A page of models matching the search query.
|
||||
"""
|
||||
resp = await self._client._async_request(
|
||||
"QUERY", "/v1/models", content=query, headers={"Content-Type": "text/plain"}
|
||||
)
|
||||
|
||||
obj = resp.json()
|
||||
obj["results"] = [
|
||||
_json_to_model(self._client, result) for result in obj["results"]
|
||||
]
|
||||
|
||||
return Page[Model](**obj)
|
||||
|
||||
@overload
|
||||
def get(self, key: str) -> Model: ...
|
||||
|
||||
@overload
|
||||
def get(self, owner: str, name: str) -> Model: ...
|
||||
|
||||
def get(self, *args, **kwargs) -> Model:
|
||||
"""
|
||||
Get a model by name.
|
||||
"""
|
||||
|
||||
url = _get_model_url(*args, **kwargs)
|
||||
resp = self._client._request("GET", url)
|
||||
|
||||
return _json_to_model(self._client, resp.json())
|
||||
|
||||
@overload
|
||||
async def async_get(self, key: str) -> Model: ...
|
||||
|
||||
@overload
|
||||
async def async_get(self, owner: str, name: str) -> Model: ...
|
||||
|
||||
async def async_get(self, *args, **kwargs) -> Model:
|
||||
"""
|
||||
Get a model by name.
|
||||
|
||||
Args:
|
||||
key: The qualified name of the model, in the format `owner/name`.
|
||||
Returns:
|
||||
The model.
|
||||
"""
|
||||
|
||||
url = _get_model_url(*args, **kwargs)
|
||||
resp = await self._client._async_request("GET", url)
|
||||
|
||||
return _json_to_model(self._client, resp.json())
|
||||
|
||||
@overload
|
||||
def delete(self, key: str) -> bool: ...
|
||||
|
||||
@overload
|
||||
def delete(self, owner: str, name: str) -> bool: ...
|
||||
|
||||
def delete(self, *args, **kwargs) -> bool:
|
||||
"""
|
||||
Delete a model by name.
|
||||
|
||||
Returns:
|
||||
`True` if deletion was successful, otherwise `False`.
|
||||
"""
|
||||
url = _delete_model_url(*args, **kwargs)
|
||||
resp = self._client._request("DELETE", url)
|
||||
return resp.status_code == 204
|
||||
|
||||
@overload
|
||||
async def async_delete(self, key: str) -> bool: ...
|
||||
|
||||
@overload
|
||||
async def async_delete(self, owner: str, name: str) -> bool: ...
|
||||
|
||||
async def async_delete(self, *args, **kwargs) -> bool:
|
||||
"""
|
||||
Asynchronously delete a model by name.
|
||||
|
||||
Returns:
|
||||
`True` if deletion was successful, otherwise `False`.
|
||||
"""
|
||||
url = _delete_model_url(*args, **kwargs)
|
||||
resp = await self._client._async_request("DELETE", url)
|
||||
return resp.status_code == 204
|
||||
|
||||
class CreateModelParams(TypedDict):
|
||||
"""Parameters for creating a model."""
|
||||
|
||||
hardware: str
|
||||
"""The SKU for the hardware used to run the model.
|
||||
|
||||
Possible values can be found by calling `replicate.hardware.list()`."""
|
||||
|
||||
visibility: Literal["public", "private"]
|
||||
"""Whether the model should be public or private."""
|
||||
|
||||
description: NotRequired[str]
|
||||
"""The description of the model."""
|
||||
|
||||
github_url: NotRequired[str]
|
||||
"""A URL for the model's source code on GitHub."""
|
||||
|
||||
paper_url: NotRequired[str]
|
||||
"""A URL for the model's paper."""
|
||||
|
||||
license_url: NotRequired[str]
|
||||
"""A URL for the model's license."""
|
||||
|
||||
cover_image_url: NotRequired[str]
|
||||
"""A URL for the model's cover image."""
|
||||
|
||||
def create(
|
||||
self,
|
||||
owner: str,
|
||||
name: str,
|
||||
**params: Unpack["Models.CreateModelParams"],
|
||||
) -> Model:
|
||||
"""
|
||||
Create a model.
|
||||
"""
|
||||
|
||||
body = _create_model_body(owner, name, **params)
|
||||
resp = self._client._request("POST", "/v1/models", json=body)
|
||||
|
||||
return _json_to_model(self._client, resp.json())
|
||||
|
||||
async def async_create(
|
||||
self, owner: str, name: str, **params: Unpack["Models.CreateModelParams"]
|
||||
) -> Model:
|
||||
"""
|
||||
Create a model.
|
||||
"""
|
||||
|
||||
body = body = _create_model_body(owner, name, **params)
|
||||
resp = await self._client._async_request("POST", "/v1/models", json=body)
|
||||
|
||||
return _json_to_model(self._client, resp.json())
|
||||
|
||||
|
||||
class ModelsPredictions(Namespace):
|
||||
"""
|
||||
Namespace for operations related to predictions in a deployment.
|
||||
"""
|
||||
|
||||
def create(
|
||||
self,
|
||||
model: Union[str, Tuple[str, str], "Model"],
|
||||
input: Dict[str, Any],
|
||||
**params: Unpack["Predictions.CreatePredictionParams"],
|
||||
) -> Prediction:
|
||||
"""
|
||||
Create a new prediction with the deployment.
|
||||
"""
|
||||
|
||||
wait = params.pop("wait", None)
|
||||
file_encoding_strategy = params.pop("file_encoding_strategy", None)
|
||||
|
||||
path = _create_prediction_path_from_model(model)
|
||||
if input is not None:
|
||||
input = encode_json(
|
||||
input,
|
||||
client=self._client,
|
||||
file_encoding_strategy=file_encoding_strategy,
|
||||
)
|
||||
|
||||
body = _create_prediction_body(version=None, input=input, **params)
|
||||
extras = _create_prediction_request_params(wait=wait)
|
||||
resp = self._client._request("POST", path, json=body, **extras)
|
||||
|
||||
return _json_to_prediction(self._client, resp.json())
|
||||
|
||||
async def async_create(
|
||||
self,
|
||||
model: Union[str, Tuple[str, str], "Model"],
|
||||
input: Dict[str, Any],
|
||||
**params: Unpack["Predictions.CreatePredictionParams"],
|
||||
) -> Prediction:
|
||||
"""
|
||||
Create a new prediction with the deployment.
|
||||
"""
|
||||
|
||||
wait = params.pop("wait", None)
|
||||
file_encoding_strategy = params.pop("file_encoding_strategy", None)
|
||||
|
||||
path = _create_prediction_path_from_model(model)
|
||||
|
||||
if input is not None:
|
||||
input = await async_encode_json(
|
||||
input,
|
||||
client=self._client,
|
||||
file_encoding_strategy=file_encoding_strategy,
|
||||
)
|
||||
|
||||
body = _create_prediction_body(version=None, input=input, **params)
|
||||
extras = _create_prediction_request_params(wait=wait)
|
||||
resp = await self._client._async_request("POST", path, json=body, **extras)
|
||||
|
||||
return _json_to_prediction(self._client, resp.json())
|
||||
|
||||
|
||||
def _create_model_body( # pylint: disable=too-many-arguments
|
||||
owner: str,
|
||||
name: str,
|
||||
*,
|
||||
visibility: str,
|
||||
hardware: str,
|
||||
description: Optional[str] = None,
|
||||
github_url: Optional[str] = None,
|
||||
paper_url: Optional[str] = None,
|
||||
license_url: Optional[str] = None,
|
||||
cover_image_url: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
body = {
|
||||
"owner": owner,
|
||||
"name": name,
|
||||
"visibility": visibility,
|
||||
"hardware": hardware,
|
||||
}
|
||||
|
||||
if description is not None:
|
||||
body["description"] = description
|
||||
|
||||
if github_url is not None:
|
||||
body["github_url"] = github_url
|
||||
|
||||
if paper_url is not None:
|
||||
body["paper_url"] = paper_url
|
||||
|
||||
if license_url is not None:
|
||||
body["license_url"] = license_url
|
||||
|
||||
if cover_image_url is not None:
|
||||
body["cover_image_url"] = cover_image_url
|
||||
|
||||
return body
|
||||
|
||||
|
||||
def _get_model_url(*args, **kwargs) -> str:
|
||||
if len(args) > 0 and len(kwargs) > 0:
|
||||
raise ValueError("Cannot mix positional and keyword arguments")
|
||||
|
||||
owner = kwargs.get("owner", None)
|
||||
name = kwargs.get("name", None)
|
||||
key = kwargs.get("key", None)
|
||||
|
||||
if key and (owner or name):
|
||||
raise ValueError(
|
||||
"Must specify exactly one of 'owner' and 'name' or single 'key' in the format 'owner/name'"
|
||||
)
|
||||
|
||||
if args:
|
||||
if len(args) == 1:
|
||||
key = args[0]
|
||||
elif len(args) == 2:
|
||||
owner, name = args
|
||||
else:
|
||||
raise ValueError("Invalid number of arguments")
|
||||
|
||||
if not key:
|
||||
if not (owner and name):
|
||||
raise ValueError(
|
||||
"Both 'owner' and 'name' must be provided if 'key' is not specified."
|
||||
)
|
||||
key = f"{owner}/{name}"
|
||||
|
||||
return f"/v1/models/{key}"
|
||||
|
||||
|
||||
def _delete_model_url(*args, **kwargs) -> str:
|
||||
return _get_model_url(*args, **kwargs)
|
||||
|
||||
|
||||
def _json_to_model(client: "Client", json: Dict[str, Any]) -> Model:
|
||||
model = Model(**json)
|
||||
model._client = client
|
||||
if model.default_example is not None:
|
||||
model.default_example._client = client
|
||||
return model
|
||||
|
||||
|
||||
def _create_prediction_path_from_model(
|
||||
model: Union[str, Tuple[str, str], "Model"],
|
||||
) -> str:
|
||||
owner, name = None, None
|
||||
if isinstance(model, Model):
|
||||
owner, name = model.owner, model.name
|
||||
elif isinstance(model, tuple):
|
||||
owner, name = model[0], model[1]
|
||||
elif isinstance(model, str):
|
||||
owner, name, version_id = ModelVersionIdentifier.parse(model)
|
||||
if version_id is not None:
|
||||
raise ValueError(
|
||||
f"Invalid reference to model version: {model}. Expected model or reference in the format owner/name"
|
||||
)
|
||||
|
||||
if owner is None or name is None:
|
||||
raise ValueError(
|
||||
"model must be a Model, a tuple of (owner, name), or a string in the format 'owner/name'"
|
||||
)
|
||||
|
||||
return f"/v1/models/{owner}/{name}/predictions"
|
||||
@@ -0,0 +1,80 @@
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
AsyncGenerator,
|
||||
Awaitable,
|
||||
Callable,
|
||||
Generator,
|
||||
Generic,
|
||||
List,
|
||||
Optional,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
|
||||
try:
|
||||
from pydantic import v1 as pydantic # type: ignore
|
||||
except ImportError:
|
||||
import pydantic # type: ignore
|
||||
|
||||
from replicate.resource import Resource
|
||||
|
||||
T = TypeVar("T", bound=Resource)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
|
||||
class Page(pydantic.BaseModel, Generic[T]): # type: ignore
|
||||
"""
|
||||
A page of results from the API.
|
||||
"""
|
||||
|
||||
previous: Optional[str] = None
|
||||
"""A pointer to the previous page of results"""
|
||||
|
||||
next: Optional[str] = None
|
||||
"""A pointer to the next page of results"""
|
||||
|
||||
results: List[T]
|
||||
"""The results on this page"""
|
||||
|
||||
def __iter__(self): # noqa: ANN204
|
||||
return iter(self.results)
|
||||
|
||||
def __getitem__(self, index: int) -> T:
|
||||
return self.results[index]
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.results)
|
||||
|
||||
|
||||
def paginate(
|
||||
list_method: Callable[[Union[str, "ellipsis", None]], Page[T]], # noqa: F821
|
||||
) -> Generator[Page[T], None, None]:
|
||||
"""
|
||||
Iterate over all items using the provided list method.
|
||||
|
||||
Args:
|
||||
list_method: A method that takes a cursor argument and returns a Page of items.
|
||||
"""
|
||||
cursor: Union[str, "ellipsis", None] = ... # noqa: F821
|
||||
while cursor is not None:
|
||||
page = list_method(cursor)
|
||||
yield page
|
||||
cursor = page.next
|
||||
|
||||
|
||||
async def async_paginate(
|
||||
list_method: Callable[[Union[str, "ellipsis", None]], Awaitable[Page[T]]], # noqa: F821
|
||||
) -> AsyncGenerator[Page[T], None]:
|
||||
"""
|
||||
Asynchronously iterate over all items using the provided list method.
|
||||
|
||||
Args:
|
||||
list_method: An async method that takes a cursor argument and returns a Page of items.
|
||||
"""
|
||||
cursor: Union[str, "ellipsis", None] = ... # noqa: F821
|
||||
while cursor is not None:
|
||||
page = await list_method(cursor)
|
||||
yield page
|
||||
cursor = page.next
|
||||
710
art-agent/backend/venv/Lib/site-packages/replicate/prediction.py
Normal file
710
art-agent/backend/venv/Lib/site-packages/replicate/prediction.py
Normal file
@@ -0,0 +1,710 @@
|
||||
import asyncio
|
||||
import re
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
AsyncIterator,
|
||||
Dict,
|
||||
Iterator,
|
||||
List,
|
||||
Literal,
|
||||
Optional,
|
||||
Tuple,
|
||||
Union,
|
||||
overload,
|
||||
)
|
||||
|
||||
import httpx
|
||||
from typing_extensions import NotRequired, TypedDict, Unpack
|
||||
|
||||
from replicate.exceptions import ModelError, ReplicateError
|
||||
from replicate.file import FileEncodingStrategy
|
||||
from replicate.helpers import async_encode_json, encode_json
|
||||
from replicate.pagination import Page
|
||||
from replicate.resource import Namespace, Resource
|
||||
from replicate.stream import EventSource
|
||||
from replicate.version import Version
|
||||
|
||||
try:
|
||||
from pydantic import v1 as pydantic # type: ignore
|
||||
except ImportError:
|
||||
import pydantic # type: ignore
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from replicate.client import Client
|
||||
from replicate.deployment import Deployment
|
||||
from replicate.model import Model
|
||||
from replicate.stream import ServerSentEvent
|
||||
|
||||
|
||||
class Prediction(Resource):
|
||||
"""
|
||||
A prediction made by a model hosted on Replicate.
|
||||
"""
|
||||
|
||||
_client: "Client" = pydantic.PrivateAttr()
|
||||
|
||||
id: str
|
||||
"""The unique ID of the prediction."""
|
||||
|
||||
model: str
|
||||
"""An identifier for the model used to create the prediction, in the form `owner/name`."""
|
||||
|
||||
version: str
|
||||
"""An identifier for the version of the model used to create the prediction."""
|
||||
|
||||
status: Literal["starting", "processing", "succeeded", "failed", "canceled"]
|
||||
"""The status of the prediction."""
|
||||
|
||||
input: Optional[Dict[str, Any]]
|
||||
"""The input to the prediction."""
|
||||
|
||||
output: Optional[Any]
|
||||
"""The output of the prediction."""
|
||||
|
||||
logs: Optional[str]
|
||||
"""The logs of the prediction."""
|
||||
|
||||
error: Optional[str]
|
||||
"""The error encountered during the prediction, if any."""
|
||||
|
||||
metrics: Optional[Dict[str, Any]]
|
||||
"""Metrics for the prediction."""
|
||||
|
||||
created_at: Optional[str]
|
||||
"""When the prediction was created."""
|
||||
|
||||
started_at: Optional[str]
|
||||
"""When the prediction was started."""
|
||||
|
||||
completed_at: Optional[str]
|
||||
"""When the prediction was completed, if finished."""
|
||||
|
||||
urls: Optional[Dict[str, str]]
|
||||
"""
|
||||
URLs associated with the prediction.
|
||||
|
||||
The following keys are available:
|
||||
- `get`: A URL to fetch the prediction.
|
||||
- `cancel`: A URL to cancel the prediction.
|
||||
"""
|
||||
|
||||
@dataclass
|
||||
class Progress:
|
||||
"""
|
||||
The progress of a prediction.
|
||||
"""
|
||||
|
||||
percentage: float
|
||||
"""The percentage of the prediction that has completed."""
|
||||
|
||||
current: int
|
||||
"""The number of items that have been processed."""
|
||||
|
||||
total: int
|
||||
"""The total number of items to process."""
|
||||
|
||||
_pattern = re.compile(
|
||||
r"^\s*(?P<percentage>\d+)%\s*\|.+?\|\s*(?P<current>\d+)\/(?P<total>\d+)"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def parse(cls, logs: str) -> Optional["Prediction.Progress"]:
|
||||
"""Parse the progress from the logs of a prediction."""
|
||||
|
||||
lines = logs.split("\n")
|
||||
for idx in reversed(range(len(lines))):
|
||||
line = lines[idx].strip()
|
||||
if cls._pattern.match(line):
|
||||
matches = cls._pattern.findall(line)
|
||||
if len(matches) == 1:
|
||||
percentage, current, total = map(int, matches[0])
|
||||
return cls(percentage / 100.0, current, total)
|
||||
|
||||
return None
|
||||
|
||||
@property
|
||||
def progress(self) -> Optional[Progress]:
|
||||
"""
|
||||
The progress of the prediction, if available.
|
||||
"""
|
||||
|
||||
if self.logs is None or self.logs == "":
|
||||
return None
|
||||
|
||||
return Prediction.Progress.parse(self.logs)
|
||||
|
||||
def wait(self) -> None:
|
||||
"""
|
||||
Wait for prediction to finish.
|
||||
"""
|
||||
|
||||
while self.status not in ["succeeded", "failed", "canceled"]:
|
||||
time.sleep(self._client.poll_interval)
|
||||
self.reload()
|
||||
|
||||
async def async_wait(self) -> None:
|
||||
"""
|
||||
Wait for prediction to finish asynchronously.
|
||||
"""
|
||||
|
||||
while self.status not in ["succeeded", "failed", "canceled"]:
|
||||
await asyncio.sleep(self._client.poll_interval)
|
||||
await self.async_reload()
|
||||
|
||||
def stream(
|
||||
self,
|
||||
use_file_output: Optional[bool] = None,
|
||||
) -> Iterator["ServerSentEvent"]:
|
||||
"""
|
||||
Stream the prediction output.
|
||||
|
||||
Raises:
|
||||
ReplicateError: If the model does not support streaming.
|
||||
"""
|
||||
|
||||
url = self.urls and self.urls.get("stream", None)
|
||||
if not url or not isinstance(url, str):
|
||||
raise ReplicateError("Model does not support streaming")
|
||||
|
||||
headers = {}
|
||||
headers["Accept"] = "text/event-stream"
|
||||
headers["Cache-Control"] = "no-store"
|
||||
|
||||
with self._client._client.stream("GET", url, headers=headers) as response:
|
||||
yield from EventSource(
|
||||
self._client, response, use_file_output=use_file_output
|
||||
)
|
||||
|
||||
async def async_stream(
|
||||
self,
|
||||
use_file_output: Optional[bool] = None,
|
||||
) -> AsyncIterator["ServerSentEvent"]:
|
||||
"""
|
||||
Stream the prediction output asynchronously.
|
||||
|
||||
Raises:
|
||||
ReplicateError: If the model does not support streaming.
|
||||
"""
|
||||
|
||||
# no-op to enforce the use of 'await' when calling this method
|
||||
await asyncio.sleep(0)
|
||||
|
||||
url = self.urls and self.urls.get("stream", None)
|
||||
if not url or not isinstance(url, str):
|
||||
raise ReplicateError("Model does not support streaming")
|
||||
|
||||
headers = {}
|
||||
headers["Accept"] = "text/event-stream"
|
||||
headers["Cache-Control"] = "no-store"
|
||||
|
||||
async with self._client._async_client.stream(
|
||||
"GET", url, headers=headers
|
||||
) as response:
|
||||
async for event in EventSource(
|
||||
self._client, response, use_file_output=use_file_output
|
||||
):
|
||||
yield event
|
||||
|
||||
def cancel(self) -> None:
|
||||
"""
|
||||
Cancels a running prediction.
|
||||
"""
|
||||
|
||||
canceled = self._client.predictions.cancel(self.id)
|
||||
for name, value in canceled.dict().items():
|
||||
setattr(self, name, value)
|
||||
|
||||
async def async_cancel(self) -> None:
|
||||
"""
|
||||
Cancels a running prediction asynchronously.
|
||||
"""
|
||||
|
||||
canceled = await self._client.predictions.async_cancel(self.id)
|
||||
for name, value in canceled.dict().items():
|
||||
setattr(self, name, value)
|
||||
|
||||
def reload(self) -> None:
|
||||
"""
|
||||
Load this prediction from the server.
|
||||
"""
|
||||
|
||||
updated = self._client.predictions.get(self.id)
|
||||
for name, value in updated.dict().items():
|
||||
setattr(self, name, value)
|
||||
|
||||
async def async_reload(self) -> None:
|
||||
"""
|
||||
Load this prediction from the server asynchronously.
|
||||
"""
|
||||
|
||||
updated = await self._client.predictions.async_get(self.id)
|
||||
for name, value in updated.dict().items():
|
||||
setattr(self, name, value)
|
||||
|
||||
def output_iterator(self) -> Iterator[Any]:
|
||||
"""
|
||||
Return an iterator of the prediction output.
|
||||
"""
|
||||
|
||||
# TODO: check output is list
|
||||
previous_output = self.output or []
|
||||
while self.status not in ["succeeded", "failed", "canceled"]:
|
||||
output = self.output or []
|
||||
new_output = output[len(previous_output) :]
|
||||
yield from new_output
|
||||
previous_output = output
|
||||
time.sleep(self._client.poll_interval) # pylint: disable=no-member
|
||||
self.reload()
|
||||
|
||||
if self.status == "failed":
|
||||
raise ModelError(self)
|
||||
|
||||
output = self.output or []
|
||||
new_output = output[len(previous_output) :]
|
||||
yield from new_output
|
||||
|
||||
async def async_output_iterator(self) -> AsyncIterator[Any]:
|
||||
"""
|
||||
Return an asynchronous iterator of the prediction output.
|
||||
"""
|
||||
|
||||
# TODO: check output is list
|
||||
previous_output = self.output or []
|
||||
while self.status not in ["succeeded", "failed", "canceled"]:
|
||||
output = self.output or []
|
||||
new_output = output[len(previous_output) :]
|
||||
for item in new_output:
|
||||
yield item
|
||||
previous_output = output
|
||||
await asyncio.sleep(self._client.poll_interval) # pylint: disable=no-member
|
||||
await self.async_reload()
|
||||
|
||||
if self.status == "failed":
|
||||
raise ModelError(self)
|
||||
|
||||
output = self.output or []
|
||||
new_output = output[len(previous_output) :]
|
||||
for output in new_output:
|
||||
yield output
|
||||
|
||||
|
||||
class Predictions(Namespace):
|
||||
"""
|
||||
Namespace for operations related to predictions.
|
||||
"""
|
||||
|
||||
def list(self, cursor: Union[str, "ellipsis", None] = ...) -> Page[Prediction]: # noqa: F821
|
||||
"""
|
||||
List your predictions.
|
||||
|
||||
Parameters:
|
||||
cursor: The cursor to use for pagination. Use the value of `Page.next` or `Page.previous`.
|
||||
Returns:
|
||||
Page[Prediction]: A page of of predictions.
|
||||
Raises:
|
||||
ValueError: If `cursor` is `None`.
|
||||
"""
|
||||
|
||||
if cursor is None:
|
||||
raise ValueError("cursor cannot be None")
|
||||
|
||||
resp = self._client._request(
|
||||
"GET", "/v1/predictions" if cursor is ... else cursor
|
||||
)
|
||||
|
||||
obj = resp.json()
|
||||
obj["results"] = [
|
||||
_json_to_prediction(self._client, result) for result in obj["results"]
|
||||
]
|
||||
|
||||
return Page[Prediction](**obj)
|
||||
|
||||
async def async_list(
|
||||
self,
|
||||
cursor: Union[str, "ellipsis", None] = ..., # noqa: F821
|
||||
) -> Page[Prediction]:
|
||||
"""
|
||||
List your predictions.
|
||||
|
||||
Parameters:
|
||||
cursor: The cursor to use for pagination. Use the value of `Page.next` or `Page.previous`.
|
||||
Returns:
|
||||
Page[Prediction]: A page of of predictions.
|
||||
Raises:
|
||||
ValueError: If `cursor` is `None`.
|
||||
"""
|
||||
|
||||
if cursor is None:
|
||||
raise ValueError("cursor cannot be None")
|
||||
|
||||
resp = await self._client._async_request(
|
||||
"GET", "/v1/predictions" if cursor is ... else cursor
|
||||
)
|
||||
|
||||
obj = resp.json()
|
||||
obj["results"] = [
|
||||
_json_to_prediction(self._client, result) for result in obj["results"]
|
||||
]
|
||||
|
||||
return Page[Prediction](**obj)
|
||||
|
||||
def get(self, id: str) -> Prediction:
|
||||
"""
|
||||
Get a prediction by ID.
|
||||
|
||||
Args:
|
||||
id: The ID of the prediction.
|
||||
Returns:
|
||||
Prediction: The prediction object.
|
||||
"""
|
||||
|
||||
resp = self._client._request("GET", f"/v1/predictions/{id}")
|
||||
|
||||
return _json_to_prediction(self._client, resp.json())
|
||||
|
||||
async def async_get(self, id: str) -> Prediction:
|
||||
"""
|
||||
Get a prediction by ID.
|
||||
|
||||
Args:
|
||||
id: The ID of the prediction.
|
||||
Returns:
|
||||
Prediction: The prediction object.
|
||||
"""
|
||||
|
||||
resp = await self._client._async_request("GET", f"/v1/predictions/{id}")
|
||||
|
||||
return _json_to_prediction(self._client, resp.json())
|
||||
|
||||
class CreatePredictionParams(TypedDict):
|
||||
"""Parameters for creating a prediction."""
|
||||
|
||||
webhook: NotRequired[str]
|
||||
"""The URL to receive a POST request with prediction updates."""
|
||||
|
||||
webhook_completed: NotRequired[str]
|
||||
"""The URL to receive a POST request when the prediction is completed."""
|
||||
|
||||
webhook_events_filter: NotRequired[List[str]]
|
||||
"""List of events to trigger webhooks."""
|
||||
|
||||
stream: NotRequired[bool]
|
||||
"""Enable streaming of prediction output."""
|
||||
|
||||
wait: NotRequired[Union[int, bool]]
|
||||
"""
|
||||
Block until the prediction is completed before returning.
|
||||
|
||||
If `True`, keep the request open for up to 60 seconds, falling back to
|
||||
polling until the prediction is completed.
|
||||
If an `int`, same as True but hold the request for a specified number of
|
||||
seconds (between 1 and 60).
|
||||
If `False`, poll for the prediction status until completed.
|
||||
"""
|
||||
|
||||
file_encoding_strategy: NotRequired[FileEncodingStrategy]
|
||||
"""The strategy to use for encoding files in the prediction input."""
|
||||
|
||||
@overload
|
||||
def create(
|
||||
self,
|
||||
version: Union[Version, str],
|
||||
input: Optional[Dict[str, Any]],
|
||||
**params: Unpack["Predictions.CreatePredictionParams"],
|
||||
) -> Prediction: ...
|
||||
|
||||
@overload
|
||||
def create(
|
||||
self,
|
||||
*,
|
||||
model: Union[str, Tuple[str, str], "Model"],
|
||||
input: Optional[Dict[str, Any]],
|
||||
**params: Unpack["Predictions.CreatePredictionParams"],
|
||||
) -> Prediction: ...
|
||||
|
||||
@overload
|
||||
def create(
|
||||
self,
|
||||
*,
|
||||
deployment: Union[str, Tuple[str, str], "Deployment"],
|
||||
input: Optional[Dict[str, Any]],
|
||||
**params: Unpack["Predictions.CreatePredictionParams"],
|
||||
) -> Prediction: ...
|
||||
|
||||
def create( # type: ignore
|
||||
self,
|
||||
*args,
|
||||
model: Optional[Union[str, Tuple[str, str], "Model"]] = None,
|
||||
version: Optional[Union[Version, str, "Version"]] = None,
|
||||
deployment: Optional[Union[str, Tuple[str, str], "Deployment"]] = None,
|
||||
input: Optional[Dict[str, Any]] = None,
|
||||
**params: Unpack["Predictions.CreatePredictionParams"],
|
||||
) -> Prediction:
|
||||
"""
|
||||
Create a new prediction for the specified model, version, or deployment.
|
||||
"""
|
||||
|
||||
wait = params.pop("wait", None)
|
||||
file_encoding_strategy = params.pop("file_encoding_strategy", None)
|
||||
|
||||
if args:
|
||||
version = args[0] if len(args) > 0 else None
|
||||
input = args[1] if len(args) > 1 else input
|
||||
|
||||
if sum(bool(x) for x in [model, version, deployment]) != 1:
|
||||
raise ValueError(
|
||||
"Exactly one of 'model', 'version', or 'deployment' must be specified."
|
||||
)
|
||||
|
||||
if model is not None:
|
||||
from replicate.model import ( # pylint: disable=import-outside-toplevel
|
||||
Models,
|
||||
)
|
||||
|
||||
return Models(self._client).predictions.create(
|
||||
model=model,
|
||||
input=input or {},
|
||||
**params,
|
||||
)
|
||||
|
||||
if deployment is not None:
|
||||
from replicate.deployment import ( # pylint: disable=import-outside-toplevel
|
||||
Deployments,
|
||||
)
|
||||
|
||||
return Deployments(self._client).predictions.create(
|
||||
deployment=deployment,
|
||||
input=input or {},
|
||||
**params,
|
||||
)
|
||||
|
||||
if input is not None:
|
||||
input = encode_json(
|
||||
input,
|
||||
client=self._client,
|
||||
file_encoding_strategy=file_encoding_strategy,
|
||||
)
|
||||
|
||||
body = _create_prediction_body(
|
||||
version,
|
||||
input,
|
||||
**params,
|
||||
)
|
||||
extras = _create_prediction_request_params(wait=wait)
|
||||
resp = self._client._request("POST", "/v1/predictions", json=body, **extras)
|
||||
|
||||
return _json_to_prediction(self._client, resp.json())
|
||||
|
||||
@overload
|
||||
async def async_create(
|
||||
self,
|
||||
version: Union[Version, str],
|
||||
input: Optional[Dict[str, Any]],
|
||||
**params: Unpack["Predictions.CreatePredictionParams"],
|
||||
) -> Prediction: ...
|
||||
|
||||
@overload
|
||||
async def async_create(
|
||||
self,
|
||||
*,
|
||||
model: Union[str, Tuple[str, str], "Model"],
|
||||
input: Optional[Dict[str, Any]],
|
||||
**params: Unpack["Predictions.CreatePredictionParams"],
|
||||
) -> Prediction: ...
|
||||
|
||||
@overload
|
||||
async def async_create(
|
||||
self,
|
||||
*,
|
||||
deployment: Union[str, Tuple[str, str], "Deployment"],
|
||||
input: Optional[Dict[str, Any]],
|
||||
**params: Unpack["Predictions.CreatePredictionParams"],
|
||||
) -> Prediction: ...
|
||||
|
||||
async def async_create( # type: ignore
|
||||
self,
|
||||
*args,
|
||||
model: Optional[Union[str, Tuple[str, str], "Model"]] = None,
|
||||
version: Optional[Union[Version, str, "Version"]] = None,
|
||||
deployment: Optional[Union[str, Tuple[str, str], "Deployment"]] = None,
|
||||
input: Optional[Dict[str, Any]] = None,
|
||||
**params: Unpack["Predictions.CreatePredictionParams"],
|
||||
) -> Prediction:
|
||||
"""
|
||||
Create a new prediction for the specified model, version, or deployment.
|
||||
"""
|
||||
wait = params.pop("wait", None)
|
||||
file_encoding_strategy = params.pop("file_encoding_strategy", None)
|
||||
|
||||
if args:
|
||||
version = args[0] if len(args) > 0 else None
|
||||
input = args[1] if len(args) > 1 else input
|
||||
|
||||
if sum(bool(x) for x in [model, version, deployment]) != 1:
|
||||
raise ValueError(
|
||||
"Exactly one of 'model', 'version', or 'deployment' must be specified."
|
||||
)
|
||||
|
||||
if model is not None:
|
||||
from replicate.model import ( # pylint: disable=import-outside-toplevel
|
||||
Models,
|
||||
)
|
||||
|
||||
return await Models(self._client).predictions.async_create(
|
||||
model=model,
|
||||
input=input or {},
|
||||
**params,
|
||||
)
|
||||
|
||||
if deployment is not None:
|
||||
from replicate.deployment import ( # pylint: disable=import-outside-toplevel
|
||||
Deployments,
|
||||
)
|
||||
|
||||
return await Deployments(self._client).predictions.async_create(
|
||||
deployment=deployment,
|
||||
input=input or {},
|
||||
**params,
|
||||
)
|
||||
|
||||
if input is not None:
|
||||
input = await async_encode_json(
|
||||
input,
|
||||
client=self._client,
|
||||
file_encoding_strategy=file_encoding_strategy,
|
||||
)
|
||||
|
||||
body = _create_prediction_body(
|
||||
version,
|
||||
input,
|
||||
**params,
|
||||
)
|
||||
extras = _create_prediction_request_params(wait=wait)
|
||||
resp = await self._client._async_request(
|
||||
"POST", "/v1/predictions", json=body, **extras
|
||||
)
|
||||
|
||||
return _json_to_prediction(self._client, resp.json())
|
||||
|
||||
def cancel(self, id: str) -> Prediction:
|
||||
"""
|
||||
Cancel a prediction.
|
||||
|
||||
Args:
|
||||
id: The ID of the prediction to cancel.
|
||||
Returns:
|
||||
Prediction: The canceled prediction object.
|
||||
"""
|
||||
|
||||
resp = self._client._request(
|
||||
"POST",
|
||||
f"/v1/predictions/{id}/cancel",
|
||||
)
|
||||
|
||||
return _json_to_prediction(self._client, resp.json())
|
||||
|
||||
async def async_cancel(self, id: str) -> Prediction:
|
||||
"""
|
||||
Cancel a prediction.
|
||||
|
||||
Args:
|
||||
id: The ID of the prediction to cancel.
|
||||
Returns:
|
||||
Prediction: The canceled prediction object.
|
||||
"""
|
||||
|
||||
resp = await self._client._async_request(
|
||||
"POST",
|
||||
f"/v1/predictions/{id}/cancel",
|
||||
)
|
||||
|
||||
return _json_to_prediction(self._client, resp.json())
|
||||
|
||||
|
||||
class CreatePredictionRequestParams(TypedDict):
|
||||
headers: NotRequired[Optional[dict]]
|
||||
timeout: NotRequired[Optional[httpx.Timeout]]
|
||||
|
||||
|
||||
def _create_prediction_request_params(
|
||||
wait: Optional[Union[int, bool]],
|
||||
) -> CreatePredictionRequestParams:
|
||||
timeout = _create_prediction_timeout(wait=wait)
|
||||
headers = _create_prediction_headers(wait=wait)
|
||||
|
||||
return {
|
||||
"headers": headers,
|
||||
"timeout": timeout,
|
||||
}
|
||||
|
||||
|
||||
def _create_prediction_timeout(
|
||||
*, wait: Optional[Union[int, bool]] = None
|
||||
) -> Union[httpx.Timeout, None]:
|
||||
"""
|
||||
Returns an `httpx.Timeout` instances appropriate for the optional
|
||||
`Prefer: wait=x` header that can be provided with the request. This
|
||||
will ensure that we give the server enough time to respond with
|
||||
a partial prediction in the event that the request times out.
|
||||
"""
|
||||
|
||||
if not wait:
|
||||
return None
|
||||
|
||||
read_timeout = 60.0 if isinstance(wait, bool) else wait
|
||||
return httpx.Timeout(5.0, read=read_timeout + 0.5)
|
||||
|
||||
|
||||
def _create_prediction_headers(
|
||||
*,
|
||||
wait: Optional[Union[int, bool]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
headers = {}
|
||||
|
||||
if wait:
|
||||
if isinstance(wait, bool):
|
||||
headers["Prefer"] = "wait"
|
||||
elif isinstance(wait, int):
|
||||
headers["Prefer"] = f"wait={wait}"
|
||||
return headers
|
||||
|
||||
|
||||
def _create_prediction_body( # pylint: disable=too-many-arguments
|
||||
version: Optional[Union[Version, str]],
|
||||
input: Optional[Dict[str, Any]],
|
||||
webhook: Optional[str] = None,
|
||||
webhook_completed: Optional[str] = None,
|
||||
webhook_events_filter: Optional[List[str]] = None,
|
||||
stream: Optional[bool] = None,
|
||||
**_kwargs,
|
||||
) -> Dict[str, Any]:
|
||||
body = {}
|
||||
|
||||
if input is not None:
|
||||
body["input"] = input
|
||||
|
||||
if version is not None:
|
||||
body["version"] = version.id if isinstance(version, Version) else version
|
||||
|
||||
if webhook is not None:
|
||||
body["webhook"] = webhook
|
||||
|
||||
if webhook_completed is not None:
|
||||
body["webhook_completed"] = webhook_completed
|
||||
|
||||
if webhook_events_filter is not None:
|
||||
body["webhook_events_filter"] = webhook_events_filter
|
||||
|
||||
if stream is not None:
|
||||
body["stream"] = stream
|
||||
|
||||
return body
|
||||
|
||||
|
||||
def _json_to_prediction(client: "Client", json: Dict[str, Any]) -> Prediction:
|
||||
prediction = Prediction(**json)
|
||||
prediction._client = client
|
||||
return prediction
|
||||
@@ -0,0 +1,27 @@
|
||||
import abc
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
try:
|
||||
from pydantic import v1 as pydantic # type: ignore
|
||||
except ImportError:
|
||||
import pydantic # type: ignore
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from replicate.client import Client
|
||||
|
||||
|
||||
class Resource(pydantic.BaseModel): # type: ignore
|
||||
"""
|
||||
A base class for representing a single object on the server.
|
||||
"""
|
||||
|
||||
|
||||
class Namespace(abc.ABC):
|
||||
"""
|
||||
A base class for representing objects of a particular type on the server.
|
||||
"""
|
||||
|
||||
_client: "Client"
|
||||
|
||||
def __init__(self, client: "Client") -> None:
|
||||
self._client = client
|
||||
185
art-agent/backend/venv/Lib/site-packages/replicate/run.py
Normal file
185
art-agent/backend/venv/Lib/site-packages/replicate/run.py
Normal file
@@ -0,0 +1,185 @@
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
AsyncIterator,
|
||||
Dict,
|
||||
Iterator,
|
||||
List,
|
||||
Optional,
|
||||
Union,
|
||||
)
|
||||
|
||||
from typing_extensions import Unpack
|
||||
|
||||
from replicate import identifier
|
||||
from replicate.exceptions import ModelError
|
||||
from replicate.helpers import transform_output
|
||||
from replicate.model import Model
|
||||
from replicate.schema import make_schema_backwards_compatible
|
||||
from replicate.version import Version, Versions
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from replicate.client import Client
|
||||
from replicate.identifier import ModelVersionIdentifier
|
||||
from replicate.prediction import Predictions
|
||||
|
||||
|
||||
def run(
|
||||
client: "Client",
|
||||
ref: Union["Model", "Version", "ModelVersionIdentifier", str],
|
||||
input: Optional[Dict[str, Any]] = None,
|
||||
*,
|
||||
use_file_output: Optional[bool] = True,
|
||||
**params: Unpack["Predictions.CreatePredictionParams"],
|
||||
) -> Union[Any, Iterator[Any]]: # noqa: ANN401
|
||||
"""
|
||||
Run a model and wait for its output.
|
||||
"""
|
||||
|
||||
if "wait" not in params:
|
||||
params["wait"] = True
|
||||
is_blocking = params["wait"] is not False
|
||||
|
||||
version, owner, name, version_id = identifier._resolve(ref)
|
||||
|
||||
if version_id is not None:
|
||||
prediction = client.predictions.create(
|
||||
version=version_id, input=input or {}, **params
|
||||
)
|
||||
elif owner and name:
|
||||
prediction = client.models.predictions.create(
|
||||
model=(owner, name), input=input or {}, **params
|
||||
)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Invalid argument: {ref}. Expected model, version, or reference in the format owner/name or owner/name:version"
|
||||
)
|
||||
|
||||
if not version and (owner and name and version_id):
|
||||
version = Versions(client, model=(owner, name)).get(version_id)
|
||||
|
||||
# Currently the "Prefer: wait" interface will return a prediction with a status
|
||||
# of "processing" rather than a terminal state because it returns before the
|
||||
# prediction has been fully processed. If request exceeds the wait time, even if
|
||||
# it is actually processing, the prediction will be in a "starting" state.
|
||||
#
|
||||
# We should fix this in the blocking API itself. Predictions that are done should
|
||||
# be in a terminal state and predictions that are processing should be in state
|
||||
# "processing".
|
||||
in_terminal_state = is_blocking and prediction.status != "starting"
|
||||
if not in_terminal_state:
|
||||
# Return a "polling" iterator if the model has an output iterator array type.
|
||||
if version and _has_output_iterator_array_type(version):
|
||||
return (
|
||||
transform_output(chunk, client)
|
||||
for chunk in prediction.output_iterator()
|
||||
)
|
||||
|
||||
prediction.wait()
|
||||
|
||||
if prediction.status == "failed":
|
||||
raise ModelError(prediction)
|
||||
|
||||
# Return an iterator for the completed prediction when needed.
|
||||
if (
|
||||
version
|
||||
and _has_output_iterator_array_type(version)
|
||||
and prediction.output is not None
|
||||
):
|
||||
return (transform_output(chunk, client) for chunk in prediction.output)
|
||||
|
||||
if use_file_output:
|
||||
return transform_output(prediction.output, client)
|
||||
|
||||
return prediction.output
|
||||
|
||||
|
||||
async def async_run(
|
||||
client: "Client",
|
||||
ref: Union["Model", "Version", "ModelVersionIdentifier", str],
|
||||
input: Optional[Dict[str, Any]] = None,
|
||||
*,
|
||||
use_file_output: Optional[bool] = True,
|
||||
**params: Unpack["Predictions.CreatePredictionParams"],
|
||||
) -> Union[Any, AsyncIterator[Any]]: # noqa: ANN401
|
||||
"""
|
||||
Run a model and wait for its output asynchronously.
|
||||
"""
|
||||
|
||||
if "wait" not in params:
|
||||
params["wait"] = True
|
||||
is_blocking = params["wait"] is not False
|
||||
|
||||
version, owner, name, version_id = identifier._resolve(ref)
|
||||
|
||||
if version or version_id:
|
||||
prediction = await client.predictions.async_create(
|
||||
version=(version or version_id), input=input or {}, **params
|
||||
)
|
||||
elif owner and name:
|
||||
prediction = await client.models.predictions.async_create(
|
||||
model=(owner, name), input=input or {}, **params
|
||||
)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Invalid argument: {ref}. Expected model, version, or reference in the format owner/name or owner/name:version"
|
||||
)
|
||||
|
||||
if not version and (owner and name and version_id):
|
||||
version = await Versions(client, model=(owner, name)).async_get(version_id)
|
||||
|
||||
# Currently the "Prefer: wait" interface will return a prediction with a status
|
||||
# of "processing" rather than a terminal state because it returns before the
|
||||
# prediction has been fully processed. If request exceeds the wait time, even if
|
||||
# it is actually processing, the prediction will be in a "starting" state.
|
||||
#
|
||||
# We should fix this in the blocking API itself. Predictions that are done should
|
||||
# be in a terminal state and predictions that are processing should be in state
|
||||
# "processing".
|
||||
in_terminal_state = is_blocking and prediction.status != "starting"
|
||||
if not in_terminal_state:
|
||||
# Return a "polling" iterator if the model has an output iterator array type.
|
||||
if version and _has_output_iterator_array_type(version):
|
||||
return (
|
||||
transform_output(chunk, client)
|
||||
async for chunk in prediction.async_output_iterator()
|
||||
)
|
||||
|
||||
await prediction.async_wait()
|
||||
|
||||
if prediction.status == "failed":
|
||||
raise ModelError(prediction)
|
||||
|
||||
# Return an iterator for completed output if the model has an output iterator array type.
|
||||
if (
|
||||
version
|
||||
and _has_output_iterator_array_type(version)
|
||||
and prediction.output is not None
|
||||
):
|
||||
return (
|
||||
transform_output(chunk, client)
|
||||
async for chunk in _make_async_iterator(prediction.output)
|
||||
)
|
||||
|
||||
if use_file_output:
|
||||
return transform_output(prediction.output, client)
|
||||
|
||||
return prediction.output
|
||||
|
||||
|
||||
def _has_output_iterator_array_type(version: Version) -> bool:
|
||||
schema = make_schema_backwards_compatible(
|
||||
version.openapi_schema, version.cog_version
|
||||
)
|
||||
output = schema.get("components", {}).get("schemas", {}).get("Output", {})
|
||||
return (
|
||||
output.get("type") == "array" and output.get("x-cog-array-type") == "iterator"
|
||||
)
|
||||
|
||||
|
||||
async def _make_async_iterator(list: list) -> AsyncIterator:
|
||||
for item in list:
|
||||
yield item
|
||||
|
||||
|
||||
__all__: List = []
|
||||
27
art-agent/backend/venv/Lib/site-packages/replicate/schema.py
Normal file
27
art-agent/backend/venv/Lib/site-packages/replicate/schema.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from typing import Optional
|
||||
|
||||
from packaging import version
|
||||
|
||||
# TODO: this code is shared with replicate's backend. Maybe we should put it in the Cog Python package as the source of truth?
|
||||
|
||||
|
||||
def version_has_no_array_type(cog_version: str) -> Optional[bool]:
|
||||
"""Iterators have x-cog-array-type=iterator in the schema from 0.3.9 onward"""
|
||||
try:
|
||||
return version.parse(cog_version) < version.parse("0.3.9")
|
||||
except version.InvalidVersion:
|
||||
return None
|
||||
|
||||
|
||||
def make_schema_backwards_compatible(
|
||||
schema: dict,
|
||||
cog_version: str,
|
||||
) -> dict:
|
||||
"""A place to add backwards compatibility logic for our openapi schema"""
|
||||
|
||||
# If the top-level output is an array, assume it is an iterator in old versions which didn't have an array type
|
||||
if version_has_no_array_type(cog_version):
|
||||
output = schema["components"]["schemas"]["Output"]
|
||||
if output.get("type") == "array":
|
||||
output["x-cog-array-type"] = "iterator"
|
||||
return schema
|
||||
280
art-agent/backend/venv/Lib/site-packages/replicate/stream.py
Normal file
280
art-agent/backend/venv/Lib/site-packages/replicate/stream.py
Normal file
@@ -0,0 +1,280 @@
|
||||
from enum import Enum
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
AsyncIterator,
|
||||
Dict,
|
||||
Iterator,
|
||||
List,
|
||||
Optional,
|
||||
Union,
|
||||
)
|
||||
|
||||
import httpx
|
||||
from typing_extensions import Unpack
|
||||
|
||||
from replicate import identifier
|
||||
from replicate.exceptions import ReplicateError
|
||||
from replicate.helpers import transform_output
|
||||
|
||||
try:
|
||||
from pydantic import v1 as pydantic # type: ignore
|
||||
except ImportError:
|
||||
import pydantic # type: ignore
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from replicate.client import Client
|
||||
from replicate.identifier import ModelVersionIdentifier
|
||||
from replicate.model import Model
|
||||
from replicate.prediction import Predictions
|
||||
from replicate.version import Version
|
||||
|
||||
|
||||
class ServerSentEvent(pydantic.BaseModel): # type: ignore
|
||||
"""
|
||||
A server-sent event.
|
||||
"""
|
||||
|
||||
class EventType(Enum):
|
||||
"""
|
||||
A server-sent event type.
|
||||
"""
|
||||
|
||||
OUTPUT = "output"
|
||||
LOGS = "logs"
|
||||
ERROR = "error"
|
||||
DONE = "done"
|
||||
|
||||
event: EventType
|
||||
data: str
|
||||
id: str
|
||||
retry: Optional[int]
|
||||
|
||||
def __str__(self) -> str:
|
||||
if self.event == ServerSentEvent.EventType.OUTPUT:
|
||||
return self.data
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
class EventSource:
|
||||
"""
|
||||
A server-sent event source.
|
||||
"""
|
||||
|
||||
client: "Client"
|
||||
response: "httpx.Response"
|
||||
use_file_output: bool
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
client: "Client",
|
||||
response: "httpx.Response",
|
||||
*,
|
||||
use_file_output: Optional[bool] = True,
|
||||
) -> None:
|
||||
self.client = client
|
||||
self.response = response
|
||||
self.use_file_output = use_file_output or True
|
||||
content_type, _, _ = response.headers["content-type"].partition(";")
|
||||
if content_type != "text/event-stream":
|
||||
raise ValueError(
|
||||
"Expected response Content-Type to be 'text/event-stream', "
|
||||
f"got {content_type!r}"
|
||||
)
|
||||
|
||||
class Decoder:
|
||||
"""
|
||||
A decoder for server-sent events.
|
||||
"""
|
||||
|
||||
event: Optional["ServerSentEvent.EventType"]
|
||||
data: List[str]
|
||||
last_event_id: Optional[str]
|
||||
retry: Optional[int]
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.event = None
|
||||
self.data = []
|
||||
self.last_event_id = None
|
||||
self.retry = None
|
||||
|
||||
def decode(self, line: str) -> Optional[ServerSentEvent]:
|
||||
"""
|
||||
Decode a line and return a server-sent event if applicable.
|
||||
"""
|
||||
|
||||
if not line:
|
||||
if (
|
||||
not any([self.event, self.data, self.last_event_id, self.retry])
|
||||
or self.event is None
|
||||
or self.last_event_id is None
|
||||
):
|
||||
return None
|
||||
|
||||
sse = ServerSentEvent(
|
||||
event=self.event,
|
||||
data="\n".join(self.data),
|
||||
id=self.last_event_id,
|
||||
retry=self.retry,
|
||||
)
|
||||
|
||||
self.event = None
|
||||
self.data = []
|
||||
self.retry = None
|
||||
|
||||
return sse
|
||||
|
||||
if line.startswith(":"):
|
||||
return None
|
||||
|
||||
fieldname, _, value = line.partition(":")
|
||||
value = value[1:] if value.startswith(" ") else value
|
||||
|
||||
if fieldname == "event":
|
||||
if event := ServerSentEvent.EventType(value):
|
||||
self.event = event
|
||||
elif fieldname == "data":
|
||||
self.data.append(value)
|
||||
elif fieldname == "id":
|
||||
if "\0" not in value:
|
||||
self.last_event_id = value
|
||||
elif fieldname == "retry":
|
||||
try:
|
||||
self.retry = int(value)
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
def __iter__(self) -> Iterator[ServerSentEvent]:
|
||||
decoder = EventSource.Decoder()
|
||||
|
||||
for line in self.response.iter_lines():
|
||||
line = line.rstrip("\n")
|
||||
sse = decoder.decode(line)
|
||||
if sse is not None:
|
||||
if sse.event == ServerSentEvent.EventType.ERROR:
|
||||
raise RuntimeError(sse.data)
|
||||
|
||||
if (
|
||||
self.use_file_output
|
||||
and sse.event == ServerSentEvent.EventType.OUTPUT
|
||||
):
|
||||
sse.data = transform_output(sse.data, client=self.client)
|
||||
|
||||
yield sse
|
||||
|
||||
if sse.event == ServerSentEvent.EventType.DONE:
|
||||
return
|
||||
|
||||
async def __aiter__(self) -> AsyncIterator[ServerSentEvent]:
|
||||
decoder = EventSource.Decoder()
|
||||
async for line in self.response.aiter_lines():
|
||||
line = line.rstrip("\n")
|
||||
sse = decoder.decode(line)
|
||||
if sse is not None:
|
||||
if sse.event == ServerSentEvent.EventType.ERROR:
|
||||
raise RuntimeError(sse.data)
|
||||
|
||||
if (
|
||||
self.use_file_output
|
||||
and sse.event == ServerSentEvent.EventType.OUTPUT
|
||||
):
|
||||
sse.data = transform_output(sse.data, client=self.client)
|
||||
|
||||
yield sse
|
||||
|
||||
if sse.event == ServerSentEvent.EventType.DONE:
|
||||
return
|
||||
|
||||
|
||||
def stream(
|
||||
client: "Client",
|
||||
ref: Union["Model", "Version", "ModelVersionIdentifier", str],
|
||||
input: Optional[Dict[str, Any]] = None,
|
||||
*,
|
||||
use_file_output: Optional[bool] = True,
|
||||
**params: Unpack["Predictions.CreatePredictionParams"],
|
||||
) -> Iterator[ServerSentEvent]:
|
||||
"""
|
||||
Run a model and stream its output.
|
||||
"""
|
||||
|
||||
params = params or {}
|
||||
params["stream"] = True
|
||||
|
||||
version, owner, name, version_id = identifier._resolve(ref)
|
||||
|
||||
if version or version_id:
|
||||
prediction = client.predictions.create(
|
||||
version=(version or version_id), input=input or {}, **params
|
||||
)
|
||||
elif owner and name:
|
||||
prediction = client.models.predictions.create(
|
||||
model=(owner, name), input=input or {}, **params
|
||||
)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Invalid argument: {ref}. Expected model, version, or reference in the format owner/name or owner/name:version"
|
||||
)
|
||||
|
||||
url = prediction.urls and prediction.urls.get("stream", None)
|
||||
if not url or not isinstance(url, str):
|
||||
raise ReplicateError("Model does not support streaming")
|
||||
|
||||
headers = {}
|
||||
headers["Accept"] = "text/event-stream"
|
||||
headers["Cache-Control"] = "no-store"
|
||||
|
||||
with client._client.stream("GET", url, headers=headers) as response:
|
||||
yield from EventSource(client, response, use_file_output=use_file_output)
|
||||
|
||||
|
||||
async def async_stream(
|
||||
client: "Client",
|
||||
ref: Union["Model", "Version", "ModelVersionIdentifier", str],
|
||||
input: Optional[Dict[str, Any]] = None,
|
||||
*,
|
||||
use_file_output: Optional[bool] = True,
|
||||
**params: Unpack["Predictions.CreatePredictionParams"],
|
||||
) -> AsyncIterator[ServerSentEvent]:
|
||||
"""
|
||||
Run a model and stream its output asynchronously.
|
||||
"""
|
||||
|
||||
params = params or {}
|
||||
params["stream"] = True
|
||||
|
||||
version, owner, name, version_id = identifier._resolve(ref)
|
||||
|
||||
if version or version_id:
|
||||
prediction = await client.predictions.async_create(
|
||||
version=(version or version_id), input=input or {}, **params
|
||||
)
|
||||
elif owner and name:
|
||||
prediction = await client.models.predictions.async_create(
|
||||
model=(owner, name), input=input or {}, **params
|
||||
)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Invalid argument: {ref}. Expected model, version, or reference in the format owner/name or owner/name:version"
|
||||
)
|
||||
|
||||
url = prediction.urls and prediction.urls.get("stream", None)
|
||||
if not url or not isinstance(url, str):
|
||||
raise ReplicateError("Model does not support streaming")
|
||||
|
||||
headers = {}
|
||||
headers["Accept"] = "text/event-stream"
|
||||
headers["Cache-Control"] = "no-store"
|
||||
|
||||
async with client._async_client.stream("GET", url, headers=headers) as response:
|
||||
async for event in EventSource(
|
||||
client, response, use_file_output=use_file_output
|
||||
):
|
||||
yield event
|
||||
|
||||
|
||||
__all__ = ["ServerSentEvent"]
|
||||
460
art-agent/backend/venv/Lib/site-packages/replicate/training.py
Normal file
460
art-agent/backend/venv/Lib/site-packages/replicate/training.py
Normal file
@@ -0,0 +1,460 @@
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Dict,
|
||||
List,
|
||||
Literal,
|
||||
Optional,
|
||||
Tuple,
|
||||
TypedDict,
|
||||
Union,
|
||||
overload,
|
||||
)
|
||||
|
||||
from typing_extensions import NotRequired, Unpack
|
||||
|
||||
from replicate.helpers import async_encode_json, encode_json
|
||||
from replicate.identifier import ModelVersionIdentifier
|
||||
from replicate.model import Model
|
||||
from replicate.pagination import Page
|
||||
from replicate.resource import Namespace, Resource
|
||||
from replicate.version import Version
|
||||
|
||||
try:
|
||||
from pydantic import v1 as pydantic # type: ignore
|
||||
except ImportError:
|
||||
import pydantic # type: ignore
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from replicate.client import Client
|
||||
from replicate.file import FileEncodingStrategy
|
||||
|
||||
|
||||
class Training(Resource):
|
||||
"""
|
||||
A training made for a model hosted on Replicate.
|
||||
"""
|
||||
|
||||
_client: "Client" = pydantic.PrivateAttr()
|
||||
|
||||
id: str
|
||||
"""The unique ID of the training."""
|
||||
|
||||
model: str
|
||||
"""An identifier for the model used to create the prediction, in the form `owner/name`."""
|
||||
|
||||
version: Union[str, Version]
|
||||
"""The version of the model used to create the training."""
|
||||
|
||||
destination: Optional[str]
|
||||
"""The model destination of the training."""
|
||||
|
||||
status: Literal["starting", "processing", "succeeded", "failed", "canceled"]
|
||||
"""The status of the training."""
|
||||
|
||||
input: Optional[Dict[str, Any]]
|
||||
"""The input to the training."""
|
||||
|
||||
output: Optional[Any]
|
||||
"""The output of the training."""
|
||||
|
||||
logs: Optional[str]
|
||||
"""The logs of the training."""
|
||||
|
||||
error: Optional[str]
|
||||
"""The error encountered during the training, if any."""
|
||||
|
||||
created_at: Optional[str]
|
||||
"""When the training was created."""
|
||||
|
||||
started_at: Optional[str]
|
||||
"""When the training was started."""
|
||||
|
||||
completed_at: Optional[str]
|
||||
"""When the training was completed, if finished."""
|
||||
|
||||
urls: Optional[Dict[str, str]]
|
||||
"""
|
||||
URLs associated with the training.
|
||||
|
||||
The following keys are available:
|
||||
- `get`: A URL to fetch the training.
|
||||
- `cancel`: A URL to cancel the training.
|
||||
"""
|
||||
|
||||
def cancel(self) -> None:
|
||||
"""
|
||||
Cancel a running training.
|
||||
"""
|
||||
|
||||
canceled = self._client.trainings.cancel(self.id)
|
||||
for name, value in canceled.dict().items():
|
||||
setattr(self, name, value)
|
||||
|
||||
async def async_cancel(self) -> None:
|
||||
"""
|
||||
Cancel a running training asynchronously.
|
||||
"""
|
||||
|
||||
canceled = await self._client.trainings.async_cancel(self.id)
|
||||
for name, value in canceled.dict().items():
|
||||
setattr(self, name, value)
|
||||
|
||||
def reload(self) -> None:
|
||||
"""
|
||||
Load the training from the server.
|
||||
"""
|
||||
|
||||
updated = self._client.trainings.get(self.id)
|
||||
for name, value in updated.dict().items():
|
||||
setattr(self, name, value)
|
||||
|
||||
async def async_reload(self) -> None:
|
||||
"""
|
||||
Load the training from the server asynchronously.
|
||||
"""
|
||||
|
||||
updated = await self._client.trainings.async_get(self.id)
|
||||
for name, value in updated.dict().items():
|
||||
setattr(self, name, value)
|
||||
|
||||
|
||||
class Trainings(Namespace):
|
||||
"""
|
||||
Namespace for operations related to trainings.
|
||||
"""
|
||||
|
||||
def list(self, cursor: Union[str, "ellipsis", None] = ...) -> Page[Training]: # noqa: F821
|
||||
"""
|
||||
List your trainings.
|
||||
|
||||
Parameters:
|
||||
cursor: The cursor to use for pagination. Use the value of `Page.next` or `Page.previous`.
|
||||
Returns:
|
||||
Page[Training]: A page of trainings.
|
||||
Raises:
|
||||
ValueError: If `cursor` is `None`.
|
||||
"""
|
||||
|
||||
if cursor is None:
|
||||
raise ValueError("cursor cannot be None")
|
||||
|
||||
resp = self._client._request(
|
||||
"GET", "/v1/trainings" if cursor is ... else cursor
|
||||
)
|
||||
|
||||
obj = resp.json()
|
||||
obj["results"] = [
|
||||
_json_to_training(self._client, result) for result in obj["results"]
|
||||
]
|
||||
|
||||
return Page[Training](**obj)
|
||||
|
||||
async def async_list(
|
||||
self,
|
||||
cursor: Union[str, "ellipsis", None] = ..., # noqa: F821
|
||||
) -> Page[Training]:
|
||||
"""
|
||||
List your trainings.
|
||||
|
||||
Parameters:
|
||||
cursor: The cursor to use for pagination. Use the value of `Page.next` or `Page.previous`.
|
||||
Returns:
|
||||
Page[Training]: A page of trainings.
|
||||
Raises:
|
||||
ValueError: If `cursor` is `None`.
|
||||
"""
|
||||
|
||||
if cursor is None:
|
||||
raise ValueError("cursor cannot be None")
|
||||
|
||||
resp = await self._client._async_request(
|
||||
"GET", "/v1/trainings" if cursor is ... else cursor
|
||||
)
|
||||
|
||||
obj = resp.json()
|
||||
obj["results"] = [
|
||||
_json_to_training(self._client, result) for result in obj["results"]
|
||||
]
|
||||
|
||||
return Page[Training](**obj)
|
||||
|
||||
def get(self, id: str) -> Training:
|
||||
"""
|
||||
Get a training by ID.
|
||||
|
||||
Args:
|
||||
id: The ID of the training.
|
||||
Returns:
|
||||
Training: The training object.
|
||||
"""
|
||||
|
||||
resp = self._client._request(
|
||||
"GET",
|
||||
f"/v1/trainings/{id}",
|
||||
)
|
||||
|
||||
return _json_to_training(self._client, resp.json())
|
||||
|
||||
async def async_get(self, id: str) -> Training:
|
||||
"""
|
||||
Get a training by ID.
|
||||
|
||||
Args:
|
||||
id: The ID of the training.
|
||||
Returns:
|
||||
Training: The training object.
|
||||
"""
|
||||
|
||||
resp = await self._client._async_request(
|
||||
"GET",
|
||||
f"/v1/trainings/{id}",
|
||||
)
|
||||
|
||||
return _json_to_training(self._client, resp.json())
|
||||
|
||||
class CreateTrainingParams(TypedDict):
|
||||
"""Parameters for creating a training."""
|
||||
|
||||
destination: Union[str, Tuple[str, str], "Model"]
|
||||
"""The destination for the trained model."""
|
||||
|
||||
webhook: NotRequired[str]
|
||||
"""The URL to receive a POST request with training updates."""
|
||||
|
||||
webhook_completed: NotRequired[str]
|
||||
"""The URL to receive a POST request when the training is completed."""
|
||||
|
||||
webhook_events_filter: NotRequired[List[str]]
|
||||
"""List of events to trigger webhooks."""
|
||||
|
||||
file_encoding_strategy: NotRequired["FileEncodingStrategy"]
|
||||
"""The strategy to use for encoding files in the training input."""
|
||||
|
||||
@overload
|
||||
def create( # pylint: disable=too-many-arguments
|
||||
self,
|
||||
version: str,
|
||||
input: Dict[str, Any],
|
||||
destination: str,
|
||||
webhook: Optional[str] = None,
|
||||
webhook_events_filter: Optional[List[str]] = None,
|
||||
**kwargs,
|
||||
) -> Training: ...
|
||||
|
||||
@overload
|
||||
def create(
|
||||
self,
|
||||
model: Union[str, Tuple[str, str], "Model"],
|
||||
version: Union[str, Version],
|
||||
input: Optional[Dict[str, Any]] = None,
|
||||
**params: Unpack["Trainings.CreateTrainingParams"],
|
||||
) -> Training: ...
|
||||
|
||||
def create( # type: ignore
|
||||
self,
|
||||
*args,
|
||||
model: Optional[Union[str, Tuple[str, str], "Model"]] = None,
|
||||
version: Optional[Union[str, Version]] = None,
|
||||
input: Optional[Dict[str, Any]] = None,
|
||||
**params: Unpack["Trainings.CreateTrainingParams"],
|
||||
) -> Training:
|
||||
"""
|
||||
Create a new training using the specified model version as a base.
|
||||
"""
|
||||
|
||||
url = None
|
||||
|
||||
# Support positional arguments for backwards compatibility
|
||||
if args:
|
||||
if shorthand := args[0] if len(args) > 0 else None:
|
||||
url = _create_training_url_from_shorthand(shorthand)
|
||||
|
||||
input = args[1] if len(args) > 1 else input
|
||||
if len(args) > 2:
|
||||
params["destination"] = args[2]
|
||||
if len(args) > 3:
|
||||
params["webhook"] = args[3]
|
||||
if len(args) > 4:
|
||||
params["webhook_completed"] = args[4]
|
||||
if len(args) > 5:
|
||||
params["webhook_events_filter"] = args[5]
|
||||
elif model and version:
|
||||
url = _create_training_url_from_model_and_version(model, version)
|
||||
elif model is None and isinstance(version, str):
|
||||
url = _create_training_url_from_shorthand(version)
|
||||
|
||||
if not url:
|
||||
raise ValueError("model and version or shorthand version must be specified")
|
||||
|
||||
file_encoding_strategy = params.pop("file_encoding_strategy", None)
|
||||
if input is not None:
|
||||
input = encode_json(
|
||||
input,
|
||||
client=self._client,
|
||||
file_encoding_strategy=file_encoding_strategy,
|
||||
)
|
||||
|
||||
body = _create_training_body(input, **params)
|
||||
|
||||
resp = self._client._request(
|
||||
"POST",
|
||||
url,
|
||||
json=body,
|
||||
)
|
||||
|
||||
return _json_to_training(self._client, resp.json())
|
||||
|
||||
async def async_create(
|
||||
self,
|
||||
model: Union[str, Tuple[str, str], "Model"],
|
||||
version: Union[str, Version],
|
||||
input: Dict[str, Any],
|
||||
**params: Unpack["Trainings.CreateTrainingParams"],
|
||||
) -> Training:
|
||||
"""
|
||||
Create a new training using the specified model version as a base.
|
||||
|
||||
Args:
|
||||
version: The ID of the base model version that you're using to train a new model version.
|
||||
input: The input to the training.
|
||||
destination: The desired model to push to in the format `{owner}/{model_name}`. This should be an existing model owned by the user or organization making the API request.
|
||||
webhook: The URL to send a POST request to when the training is completed. Defaults to None.
|
||||
webhook_completed: The URL to receive a POST request when the prediction is completed.
|
||||
webhook_events_filter: The events to send to the webhook. Defaults to None.
|
||||
Returns:
|
||||
The training object.
|
||||
"""
|
||||
|
||||
url = _create_training_url_from_model_and_version(model, version)
|
||||
|
||||
file_encoding_strategy = params.pop("file_encoding_strategy", None)
|
||||
if input is not None:
|
||||
input = await async_encode_json(
|
||||
input,
|
||||
client=self._client,
|
||||
file_encoding_strategy=file_encoding_strategy,
|
||||
)
|
||||
body = _create_training_body(input, **params)
|
||||
|
||||
resp = await self._client._async_request(
|
||||
"POST",
|
||||
url,
|
||||
json=body,
|
||||
)
|
||||
|
||||
return _json_to_training(self._client, resp.json())
|
||||
|
||||
def cancel(self, id: str) -> Training:
|
||||
"""
|
||||
Cancel a training.
|
||||
|
||||
Args:
|
||||
id: The ID of the training to cancel.
|
||||
Returns:
|
||||
Training: The canceled training object.
|
||||
"""
|
||||
|
||||
resp = self._client._request(
|
||||
"POST",
|
||||
f"/v1/trainings/{id}/cancel",
|
||||
)
|
||||
|
||||
return _json_to_training(self._client, resp.json())
|
||||
|
||||
async def async_cancel(self, id: str) -> Training:
|
||||
"""
|
||||
Cancel a training.
|
||||
|
||||
Args:
|
||||
id: The ID of the training to cancel.
|
||||
Returns:
|
||||
Training: The canceled training object.
|
||||
"""
|
||||
|
||||
resp = await self._client._async_request(
|
||||
"POST",
|
||||
f"/v1/trainings/{id}/cancel",
|
||||
)
|
||||
|
||||
return _json_to_training(self._client, resp.json())
|
||||
|
||||
|
||||
def _create_training_body(
|
||||
input: Optional[Dict[str, Any]] = None,
|
||||
*,
|
||||
destination: Optional[Union[str, Tuple[str, str], "Model"]] = None,
|
||||
webhook: Optional[str] = None,
|
||||
webhook_completed: Optional[str] = None,
|
||||
webhook_events_filter: Optional[List[str]] = None,
|
||||
**_kwargs,
|
||||
) -> Dict[str, Any]:
|
||||
body = {}
|
||||
|
||||
if input is not None:
|
||||
body["input"] = input
|
||||
|
||||
if destination is None:
|
||||
raise ValueError(
|
||||
"A destination must be provided as a positional or keyword argument."
|
||||
)
|
||||
if isinstance(destination, Model):
|
||||
destination = f"{destination.owner}/{destination.name}"
|
||||
elif isinstance(destination, tuple):
|
||||
destination = f"{destination[0]}/{destination[1]}"
|
||||
body["destination"] = destination
|
||||
|
||||
if webhook is not None:
|
||||
body["webhook"] = webhook
|
||||
|
||||
if webhook_completed is not None:
|
||||
body["webhook_completed"] = webhook_completed
|
||||
|
||||
if webhook_events_filter is not None:
|
||||
body["webhook_events_filter"] = webhook_events_filter
|
||||
|
||||
return body
|
||||
|
||||
|
||||
def _create_training_url_from_shorthand(ref: str) -> str:
|
||||
owner, name, version_id = ModelVersionIdentifier.parse(ref)
|
||||
return f"/v1/models/{owner}/{name}/versions/{version_id}/trainings"
|
||||
|
||||
|
||||
def _create_training_url_from_model_and_version(
|
||||
model: Union[str, Tuple[str, str], "Model"],
|
||||
version: Union[str, "Version"],
|
||||
) -> str:
|
||||
if isinstance(model, Model):
|
||||
owner, name = model.owner, model.name
|
||||
elif isinstance(model, tuple):
|
||||
owner, name = model[0], model[1]
|
||||
elif isinstance(model, str):
|
||||
owner, name, _ = ModelVersionIdentifier.parse(model)
|
||||
else:
|
||||
raise ValueError(
|
||||
"model must be a Model, a tuple of (owner, name), or a string in the format 'owner/name'"
|
||||
)
|
||||
|
||||
if isinstance(version, Version):
|
||||
version_id = version.id
|
||||
else:
|
||||
version_id = version
|
||||
|
||||
return f"/v1/models/{owner}/{name}/versions/{version_id}/trainings"
|
||||
|
||||
|
||||
def _json_to_training(client: "Client", json: Dict[str, Any]) -> Training:
|
||||
training = Training(**json)
|
||||
training._client = client
|
||||
|
||||
# FIXME: This should be populated by the API
|
||||
if (
|
||||
training.output
|
||||
and isinstance(training.output, dict)
|
||||
and "version" in training.output
|
||||
):
|
||||
id = ModelVersionIdentifier.parse(training.output["version"])
|
||||
training.destination = f"{id.owner}/{id.name}"
|
||||
|
||||
return training
|
||||
158
art-agent/backend/venv/Lib/site-packages/replicate/version.py
Normal file
158
art-agent/backend/venv/Lib/site-packages/replicate/version.py
Normal file
@@ -0,0 +1,158 @@
|
||||
import datetime
|
||||
from typing import TYPE_CHECKING, Any, Dict, Tuple, Union
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from replicate.client import Client
|
||||
from replicate.model import Model
|
||||
|
||||
from replicate.pagination import Page
|
||||
from replicate.resource import Namespace, Resource
|
||||
|
||||
|
||||
class Version(Resource):
|
||||
"""
|
||||
A version of a model.
|
||||
"""
|
||||
|
||||
id: str
|
||||
"""The unique ID of the version."""
|
||||
|
||||
created_at: datetime.datetime
|
||||
"""When the version was created."""
|
||||
|
||||
cog_version: str
|
||||
"""The version of the Cog used to create the version."""
|
||||
|
||||
openapi_schema: dict
|
||||
"""An OpenAPI description of the model inputs and outputs."""
|
||||
|
||||
|
||||
class Versions(Namespace):
|
||||
"""
|
||||
Namespace for operations related to model versions.
|
||||
"""
|
||||
|
||||
model: Tuple[str, str]
|
||||
|
||||
def __init__(
|
||||
self, client: "Client", model: Union[str, Tuple[str, str], "Model"]
|
||||
) -> None:
|
||||
super().__init__(client=client)
|
||||
|
||||
from replicate.model import Model # pylint: disable=import-outside-toplevel
|
||||
|
||||
if isinstance(model, Model):
|
||||
self.model = (model.owner, model.name)
|
||||
elif isinstance(model, str):
|
||||
owner, name = model.split("/", 1)
|
||||
self.model = (owner, name)
|
||||
else:
|
||||
self.model = model
|
||||
|
||||
def get(self, id: str) -> Version:
|
||||
"""
|
||||
Get a specific model version.
|
||||
|
||||
Args:
|
||||
id: The version ID.
|
||||
Returns:
|
||||
The model version.
|
||||
"""
|
||||
|
||||
resp = self._client._request(
|
||||
"GET", f"/v1/models/{self.model[0]}/{self.model[1]}/versions/{id}"
|
||||
)
|
||||
|
||||
return _json_to_version(resp.json())
|
||||
|
||||
async def async_get(self, id: str) -> Version:
|
||||
"""
|
||||
Get a specific model version.
|
||||
|
||||
Args:
|
||||
id: The version ID.
|
||||
Returns:
|
||||
The model version.
|
||||
"""
|
||||
|
||||
resp = await self._client._async_request(
|
||||
"GET", f"/v1/models/{self.model[0]}/{self.model[1]}/versions/{id}"
|
||||
)
|
||||
|
||||
return _json_to_version(resp.json())
|
||||
|
||||
def list(self) -> Page[Version]:
|
||||
"""
|
||||
Return a list of all versions for a model.
|
||||
|
||||
Returns:
|
||||
List[Version]: A list of version objects.
|
||||
"""
|
||||
|
||||
resp = self._client._request(
|
||||
"GET", f"/v1/models/{self.model[0]}/{self.model[1]}/versions"
|
||||
)
|
||||
obj = resp.json()
|
||||
obj["results"] = [_json_to_version(result) for result in obj["results"]]
|
||||
|
||||
return Page[Version](**obj)
|
||||
|
||||
async def async_list(self) -> Page[Version]:
|
||||
"""
|
||||
Return a list of all versions for a model.
|
||||
|
||||
Returns:
|
||||
List[Version]: A list of version objects.
|
||||
"""
|
||||
|
||||
resp = await self._client._async_request(
|
||||
"GET", f"/v1/models/{self.model[0]}/{self.model[1]}/versions"
|
||||
)
|
||||
obj = resp.json()
|
||||
obj["results"] = [_json_to_version(result) for result in obj["results"]]
|
||||
|
||||
return Page[Version](**obj)
|
||||
|
||||
def delete(self, id: str) -> bool:
|
||||
"""
|
||||
Delete a model version and all associated predictions, including all output files.
|
||||
|
||||
Model version deletion has some restrictions:
|
||||
|
||||
* You can only delete versions from models you own.
|
||||
* You can only delete versions from private models.
|
||||
* You cannot delete a version if someone other than you
|
||||
has run predictions with it.
|
||||
|
||||
Args:
|
||||
id: The version ID.
|
||||
"""
|
||||
|
||||
resp = self._client._request(
|
||||
"DELETE", f"/v1/models/{self.model[0]}/{self.model[1]}/versions/{id}"
|
||||
)
|
||||
return resp.status_code == 204
|
||||
|
||||
async def async_delete(self, id: str) -> bool:
|
||||
"""
|
||||
Delete a model version and all associated predictions, including all output files.
|
||||
|
||||
Model version deletion has some restrictions:
|
||||
|
||||
* You can only delete versions from models you own.
|
||||
* You can only delete versions from private models.
|
||||
* You cannot delete a version if someone other than you
|
||||
has run predictions with it.
|
||||
|
||||
Args:
|
||||
id: The version ID.
|
||||
"""
|
||||
|
||||
resp = await self._client._async_request(
|
||||
"DELETE", f"/v1/models/{self.model[0]}/{self.model[1]}/versions/{id}"
|
||||
)
|
||||
return resp.status_code == 204
|
||||
|
||||
|
||||
def _json_to_version(json: Dict[str, Any]) -> Version:
|
||||
return Version(**json)
|
||||
203
art-agent/backend/venv/Lib/site-packages/replicate/webhook.py
Normal file
203
art-agent/backend/venv/Lib/site-packages/replicate/webhook.py
Normal file
@@ -0,0 +1,203 @@
|
||||
import base64
|
||||
import hmac
|
||||
from hashlib import sha256
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Dict,
|
||||
Optional,
|
||||
overload,
|
||||
)
|
||||
|
||||
from replicate.resource import Namespace, Resource
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import httpx
|
||||
|
||||
|
||||
class WebhookSigningSecret(Resource):
|
||||
"""
|
||||
A webhook signing secret.
|
||||
"""
|
||||
|
||||
key: str
|
||||
|
||||
|
||||
class WebhookValidationError(ValueError):
|
||||
"""Base exception for webhook validation errors."""
|
||||
|
||||
|
||||
class MissingWebhookHeaderError(WebhookValidationError):
|
||||
"""Exception raised when a required webhook header is missing."""
|
||||
|
||||
|
||||
class InvalidSecretKeyError(WebhookValidationError):
|
||||
"""Exception raised when the secret key format is invalid."""
|
||||
|
||||
|
||||
class MissingWebhookBodyError(WebhookValidationError):
|
||||
"""Exception raised when the webhook body is missing."""
|
||||
|
||||
|
||||
class InvalidTimestampError(WebhookValidationError):
|
||||
"""Exception raised when the webhook timestamp is invalid or outside the tolerance."""
|
||||
|
||||
|
||||
class InvalidSignatureError(WebhookValidationError):
|
||||
"""Exception raised when the webhook signature is invalid."""
|
||||
|
||||
|
||||
class Webhooks(Namespace):
|
||||
"""
|
||||
Namespace for operations related to webhooks.
|
||||
"""
|
||||
|
||||
@property
|
||||
def default(self) -> "Webhooks.Default":
|
||||
"""
|
||||
Namespace for operations related to the default webhook.
|
||||
"""
|
||||
|
||||
return self.Default(self._client)
|
||||
|
||||
class Default(Namespace):
|
||||
"""
|
||||
Namespace for operations related to the default webhook.
|
||||
"""
|
||||
|
||||
def secret(self) -> WebhookSigningSecret:
|
||||
"""
|
||||
Get the default webhook signing secret.
|
||||
|
||||
Returns:
|
||||
WebhookSigningSecret: The default webhook signing secret.
|
||||
"""
|
||||
|
||||
resp = self._client._request("GET", "/v1/webhooks/default/secret")
|
||||
return WebhookSigningSecret(**resp.json())
|
||||
|
||||
async def async_secret(self) -> WebhookSigningSecret:
|
||||
"""
|
||||
Get the default webhook signing secret.
|
||||
|
||||
Returns:
|
||||
WebhookSigningSecret: The default webhook signing secret.
|
||||
"""
|
||||
|
||||
resp = await self._client._async_request(
|
||||
"GET", "/v1/webhooks/default/secret"
|
||||
)
|
||||
return WebhookSigningSecret(**resp.json())
|
||||
|
||||
@overload
|
||||
@staticmethod
|
||||
def validate(
|
||||
request: "httpx.Request",
|
||||
secret: WebhookSigningSecret,
|
||||
tolerance: Optional[int] = None,
|
||||
) -> bool: ...
|
||||
|
||||
@overload
|
||||
@staticmethod
|
||||
def validate(
|
||||
headers: Dict[str, str],
|
||||
body: str,
|
||||
secret: WebhookSigningSecret,
|
||||
tolerance: Optional[int] = None,
|
||||
) -> bool: ...
|
||||
|
||||
@staticmethod
|
||||
def validate( # type: ignore # pylint: disable=too-many-branches,too-many-locals
|
||||
request: Optional["httpx.Request"] = None,
|
||||
headers: Optional[Dict[str, str]] = None,
|
||||
body: Optional[str] = None,
|
||||
secret: Optional[WebhookSigningSecret] = None,
|
||||
tolerance: Optional[int] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Validate the signature from an incoming webhook request using the provided secret.
|
||||
|
||||
Args:
|
||||
request (httpx.Request): The request object.
|
||||
headers (Dict[str, str]): The request headers.
|
||||
body (str): The request body.
|
||||
secret (WebhookSigningSecret): The webhook signing secret.
|
||||
tolerance (Optional[int]): Maximum allowed time difference (in seconds) between the current time and the webhook timestamp.
|
||||
|
||||
Returns:
|
||||
None: If the request is valid.
|
||||
|
||||
Raises:
|
||||
MissingWebhookHeaderError: If required webhook headers are missing.
|
||||
InvalidSecretKeyError: If the secret key format is invalid.
|
||||
MissingWebhookBodyError: If the webhook body is missing.
|
||||
InvalidTimestampError: If the webhook timestamp is invalid or outside the tolerance.
|
||||
InvalidSignatureError: If the webhook signature is invalid.
|
||||
"""
|
||||
|
||||
if not secret:
|
||||
raise ValueError("Missing webhook signing secret")
|
||||
|
||||
if request and any([headers, body]):
|
||||
raise ValueError("Only one of request or headers/body can be provided")
|
||||
|
||||
if request and request.headers:
|
||||
webhook_id = request.headers.get("webhook-id")
|
||||
timestamp = request.headers.get("webhook-timestamp")
|
||||
signature = request.headers.get("webhook-signature")
|
||||
body = request.content.decode("utf-8")
|
||||
else:
|
||||
if not headers:
|
||||
raise MissingWebhookHeaderError("Missing webhook headers")
|
||||
|
||||
# Convert headers to case-insensitive dictionary
|
||||
headers = {k.lower(): v for k, v in headers.items()}
|
||||
|
||||
webhook_id = headers.get("webhook-id")
|
||||
timestamp = headers.get("webhook-timestamp")
|
||||
signature = headers.get("webhook-signature")
|
||||
|
||||
if not webhook_id:
|
||||
raise MissingWebhookHeaderError("Missing webhook id")
|
||||
if not timestamp:
|
||||
raise MissingWebhookHeaderError("Missing webhook timestamp")
|
||||
if not signature:
|
||||
raise MissingWebhookHeaderError("Missing webhook signature")
|
||||
if not body:
|
||||
raise MissingWebhookBodyError("Missing webhook body")
|
||||
|
||||
if tolerance is not None:
|
||||
import time # pylint: disable=import-outside-toplevel
|
||||
|
||||
current_time = int(time.time())
|
||||
webhook_time = int(timestamp)
|
||||
time_difference = abs(current_time - webhook_time)
|
||||
if time_difference > tolerance:
|
||||
raise InvalidTimestampError(
|
||||
f"Webhook timestamp is outside the allowed tolerance of {tolerance} seconds"
|
||||
)
|
||||
|
||||
signed_content = f"{webhook_id}.{timestamp}.{body}"
|
||||
|
||||
key_parts = secret.key.split("_")
|
||||
if len(key_parts) != 2:
|
||||
raise InvalidSecretKeyError(f"Invalid secret key format: {secret.key}")
|
||||
|
||||
secret_bytes = base64.b64decode(key_parts[1])
|
||||
|
||||
h = hmac.new(secret_bytes, signed_content.encode(), sha256)
|
||||
computed_signature = h.digest()
|
||||
|
||||
valid = False
|
||||
for sig in signature.split():
|
||||
sig_parts = sig.split(",")
|
||||
if len(sig_parts) < 2:
|
||||
raise InvalidSignatureError(f"Invalid signature format: {sig}")
|
||||
|
||||
sig_bytes = base64.b64decode(sig_parts[1])
|
||||
|
||||
if hmac.compare_digest(sig_bytes, computed_signature):
|
||||
valid = True
|
||||
break
|
||||
|
||||
if not valid:
|
||||
raise InvalidSignatureError("Webhook signature is invalid")
|
||||
Reference in New Issue
Block a user