diff --git a/deploy/mainnet/docker-compose.supplier-ds-flash-htz-chat.yml b/deploy/mainnet/docker-compose.supplier-ds-flash-htz-chat.yml new file mode 100644 index 0000000..1836ad7 --- /dev/null +++ b/deploy/mainnet/docker-compose.supplier-ds-flash-htz-chat.yml @@ -0,0 +1,100 @@ +# deploy/mainnet/docker-compose.supplier-ds-flash-htz-chat.yml — Supplier — MAINNET +# (Hetzner Inference / DeepSeek-V4-Flash-0731 — MULTI-TURN CHAT capability `llm.chat.v1`) +# +# The chat-session sibling of docker-compose.supplier-ds-flash-htz.yml. Same +# image, same Hetzner backend (DeepSeek-V4-Flash-0731), but CAPABILITY_KIND=chat-session so +# it mounts /v1/chat/{start,message,end} (escrow bookends, off-chain streaming +# turns) instead of the one-off /v1/chat/completions route. +# +# A SEPARATE process/advert from the one-off supplier on purpose: the chat +# session holds the supplier's single-slot lock for the whole conversation +# (minutes), which would starve one-off traffic if co-hosted. +# +# One of 8 Hetzner-backed suppliers (4 models x {one-off, chat-session}); +# fleet runbook: docs/HETZNER_SETUP.md. +# +# Pre-flight: +# 1. DNS: mp-suppliers-ds-flash-htz-chat.vector.apexfusion.org → host +# (wildcard *.vector.apexfusion.org already in place via Cloudflare) +# 2. supplier/.env.ds-flash-htz-chat written on host (chmod 600) with at minimum: +# SUPPLIER_PRIV_KEY_HEX, SUPPLIER_ADDRESS, SUPPLIER_PKH, +# SUPPLIER_PUB_KEY_HEX, OPENAI_API_KEY, OGMIOS_URL, NETWORK_ID=1, +# VECTOR_ZERO_TIME_MS, ESCROW_REF_UTXO, ADVERT_REF_UTXO, ADVERT_REF, +# LIVE_CHAIN=1 +# The advert must be posted with --capability-id llm.chat.v1 and a large +# --max-processing-ms (~1800000) so Claim+Submit both land before deliver_by. +# 3. Networks already exist (created by the canonical supplier compose): +# docker network create marketplace-mainnet-net # one-shot +# # traefik-net is created by the Traefik project + +name: marketplace-mainnet-supplier-ds-flash-htz-chat + +services: + supplier: + build: + context: ../.. + dockerfile: supplier/Dockerfile + container_name: marketplace-mainnet-supplier-ds-flash-htz-chat + restart: unless-stopped + env_file: + # Self-contained env for this Hetzner-backed variant. Holds this + # supplier's distinct SUPPLIER_PRIV_KEY_HEX (+ derived wallet vars), + # ADVERT_REF, the shared Hetzner OPENAI_API_KEY, and the shared mainnet + # plumbing duplicated in full (NETWORK_ID, OGMIOS_URL, VECTOR_ZERO_TIME_MS, + # ref-script UTxOs, LIVE_CHAIN). No env_file ordering / override magic. + - path: ../../supplier/.env.ds-flash-htz-chat + required: false + environment: + # Multi-turn chat session capability (mounts /v1/chat/{start,message,end}). + CAPABILITY_KIND: chat-session + LLM_BACKEND: openai + # No /v1 suffix — supplier/src/openai.ts appends /v1/chat/completions + # (Hetzner serves at https://inference.hetzner.com/api/v1/...). + OPENAI_BASE_URL: https://inference.hetzner.com/api + # Reasoning-style models can be slow; mirror the one-off compose. + OPENAI_TIMEOUT_MS: "300000" + # Idle end (supplier Submits the transcript): single-slot suppliers must + # free up quickly (the gateway transparently reopens a session on the + # next turn). Mirrors the kimi-chat fleet. + CHAT_IDLE_TIMEOUT_MS: "900000" + CHAT_SETTLE_MODE: ticket + MAX_CHAT_SESSIONS: "8" + PORT: "8080" + # Enable LiveOgmiosProvider so Claim/Submit actually broadcast. + LIVE_CHAIN: "1" + # NETWORK_ID and OGMIOS_URL intentionally not pinned here — they MUST + # come from the operator-provided supplier/.env.ds-flash-htz-chat on a mainnet host. + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://localhost:8080/healthz >/dev/null || exit 1"] + interval: 20s + timeout: 5s + retries: 3 + start_period: 15s + networks: + - marketplace-mainnet-net + - traefik-net + labels: + - "traefik.enable=true" + - "traefik.docker.network=traefik-net" + - "traefik.http.routers.mp-suppliers-ds-flash-htz-chat-mainnet.entrypoints=web" + - "traefik.http.routers.mp-suppliers-ds-flash-htz-chat-mainnet.rule=Host(`mp-suppliers-ds-flash-htz-chat.vector.apexfusion.org`)" + - "traefik.http.routers.mp-suppliers-ds-flash-htz-chat-mainnet.middlewares=mp-suppliers-ds-flash-htz-chat-mainnet-https" + - "traefik.http.middlewares.mp-suppliers-ds-flash-htz-chat-mainnet-https.redirectscheme.scheme=https" + - "traefik.http.middlewares.mp-suppliers-ds-flash-htz-chat-mainnet-https.redirectscheme.permanent=true" + - "traefik.http.routers.mp-suppliers-ds-flash-htz-chat-mainnet-tls.entrypoints=websecure" + - "traefik.http.routers.mp-suppliers-ds-flash-htz-chat-mainnet-tls.rule=Host(`mp-suppliers-ds-flash-htz-chat.vector.apexfusion.org`)" + - "traefik.http.routers.mp-suppliers-ds-flash-htz-chat-mainnet-tls.tls.certresolver=letsencrypt" + - "traefik.http.services.mp-suppliers-ds-flash-htz-chat-mainnet.loadbalancer.server.port=8080" + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + +networks: + marketplace-mainnet-net: + external: true + name: marketplace-mainnet-net + traefik-net: + external: true + name: traefik-net diff --git a/deploy/mainnet/docker-compose.supplier-ds-flash-htz.yml b/deploy/mainnet/docker-compose.supplier-ds-flash-htz.yml new file mode 100644 index 0000000..f90fd08 --- /dev/null +++ b/deploy/mainnet/docker-compose.supplier-ds-flash-htz.yml @@ -0,0 +1,88 @@ +# deploy/mainnet/docker-compose.supplier-ds-flash-htz.yml — Supplier — MAINNET +# (Hetzner Inference / DeepSeek-V4-Flash-0731 — one-off `llm.text.generate.v1`) +# +# Hetzner-backed supplier. LLM_BACKEND=openai pointed directly at Hetzner's +# hosted inference API (OpenAI-compatible at https://inference.hetzner.com/api). +# No local proxy container — reachable over public HTTPS. +# +# One of 8 Hetzner-backed suppliers (4 models x {one-off, chat-session}); +# fleet runbook: docs/HETZNER_SETUP.md. +# +# Pre-flight: +# 1. DNS: mp-suppliers-ds-flash-htz.vector.apexfusion.org → host +# (wildcard *.vector.apexfusion.org already in place via Cloudflare) +# 2. supplier/.env.ds-flash-htz written on host (chmod 600) with at minimum: +# SUPPLIER_PRIV_KEY_HEX, SUPPLIER_ADDRESS, SUPPLIER_PKH, +# SUPPLIER_PUB_KEY_HEX, OPENAI_API_KEY, OGMIOS_URL, NETWORK_ID=1, +# VECTOR_ZERO_TIME_MS, ESCROW_REF_UTXO, ADVERT_REF_UTXO, ADVERT_REF, +# LIVE_CHAIN=1 +# 3. Networks already exist (created by the canonical supplier compose): +# docker network create marketplace-mainnet-net # one-shot +# # traefik-net is created by the Traefik project + +name: marketplace-mainnet-supplier-ds-flash-htz + +services: + supplier: + build: + context: ../.. + dockerfile: supplier/Dockerfile + container_name: marketplace-mainnet-supplier-ds-flash-htz + restart: unless-stopped + env_file: + # Self-contained env for this Hetzner-backed variant. Holds this + # supplier's distinct SUPPLIER_PRIV_KEY_HEX (+ derived wallet vars), + # ADVERT_REF, the shared Hetzner OPENAI_API_KEY, and the shared mainnet + # plumbing duplicated in full (NETWORK_ID, OGMIOS_URL, VECTOR_ZERO_TIME_MS, + # ref-script UTxOs, LIVE_CHAIN). No env_file ordering / override magic. + - path: ../../supplier/.env.ds-flash-htz + required: false + environment: + LLM_BACKEND: openai + # No /v1 suffix — supplier/src/openai.ts appends /v1/chat/completions + # (Hetzner serves at https://inference.hetzner.com/api/v1/...). + OPENAI_BASE_URL: https://inference.hetzner.com/api + # Reasoning-style models can be slow; matches the advert's + # max_processing_ms (300000). + OPENAI_TIMEOUT_MS: "300000" + # OPENAI_MAX_TOKENS intentionally unset: no max_tokens forwarded upstream, + # the provider grants context − prompt. Advert cap = model context length. + PORT: "8080" + # Enable LiveOgmiosProvider so submitTx/awaitTx actually broadcast. + LIVE_CHAIN: "1" + # NETWORK_ID and OGMIOS_URL intentionally not pinned here — they MUST + # come from the operator-provided supplier/.env.ds-flash-htz on a mainnet host. + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://localhost:8080/healthz >/dev/null || exit 1"] + interval: 20s + timeout: 5s + retries: 3 + start_period: 15s + networks: + - marketplace-mainnet-net + - traefik-net + labels: + - "traefik.enable=true" + - "traefik.docker.network=traefik-net" + - "traefik.http.routers.mp-suppliers-ds-flash-htz-mainnet.entrypoints=web" + - "traefik.http.routers.mp-suppliers-ds-flash-htz-mainnet.rule=Host(`mp-suppliers-ds-flash-htz.vector.apexfusion.org`)" + - "traefik.http.routers.mp-suppliers-ds-flash-htz-mainnet.middlewares=mp-suppliers-ds-flash-htz-mainnet-https" + - "traefik.http.middlewares.mp-suppliers-ds-flash-htz-mainnet-https.redirectscheme.scheme=https" + - "traefik.http.middlewares.mp-suppliers-ds-flash-htz-mainnet-https.redirectscheme.permanent=true" + - "traefik.http.routers.mp-suppliers-ds-flash-htz-mainnet-tls.entrypoints=websecure" + - "traefik.http.routers.mp-suppliers-ds-flash-htz-mainnet-tls.rule=Host(`mp-suppliers-ds-flash-htz.vector.apexfusion.org`)" + - "traefik.http.routers.mp-suppliers-ds-flash-htz-mainnet-tls.tls.certresolver=letsencrypt" + - "traefik.http.services.mp-suppliers-ds-flash-htz-mainnet.loadbalancer.server.port=8080" + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + +networks: + marketplace-mainnet-net: + external: true + name: marketplace-mainnet-net + traefik-net: + external: true + name: traefik-net diff --git a/deploy/mainnet/docker-compose.supplier-glm-chat.yml b/deploy/mainnet/docker-compose.supplier-glm-chat.yml new file mode 100644 index 0000000..7a94e1e --- /dev/null +++ b/deploy/mainnet/docker-compose.supplier-glm-chat.yml @@ -0,0 +1,100 @@ +# deploy/mainnet/docker-compose.supplier-glm-chat.yml — Supplier — MAINNET +# (Hetzner Inference / GLM-5.2-NVFP4 — MULTI-TURN CHAT capability `llm.chat.v1`) +# +# The chat-session sibling of docker-compose.supplier-glm.yml. Same +# image, same Hetzner backend (GLM-5.2-NVFP4), but CAPABILITY_KIND=chat-session so +# it mounts /v1/chat/{start,message,end} (escrow bookends, off-chain streaming +# turns) instead of the one-off /v1/chat/completions route. +# +# A SEPARATE process/advert from the one-off supplier on purpose: the chat +# session holds the supplier's single-slot lock for the whole conversation +# (minutes), which would starve one-off traffic if co-hosted. +# +# One of 8 Hetzner-backed suppliers (4 models x {one-off, chat-session}); +# fleet runbook: docs/HETZNER_SETUP.md. +# +# Pre-flight: +# 1. DNS: mp-suppliers-glm-chat.vector.apexfusion.org → host +# (wildcard *.vector.apexfusion.org already in place via Cloudflare) +# 2. supplier/.env.glm-chat written on host (chmod 600) with at minimum: +# SUPPLIER_PRIV_KEY_HEX, SUPPLIER_ADDRESS, SUPPLIER_PKH, +# SUPPLIER_PUB_KEY_HEX, OPENAI_API_KEY, OGMIOS_URL, NETWORK_ID=1, +# VECTOR_ZERO_TIME_MS, ESCROW_REF_UTXO, ADVERT_REF_UTXO, ADVERT_REF, +# LIVE_CHAIN=1 +# The advert must be posted with --capability-id llm.chat.v1 and a large +# --max-processing-ms (~1800000) so Claim+Submit both land before deliver_by. +# 3. Networks already exist (created by the canonical supplier compose): +# docker network create marketplace-mainnet-net # one-shot +# # traefik-net is created by the Traefik project + +name: marketplace-mainnet-supplier-glm-chat + +services: + supplier: + build: + context: ../.. + dockerfile: supplier/Dockerfile + container_name: marketplace-mainnet-supplier-glm-chat + restart: unless-stopped + env_file: + # Self-contained env for this Hetzner-backed variant. Holds this + # supplier's distinct SUPPLIER_PRIV_KEY_HEX (+ derived wallet vars), + # ADVERT_REF, the shared Hetzner OPENAI_API_KEY, and the shared mainnet + # plumbing duplicated in full (NETWORK_ID, OGMIOS_URL, VECTOR_ZERO_TIME_MS, + # ref-script UTxOs, LIVE_CHAIN). No env_file ordering / override magic. + - path: ../../supplier/.env.glm-chat + required: false + environment: + # Multi-turn chat session capability (mounts /v1/chat/{start,message,end}). + CAPABILITY_KIND: chat-session + LLM_BACKEND: openai + # No /v1 suffix — supplier/src/openai.ts appends /v1/chat/completions + # (Hetzner serves at https://inference.hetzner.com/api/v1/...). + OPENAI_BASE_URL: https://inference.hetzner.com/api + # Reasoning-style models can be slow; mirror the one-off compose. + OPENAI_TIMEOUT_MS: "300000" + # Idle end (supplier Submits the transcript): single-slot suppliers must + # free up quickly (the gateway transparently reopens a session on the + # next turn). Mirrors the kimi-chat fleet. + CHAT_IDLE_TIMEOUT_MS: "900000" + CHAT_SETTLE_MODE: ticket + MAX_CHAT_SESSIONS: "8" + PORT: "8080" + # Enable LiveOgmiosProvider so Claim/Submit actually broadcast. + LIVE_CHAIN: "1" + # NETWORK_ID and OGMIOS_URL intentionally not pinned here — they MUST + # come from the operator-provided supplier/.env.glm-chat on a mainnet host. + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://localhost:8080/healthz >/dev/null || exit 1"] + interval: 20s + timeout: 5s + retries: 3 + start_period: 15s + networks: + - marketplace-mainnet-net + - traefik-net + labels: + - "traefik.enable=true" + - "traefik.docker.network=traefik-net" + - "traefik.http.routers.mp-suppliers-glm-chat-mainnet.entrypoints=web" + - "traefik.http.routers.mp-suppliers-glm-chat-mainnet.rule=Host(`mp-suppliers-glm-chat.vector.apexfusion.org`)" + - "traefik.http.routers.mp-suppliers-glm-chat-mainnet.middlewares=mp-suppliers-glm-chat-mainnet-https" + - "traefik.http.middlewares.mp-suppliers-glm-chat-mainnet-https.redirectscheme.scheme=https" + - "traefik.http.middlewares.mp-suppliers-glm-chat-mainnet-https.redirectscheme.permanent=true" + - "traefik.http.routers.mp-suppliers-glm-chat-mainnet-tls.entrypoints=websecure" + - "traefik.http.routers.mp-suppliers-glm-chat-mainnet-tls.rule=Host(`mp-suppliers-glm-chat.vector.apexfusion.org`)" + - "traefik.http.routers.mp-suppliers-glm-chat-mainnet-tls.tls.certresolver=letsencrypt" + - "traefik.http.services.mp-suppliers-glm-chat-mainnet.loadbalancer.server.port=8080" + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + +networks: + marketplace-mainnet-net: + external: true + name: marketplace-mainnet-net + traefik-net: + external: true + name: traefik-net diff --git a/deploy/mainnet/docker-compose.supplier-glm.yml b/deploy/mainnet/docker-compose.supplier-glm.yml new file mode 100644 index 0000000..aa2d5f3 --- /dev/null +++ b/deploy/mainnet/docker-compose.supplier-glm.yml @@ -0,0 +1,88 @@ +# deploy/mainnet/docker-compose.supplier-glm.yml — Supplier — MAINNET +# (Hetzner Inference / GLM-5.2-NVFP4 — one-off `llm.text.generate.v1`) +# +# Hetzner-backed supplier. LLM_BACKEND=openai pointed directly at Hetzner's +# hosted inference API (OpenAI-compatible at https://inference.hetzner.com/api). +# No local proxy container — reachable over public HTTPS. +# +# One of 8 Hetzner-backed suppliers (4 models x {one-off, chat-session}); +# fleet runbook: docs/HETZNER_SETUP.md. +# +# Pre-flight: +# 1. DNS: mp-suppliers-glm.vector.apexfusion.org → host +# (wildcard *.vector.apexfusion.org already in place via Cloudflare) +# 2. supplier/.env.glm written on host (chmod 600) with at minimum: +# SUPPLIER_PRIV_KEY_HEX, SUPPLIER_ADDRESS, SUPPLIER_PKH, +# SUPPLIER_PUB_KEY_HEX, OPENAI_API_KEY, OGMIOS_URL, NETWORK_ID=1, +# VECTOR_ZERO_TIME_MS, ESCROW_REF_UTXO, ADVERT_REF_UTXO, ADVERT_REF, +# LIVE_CHAIN=1 +# 3. Networks already exist (created by the canonical supplier compose): +# docker network create marketplace-mainnet-net # one-shot +# # traefik-net is created by the Traefik project + +name: marketplace-mainnet-supplier-glm + +services: + supplier: + build: + context: ../.. + dockerfile: supplier/Dockerfile + container_name: marketplace-mainnet-supplier-glm + restart: unless-stopped + env_file: + # Self-contained env for this Hetzner-backed variant. Holds this + # supplier's distinct SUPPLIER_PRIV_KEY_HEX (+ derived wallet vars), + # ADVERT_REF, the shared Hetzner OPENAI_API_KEY, and the shared mainnet + # plumbing duplicated in full (NETWORK_ID, OGMIOS_URL, VECTOR_ZERO_TIME_MS, + # ref-script UTxOs, LIVE_CHAIN). No env_file ordering / override magic. + - path: ../../supplier/.env.glm + required: false + environment: + LLM_BACKEND: openai + # No /v1 suffix — supplier/src/openai.ts appends /v1/chat/completions + # (Hetzner serves at https://inference.hetzner.com/api/v1/...). + OPENAI_BASE_URL: https://inference.hetzner.com/api + # Reasoning-style models can be slow; matches the advert's + # max_processing_ms (300000). + OPENAI_TIMEOUT_MS: "300000" + # OPENAI_MAX_TOKENS intentionally unset: no max_tokens forwarded upstream, + # the provider grants context − prompt. Advert cap = model context length. + PORT: "8080" + # Enable LiveOgmiosProvider so submitTx/awaitTx actually broadcast. + LIVE_CHAIN: "1" + # NETWORK_ID and OGMIOS_URL intentionally not pinned here — they MUST + # come from the operator-provided supplier/.env.glm on a mainnet host. + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://localhost:8080/healthz >/dev/null || exit 1"] + interval: 20s + timeout: 5s + retries: 3 + start_period: 15s + networks: + - marketplace-mainnet-net + - traefik-net + labels: + - "traefik.enable=true" + - "traefik.docker.network=traefik-net" + - "traefik.http.routers.mp-suppliers-glm-mainnet.entrypoints=web" + - "traefik.http.routers.mp-suppliers-glm-mainnet.rule=Host(`mp-suppliers-glm.vector.apexfusion.org`)" + - "traefik.http.routers.mp-suppliers-glm-mainnet.middlewares=mp-suppliers-glm-mainnet-https" + - "traefik.http.middlewares.mp-suppliers-glm-mainnet-https.redirectscheme.scheme=https" + - "traefik.http.middlewares.mp-suppliers-glm-mainnet-https.redirectscheme.permanent=true" + - "traefik.http.routers.mp-suppliers-glm-mainnet-tls.entrypoints=websecure" + - "traefik.http.routers.mp-suppliers-glm-mainnet-tls.rule=Host(`mp-suppliers-glm.vector.apexfusion.org`)" + - "traefik.http.routers.mp-suppliers-glm-mainnet-tls.tls.certresolver=letsencrypt" + - "traefik.http.services.mp-suppliers-glm-mainnet.loadbalancer.server.port=8080" + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + +networks: + marketplace-mainnet-net: + external: true + name: marketplace-mainnet-net + traefik-net: + external: true + name: traefik-net diff --git a/deploy/mainnet/docker-compose.supplier-kimi-code-chat.yml b/deploy/mainnet/docker-compose.supplier-kimi-code-chat.yml new file mode 100644 index 0000000..a6a268d --- /dev/null +++ b/deploy/mainnet/docker-compose.supplier-kimi-code-chat.yml @@ -0,0 +1,100 @@ +# deploy/mainnet/docker-compose.supplier-kimi-code-chat.yml — Supplier — MAINNET +# (Hetzner Inference / Kimi-K2.7-Code — MULTI-TURN CHAT capability `llm.chat.v1`) +# +# The chat-session sibling of docker-compose.supplier-kimi-code.yml. Same +# image, same Hetzner backend (Kimi-K2.7-Code), but CAPABILITY_KIND=chat-session so +# it mounts /v1/chat/{start,message,end} (escrow bookends, off-chain streaming +# turns) instead of the one-off /v1/chat/completions route. +# +# A SEPARATE process/advert from the one-off supplier on purpose: the chat +# session holds the supplier's single-slot lock for the whole conversation +# (minutes), which would starve one-off traffic if co-hosted. +# +# One of 8 Hetzner-backed suppliers (4 models x {one-off, chat-session}); +# fleet runbook: docs/HETZNER_SETUP.md. +# +# Pre-flight: +# 1. DNS: mp-suppliers-kimi-code-chat.vector.apexfusion.org → host +# (wildcard *.vector.apexfusion.org already in place via Cloudflare) +# 2. supplier/.env.kimi-code-chat written on host (chmod 600) with at minimum: +# SUPPLIER_PRIV_KEY_HEX, SUPPLIER_ADDRESS, SUPPLIER_PKH, +# SUPPLIER_PUB_KEY_HEX, OPENAI_API_KEY, OGMIOS_URL, NETWORK_ID=1, +# VECTOR_ZERO_TIME_MS, ESCROW_REF_UTXO, ADVERT_REF_UTXO, ADVERT_REF, +# LIVE_CHAIN=1 +# The advert must be posted with --capability-id llm.chat.v1 and a large +# --max-processing-ms (~1800000) so Claim+Submit both land before deliver_by. +# 3. Networks already exist (created by the canonical supplier compose): +# docker network create marketplace-mainnet-net # one-shot +# # traefik-net is created by the Traefik project + +name: marketplace-mainnet-supplier-kimi-code-chat + +services: + supplier: + build: + context: ../.. + dockerfile: supplier/Dockerfile + container_name: marketplace-mainnet-supplier-kimi-code-chat + restart: unless-stopped + env_file: + # Self-contained env for this Hetzner-backed variant. Holds this + # supplier's distinct SUPPLIER_PRIV_KEY_HEX (+ derived wallet vars), + # ADVERT_REF, the shared Hetzner OPENAI_API_KEY, and the shared mainnet + # plumbing duplicated in full (NETWORK_ID, OGMIOS_URL, VECTOR_ZERO_TIME_MS, + # ref-script UTxOs, LIVE_CHAIN). No env_file ordering / override magic. + - path: ../../supplier/.env.kimi-code-chat + required: false + environment: + # Multi-turn chat session capability (mounts /v1/chat/{start,message,end}). + CAPABILITY_KIND: chat-session + LLM_BACKEND: openai + # No /v1 suffix — supplier/src/openai.ts appends /v1/chat/completions + # (Hetzner serves at https://inference.hetzner.com/api/v1/...). + OPENAI_BASE_URL: https://inference.hetzner.com/api + # Reasoning-style models can be slow; mirror the one-off compose. + OPENAI_TIMEOUT_MS: "300000" + # Idle end (supplier Submits the transcript): single-slot suppliers must + # free up quickly (the gateway transparently reopens a session on the + # next turn). Mirrors the kimi-chat fleet. + CHAT_IDLE_TIMEOUT_MS: "900000" + CHAT_SETTLE_MODE: ticket + MAX_CHAT_SESSIONS: "8" + PORT: "8080" + # Enable LiveOgmiosProvider so Claim/Submit actually broadcast. + LIVE_CHAIN: "1" + # NETWORK_ID and OGMIOS_URL intentionally not pinned here — they MUST + # come from the operator-provided supplier/.env.kimi-code-chat on a mainnet host. + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://localhost:8080/healthz >/dev/null || exit 1"] + interval: 20s + timeout: 5s + retries: 3 + start_period: 15s + networks: + - marketplace-mainnet-net + - traefik-net + labels: + - "traefik.enable=true" + - "traefik.docker.network=traefik-net" + - "traefik.http.routers.mp-suppliers-kimi-code-chat-mainnet.entrypoints=web" + - "traefik.http.routers.mp-suppliers-kimi-code-chat-mainnet.rule=Host(`mp-suppliers-kimi-code-chat.vector.apexfusion.org`)" + - "traefik.http.routers.mp-suppliers-kimi-code-chat-mainnet.middlewares=mp-suppliers-kimi-code-chat-mainnet-https" + - "traefik.http.middlewares.mp-suppliers-kimi-code-chat-mainnet-https.redirectscheme.scheme=https" + - "traefik.http.middlewares.mp-suppliers-kimi-code-chat-mainnet-https.redirectscheme.permanent=true" + - "traefik.http.routers.mp-suppliers-kimi-code-chat-mainnet-tls.entrypoints=websecure" + - "traefik.http.routers.mp-suppliers-kimi-code-chat-mainnet-tls.rule=Host(`mp-suppliers-kimi-code-chat.vector.apexfusion.org`)" + - "traefik.http.routers.mp-suppliers-kimi-code-chat-mainnet-tls.tls.certresolver=letsencrypt" + - "traefik.http.services.mp-suppliers-kimi-code-chat-mainnet.loadbalancer.server.port=8080" + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + +networks: + marketplace-mainnet-net: + external: true + name: marketplace-mainnet-net + traefik-net: + external: true + name: traefik-net diff --git a/deploy/mainnet/docker-compose.supplier-kimi-code.yml b/deploy/mainnet/docker-compose.supplier-kimi-code.yml new file mode 100644 index 0000000..4228e4b --- /dev/null +++ b/deploy/mainnet/docker-compose.supplier-kimi-code.yml @@ -0,0 +1,88 @@ +# deploy/mainnet/docker-compose.supplier-kimi-code.yml — Supplier — MAINNET +# (Hetzner Inference / Kimi-K2.7-Code — one-off `llm.text.generate.v1`) +# +# Hetzner-backed supplier. LLM_BACKEND=openai pointed directly at Hetzner's +# hosted inference API (OpenAI-compatible at https://inference.hetzner.com/api). +# No local proxy container — reachable over public HTTPS. +# +# One of 8 Hetzner-backed suppliers (4 models x {one-off, chat-session}); +# fleet runbook: docs/HETZNER_SETUP.md. +# +# Pre-flight: +# 1. DNS: mp-suppliers-kimi-code.vector.apexfusion.org → host +# (wildcard *.vector.apexfusion.org already in place via Cloudflare) +# 2. supplier/.env.kimi-code written on host (chmod 600) with at minimum: +# SUPPLIER_PRIV_KEY_HEX, SUPPLIER_ADDRESS, SUPPLIER_PKH, +# SUPPLIER_PUB_KEY_HEX, OPENAI_API_KEY, OGMIOS_URL, NETWORK_ID=1, +# VECTOR_ZERO_TIME_MS, ESCROW_REF_UTXO, ADVERT_REF_UTXO, ADVERT_REF, +# LIVE_CHAIN=1 +# 3. Networks already exist (created by the canonical supplier compose): +# docker network create marketplace-mainnet-net # one-shot +# # traefik-net is created by the Traefik project + +name: marketplace-mainnet-supplier-kimi-code + +services: + supplier: + build: + context: ../.. + dockerfile: supplier/Dockerfile + container_name: marketplace-mainnet-supplier-kimi-code + restart: unless-stopped + env_file: + # Self-contained env for this Hetzner-backed variant. Holds this + # supplier's distinct SUPPLIER_PRIV_KEY_HEX (+ derived wallet vars), + # ADVERT_REF, the shared Hetzner OPENAI_API_KEY, and the shared mainnet + # plumbing duplicated in full (NETWORK_ID, OGMIOS_URL, VECTOR_ZERO_TIME_MS, + # ref-script UTxOs, LIVE_CHAIN). No env_file ordering / override magic. + - path: ../../supplier/.env.kimi-code + required: false + environment: + LLM_BACKEND: openai + # No /v1 suffix — supplier/src/openai.ts appends /v1/chat/completions + # (Hetzner serves at https://inference.hetzner.com/api/v1/...). + OPENAI_BASE_URL: https://inference.hetzner.com/api + # Reasoning-style models can be slow; matches the advert's + # max_processing_ms (300000). + OPENAI_TIMEOUT_MS: "300000" + # OPENAI_MAX_TOKENS intentionally unset: no max_tokens forwarded upstream, + # the provider grants context − prompt. Advert cap = model context length. + PORT: "8080" + # Enable LiveOgmiosProvider so submitTx/awaitTx actually broadcast. + LIVE_CHAIN: "1" + # NETWORK_ID and OGMIOS_URL intentionally not pinned here — they MUST + # come from the operator-provided supplier/.env.kimi-code on a mainnet host. + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://localhost:8080/healthz >/dev/null || exit 1"] + interval: 20s + timeout: 5s + retries: 3 + start_period: 15s + networks: + - marketplace-mainnet-net + - traefik-net + labels: + - "traefik.enable=true" + - "traefik.docker.network=traefik-net" + - "traefik.http.routers.mp-suppliers-kimi-code-mainnet.entrypoints=web" + - "traefik.http.routers.mp-suppliers-kimi-code-mainnet.rule=Host(`mp-suppliers-kimi-code.vector.apexfusion.org`)" + - "traefik.http.routers.mp-suppliers-kimi-code-mainnet.middlewares=mp-suppliers-kimi-code-mainnet-https" + - "traefik.http.middlewares.mp-suppliers-kimi-code-mainnet-https.redirectscheme.scheme=https" + - "traefik.http.middlewares.mp-suppliers-kimi-code-mainnet-https.redirectscheme.permanent=true" + - "traefik.http.routers.mp-suppliers-kimi-code-mainnet-tls.entrypoints=websecure" + - "traefik.http.routers.mp-suppliers-kimi-code-mainnet-tls.rule=Host(`mp-suppliers-kimi-code.vector.apexfusion.org`)" + - "traefik.http.routers.mp-suppliers-kimi-code-mainnet-tls.tls.certresolver=letsencrypt" + - "traefik.http.services.mp-suppliers-kimi-code-mainnet.loadbalancer.server.port=8080" + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + +networks: + marketplace-mainnet-net: + external: true + name: marketplace-mainnet-net + traefik-net: + external: true + name: traefik-net diff --git a/deploy/mainnet/docker-compose.supplier-qwen35b-chat.yml b/deploy/mainnet/docker-compose.supplier-qwen35b-chat.yml new file mode 100644 index 0000000..c8ea8b8 --- /dev/null +++ b/deploy/mainnet/docker-compose.supplier-qwen35b-chat.yml @@ -0,0 +1,100 @@ +# deploy/mainnet/docker-compose.supplier-qwen35b-chat.yml — Supplier — MAINNET +# (Hetzner Inference / Qwen/Qwen3.6-35B-A3B-FP8 — MULTI-TURN CHAT capability `llm.chat.v1`) +# +# The chat-session sibling of docker-compose.supplier-qwen35b.yml. Same +# image, same Hetzner backend (Qwen/Qwen3.6-35B-A3B-FP8), but CAPABILITY_KIND=chat-session so +# it mounts /v1/chat/{start,message,end} (escrow bookends, off-chain streaming +# turns) instead of the one-off /v1/chat/completions route. +# +# A SEPARATE process/advert from the one-off supplier on purpose: the chat +# session holds the supplier's single-slot lock for the whole conversation +# (minutes), which would starve one-off traffic if co-hosted. +# +# One of 8 Hetzner-backed suppliers (4 models x {one-off, chat-session}); +# fleet runbook: docs/HETZNER_SETUP.md. +# +# Pre-flight: +# 1. DNS: mp-suppliers-qwen35b-chat.vector.apexfusion.org → host +# (wildcard *.vector.apexfusion.org already in place via Cloudflare) +# 2. supplier/.env.qwen35b-chat written on host (chmod 600) with at minimum: +# SUPPLIER_PRIV_KEY_HEX, SUPPLIER_ADDRESS, SUPPLIER_PKH, +# SUPPLIER_PUB_KEY_HEX, OPENAI_API_KEY, OGMIOS_URL, NETWORK_ID=1, +# VECTOR_ZERO_TIME_MS, ESCROW_REF_UTXO, ADVERT_REF_UTXO, ADVERT_REF, +# LIVE_CHAIN=1 +# The advert must be posted with --capability-id llm.chat.v1 and a large +# --max-processing-ms (~1800000) so Claim+Submit both land before deliver_by. +# 3. Networks already exist (created by the canonical supplier compose): +# docker network create marketplace-mainnet-net # one-shot +# # traefik-net is created by the Traefik project + +name: marketplace-mainnet-supplier-qwen35b-chat + +services: + supplier: + build: + context: ../.. + dockerfile: supplier/Dockerfile + container_name: marketplace-mainnet-supplier-qwen35b-chat + restart: unless-stopped + env_file: + # Self-contained env for this Hetzner-backed variant. Holds this + # supplier's distinct SUPPLIER_PRIV_KEY_HEX (+ derived wallet vars), + # ADVERT_REF, the shared Hetzner OPENAI_API_KEY, and the shared mainnet + # plumbing duplicated in full (NETWORK_ID, OGMIOS_URL, VECTOR_ZERO_TIME_MS, + # ref-script UTxOs, LIVE_CHAIN). No env_file ordering / override magic. + - path: ../../supplier/.env.qwen35b-chat + required: false + environment: + # Multi-turn chat session capability (mounts /v1/chat/{start,message,end}). + CAPABILITY_KIND: chat-session + LLM_BACKEND: openai + # No /v1 suffix — supplier/src/openai.ts appends /v1/chat/completions + # (Hetzner serves at https://inference.hetzner.com/api/v1/...). + OPENAI_BASE_URL: https://inference.hetzner.com/api + # Reasoning-style models can be slow; mirror the one-off compose. + OPENAI_TIMEOUT_MS: "300000" + # Idle end (supplier Submits the transcript): single-slot suppliers must + # free up quickly (the gateway transparently reopens a session on the + # next turn). Mirrors the kimi-chat fleet. + CHAT_IDLE_TIMEOUT_MS: "900000" + CHAT_SETTLE_MODE: ticket + MAX_CHAT_SESSIONS: "8" + PORT: "8080" + # Enable LiveOgmiosProvider so Claim/Submit actually broadcast. + LIVE_CHAIN: "1" + # NETWORK_ID and OGMIOS_URL intentionally not pinned here — they MUST + # come from the operator-provided supplier/.env.qwen35b-chat on a mainnet host. + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://localhost:8080/healthz >/dev/null || exit 1"] + interval: 20s + timeout: 5s + retries: 3 + start_period: 15s + networks: + - marketplace-mainnet-net + - traefik-net + labels: + - "traefik.enable=true" + - "traefik.docker.network=traefik-net" + - "traefik.http.routers.mp-suppliers-qwen35b-chat-mainnet.entrypoints=web" + - "traefik.http.routers.mp-suppliers-qwen35b-chat-mainnet.rule=Host(`mp-suppliers-qwen35b-chat.vector.apexfusion.org`)" + - "traefik.http.routers.mp-suppliers-qwen35b-chat-mainnet.middlewares=mp-suppliers-qwen35b-chat-mainnet-https" + - "traefik.http.middlewares.mp-suppliers-qwen35b-chat-mainnet-https.redirectscheme.scheme=https" + - "traefik.http.middlewares.mp-suppliers-qwen35b-chat-mainnet-https.redirectscheme.permanent=true" + - "traefik.http.routers.mp-suppliers-qwen35b-chat-mainnet-tls.entrypoints=websecure" + - "traefik.http.routers.mp-suppliers-qwen35b-chat-mainnet-tls.rule=Host(`mp-suppliers-qwen35b-chat.vector.apexfusion.org`)" + - "traefik.http.routers.mp-suppliers-qwen35b-chat-mainnet-tls.tls.certresolver=letsencrypt" + - "traefik.http.services.mp-suppliers-qwen35b-chat-mainnet.loadbalancer.server.port=8080" + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + +networks: + marketplace-mainnet-net: + external: true + name: marketplace-mainnet-net + traefik-net: + external: true + name: traefik-net diff --git a/deploy/mainnet/docker-compose.supplier-qwen35b.yml b/deploy/mainnet/docker-compose.supplier-qwen35b.yml new file mode 100644 index 0000000..fa67c81 --- /dev/null +++ b/deploy/mainnet/docker-compose.supplier-qwen35b.yml @@ -0,0 +1,88 @@ +# deploy/mainnet/docker-compose.supplier-qwen35b.yml — Supplier — MAINNET +# (Hetzner Inference / Qwen/Qwen3.6-35B-A3B-FP8 — one-off `llm.text.generate.v1`) +# +# Hetzner-backed supplier. LLM_BACKEND=openai pointed directly at Hetzner's +# hosted inference API (OpenAI-compatible at https://inference.hetzner.com/api). +# No local proxy container — reachable over public HTTPS. +# +# One of 8 Hetzner-backed suppliers (4 models x {one-off, chat-session}); +# fleet runbook: docs/HETZNER_SETUP.md. +# +# Pre-flight: +# 1. DNS: mp-suppliers-qwen35b.vector.apexfusion.org → host +# (wildcard *.vector.apexfusion.org already in place via Cloudflare) +# 2. supplier/.env.qwen35b written on host (chmod 600) with at minimum: +# SUPPLIER_PRIV_KEY_HEX, SUPPLIER_ADDRESS, SUPPLIER_PKH, +# SUPPLIER_PUB_KEY_HEX, OPENAI_API_KEY, OGMIOS_URL, NETWORK_ID=1, +# VECTOR_ZERO_TIME_MS, ESCROW_REF_UTXO, ADVERT_REF_UTXO, ADVERT_REF, +# LIVE_CHAIN=1 +# 3. Networks already exist (created by the canonical supplier compose): +# docker network create marketplace-mainnet-net # one-shot +# # traefik-net is created by the Traefik project + +name: marketplace-mainnet-supplier-qwen35b + +services: + supplier: + build: + context: ../.. + dockerfile: supplier/Dockerfile + container_name: marketplace-mainnet-supplier-qwen35b + restart: unless-stopped + env_file: + # Self-contained env for this Hetzner-backed variant. Holds this + # supplier's distinct SUPPLIER_PRIV_KEY_HEX (+ derived wallet vars), + # ADVERT_REF, the shared Hetzner OPENAI_API_KEY, and the shared mainnet + # plumbing duplicated in full (NETWORK_ID, OGMIOS_URL, VECTOR_ZERO_TIME_MS, + # ref-script UTxOs, LIVE_CHAIN). No env_file ordering / override magic. + - path: ../../supplier/.env.qwen35b + required: false + environment: + LLM_BACKEND: openai + # No /v1 suffix — supplier/src/openai.ts appends /v1/chat/completions + # (Hetzner serves at https://inference.hetzner.com/api/v1/...). + OPENAI_BASE_URL: https://inference.hetzner.com/api + # Reasoning-style models can be slow; matches the advert's + # max_processing_ms (300000). + OPENAI_TIMEOUT_MS: "300000" + # OPENAI_MAX_TOKENS intentionally unset: no max_tokens forwarded upstream, + # the provider grants context − prompt. Advert cap = model context length. + PORT: "8080" + # Enable LiveOgmiosProvider so submitTx/awaitTx actually broadcast. + LIVE_CHAIN: "1" + # NETWORK_ID and OGMIOS_URL intentionally not pinned here — they MUST + # come from the operator-provided supplier/.env.qwen35b on a mainnet host. + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://localhost:8080/healthz >/dev/null || exit 1"] + interval: 20s + timeout: 5s + retries: 3 + start_period: 15s + networks: + - marketplace-mainnet-net + - traefik-net + labels: + - "traefik.enable=true" + - "traefik.docker.network=traefik-net" + - "traefik.http.routers.mp-suppliers-qwen35b-mainnet.entrypoints=web" + - "traefik.http.routers.mp-suppliers-qwen35b-mainnet.rule=Host(`mp-suppliers-qwen35b.vector.apexfusion.org`)" + - "traefik.http.routers.mp-suppliers-qwen35b-mainnet.middlewares=mp-suppliers-qwen35b-mainnet-https" + - "traefik.http.middlewares.mp-suppliers-qwen35b-mainnet-https.redirectscheme.scheme=https" + - "traefik.http.middlewares.mp-suppliers-qwen35b-mainnet-https.redirectscheme.permanent=true" + - "traefik.http.routers.mp-suppliers-qwen35b-mainnet-tls.entrypoints=websecure" + - "traefik.http.routers.mp-suppliers-qwen35b-mainnet-tls.rule=Host(`mp-suppliers-qwen35b.vector.apexfusion.org`)" + - "traefik.http.routers.mp-suppliers-qwen35b-mainnet-tls.tls.certresolver=letsencrypt" + - "traefik.http.services.mp-suppliers-qwen35b-mainnet.loadbalancer.server.port=8080" + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + +networks: + marketplace-mainnet-net: + external: true + name: marketplace-mainnet-net + traefik-net: + external: true + name: traefik-net diff --git a/docs/HETZNER_SETUP.md b/docs/HETZNER_SETUP.md new file mode 100644 index 0000000..2a77265 --- /dev/null +++ b/docs/HETZNER_SETUP.md @@ -0,0 +1,128 @@ +# Hetzner Inference Supplier Fleet — Setup Runbook + +> **Status:** shipped. Configures 8 mainnet supplier nodes that use **Hetzner +> Inference** (`https://inference.hetzner.com`) as their **compute backend** for +> bonded inference work commissioned on Vector. Config only — no code changes. + +--- + +## 1. What this is + +Hetzner Inference is an OpenAI-compatible hosted API. Because the supplier's +upstream client (`supplier/src/openai.ts`) already speaks plain +`POST /v1/chat/completions` with a Bearer token, pointing it at Hetzner is +**pure configuration**: `LLM_BACKEND=openai` + `OPENAI_BASE_URL` + the API key. +This is the same pattern as the DeepSeek-direct and OpenRouter suppliers, and +the direct sibling of `docs/HUGGINGFACE_ROUTER_SETUP.md`. + +One supplier per (model × capability) — 4 models, both the one-off +`llm.text.generate.v1` and the multi-turn `llm.chat.v1` capability: + +| Supplier name | Model (on-chain + API param, verbatim) | Capability | Advert max_output_tokens | +|---|---|---|---| +| `kimi-code` | `Kimi-K2.7-Code` | `llm.text.generate.v1` | 262144 | +| `kimi-code-chat` | `Kimi-K2.7-Code` | `llm.chat.v1` | 262144 | +| `glm` | `GLM-5.2-NVFP4` | `llm.text.generate.v1` | 512000 | +| `glm-chat` | `GLM-5.2-NVFP4` | `llm.chat.v1` | 512000 | +| `ds-flash-htz` | `DeepSeek-V4-Flash-0731` | `llm.text.generate.v1` | 512000 | +| `ds-flash-htz-chat` | `DeepSeek-V4-Flash-0731` | `llm.chat.v1` | 512000 | +| `qwen35b` | `Qwen/Qwen3.6-35B-A3B-FP8` | `llm.text.generate.v1` | 262144 | +| `qwen35b-chat` | `Qwen/Qwen3.6-35B-A3B-FP8` | `llm.chat.v1` | 262144 | + +Shared advert parameters: price **200000 lovelace (0.2 AP3X)** flat per job, +bonds **1000000 lovelace (1 AP3X)** both sides, `max_processing_ms` **300000** +for one-off / **1800000** for chat (the session spans the whole conversation). +`max_output_tokens` is set to the model's context length: no `max_tokens` is +forwarded upstream (`OPENAI_MAX_TOKENS` unset), so the advert value only gates +what buyers may request. + +Names follow the brand-by-model convention; `ds-flash-htz` carries a `-htz` +suffix only because `deepseek-flash-*` was already taken by the OpenRouter +fleet serving `deepseek/deepseek-v4-flash`. + +## 2. Prerequisites + +1. A Hetzner account with Inference access and an API key (usage-based billing + to you as the node operator — the flat 0.2 AP3X job price is a business + choice, not cost-derived). One key is shared by all 8 suppliers; Hetzner + rate limits apply to the aggregate. +2. Eight funded supplier wallets (50 AP3X each recommended; bonds are 1 AP3X + per in-flight job and returned on completion). +3. The usual mainnet supplier deploy prerequisites (`deploy/README.md`); the + wildcard DNS `*.vector.apexfusion.org` already covers the new hosts. + +## 3. Verify the backend before spending on-chain + +The four model ids come from `GET https://inference.hetzner.com/api/v1/models` +and are sent **verbatim** as the upstream `model` param (the runtime uses +`advert.model`). Before posting adverts, smoke-test each model: + +```bash +curl -sS -X POST https://inference.hetzner.com/api/v1/chat/completions \ + -H "Authorization: Bearer $HETZNER_KEY" -H "Content-Type: application/json" \ + -d '{"model":"GLM-5.2-NVFP4","messages":[{"role":"user","content":"ping"}]}' \ + | jq '.choices[0].message.content' +``` + +`choices[0].message.content` must be a non-empty string with **no `max_tokens` +sent** (matches runtime config) — the supplier throws `openai_malformed` +otherwise and forfeits its 1 AP3X bond per failed job. For chat suppliers also +verify a `tools`/`tool_choice` round-trip. All four models passed both checks +on 2026-08-11. + +## 4. Per-supplier bring-up (repeat ×8) + +```bash +# 1. wallet — prints privateKeyHex, publicKeyHex, pubKeyHash, address +pnpm --filter @marketplace/supplier tx:gen-keypair --network 1 +# fund the printed address (50 AP3X recommended), then: + +# 2. env file on the mainnet host +cp supplier/.env.hetzner.example /root/agents-marketplace/supplier/.env. +chmod 600 /root/agents-marketplace/supplier/.env. +# fill in: ALL FOUR wallet vars (PRIV_KEY_HEX, ADDRESS, PKH, PUB_KEY_HEX — +# they are NOT derived at boot; missing derived vars ⇒ 403 wrong_supplier), +# OPENAI_API_KEY, and the ADVERT_* values from the table above. + +# 3. advert (one-off shown; chat: --capability-id llm.chat.v1 --max-processing-ms 1800000) +pnpm --filter @marketplace/supplier tx:post-advert \ + --capability-id llm.text.generate.v1 \ + --model 'GLM-5.2-NVFP4' \ + --max-output-tokens 512000 \ + --max-processing-ms 300000 \ + --price-lovelace 200000 \ + --endpoint-url https://mp-suppliers-.vector.apexfusion.org +# paste the printed "#0" into ADVERT_REF in the env file + +# 4. first start is always manual (CD skips compose projects with no running +# containers); subsequent rollouts ride the mainnet CD pipeline +docker compose -f deploy/mainnet/docker-compose.supplier-.yml up -d + +# 5. register the wallet for balance alerts +# add {name, address} to wallet-monitor/wallets.json on the host +``` + +## 5. Verification + +- `curl https://mp-suppliers-.vector.apexfusion.org/healthz` → `{"ok":true}`; + `/capability` shows the right model + pkh. +- Indexer: buyer `GET /v1/indexer/suppliers` lists the new entries with + `advert_status: Active`, `status: free`. +- Gateway `GET /openai/v1/models` includes the four Hetzner model ids. +- Watch `docker logs -f marketplace-mainnet-supplier-` through the first + jobs for `openai_malformed` (reasoning-model content-shape risk). + +## 6. Footguns + +- `OPENAI_BASE_URL=https://inference.hetzner.com/api` — **no `/v1`**; the + client appends `/v1/chat/completions`. +- Do **not** set `OPENAI_REASONING` — `reasoning:{enabled:false}` is an + OpenRouter-only param. +- The advert `--model` must match the Hetzner id exactly, including the + `Qwen/` prefix on `Qwen/Qwen3.6-35B-A3B-FP8` (slash is fine on-chain; + precedent `moonshotai/kimi-k2.6`). +- `Kimi-K2.7-Code` and `DeepSeek-V4-Flash-0731` auto-join the buyer's PDF pool + (case-insensitive substring allowlist `["kimi","deepseek","gpt"]` in + `buyer/src/pdf/caps.ts`); GLM and Qwen stay out. Deliberate — adjust + `PDF_MODEL_ALLOWLIST`/`PDF_MODEL_DENYLIST` in the buyer env to change it. +- `OPENAI_TIMEOUT_MS` must stay ≤ the advert's `max_processing_ms`. diff --git a/supplier/.env.hetzner.example b/supplier/.env.hetzner.example new file mode 100644 index 0000000..b04e15f --- /dev/null +++ b/supplier/.env.hetzner.example @@ -0,0 +1,105 @@ +# supplier/.env.hetzner.example +# +# Preset for a supplier that uses Hetzner Inference +# (https://inference.hetzner.com) as its COMPUTE BACKEND to fulfil bonded +# inference work on Vector. Hetzner exposes an OpenAI-compatible API, so this +# reuses the existing LLM_BACKEND=openai path with NO code changes — config only. +# +# One template for all 8 Hetzner-backed suppliers (4 models x {one-off, chat}): +# kimi-code[-chat] → Kimi-K2.7-Code (ctx 262144) +# glm[-chat] → GLM-5.2-NVFP4 (ctx 512000) +# ds-flash-htz[-chat] → DeepSeek-V4-Flash-0731 (ctx 512000) +# qwen35b[-chat] → Qwen/Qwen3.6-35B-A3B-FP8 (ctx 262144) +# +# Copy to supplier/.env. on the host (chmod 600) and fill in real values. +# .env.* is gitignored — NEVER commit a populated copy. +# +# Runbook: docs/HETZNER_SETUP.md +# +# Lines marked SECRET must never be shared, logged, or committed. + +# ─── Wallet identity ────────────────────────────────────────────────────── +# Generate all four values in one shot (from repo root): +# pnpm --filter @marketplace/supplier tx:gen-keypair --network 1 +# +# ALL FOUR are required. The supplier does NOT re-derive address/pkh/pubkey +# from the private key at boot — leaving them unset silently yields an empty +# wallet identity and every escrow is rejected 403 wrong_supplier (this took +# down the TTS supplier once; don't repeat it). + +# 64-char hex Ed25519 private key seed for this supplier's wallet. SECRET. +SUPPLIER_PRIV_KEY_HEX=PASTE_64_HEX_CHARS_HERE +SUPPLIER_ADDRESS=PASTE_ADDR1_HERE +SUPPLIER_PKH=PASTE_28_BYTE_HEX_HERE +SUPPLIER_PUB_KEY_HEX=PASTE_32_BYTE_HEX_HERE + +# ─── Chain plumbing (mainnet) ───────────────────────────────────────────── +OGMIOS_URL=wss://ogmios.vector.mainnet.apexfusion.org +NETWORK_ID=1 +VECTOR_ZERO_TIME_MS=1756485600000 + +# CIP-33 reference scripts (shared mainnet deployment). +ESCROW_REF_UTXO=c8d84c6d67ec67a1efe5e9c6c06d53020e05d1bb96d1c55ecb1eb7d5010c4d54#0 +ADVERT_REF_UTXO=c8d84c6d67ec67a1efe5e9c6c06d53020e05d1bb96d1c55ecb1eb7d5010c4d54#1 + +# OutputReference of this supplier's on-chain advertisement UTxO. +# Post it first (see tx:post-advert below), then paste the printed "#0". +ADVERT_REF=PASTE_TXHASH_HERE#0 + +# LIVE_CHAIN: only the literal "1" opts in to real Claim/Submit txs. +LIVE_CHAIN=1 + +# ─── Backend: Hetzner Inference (OpenAI-compatible) ─────────────────────── +LLM_BACKEND=openai + +# Hetzner base URL. The supplier appends /v1/chat/completions itself, so do +# NOT include the /api/v1 → only up to /api. +OPENAI_BASE_URL=https://inference.hetzner.com/api + +# Hetzner Inference API key. Shared across all 8 Hetzner-backed suppliers — +# rate limits apply to the aggregate. SECRET. +OPENAI_API_KEY=PASTE_HETZNER_KEY_HERE + +# All four models are reasoning-style; generations can be slow. +OPENAI_TIMEOUT_MS=300000 + +# Do NOT set OPENAI_REASONING — `reasoning:{enabled:false}` is an +# OpenRouter-only param. +# OPENAI_REASONING= + +# OPENAI_MAX_TOKENS intentionally unset: no max_tokens is forwarded upstream, +# Hetzner grants context − prompt. The advert's max_output_tokens (= model +# context length) only caps what buyers may request. +# OPENAI_MAX_TOKENS= + +PORT=8080 + +# ─── tx:post-advert — run once before first deploy ──────────────────────── +# One-off (llm.text.generate.v1) suppliers, from repo root: +# pnpm --filter @marketplace/supplier tx:post-advert \ +# --capability-id llm.text.generate.v1 \ +# --model \ +# --max-output-tokens <262144 or 512000 per table> \ +# --max-processing-ms 300000 \ +# --price-lovelace 200000 \ +# --endpoint-url https://mp-suppliers-.vector.apexfusion.org +# +# Chat (llm.chat.v1) suppliers: same, but +# --capability-id llm.chat.v1 --max-processing-ms 1800000 +# (the session spans the whole conversation; Claim+Submit must land before +# deliver_by — mirrors the kimi-chat fleet). +# +# CRITICAL: --model goes on-chain AND is sent verbatim to Hetzner as the API +# `model` param (jobRunner uses advert.model). It must be the EXACT id from +# GET https://inference.hetzner.com/api/v1/models — including the "Qwen/" +# prefix on Qwen/Qwen3.6-35B-A3B-FP8. +ADVERT_CAPABILITY_ID=llm.text.generate.v1 +ADVERT_MODEL=GLM-5.2-NVFP4 +ADVERT_MAX_OUTPUT_TOKENS=512000 +ADVERT_MAX_PROCESSING_MS=300000 +ADVERT_PRICE_LOVELACE=200000 +ADVERT_SUPPLIER_BOND_LOVELACE=1000000 +ADVERT_BUYER_BOND_LOVELACE=1000000 +ADVERT_ENDPOINT_URL=https://mp-suppliers-glm.vector.apexfusion.org +ADVERT_DETAIL_URI= +ADVERT_DETAIL_HASH=