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.
@@ -0,0 +1,5 @@
1
+ """Replicate provider adapter for genblaze."""
2
+
3
+ from genblaze_replicate.provider import ReplicateProvider
4
+
5
+ __all__ = ["ReplicateProvider"]
@@ -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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [genblaze.providers]
2
+ replicate = genblaze_replicate:ReplicateProvider