95d2ae1d58
One shared TokenBucket (default 8 MB/s; BANDWIDTH_LIMIT_MB_S, 0 = unlimited; live MB/s dial + net readout in the control UI) is charged by every still download (streamed chunk reads) and every ffmpeg video stream (metered from outside via /proc/<pid>/io and SIGSTOP/SIGCONTed into budget). Why: D1 re-measurement 2026-07-02 — the idle link moves ~38 MB/s, but 8 unthrottled downloaders bufferbloated it to ~1-1.5 MB/s PER STREAM (operator's browser included). Capping the aggregate keeps the desktop usable and still beats the collapsed sweep throughput it replaces. Agent build 2026-07-02.4. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
91 lines
5.3 KiB
Python
91 lines
5.3 KiB
Python
"""Agent config, all from env (the control container is configured at run)."""
|
|
# Lazy annotations so the `from_env(cls) -> Config` self-reference is a string,
|
|
# not evaluated at class-definition time — otherwise it NameErrors on the agent's
|
|
# Python 3.10 (CI lints on 3.14, where PEP 649 hides this).
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from dataclasses import dataclass
|
|
|
|
|
|
def _bool_env(name: str, default: str = "") -> bool:
|
|
"""A boolean env var — present + truthy ('1'/'true'/'yes') → True."""
|
|
return os.environ.get(name, default).lower() in ("1", "true", "yes")
|
|
|
|
|
|
@dataclass
|
|
class Config:
|
|
fc_url: str # base URL of the FabledCurator web service
|
|
token: str # the bearer token from Settings → Tagging → GPU agent
|
|
agent_id: str # identifies this agent's leases
|
|
batch_size: int # jobs a worker leases per round
|
|
concurrency: int # INITIAL parallel workers (tunable live from the UI)
|
|
ccip_model: str # imgutils CCIP model name ("" → imgutils default)
|
|
detector_level: str # imgutils person-detector level: n|s|m|x
|
|
poll_idle_seconds: float # wait between empty leases
|
|
embed_dtype: str # torch dtype for the crop embedder: float16|float32
|
|
embed_model_override: str # force a SigLIP-family model ("" → use the one
|
|
# the server announces in the lease)
|
|
auto_start: bool # start the worker pool on boot (so a container restart
|
|
# resumes processing without anyone clicking Start)
|
|
auto_scale: bool # autoscale the worker count (throughput hill-climb)
|
|
# Crop PROPOSERS (extra YOLO detectors that say where to crop). Each weight
|
|
# spec is an ultralytics name | http(s) URL | "hf_repo::file" ("" = off).
|
|
person_weights: str # general COCO person detector (Western/realistic figs)
|
|
person_conf: float
|
|
anatomy_weights: str # booru_yolo anime/furry/NSFW components
|
|
anatomy_conf: float
|
|
panel_weights: str # comic-panel detector
|
|
panel_conf: float
|
|
max_components: int # cap anatomy component crops per frame
|
|
max_panels: int # cap panel crops per frame
|
|
max_figures: int # cap figure boxes per frame (each = a CCIP call + crop)
|
|
max_regions: int # hard cap on total regions per JOB (submit-size backstop)
|
|
dedupe_iou: float # crops overlapping >= this (same kind) are near-dupes,
|
|
# dropped before the embed; >=1.0 disables it
|
|
frame_dedupe_distance: int # video frames whose dHash differs by < this many
|
|
# bits are near-duplicates, dropped before detect;
|
|
# higher keeps more frames, 0 disables
|
|
ffmpeg_timeout: float # hard ceiling (s) for ffmpeg-from-URL video sampling;
|
|
# generous so a SLOW media link still completes
|
|
bandwidth_limit_mb_s: float # aggregate download cap in MEGABYTES/s across
|
|
# all downloaders + video streams (0 = unlimited);
|
|
# tunable live from the agent UI
|
|
|
|
@classmethod
|
|
def from_env(cls) -> Config:
|
|
return cls(
|
|
fc_url=os.environ.get("FC_URL", "http://localhost:8000").rstrip("/"),
|
|
token=os.environ.get("FC_TOKEN", ""),
|
|
agent_id=os.environ.get("AGENT_ID", "desktop-agent"),
|
|
batch_size=int(os.environ.get("BATCH_SIZE", "4")),
|
|
concurrency=int(os.environ.get("CONCURRENCY", "1")),
|
|
ccip_model=os.environ.get("CCIP_MODEL", ""),
|
|
detector_level=os.environ.get("DETECTOR_LEVEL", "m"),
|
|
poll_idle_seconds=float(os.environ.get("POLL_IDLE_SECONDS", "10")),
|
|
embed_dtype=os.environ.get("SIGLIP_DTYPE", "float16"),
|
|
embed_model_override=os.environ.get("EMBED_MODEL_NAME", ""),
|
|
auto_start=_bool_env("AUTO_START"),
|
|
auto_scale=_bool_env("AUTO_SCALE", "true"),
|
|
person_weights=os.environ.get("PERSON_WEIGHTS", "yolo11n.pt"),
|
|
person_conf=float(os.environ.get("PERSON_CONF", "0.35")),
|
|
anatomy_weights=os.environ.get("ANATOMY_WEIGHTS", ""),
|
|
anatomy_conf=float(os.environ.get("ANATOMY_CONF", "0.30")),
|
|
panel_weights=os.environ.get("PANEL_WEIGHTS", ""),
|
|
panel_conf=float(os.environ.get("PANEL_CONF", "0.30")),
|
|
max_components=int(os.environ.get("MAX_COMPONENTS", "8")),
|
|
max_panels=int(os.environ.get("MAX_PANELS", "8")),
|
|
max_figures=int(os.environ.get("MAX_FIGURES", "8")),
|
|
max_regions=int(os.environ.get("MAX_REGIONS", "128")),
|
|
dedupe_iou=float(os.environ.get("DEDUPE_IOU", "0.85")),
|
|
frame_dedupe_distance=int(os.environ.get("FRAME_DEDUPE_DISTANCE", "8")),
|
|
ffmpeg_timeout=float(os.environ.get("FFMPEG_TIMEOUT", "1200")),
|
|
# Default 8 MB/s (~64 Mbit/s): ~20% of the measured ~300 Mbit/s home
|
|
# WiFi, so browsing stays snappy while the agent works — yet MORE
|
|
# sweep throughput than the self-inflicted congestion collapse this
|
|
# replaces (2026-07-02: 8 unthrottled downloaders bufferbloated the
|
|
# link to ~1-1.5 MB/s per stream, browser included). Raise it (or 0)
|
|
# from the agent UI on wired/faster networks.
|
|
bandwidth_limit_mb_s=float(os.environ.get("BANDWIDTH_LIMIT_MB_S", "8")),
|
|
)
|