diff --git a/.dockerignore b/.dockerignore index 4aab96452..b17fd900d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,3 +2,7 @@ node_modules .changeset .git .github + +__pycache__ +.pytest_cache +.venv diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index da1cfab74..11adaa2ed 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,19 +1,26 @@ -name: Tests - -on: - workflow_dispatch: - push: - branches-ignore: [main] - pull_request: - types: [opened, synchronize, reopened, ready_for_review] - -jobs: - test: - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Placeholder test - run: echo "Tests temporarily disabled - placeholder to satisfy required checks" +name: Tests + +on: + workflow_dispatch: + push: + branches-ignore: [main] + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Install test dependencies + run: python -m pip install -r requirements.txt pytest jsonschema + - name: Worker contracts and lifecycle + run: python -m pytest tests -q + - name: Startup syntax + run: bash -n src/start.sh diff --git a/Dockerfile b/Dockerfile index 1dd71a953..765dc27ed 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ ARG BASE_IMAGE=nvidia/cuda:12.8.1-cudnn-runtime-ubuntu24.04 FROM ${BASE_IMAGE} AS base # Build arguments for this stage with sensible defaults for standalone builds -ARG COMFYUI_VERSION=0.34.0 +ARG COMFYUI_VERSION=0.36.0 ARG CUDA_VERSION_FOR_COMFY=12.8 ARG ENABLE_PYTORCH_UPGRADE=false ARG PYTORCH_INDEX_URL @@ -106,14 +106,23 @@ WORKDIR /comfyui # Support for the network volume ADD src/extra_model_paths.yaml ./ +# Image identity, reported by the handler as trace.image (senai-worker/1 §5/§8). +ARG SENAI_IMAGE_REF="" +ENV SENAI_IMAGE_REF="${SENAI_IMAGE_REF}" +RUN echo "${COMFYUI_VERSION}" > /etc/senai-comfyui-version + # Go back to the root WORKDIR / # Install Python runtime dependencies for the handler -RUN uv pip install runpod requests websocket-client +COPY requirements.txt /requirements.txt +RUN uv pip install -r /requirements.txt # Add application code and scripts -ADD src/start.sh src/network_volume.py handler.py test_input.json ./ +ADD src/start.sh src/network_volume.py src/workflow_models.py src/media_output.py \ + src/senai_worker.py src/senai_errors.py src/comfy_client.py src/guard.py \ + handler.py test_input.json ./ +COPY workflow/ /workflow/ RUN chmod +x /start.sh # Add script to install custom nodes @@ -130,6 +139,13 @@ RUN chmod +x /usr/local/bin/comfy-manager-set-mode # Set the default command to run when starting the container CMD ["/start.sh"] +# Optional custom-node dependencies, chosen by manifest at build time. +# No workflow, model family or weights are selected by default. +ARG CUSTOM_NODE_MANIFESTS="" +COPY scripts/install-workflow-nodes.py /install-workflow-nodes.py +RUN python /install-workflow-nodes.py --manifests "$CUSTOM_NODE_MANIFESTS" + +# Legacy opt-in targets used by existing release jobs; not built by default. # Stage 2: Download models FROM base AS downloader @@ -180,7 +196,10 @@ RUN if [ "$MODEL_TYPE" = "z-image-turbo" ]; then \ fi # Stage 3: Final image -FROM base AS final +FROM base AS baked-models # Copy models from stage 2 to the final image -COPY --from=downloader /comfyui/models /comfyui/models \ No newline at end of file +COPY --from=downloader /comfyui/models /comfyui/models + +# Default: generic worker with no model weights. +FROM base AS final diff --git a/README.md b/README.md index b4da1825c..93c7c7700 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,8 @@ This project allows you to run ComfyUI workflows as a serverless API endpoint on ## Table of Contents -- [Quickstart](#quickstart) +- [Alur worker generik: dari workflow sampai endpoint](#alur-worker-generik-dari-workflow-sampai-endpoint) +- [Quickstart upstream](#quickstart-upstream) - [Available Docker Images](#available-docker-images) - [API Specification](#api-specification) - [Usage](#usage) @@ -23,7 +24,258 @@ This project allows you to run ComfyUI workflows as a serverless API endpoint on --- -## Quickstart +## Alur worker generik: dari workflow sampai endpoint + +Fork ini melayani permintaan Senai lewat protokol **`senai-worker/1`** +(`input.protocol == "senai-worker/1"`), bukan lagi lewat env `OUTPUT_FORMAT=senai` +yang sudah dicabut. Kontrak kabel lengkap ada di `contract/senai-worker-1/` +(salinan dari `contracts/runpod-senai-worker/` repo senai) dan detail alur +internal worker ada di [`docs/senai-worker-internals.md`](docs/senai-worker-internals.md). +Bagian di bawah ini menjelaskan bagian yang masih sama untuk kedua protokol +(pemilihan workflow/model), lalu [§6](#6-siapkan-image-dan-konfigurasi-endpoint) +menjelaskan env spesifik protokol senai. + +Alur yang digunakan repo ini: + +**Pilih workflow → tes lokal → build image → checklist model → isi cache sekali → jalankan endpoint `cache-only` → hasil dikirim → worker dihentikan.** + +Image default berisi runtime ComfyUI dan handler, tanpa bobot model dan tanpa +workflow aktif. Menambahkan workflow tidak memerlukan target Docker baru. +Semua perintah berikut dijalankan dari direktori `worker-comfyui/`. + +### 1. Pilih workflow yang akan dilayani + +| Workflow | Nilai `WORKFLOWS` | Sumber daftar model | Dependency tambahan saat build | +| --- | --- | --- | --- | +| MiniMax H3 R2V | `minimax-h3.yaml` | Metadata di `video_minimax_h3_r2v.json`; 5 aset | Tidak ada custom node dalam graph yang disediakan | +| LTX 2.5 Senai | `ltx25.yaml` | Enam aset untuk graph T2V, I2V, dan FLF | `CUSTOM_NODE_MANIFESTS=ltx25.yaml` | + +Contoh berikut memakai MiniMax: + +```bash +export WORKFLOWS=minimax-h3.yaml +export WORKER_IMAGE=worker-comfyui:generic +``` + +`WORKFLOWS=video_minimax_h3_r2v.json` juga valid untuk membaca metadata model +langsung. File lain dalam folder `workflow/` tidak otomatis diunduh. +`WORKFLOW_MANIFESTS` tetap didukung sebagai alias lama; `WORKFLOWS` diprioritaskan. + +**Bedakan dua format:** JSON editor MiniMax dipakai untuk menemukan aset model. +Handler tetap menerima **JSON API** dalam `input.workflow`; export melalui +ComfyUI sebelum mengirim job. Pemilihan env menyiapkan aset, bukan otomatis +menjalankan graph atau mengubah format editor menjadi API. + +### 2. Jalankan tes lokal tanpa GPU dan tanpa download model + +```bash +docker build --platform linux/amd64 \ + -f tests/Dockerfile -t worker-comfyui-tests . +docker run --rm worker-comfyui-tests +``` + +Tes mencakup pemilihan aset, cache, download dengan data tiruan, respons handler, +SDK Runpod `stopPod`, serta cleanup proses ketika selesai, crash, dan SIGTERM. +Hasil terakhir: **49 tes lulus**. Layanan ComfyUI/GPU, download dan S3 dimock; +ini belum membuktikan inference atau penghentian worker di cloud. + +### 3. Build image runtime generik + +Untuk MiniMax: + +```bash +docker build --platform linux/amd64 -t "$WORKER_IMAGE" . +``` + +Untuk LTX, gunakan Dockerfile yang sama dengan dependency terpilih: + +```bash +export WORKFLOWS=ltx25.yaml +export WORKER_IMAGE=worker-comfyui:with-ltx-nodes +docker build --platform linux/amd64 \ + --build-arg CUSTOM_NODE_MANIFESTS=ltx25.yaml \ + -t "$WORKER_IMAGE" . +``` + +Build arg memasang custom node yang dipin dalam YAML, bukan bobot model atau +pilihan workflow runtime. Tidak ada target `--target ltx25`. Perubahan dependency +Python/custom node membutuhkan rebuild; perubahan model tidak. + +### 4. Periksa checklist model terlebih dahulu + +```bash +docker run --rm \ + -e WORKFLOWS="$WORKFLOWS" \ + -e MODEL_DOWNLOAD_CHECK_ONLY=true \ + "$WORKER_IMAGE" +``` + +Container mencetak aset terpilih lalu berhenti, tanpa GPU check, download, +ComfyUI, atau handler. MiniMax harus menampilkan lima aset MiniMax saja. + +**Untuk tahap validasi tanpa bobot nyata, berhenti di sini.** Langkah pengisian +cache berikut memang melakukan download; lanjutkan ketika siap mengunduh model. + +### 5. Isi cache sekali, terpisah dari worker yang melayani job + +Contoh cache lokal: + +```bash +mkdir -p model-cache +docker run --rm \ + -v "$PWD/model-cache:/runpod-volume" \ + -e WORKFLOWS="$WORKFLOWS" \ + -e PREPARE_MODELS_ONLY=true \ + -e MODEL_DOWNLOAD_POLICY=missing \ + -e MODEL_DOWNLOAD_CONCURRENCY=4 \ + "$WORKER_IMAGE" +``` + +Untuk model yang memerlukan autentikasi, isi `HF_TOKEN` di lingkungan shell dan +tambahkan `-e HF_TOKEN` pada perintah tersebut. Proses preparation tidak memerlukan +GPU dan berhenti setelah selesai. Bobot tersimpan di `model-cache/models/`. + +Download menggunakan file sementara, penggantian atomik, dan lock per model. +Receipt `.worker-cache.json` disimpan di samping bobot. Pertahankan receipt ketika +menggunakan kembali cache; `cache-only` memeriksa receipt, sumber, ukuran, dan +mtime tanpa membaca seluruh bobot untuk menghitung ulang hash. + +Untuk Runpod, isi **network volume yang sama** dengan yang akan dipasang pada +endpoint. Folder Docker lokal di atas bukan network volume Runpod. Jalankan image +preparation pada compute sementara yang memasang volume tersebut dan atur +`COMFY_MODEL_ROOT` ke lokasi aktualnya. Contoh: bila volume pada Pod terpasang di +`/workspace`, gunakan `/workspace/models`; isi volume itu nantinya terlihat sebagai +`/runpod-volume/models` pada serverless. Hentikan compute preparation setelah selesai. + +Jika cache dipindahkan dan mtime berubah, jalankan preparation lagi terhadap +lokasi tujuan sebelum serving. Untuk unduhan yang dapat direproduksi, pin revision +URL dan isi SHA256 dalam manifest; contoh awal masih memakai URL upstream `main`. + +### 6. Siapkan image dan konfigurasi endpoint + +Push image hasil build ke registry milik Anda: + +```bash +export REGISTRY_IMAGE=ghcr.io/your-account/worker-comfyui:generic-v1 +docker login ghcr.io +docker tag "$WORKER_IMAGE" "$REGISTRY_IMAGE" +docker push "$REGISTRY_IMAGE" +``` + +Ganti `your-account` dan tag sesuai registry tujuan. Di Runpod, buat template +serverless menggunakan image tersebut, lalu endpoint queue-based dengan: + +- Network volume yang sudah diisi pada langkah 5. +- GPU dan kapasitas disk yang sesuai dengan workflow. +- Active Workers **0**, Max Workers **1** untuk pengujian awal. +- Idle timeout **5 detik** dan satu job per worker pada satu waktu. + +Isi env endpoint (contoh MiniMax): + +```dotenv +WORKFLOWS=minimax-h3.yaml +MODEL_DOWNLOAD_POLICY=cache-only +COMFY_MODEL_ROOT=/runpod-volume/models +REFRESH_WORKER=true +``` + +Jangan aktifkan `PREPARE_MODELS_ONLY`, `MODEL_DOWNLOAD_CHECK_ONLY`, atau +`SERVE_API_LOCALLY` pada endpoint serving. Cache yang belum siap akan menghasilkan +error yang meminta preparation; worker tidak diam-diam mengunduh saat cold start. + +**Endpoint yang melayani Senai** memakai protokol `senai-worker/1`, bukan lagi +env `OUTPUT_FORMAT=senai` (dicabut). Tidak ada env pemilih protokol di sisi +worker — dispatch ditentukan oleh `input.protocol` pada tiap request; endpoint +hanya perlu env berikut selain env pemilihan workflow di atas: + +```dotenv +HF_CACHE_ROOT=/runpod-volume/huggingface-cache/hub # default, sesuaikan bila beda +AWS_BUCKET_NAME=your-output-bucket +AWS_ACCESS_KEY_ID=*** +AWS_SECRET_ACCESS_KEY=*** +AWS_DEFAULT_REGION=auto +AWS_ENDPOINT_URL=https://your-account.r2.cloudflarestorage.com +INPUT_ALLOWED_HOSTS=your-senai-asset-bucket.example.com +REFRESH_WORKER=dirty +``` + +Untuk deployment Senai, bobot **bukan** diisi lewat langkah 4–5 di atas (network +volume yang disiapkan manual): produksi memakai fitur **cached model Hugging +Face RunPod** — repo HF yang dipilih di template endpoint, dicache RunPod di +`HF_CACHE_ROOT/models----/snapshots//`. Boot +memverifikasi keberadaan tiap file dengan `stat` (ukuran cocok manifest), tanpa +menghitung ulang hash. Cache yang kurang tidak membuat worker crash-loop; +worker tetap boot dalam **mode unready** (`ready:false`, kode +`MODEL_CACHE_MISSING`/`OUTPUT_NOT_CONFIGURED`) dan menjawab tiap job dengan +kode itu, supaya operator melihat penyebabnya lewat `/health` alih-alih retry +tanpa akhir. Detail env lengkap, nilai produksi, dan penjelasan protokol ada di +[Configuration Guide](docs/configuration.md#senai-worker1-protocol). + +### 7. Kirim job API dan verifikasi sampai worker berhenti + +Contoh di bawah memakai payload upstream (`input.workflow` + `input.images`), +berlaku hanya ketika `LEGACY_UPSTREAM_INPUT=true` dan tidak dipakai di endpoint +Senai. Untuk endpoint Senai, kirim envelope `senai-worker/1` +(`input.protocol`, `input.workflow`, `input.inputs[]`, `input.trace`, +`input.limits`) seperti dicontohkan di +[Configuration Guide](docs/configuration.md#senai-worker1-protocol); jangan +mengirim JSON editor MiniMax langsung sebagai payload pada kedua jalur. + +Siapkan `request.json` dengan envelope `{"input":{"workflow": ...}}` berisi +graph API lengkap. Untuk R2V/I2V, tambahkan `input.images` berisi nama dan base64 +gambar referensi yang cocok dengan node `LoadImage`. + +Dengan `RUNPOD_API_KEY` dan `RUNPOD_ENDPOINT_ID` sudah diisi pada shell: + +```bash +curl --fail-with-body \ + -H "Authorization: Bearer $RUNPOD_API_KEY" \ + -H "Content-Type: application/json" \ + --data-binary @request.json \ + "https://api.runpod.ai/v2/$RUNPOD_ENDPOINT_ID/run" +``` + +Salin `id` dari respons, kemudian periksa status sampai terminal: + +```bash +export RUNPOD_JOB_ID=replace-with-returned-job-id +curl --fail-with-body \ + -H "Authorization: Bearer $RUNPOD_API_KEY" \ + "https://api.runpod.ai/v2/$RUNPOD_ENDPOINT_ID/status/$RUNPOD_JOB_ID" +``` + +Pastikan hasil video tersedia, URL storage tetap dapat dibaca setelah worker +berhenti, dan jumlah worker aktif/idle kembali nol saat antrean kosong. +`REFRESH_WORKER=true` meminta retirement melalui SDK setelah hasil dikembalikan; +Runpod yang melakukan penghentian cloud. `start.sh` membersihkan proses anak pada +exit/SIGTERM. Verifikasi ini tetap diperlukan di staging. + +Cache menghilangkan download dan hashing seluruh bobot saat cold start, tetapi +image pull, startup ComfyUI, dan pemuatan bobot ke GPU tetap membutuhkan waktu. +Build produksi GPU, download bobot nyata, dan latensi cloud belum diverifikasi +oleh tes CPU di atas. + +### 8. Tambahkan atau ganti workflow berikutnya + +1. Tambahkan JSON ke `workflow/`. Jika editor JSON memiliki `properties.models`, + buat YAML yang merujuk file itu, seperti [`minimax-h3.yaml`](workflow/minimax-h3.yaml). + Untuk graph tanpa URL model, tulis `models` secara eksplisit seperti + [`ltx25.yaml`](workflow/ltx25.yaml). +2. Pasang definisi workflow melalui volume dengan `WORKFLOW_DIR` yang sesuai, + atau rebuild image untuk menyertakan file baru di `/workflow`. +3. Jika ada custom node baru, deklarasikan repo dan commit dalam YAML, lalu build + dengan `CUSTOM_NODE_MANIFESTS` yang sesuai. Dependency tidak dipasang saat boot. +4. Jalankan checklist dan preparation untuk workflow baru pada cache yang sama. +5. Ubah `WORKFLOWS` pada endpoint dan tetap gunakan `cache-only`. Beberapa workflow + bisa dipilih dengan koma; hanya pilih yang memang akan dilayani. + +Detail schema dan cache ada di [Customization Guide](docs/customization.md#generic-workflow-selection-and-model-cache), +dan checklist staging ada di [Deployment Guide](docs/deployment.md#generic-worker-prepare-assets-separately-from-serving). + +## Quickstart upstream + +Bagian berikut menjelaskan image upstream yang sudah dipublikasikan. Untuk build +repo ini dan alur cache baru, gunakan panduan worker generik di atas. 1. 🐳 Choose one of the [available Docker images](#available-docker-images) for your serverless endpoint (e.g., `runpod/worker-comfyui:-sd3`). 2. 📄 Follow the [Deployment Guide](docs/deployment.md) to set up your RunPod template and endpoint. diff --git a/contract/senai-worker-1/CHANGELOG.md b/contract/senai-worker-1/CHANGELOG.md new file mode 100644 index 000000000..f6bc926f0 --- /dev/null +++ b/contract/senai-worker-1/CHANGELOG.md @@ -0,0 +1,40 @@ +# Changelog — kontrak runpod-senai-worker + +## 0.2.1 — 2026-09-23 + +Klarifikasi dari review implementasi, tanpa perubahan bentuk kabel: + +- §7: rujukan model hanya string tanpa whitespace, supaya prompt yang berakhiran `.pt`/`.bin` + tidak salah ditolak `MODEL_NOT_IN_MANIFEST`. +- §4.2: flag yang berbeda dari tabel §5 dicatat (log + `raw.flag_mismatch`), job tidak gagal. +- §6.1: output error dengan `protocol` salah dan sukses tanpa `trace` → `PROVIDER_CONTRACT_ERROR`. +- §4.3: `manifest_sha256` selalu diisi, juga saat unready. + +## 0.2.0 — 2026-09-23 + +Breaking (masih draft, belum ada deployment): + +- Objek error protokol pindah dari `output.error` ke **`output.failure`**. SDK RunPod + (`rp_job.run_job`) menjalankan `pop("error")` dan `pop("refresh_worker")` pada return + handler, sehingga `output.error` berubah menjadi job `FAILED` tanpa detail. Temuan executor + WP A1 dengan `run_job` SDK asli, diverifikasi koordinator di kode SDK 1.7.x dan 1.12.0. +- Schema output menolak kunci `error` dan `refresh_worker`, karena keduanya dicadangkan SDK. +- `pins.yaml`: `output.error_object_key`, `output.sdk_reserved_keys`. + +## 0.1.0 — 2026-09-23 + +Draft awal protokol `senai-worker/1`. Ditulis sebelum implementasi, sebagai spesifikasi +bersama untuk worker (`maulanashendi/worker-comfyui`, basis `094dbbe`) dan adapter senai. +Rancangannya ada di `docs/design/2026-09-23-runpod-comfyui-pipeline/`. + +- Envelope request: `protocol`, graph format API dari senai, `inputs[]` (URL R2 atau base64 + gambar kecil), `trace` yang wajib dan di-echo balik, dan `limits.deadline_at`. +- Envelope response: sukses dengan metadata konten (ffprobe) dan key R2 deterministik; error + bertipe di dalam `output`, bukan `error` top-level; health dengan status manifest. +- Taksonomi 22 kode error dengan flag `gpu_work`/`infra`/`retryable`/`refresh` (§5), yang juga + ada mesin-terbaca di `pins.yaml`. +- Set workflow dipilih `WORKFLOWS`; bobot dari cached model Hugging Face (K1); GPU minimum + 48 GB (K2). +- Graph LTX di-pin sama dengan `runpod-ltx25`. Graph H3 berstatus `pending` sampai diekspor + dari ComfyUI v0.36.0. +- Klaim yang belum terbukti di GPU tercatat di §10 dan menentukan kenaikan ke 1.0.0. diff --git a/contract/senai-worker-1/CONTRACT.md b/contract/senai-worker-1/CONTRACT.md new file mode 100644 index 000000000..baa1802a4 --- /dev/null +++ b/contract/senai-worker-1/CONTRACT.md @@ -0,0 +1,351 @@ +# Kontrak: RunPod senai-worker (protokol `senai-worker/1`) + +Status: **draft mengikat** (lihat urutan otoritas di `contracts/README.md`). +Versi: `0.2.1` — lihat `pins.yaml` dan `CHANGELOG.md`. + +Kontrak ini adalah spesifikasi yang dipakai **kedua sisi** untuk implementasi paralel: +worker (`maulanashendi/worker-comfyui`, fork `runpod-workers/worker-comfyui`) dan senai +(adapter RunPod di `backend/src/senai/modules/providers/runpod/`). Rancangan dan alasannya ada +di `docs/design/2026-09-23-runpod-comfyui-pipeline/01-rancangan.md`. Kalau dokumen itu dan +kontrak ini berbeda, kontrak ini yang menang, dan perbedaannya dilaporkan. + +Status draft berarti bentuknya sudah mengikat untuk implementasi, tetapi belum dibuktikan di +GPU. Kontrak naik ke `1.0.0` setelah gate G5 dan G7 lulus dengan angka nyata (§10). + +## 1. Ruang lingkup & kepemilikan + +- **Senai memegang graph.** Senai mengirim graph ComfyUI format API yang dibangun codec-nya. + Worker tidak menyimpan graph yang dieksekusi. Salinan graph di repo worker hanya dipakai + untuk menurunkan manifest model dan untuk test, dan harus identik dengan pin di §8. +- **Worker adalah eksekutor generik yang dijaga.** Worker memvalidasi envelope, allowlist + node, input, dan model, lalu menjalankan graph dengan batas waktu dan melaporkan hasil + beserta jejaknya. +- **Satu deployment = satu set workflow.** Set dipilih env `WORKFLOWS` (§7). Semua deployment + memakai image yang sama. +- Kontrak ini tidak menyentuh kontrak `runpod-ltx25` (vavo). Keduanya berlaku berdampingan + sampai cutover. + +## 2. Transport + +Transport RunPod sama dengan `contracts/runpod-ltx25/CONTRACT.md` §3: `/run`, `/status/{id}`, +`/cancel/{id}`, `/health`, bearer auth, batas request 10 MB, dan pemetaan error HTTP ke +taksonomi senai. Kontrak ini tidak menduplikasinya. Tambahan yang mengikat: + +- Senai **selalu** mengirim `policy.executionTimeout` (ms) dan `policy.ttl` (ms) di body `/run`. +- `/runsync` tidak dipakai. + +## 3. Request + +Satu-satunya bentuk yang diterima jalur produksi adalah `input.protocol == "senai-worker/1"`. +Request tanpa `protocol` ditolak dengan `UNSUPPORTED_PROTOCOL`. Pengecualiannya hanya bila env +`LEGACY_UPSTREAM_INPUT=true`, yang membuat request diteruskan ke handler upstream +`worker-comfyui` apa adanya. Env itu tidak pernah dipakai di endpoint senai. + +### 3.1 Job workflow + +```json +{ + "input": { + "protocol": "senai-worker/1", + "workflow": {"": {"class_type": "…", "inputs": {}}}, + "inputs": [ + {"name": "start.png", "data": "data:image/png;base64,…", "media_type": "image/png"}, + {"name": "ref_video_0.mp4", "url": "https://.r2.cloudflarestorage.com/…", "media_type": "video/mp4", "bytes": 4100000} + ], + "trace": { + "generation_id": "5b64b095-…", "attempt": 1, + "binding_alias": "mnx-h3", "binding_revision": 3, + "adapter": "minimax_h3_senai_worker_v1", "workflow_id": "minimax-h3-r2v-v1", + "graph_sha256": "…" + }, + "limits": {"deadline_at": "2026-09-23T10:00:00Z", "no_progress_sec": 120, "no_progress_load_sec": 600} + }, + "policy": {"executionTimeout": 1200000, "ttl": 1800000} +} +``` + +| Field | Wajib | Aturan | +| --- | --- | --- | +| `protocol` | Ya | Konstanta `"senai-worker/1"` | +| `workflow` | Ya | Graph format API: objek `node_id → {class_type, inputs}`. Format editor (punya `nodes`/`links`) ditolak `EDITOR_FORMAT`. | +| `inputs[]` | Tidak | Setiap entri punya `name` dan **tepat satu** dari `url` / `data`. | +| `inputs[].name` | Ya | Nama berkas yang sama persis dengan nilai yang dirujuk graph, misalnya `LoadImage.inputs.image`. Hanya `[A-Za-z0-9._-]`, tanpa path. | +| `inputs[].url` | Salah satu | `https`, host harus ada di `INPUT_ALLOWED_HOSTS` worker. Ukuran ≤ `INPUT_MAX_BYTES`. | +| `inputs[].data` | Salah satu | Base64 polos atau data URI. Hanya untuk `media_type` `image/*`, dengan ukuran hasil decode ≤ `INPUT_INLINE_MAX_BYTES`. | +| `inputs[].media_type` | Ya | Dicocokkan dengan sniff konten. Kalau tidak cocok → `INPUT_INVALID`. | +| `inputs[].bytes`, `sha256` | Tidak | Kalau ada, diverifikasi setelah fetch. | +| `trace` | Ya | Semua field wajib. Di-echo balik utuh (§4). | +| `limits.deadline_at` | Ya | RFC 3339 UTC. Deadline efektif = min(`deadline_at`, mulai job + `JOB_DEADLINE_CEILING_SEC`). | +| `limits.no_progress_sec` | Tidak | Default dari env `NO_PROGRESS_SEC`. | +| `limits.no_progress_load_sec` | Tidak | Jendela tanpa-progres saat node loader aktif. Default dari env `NO_PROGRESS_LOAD_SEC`. | + +Kunci lain di `input` ditolak (`INVALID_ENVELOPE`), termasuk `images`, `comfy_org_api_key`, +dan `api_key_comfy_org`. Env `COMFY_ORG_API_KEY` diabaikan di jalur protokol ini, sehingga +node API berbayar Comfy.org tidak pernah bisa dipakai. + +### 3.2 Probe kesehatan + +```json +{"input": {"protocol": "senai-worker/1", "health_check": true}} +``` + +Tidak ada kerja GPU. Worker menjawab dengan bentuk §4.3. + +## 4. Response (`output` dari `/status`) + +Handler **selalu** mengembalikan objek dengan `status`, dan objek itu menjadi `output`. Semua +kegagalan yang tertangkap dilaporkan sebagai `output.status == "error"` dengan objek detail +di **`output.failure`**, dengan outer `COMPLETED`. + +**Kunci `error` dan `refresh_worker` dicadangkan SDK RunPod dan tidak boleh dipakai untuk data +protokol.** `runpod.serverless.modules.rp_job.run_job` menjalankan `job_output.pop("error")` +dan `job_output.pop("refresh_worker")` pada dict yang dikembalikan handler. `error` dipindah ke +`error` top-level `run_result`, sehingga job menjadi `FAILED` dan detail terstruktur hilang +dari `output`. `refresh_worker` diubah menjadi `stopPod`. Ini diverifikasi di kode SDK (1.7.x +dan 1.12.0) dan dengan `run_job` SDK asli di test worker, 2026-09-23. Karena itu objek error +protokol bernama `failure`. + +Worker menambahkan `refresh_worker: true` di return handler untuk meminta RunPod memensiunkan +worker. SDK mencabutnya sebelum hasil dikirim, jadi field itu tidak pernah terlihat di +`/status`, dan senai tidak bergantung padanya. + +### 4.1 Sukses + +```json +{ + "status": "success", + "protocol": "senai-worker/1", + "outputs": [ + {"url": "https://.r2.cloudflarestorage.com//renders//1/00-MiniMax_H3_00001_.mp4", + "bucket": "", "key": "renders//1/00-MiniMax_H3_00001_.mp4", + "filename": "MiniMax_H3_00001_.mp4", "node_id": "92", + "media_type": "video/mp4", "bytes": 5123456, "sha256": "…", + "width": 1344, "height": 768, "duration_sec": 5.17, "fps": 24.0, "has_audio": true} + ], + "trace": {"…echo request trace…": "…", + "rp_job_id": "…", "worker_id": "", "prompt_id": "…", + "image": ":@sha256:…", "comfyui": "v0.36.0", + "workflows": "minimax-h3.yaml", "manifest_sha256": "…", + "gpu": "NVIDIA L40S", "cuda": "12.8"}, + "timings": {"cold": true, "boot_age_sec": 41.2, "boot_timeline": {"…": 0.0}, + "fetch_ms": 900, "comfy_queue_ms": 5, "execution_ms": 61234, + "collect_ms": 300, "upload_ms": 1200} +} +``` + +- `outputs[]` minimal satu entri. Entri `type: "temp"` dari history ComfyUI tidak pernah + dilaporkan. +- `media_type` berasal dari **konten** (ffprobe/sniff), bukan ekstensi atau kunci history. + `width`/`height` wajib untuk `image/*` dan `video/*`. `duration_sec`/`fps`/`has_audio` wajib + untuk `video/*`. `duration_sec` wajib untuk `audio/*`. +- `url` selalu presigned GET `https`, berlaku `OUTPUT_PRESIGN_TTL_SEC` (default 86400). + Base64 tidak pernah dikirim. +- Key output deterministik: `{OUTPUT_PREFIX}/{trace.generation_id}/{trace.attempt}/{NN}-{filename}`, + dengan `NN` = urutan dua digit mulai `00` dan `OUTPUT_PREFIX` default `renders`. +- `timings.boot_timeline` hanya ada di job pertama sebuah worker (`cold: true`). Isinya detik + sejak proses start per tahap: `gpu_check`, `model_verify`, `comfy_start`, `ready`, `warmup` + (bila aktif), `serverless_start`. + +### 4.2 Error + +```json +{ + "status": "error", + "protocol": "senai-worker/1", + "failure": {"type": "timeout", "code": "NO_PROGRESS", "stage": "execute", + "message": "no progress for 120s at node 136 (MiniMaxH3ReferenceToVideo)", + "infra": true, "retryable": true, "gpu_work": true, + "node_id": "136", "class_type": "MiniMaxH3ReferenceToVideo"}, + "trace": {"…": "…"}, + "timings": {"…": "…"} +} +``` + +- `message` ≤ 500 karakter, tanpa URL presigned, token, atau credential. +- `node_errors` (opsional) diteruskan dari respons 400 ComfyUI untuk `PROMPT_REJECTED`, dan + dipotong ke 20 entri. +- Flag `infra`, `retryable`, dan `gpu_work` **harus** sama dengan tabel §5 untuk `code` + tersebut. Bila berbeda, senai memakai tabel, bukan flag kiriman, lalu mencatat perbedaannya + sebagai log peringatan dan `raw.flag_mismatch`. Job tidak digagalkan hanya karena itu. + +### 4.3 Sehat + +```json +{ + "status": "healthy", + "protocol": "senai-worker/1", + "worker": {"image": "…", "comfyui": "v0.36.0", "workflows": "ltx25.yaml", + "manifest_sha256": "…", "gpu": "NVIDIA L40S", "cuda": "12.8", "vram_gb": 48, + "ready": true, "unready_code": null, + "models": {"declared": 6, "present": 6, "missing": [], + "bytes_manifest": 0, "bytes_visible": 0}}, + "timings": {"boot_age_sec": 12.3, "boot_timeline": {}} +} +``` + +`manifest_sha256` selalu diisi, termasuk saat unready. Bila manifest gagal di-parse, isinya +sha256 isi mentah berkas manifest yang dipilih `WORKFLOWS`. `ready: false` berarti worker ada +dalam mode unready. `unready_code` adalah kode §5 yang +akan dikembalikan ke setiap job workflow, dan `models.missing` mencantumkan path yang kurang. + +## 5. Taksonomi error + +`gpu_work` = apakah GPU sudah bekerja untuk job ini. `infra` = dihitung circuit breaker +binding. `retryable` = boleh diulang otomatis oleh senai (dengan batas di §6.3). +`refresh` = worker memensiunkan dirinya (`refresh_worker: true`). + +| `type` | `code` | `stage` | `gpu_work` | `infra` | `retryable` | `refresh` | +| --- | --- | --- | --- | --- | --- | --- | +| `validation_error` | `INVALID_ENVELOPE` | validate | false | false | false | false | +| `validation_error` | `UNSUPPORTED_PROTOCOL` | validate | false | false | false | false | +| `validation_error` | `EDITOR_FORMAT` | validate | false | false | false | false | +| `validation_error` | `NODE_NOT_ALLOWED` | validate | false | false | false | false | +| `validation_error` | `DEADLINE_PASSED` | validate | false | false | false | false | +| `input_error` | `INPUT_HOST_REJECTED` | fetch | false | false | false | false | +| `input_error` | `INPUT_TOO_LARGE` | fetch | false | false | false | false | +| `input_error` | `INPUT_INVALID` | fetch | false | false | false | false | +| `input_error` | `INPUT_FETCH_FAILED` | fetch | false | false | false | false | +| `model_missing` | `MODEL_NOT_IN_MANIFEST` | preflight | false | false | false | false | +| `model_missing` | `MODEL_CACHE_MISSING` | boot | false | true | false | false | +| `prompt_rejected` | `PROMPT_REJECTED` | submit | false | false | false | false | +| `execution_error` | `NODE_EXCEPTION` | execute | true | false | false | false | +| `oom` | `CUDA_OOM` | execute | true | true | true | true | +| `timeout` | `NO_PROGRESS` | execute | true | true | true | true | +| `timeout` | `EXECUTION_DEADLINE` | execute | true | true | false | true | +| `comfyui_down` | `COMFYUI_UNREACHABLE` | boot | false | true | true | true | +| `comfyui_down` | `COMFYUI_CRASHED` | execute | true | true | true | true | +| `output_error` | `OUTPUT_EMPTY` | collect | true | false | false | false | +| `output_error` | `UPLOAD_FAILED` | upload | true | true | true | false | +| `internal_error` | `OUTPUT_NOT_CONFIGURED` | boot | false | true | false | false | +| `internal_error` | `INTERNAL` | internal | true | true | false | true | + +Tabel yang sama, dalam bentuk mesin-terbaca, ada di `pins.yaml` (`error_codes`). Test kontrak +memastikan keduanya konsisten. + +## 6. Pemetaan di sisi senai + +### 6.1 Status + +Parser: `backend/src/senai/modules/providers/runpod/codecs/senai_worker_protocol.py`. + +| RunPod `status` | `output` | Hasil senai (`ProviderJob`) | +| --- | --- | --- | +| `IN_QUEUE` | — | `queued` | +| `IN_PROGRESS`, `RUNNING` | — (atau progress) | `running` | +| `COMPLETED` | `status == "success"` dan lolos §6.2 | `succeeded`, dengan `context.result_manifest` | +| `COMPLETED` | `status == "error"` + `failure` | `failed`, `error_code = "WORKER_" + failure.code` | +| `COMPLETED` | bentuk lain, `protocol` salah (termasuk pada output error), atau sukses tanpa `trace` | `ProviderContractError` (`PROVIDER_CONTRACT_ERROR`) | +| `FAILED` | apa pun | `failed`, `PROVIDER_FAILED` (infra true, retryable true: worker hilang, atau handler melanggar §4 dengan mengembalikan kunci `error`) | +| `TIMED_OUT` | — | `failed`, `PROVIDER_TIMEOUT` (infra true, retryable false) | +| `CANCELLED` | — | `cancelled` | +| lainnya | — | `ProviderContractError` | + +### 6.2 Validasi output sukses + +1. `outputs` tidak kosong, dan minimal satu entri cocok dengan kapabilitas: `video/*` untuk + kapabilitas video. +2. Setiap `url` berskema `https`, dan host-nya ada di `allowed_output_hosts` binding. Kalau + tidak → `PROVIDER_OUTPUT_HOST_REJECTED`. +3. `trace.generation_id` dan `trace.attempt` sama dengan job senai. Kalau tidak → + `PROVIDER_CONTRACT_ERROR`. +4. Artefak di `result_manifest` diklasifikasi dari `media_type`: `video/*` → video, + `image/*` → image, `audio/*` → audio. + +### 6.3 Retry otomatis + +Hanya kode dengan `retryable: true` (§5, plus `PROVIDER_FAILED`) yang boleh diulang. Batasnya: +- `max_auto_retries` (default 1); +- `retry_gpu_budget_seconds` (jumlah `execution_ms` semua attempt); +- sisa deadline ≥ `execution_timeout_seconds`; +- circuit binding tertutup; +- jeda ≥ 30 detik. + +Setiap retry adalah attempt baru dengan `trace.attempt` bertambah. Submit yang ambigu dan +cancel oleh user tidak pernah diulang. + +### 6.4 Identitas binding + +| `provider_endpoints` | Nilai | +| --- | --- | +| `adapter` | `ltx25_senai_worker_v1` (graph LTX dari `Ltx25Codec`) atau `minimax_h3_senai_worker_v1` | +| `contract_version` | `senai-worker/1` | +| `image_digest` | Digest image yang di-deploy (G3) | + +Kunci `config` binding baru (JSONB, tanpa migrasi): `gpu_usd_per_second`, +`input_url_ttl_seconds`, `no_progress_seconds`, `no_progress_load_seconds`, +`max_auto_retries`, `retry_gpu_budget_seconds`. Kunci yang sudah ada tetap berlaku: +`queue_timeout_seconds`, `execution_timeout_seconds`, `ingest_timeout_seconds`, +`max_active_jobs`, `allowed_output_hosts`, `profiles`. + +## 7. Environment worker + +Daftar mesin-terbaca ada di `pins.yaml` (`env`). Yang wajib di setiap endpoint senai: + +| Env | Nilai produksi | Catatan | +| --- | --- | --- | +| `WORKFLOWS` | `ltx25.yaml` atau `minimax-h3.yaml` | Selektor set. Hanya manifest yang disebut yang dibaca. | +| `MODEL_DOWNLOAD_POLICY` | `cache-only` | Worker GPU tidak pernah mengunduh bobot. | +| `HF_CACHE_ROOT` | default `/runpod-volume/huggingface-cache/hub` | Root cached model Hugging Face RunPod. | +| `AWS_BUCKET_NAME`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_DEFAULT_REGION=auto`, `AWS_ENDPOINT_URL` | R2 staging | Nama sama dengan endpoint vavo. Tanpa bucket, worker masuk mode unready dengan `OUTPUT_NOT_CONFIGURED`, dan tidak pernah mengirim base64. | +| `INPUT_ALLOWED_HOSTS` | host R2 bucket aset senai | Daftar host dipisah koma. Kosong → semua input `url` ditolak. | +| `REFRESH_WORKER` | `dirty` | `dirty` (default baru) \| `always` \| `never` | + +Manifest set format v2 (`workflow/.yaml` di repo worker): + +```yaml +version: 2 +set: minimax-h3 +requires_comfyui: ">=0.35.0" +custom_nodes: [] # nama folder custom node yang di-whitelist saat boot +workflows: [minimax-h3-r2v-v1.json, minimax-h3-motion-v1.json] +allowed_class_types_extra: [] # di luar union class_type semua graph di `workflows` +limits: {no_progress_sec: 120, no_progress_load_sec: 600, execution_ceiling_sec: 1800} +warmup_graph: null +models: + - path: diffusion_models/minimax_h3_ref2va_pruned_int8_convrot.safetensors + hf: {repo: Comfy-Org/MiniMax-H3, revision: "", file: diffusion_models/minimax_h3_ref2va_pruned_int8_convrot.safetensors} + sha256: "<64-hex dari metadata LFS>" + bytes: 0 +``` + +- **Allowlist `class_type`** = union `class_type` semua graph di `workflows`, ditambah + `allowed_class_types_extra`. +- **Preflight model**: setiap nilai input string graph yang **tidak mengandung whitespace** dan + berakhiran `.safetensors|.gguf|.ckpt|.pt|.pth|.bin` harus sama dengan `path` tanpa kategori + pertama. String dengan whitespace, misalnya prompt yang kebetulan berakhiran `.pt`, bukan + rujukan model (seperti `load_plan` sekarang) dari model di + manifest. Kalau tidak → `MODEL_NOT_IN_MANIFEST`. +- **Verifikasi boot**: setiap model harus ada dengan ukuran = `bytes`, di bawah + `HF_CACHE_ROOT/models----/snapshots//`, atau di `COMFY_MODEL_ROOT` + untuk jalur cadangan. Kalau tidak → mode unready `MODEL_CACHE_MISSING`. + +## 8. Pin graph + +Graph yang dikirim senai di-pin sha256 kanonik di `pins.yaml` (`workflows`). Algoritmanya sama +dengan kontrak ltx25: `sha256(json.dumps(d, sort_keys=True, separators=(',', ':')).encode())`. +Graph LTX untuk worker ini adalah tiga graph yang sama dengan kontrak ltx25. Graph H3 +berstatus `pending` sampai diekspor dari ComfyUI pada versi image (WP3.3), dan tidak boleh +ditulis tangan. + +## 9. Batas yang tidak boleh dilanggar + +- Graph dari request publik tidak pernah diteruskan. Hanya codec senai yang membangun graph. +- Output base64 tidak pernah diterima. Output diklasifikasi dari `media_type`, bukan dari + kunci history. +- Credential (R2, HF, RunPod) tidak pernah masuk ke `trace`, `message`, log, atau + `provider_context`. +- 200 dari `/run` bukan bukti sukses. Hanya `/status` + `output.status == "success"` yang + lolos §6.2. +- Worker tidak pernah mengulang render sendiri, dan tidak pernah mengunduh bobot di jalur job. +- Endpoint vavo `338flghj3ra4qj` dan template-nya tidak disentuh oleh kontrak ini. + +## 10. Yang belum terbukti (menentukan naik ke 1.0.0) + +| Klaim | Diverifikasi di | +| --- | --- | +| Di sisi server RunPod, `output` dengan `status: "error"` + `failure` tetap utuh bersama outer `COMPLETED`. Perilaku SDK sudah terbukti lokal (§4), sisi server belum. | G1 (stub CPU) | +| `refresh_worker` benar-benar memensiunkan worker; `progress_update` terlihat di `/status` | G1 | +| `/cancel` pada `IN_PROGRESS` menghentikan handler | G1 | +| Gaya host URL presign R2 cocok dengan `allowed_output_hosts` | G2 | +| Layout snapshot cached model HF sesuai §7, dan ukuran berkas = metadata LFS | G4 | +| Cold start, VRAM puncak, durasi per tahap di GPU 48 GB | G5, G7 | diff --git a/contract/senai-worker-1/examples/completed_error_no_progress.response.json b/contract/senai-worker-1/examples/completed_error_no_progress.response.json new file mode 100644 index 000000000..07d3c9fe6 --- /dev/null +++ b/contract/senai-worker-1/examples/completed_error_no_progress.response.json @@ -0,0 +1,47 @@ +{ + "id": "sync-9c2b-1f0e-e1", + "status": "COMPLETED", + "delayTime": 1200, + "executionTime": 131002, + "workerId": "p3n8v5t2wxyz", + "output": { + "status": "error", + "protocol": "senai-worker/1", + "trace": { + "generation_id": "9a1d2c4e-5b6f-4a7b-8c9d-0e1f2a3b4c5d", + "attempt": 2, + "binding_alias": "mnx-h3", + "binding_revision": 3, + "adapter": "minimax_h3_senai_worker_v1", + "workflow_id": "minimax-h3-r2v-v1", + "graph_sha256": "1111111111111111111111111111111111111111111111111111111111111111", + "rp_job_id": "sync-9c2b-1f0e-e1", + "worker_id": "p3n8v5t2wxyz", + "prompt_id": "a4b1c2d3-0000-4000-8000-000000000001", + "image": "docker.io/maulanashendi/worker-comfyui:senai-0.1.0@sha256:3333333333333333333333333333333333333333333333333333333333333333", + "comfyui": "v0.36.0", + "workflows": "minimax-h3.yaml", + "manifest_sha256": "4444444444444444444444444444444444444444444444444444444444444444", + "gpu": "NVIDIA L40S", + "cuda": "12.8" + }, + "timings": { + "cold": false, + "boot_age_sec": 812.0, + "fetch_ms": 1830, + "comfy_queue_ms": 3, + "execution_ms": 129100 + }, + "failure": { + "type": "timeout", + "code": "NO_PROGRESS", + "stage": "execute", + "message": "no progress for 120s at node 136 (MiniMaxH3ReferenceToVideo)", + "infra": true, + "retryable": true, + "gpu_work": true, + "node_id": "136", + "class_type": "MiniMaxH3ReferenceToVideo" + } + } +} diff --git a/contract/senai-worker-1/examples/completed_healthy.response.json b/contract/senai-worker-1/examples/completed_healthy.response.json new file mode 100644 index 000000000..264179e84 --- /dev/null +++ b/contract/senai-worker-1/examples/completed_healthy.response.json @@ -0,0 +1,37 @@ +{ + "id": "sync-0a0a-health", + "status": "COMPLETED", + "delayTime": 35011, + "executionTime": 88, + "output": { + "status": "healthy", + "protocol": "senai-worker/1", + "worker": { + "image": "docker.io/maulanashendi/worker-comfyui:senai-0.1.0@sha256:3333333333333333333333333333333333333333333333333333333333333333", + "comfyui": "v0.36.0", + "workflows": "minimax-h3.yaml", + "manifest_sha256": "6666666666666666666666666666666666666666666666666666666666666666", + "gpu": "NVIDIA L40S", + "cuda": "12.8", + "vram_gb": 48, + "ready": false, + "unready_code": "MODEL_CACHE_MISSING", + "models": { + "declared": 8, + "present": 7, + "missing": [ + "model_patches/minimax_h3_fun_controlnet_union_pruned_int8_convrot.safetensors" + ], + "bytes_manifest": 48747878810, + "bytes_visible": 46000000000 + } + }, + "timings": { + "boot_age_sec": 35.2, + "boot_timeline": { + "gpu_check": 2.9, + "model_verify": 3.0 + } + } + } +} diff --git a/contract/senai-worker-1/examples/completed_success.response.json b/contract/senai-worker-1/examples/completed_success.response.json new file mode 100644 index 000000000..a2ad029f9 --- /dev/null +++ b/contract/senai-worker-1/examples/completed_success.response.json @@ -0,0 +1,62 @@ +{ + "id": "sync-3e1f-7d2a-e2", + "status": "COMPLETED", + "delayTime": 93012, + "executionTime": 81340, + "workerId": "k7x2m9q1abcd", + "output": { + "status": "success", + "protocol": "senai-worker/1", + "outputs": [ + { + "url": "https://acct.r2.cloudflarestorage.com/senai-staging/renders/0f7f5a52-3a4c-4d53-9d6e-2b8f3c1a7e10/1/00-LTX-2.5_i2v_00001_.mp4?X-Amz-Signature=ghi", + "bucket": "senai-staging", + "key": "renders/0f7f5a52-3a4c-4d53-9d6e-2b8f3c1a7e10/1/00-LTX-2.5_i2v_00001_.mp4", + "filename": "LTX-2.5_i2v_00001_.mp4", + "node_id": "75", + "media_type": "video/mp4", + "bytes": 6123456, + "sha256": "5555555555555555555555555555555555555555555555555555555555555555", + "width": 1280, + "height": 704, + "duration_sec": 5.04, + "fps": 24.0, + "has_audio": true + } + ], + "trace": { + "generation_id": "0f7f5a52-3a4c-4d53-9d6e-2b8f3c1a7e10", + "attempt": 1, + "binding_alias": "ltx25-self", + "binding_revision": 1, + "adapter": "ltx25_senai_worker_v1", + "workflow_id": "ltx25-i2v-v1", + "graph_sha256": "be0ee529599ea4754d7d7612bb709b5e6b7f2101c8cc1780f6632255e9d6bfcc", + "rp_job_id": "sync-3e1f-7d2a-e2", + "worker_id": "k7x2m9q1abcd", + "prompt_id": "a4b1c2d3-0000-4000-8000-000000000001", + "image": "docker.io/maulanashendi/worker-comfyui:senai-0.1.0@sha256:3333333333333333333333333333333333333333333333333333333333333333", + "comfyui": "v0.36.0", + "workflows": "ltx25.yaml", + "manifest_sha256": "4444444444444444444444444444444444444444444444444444444444444444", + "gpu": "NVIDIA L40S", + "cuda": "12.8" + }, + "timings": { + "cold": true, + "boot_age_sec": 44.7, + "boot_timeline": { + "gpu_check": 3.1, + "model_verify": 3.2, + "comfy_start": 21.9, + "ready": 27.4, + "serverless_start": 27.5 + }, + "fetch_ms": 12, + "comfy_queue_ms": 4, + "execution_ms": 78811, + "collect_ms": 210, + "upload_ms": 1450 + } + } +} diff --git a/contract/senai-worker-1/examples/failed_worker_lost.response.json b/contract/senai-worker-1/examples/failed_worker_lost.response.json new file mode 100644 index 000000000..4efab2f99 --- /dev/null +++ b/contract/senai-worker-1/examples/failed_worker_lost.response.json @@ -0,0 +1,7 @@ +{ + "id": "sync-dead-beef-e1", + "status": "FAILED", + "delayTime": 1500, + "executionTime": 44000, + "error": "worker exited unexpectedly" +} diff --git a/contract/senai-worker-1/examples/h3_r2v_url_inputs.request.json b/contract/senai-worker-1/examples/h3_r2v_url_inputs.request.json new file mode 100644 index 000000000..2de44c8f9 --- /dev/null +++ b/contract/senai-worker-1/examples/h3_r2v_url_inputs.request.json @@ -0,0 +1,80 @@ +{ + "input": { + "protocol": "senai-worker/1", + "workflow": { + "137": { + "class_type": "LoadImage", + "inputs": { + "image": "ref_image_0.png" + } + }, + "150": { + "class_type": "LoadVideo", + "inputs": { + "file": "ref_video_0.mp4" + } + }, + "136": { + "class_type": "MiniMaxH3ReferenceToVideo", + "inputs": { + "prompt": " walks like