Skip to main content
POST /v2/models/{provider}/{model} 会一直保持连接,直到模型完成。队列投递使用相同的模型 ID 和相同的原生请求体,但只要 Router 接纳了本次运行就会立即返回。你会立刻拿到一个 request_id,并在结果就绪时获取它,可以在同一进程中,也可以在另一个进程中。 同步路由有 10 分钟的上限。 Router 的默认截止时间可按部署配置。同步调用一旦达到该时限就会被切断,返回 504 / deadline_exceeded,不论提供商是否仍在工作。如果一次运行可能超过该截止时间,或者调用方无法将连接保持打开那么久,就请使用队列。关于截止时间对计费会说明什么、不会说明什么,请参阅调用会在服务器截止时间被切断。 在以下情况使用队列:一次生成可能超出你能保持的连接时长;Web 请求必须立即返回;你在一个进程中提交、在另一个进程中收集结果;或者你希望同时进行多次生成。排序、接纳、重试、超时、计费和过期都由服务器决定。SDK 只是在其上增加了轮询和易用性封装,别无其他。

选择交付模式

SDK(comfy-sdk 和 @comfyorg/sdk,0.3.0 或更高版本)在 run 旁边将队列暴露为三个方法:
  • submit(model, body) 立即发送请求并返回一个句柄。该句柄提供 status()、get()、cancel() 以及一个事件迭代器(Python 中为 iter_events(),TypeScript 中为 events())。
  • subscribe(model, body, ...) 在一次调用中完成提交、轮询和收集,并带有进度回调。
  • handle(model, request_id) 在另一个进程中根据这两个 ID 重建句柄,不会发起任何调用。
这两个 ID 在任何地方都必不可少,因为它们共同定位该请求: 路由是 /v2/models/{provider}/{model}/requests/{request_id}。
这里的 events() 不是 Comfy Cloud 客户端的 job.events()。这里的 events() 会轮询状态路由,并为 Router 请求产生队列观察结果(状态和队列位置)。Cloud 的那个则是 ComfyUI 工作流任务的实时 SSE 流,携带进度、预览和输出。subscribe() 又是第三种东西: 它根本不是迭代器,而是把提交、轮询和收集合并成一次调用。参见 events() 不是 subscribe()。

队列路由

status 为 IN_QUEUE、IN_PROGRESS 或 COMPLETED 之一。不存在单独的失败或已取消状态:未成功的请求会是携带 error_type 的 COMPLETED,因此请根据该字段是否存在来分支处理,而不是根据第四种状态值。SDK 已为你处理这一点:get() 会抛出或拒绝并返回类型化的 Router 错误,而不是把失败作为结果返回。 没有进度事件、webhook 或优先级级别:状态路由就是你跟踪请求的方式。API 参考 包含了每个路由的完整约定。

提交并收集请求

这会排队与快速入门发送的相同请求,并收集图像。请先将你的密钥导出为 COMFY_API_KEY。
每个模型页面都会在自己的模型下、排队并稍后收集中带有这种形式,与同步代码片段并列。

在一次调用中跟踪进度并收集

当你确实想要等待,同时也想显示进度时,subscribe 会将提交、轮询和收集合并为一次调用:
超时是客户端侧的界限,在服务器端没有含义。超时后,subscribe 会在抛出异常前进行一次尽力而为的取消。取消只对尚未开始运行的请求生效:已经在合作伙伴处进行中的生成会完成并被计费,无论是否有人收集它。当请求应当比调用方存活更久时,请使用 submit。

从另一个进程收集

将 request_id 与模型 ID 一起存储。重建句柄两者都需要,在你使用它之前不会发起任何调用。

检查状态或取消

status() 是一次轮询,返回当前状态。cancel() 请求服务器停止尚未完成的请求。这是一个请求,不是保证:已经在合作伙伴处开始执行的运行可能仍会完成,而下一次 status() 才是真实情况。

异步 Python

AsyncComfy 镜像了每个名称、参数和参数顺序。没有 submit_async,原因与没有 run_async 相同。

SDK 抛出的错误

已完成但未成功的请求会报告为 COMPLETED 并带有 error_type。get() 和 subscribe() 会将其转换为该类别对应的类型化 Router 错误:Python 中是 comfy_sdk.router_exceptions 中的类,TypeScript 中是 routerErrors.*。事件迭代器在这种情况下不会抛出异常,因为它是队列进度的视图:带有 error_type 的完成会作为最后一条观察结果产出,而收集工作由 get() 完成。提交时的 403 not_enabled 会以 NotEnabled 形式到达,并且是终端错误,因此 SDK 不会重试它。

队列响应形状

提交,201。 此时 status 始终为 IN_QUEUE。这三个 URL 都是绝对地址,并使用与提交相同的密钥进行鉴权。
request_id 同时也是提交请求的 X-Comfy-Request-Id 头的值。请把它与模型 ID 放在一起:该请求需要二者共同定位。 状态,200。 形状相同,但包含当前状态。queue_position 统计排在你前面的请求数量,当运行位于队首时其值为 0。此响应上的 Retry-After 是 Router 对何时值得再次轮询一次往返的预估。它只是提示,不是硬性限制,排在队列末尾的请求会被要求等待得比已在运行的请求更久。轮询得再快也不会更早获知任何信息,只会消耗你自己的速率限制额度。
一个已完成但未成功的请求为 COMPLETED,并带有 error_type,其中携带的粗粒度分类与读取结果时放在 X-Comfy-Error-Type 上的相同。该字段在成功时是缺失的,而不是 null。
结果。 200 携带模型自身的原生输出,与同一模型和输入的同步路由所返回的内容逐字节一致,并使用提供商自己的 Content-Type。当请求尚未完成时,读取会返回 202 和上面的状态响应体,因此只轮询结果 URL 的客户端只需解析一种类型。失败的请求会作为错误响应返回,并设置 X-Comfy-Error-Type,其分类与同步路由相同。 取消。 202 加 CANCELLATION_REQUESTED 表示取消请求已被接受,并不表示运行已经停止。已经发往合作伙伴处运行的请求仍可能完成,而合作伙伴已完成的一次生成无论是否有人取回都会被计费。之后请读取状态:生效的取消会显示为 COMPLETED,并带有 error_type: cancelled。在能够运行之前就已超时的请求会以同样方式显示 queue_timeout。已经完成的请求会返回 409,并带有 ALREADY_COMPLETED。

幂等性与计费

  • 与同步路由相同的收费。 提供商向 Comfy 计费时才会向你计费。在队列中等待所花费的时间不计费。
  • 每次提交使用一个 Idempotency-Key。 SDK 会为每次 submit 调用生成一个新密钥,因此对同一输入的两次有意提交就是两个请求。同一次调用在同一密钥下的重试不会排入第二次运行:它会返回原始句柄,并带有 Idempotent-Replayed: true。当响应丢失可能让你损失 request_id 时,请传入你自己的密钥。参见请求头。
  • 结果会过期。 已完成请求在完成后会保留 24 小时。之后,状态和结果的读取会返回 410,结果已不复存在。请及时收集并下载输出所携带的任何资产 URL。
  • 轮询也是请求。 状态和结果的读取都会计入每个调用方的请求速率。请遵守 Retry-After,而不要以固定的短间隔持续轮询。

队列错误响应

每个错误响应都携带 X-Comfy-Request-Id。联系支持时请附上该 ID。

后续

  • 错误与重试:了解错误响应,并在不产生二次扣费的情况下重试。
  • API 参考:每条队列路由的完整契约。