> ## Documentation Index
> Fetch the complete documentation index at: https://dripart-docs-recommend-assets-api.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# V3 迁移指南

> 如何将现有的 V1 节点迁移到新的 V3 架构。

## 概述

ComfyUI V3 架构引入了一种更有序的节点定义方式，今后的节点功能扩展只会在 V3 架构中进行。本指南将帮助你将现有的 V1 节点迁移到新的 V3 架构。

## 核心概念

V3 架构基于新的版本化 Comfy API，这意味着未来的架构更新都将向后兼容。`comfy_api.latest` 指向正在开发中的最新版本 API，而 latest 之前的版本可以认为是“稳定版”。目前版本 `v0_0_2` 是第一个 API 版本，之后可能还会有不兼容的更改。当它稳定后，会创建新的 `v0_0_3` 版本供 `latest` 指向。

```python theme={null}
# 使用最新的 ComfyUI API
from comfy_api.latest import ComfyExtension, io, ui

# 使用特定版本的 ComfyUI API
from comfy_api.v0_0_2 import ComfyExtension, io, ui
```

### V1 与 V3 架构

V3 架构的主要变化包括：

* 输入和输出使用对象定义，而不是字典。
* 执行方法统一命名为 'execute'，并且是类方法。
* 使用 `def comfy_entrypoint()` 函数返回 ComfyExtension 对象来定义节点，取代 NODE\_CLASS\_MAPPINGS/NODE\_DISPLAY\_NAME\_MAPPINGS
* 节点对象不保存状态 - `def __init__(self)` 不会影响节点函数的暴露内容，因为所有方法都是类方法。节点类在执行前也会被清理。

#### V1 (旧版)

```python theme={null}
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 (现代版)

```python theme={null}
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:**

```python theme={null}
class Example:
    def __init__(self):
        pass
```

**V3:**

```python theme={null}
from comfy_api.latest import io

class Example(io.ComfyNode):
    # 不需要 __init__
```

### 步骤 2: 将 INPUT\_TYPES 转换为 define\_schema

原来分散在代码不同位置（如字典和类属性）的节点属性（节点 ID、显示名称、类别等）现在都通过 `Schema` 类统一管理。

`define_schema(cls)` 函数需要返回一个 `Schema` 对象，工作方式与 V1 中的 INPUT\_TYPES(s) 类似。

支持的核心输入/输出类型存储在 `comfy_api/{version}` 的 `_io.py` 文件中，默认以 `io` 作为命名空间。由于输入/输出现在由类定义而不是字典或字符串，自定义类型可以通过编写自己的类或使用 `io` 中的 `Custom` 辅助函数来实现。

自定义类型在下面的章节中有详细说明。

类型类包含以下属性：

* `class Input` 用于定义输入（如 `Model.Input(...)`）
* `class Output` 用于定义输出（如 `Model.Output(...)`）。注意，不是所有类型都支持作为输出。
* `Type` 用于获取类型提示（如 `Model.Type`）。有些类型提示可能只是 `any`，未来会进一步完善。这些类型提示不会被强制执行，仅作为文档参考。

**V1:**

```python theme={null}
@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:**

```python theme={null}
@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: 更新执行方法

V3 中所有的执行函数都命名为 `execute` 并且必须是类方法。

**V1:**

```python theme={null}
def test(self, image, string_field, int_field):
    # Process
    image = 1.0 - image
    return (image,)
```

**V3:**

```python theme={null}
@classmethod
def execute(cls, image, string_field, int_field) -> io.NodeOutput:
    # Process
    image = 1.0 - image

    # Return with optional UI preview
    return io.NodeOutput(image, ui=ui.PreviewImage(image, cls=cls))
```

### 步骤 4: 转换节点属性

以下是一些属性名称的对照表，更多详细信息请查看 `comfy_api.latest._io` 中的源代码。

| V1 属性          | V3 规范字段                     | 备注        |
| -------------- | --------------------------- | --------- |
| `RETURN_TYPES` | Schema 中的 `outputs`         | 输出对象列表    |
| `RETURN_NAMES` | 输出中的 `display_name`         | 每个输出的显示名称 |
| `FUNCTION`     | 始终为 `execute`               | 方法名称标准化   |
| `CATEGORY`     | Schema 中的 `category`        | 字符串值      |
| `OUTPUT_NODE`  | Schema 中的 `is_output_node`  | 布尔标志      |
| `DEPRECATED`   | Schema 中的 `is_deprecated`   | 布尔标志      |
| `EXPERIMENTAL` | Schema 中的 `is_experimental` | 布尔标志      |

### 步骤 5: 处理特殊方法

V3 支持与 V1 相同的特殊方法，但方法名改为小写或重新命名以更加清晰。使用方式保持不变。

#### 验证 (V1 → V3)

输入验证函数重命名为 `validate_inputs`。

**V1:**

```python theme={null}
@classmethod
def VALIDATE_INPUTS(s, **kwargs):
    # Validation logic
    return True
```

**V3:**

```python theme={null}
@classmethod
def validate_inputs(cls, **kwargs) -> bool | str:
    # Return True if valid, error string if not
    if error_condition:
        return "Error message"
    return True
```

#### 惰性求值 (V1 → V3)

`check_lazy_status` 函数改为类方法，其他部分保持不变。

**V1:**

```python theme={null}
def check_lazy_status(self, image, string_field, ...):
    if condition:
        return ["string_field"]
    return []
```

**V3:**

```python theme={null}
@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:**

```python theme={null}
@classmethod
def IS_CHANGED(s, **kwargs):
    return "unique_value"
```

**V3:**

```python theme={null}
@classmethod
def fingerprint_inputs(cls, **kwargs):
    return "unique_value"
```

### 步骤 6: 创建扩展和入口点

不再使用字典来映射节点 ID 到节点类/显示名称，现在需要定义 `ComfyExtension` 类和 `comfy_entrypoint` 函数。

将来可能会在 ComfyExtension 中添加更多函数，通过 `get_node_list` 注册节点以外的其他内容。

`comfy_entrypoint` 可以是同步或异步函数，但 `get_node_list` 必须声明为异步。

**V1:**

```python theme={null}
NODE_CLASS_MAPPINGS = {
    "Example": Example
}

NODE_DISPLAY_NAME_MAPPINGS = {
    "Example": "Example Node"
}
```

**V3:**

```python theme={null}
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` | 每次生成后值随机化。   |

```python theme={null}
# 启用控制小部件（用户在 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="Input image")` |
| `"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")`                          |

### 组合类型（下拉框/选择列表）

V3 中的组合类型需要显式定义。

**V1:**

```python theme={null}
"mode": (["option1", "option2", "option3"],)
```

**V3:**

```python theme={null}
io.Combo.Input("mode", options=["option1", "option2", "option3"])
```

## Schema 参考

`Schema` 数据类定义了 V3 节点的所有属性。以下是所有可用字段的完整参考：

| 字段                  | 类型             | 默认值     | 描述                                                       |
| ------------------- | -------------- | ------- | -------------------------------------------------------- |
| `node_id`           | `str`          | *必需*    | 节点的全局唯一 ID。自定义节点应添加前缀/后缀以避免冲突。                           |
| `display_name`      | `str`          | `None`  | 在 UI 中显示的名称。如果未设置，则回退到 `node_id`。                        |
| `category`          | `str`          | `"sd"`  | 在"添加节点"菜单中的类别（例如 `"image/transform"`）。                   |
| `description`       | `str`          | `""`    | 悬停在节点上时显示的工具提示。                                          |
| `inputs`            | `list[Input]`  | `[]`    | 输入定义列表。                                                  |
| `outputs`           | `list[Output]` | `[]`    | 输出定义列表。                                                  |
| `hidden`            | `list[Hidden]` | `[]`    | 要请求的隐藏输入列表（参见[隐藏输入](#隐藏输入)）。                             |
| `search_aliases`    | `list[str]`    | `[]`    | 搜索的备用名称。适用于同义词或重命名后的旧名称。                                 |
| `is_output_node`    | `bool`         | `False` | 将节点标记为输出节点，使其及其依赖项被执行。                                   |
| `is_input_list`     | `bool`         | `False` | 当为 True 时，无论传入多少项，所有输入都变为 `list[type]`。                  |
| `is_deprecated`     | `bool`         | `False` | 将节点标记为已弃用，提示用户寻找替代方案。                                    |
| `is_experimental`   | `bool`         | `False` | 将节点标记为实验性，警告用户它可能会更改。                                    |
| `is_dev_only`       | `bool`         | `False` | 除非启用开发模式，否则从搜索/菜单中隐藏节点。                                  |
| `is_api_node`       | `bool`         | `False` | 将节点标记为 Comfy API 服务的 API 节点。                             |
| `not_idempotent`    | `bool`         | `False` | 当为 True 时，节点将始终重新运行，永远不会重用图中另一个相同节点的缓存输出。                |
| `enable_expand`     | `bool`         | `False` | 允许 `NodeOutput` 包含用于节点扩展的 `expand` 属性。                   |
| `accept_all_inputs` | `bool`         | `False` | 当为 True 时，来自 prompt 的所有输入都将作为 kwargs 传递，即使未在 schema 中定义。 |

### 通用输入参数

所有输入类型共享这些基本参数：

| 参数             | 类型     | 默认值     | 描述                                 |
| -------------- | ------ | ------- | ---------------------------------- |
| `id`           | `str`  | *必需*    | 输入的唯一标识符，用作 `execute` 中的 kwarg 名称。 |
| `display_name` | `str`  | `None`  | 在 UI 中显示的标签。默认为 `id`。              |
| `optional`     | `bool` | `False` | 输入是否可选。                            |
| `tooltip`      | `str`  | `None`  | 悬停时的工具提示文本。                        |
| `lazy`         | `bool` | `None`  | 标记输入为惰性求值（参见[惰性求值](#惰性求值-v1--v3)）。 |
| `raw_link`     | `bool` | `None`  | 当为 True 时，传递原始链接信息而不是解析后的值。        |
| `advanced`     | `bool` | `None`  | 当为 True 时，输入隐藏在 UI 中的"高级"切换后面。     |

小部件输入（Int、Float、String、Boolean、Combo）还支持：

| 参数            | 类型     | 默认值    | 描述                            |
| ------------- | ------ | ------ | ----------------------------- |
| `default`     | 不定     | `None` | 小部件的默认值。                      |
| `socketless`  | `bool` | `None` | 当为 True 时，隐藏输入插槽（仅小部件，无传入连接）。 |
| `force_input` | `bool` | `None` | 当为 True 时，强制小部件显示为插槽输入。       |

## 高级功能

### 隐藏输入

隐藏输入提供对执行上下文的访问，如 prompt 元数据、节点 ID 和其他内部值。它们在 UI 中不可见。

在 V1 中，隐藏输入在 `INPUT_TYPES` 中声明为 `"hidden"` 键。在 V3 中，它们通过 Schema 上的 `hidden` 参数声明，其值通过 `cls.hidden` 访问。

**V1:**

```python theme={null}
@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:**

```python theme={null}
@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)
```

可用的隐藏值：

| Hidden 枚举                        | 描述                             |
| -------------------------------- | ------------------------------ |
| `io.Hidden.unique_id`            | 节点的唯一标识符，与客户端上的 id 匹配。         |
| `io.Hidden.prompt`               | 客户端发送的完整 prompt。               |
| `io.Hidden.extra_pnginfo`        | 复制到保存的 `.png` 文件元数据中的字典。       |
| `io.Hidden.dynprompt`            | 可能在执行期间变化的 `DynamicPrompt` 实例。 |
| `io.Hidden.auth_token_comfy_org` | 从前端登录 ComfyOrg 账户获取的令牌。        |
| `io.Hidden.api_key_comfy_org`    | ComfyOrg 生成的 API 密钥，允许跳过前端登录。  |

<Note>
  某些隐藏值会根据 Schema 标志自动添加。输出节点（`is_output_node=True`）自动接收 `prompt` 和 `extra_pnginfo`。API 节点（`is_api_node=True`）自动接收认证令牌。
</Note>

### UI 辅助函数

V3 在 `ui` 模块中提供内置的 UI 辅助函数来处理常见模式，如预览和保存文件。通过 `ui` 参数将它们传递给 `io.NodeOutput`。

#### 预览辅助函数

预览辅助函数保存临时文件并返回用于节点内显示的 UI 数据。

```python theme={null}
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))
```

#### 保存辅助函数

保存辅助函数提供将文件保存到输出目录并正确嵌入元数据的方法。它们通常用于输出节点。

```python theme={null}
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 用于元数据
    )
)

# 保存动画 PNG
return io.NodeOutput(
    ui=ui.ImageSaveHelper.get_save_animated_png_ui(
        images=images,
        filename_prefix=filename_prefix,
        cls=cls,
        fps=6.0,
        compress_level=4,
    )
)

# 保存动画 WebP
return io.NodeOutput(
    ui=ui.ImageSaveHelper.get_save_animated_webp_ui(
        images=images,
        filename_prefix=filename_prefix,
        cls=cls,
        fps=6.0,
        lossless=True,
        quality=80,
        method=4,
    )
)

# 保存音频（支持 flac、mp3、opus）
return io.NodeOutput(
    ui=ui.AudioSaveHelper.get_save_audio_ui(
        audio=audio,
        filename_prefix=filename_prefix,
        cls=cls,
        format="flac",
    )
)
```

<Tip>
  将 `cls=cls` 传递给保存/预览辅助函数允许它们自动在保存的文件中嵌入工作流元数据（prompt、extra\_pnginfo）。确保在你的 schema 的 hidden 列表中包含 `io.Hidden.prompt` 和 `io.Hidden.extra_pnginfo`，或设置 `is_output_node=True` 会自动添加它们。
</Tip>

#### 返回原始 UI 字典

如果你需要返回没有辅助函数的 UI 数据，可以直接传递字典：

```python theme={null}
return io.NodeOutput(ui={"images": results})
```

### 输出节点

适用于产生副作用的节点（如保存文件）。与 V1 一样，将节点标记为输出节点后，会在节点的上下文菜单中显示 `run` 播放按钮，允许部分执行流程图。

```python theme={null}
@classmethod
def define_schema(cls) -> io.Schema:
    return io.Schema(
        node_id="SaveNode",
        inputs=[...],
        outputs=[],  # 不需要为空。
        is_output_node=True  # 标记为输出节点
    )
```

### 自定义类型

可以通过编写类或使用 `Custom` 辅助函数来创建自定义输入/输出类型。

```python theme={null}
from comfy_api.latest import io

# 方法 1: 使用装饰器定义类
@io.comfytype(io_type="MY_CUSTOM_TYPE")
class MyCustomType:
    Type = torch.Tensor  # Python 类型注释

    class Input(io.Input):
        def __init__(self, id: str, **kwargs):
            super().__init__(id, **kwargs)

    class Output(io.Output):
        def __init__(self, **kwargs):
            super().__init__(**kwargs)

# 方法 2: 使用 Custom 辅助函数
# 为了方便起见，也可以直接使用辅助函数而不先保存到变量
MyCustomType = io.Custom("MY_CUSTOM_TYPE")
```

### MultiType 输入

`MultiType` 允许一个输入接受多种类型。当节点可以通过同一个输入插槽对不同数据类型进行操作时，这很有用。

如果第一个参数（`id`）是 `Input` 类的实例而不是字符串，则该输入将用于创建具有其覆盖值的小部件。否则，输入仅为插槽。

```python theme={null}
# 仅插槽的多类型输入（无小部件）
io.MultiType.Input("input", types=[io.Image, io.Mask])

# 带有小部件回退的多类型输入（未连接时显示 String 小部件）
io.MultiType.Input(
    io.String.Input("model_file", default="", multiline=False),
    types=[io.File3DGLB, io.File3DGLTF, io.File3DOBJ],
    tooltip="3D 模型文件或路径字符串",
)
```

### MatchType（泛型类型匹配）

`MatchType` 创建类型关联的输入和输出。当用户将特定类型连接到 MatchType 输入时，所有共享相同模板的其他输入和输出会自动约束为该类型。这就是 Switch 和 Create List 等节点处理任意类型的方式。

```python theme={null}
@classmethod
def define_schema(cls):
    # 创建模板 - 所有共享相同模板的输入/输出将匹配类型
    template = io.MatchType.Template("switch")
    return io.Schema(
        node_id="SwitchNode",
        display_name="Switch",
        category="logic",
        inputs=[
            io.Boolean.Input("switch"),
            io.MatchType.Input("on_false", template=template, lazy=True),
            io.MatchType.Input("on_true", template=template, lazy=True),
        ],
        outputs=[
            io.MatchType.Output(template=template, display_name="output"),
        ],
    )
```

你还可以限制允许的类型：

```python theme={null}
# 仅允许 Image、Mask 或 Latent 类型
template = io.MatchType.Template("input_type", allowed_types=[io.Image, io.Mask, io.Latent])
```

### 动态输入

V3 引入了动态输入类型，根据用户交互改变可用的输入。V1 中没有这些功能的等效项。

#### Autogrow

`Autogrow` 创建可变数量的输入，随着用户连接更多输入会自动增长。有两种模板类型：

**TemplatePrefix** 生成带有编号前缀的输入（例如 `image0`、`image1`、`image2`...）：

```python theme={null}
@classmethod
def define_schema(cls):
    autogrow_template = io.Autogrow.TemplatePrefix(
        input=io.Image.Input("image"),  # 每个输入的模板
        prefix="image",                  # 生成的输入名称前缀
        min=2,                           # 显示的最小输入数
        max=50,                          # 允许的最大输入数
    )
    return io.Schema(
        node_id="BatchImagesNode",
        display_name="Batch Images",
        category="image",
        inputs=[io.Autogrow.Input("images", template=autogrow_template)],
        outputs=[io.Image.Output()],
    )

@classmethod
def execute(cls, images: io.Autogrow.Type) -> io.NodeOutput:
    # 'images' 是一个将输入名称映射到其值的字典
    image_list = list(images.values())
    return io.NodeOutput(batch(image_list))
```

**TemplateNames** 生成具有特定名称的输入：

```python theme={null}
template = io.Autogrow.TemplateNames(
    input=io.Float.Input("float"),
    names=["x", "y", "z"],  # 每个输入的显式名称
    min=1,                    # 显示的最小输入数
)
```

Autogrow 可以与 MatchType 结合使用来创建类型匹配输入的列表：

```python theme={null}
@classmethod
def define_schema(cls):
    template_matchtype = io.MatchType.Template("type")
    template_autogrow = io.Autogrow.TemplatePrefix(
        input=io.MatchType.Input("input", template=template_matchtype),
        prefix="input",
    )
    return io.Schema(
        node_id="CreateList",
        display_name="Create List",
        category="logic",
        is_input_list=True,
        inputs=[io.Autogrow.Input("inputs", template=template_autogrow)],
        outputs=[
            io.MatchType.Output(
                template=template_matchtype,
                is_output_list=True,
                display_name="list",
            ),
        ],
    )
```

#### DynamicCombo

`DynamicCombo` 创建一个下拉菜单，根据选择的选项显示/隐藏不同的输入。当不同模式需要不同参数时，这很有用。

```python theme={null}
@classmethod
def define_schema(cls):
    return io.Schema(
        node_id="ResizeNode",
        display_name="Resize",
        category="transform",
        inputs=[
            io.Image.Input("image"),
            io.DynamicCombo.Input("resize_type", options=[
                io.DynamicCombo.Option("scale by dimensions", [
                    io.Int.Input("width", default=512, min=0, max=8192),
                    io.Int.Input("height", default=512, min=0, max=8192),
                ]),
                io.DynamicCombo.Option("scale by multiplier", [
                    io.Float.Input("multiplier", default=1.0, min=0.01, max=8.0),
                ]),
                io.DynamicCombo.Option("scale to megapixels", [
                    io.Float.Input("megapixels", default=1.0, min=0.01, max=16.0),
                ]),
            ]),
        ],
        outputs=[io.Image.Output()],
    )

@classmethod
def execute(cls, image, resize_type: dict) -> io.NodeOutput:
    # resize_type 是一个包含所选选项键及其输入的字典
    selected = resize_type["resize_type"]
    if selected == "scale by dimensions":
        width = resize_type["width"]
        height = resize_type["height"]
        # ...
    elif selected == "scale by multiplier":
        multiplier = resize_type["multiplier"]
        # ...
```

DynamicCombo 选项也可以嵌套：

```python theme={null}
io.DynamicCombo.Input("combo", options=[
    io.DynamicCombo.Option("option1", [io.String.Input("string")]),
    io.DynamicCombo.Option("option2", [
        io.DynamicCombo.Input("subcombo", options=[
            io.DynamicCombo.Option("sub_opt1", [io.Float.Input("x"), io.Float.Input("y")]),
            io.DynamicCombo.Option("sub_opt2", [io.Mask.Input("mask", optional=True)]),
        ])
    ]),
])
```

### 异步执行

V3 支持异步 `execute` 方法。对于执行 I/O 操作、API 调用或其他异步工作的节点，这很有用。只需将 `execute` 声明为 `async`：

```python theme={null}
@classmethod
async def execute(cls, prompt, **kwargs) -> io.NodeOutput:
    result = await some_async_operation(prompt)
    return io.NodeOutput(result)
```

### ComfyAPI

`ComfyAPI` 类提供对 ComfyUI 运行时服务的访问，如进度报告和节点替换注册。导入它并创建实例：

```python theme={null}
from comfy_api.latest import ComfyAPI

api = ComfyAPI()
```

#### 进度报告

从节点的 `execute` 方法中报告执行进度。进度条显示在 ComfyUI 界面中。这取代了 V1 中使用 `comfy.utils.PROGRESS_BAR_HOOK` 的模式。

```python theme={null}
from comfy_api.latest import ComfyAPI

api = ComfyAPI()

@classmethod
async def execute(cls, images, **kwargs) -> io.NodeOutput:
    total = len(images)
    for i, image in enumerate(images):
        process(image)
        await api.execution.set_progress(
            value=i + 1,
            max_value=total,
            preview_image=image,  # 可选：在进度期间显示预览
        )
    return io.NodeOutput(result)
```

<Note>
  `set_progress` 可以接受 PIL Image、`ImageInput` 张量或 `None` 作为 `preview_image` 参数。当从 `execute` 内部调用时，`node_id` 会自动从执行上下文中确定。
</Note>

#### 节点替换

节点替换允许将旧的/已弃用的节点映射到新节点，因此现有工作流会自动升级。在扩展的 `on_load` 方法中使用 `ComfyAPI` 注册替换。

```python theme={null}
from comfy_api.latest import ComfyAPI, ComfyExtension, io

api = ComfyAPI()

class MyExtension(ComfyExtension):
    async def on_load(self) -> None:
        await api.node_replacement.register(io.NodeReplace(
            new_node_id="MyNewNode",
            old_node_id="MyOldNode",
            old_widget_ids=["param1", "param2"],  # 用于位置映射的有序小部件 ID
            input_mapping=[
                {"new_id": "image", "old_id": "input_image"},       # 重命名输入
                {"new_id": "method", "set_value": "lanczos"},       # 设置固定值
            ],
            output_mapping=[
                {"new_idx": 0, "old_idx": 0},  # 按索引映射输出
            ],
        ))

    async def get_node_list(self) -> list[type[io.ComfyNode]]:
        return [MyNewNode]
```

`old_widget_ids` 参数很重要：工作流 JSON 按位置索引存储小部件值，而不是按名称。此列表将这些位置索引映射到输入 ID，以便替换系统在迁移期间可以正确识别小部件值。

对于使用动态输入（如 Autogrow）的节点，在映射中使用点分路径：

```python theme={null}
input_mapping=[
    {"new_id": "images.image0", "old_id": "image1"},
    {"new_id": "images.image1", "old_id": "image2"},
]
```

### 扩展生命周期

`ComfyExtension` 类支持除 `get_node_list` 之外的生命周期钩子：

```python theme={null}
from comfy_api.latest import ComfyExtension, io

class MyExtension(ComfyExtension):
    async def on_load(self) -> None:
        """扩展加载时调用。
        用于一次性初始化：注册节点替换、
        设置全局资源等。
        """
        pass

    async def get_node_list(self) -> list[type[io.ComfyNode]]:
        """返回此扩展提供的节点类列表。"""
        return [MyNode]

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

### NodeOutput

`NodeOutput` 类是 `execute` 的标准化返回值。它支持多种模式：

```python theme={null}
# 返回单个输出值
return io.NodeOutput(image)

# 返回多个输出值（顺序与 schema 中的 outputs 列表匹配）
return io.NodeOutput(width, height, batch_size)

# 仅返回 UI 数据（无输出值）
return io.NodeOutput(ui=ui.PreviewImage(images, cls=cls))

# 返回输出值和 UI 数据
return io.NodeOutput(image, ui=ui.PreviewImage(image, cls=cls))

# 返回 None/空（对于没有输出的节点）
return io.NodeOutput()
```

## 完整示例

这是一个包含多个节点的完整 V3 扩展文件示例：

```python theme={null}
from comfy_api.latest import ComfyExtension, io, ui

class InvertImage(io.ComfyNode):
    @classmethod
    def define_schema(cls):
        return io.Schema(
            node_id="MyPack_InvertImage",  # 添加前缀以避免冲突
            display_name="Invert Image",
            category="my_pack/image",
            description="反转图像的颜色。",
            inputs=[
                io.Image.Input("image"),
            ],
            outputs=[
                io.Image.Output(display_name="inverted"),
            ],
        )

    @classmethod
    def execute(cls, image) -> io.NodeOutput:
        inverted = 1.0 - image
        return io.NodeOutput(inverted, ui=ui.PreviewImage(inverted, cls=cls))


class SaveImage(io.ComfyNode):
    @classmethod
    def define_schema(cls):
        return io.Schema(
            node_id="MyPack_SaveImage",
            display_name="Save Image",
            category="my_pack/image",
            is_output_node=True,
            inputs=[
                io.Image.Input("images"),
                io.String.Input("filename_prefix", default="ComfyUI"),
            ],
            outputs=[],
        )

    @classmethod
    def execute(cls, images, filename_prefix) -> io.NodeOutput:
        return io.NodeOutput(
            ui=ui.ImageSaveHelper.get_save_images_ui(
                images=images,
                filename_prefix=filename_prefix,
                cls=cls,
            )
        )


class MyPackExtension(ComfyExtension):
    async def get_node_list(self) -> list[type[io.ComfyNode]]:
        return [InvertImage, SaveImage]

async def comfy_entrypoint() -> MyPackExtension:
    return MyPackExtension()
```
