genblaze-replicate 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- genblaze_replicate/__init__.py +5 -0
- genblaze_replicate/_errors.py +21 -0
- genblaze_replicate/provider.py +202 -0
- genblaze_replicate/py.typed +0 -0
- genblaze_replicate-0.2.0.dist-info/METADATA +128 -0
- genblaze_replicate-0.2.0.dist-info/RECORD +8 -0
- genblaze_replicate-0.2.0.dist-info/WHEEL +4 -0
- genblaze_replicate-0.2.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Shared Replicate error mapping — used by provider.py."""
|
|
2
|
+
|
|
3
|
+
from genblaze_core.models.enums import ProviderErrorCode
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def map_replicate_error(exc_or_msg: Exception | str) -> ProviderErrorCode:
|
|
7
|
+
"""Map a Replicate API exception (or error string) to a ProviderErrorCode."""
|
|
8
|
+
msg = (str(exc_or_msg)).lower()
|
|
9
|
+
if "rate" in msg or "429" in msg:
|
|
10
|
+
return ProviderErrorCode.RATE_LIMIT
|
|
11
|
+
if "auth" in msg or "401" in msg or "403" in msg or "token" in msg:
|
|
12
|
+
return ProviderErrorCode.AUTH_FAILURE
|
|
13
|
+
if "invalid" in msg or "400" in msg or "validation" in msg:
|
|
14
|
+
return ProviderErrorCode.INVALID_INPUT
|
|
15
|
+
if "timeout" in msg or "timed out" in msg:
|
|
16
|
+
return ProviderErrorCode.TIMEOUT
|
|
17
|
+
if "model" in msg and ("not found" in msg or "error" in msg or "does not exist" in msg):
|
|
18
|
+
return ProviderErrorCode.MODEL_ERROR
|
|
19
|
+
if "500" in msg or "502" in msg or "503" in msg:
|
|
20
|
+
return ProviderErrorCode.SERVER_ERROR
|
|
21
|
+
return ProviderErrorCode.UNKNOWN
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""ReplicateProvider — adapter for the Replicate API.
|
|
2
|
+
|
|
3
|
+
Models are free-form strings (any Replicate model slug). Default pricing is
|
|
4
|
+
compute-time-based (``predict_time`` × rate). Override per-model or wildcard::
|
|
5
|
+
|
|
6
|
+
from genblaze_replicate import ReplicateProvider
|
|
7
|
+
reg = ReplicateProvider.models_default().fork()
|
|
8
|
+
reg.register_pricing("owner/my-model", per_response_metric(...))
|
|
9
|
+
provider = ReplicateProvider(models=reg)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import mimetypes
|
|
15
|
+
from typing import Any
|
|
16
|
+
from urllib.parse import urlparse
|
|
17
|
+
|
|
18
|
+
from genblaze_core.exceptions import ProviderError
|
|
19
|
+
from genblaze_core.models.asset import Asset
|
|
20
|
+
from genblaze_core.models.enums import Modality, ProviderErrorCode
|
|
21
|
+
from genblaze_core.models.step import Step
|
|
22
|
+
from genblaze_core.providers import (
|
|
23
|
+
BaseProvider,
|
|
24
|
+
ModelRegistry,
|
|
25
|
+
ModelSpec,
|
|
26
|
+
PricingContext,
|
|
27
|
+
ProviderCapabilities,
|
|
28
|
+
per_response_metric,
|
|
29
|
+
route_by_media_type,
|
|
30
|
+
validate_asset_url,
|
|
31
|
+
)
|
|
32
|
+
from genblaze_core.runnable.config import RunnableConfig
|
|
33
|
+
|
|
34
|
+
from ._errors import map_replicate_error
|
|
35
|
+
|
|
36
|
+
# Default per-second GPU-compute rate (Nvidia A40/T4 tier).
|
|
37
|
+
_COST_PER_SEC = 0.000225
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _compute_time_cost(ctx: PricingContext) -> float | None:
|
|
41
|
+
payload = ctx.provider_payload.get("replicate") if ctx.provider_payload else None
|
|
42
|
+
if not isinstance(payload, dict):
|
|
43
|
+
return None
|
|
44
|
+
predict_time = payload.get("predict_time")
|
|
45
|
+
if predict_time is None:
|
|
46
|
+
return None
|
|
47
|
+
try:
|
|
48
|
+
return float(predict_time) * _COST_PER_SEC
|
|
49
|
+
except (TypeError, ValueError):
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# Replicate has no enumerable model list; fallback spec applies to every model.
|
|
54
|
+
_FALLBACK_SPEC = ModelSpec(
|
|
55
|
+
model_id="*",
|
|
56
|
+
pricing=per_response_metric(_compute_time_cost),
|
|
57
|
+
input_mapping=route_by_media_type({"image": "image", "video": "video", "audio": "audio"}),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class ReplicateProvider(BaseProvider):
|
|
62
|
+
"""Provider adapter for Replicate (replicate.com)."""
|
|
63
|
+
|
|
64
|
+
name = "replicate"
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
def create_registry(cls) -> ModelRegistry:
|
|
68
|
+
# No enumerable defaults — every model uses the fallback spec.
|
|
69
|
+
return ModelRegistry(defaults={}, fallback=_FALLBACK_SPEC)
|
|
70
|
+
|
|
71
|
+
def get_capabilities(self) -> ProviderCapabilities:
|
|
72
|
+
"""Replicate: multi-modal generation depending on selected model."""
|
|
73
|
+
return ProviderCapabilities(
|
|
74
|
+
supported_modalities=[Modality.IMAGE, Modality.VIDEO, Modality.AUDIO],
|
|
75
|
+
supported_inputs=["text", "image", "video", "audio"],
|
|
76
|
+
accepts_chain_input=True,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def __init__(
|
|
80
|
+
self,
|
|
81
|
+
api_token: str | None = None,
|
|
82
|
+
poll_interval: float = 1.0,
|
|
83
|
+
http_timeout: float = 30.0,
|
|
84
|
+
*,
|
|
85
|
+
models: ModelRegistry | None = None,
|
|
86
|
+
):
|
|
87
|
+
super().__init__(models=models)
|
|
88
|
+
self.poll_interval = poll_interval
|
|
89
|
+
self._client: Any = None
|
|
90
|
+
self._api_token = api_token
|
|
91
|
+
self._http_timeout = http_timeout
|
|
92
|
+
|
|
93
|
+
def _get_client(self):
|
|
94
|
+
if self._client is None:
|
|
95
|
+
try:
|
|
96
|
+
import httpx
|
|
97
|
+
import replicate
|
|
98
|
+
|
|
99
|
+
timeout = httpx.Timeout(self._http_timeout, connect=10.0)
|
|
100
|
+
if self._api_token:
|
|
101
|
+
self._client = replicate.Client(
|
|
102
|
+
api_token=self._api_token, # noqa: S106
|
|
103
|
+
timeout=timeout,
|
|
104
|
+
)
|
|
105
|
+
else:
|
|
106
|
+
self._client = replicate.Client(timeout=timeout)
|
|
107
|
+
except ImportError as exc:
|
|
108
|
+
raise ProviderError(
|
|
109
|
+
"replicate package not installed. Run: pip install replicate"
|
|
110
|
+
) from exc
|
|
111
|
+
return self._client
|
|
112
|
+
|
|
113
|
+
def submit(self, step: Step, config: RunnableConfig | None = None) -> Any:
|
|
114
|
+
client = self._get_client()
|
|
115
|
+
try:
|
|
116
|
+
input_params = self.prepare_payload(step)
|
|
117
|
+
prediction = client.predictions.create(
|
|
118
|
+
model=step.model,
|
|
119
|
+
input=input_params,
|
|
120
|
+
)
|
|
121
|
+
return prediction.id
|
|
122
|
+
except Exception as exc:
|
|
123
|
+
raise ProviderError(
|
|
124
|
+
f"Replicate submit failed: {exc}",
|
|
125
|
+
error_code=map_replicate_error(exc),
|
|
126
|
+
) from exc
|
|
127
|
+
|
|
128
|
+
def poll(self, prediction_id: Any, config: RunnableConfig | None = None) -> bool:
|
|
129
|
+
client = self._get_client()
|
|
130
|
+
try:
|
|
131
|
+
prediction = client.predictions.get(prediction_id)
|
|
132
|
+
if prediction.status in ("succeeded", "failed", "canceled"):
|
|
133
|
+
self._cache_poll_result(prediction_id, prediction)
|
|
134
|
+
return True
|
|
135
|
+
return False
|
|
136
|
+
except Exception as exc:
|
|
137
|
+
raise ProviderError(
|
|
138
|
+
f"Replicate poll failed: {exc}",
|
|
139
|
+
error_code=map_replicate_error(exc),
|
|
140
|
+
) from exc
|
|
141
|
+
|
|
142
|
+
def fetch_output(self, prediction_id: Any, step: Step) -> Step:
|
|
143
|
+
client = self._get_client()
|
|
144
|
+
try:
|
|
145
|
+
prediction = self._get_cached_poll_result(prediction_id)
|
|
146
|
+
if prediction is None:
|
|
147
|
+
prediction = client.predictions.get(prediction_id)
|
|
148
|
+
|
|
149
|
+
# Capture predict_time so registry pricing can read it.
|
|
150
|
+
metrics = getattr(prediction, "metrics", None)
|
|
151
|
+
predict_time = getattr(metrics, "predict_time", None) if metrics else None
|
|
152
|
+
|
|
153
|
+
step.provider_payload = {
|
|
154
|
+
"replicate": {
|
|
155
|
+
"prediction_id": prediction.id,
|
|
156
|
+
"model": prediction.model if hasattr(prediction, "model") else None,
|
|
157
|
+
"version": prediction.version if hasattr(prediction, "version") else None,
|
|
158
|
+
"status": prediction.status,
|
|
159
|
+
"created_at": str(prediction.created_at)
|
|
160
|
+
if hasattr(prediction, "created_at")
|
|
161
|
+
else None,
|
|
162
|
+
"predict_time": predict_time,
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if prediction.status == "failed":
|
|
167
|
+
error_msg = prediction.error or "Unknown error"
|
|
168
|
+
raise ProviderError(
|
|
169
|
+
error_msg,
|
|
170
|
+
error_code=map_replicate_error(error_msg),
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
if prediction.status == "canceled":
|
|
174
|
+
raise ProviderError(
|
|
175
|
+
"Prediction was canceled",
|
|
176
|
+
error_code=ProviderErrorCode.UNKNOWN,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
output = prediction.output
|
|
180
|
+
if isinstance(output, str):
|
|
181
|
+
output = [output]
|
|
182
|
+
elif output is None:
|
|
183
|
+
output = []
|
|
184
|
+
|
|
185
|
+
for url in output:
|
|
186
|
+
url_str = str(url)
|
|
187
|
+
validate_asset_url(url_str)
|
|
188
|
+
path = urlparse(url_str).path
|
|
189
|
+
mime, _ = mimetypes.guess_type(path)
|
|
190
|
+
if mime is None:
|
|
191
|
+
mime = f"{step.modality.value}/octet-stream"
|
|
192
|
+
step.assets.append(Asset(url=url_str, media_type=mime))
|
|
193
|
+
|
|
194
|
+
self._apply_registry_pricing(step)
|
|
195
|
+
return step
|
|
196
|
+
except ProviderError:
|
|
197
|
+
raise
|
|
198
|
+
except Exception as exc:
|
|
199
|
+
raise ProviderError(
|
|
200
|
+
f"Replicate fetch_output failed: {exc}",
|
|
201
|
+
error_code=map_replicate_error(exc),
|
|
202
|
+
) from exc
|
|
File without changes
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: genblaze-replicate
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Replicate provider adapter for genblaze
|
|
5
|
+
Project-URL: Documentation, https://github.com/backblaze-labs/genblaze
|
|
6
|
+
Project-URL: Repository, https://github.com/backblaze-labs/genblaze
|
|
7
|
+
Project-URL: Issues, https://github.com/backblaze-labs/genblaze/issues
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Typing :: Typed
|
|
12
|
+
Requires-Python: >=3.11
|
|
13
|
+
Requires-Dist: genblaze-core<0.3,>=0.2.0
|
|
14
|
+
Requires-Dist: replicate>=0.25
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
<!-- last_verified: 2026-04-22 -->
|
|
20
|
+
# genblaze-replicate
|
|
21
|
+
|
|
22
|
+
**[Replicate](https://replicate.com) provider adapter for [genblaze](https://github.com/backblaze-labs/genblaze) — run any Replicate-hosted image, video, or audio model through a unified AI pipeline with SHA-256 provenance.**
|
|
23
|
+
|
|
24
|
+
`genblaze-replicate` wraps Replicate's hosted model catalog (FLUX, SDXL, Stable Diffusion, video models, music models — anything on Replicate) as a genblaze provider. Compose Replicate calls into multi-step pipelines, persist outputs to [Backblaze B2](https://www.backblaze.com/cloud-storage?utm_source=github&utm_medium=referral&utm_campaign=ai_artifacts&utm_content=genblaze) or any S3-compatible store, and emit a tamper-evident provenance manifest for every run.
|
|
25
|
+
|
|
26
|
+
## Why genblaze-replicate
|
|
27
|
+
|
|
28
|
+
- **Any Replicate model, one API** — Pass the `owner/model` slug; genblaze handles submission, polling, asset download, and manifest capture.
|
|
29
|
+
- **Provenance by default** — SHA-256 hash of every output, canonical manifest with prompt + params + timestamps.
|
|
30
|
+
- **Composable** — Chain Replicate outputs into downstream steps (image → video, upscale, transcode).
|
|
31
|
+
- **Production-ready** — Timeouts, retries, moderation hooks, step caching, OpenTelemetry tracing.
|
|
32
|
+
- **Durable storage** — Drop the `genblaze-s3` sink in for B2/S3/R2/MinIO persistence of assets + manifests.
|
|
33
|
+
|
|
34
|
+
## Models
|
|
35
|
+
|
|
36
|
+
Supports any model hosted on Replicate — including:
|
|
37
|
+
|
|
38
|
+
- **Image** — `black-forest-labs/flux-schnell`, `black-forest-labs/flux-dev`, `stability-ai/sdxl`, `stability-ai/stable-diffusion-3`
|
|
39
|
+
- **Video** — text-to-video and image-to-video models on Replicate
|
|
40
|
+
- **Audio** — music and speech models on Replicate
|
|
41
|
+
|
|
42
|
+
Use the full `owner/model` slug from https://replicate.com/explore.
|
|
43
|
+
|
|
44
|
+
## Install
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install genblaze-replicate
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Registers the `replicate` provider via entry points; [`genblaze-core`](https://pypi.org/project/genblaze-core/) discovers it automatically.
|
|
51
|
+
|
|
52
|
+
## Quickstart — FLUX Schnell (text-to-image)
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install genblaze-core genblaze-replicate
|
|
56
|
+
export REPLICATE_API_TOKEN="r8_..."
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from genblaze_core import Modality, Pipeline
|
|
61
|
+
from genblaze_replicate import ReplicateProvider
|
|
62
|
+
|
|
63
|
+
run, manifest = (
|
|
64
|
+
Pipeline("flux-demo")
|
|
65
|
+
.step(
|
|
66
|
+
ReplicateProvider(),
|
|
67
|
+
model="black-forest-labs/flux-schnell",
|
|
68
|
+
prompt="a photorealistic golden retriever puppy in a field of wildflowers, "
|
|
69
|
+
"golden hour, shallow depth of field",
|
|
70
|
+
modality=Modality.IMAGE,
|
|
71
|
+
num_outputs=1,
|
|
72
|
+
aspect_ratio="1:1",
|
|
73
|
+
)
|
|
74
|
+
.run()
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
print(run.steps[0].assets[0].url)
|
|
78
|
+
print(manifest.canonical_hash)
|
|
79
|
+
assert manifest.verify()
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Quickstart — persist to Backblaze B2
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pip install genblaze-core genblaze-replicate genblaze-s3
|
|
86
|
+
export REPLICATE_API_TOKEN="r8_..."
|
|
87
|
+
export B2_KEY_ID="..." B2_APP_KEY="..."
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from genblaze_core import KeyStrategy, ObjectStorageSink, Pipeline
|
|
92
|
+
from genblaze_replicate import ReplicateProvider
|
|
93
|
+
from genblaze_s3 import S3StorageBackend
|
|
94
|
+
|
|
95
|
+
storage = ObjectStorageSink(
|
|
96
|
+
S3StorageBackend.for_backblaze("my-bucket"),
|
|
97
|
+
key_strategy=KeyStrategy.CONTENT_ADDRESSABLE,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
result = (
|
|
101
|
+
Pipeline("flux-b2")
|
|
102
|
+
.step(ReplicateProvider(), model="black-forest-labs/flux-schnell",
|
|
103
|
+
prompt="cyberpunk tokyo street at night, neon reflections on wet pavement")
|
|
104
|
+
.run(sink=storage, timeout=120)
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
print(result.run.steps[0].assets[0].url) # durable B2 URL
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Credentials
|
|
111
|
+
|
|
112
|
+
| Env var | Where to get it |
|
|
113
|
+
|---|---|
|
|
114
|
+
| `REPLICATE_API_TOKEN` | https://replicate.com/account/api-tokens |
|
|
115
|
+
|
|
116
|
+
## Documentation
|
|
117
|
+
|
|
118
|
+
- **Main repo**: https://github.com/backblaze-labs/genblaze
|
|
119
|
+
- **Runnable example**: [`replicate_flux_pipeline.py`](https://github.com/backblaze-labs/genblaze/blob/main/examples/replicate_flux_pipeline.py)
|
|
120
|
+
|
|
121
|
+
## Related packages
|
|
122
|
+
|
|
123
|
+
- [`genblaze-core`](https://pypi.org/project/genblaze-core/) — the pipeline SDK
|
|
124
|
+
- [`genblaze-s3`](https://pypi.org/project/genblaze-s3/) — durable storage on [Backblaze B2](https://www.backblaze.com/cloud-storage?utm_source=github&utm_medium=referral&utm_campaign=ai_artifacts&utm_content=genblaze) and other S3-compatible backends
|
|
125
|
+
|
|
126
|
+
## License
|
|
127
|
+
|
|
128
|
+
MIT
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
genblaze_replicate/__init__.py,sha256=Z_DPw0bY5RwVIM-dQotVAqYBjsDZ4hTK0NxPR4RXjoE,139
|
|
2
|
+
genblaze_replicate/_errors.py,sha256=FIzMWCKlFVGW1xZ8wwEpqKbtKxyQC-mnka_dV3kqq2g,988
|
|
3
|
+
genblaze_replicate/provider.py,sha256=IRfhiO99O9SI-m7odbY0beiiqFfEcaShPyaXPfFdXx4,7242
|
|
4
|
+
genblaze_replicate/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
genblaze_replicate-0.2.0.dist-info/METADATA,sha256=mQFaJhdwu4UNEcA_LygBKddQntSY3eUTrfvefqGkMpw,4846
|
|
6
|
+
genblaze_replicate-0.2.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
7
|
+
genblaze_replicate-0.2.0.dist-info/entry_points.txt,sha256=_7zgMbb_050SVJbdqCV0ApUVpqMDIWEtJ_074uYvC9g,70
|
|
8
|
+
genblaze_replicate-0.2.0.dist-info/RECORD,,
|