Feedback from building a screenplay-to-previs pipeline (Backblaze Gen Media Hackathon)
Project: [Reel](https://github.com/woadi-vector/reel) — turns a screenplay scene
into a previsualized cut (shot breakdown → stills → motion clips → score → assembled
MP4), stored on B2 with provenance. Built during the hackathon.
SDK versions: genblaze 0.4.1, genblaze-core 0.3.4, genblaze-s3 0.3.4,
genblaze-replicate, genblaze-gmicloud 0.3.2
Thanks for the SDK — the Pipeline.step() model, ObjectStorageSink, and the
manifest provenance are genuinely good, and the hierarchical key strategy meant I
got a well-organized bucket for free. Below is friction I hit, roughly in order of
impact. Happy to split any of these into separate issues if that's more useful.
1. Cross-provider image→video handoff forces direct S3 calls (highest impact)
Image-to-video models require a publicly fetchable start image. Kling
(kwaivgi/kling-v2.1) takes a start_image URL that its servers fetch. But
assets written through ObjectStorageSink land in a private B2 bucket, so the
video provider can't retrieve them.
.from_result() chaining didn't bridge this — the video step failed with:
422 Input validation failed: - input: start_image is required
The working pattern turned out to be provider-to-provider: take the image
provider's own public delivery URL and hand it straight to the video model, then
persist the resulting clip to B2 afterward. That works, but it means the video
and audio stages leave the Genblaze sink path and use direct S3 calls, which is
exactly what the reference sample (genblaze-gen-media-multi-provider-sample)
says not to do ("zero direct boto3 calls").
Suggestion: a first-class path for cross-provider chaining where the
downstream provider needs a fetchable URL — e.g. an opt-in presigned-URL handoff
from the sink, or a documented chain(public=True) mode. Right now the naive
approach (chain through the sink) fails in a way that's hard to diagnose, because
the error surfaces as a validation error on the video step rather than anything
pointing at storage visibility.
At minimum this deserves a documented note: private storage breaks image→video
chaining, and here's the pattern that works. I lost real time to it.
2. Non-official Replicate models 404 on name:version strings
meta/musicgen is not an "official" Replicate model, so it needs a version. But:
replicate.run("meta/musicgen") # 404
replicate.run("meta/musicgen:671ac645...") # 404
The second one 404s because the client splits on / into (owner, name) and the
:version suffix rides along in name. What works:
m = replicate.models.get("meta/musicgen")
v = m.versions.list()[0]
replicate.run(v, input={...}) # works
I see v0.5.0 adds "automatic version resolution" for Replicate community models —
if that covers this case, great, and it'd be worth calling out explicitly in the
release notes since the failure mode (a bare 404 with no detail) gives you nothing
to work with.
3. Model IDs are the single biggest source of lost time
Nearly every failure in this build traced back to a model string that didn't
resolve. Doc/docstring examples that don't exist on a given account are especially
costly, e.g. genblaze_gmicloud.chat's docstring cites
"deepseek-ai/DeepSeek-V3", which 404'd for me:
GMICloud chat failed (404): No matching target server found for model deepseek-ai/DeepSeek-V3
What finally worked was introspecting the registries:
from genblaze_gmicloud.models import build_image_registry, build_video_registry
build_image_registry().known()
Suggestions:
- Make registry introspection a documented, first-class thing — a
genblaze models --provider gmicloud CLI would have saved me hours.
- Prefer account-verified examples in docstrings, or annotate them as
illustrative only.
ModelRegistry.known() being a method while the object also exposes items /
families took some trial and error; a short registry doc page would help.
4. GMICloud chat: default base URL fails with an SSL error
genblaze_gmicloud/chat.py has _DEFAULT_BASE_URL = "https://api.gmi-serving.com/v1",
while _base.py uses https://console.gmicloud.ai/api/v1/ie/requestqueue/apikey.
Calling chat() against the default gave:
GMICloud chat failed: [SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1032)
on a normal home connection (no proxy/VPN). Two different base URLs in one package
is confusing on its own; if one of them is stale, that's worth a fix. Also relevant:
chat models on my GMICloud account appeared to be dedicated-instance (hourly GPU
deployment) rather than shared serverless inference, which may be why the shared
endpoint didn't serve them. Documenting that distinction would help — I nearly
started a $6/hr deployment by clicking through from a model card.
5. ParquetSink table/schema names aren't documented
I wired ParquetSink alongside the B2 sink to build a queryable provenance index
(the feature I most wanted: "which model made which shot, and what did it cost").
But I had to guess at the output layout — single files vs. partitioned dirs, and
table names (runs / steps / assets?). A short schema doc would make this a
much more discoverable feature, because it's a genuinely strong differentiator.
6. Throttling needs a documented retry story
Replicate throttles hard below a credit threshold (burst-of-1), which silently
killed multi-step runs mid-scene:
429 Request was throttled. Your rate limit ... is reduced to 6 requests per minute
with a burst of 1 requests while you have less than $5.0 in credit.
I wrapped every call in retry-with-backoff, which fixed it. Since Genblaze already
owns the execution loop, built-in retry-on-429 with backoff (or a documented
retries= on .step()) seems like a natural fit — this will hit anyone running a
multi-shot pipeline on a new account.
7. Corroborating the sample's chat() vs Pipeline.step() note
The multi-provider sample flags that genblaze-openai's chat() is a standalone
function rather than a BaseProvider, so planning steps can't ride
Pipeline.step(). I hit the same asymmetry with genblaze-gmicloud.chat() — the
scene-breakdown stage sits outside the pipeline and its provenance has to be
persisted by hand, which means my most important upstream step (the one that
determines every downstream asset) is the one not covered by manifests.
Making chat a first-class provider would close a real provenance gap, not just an
ergonomic one.
What worked well (so it doesn't get lost)
KeyStrategy.HIERARCHICAL — organized bucket for free, no key design needed.
- Manifests: provider/model/prompt/hash per asset made "provenance-aware
workflow" a real feature rather than a claim.
response_format=<PydanticModel> on GMICloud chat() was excellent — schema-
enforced JSON with zero defensive parsing. When I later moved that step to a
provider without it, I had to reintroduce fence-stripping and retry logic. That
feature is worth propagating everywhere chat exists.
- Draft-versioned lineage via
from_result() / parent_run_id mapped cleanly onto
screenplay drafts (v26 → v27 forks instead of overwriting).
Feedback from building a screenplay-to-previs pipeline (Backblaze Gen Media Hackathon)
Project: [Reel](https://github.com/woadi-vector/reel) — turns a screenplay scene
into a previsualized cut (shot breakdown → stills → motion clips → score → assembled
MP4), stored on B2 with provenance. Built during the hackathon.
SDK versions:
genblaze0.4.1,genblaze-core0.3.4,genblaze-s30.3.4,genblaze-replicate,genblaze-gmicloud0.3.2Thanks for the SDK — the
Pipeline.step()model,ObjectStorageSink, and themanifest provenance are genuinely good, and the hierarchical key strategy meant I
got a well-organized bucket for free. Below is friction I hit, roughly in order of
impact. Happy to split any of these into separate issues if that's more useful.
1. Cross-provider image→video handoff forces direct S3 calls (highest impact)
Image-to-video models require a publicly fetchable start image. Kling
(
kwaivgi/kling-v2.1) takes astart_imageURL that its servers fetch. Butassets written through
ObjectStorageSinkland in a private B2 bucket, so thevideo provider can't retrieve them.
.from_result()chaining didn't bridge this — the video step failed with:The working pattern turned out to be provider-to-provider: take the image
provider's own public delivery URL and hand it straight to the video model, then
persist the resulting clip to B2 afterward. That works, but it means the video
and audio stages leave the Genblaze sink path and use direct S3 calls, which is
exactly what the reference sample (
genblaze-gen-media-multi-provider-sample)says not to do ("zero direct boto3 calls").
Suggestion: a first-class path for cross-provider chaining where the
downstream provider needs a fetchable URL — e.g. an opt-in presigned-URL handoff
from the sink, or a documented
chain(public=True)mode. Right now the naiveapproach (chain through the sink) fails in a way that's hard to diagnose, because
the error surfaces as a validation error on the video step rather than anything
pointing at storage visibility.
At minimum this deserves a documented note: private storage breaks image→video
chaining, and here's the pattern that works. I lost real time to it.
2. Non-official Replicate models 404 on
name:versionstringsmeta/musicgenis not an "official" Replicate model, so it needs a version. But:The second one 404s because the client splits on
/into(owner, name)and the:versionsuffix rides along inname. What works:I see v0.5.0 adds "automatic version resolution" for Replicate community models —
if that covers this case, great, and it'd be worth calling out explicitly in the
release notes since the failure mode (a bare 404 with no detail) gives you nothing
to work with.
3. Model IDs are the single biggest source of lost time
Nearly every failure in this build traced back to a model string that didn't
resolve. Doc/docstring examples that don't exist on a given account are especially
costly, e.g.
genblaze_gmicloud.chat's docstring cites"deepseek-ai/DeepSeek-V3", which 404'd for me:What finally worked was introspecting the registries:
Suggestions:
genblaze models --provider gmicloudCLI would have saved me hours.illustrative only.
ModelRegistry.known()being a method while the object also exposesitems/familiestook some trial and error; a short registry doc page would help.4. GMICloud chat: default base URL fails with an SSL error
genblaze_gmicloud/chat.pyhas_DEFAULT_BASE_URL = "https://api.gmi-serving.com/v1",while
_base.pyuseshttps://console.gmicloud.ai/api/v1/ie/requestqueue/apikey.Calling
chat()against the default gave:on a normal home connection (no proxy/VPN). Two different base URLs in one package
is confusing on its own; if one of them is stale, that's worth a fix. Also relevant:
chat models on my GMICloud account appeared to be dedicated-instance (hourly GPU
deployment) rather than shared serverless inference, which may be why the shared
endpoint didn't serve them. Documenting that distinction would help — I nearly
started a $6/hr deployment by clicking through from a model card.
5.
ParquetSinktable/schema names aren't documentedI wired
ParquetSinkalongside the B2 sink to build a queryable provenance index(the feature I most wanted: "which model made which shot, and what did it cost").
But I had to guess at the output layout — single files vs. partitioned dirs, and
table names (
runs/steps/assets?). A short schema doc would make this amuch more discoverable feature, because it's a genuinely strong differentiator.
6. Throttling needs a documented retry story
Replicate throttles hard below a credit threshold (burst-of-1), which silently
killed multi-step runs mid-scene:
I wrapped every call in retry-with-backoff, which fixed it. Since Genblaze already
owns the execution loop, built-in retry-on-429 with backoff (or a documented
retries=on.step()) seems like a natural fit — this will hit anyone running amulti-shot pipeline on a new account.
7. Corroborating the sample's
chat()vsPipeline.step()noteThe multi-provider sample flags that
genblaze-openai'schat()is a standalonefunction rather than a
BaseProvider, so planning steps can't ridePipeline.step(). I hit the same asymmetry withgenblaze-gmicloud.chat()— thescene-breakdown stage sits outside the pipeline and its provenance has to be
persisted by hand, which means my most important upstream step (the one that
determines every downstream asset) is the one not covered by manifests.
Making chat a first-class provider would close a real provenance gap, not just an
ergonomic one.
What worked well (so it doesn't get lost)
KeyStrategy.HIERARCHICAL— organized bucket for free, no key design needed.workflow" a real feature rather than a claim.
response_format=<PydanticModel>on GMICloudchat()was excellent — schema-enforced JSON with zero defensive parsing. When I later moved that step to a
provider without it, I had to reintroduce fence-stripping and retry logic. That
feature is worth propagating everywhere chat exists.
from_result()/parent_run_idmapped cleanly ontoscreenplay drafts (v26 → v27 forks instead of overwriting).