comfyui: add native ComfyUI provider for image, video, and music generation

Adds three new BaseTool providers that delegate GPU work to a running
ComfyUI server via its REST API.  This avoids the need to install
PyTorch/diffusers directly, which is critical on hardware where the
ecosystem hasn't caught up (e.g. NVIDIA Blackwell / DGX Spark, aarch64
+ CUDA 13.0).

New files:
- tools/_comfyui/client.py — shared REST client (submit/poll/download)
- tools/_comfyui/workflows/ — 4 bundled workflow templates
- tools/graphics/comfyui_image.py — FLUX 2 Dev NVFP4 text-to-image
- tools/video/comfyui_video.py — WAN 2.2 14B t2v + i2v (4-step LightX2V)
- tools/audio/comfyui_music.py — ACE-Step 3.5B music generation
- tests/contracts/test_comfyui_tools.py — 41 contract tests
- docs/comfyui-adapter-plan.md — design document

Zero changes to existing tools, selectors, registry, or pipelines.
Tools are auto-discovered and selectors pick them up via capability match.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
martimramos
2026-04-16 23:59:33 +01:00
committed by Alastair Beal
parent 80e51fd618
commit 6ec2bbb090
11 changed files with 1793 additions and 0 deletions
+192
View File
@@ -0,0 +1,192 @@
"""ComfyUI music generation via ACE-Step model.
Generates background music and songs locally using the ACE-Step 3.5B
model running inside a ComfyUI server. Custom workflows are accepted
via the ``workflow_json`` input.
"""
from __future__ import annotations
import json
import time
from pathlib import Path
from typing import Any
from tools.base_tool import (
BaseTool,
Determinism,
ExecutionMode,
ResourceProfile,
RetryPolicy,
ToolResult,
ToolRuntime,
ToolStability,
ToolStatus,
ToolTier,
)
from tools._comfyui.client import ComfyUIClient, ComfyUIError
_WORKFLOWS = Path(__file__).resolve().parent.parent / "_comfyui" / "workflows"
_OUTPUT_NODE = "3"
class ComfyUIMusic(BaseTool):
name = "comfyui_music"
version = "0.1.0"
tier = ToolTier.GENERATE
capability = "music_generation"
provider = "comfyui"
stability = ToolStability.EXPERIMENTAL
execution_mode = ExecutionMode.SYNC
determinism = Determinism.SEEDED
runtime = ToolRuntime.LOCAL_GPU
dependencies = []
install_instructions = (
"Start a ComfyUI server and set COMFYUI_SERVER_URL "
"(default http://localhost:8188).\n"
"Requires ACE-Step model (ace_step_v1_3.5b.safetensors) in "
"ComfyUI's checkpoints directory and the ACE-Step custom node installed."
)
agent_skills = ["music"]
capabilities = [
"generate_background_music",
"generate_instrumental",
"generate_song",
"text_to_music",
]
supports = {
"seed": True,
"duration_control": True,
"lyrics": True,
"custom_workflow": True,
"offline": True,
}
best_for = [
"local music generation without API costs",
"background music and instrumentals for video production",
"song generation with lyrics",
]
not_good_for = [
"setups without a running ComfyUI server",
"highest quality commercial music (use Suno or ElevenLabs)",
]
fallback = "suno_music"
fallback_tools = ["suno_music", "elevenlabs_music", "freesound_music"]
input_schema = {
"type": "object",
"required": ["prompt"],
"properties": {
"prompt": {
"type": "string",
"description": "Music style / mood description (e.g. 'upbeat corporate background music')",
},
"lyrics": {
"type": "string",
"default": "",
"description": "Optional lyrics for song generation",
},
"duration": {
"type": "number",
"default": 30.0,
"description": "Duration in seconds",
},
"steps": {"type": "integer", "default": 60},
"cfg": {"type": "number", "default": 3.0},
"seed": {"type": "integer", "description": "Random if omitted"},
"output_path": {"type": "string", "description": "Where to save the audio"},
"workflow_json": {
"type": "string",
"description": "Optional full ComfyUI workflow JSON (overrides default)",
},
},
}
resource_profile = ResourceProfile(
cpu_cores=2, ram_mb=8000, vram_mb=6000, disk_mb=500, network_required=False,
)
retry_policy = RetryPolicy(max_retries=1, retryable_errors=["timeout"])
idempotency_key_fields = ["prompt", "lyrics", "duration", "steps", "seed"]
side_effects = ["writes audio file to output_path"]
user_visible_verification = ["Listen to generated audio for quality and mood match"]
def __init__(self) -> None:
self._client = ComfyUIClient()
def get_status(self) -> ToolStatus:
if self._client.is_available():
return ToolStatus.AVAILABLE
return ToolStatus.UNAVAILABLE
def estimate_cost(self, inputs: dict[str, Any]) -> float:
return 0.0
def estimate_runtime(self, inputs: dict[str, Any]) -> float:
duration = inputs.get("duration", 30.0)
return duration * 2.0 # rough: ~2x realtime
def execute(self, inputs: dict[str, Any]) -> ToolResult:
if not self._client.is_available():
return ToolResult(
success=False,
error="ComfyUI server not reachable. " + self.install_instructions,
)
start = time.time()
seed = inputs.get("seed") or ComfyUIClient.random_seed()
duration = inputs.get("duration", 30.0)
output_path = Path(
inputs.get("output_path", f"comfyui_music_{seed}.wav")
)
try:
if inputs.get("workflow_json"):
workflow = json.loads(inputs["workflow_json"])
else:
workflow = ComfyUIClient.load_workflow(
_WORKFLOWS / "ace-step-music.json"
)
workflow = ComfyUIClient.patch_workflow(workflow, {
"2": {
"prompt": inputs["prompt"],
"lyrics": inputs.get("lyrics", ""),
"duration": duration,
"seed": seed,
"steps": inputs.get("steps", 60),
"cfg": inputs.get("cfg", 3.0),
},
"3": {"filename_prefix": output_path.stem},
})
paths = self._client.generate(
workflow,
output_node=_OUTPUT_NODE,
dest=output_path,
timeout=int(duration * 4), # generous timeout
)
except ComfyUIError as exc:
return ToolResult(success=False, error=str(exc))
except Exception as exc:
return ToolResult(success=False, error=f"ComfyUI music generation failed: {exc}")
return ToolResult(
success=True,
data={
"provider": "comfyui",
"model": "ace-step-v1-3.5b",
"prompt": inputs["prompt"],
"lyrics": inputs.get("lyrics", ""),
"duration": duration,
"output": str(paths[0]),
"format": output_path.suffix.lstrip("."),
},
artifacts=[str(p) for p in paths],
cost_usd=0.0,
duration_seconds=round(time.time() - start, 2),
seed=seed,
model="ace-step-v1-3.5b",
)