genblaze-core 0.1.0__tar.gz
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_core-0.1.0/.gitignore +76 -0
- genblaze_core-0.1.0/PKG-INFO +167 -0
- genblaze_core-0.1.0/README.md +130 -0
- genblaze_core-0.1.0/genblaze_core/__init__.py +110 -0
- genblaze_core-0.1.0/genblaze_core/_utils.py +158 -0
- genblaze_core-0.1.0/genblaze_core/_version.py +1 -0
- genblaze_core-0.1.0/genblaze_core/agents/__init__.py +20 -0
- genblaze_core-0.1.0/genblaze_core/agents/evaluator.py +100 -0
- genblaze_core-0.1.0/genblaze_core/agents/loop.py +307 -0
- genblaze_core-0.1.0/genblaze_core/builders/__init__.py +6 -0
- genblaze_core-0.1.0/genblaze_core/builders/run_builder.py +55 -0
- genblaze_core-0.1.0/genblaze_core/builders/step_builder.py +67 -0
- genblaze_core-0.1.0/genblaze_core/canonical/__init__.py +5 -0
- genblaze_core-0.1.0/genblaze_core/canonical/_normalize.py +59 -0
- genblaze_core-0.1.0/genblaze_core/canonical/json.py +23 -0
- genblaze_core-0.1.0/genblaze_core/exceptions.py +44 -0
- genblaze_core-0.1.0/genblaze_core/media/__init__.py +83 -0
- genblaze_core-0.1.0/genblaze_core/media/aac.py +97 -0
- genblaze_core-0.1.0/genblaze_core/media/base.py +66 -0
- genblaze_core-0.1.0/genblaze_core/media/embedder.py +162 -0
- genblaze_core-0.1.0/genblaze_core/media/flac.py +90 -0
- genblaze_core-0.1.0/genblaze_core/media/jpeg.py +110 -0
- genblaze_core-0.1.0/genblaze_core/media/mp3.py +82 -0
- genblaze_core-0.1.0/genblaze_core/media/mp4.py +353 -0
- genblaze_core-0.1.0/genblaze_core/media/png.py +71 -0
- genblaze_core-0.1.0/genblaze_core/media/sidecar.py +124 -0
- genblaze_core-0.1.0/genblaze_core/media/wav.py +173 -0
- genblaze_core-0.1.0/genblaze_core/media/webp.py +78 -0
- genblaze_core-0.1.0/genblaze_core/models/__init__.py +35 -0
- genblaze_core-0.1.0/genblaze_core/models/asset.py +100 -0
- genblaze_core-0.1.0/genblaze_core/models/enums.py +59 -0
- genblaze_core-0.1.0/genblaze_core/models/manifest.py +300 -0
- genblaze_core-0.1.0/genblaze_core/models/policy.py +16 -0
- genblaze_core-0.1.0/genblaze_core/models/prompt_template.py +42 -0
- genblaze_core-0.1.0/genblaze_core/models/run.py +41 -0
- genblaze_core-0.1.0/genblaze_core/models/step.py +70 -0
- genblaze_core-0.1.0/genblaze_core/observability/__init__.py +24 -0
- genblaze_core-0.1.0/genblaze_core/observability/events.py +94 -0
- genblaze_core-0.1.0/genblaze_core/observability/logger.py +59 -0
- genblaze_core-0.1.0/genblaze_core/observability/span.py +70 -0
- genblaze_core-0.1.0/genblaze_core/observability/tracer.py +297 -0
- genblaze_core-0.1.0/genblaze_core/pipeline/__init__.py +7 -0
- genblaze_core-0.1.0/genblaze_core/pipeline/cache.py +104 -0
- genblaze_core-0.1.0/genblaze_core/pipeline/moderation.py +82 -0
- genblaze_core-0.1.0/genblaze_core/pipeline/pipeline.py +1422 -0
- genblaze_core-0.1.0/genblaze_core/pipeline/result.py +112 -0
- genblaze_core-0.1.0/genblaze_core/pipeline/streaming.py +126 -0
- genblaze_core-0.1.0/genblaze_core/pipeline/template.py +176 -0
- genblaze_core-0.1.0/genblaze_core/providers/__init__.py +17 -0
- genblaze_core-0.1.0/genblaze_core/providers/_ffmpeg_utils.py +112 -0
- genblaze_core-0.1.0/genblaze_core/providers/base.py +635 -0
- genblaze_core-0.1.0/genblaze_core/providers/compositor.py +147 -0
- genblaze_core-0.1.0/genblaze_core/providers/progress.py +32 -0
- genblaze_core-0.1.0/genblaze_core/providers/registry.py +32 -0
- genblaze_core-0.1.0/genblaze_core/providers/transform.py +376 -0
- genblaze_core-0.1.0/genblaze_core/py.typed +0 -0
- genblaze_core-0.1.0/genblaze_core/runnable/__init__.py +6 -0
- genblaze_core-0.1.0/genblaze_core/runnable/base.py +33 -0
- genblaze_core-0.1.0/genblaze_core/runnable/config.py +24 -0
- genblaze_core-0.1.0/genblaze_core/sinks/__init__.py +6 -0
- genblaze_core-0.1.0/genblaze_core/sinks/base.py +43 -0
- genblaze_core-0.1.0/genblaze_core/sinks/parquet.py +190 -0
- genblaze_core-0.1.0/genblaze_core/storage/__init__.py +19 -0
- genblaze_core-0.1.0/genblaze_core/storage/base.py +164 -0
- genblaze_core-0.1.0/genblaze_core/storage/sink.py +262 -0
- genblaze_core-0.1.0/genblaze_core/storage/transfer.py +549 -0
- genblaze_core-0.1.0/genblaze_core/testing.py +338 -0
- genblaze_core-0.1.0/genblaze_core/webhooks/__init__.py +6 -0
- genblaze_core-0.1.0/genblaze_core/webhooks/notifier.py +294 -0
- genblaze_core-0.1.0/genblaze_core/webhooks/sink.py +49 -0
- genblaze_core-0.1.0/pyproject.toml +73 -0
- genblaze_core-0.1.0/tests/__init__.py +0 -0
- genblaze_core-0.1.0/tests/conftest.py +116 -0
- genblaze_core-0.1.0/tests/golden/__init__.py +0 -0
- genblaze_core-0.1.0/tests/golden/test_canonical_hash_stability.py +71 -0
- genblaze_core-0.1.0/tests/golden/test_png_roundtrip.py +58 -0
- genblaze_core-0.1.0/tests/integration/__init__.py +0 -0
- genblaze_core-0.1.0/tests/integration/test_pipeline_chain_integration.py +193 -0
- genblaze_core-0.1.0/tests/integration/test_pipeline_embed_roundtrip.py +68 -0
- genblaze_core-0.1.0/tests/unit/__init__.py +0 -0
- genblaze_core-0.1.0/tests/unit/test_aac_handler.py +71 -0
- genblaze_core-0.1.0/tests/unit/test_agent_loop.py +282 -0
- genblaze_core-0.1.0/tests/unit/test_builders.py +68 -0
- genblaze_core-0.1.0/tests/unit/test_canonical.py +67 -0
- genblaze_core-0.1.0/tests/unit/test_canonical_properties.py +69 -0
- genblaze_core-0.1.0/tests/unit/test_compositor.py +296 -0
- genblaze_core-0.1.0/tests/unit/test_embedder.py +143 -0
- genblaze_core-0.1.0/tests/unit/test_ffmpeg_utils.py +123 -0
- genblaze_core-0.1.0/tests/unit/test_flac_handler.py +87 -0
- genblaze_core-0.1.0/tests/unit/test_jpeg.py +64 -0
- genblaze_core-0.1.0/tests/unit/test_mock_providers.py +216 -0
- genblaze_core-0.1.0/tests/unit/test_models.py +425 -0
- genblaze_core-0.1.0/tests/unit/test_moderation.py +318 -0
- genblaze_core-0.1.0/tests/unit/test_mp3.py +51 -0
- genblaze_core-0.1.0/tests/unit/test_mp4.py +238 -0
- genblaze_core-0.1.0/tests/unit/test_object_storage_sink.py +849 -0
- genblaze_core-0.1.0/tests/unit/test_observability.py +101 -0
- genblaze_core-0.1.0/tests/unit/test_parquet.py +101 -0
- genblaze_core-0.1.0/tests/unit/test_pipeline.py +1593 -0
- genblaze_core-0.1.0/tests/unit/test_pipeline_template.py +266 -0
- genblaze_core-0.1.0/tests/unit/test_png.py +45 -0
- genblaze_core-0.1.0/tests/unit/test_policy.py +104 -0
- genblaze_core-0.1.0/tests/unit/test_progress.py +46 -0
- genblaze_core-0.1.0/tests/unit/test_prompt_template.py +173 -0
- genblaze_core-0.1.0/tests/unit/test_provider_registry.py +70 -0
- genblaze_core-0.1.0/tests/unit/test_provider_retry.py +343 -0
- genblaze_core-0.1.0/tests/unit/test_runnable.py +21 -0
- genblaze_core-0.1.0/tests/unit/test_schema_compliance.py +74 -0
- genblaze_core-0.1.0/tests/unit/test_secret_redaction.py +82 -0
- genblaze_core-0.1.0/tests/unit/test_sidecar.py +92 -0
- genblaze_core-0.1.0/tests/unit/test_storage_backend.py +109 -0
- genblaze_core-0.1.0/tests/unit/test_streaming.py +361 -0
- genblaze_core-0.1.0/tests/unit/test_sync_provider.py +349 -0
- genblaze_core-0.1.0/tests/unit/test_tracer.py +245 -0
- genblaze_core-0.1.0/tests/unit/test_transfer.py +762 -0
- genblaze_core-0.1.0/tests/unit/test_transform.py +295 -0
- genblaze_core-0.1.0/tests/unit/test_unicode.py +30 -0
- genblaze_core-0.1.0/tests/unit/test_utils.py +135 -0
- genblaze_core-0.1.0/tests/unit/test_wav.py +59 -0
- genblaze_core-0.1.0/tests/unit/test_webhook.py +287 -0
- genblaze_core-0.1.0/tests/unit/test_webp.py +64 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
|
|
7
|
+
# Distribution / packaging
|
|
8
|
+
dist/
|
|
9
|
+
build/
|
|
10
|
+
*.egg-info/
|
|
11
|
+
*.egg
|
|
12
|
+
wheels/
|
|
13
|
+
/MANIFEST
|
|
14
|
+
|
|
15
|
+
# Virtual environments
|
|
16
|
+
.venv/
|
|
17
|
+
venv/
|
|
18
|
+
env/
|
|
19
|
+
ENV/
|
|
20
|
+
|
|
21
|
+
# IDE / editors
|
|
22
|
+
.vscode/
|
|
23
|
+
.idea/
|
|
24
|
+
*.swp
|
|
25
|
+
*.swo
|
|
26
|
+
*~
|
|
27
|
+
.project
|
|
28
|
+
.settings/
|
|
29
|
+
|
|
30
|
+
# Testing / coverage
|
|
31
|
+
.pytest_cache/
|
|
32
|
+
.coverage
|
|
33
|
+
.coverage.*
|
|
34
|
+
htmlcov/
|
|
35
|
+
coverage.xml
|
|
36
|
+
*.cover
|
|
37
|
+
|
|
38
|
+
# Type checking
|
|
39
|
+
.mypy_cache/
|
|
40
|
+
.pytype/
|
|
41
|
+
.pyre/
|
|
42
|
+
|
|
43
|
+
# Linting
|
|
44
|
+
.ruff_cache/
|
|
45
|
+
|
|
46
|
+
# OS files
|
|
47
|
+
.DS_Store
|
|
48
|
+
Thumbs.db
|
|
49
|
+
ehthumbs.db
|
|
50
|
+
|
|
51
|
+
# Environment / secrets
|
|
52
|
+
.env
|
|
53
|
+
.env.*
|
|
54
|
+
!.env.example
|
|
55
|
+
*.pem
|
|
56
|
+
*.key
|
|
57
|
+
credentials.json
|
|
58
|
+
|
|
59
|
+
# Jupyter
|
|
60
|
+
.ipynb_checkpoints/
|
|
61
|
+
|
|
62
|
+
# MkDocs build output
|
|
63
|
+
site/
|
|
64
|
+
|
|
65
|
+
# Claude Code — local/ephemeral
|
|
66
|
+
.claude/settings.local.json
|
|
67
|
+
.claude/worktrees/
|
|
68
|
+
CLAUDE.local.md
|
|
69
|
+
|
|
70
|
+
# Hypothesis test cache
|
|
71
|
+
.hypothesis/
|
|
72
|
+
|
|
73
|
+
# Temp files
|
|
74
|
+
*.tmp
|
|
75
|
+
*.bak
|
|
76
|
+
*.log
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: genblaze-core
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Core SDK for genblaze media generation orchestration
|
|
5
|
+
Project-URL: Documentation, https://github.com/backblaze-labs/genblaze
|
|
6
|
+
Project-URL: Repository, https://github.com/backblaze-labs/genblaze
|
|
7
|
+
Project-URL: Changelog, https://github.com/backblaze-labs/genblaze/blob/main/CHANGELOG.md
|
|
8
|
+
Project-URL: Issues, https://github.com/backblaze-labs/genblaze/issues
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Multimedia
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Requires-Dist: pillow>=10.0
|
|
21
|
+
Requires-Dist: pydantic<3,>=2.0
|
|
22
|
+
Provides-Extra: audio
|
|
23
|
+
Requires-Dist: mutagen>=1.47; extra == 'audio'
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: hypothesis>=6.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: jsonschema>=4.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: mutagen>=1.47; extra == 'dev'
|
|
28
|
+
Requires-Dist: mypy>=1.8; extra == 'dev'
|
|
29
|
+
Requires-Dist: pyarrow>=14.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
34
|
+
Provides-Extra: parquet
|
|
35
|
+
Requires-Dist: pyarrow>=14.0; extra == 'parquet'
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
|
|
38
|
+
<!-- last_verified: 2026-04-22 -->
|
|
39
|
+
# genblaze-core
|
|
40
|
+
|
|
41
|
+
**Python SDK for building generative AI pipelines across video, image, and audio — with built-in SHA-256 provenance.**
|
|
42
|
+
|
|
43
|
+
`genblaze-core` is the core of [genblaze](https://github.com/backblaze-labs/genblaze), an open-source orchestration framework by [Backblaze](https://www.backblaze.com/cloud-storage?utm_source=github&utm_medium=referral&utm_campaign=ai_artifacts&utm_content=genblaze) for composing multi-step AI media generation workflows. It gives you a single, provider-agnostic `Pipeline` API for text-to-video, text-to-image, text-to-speech, image-to-video, and audio generation — so you can swap models (Sora, Veo, Runway, Luma, Flux, DALL·E, ElevenLabs, Stable Audio, LMNT, GMICloud) without rewriting pipeline logic.
|
|
44
|
+
|
|
45
|
+
Every pipeline run emits a canonical, hash-verified **provenance manifest** — a tamper-evident JSON document capturing the provider, model, prompt, parameters, timestamps, and SHA-256 hash of every generated asset. Manifests can be embedded directly into PNG, JPEG, WebP, MP4, MP3, and WAV files, uploaded alongside assets to S3-compatible storage, or exported to Parquet for analytics.
|
|
46
|
+
|
|
47
|
+
## Why genblaze-core
|
|
48
|
+
|
|
49
|
+
- **One API for every generative AI provider** — Pipelines, not per-vendor SDK glue. Fluent, composable, chainable.
|
|
50
|
+
- **Built-in provenance** — Every asset gets a SHA-256–verified manifest. Prove how media was made; detect tampering.
|
|
51
|
+
- **Production-ready** — Retries, timeouts, progress streaming, moderation hooks, OpenTelemetry tracing, step caching.
|
|
52
|
+
- **Storage-agnostic sinks** — Drop into Backblaze B2, AWS S3, Cloudflare R2, MinIO, Parquet, or local disk.
|
|
53
|
+
- **Policy + privacy controls** — Redact prompts, strip params, pointer-mode for sensitive content.
|
|
54
|
+
- **Agent loops + templates** — Evaluator-driven iteration, reusable pipeline and step templates.
|
|
55
|
+
- **Zero lock-in** — MIT licensed, typed, lazy imports, provider adapters are separate packages.
|
|
56
|
+
|
|
57
|
+
## Features
|
|
58
|
+
|
|
59
|
+
| Capability | What you get |
|
|
60
|
+
|---|---|
|
|
61
|
+
| `Pipeline` API | Fluent multi-step generation, fan-in (`input_from`), AV compositing via FFmpeg |
|
|
62
|
+
| Provider discovery | Entry-point–based registry — `pip install genblaze-<provider>` and it's available |
|
|
63
|
+
| Manifest (Pydantic) | `Run`, `Step`, `Asset` models with canonical JSON hashing and `.verify()` |
|
|
64
|
+
| Media embedding | `PngHandler`, `Mp4Handler`, `Mp3Handler`, etc. — embed + extract manifests in-file |
|
|
65
|
+
| Storage sink | `ObjectStorageSink` with hierarchical or content-addressable key layout |
|
|
66
|
+
| Parquet sink | Partitioned run/step/asset tables for downstream analytics |
|
|
67
|
+
| Observability | `OTelTracer`, `LoggingTracer`, `CompositeTracer`, structured events |
|
|
68
|
+
| Agents | `AgentLoop` with pluggable `Evaluator` for iterative refinement |
|
|
69
|
+
| Moderation | Pre/post moderation hooks, configurable embed policies |
|
|
70
|
+
| Testing | `MockProvider`, `MockVideoProvider`, `MockAudioProvider` for offline tests |
|
|
71
|
+
|
|
72
|
+
## Install
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pip install genblaze-core
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Optional extras:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
pip install "genblaze-core[parquet]" # ParquetSink for analytics
|
|
82
|
+
pip install "genblaze-core[audio]" # Audio metadata embedding (mutagen)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Add provider adapters separately:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
pip install genblaze-openai genblaze-google genblaze-runway genblaze-luma \
|
|
89
|
+
genblaze-decart genblaze-replicate genblaze-elevenlabs \
|
|
90
|
+
genblaze-stability-audio genblaze-lmnt genblaze-gmicloud
|
|
91
|
+
|
|
92
|
+
pip install genblaze-s3 # Storage backend for Backblaze B2 / AWS S3 / R2 / MinIO
|
|
93
|
+
pip install genblaze-cli # Extract / verify / replay / index manifests
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Quickstart — local, zero API keys
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from genblaze_core import Modality, Pipeline
|
|
100
|
+
from genblaze_core.testing import MockVideoProvider
|
|
101
|
+
|
|
102
|
+
run, manifest = (
|
|
103
|
+
Pipeline("hello-genblaze")
|
|
104
|
+
.step(MockVideoProvider(), model="mock-v1",
|
|
105
|
+
prompt="A drone shot over a coastal city at golden hour",
|
|
106
|
+
modality=Modality.VIDEO)
|
|
107
|
+
.run()
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
print(manifest.canonical_hash) # deterministic SHA-256 of the run
|
|
111
|
+
print(manifest.verify()) # True
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Quickstart — Sora + Backblaze B2 storage
|
|
115
|
+
|
|
116
|
+
Generate a video, upload it + its manifest to [Backblaze B2](https://www.backblaze.com/cloud-storage?utm_source=github&utm_medium=referral&utm_campaign=ai_artifacts&utm_content=genblaze), verify the hash:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
pip install genblaze-core genblaze-openai genblaze-s3
|
|
120
|
+
export OPENAI_API_KEY="sk-..."
|
|
121
|
+
export B2_KEY_ID="..."
|
|
122
|
+
export B2_APP_KEY="..."
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from genblaze_core import KeyStrategy, Modality, ObjectStorageSink, Pipeline
|
|
127
|
+
from genblaze_openai import SoraProvider
|
|
128
|
+
from genblaze_s3 import S3StorageBackend
|
|
129
|
+
|
|
130
|
+
storage = ObjectStorageSink(
|
|
131
|
+
S3StorageBackend.for_backblaze("my-bucket"),
|
|
132
|
+
key_strategy=KeyStrategy.HIERARCHICAL,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
result = (
|
|
136
|
+
Pipeline("hero-reel")
|
|
137
|
+
.step(SoraProvider(), model="sora-2",
|
|
138
|
+
prompt="Aerial flyover of a mountain lake at sunrise",
|
|
139
|
+
modality=Modality.VIDEO, seconds=4, size="1280x720")
|
|
140
|
+
.run(sink=storage, timeout=300)
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
print(result.run.steps[0].assets[0].url) # durable B2 URL
|
|
144
|
+
print(result.manifest.canonical_hash) # SHA-256 of the full run
|
|
145
|
+
assert result.manifest.verify()
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Storage — Backblaze B2 recommended
|
|
149
|
+
|
|
150
|
+
[Backblaze B2](https://www.backblaze.com/cloud-storage?utm_source=github&utm_medium=referral&utm_campaign=ai_artifacts&utm_content=genblaze) is the recommended default sink for genblaze — purpose-built for large AI-generated media with S3-compatible APIs, resilient multipart uploads, Object Lock for immutable manifests, and strong cost economics at scale. One-liner credentials from `B2_KEY_ID` / `B2_APP_KEY`. See the [`genblaze-s3`](https://pypi.org/project/genblaze-s3/) backend for the full recipe plus AWS S3, Cloudflare R2, and MinIO variants.
|
|
151
|
+
|
|
152
|
+
## Documentation
|
|
153
|
+
|
|
154
|
+
- **Main repo**: https://github.com/backblaze-labs/genblaze
|
|
155
|
+
- **Architecture**: https://github.com/backblaze-labs/genblaze/blob/main/ARCHITECTURE.md
|
|
156
|
+
- **Feature docs**: https://github.com/backblaze-labs/genblaze/tree/main/docs/features
|
|
157
|
+
- **Runnable examples**: https://github.com/backblaze-labs/genblaze/tree/main/examples
|
|
158
|
+
|
|
159
|
+
## Related packages
|
|
160
|
+
|
|
161
|
+
Provider adapters: [`genblaze-openai`](https://pypi.org/project/genblaze-openai/) · [`genblaze-google`](https://pypi.org/project/genblaze-google/) · [`genblaze-runway`](https://pypi.org/project/genblaze-runway/) · [`genblaze-luma`](https://pypi.org/project/genblaze-luma/) · [`genblaze-decart`](https://pypi.org/project/genblaze-decart/) · [`genblaze-replicate`](https://pypi.org/project/genblaze-replicate/) · [`genblaze-elevenlabs`](https://pypi.org/project/genblaze-elevenlabs/) · [`genblaze-stability-audio`](https://pypi.org/project/genblaze-stability-audio/) · [`genblaze-lmnt`](https://pypi.org/project/genblaze-lmnt/) · [`genblaze-gmicloud`](https://pypi.org/project/genblaze-gmicloud/)
|
|
162
|
+
|
|
163
|
+
Storage + tooling: [`genblaze-s3`](https://pypi.org/project/genblaze-s3/) · [`genblaze-cli`](https://pypi.org/project/genblaze-cli/) · [`genblaze-langsmith`](https://pypi.org/project/genblaze-langsmith/)
|
|
164
|
+
|
|
165
|
+
## License
|
|
166
|
+
|
|
167
|
+
MIT
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
<!-- last_verified: 2026-04-22 -->
|
|
2
|
+
# genblaze-core
|
|
3
|
+
|
|
4
|
+
**Python SDK for building generative AI pipelines across video, image, and audio — with built-in SHA-256 provenance.**
|
|
5
|
+
|
|
6
|
+
`genblaze-core` is the core of [genblaze](https://github.com/backblaze-labs/genblaze), an open-source orchestration framework by [Backblaze](https://www.backblaze.com/cloud-storage?utm_source=github&utm_medium=referral&utm_campaign=ai_artifacts&utm_content=genblaze) for composing multi-step AI media generation workflows. It gives you a single, provider-agnostic `Pipeline` API for text-to-video, text-to-image, text-to-speech, image-to-video, and audio generation — so you can swap models (Sora, Veo, Runway, Luma, Flux, DALL·E, ElevenLabs, Stable Audio, LMNT, GMICloud) without rewriting pipeline logic.
|
|
7
|
+
|
|
8
|
+
Every pipeline run emits a canonical, hash-verified **provenance manifest** — a tamper-evident JSON document capturing the provider, model, prompt, parameters, timestamps, and SHA-256 hash of every generated asset. Manifests can be embedded directly into PNG, JPEG, WebP, MP4, MP3, and WAV files, uploaded alongside assets to S3-compatible storage, or exported to Parquet for analytics.
|
|
9
|
+
|
|
10
|
+
## Why genblaze-core
|
|
11
|
+
|
|
12
|
+
- **One API for every generative AI provider** — Pipelines, not per-vendor SDK glue. Fluent, composable, chainable.
|
|
13
|
+
- **Built-in provenance** — Every asset gets a SHA-256–verified manifest. Prove how media was made; detect tampering.
|
|
14
|
+
- **Production-ready** — Retries, timeouts, progress streaming, moderation hooks, OpenTelemetry tracing, step caching.
|
|
15
|
+
- **Storage-agnostic sinks** — Drop into Backblaze B2, AWS S3, Cloudflare R2, MinIO, Parquet, or local disk.
|
|
16
|
+
- **Policy + privacy controls** — Redact prompts, strip params, pointer-mode for sensitive content.
|
|
17
|
+
- **Agent loops + templates** — Evaluator-driven iteration, reusable pipeline and step templates.
|
|
18
|
+
- **Zero lock-in** — MIT licensed, typed, lazy imports, provider adapters are separate packages.
|
|
19
|
+
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
| Capability | What you get |
|
|
23
|
+
|---|---|
|
|
24
|
+
| `Pipeline` API | Fluent multi-step generation, fan-in (`input_from`), AV compositing via FFmpeg |
|
|
25
|
+
| Provider discovery | Entry-point–based registry — `pip install genblaze-<provider>` and it's available |
|
|
26
|
+
| Manifest (Pydantic) | `Run`, `Step`, `Asset` models with canonical JSON hashing and `.verify()` |
|
|
27
|
+
| Media embedding | `PngHandler`, `Mp4Handler`, `Mp3Handler`, etc. — embed + extract manifests in-file |
|
|
28
|
+
| Storage sink | `ObjectStorageSink` with hierarchical or content-addressable key layout |
|
|
29
|
+
| Parquet sink | Partitioned run/step/asset tables for downstream analytics |
|
|
30
|
+
| Observability | `OTelTracer`, `LoggingTracer`, `CompositeTracer`, structured events |
|
|
31
|
+
| Agents | `AgentLoop` with pluggable `Evaluator` for iterative refinement |
|
|
32
|
+
| Moderation | Pre/post moderation hooks, configurable embed policies |
|
|
33
|
+
| Testing | `MockProvider`, `MockVideoProvider`, `MockAudioProvider` for offline tests |
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install genblaze-core
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Optional extras:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install "genblaze-core[parquet]" # ParquetSink for analytics
|
|
45
|
+
pip install "genblaze-core[audio]" # Audio metadata embedding (mutagen)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Add provider adapters separately:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install genblaze-openai genblaze-google genblaze-runway genblaze-luma \
|
|
52
|
+
genblaze-decart genblaze-replicate genblaze-elevenlabs \
|
|
53
|
+
genblaze-stability-audio genblaze-lmnt genblaze-gmicloud
|
|
54
|
+
|
|
55
|
+
pip install genblaze-s3 # Storage backend for Backblaze B2 / AWS S3 / R2 / MinIO
|
|
56
|
+
pip install genblaze-cli # Extract / verify / replay / index manifests
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Quickstart — local, zero API keys
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from genblaze_core import Modality, Pipeline
|
|
63
|
+
from genblaze_core.testing import MockVideoProvider
|
|
64
|
+
|
|
65
|
+
run, manifest = (
|
|
66
|
+
Pipeline("hello-genblaze")
|
|
67
|
+
.step(MockVideoProvider(), model="mock-v1",
|
|
68
|
+
prompt="A drone shot over a coastal city at golden hour",
|
|
69
|
+
modality=Modality.VIDEO)
|
|
70
|
+
.run()
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
print(manifest.canonical_hash) # deterministic SHA-256 of the run
|
|
74
|
+
print(manifest.verify()) # True
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Quickstart — Sora + Backblaze B2 storage
|
|
78
|
+
|
|
79
|
+
Generate a video, upload it + its manifest to [Backblaze B2](https://www.backblaze.com/cloud-storage?utm_source=github&utm_medium=referral&utm_campaign=ai_artifacts&utm_content=genblaze), verify the hash:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pip install genblaze-core genblaze-openai genblaze-s3
|
|
83
|
+
export OPENAI_API_KEY="sk-..."
|
|
84
|
+
export B2_KEY_ID="..."
|
|
85
|
+
export B2_APP_KEY="..."
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from genblaze_core import KeyStrategy, Modality, ObjectStorageSink, Pipeline
|
|
90
|
+
from genblaze_openai import SoraProvider
|
|
91
|
+
from genblaze_s3 import S3StorageBackend
|
|
92
|
+
|
|
93
|
+
storage = ObjectStorageSink(
|
|
94
|
+
S3StorageBackend.for_backblaze("my-bucket"),
|
|
95
|
+
key_strategy=KeyStrategy.HIERARCHICAL,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
result = (
|
|
99
|
+
Pipeline("hero-reel")
|
|
100
|
+
.step(SoraProvider(), model="sora-2",
|
|
101
|
+
prompt="Aerial flyover of a mountain lake at sunrise",
|
|
102
|
+
modality=Modality.VIDEO, seconds=4, size="1280x720")
|
|
103
|
+
.run(sink=storage, timeout=300)
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
print(result.run.steps[0].assets[0].url) # durable B2 URL
|
|
107
|
+
print(result.manifest.canonical_hash) # SHA-256 of the full run
|
|
108
|
+
assert result.manifest.verify()
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Storage — Backblaze B2 recommended
|
|
112
|
+
|
|
113
|
+
[Backblaze B2](https://www.backblaze.com/cloud-storage?utm_source=github&utm_medium=referral&utm_campaign=ai_artifacts&utm_content=genblaze) is the recommended default sink for genblaze — purpose-built for large AI-generated media with S3-compatible APIs, resilient multipart uploads, Object Lock for immutable manifests, and strong cost economics at scale. One-liner credentials from `B2_KEY_ID` / `B2_APP_KEY`. See the [`genblaze-s3`](https://pypi.org/project/genblaze-s3/) backend for the full recipe plus AWS S3, Cloudflare R2, and MinIO variants.
|
|
114
|
+
|
|
115
|
+
## Documentation
|
|
116
|
+
|
|
117
|
+
- **Main repo**: https://github.com/backblaze-labs/genblaze
|
|
118
|
+
- **Architecture**: https://github.com/backblaze-labs/genblaze/blob/main/ARCHITECTURE.md
|
|
119
|
+
- **Feature docs**: https://github.com/backblaze-labs/genblaze/tree/main/docs/features
|
|
120
|
+
- **Runnable examples**: https://github.com/backblaze-labs/genblaze/tree/main/examples
|
|
121
|
+
|
|
122
|
+
## Related packages
|
|
123
|
+
|
|
124
|
+
Provider adapters: [`genblaze-openai`](https://pypi.org/project/genblaze-openai/) · [`genblaze-google`](https://pypi.org/project/genblaze-google/) · [`genblaze-runway`](https://pypi.org/project/genblaze-runway/) · [`genblaze-luma`](https://pypi.org/project/genblaze-luma/) · [`genblaze-decart`](https://pypi.org/project/genblaze-decart/) · [`genblaze-replicate`](https://pypi.org/project/genblaze-replicate/) · [`genblaze-elevenlabs`](https://pypi.org/project/genblaze-elevenlabs/) · [`genblaze-stability-audio`](https://pypi.org/project/genblaze-stability-audio/) · [`genblaze-lmnt`](https://pypi.org/project/genblaze-lmnt/) · [`genblaze-gmicloud`](https://pypi.org/project/genblaze-gmicloud/)
|
|
125
|
+
|
|
126
|
+
Storage + tooling: [`genblaze-s3`](https://pypi.org/project/genblaze-s3/) · [`genblaze-cli`](https://pypi.org/project/genblaze-cli/) · [`genblaze-langsmith`](https://pypi.org/project/genblaze-langsmith/)
|
|
127
|
+
|
|
128
|
+
## License
|
|
129
|
+
|
|
130
|
+
MIT
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""genblaze-core — orchestration framework for media generation."""
|
|
2
|
+
|
|
3
|
+
from genblaze_core._version import __version__
|
|
4
|
+
|
|
5
|
+
_LAZY_IMPORTS: dict[str, tuple[str, str]] = {
|
|
6
|
+
# pipeline (primary entry point)
|
|
7
|
+
"Pipeline": ("genblaze_core.pipeline.pipeline", "Pipeline"),
|
|
8
|
+
"PipelineResult": ("genblaze_core.pipeline.result", "PipelineResult"),
|
|
9
|
+
"StepCompleteEvent": ("genblaze_core.pipeline.result", "StepCompleteEvent"),
|
|
10
|
+
# pipeline cache
|
|
11
|
+
"StepCache": ("genblaze_core.pipeline.cache", "StepCache"),
|
|
12
|
+
# pipeline templates
|
|
13
|
+
"PipelineTemplate": ("genblaze_core.pipeline.template", "PipelineTemplate"),
|
|
14
|
+
"StepTemplate": ("genblaze_core.pipeline.template", "StepTemplate"),
|
|
15
|
+
# moderation
|
|
16
|
+
"ModerationHook": ("genblaze_core.pipeline.moderation", "ModerationHook"),
|
|
17
|
+
"ModerationResult": ("genblaze_core.pipeline.moderation", "ModerationResult"),
|
|
18
|
+
# builders
|
|
19
|
+
"RunBuilder": ("genblaze_core.builders.run_builder", "RunBuilder"),
|
|
20
|
+
"StepBuilder": ("genblaze_core.builders.step_builder", "StepBuilder"),
|
|
21
|
+
# providers
|
|
22
|
+
"BaseProvider": ("genblaze_core.providers.base", "BaseProvider"),
|
|
23
|
+
"ProviderCapabilities": ("genblaze_core.providers.base", "ProviderCapabilities"),
|
|
24
|
+
"SubmitResult": ("genblaze_core.providers.base", "SubmitResult"),
|
|
25
|
+
"SyncProvider": ("genblaze_core.providers.base", "SyncProvider"),
|
|
26
|
+
"validate_asset_url": ("genblaze_core.providers.base", "validate_asset_url"),
|
|
27
|
+
"validate_chain_input_url": ("genblaze_core.providers.base", "validate_chain_input_url"),
|
|
28
|
+
"FFmpegCompositor": ("genblaze_core.providers.compositor", "FFmpegCompositor"),
|
|
29
|
+
"FFmpegTransform": ("genblaze_core.providers.transform", "FFmpegTransform"),
|
|
30
|
+
"ProgressEvent": ("genblaze_core.providers.progress", "ProgressEvent"),
|
|
31
|
+
# observability
|
|
32
|
+
"StreamEvent": ("genblaze_core.observability.events", "StreamEvent"),
|
|
33
|
+
"Tracer": ("genblaze_core.observability.tracer", "Tracer"),
|
|
34
|
+
"NoOpTracer": ("genblaze_core.observability.tracer", "NoOpTracer"),
|
|
35
|
+
"LoggingTracer": ("genblaze_core.observability.tracer", "LoggingTracer"),
|
|
36
|
+
"OTelTracer": ("genblaze_core.observability.tracer", "OTelTracer"),
|
|
37
|
+
"CompositeTracer": ("genblaze_core.observability.tracer", "CompositeTracer"),
|
|
38
|
+
"StructuredLogger": ("genblaze_core.observability.logger", "StructuredLogger"),
|
|
39
|
+
# agents
|
|
40
|
+
"AgentLoop": ("genblaze_core.agents.loop", "AgentLoop"),
|
|
41
|
+
"AgentContext": ("genblaze_core.agents.loop", "AgentContext"),
|
|
42
|
+
"AgentIteration": ("genblaze_core.agents.loop", "AgentIteration"),
|
|
43
|
+
"AgentResult": ("genblaze_core.agents.loop", "AgentResult"),
|
|
44
|
+
"Evaluator": ("genblaze_core.agents.evaluator", "Evaluator"),
|
|
45
|
+
"EvaluationResult": ("genblaze_core.agents.evaluator", "EvaluationResult"),
|
|
46
|
+
"CallableEvaluator": ("genblaze_core.agents.evaluator", "CallableEvaluator"),
|
|
47
|
+
"ThresholdEvaluator": ("genblaze_core.agents.evaluator", "ThresholdEvaluator"),
|
|
48
|
+
# models
|
|
49
|
+
"Manifest": ("genblaze_core.models.manifest", "Manifest"),
|
|
50
|
+
"Run": ("genblaze_core.models.run", "Run"),
|
|
51
|
+
"Step": ("genblaze_core.models.step", "Step"),
|
|
52
|
+
"Asset": ("genblaze_core.models.asset", "Asset"),
|
|
53
|
+
"AudioMetadata": ("genblaze_core.models.asset", "AudioMetadata"),
|
|
54
|
+
"Track": ("genblaze_core.models.asset", "Track"),
|
|
55
|
+
"VideoMetadata": ("genblaze_core.models.asset", "VideoMetadata"),
|
|
56
|
+
"WordTiming": ("genblaze_core.models.asset", "WordTiming"),
|
|
57
|
+
# enums
|
|
58
|
+
"Modality": ("genblaze_core.models.enums", "Modality"),
|
|
59
|
+
"StepType": ("genblaze_core.models.enums", "StepType"),
|
|
60
|
+
"RunStatus": ("genblaze_core.models.enums", "RunStatus"),
|
|
61
|
+
"StepStatus": ("genblaze_core.models.enums", "StepStatus"),
|
|
62
|
+
"PromptVisibility": ("genblaze_core.models.enums", "PromptVisibility"),
|
|
63
|
+
"ProviderErrorCode": ("genblaze_core.models.enums", "ProviderErrorCode"),
|
|
64
|
+
"EmbedPolicy": ("genblaze_core.models.policy", "EmbedPolicy"),
|
|
65
|
+
"PromptTemplate": ("genblaze_core.models.prompt_template", "PromptTemplate"),
|
|
66
|
+
# provider discovery
|
|
67
|
+
"discover_providers": ("genblaze_core.providers.registry", "discover_providers"),
|
|
68
|
+
# sinks
|
|
69
|
+
"BaseSink": ("genblaze_core.sinks.base", "BaseSink"),
|
|
70
|
+
"ParquetSink": ("genblaze_core.sinks.parquet", "ParquetSink"),
|
|
71
|
+
# storage
|
|
72
|
+
"StorageBackend": ("genblaze_core.storage.base", "StorageBackend"),
|
|
73
|
+
"ObjectStorageSink": ("genblaze_core.storage.sink", "ObjectStorageSink"),
|
|
74
|
+
"AssetTransfer": ("genblaze_core.storage.transfer", "AssetTransfer"),
|
|
75
|
+
"KeyStrategy": ("genblaze_core.storage.base", "KeyStrategy"),
|
|
76
|
+
"ObjectLockConfig": ("genblaze_core.storage.base", "ObjectLockConfig"),
|
|
77
|
+
# testing
|
|
78
|
+
"MockProvider": ("genblaze_core.testing", "MockProvider"),
|
|
79
|
+
"MockVideoProvider": ("genblaze_core.testing", "MockVideoProvider"),
|
|
80
|
+
"MockAudioProvider": ("genblaze_core.testing", "MockAudioProvider"),
|
|
81
|
+
"ProviderComplianceTests": ("genblaze_core.testing", "ProviderComplianceTests"),
|
|
82
|
+
# exceptions
|
|
83
|
+
"GenblazeError": ("genblaze_core.exceptions", "GenblazeError"),
|
|
84
|
+
"PipelineTimeoutError": ("genblaze_core.exceptions", "PipelineTimeoutError"),
|
|
85
|
+
"EmbeddingError": ("genblaze_core.exceptions", "EmbeddingError"),
|
|
86
|
+
"StorageError": ("genblaze_core.exceptions", "StorageError"),
|
|
87
|
+
"ProviderError": ("genblaze_core.exceptions", "ProviderError"),
|
|
88
|
+
"ManifestError": ("genblaze_core.exceptions", "ManifestError"),
|
|
89
|
+
"SinkError": ("genblaze_core.exceptions", "SinkError"),
|
|
90
|
+
"WebhookError": ("genblaze_core.exceptions", "WebhookError"),
|
|
91
|
+
# webhooks
|
|
92
|
+
"WebhookNotifier": ("genblaze_core.webhooks.notifier", "WebhookNotifier"),
|
|
93
|
+
"WebhookConfig": ("genblaze_core.webhooks.notifier", "WebhookConfig"),
|
|
94
|
+
"WebhookEvent": ("genblaze_core.webhooks.notifier", "WebhookEvent"),
|
|
95
|
+
"WebhookSink": ("genblaze_core.webhooks.sink", "WebhookSink"),
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
__all__ = [*_LAZY_IMPORTS.keys(), "__version__"]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def __getattr__(name: str):
|
|
102
|
+
if name in _LAZY_IMPORTS:
|
|
103
|
+
module_path, attr = _LAZY_IMPORTS[name]
|
|
104
|
+
import importlib
|
|
105
|
+
|
|
106
|
+
mod = importlib.import_module(module_path)
|
|
107
|
+
val = getattr(mod, attr)
|
|
108
|
+
globals()[name] = val
|
|
109
|
+
return val
|
|
110
|
+
raise AttributeError(f"module 'genblaze_core' has no attribute {name!r}")
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""Internal utilities."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import hashlib
|
|
7
|
+
import ipaddress
|
|
8
|
+
import random
|
|
9
|
+
import re
|
|
10
|
+
import socket
|
|
11
|
+
import tempfile
|
|
12
|
+
import uuid
|
|
13
|
+
from collections.abc import Coroutine
|
|
14
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
15
|
+
from datetime import UTC, datetime
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any
|
|
18
|
+
from urllib.parse import urlparse as _urlparse
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def new_id() -> str:
|
|
22
|
+
"""Generate a new UUID4 string."""
|
|
23
|
+
return str(uuid.uuid4())
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def utc_now() -> datetime:
|
|
27
|
+
"""Return current UTC datetime."""
|
|
28
|
+
return datetime.now(UTC)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def compute_sha256(data: bytes) -> str:
|
|
32
|
+
"""Compute SHA-256 hex digest of raw bytes."""
|
|
33
|
+
return hashlib.sha256(data).hexdigest()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _run_async(coro: Coroutine) -> Any:
|
|
37
|
+
"""Run an async coroutine from sync code safely.
|
|
38
|
+
|
|
39
|
+
If there's already a running event loop (e.g. inside Jupyter or an async provider
|
|
40
|
+
called from BaseProvider.invoke), runs in a new thread to avoid RuntimeError.
|
|
41
|
+
Otherwise uses asyncio.run().
|
|
42
|
+
"""
|
|
43
|
+
try:
|
|
44
|
+
loop = asyncio.get_running_loop()
|
|
45
|
+
except RuntimeError:
|
|
46
|
+
loop = None
|
|
47
|
+
|
|
48
|
+
if loop is not None and loop.is_running():
|
|
49
|
+
# Already inside an event loop — run in a separate thread
|
|
50
|
+
with ThreadPoolExecutor(max_workers=1) as pool:
|
|
51
|
+
return pool.submit(asyncio.run, coro).result()
|
|
52
|
+
return asyncio.run(coro)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# ---------------------------------------------------------------------------
|
|
56
|
+
# SSRF protection — shared by storage/transfer.py and webhooks/notifier.py
|
|
57
|
+
# ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
# Private/reserved IP ranges blocked to prevent SSRF
|
|
60
|
+
BLOCKED_NETWORKS = [
|
|
61
|
+
ipaddress.ip_network("0.0.0.0/8"), # "This host" — resolves to loopback on Linux
|
|
62
|
+
ipaddress.ip_network("10.0.0.0/8"),
|
|
63
|
+
ipaddress.ip_network("172.16.0.0/12"),
|
|
64
|
+
ipaddress.ip_network("192.168.0.0/16"),
|
|
65
|
+
ipaddress.ip_network("127.0.0.0/8"),
|
|
66
|
+
ipaddress.ip_network("169.254.0.0/16"), # Link-local / IMDS
|
|
67
|
+
ipaddress.ip_network("100.64.0.0/10"), # Carrier-grade NAT
|
|
68
|
+
ipaddress.ip_network("::1/128"), # IPv6 loopback
|
|
69
|
+
ipaddress.ip_network("fc00::/7"), # IPv6 unique local
|
|
70
|
+
ipaddress.ip_network("fe80::/10"), # IPv6 link-local
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# Allowed parent directories for file:// inputs (shared by storage + ffmpeg providers)
|
|
75
|
+
# Deduplicate: /tmp and gettempdir() may resolve to the same or different paths
|
|
76
|
+
ALLOWED_FILE_ROOTS: tuple[Path, ...] = tuple(
|
|
77
|
+
{Path(tempfile.gettempdir()).resolve(), Path("/tmp").resolve()} # noqa: S108
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def check_ssrf(url: str, *, exc_type: type[Exception] = ValueError) -> None:
|
|
82
|
+
"""Reject non-HTTPS URLs and hostnames resolving to private IP ranges.
|
|
83
|
+
|
|
84
|
+
Shared SSRF guard used by storage transfers and webhook dispatch.
|
|
85
|
+
Callers pass their domain-specific exception type via ``exc_type``.
|
|
86
|
+
"""
|
|
87
|
+
parsed = _urlparse(url)
|
|
88
|
+
if parsed.scheme not in ("https",):
|
|
89
|
+
raise exc_type(f"Only HTTPS URLs are allowed, got: {parsed.scheme}://")
|
|
90
|
+
|
|
91
|
+
host = parsed.hostname or ""
|
|
92
|
+
if host.lower() == "localhost":
|
|
93
|
+
raise exc_type(f"Private/loopback URLs are not allowed: {host}")
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
addrinfos = socket.getaddrinfo(host, None, proto=socket.IPPROTO_TCP)
|
|
97
|
+
except socket.gaierror as exc:
|
|
98
|
+
raise exc_type(f"Cannot resolve hostname: {host}") from exc
|
|
99
|
+
|
|
100
|
+
for _, _, _, _, sockaddr in addrinfos:
|
|
101
|
+
try:
|
|
102
|
+
ip = ipaddress.ip_address(str(sockaddr[0]))
|
|
103
|
+
except ValueError:
|
|
104
|
+
continue
|
|
105
|
+
if any(ip in net for net in BLOCKED_NETWORKS):
|
|
106
|
+
raise exc_type(f"Private/loopback URLs are not allowed: {host}")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# ---------------------------------------------------------------------------
|
|
110
|
+
# Manifest size cap — bounds the JSON payload accepted from disk/media
|
|
111
|
+
# ---------------------------------------------------------------------------
|
|
112
|
+
# Real manifests are O(KB). 16 MiB is generous and bounds OOM blast from
|
|
113
|
+
# malicious media or sidecars that declare absurd payload sizes.
|
|
114
|
+
MAX_MANIFEST_BYTES = 16 * 1024 * 1024
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# ---------------------------------------------------------------------------
|
|
118
|
+
# Credential pattern detection — used by error sanitization (providers/base.py)
|
|
119
|
+
# AND by Pipeline.step build-time rejection of secret-shaped params values.
|
|
120
|
+
# Centralized here so both call sites share one regex of record.
|
|
121
|
+
# ---------------------------------------------------------------------------
|
|
122
|
+
_SECRET_PATTERNS = re.compile(
|
|
123
|
+
r"(r8_[A-Za-z0-9]{20,})" # Replicate tokens
|
|
124
|
+
r"|(sk-ant-[A-Za-z0-9\-]{20,})" # Anthropic API keys (before generic sk-)
|
|
125
|
+
r"|(sk-[A-Za-z0-9]{20,})" # OpenAI-style keys
|
|
126
|
+
r"|(AIza[A-Za-z0-9_\-]{30,})" # Google API keys
|
|
127
|
+
r"|(AKIA[A-Z0-9]{16})" # AWS access key IDs
|
|
128
|
+
r"|(Bearer\s+[A-Za-z0-9._\-]{20,})" # Bearer tokens
|
|
129
|
+
r"|(Token\s+[A-Za-z0-9._\-]{20,})" # Token auth headers
|
|
130
|
+
r"|(\bapi[_-]key[=:]\s*[A-Za-z0-9._\-]{20,})", # api_key=... / api-key:...
|
|
131
|
+
re.IGNORECASE,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def jittered_backoff(attempt: int) -> float:
|
|
136
|
+
"""Compute exponential backoff with jitter to avoid thundering herd.
|
|
137
|
+
|
|
138
|
+
Starts at 1s, doubles per attempt, capped at 30s base with up to 25% jitter.
|
|
139
|
+
"""
|
|
140
|
+
base = min(2**attempt, 30)
|
|
141
|
+
return base * (1 + random.uniform(0, 0.25)) # noqa: S311 — jitter, not crypto
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def probe_audio_duration(path: str | Any) -> float | None:
|
|
145
|
+
"""Try to read audio duration from a file using mutagen (optional dep).
|
|
146
|
+
|
|
147
|
+
Returns duration in seconds, or None if mutagen is not installed or
|
|
148
|
+
the file format is not recognized.
|
|
149
|
+
"""
|
|
150
|
+
try:
|
|
151
|
+
from mutagen import File as MutagenFile
|
|
152
|
+
|
|
153
|
+
audio = MutagenFile(str(path))
|
|
154
|
+
if audio is not None and audio.info is not None:
|
|
155
|
+
return audio.info.length
|
|
156
|
+
except Exception: # noqa: S110 — mutagen is optional, fail gracefully
|
|
157
|
+
pass
|
|
158
|
+
return None
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Agent / reasoning layer — evaluate outputs, refine prompts, retry."""
|
|
2
|
+
|
|
3
|
+
from genblaze_core.agents.evaluator import (
|
|
4
|
+
CallableEvaluator,
|
|
5
|
+
EvaluationResult,
|
|
6
|
+
Evaluator,
|
|
7
|
+
ThresholdEvaluator,
|
|
8
|
+
)
|
|
9
|
+
from genblaze_core.agents.loop import AgentContext, AgentIteration, AgentLoop, AgentResult
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"AgentContext",
|
|
13
|
+
"AgentIteration",
|
|
14
|
+
"AgentLoop",
|
|
15
|
+
"AgentResult",
|
|
16
|
+
"CallableEvaluator",
|
|
17
|
+
"EvaluationResult",
|
|
18
|
+
"Evaluator",
|
|
19
|
+
"ThresholdEvaluator",
|
|
20
|
+
]
|