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

# Comfy SDKs

> 用于在您自己的应用程序中运行 ComfyUI 工作流的官方 Python 和 TypeScript SDK

<Warning>
  **测试版。** 这些 SDK 及其调用的 Comfy API v2 目前仍处于 1.0 之前的阶段。API 的结构在稳定之前仍可能发生变化。请通过[反馈](#反馈)报告问题。
</Warning>

**一个包，两种接口。** `comfy-sdk`（PyPI）和 `@comfyorg/sdk`（npm）提供了两个客户端，分别与两个不同的服务通信。方法名会在两者之间重复出现（`run`、`submit`、`events`），但含义各不相同，因此在复制代码片段之前，请先确认它构建的是哪个客户端。

| 接口                                                                     | 作用                                                  | 访问方式                                                                                                 | Base URL                                                   | 认证                                                |
| ---------------------------------------------------------------------- | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | ------------------------------------------------- |
| **[Comfy Router](/zh/development/comfy-router/quickstart)**            | 使用合作伙伴模型（Flux、Veo、Gemini、Kling）各自的请求体调用该模型，并返回其原生响应 | Python：在 `Comfy()` 上使用 `client.models`。TypeScript：在模块级 `comfy` 命名空间上使用 `comfy.models`                | `https://api.comfy.org`，路由 `/v2/models/{provider}/{model}` | `COMFY_API_KEY`，或 `comfy.config({ credentials })` |
| **[Comfy Cloud 与 Comfy API v2](/zh/development/api-development/sdks)** | 端到端运行 API 格式的 ComfyUI 工作流：上传资产、提交节点图、跟踪任务、下载输出      | `Comfy(api_key=...)` / `new Comfy({ apiKey })`，然后使用 `client.workflows`、`client.assets`、`client.jobs` | 默认为 `https://cloud.comfy.org`，或 `COMFY_BASE_URL` 指定的地址     | 构造函数上的 `api_key` / `apiKey`                       |

两种接口互不封装，在[你的 Comfy 工作区](https://platform.comfy.org/profile/api-keys)中创建一个 API 密钥即可同时用于两者。

<Note>
  两种语言对 Router 接口的暴露方式不同。在 Python 中，`Comfy()` 同时承载两者：`client.models.run(...)` 用于 Router，`client.workflows` / `client.assets` / `client.jobs` 用于 Cloud。在 TypeScript 中，它们是两个独立的导出，名称刻意相近：`comfy`（小写，模块级命名空间）包含 `comfy.models`，而 `Comfy`（类）是 Cloud 客户端，没有 `.models`。
</Note>

本页介绍第二行：Comfy Cloud 和 Comfy API v2 客户端。如果您要使用模型路由器，请从 [Comfy Router 快速入门](/zh/development/comfy-router/quickstart)开始。

Comfy SDK 让您的应用程序能够运行 ComfyUI 工作流并取回结果。您提交工作流，ComfyUI 执行它，然后您下载输出。同一份代码既可用于 Comfy Cloud，也可用于您自行托管的 ComfyUI 实例，只需更改基础 URL。

这些 SDK 是 [Comfy API v2](/zh/api-reference/v2/overview) 的客户端。Comfy API v2 是一个版本化的 HTTP API，我们计划长期支持。未来发布的 ComfyUI 新版本不会破坏基于该 API 构建的集成。

人们通过这种方式构建的内容：

* 在其他应用程序（如 Blender 或 Krita）内部生成内容的插件
* 代表用户执行生成的面向消费者的应用
* 批处理流水线，例如对视频的每一帧运行同一个工作流
* 需要同时运行大量工作流的后端服务

<Note>
  这些 SDK 是从**外部**驱动 ComfyUI。如果您要编写在 ComfyUI **内部**运行的自定义节点或前端扩展，请改用[开发自定义节点](/zh/custom-nodes/overview)。那是一套独立的 API。
</Note>

## 安装

<CodeGroup>
  ```bash Python theme={null}
  pip install comfy-sdk
  ```

  ```bash TypeScript theme={null}
  npm i @comfyorg/sdk
  ```
</CodeGroup>

Python 3.10 或更高版本。Node 22 或更高版本。

<Note>
  **当前版本：0.4.0。** PyPI 上的 [`comfy-sdk`](https://pypi.org/project/comfy-sdk/) 与 npm 上的 [`@comfyorg/sdk`](https://www.npmjs.com/package/@comfyorg/sdk) 一同发布，并共用同一个版本号。请安装最新版本（`pip install comfy-sdk`、`npm install @comfyorg/sdk`），或声明一个范围，而不是精确固定某个版本。两个生态对此的写法不同：`comfy-sdk>=0.4` 是一个下限，接受后续的次版本；而 `@comfyorg/sdk@^0.4` 是 npm 针对 `0.4.x` 的兼容范围，止步于 `0.5.0` 之前，因此当新的次版本发布时，插入符范围需要上调。如果你希望 npm 端也跟随后续的次版本，请使用 `@comfyorg/sdk@>=0.4.0`。如果本说明滞后，以注册表页面为准。
</Note>

## 快速入门

上传输入图像，运行工作流，并将结果写入磁盘。

<CodeGroup>
  ```python Python theme={null}
  from comfy_sdk import Comfy

  # Comfy Cloud
  client = Comfy(api_key="comfyui-...")

  wf = client.workflows.from_file("workflow_api.json")

  asset = client.assets.from_file("photo.png")
  wf.set_input("10", "image", asset)

  job = client.run(wf)
  for output in job.get_outputs("9"):
      output.to_file(output.name)
  ```

  ```typescript TypeScript theme={null}
  import { Comfy } from "@comfyorg/sdk";

  // Comfy Cloud
  const client = new Comfy({ apiKey: "comfyui-..." });

  const wf = await client.workflows.fromFile("workflow_api.json");

  const asset = client.assets.fromFile("photo.png");
  wf.setInput("10", "image", asset);

  const job = await client.run(wf);
  await job.getOutputs("9")[0].toFile("out.png");
  ```
</CodeGroup>

`workflow_api.json` 是以 [API 格式](/zh/development/api-development/workflow-api-format) 保存的工作流。`"10"` 和 `"9"` 是该文件中的节点 ID：即输入图像送入的节点，以及你希望获取结果的输出节点。

资产句柄是惰性的。`photo.png` 会在本地进行哈希计算，仅当服务器尚未拥有这些字节时才会被上传，因此使用相同的输入重新运行不会产生任何成本。

`run()` 提交任务并等待其达到终端状态。如要在其执行期间进行其他操作，请改用 `submit()`，并观察 [事件流](#查看任务运行)。

若要改为在你自己的 ComfyUI 上运行，请设置 `COMFY_BASE_URL` 并去掉密钥。请参阅下文。

## 选择基础 URL

| 使用环境             | 基础 URL                               | API 密钥             |
| ---------------- | ------------------------------------ | ------------------ |
| **Comfy Cloud**  | `https://cloud.comfy.org`（默认）        | 必填                 |
| **Comfy API 部署** | `https://<deployment>.run.comfy.app` | 必填                 |
| **您自己的 ComfyUI** | `http://127.0.0.1:8189`（本地代理）        | 默认无。可选静态 bearer 令牌 |

基础 URL 来自 `COMFY_BASE_URL` 环境变量，而不是构造函数参数：

```bash theme={null}
export COMFY_BASE_URL="https://<deployment>.run.comfy.app"  # Comfy API 部署
export COMFY_BASE_URL="http://127.0.0.1:8189"               # 自托管代理
```

客户端每次构造时都会读取该变量，它必须是 `http(s)` URL，未设置或为空则默认为 Comfy Cloud。因此客户端本身在所有环境下都是相同的：

<CodeGroup>
  ```python Python theme={null}
  client = Comfy(api_key="comfyui-...")
  ```

  ```typescript TypeScript theme={null}
  const client = new Comfy({ apiKey: "comfyui-..." });
  ```
</CodeGroup>

<Note>
  从早期版本升级？`Comfy("<url>", "<key>")` 现在是设置 `COMFY_BASE_URL` 后的 `Comfy(api_key="<key>")`。`api_key` 是仅限关键字参数，因此旧的位置参数调用会抛出 `TypeError`，而不会将 URL 静默当作 API 密钥读取。
</Note>

### Comfy Cloud

开箱即用。创建 [API 密钥](/zh/development/api-development/getting-an-api-key) 并将其传递给客户端。

<Note>
  API 访问需要付费的 Comfy Cloud 订阅。免费版不包含此权限。一次可执行的任务数取决于您的层级。请参阅[并行执行](/zh/development/deploy/cloud#并行执行（并发任务）)。
</Note>

<a id="serverless-deployment" />

### Comfy API 部署

通过 [开发者平台](https://platform.comfy.org) 部署的工作流会有自己的端点。将 `COMFY_BASE_URL` 指向该端点并使用您的 API 密钥，与 Comfy Cloud 完全相同。本指南中的所有内容都以相同的方式工作。

Comfy API 部署可以使用其 Build 中包含的模型和自定义节点运行多个工作流。对于每个任务，`get_workflow()` 返回的工作流响应带有 `format: "api"`，并在其 `.graph` 属性中包含执行后的图。

### 您自己的 ComfyUI

在测试版期间，v2 API 由 [comfy-api-proxy](https://github.com/Comfy-Org/comfy-api-proxy) 提供，这是一个与您的 ComfyUI 一起运行的小型开源服务。安装它、运行它，并设置 `COMFY_BASE_URL="http://127.0.0.1:8189"`：

```bash theme={null}
pip install comfy-api-proxy
comfy-api-proxy
export COMFY_BASE_URL="http://127.0.0.1:8189"
```

关于配置、身份验证以及该代理为什么存在，请参阅[自托管 ComfyUI 的 API 代理](/zh/development/comfyui-server/api-proxy)。它只是过渡方案：v2 API 稳定后会并入 ComfyUI 核心，届时不再需要代理。

## 重试提交：幂等键

`submit()` 和 `run()` 在每次提交时都会发送一个 `Idempotency-Key`，而 Comfy API v2 对该键的契约是**重复即拒绝，而非记录并重放**。在把提交包进重试循环之前，请先阅读本节。

* **每次调用都会生成一个新的键。** 用同一个工作流调用 `submit()` 两次，就是两次提交、两个任务和两笔计费。在 `submit()` 外面套一个天真的 `for attempt in range(3)`，得到的是双重扣费，而不是重试。
* **被复用的键会被拒绝，而不是被重放。** 传入你自己的 `idempotency_key`（TypeScript 中为 `idempotencyKey`）可以让重试具备幂等性，而使用该键的第二次请求会以 `422 idempotency_key_reuse` 失败，而不是返回第一个任务。各 SDK 会抛出 `IdempotencyKeyReuse`。这与“幂等重试”通常的含义正好相反，因此需要显式处理。
* **恢复方式是去找回任务，而不是重新提交。** 遇到 `IdempotencyKeyReuse` 时，第一次尝试很可能已经创建了任务。用 `client.jobs.get(job_id)` 取回它。该查找需要你已存储的 id，因此它只能恢复“提交已返回、且你在故障发生前持久化了 id”这一情形。如果连接在记录 id 之前就断开了，那就没有可查找的对象，也没有自动恢复的办法：下面的示例选择重新抛出异常交由人工处理，而不是在已占用的键下重新提交。
* **当提交确定失败时，该键会被释放。** 验证错误、积分不足被拒绝或队列已满被拒绝都不会创建任务，并会释放该键，因此在该键下重新提交是没问题的。在*结果不明*的失败之后（读取超时、请求中途连接断开），该键仍保持占用：此时应查找任务，而不是重新提交。
* **键会在 24 小时后过期**，且必须非空、由可打印 ASCII 字符组成并符合长度限制。无效的键会在发出任何请求之前抛出 `ValueError`，因此显式的 `""` 绝不会静默回退到一个自动生成的键。

因此，一个可以安全运行多次的重试必须在各次尝试之间携带两样东西：键，让服务器能够区分重试与新的提交；以及任务ID，让你能够找到已被占用的键所创建的任务。

<CodeGroup>
  ```python Python theme={null}
  import uuid
  from comfy_sdk import IdempotencyKeyReuse

  # 这两个值都应该放在你自己的存储里，以该请求对你而言
  # 的含义作为键。`key` 只在第一次尝试时生成一次；`job_id`
  # 在提交返回后立即填入。
  state = load_submission_state()  # {"key": ..., "job_id": ...} or {}
  key = state.get("key") or str(uuid.uuid4())
  save_submission_state(key=key)

  try:
      job = client.submit(wf, idempotency_key=key)
      save_submission_state(key=key, job_id=job.id)
  except IdempotencyKeyReuse:
      # 此前在该键下的一次尝试已经到达提交环节，因此可能已存在一个任务。
      # 不要重新提交：去找回它。
      job_id = state.get("job_id")
      if job_id is None:
          raise  # 没有记录可供查找；这需要人工处理
      job = client.jobs.get(job_id)
  ```

  ```typescript TypeScript theme={null}
  import { IdempotencyKeyReuse } from "@comfyorg/sdk";

  // 这两个值都应该放在你自己的存储里，以该请求对你而言
  // 的含义作为键。`key` 只在第一次尝试时生成一次；`jobId`
  // 在提交返回后立即填入。
  const state = await loadSubmissionState(); // { key?, jobId? }
  const key = state.key ?? crypto.randomUUID();
  await saveSubmissionState({ key });

  let job;
  try {
    job = await client.submit(wf, { idempotencyKey: key });
    await saveSubmissionState({ key, jobId: job.id });
  } catch (err) {
    // 此前在该键下的一次尝试已经到达提交环节，因此可能已存在一个任务。
    // 不要重新提交：去找回它。
    if (!(err instanceof IdempotencyKeyReuse) || !state.jobId) throw err;
    job = await client.jobs.get(state.jobId);
  }
  ```
</CodeGroup>

`client.jobs.get(job_id)` 是 SDK 的重建路径，它需要一个 id，这正是为什么在提交返回的那一刻记录 id 才让这一切具备可恢复性。关于 `POST /api/v2/jobs` 上完整的键契约，请参阅 [Comfy API v2](/zh/api-reference/v2/overview)。

<Note>
  Comfy Router 的 `Idempotency-Key` 行为**不同**。在 Router 上，该键是一个重放句柄，而不是一次性令牌：用同一个键重新发送一个已排队的提交，会返回原始请求，而不是再排队一个。请参阅[队列投递](/zh/development/comfy-router/queue#幂等性与计费)和 [Router 重试结果](/zh/development/comfy-router/api#retry-outcomes)。这两个接口只是共用一个请求头名称，契约并不相同。
</Note>

## 查看任务运行

`job.events()` 会提供任务状态的实时流：节点和步骤进度、预览帧，以及每个输出一经提交的即时通知。如果连接中断，它会自动重新连接。

<CodeGroup>
  ```python Python theme={null}
  from comfy_sdk import Progress, Preview, OutputReady, StatusChange

  job = client.submit(wf)

  for event in job.events():
      match event:
          case Progress() as p:
              print(f"{p.value:.0%} {p.message}")
          case Preview() as pv:
              image = pv.to_pil()
          case OutputReady() as o:
              o.output.to_file(f"partial/{o.output.name}")
          case StatusChange(status="succeeded"):
              break

  result = job.result()
  ```

  ```typescript TypeScript theme={null}
  const job = await client.submit(wf);

  // 标签是必需的：switch 内的裸 `break` 跳出的是 switch，
  // 而不是循环。
  eventLoop: for await (const event of job.events()) {
    switch (event.kind) {
      case "progress":
        console.log(event.value);
        break;
      case "outputReady":
        await event.output.toFile(`${event.output.name}`);
        break;
      case "statusChange":
        if (event.status === "succeeded") break eventLoop;
    }
  }
  ```
</CodeGroup>

`Preview.to_pil()` 需要可选的 Pillow extra：`pip install "comfy-sdk[pil]"`。

`result()` 返回已完成的任务，如果执行失败，则抛出带有节点级详细信息的 `JobFailed` 异常。有关完整的事件目录，请参阅适用于您的语言的 [SDK README](#参考)。

### `events()` 不是 `subscribe()`

在这两个接口中存在三个名称相似的东西，而本页只涉及其中一个：

| 调用                                                            | 接口                                         | 产出内容                                                                             |
| ------------------------------------------------------------- | ------------------------------------------ | -------------------------------------------------------------------------------- |
| `job.events()`                                                | Comfy Cloud（本页）                            | 单个工作流任务的实时 SSE 流：`Progress`、`Preview`、`OutputReady`、`StatusChange`。会自动重连，并回退到轮询。 |
| `handle.events()`（TypeScript）/ `handle.iter_events()`（Python） | [队列投递](/zh/development/comfy-router/queue) | 针对一个 Router 请求的队列观测：状态和队列位置。它会轮询状态路由；没有流，也没有进度或预览事件。                             |
| `comfy.models.subscribe(...)`                                 | [队列投递](/zh/development/comfy-router/queue) | 不是迭代器。一次调用即可提交、轮询并收集，带有 `on_queue_update` / `onQueueUpdate` 回调，最终解析为结果。          |

Comfy Cloud 客户端上没有 `subscribe()`，任何地方也都没有 `job.subscribe()`。在 Cloud 上，`subscribe` 的等价物是 `run()`：提交并等待终止状态。

该流是实时信息流，而不是可重放的日志。它的存在是为了让你能够呈现进度，而不是让你依赖它来获取结果。轮询任务才是权威来源，`run()`、`wait()` 和 `result()` 会自动回退到轮询。原因请参阅 [设计说明](/zh/development/api-development/sdks-design#先轮询再流式获取进度)。

## 将输出回溯到其工作流

输出带有生成它们的任务的 ID，因此您可以以文件为起点反向追溯，而无需额外维护一张对应表。

<CodeGroup>
  ```python Python theme={null}
  output = job.outputs[0]
  output.job_id          # the job that produced this file
  ```

  ```typescript TypeScript theme={null}
  const output = job.outputs[0];
  output.jobId; // the job that produced this file
  ```
</CodeGroup>

单独获取的资源也带有相同的 ID，因此您之后找到的文件依然能回溯到生成它的任务。对于您上传的资源，该 ID 为 `None`（TypeScript 中为 `undefined`），因为上传的资源没有对应的生成任务。

您可以向任务查询其背后的工作流。即使该任务不是在当前进程中提交的，只要通过 ID 重新加载，同样可以做到：

<CodeGroup>
  ```python Python theme={null}
  wf = job.get_workflow()

  if wf.format == "save":
      ...  # the workflow as authored, canvas layout and Note nodes intact
  else:
      ...  # the executed API-format graph
  ```

  ```typescript TypeScript theme={null}
  const wf = await job.getWorkflow();

  if (wf.format === "save") {
    // the workflow as authored, canvas layout and Note nodes intact
  } else {
    // the executed API-format graph
  }
  ```
</CodeGroup>

**始终根据 `format` 进行分支判断。** 返回哪种形状取决于任务的提交方式，而不是您在每次请求中能控制的任何因素：

| `format` | 您获得的内容                                       | 适用情况                                |
| -------- | -------------------------------------------- | ----------------------------------- |
| `save`   | 任务运行时所使用的版本对应的创作工作流，包含画布布局和仅编辑器可见的节点（如 Note） | 从 Comfy Cloud 编辑器提交的任务，此类任务会固定工作流版本 |
| `api`    | 已执行的图。仅编辑器可见的结构已消失，Get/Set 节点已展开             | 其他所有情况，包括目前通过这些 SDK 提交的每个任务         |

您通过 SDK 提交的任务始终返回 `api`，因为 v2 提交目前还没有版本固定字段。这种情况将来会改变；判别字段的存在就是为了让您的代码无需随之改变。

## SDK 目前涵盖的内容

第一个版本只做好一件事：运行工作流并取回结果。

* **资产。** 从文件、字节、流或 URL 创建输入句柄。句柄是惰性的，并按内容寻址，因此使用相同输入重新运行时不会再次上传。
* **提交。** 提交 API 格式的节点图。提交是幂等的，队列已满时会在有限的预算内自动重试。
* **执行。** 使用 `wait()` 轮询，或通过 `events()` 跟踪实时进度。
* **输出。** 写入磁盘、缓冲到内存、获取字节范围，或获取短期有效的下载 URL。`getDownloadUrl()` 返回一个签名 URL，任何人都可以在没有 API 密钥的情况下读取，有效期约为 6 小时。在存储它之前请先阅读[输出 URL 及其有效时长](/zh/api-reference/v2/overview#输出-url-及其有效期)：若要在之后继续展示某个输出，请重新托管字节内容，或按需重新生成该 URL。
* **可追溯性。** 每个输出都带有生成它的任务的 ID，并且任务可以返回其背后的工作流。
* **删除资产。** 通过句柄或 ID 移除你上传的资产。
* **错误。** 提供类型化异常，如 `JobFailed`、`Unauthorized`、`InsufficientCredits` 和 `QueueFull`，而不是原始状态码。
* **取消。** 任务可以在运行时取消。TypeScript 还在任何调用上接受 `AbortSignal`。

Python 同时提供同步的 `Comfy` 客户端和接口相同的 `AsyncComfy` 客户端。TypeScript 仅支持异步。

此版本不包含：管理已保存的工作流、模型库、节点内省，以及命名工作流参数。[设计说明](/zh/development/api-development/sdks-design#首个版本的范围) 解释了为何接口从如此小的范围起步。

## 参考

SDK 的 README 是每种语言的完整参考，涵盖认证、资产、错误处理以及低层逃生通道。

<CardGroup cols={2}>
  <Card title="Python SDK" icon="python" href="https://github.com/Comfy-Org/comfy-python-sdk">
    <code>comfy-sdk</code> 位于 PyPI。提供同步和异步客户端。
  </Card>

  <Card title="TypeScript SDK" icon="js" href="https://github.com/Comfy-Org/comfy-typescript-sdk">
    <code>@comfyorg/sdk</code> 位于 npm。带类型、异步，并提供低层客户端。
  </Card>

  <Card title="Comfy API v2 参考" icon="code" href="/zh/api-reference/v2/overview">
    两个 SDK 底层的 HTTP API。任何语言都可以直接使用。
  </Card>

  <Card title="设计说明" icon="compass" href="/zh/development/api-development/sdks-design">
    这个 API 存在的原因、它与现有 ComfyUI API 的关系，以及未来的发展方向。
  </Card>

  <Card title="Comfy Router" icon="shuffle" href="/zh/development/comfy-router/quickstart">
    同一个包中的另一个接口：针对 Flux、Veo 和 Gemini 等合作伙伴模型的 <code>comfy.models.run</code>。
  </Card>
</CardGroup>

## 反馈

这些 SDK 目前仍处于 1.0 之前的阶段。方法名称、客户端形状、事件目录、错误分类法以及资产处理方式都仍有可能变化，预计接口表面会在未来几周内趋于稳定。一旦稳定下来，长期支持的承诺就会限制哪些内容还可以修改。

请告诉我们哪些地方用起来别扭、你期望找到却没有找到的功能，以及你不得不绕行处理的地方。[我们的 Discord](https://discord.com/invite/comfyorg) 中的 `#developer-platform` 通道就是反馈这些内容的地方。

如果你希望有其他语言的第一方 SDK，也可以在那里提出。两个 SDK 都建立在同一套有文档记录的 HTTP 契约之上，所以如今任何语言都可以与 API 通信，但我们更希望了解需求在哪里。
