thangvip's picture
fix: call Modal directly from the canonical Space (#6)
cdd4f77
Raw
History Blame Contribute Delete
5.13 kB
from __future__ import annotations
import os
from pathlib import Path
from typing import Literal
from urllib.parse import urlparse
from pydantic import BaseModel, ConfigDict, Field, SecretStr, model_validator
from .schema import ForestStyle
class AppConfig(BaseModel):
model_config = ConfigDict(extra="forbid")
text_backend: Literal["demo", "hf_inference", "llama_cpp", "transformers", "modal"] = "demo"
image_backend: Literal["demo", "flux", "hf_inference", "modal", "zerogpu"] = "demo"
music_backend: Literal["none", "modal"] = "none"
hf_text_model: str = "openbmb/MiniCPM4.1-8B"
transformers_text_model: str = "openbmb/MiniCPM4.1-8B"
hf_image_model: str = "black-forest-labs/FLUX.1-schnell"
llama_base_url: str = "http://127.0.0.1:8080"
llama_model: str = "compliment-forest-minicpm5-1b"
flux_model_id: str = "black-forest-labs/FLUX.1-dev"
flux_lora_id: str = "build-small-hackathon/compliment-forest-flux-lora"
modal_text_endpoint: str | None = None
modal_image_endpoint: str | None = None
modal_music_endpoint: str | None = None
modal_signing_key: SecretStr | None = None
upstream_space_url: str | None = None
local_files_only: bool = False
default_seed: int = Field(default=3407, ge=0, le=2_147_483_647)
default_style: ForestStyle = "surprise"
trace_path: Path | None = None
@model_validator(mode="after")
def validate_local_text_server(self) -> AppConfig:
if self.text_backend == "llama_cpp":
hostname = urlparse(self.llama_base_url).hostname
if hostname not in {"127.0.0.1", "localhost", "::1"}:
raise ValueError("llama.cpp model server must be local")
if self.text_backend == "modal":
if not self.modal_text_endpoint or not self.modal_signing_key:
raise ValueError("modal text backend requires endpoint credentials")
if urlparse(self.modal_text_endpoint).scheme != "https":
raise ValueError("modal text endpoint must use HTTPS")
if self.image_backend == "modal":
if not self.modal_image_endpoint or not self.modal_signing_key:
raise ValueError("modal image backend requires endpoint credentials")
if urlparse(self.modal_image_endpoint).scheme != "https":
raise ValueError("modal image endpoint must use HTTPS")
if self.music_backend == "modal":
if not self.modal_music_endpoint or not self.modal_signing_key:
raise ValueError("modal music backend requires endpoint credentials")
if urlparse(self.modal_music_endpoint).scheme != "https":
raise ValueError("modal music endpoint must use HTTPS")
if self.upstream_space_url:
parsed_upstream = urlparse(self.upstream_space_url)
if parsed_upstream.scheme != "https" or not parsed_upstream.netloc:
raise ValueError("upstream Space URL must use HTTPS")
return self
@classmethod
def from_env(cls) -> AppConfig:
trace_path = os.getenv("CF_TRACE_PATH")
hosted_space = bool(os.getenv("SPACE_ID"))
default_text_backend = "transformers" if hosted_space else "demo"
default_image_backend = "zerogpu" if hosted_space else "demo"
return cls(
text_backend=os.getenv("CF_TEXT_BACKEND", default_text_backend),
image_backend=os.getenv("CF_IMAGE_BACKEND", default_image_backend),
music_backend=os.getenv("CF_MUSIC_BACKEND", "none"),
hf_text_model=os.getenv(
"CF_HF_TEXT_MODEL",
"openbmb/MiniCPM4.1-8B",
),
transformers_text_model=os.getenv(
"CF_TRANSFORMERS_TEXT_MODEL",
"openbmb/MiniCPM4.1-8B",
),
hf_image_model=os.getenv(
"CF_HF_IMAGE_MODEL",
"black-forest-labs/FLUX.1-schnell",
),
llama_base_url=os.getenv("CF_LLAMA_BASE_URL", "http://127.0.0.1:8080"),
llama_model=os.getenv(
"CF_LLAMA_MODEL",
"compliment-forest-minicpm5-1b",
),
flux_model_id=os.getenv(
"CF_FLUX_MODEL_ID",
"black-forest-labs/FLUX.1-dev",
),
flux_lora_id=os.getenv(
"CF_FLUX_LORA_ID",
"build-small-hackathon/compliment-forest-flux-lora",
),
modal_text_endpoint=os.getenv("CF_MODAL_TEXT_ENDPOINT"),
modal_image_endpoint=os.getenv("CF_MODAL_IMAGE_ENDPOINT"),
modal_music_endpoint=os.getenv("CF_MODAL_MUSIC_ENDPOINT"),
modal_signing_key=(os.getenv("CF_MODAL_SIGNING_KEY") or os.getenv("HF_TOKEN")),
upstream_space_url=os.getenv("CF_UPSTREAM_SPACE_URL"),
local_files_only=os.getenv("CF_LOCAL_FILES_ONLY", "0") == "1",
default_seed=int(os.getenv("CF_DEFAULT_SEED", "3407")),
default_style=os.getenv("CF_DEFAULT_STYLE", "surprise"),
trace_path=Path(trace_path) if trace_path else None,
)