本文重点讲后两类:异步任务的 TaskHandle 轮询 和 chat 流式响应 的细节、陷阱和跨语言差异。
一、TaskHandle —— 异步任务的统一抽象
三种 SDK 都把异步任务封装成TaskHandle,提供同样的 4 个方法:
创建任务的两种调用方式
每个异步资源(images.generate / video.generate / audio.generate)都有 wait 参数:
wait=False(默认):立即返回TaskHandle,业务代码自己决定何时轮询。wait=True:SDK 内部直接调handle.wait(),函数返回完成后的响应。仅当你确定目标 API 一定会返回status: succeeded字段时再用——少数 provider 没遵守这个约定,会让wait一直转到max_wait才抛TimeoutError。
单位差异(⚠️ 必看)
poll_interval 和 max_wait 的单位在三种语言里不一样,跨语言迁移时是常见踩坑点:
把 TS 的{ pollInterval: 3000 }当成秒翻成 Pythonpoll_interval=3000,会让 SDK 等 50 分钟才轮询第二次。
示例:Python 显式轮询 Midjourney
images.generate(..., wait=False)把prompt提交给 Midjourney API,立即拿到handle,不阻塞。handle.wait(poll_interval=3.0, max_wait=180.0)内部每 3 秒 POST 一次/midjourney/tasks,直到status变成succeeded或failed,或总耗时超过 180 秒抛TimeoutError。- 完成后
result["response"]["data"]通常包含 4 张图(Midjourney 默认 2x2 grid)。
示例:TypeScript 显式轮询
同步生成 vs 异步任务的取舍
如果你的 provider 本身就是同步出图(NanoBanana / Flux / Seedream),不要传wait:
task_id + /tasks 这一对,就是同步生成;同步生成的响应里 data 字段已经包含最终结果。
TaskHandle 内部协议
TaskHandle.get() 调用的是:
response 包裹的旧版响应——直接读取顶层 status,所以新旧版响应切换不影响业务代码。
二、SSE 流式响应(chat.completions)
chat.completions.create(stream=True) 是目前 SDK 里唯一的流式接口(音频 / 视频流尚未支持)。三种语言的迭代风格各自原生:
TypeScript
Python
Go
流式 chunk 的结构
每一帧 chunk 都是一个 OpenAI 兼容的chat.completion.chunk:
- 第一个 chunk 通常带
delta.role: "assistant"但content为空。 - 中间的 chunk 每个带
delta.content,可以直接拼接。 - 最后一个 chunk
delta为空、finish_reason是stop/length/content_filter。
中途取消
提前取消已经计费的 token —— 取消时刻之前生成的 token 还是会按实际消耗扣费。
三、超时和重试
三种 SDK 共享同一套重试策略:
要禁用重试:构造客户端时传
max_retries=0 / maxRetries: 0 / WithMaxRetries(0)。
异步任务(TaskHandle)的轮询本身不受 max_retries 影响——它的循环是业务级的而不是 HTTP 级的,靠 max_wait 控制总时长。
四、常见陷阱
- 同步 provider 不要传
wait:NanoBanana / Flux / Seedream 都是同步生成,强行wait=True会让 SDK 去轮询一个根本不会更新的tasks接口。 - TaskHandle 单位差异:Python 是秒、TS 是毫秒,跨语言移植时一定换算。
wait=True仍可能TimeoutError:响应必须满足status in ('succeeded','failed')才会退出循环;如果 provider 用了别的字段名,需要业务代码自己handle.get()解析。- 流式取消:取消前生成的 token 已经计费。
- 同一 process 内复用 client:SDK 自带连接池,频繁
new AceDataCloud()/AceDataCloud()会让 TLS 握手成为瓶颈。

