메인 콘텐츠로 건너뛰기

개요

ComfyUI V3 스키마는 노드를 정의하는 방식을 보다 체계적으로 개선했으며, 향후 노드 기능 확장은 오직 V3 스키마에만 추가됩니다. 이 가이드를 사용해 기존 V1 노드를 새 V3 스키마로 마이그레이션할 수 있습니다.

핵심 개념

V3 스키마는 새로운 버전화된 Comfy API에 유지되며, 이는 스키마의 향후 수정사항이 하위 호환성을 보장한다는 것을 의미합니다. comfy_api.latest는 여전히 개발 중인 최신 번호의 API를 가리키며, 최신 버전 바로 전 버전이 ‘안정적’이라고 볼 수 있습니다. 현재(첫 번째) API 버전은 v0_0_2이며, 이 버전에는 경고 없이 더 많은 변경이 이루어질 것입니다. 안정적인 버전으로 간주되면, latest가 가리키는 버전이 새롭게 v0_0_3으로 변경됩니다.
# 최신 ComfyUI API 사용
from comfy_api.latest import ComfyExtension, io, ui

# 특정 버전의 ComfyUI API 사용
from comfy_api.v0_0_2 import ComfyExtension, io, ui

V1 vs V3 아키텍처

V3 스키마에서 가장 큰 변화는 다음과 같습니다:
  • 입력과 출력이 사전 대신 객체로 정의됨.
  • 실행 방식이 ‘execute’라는 이름으로 고정되고 클래스 메서드임.
  • def comfy_entrypoint() 함수는 ComfyExtension 객체를 반환하며, 노출되는 노드를 NODE_CLASS_MAPPINGS/NODE_DISPLAY_NAME_MAPPINGS 대신 정의함.
  • 노드 객체는 ‘상태’를 노출하지 않음 - def __init__(self)는 노드의 함수에서 노출되는 내용에 아무런 영향을 미치지 않으며, 모든 함수는 클래스 메서드임. 또한 노드 클래스는 실행 전에 정제됨.

V1 (레거시)

class MyNode:
    @classmethod
    def INPUT_TYPES(s):
        return {"required": {...}}

    RETURN_TYPES = ("IMAGE",)
    FUNCTION = "execute"
    CATEGORY = "my_category"

    def execute(self, ...):
        return (result,)

NODE_CLASS_MAPPINGS = {"MyNode": MyNode}

V3 (현대)

from comfy_api.latest import ComfyExtension, io

class MyNode(io.ComfyNode):
    @classmethod
    def define_schema(cls) -> io.Schema:
        return io.Schema(
            node_id="MyNode",
            display_name="My Node",
            category="my_category",
            inputs=[...],
            outputs=[...]
        )

    @classmethod
    def execute(cls, ...) -> io.NodeOutput:
        return io.NodeOutput(result)

class MyExtension(ComfyExtension):
    async def get_node_list(self) -> list[type[io.ComfyNode]]:
        return [MyNode]

async def comfy_entrypoint() -> ComfyExtension:
    return MyExtension()

마이그레이션 단계

대부분의 경우 V1에서 V3으로의 이동은 간단하며, 단순한 문법 변경에 불과합니다.

단계 1: 기본 클래스 변경

모든 V3 스키마 노드는 ComfyNode를 상속해야 합니다. 여러 계층의 상속도 괜찮으며, 체인의 맨 위에 ComfyNode 부모가 있으면 됩니다. V1:
class Example:
    def __init__(self):
        pass
V3:
from comfy_api.latest import io

class Example(io.ComfyNode):
    # __init__ 필요 없음

단계 2: INPUT_TYPES를 define_schema로 변환

노드 ID, 표시 이름, 카테고리 등 코드 내 다양한 위치에서 할당되었던 노드 속성들은 이제 Schema 클래스를 통해 함께 관리됩니다. define_schema(cls) 함수는 V1에서 INPUT_TYPES(s)가 작동한 방식과 매우 유사하게 Schema 객체를 반환해야 합니다. 지원되는 핵심 입력/출력 유형은 comfy_api/{version}_io.py에 저장 및 문서화되어 있으며, 기본적으로 io로 네임스페이스가 지정됩니다. 이제 입력/출력이 사전이나 문자열 대신 클래스로 정의되므로, 사용자 정의 유형은 직접 클래스를 정의하거나 ioCustom 헬퍼 함수를 사용하여 지원됩니다. 사용자 정의 유형에 대한 자세한 내용은 아래 별도의 섹션에서 다룹니다. 유형 클래스는 다음과 같은 속성을 가집니다:
  • 입력에 대한 class Input (예: Model.Input(...))
  • 출력에 대한 class Output (예: Model.Output(...)). 모든 유형이 출력을 지원하지는 않습니다.
  • 유형의 타입 힌트를 얻기 위한 Type (예: Model.Type). 일부 타입 힌트는 단순히 any이며, 이는 향후 업데이트될 수 있습니다. 이러한 타입 힌트는 강제되지 않으며 유용한 문서 역할을 합니다.
V1:
@classmethod
def INPUT_TYPES(s):
    return {
        "required": {
            "image": ("IMAGE",),
            "int_field": ("INT", {
                "default": 0,
                "min": 0,
                "max": 4096,
                "step": 64,
                "display": "number"
            }),
            "string_field": ("STRING", {
                "multiline": False,
                "default": "Hello"
            }),
            # V1 처리: 임의의 유형
            "custom_field": ("MY_CUSTOM_TYPE",),
        },
        "optional": {
            "mask": ("MASK",)
        }
    }
V3:
@classmethod
def define_schema(cls) -> io.Schema:
    return io.Schema(
        node_id="Example",
        display_name="Example Node",
        category="examples",
        description="Node description here",
        inputs=[
            io.Image.Input("image"),
            io.Int.Input("int_field",
                default=0,
                min=0,
                max=4096,
                step=64,
                display_mode=io.NumberDisplay.number
            ),
            io.String.Input("string_field",
                default="Hello",
                multiline=False
            ),
            # V3 처리: 임의의 유형
            io.Custom("my_custom_type").Input("custom_input"),
            io.Mask.Input("mask", optional=True)
        ],
        outputs=[
            io.Image.Output()
        ]
    )

단계 3: Execute 메서드 업데이트

V3의 모든 실행 함수는 execute라는 이름을 가지며 클래스 메서드입니다. V1:
def test(self, image, string_field, int_field):
    # Process
    image = 1.0 - image
    return (image,)
V3:
@classmethod
def execute(cls, image, string_field, int_field) -> io.NodeOutput:
    # Process
    image = 1.0 - image

    # 선택적 UI 미리보기와 함께 반환
    return io.NodeOutput(image, ui=ui.PreviewImage(image, cls=cls))

단계 4: 노드 속성 변환

다음은 속성 이름의 몇 가지 예시입니다. 자세한 내용은 comfy_api.latest._io의 소스 코드를 참조하세요.
V1 속성V3 스키마 필드비고
RETURN_TYPES스키마의 outputs출력 객체 목록
RETURN_NAMES출력의 display_name출력별 표시 이름
FUNCTION항상 execute메서드 이름이 표준화됨
CATEGORY스키마의 category문자열 값
OUTPUT_NODE스키마의 is_output_node불리언 플래그
DEPRECATED스키마의 is_deprecated불리언 플래그
EXPERIMENTAL스키마의 is_experimental불리언 플래그

단계 5: 특수 메서드 처리

V1과 동일한 특수 메서드가 지원되지만, 더 명확하게 하기 위해 소문자화되거나 완전히 이름이 변경되었습니다. 사용법은 동일합니다.

검증 (V1 → V3)

입력 검증 함수는 validate_inputs로 이름이 변경되었습니다. V1:
@classmethod
def VALIDATE_INPUTS(s, **kwargs):
    # 검증 로직
    return True
V3:
@classmethod
def validate_inputs(cls, **kwargs) -> bool | str:
    # 유효하면 True, 아니면 오류 메시지 반환
    if error_condition:
        return "Error message"
    return True

지연 평가 (V1 → V3)

check_lazy_status 함수는 클래스 메서드이며, 그 외에는 동일하게 유지됩니다. V1:
def check_lazy_status(self, image, string_field, ...):
    if condition:
        return ["string_field"]
    return []
V3:
@classmethod
def check_lazy_status(cls, image, string_field, ...):
    if condition:
        return ["string_field"]
    return []

캐시 제어 (V1 → V3)

캐시 제어의 기능은 V1과 동일하게 유지되지만, 원래 이름은 작동 방식을 매우 오해하기 쉽게 만들었습니다. V1의 IS_CHANGED 함수는 반환값이 노드가 마지막으로 실행되었을 때와 동일한 경우 노드 재실행을 트리거하지 않도록 신호를 보냅니다. 따라서 IS_CHANGED 함수는 fingerprint_inputs로 이름이 변경되었습니다. 개발자들이 저지르는 가장 흔한 실수 중 하나는 True를 반환하면 노드가 항상 재실행된다고 생각하는 것이었습니다. True가 항상 반환되면, 실제로는 노드를 한 번만 실행하고 캐시된 값을 재사용하는 반대 효과가 발생합니다. 이 함수의 사용 예시는 LoadImage 노드입니다. 이 노드는 선택된 파일의 해시를 반환하여, 파일이 변경되면 노드가 강제로 재실행되도록 합니다. V1:
@classmethod
def IS_CHANGED(s, **kwargs):
    return "unique_value"
V3:
@classmethod
def fingerprint_inputs(cls, **kwargs):
    return "unique_value"

단계 6: 확장 프로그램 및 진입점 생성

노드 ID를 노드 클래스/표시 이름에 연결하는 사전을 정의하는 대신, 이제 ComfyExtension 클래스와 정의해야 할 comfy_entrypoint 함수가 있습니다. 향후 get_node_list를 통해 노드만 등록하는 것 이상의 기능을 등록하기 위해 ComfyExtension에 더 많은 함수가 추가될 수 있습니다. comfy_entrypoint는 비동기이거나 아니어도 되지만, get_node_list는 반드시 비동기로 정의해야 합니다. V1:
NODE_CLASS_MAPPINGS = {
    "Example": Example
}

NODE_DISPLAY_NAME_MAPPINGS = {
    "Example": "Example Node"
}
V3:
from comfy_api.latest import ComfyExtension

class MyExtension(ComfyExtension):
    # 반드시 비동기로 선언해야 함
    async def get_node_list(self) -> list[type[io.ComfyNode]]:
        return [
            Example,
            # 여기에 더 많은 노드 추가
        ]

# 비동기 또는 비동기가 아닌 형태로 선언 가능, 둘 다 작동함
async def comfy_entrypoint() -> MyExtension:
    return MyExtension()

입력 유형 참조

이미 단계 2에서 설명했지만, V1 대비 V3의 몇 가지 유형 참조 비교를 제공합니다. 전체 유형 선언은 comfy_api.latest._io를 참조하세요.

기본 유형

V1 유형V3 유형예시
"INT"io.Int.Input()io.Int.Input("count", default=1, min=0, max=100)
"FLOAT"io.Float.Input()io.Float.Input("strength", default=1.0, min=0.0, max=10.0)
"STRING"io.String.Input()io.String.Input("text", multiline=True)
"BOOLEAN"io.Boolean.Input()io.Boolean.Input("enabled", default=True)

control_after_generate

Int 및 Combo 입력은 각 생성 후 값을 자동으로 변경하기 위한 제어 위젯을 추가하는 control_after_generate 파라미터를 지원합니다. V1에서는 이 값이 단순한 bool이었지만, V3에서는 io.ControlAfterGenerate 열거형을 사용하여 명시적으로 제어할 수 있습니다. True를 전달하는 것은 io.ControlAfterGenerate.randomize와 동일합니다.
동작
io.ControlAfterGenerate.fixed각 생성 후 값이 동일하게 유지됩니다.
io.ControlAfterGenerate.increment각 생성 후 값이 단계만큼 증가합니다.
io.ControlAfterGenerate.decrement각 생성 후 값이 단계만큼 감소합니다.
io.ControlAfterGenerate.randomize각 생성 후 값이 무작위화됩니다.
# 제어 위젯 활성화 (UI에서 사용자가 모드 선택)
io.Int.Input("seed", default=0, min=0, max=0xFFFFFFFFFFFFFFFF, control_after_generate=True)

# 특정 기본 모드 설정
io.Int.Input("seed", default=0, min=0, max=0xFFFFFFFFFFFFFFFF,
    control_after_generate=io.ControlAfterGenerate.randomize)

ComfyUI 유형

V1 유형V3 유형예시
"IMAGE"io.Image.Input()io.Image.Input("image", tooltip="입력 이미지")
"MASK"io.Mask.Input()io.Mask.Input("mask", optional=True)
"LATENT"io.Latent.Input()io.Latent.Input("latent")
"CONDITIONING"io.Conditioning.Input()io.Conditioning.Input("positive")
"MODEL"io.Model.Input()io.Model.Input("model")
"VAE"io.VAE.Input()io.VAE.Input("vae")
"CLIP"io.CLIP.Input()io.CLIP.Input("clip")

Combo (드롭다운/선택 목록)

V3의 Combo 유형은 명시적인 클래스 정의가 필요합니다. V1:
"mode": (["option1", "option2", "option3"],)
V3:
io.Combo.Input("mode", options=["option1", "option2", "option3"])

스키마 참조

Schema 데이터클래스는 V3 노드의 모든 속성을 정의합니다. 사용 가능한 모든 필드에 대한 완전한 참조입니다:
필드유형기본값설명
node_idstr필수전역적으로 고유한 노드 ID. 커스텀 노드는 충돌을 피하기 위해 접두사/접미사를 추가해야 합니다.
display_namestrNoneUI에 표시되는 표시 이름. 설정하지 않으면 node_id로 대체됩니다.
categorystr"sd"”노드 추가” 메뉴의 카테고리 (예: "image/transform").
descriptionstr""노드 위에 마우스를 올리면 표시되는 툴팁.
inputslist[Input][]입력 정의 목록.
outputslist[Output][]출력 정의 목록.
hiddenlist[Hidden][]요청할 히든 입력 목록 (참조: 히든 입력).
search_aliaseslist[str][]검색을 위한 대체 이름. 동의어나 이름 변경 후의 이전 이름에 유용합니다.
is_output_nodeboolFalse노드를 출력 노드로 표시하여, 해당 노드와 그 의존성이 실행되도록 합니다.
is_input_listboolFalseTrue이면 모든 입력이 전달된 항목 수에 관계없이 list[type]이 됩니다.
is_deprecatedboolFalse노드를 지원 중단됨으로 표시하여, 사용자에게 대안을 찾도록 알립니다.
is_experimentalboolFalse노드를 실험적으로 표시하여, 사용자에게 변경될 수 있음을 경고합니다.
is_dev_onlyboolFalse개발자 모드가 활성화되지 않은 경우 검색/메뉴에서 노드를 숨깁니다.
is_api_nodeboolFalseComfy API 서비스를 위한 API 노드로 표시합니다.
not_idempotentboolFalseTrue이면, 그래프의 다른 동일한 노드에서 생성된 캐시 출력을 재사용하지 않고 항상 다시 실행합니다.
enable_expandboolFalseNodeOutput이 노드 확장을 위한 expand 속성을 포함할 수 있도록 허용합니다.
accept_all_inputsboolFalseTrue이면, 스키마에 정의되지 않은 입력이라도 프롬프트의 모든 입력이 kwargs로 전달됩니다.

공통 입력 파라미터

모든 입력 유형은 다음과 같은 기본 파라미터를 공유합니다:
파라미터유형기본값설명
idstr필수입력의 고유 식별자, execute에서 kwarg 이름으로 사용됩니다.
display_namestrNoneUI에 표시되는 레이블. 기본값은 id입니다.
optionalboolFalse입력이 선택 사항인지 여부.
tooltipstrNone마우스 오버 툴팁 텍스트.
lazyboolNone지연 평가를 위한 입력을 표시합니다 (참조: 지연 평가).
raw_linkboolNoneTrue이면, 해석된 값 대신 원시 링크 정보를 전달합니다.
advancedboolNoneTrue이면, 입력은 UI의 “고급” 토글 뒤에 숨겨집니다.
위젯 입력(Int, Float, String, Boolean, Combo)은 추가로 다음을 지원합니다:
파라미터유형기본값설명
default다양함None위젯의 기본값.
socketlessboolNoneTrue이면 입력 소켓을 숨깁니다 (위젯만, 들어오는 연결 없음).
force_inputboolNoneTrue이면, 대신 위젯이 소켓 입력으로 표시되도록 강제합니다.

고급 기능

히든 입력

히든 입력은 프롬프트 메타데이터, 노드 ID 및 기타 내부 값과 같은 실행 컨텍스트에 대한 접근을 제공합니다. 이들은 UI에 표시되지 않습니다. V1에서는 히든 입력이 INPUT_TYPES"hidden" 키로 선언되었습니다. V3에서는 스키마의 hidden 파라미터를 통해 선언되며, 해당 값은 cls.hidden을 통해 접근됩니다. V1:
@classmethod
def INPUT_TYPES(s):
    return {
        "required": {...},
        "hidden": {
            "unique_id": "UNIQUE_ID",
            "prompt": "PROMPT",
            "extra_pnginfo": "EXTRA_PNGINFO",
        }
    }

def execute(self, unique_id, prompt, extra_pnginfo, ...):
    # 히든 값은 일반 인자로 전달됨
    print(unique_id)
V3:
@classmethod
def define_schema(cls) -> io.Schema:
    return io.Schema(
        node_id="MyNode",
        inputs=[...],
        hidden=[io.Hidden.unique_id, io.Hidden.prompt, io.Hidden.extra_pnginfo],
    )

@classmethod
def execute(cls, ...) -> io.NodeOutput:
    # 히든 값은 cls.hidden을 통해 접근
    print(cls.hidden.unique_id)
    print(cls.hidden.prompt)
    print(cls.hidden.extra_pnginfo)
사용 가능한 히든 값:
히든 열거형설명
io.Hidden.unique_id클라이언트 측 ID와 일치하는 노드의 고유 식별자.
io.Hidden.prompt클라이언트가 보낸 전체 프롬프트.
io.Hidden.extra_pnginfo저장된 .png 파일의 메타데이터에 복사되는 사전.
io.Hidden.dynprompt실행 중에 변경될 수 있는 DynamicPrompt 인스턴스.
io.Hidden.auth_token_comfy_org프론트엔드에서 ComfyOrg 계정에 로그인하여 획득한 토큰.
io.Hidden.api_key_comfy_orgComfyOrg에서 생성한 API 키로, 프론트엔드 로그인을 건너뛸 수 있게 해줍니다.
일부 히든 값은 스키마 플래그에 따라 자동으로 추가됩니다. 출력 노드(is_output_node=True)는 자동으로 promptextra_pnginfo를 받습니다. API 노드(is_api_node=True)는 자동으로 인증 토큰을 받습니다.

UI 헬퍼

V3는 ui 모듈에서 미리보기 및 파일 저장과 같은 일반적인 패턴을 처리하기 위한 내장 UI 헬퍼를 제공합니다. ui 파라미터를 통해 io.NodeOutput에 전달하세요.

미리보기 헬퍼

미리보기 헬퍼는 임시 파일을 저장하고 노드 내 표시를 위한 UI 데이터를 반환합니다.
from comfy_api.latest import ui

# 노드에서 이미지 미리보기
return io.NodeOutput(images, ui=ui.PreviewImage(images, cls=cls))

# 마스크 미리보기 (표시를 위해 자동으로 3채널로 변환)
return io.NodeOutput(mask, ui=ui.PreviewMask(mask, cls=cls))

# 오디오 미리듣기
return io.NodeOutput(audio, ui=ui.PreviewAudio(audio, cls=cls))

# 텍스트 미리보기
return io.NodeOutput(ui=ui.PreviewText("Some text value"))

# 3D 모델 미리보기
return io.NodeOutput(ui=ui.PreviewUI3D(model_file, camera_info))

저장 헬퍼

저장 헬퍼는 적절한 메타데이터를 포함하여 파일을 출력 디렉토리에 저장하는 메서드를 제공합니다. 일반적으로 출력 노드에서 사용됩니다.
from comfy_api.latest import ui, io

# 이미지 저장 및 UI 데이터 반환 (가장 일반적인 패턴)
return io.NodeOutput(
    ui=ui.ImageSaveHelper.get_save_images_ui(
        images=images,
        filename_prefix=filename_prefix,
        cls=cls,  # 메타데이터를 위한 히든 prompt/extra_pnginfo