> ## Documentation Index
> Fetch the complete documentation index at: https://docs.acedata.cloud/llms.txt
> Use this file to discover all available pages before exploring further.

# SDK zadania polling i odpowiedzi strumieniowe

> Platform API guide - Ace Data Cloud

Usługi na Ace Data Cloud dzielą się na dwa rodzaje w zależności od trybu odpowiedzi:

| Typ | Typowe usługi | Tryb wywołania |
| - | - | - |
| **Synchronizacja** | NanoBanana / Flux / Seedream / Chat Completions (nie-strumieniowe) / Google Search | Jedno HTTP, wynik w ciele odpowiedzi |
| **Odpowiedź strumieniowa** | Chat Completions (`stream: true`) | SSE, wiele ramek przesyłanych token po tokenie |
| **Zadania asynchroniczne** | Midjourney / Sora / Veo / Luma / Kling / Hailuo / Suno / Pixverse / Seedance | Najpierw utwórz zadanie, aby uzyskać `task_id`, następnie polling `/<provider>/tasks` |

Artykuł koncentruje się na dwóch ostatnich kategoriach: **polling TaskHandle dla zadań asynchronicznych** oraz **szczegóły, pułapki i różnice językowe w odpowiedziach strumieniowych czatu**.

## I. TaskHandle — jednolita abstrakcja zadań asynchronicznych

Trzy SDK opakowują zadania asynchroniczne w `TaskHandle`, oferując te same 4 metody:

| Metoda | Zachowanie |
| - | - |
| `get()` | Pobierz najnowszy stan (`POST /<provider>/tasks {id, action: "retrieve"}`) |
| `is_completed()` / `isCompleted()` | `get()` raz, sprawdź, czy `status` to `succeeded` / `failed` |
| `wait()` | Blokujący polling, aż do `succeeded` / `failed` lub przekroczenia `max_wait` |
| Atrybut `result` | Ostatnia pełna odpowiedź uzyskana z `wait()`; przed wywołaniem `null` |

### Dwa sposoby wywołania do tworzenia zadań

Każdy zasób asynchroniczny (`images.generate` / `video.generate` / `audio.generate`) ma parametr `wait`:

* `wait=False` (domyślnie): natychmiast zwraca `TaskHandle`, kod biznesowy decyduje, kiedy przeprowadzić polling.
* `wait=True`: SDK wewnętrznie wywołuje `handle.wait()`, funkcja zwraca odpowiedź po zakończeniu. **Używaj tylko wtedy, gdy masz pewność, że docelowe API na pewno zwróci pole `status: succeeded`** — nieliczni dostawcy nie przestrzegają tej zasady, co sprawi, że `wait` będzie czekać do `max_wait`, zanim zgłosi `TimeoutError`.

### Różnice jednostek (⚠️ Konieczne do przeczytania)

Jednostki `poll_interval` i `max_wait` są **różne w trzech językach**, co jest powszechnym punktem zapalnym podczas migracji między językami:

| Język | Jednostka `poll_interval` | Jednostka `max_wait` | Wartość domyślna |
| - | - | - | - |
| **TypeScript** | **milisekundy** | **milisekundy** | `pollInterval=3000`, `maxWait=600000` |
| **Python** | **sekundy** | **sekundy** | `poll_interval=3.0`, `max_wait=600.0` |
| Go | (TaskHandle nie został jeszcze ujawniony w SDK Go) | — | — |

> Przekształcenie `{ pollInterval: 3000 }` z TS na sekundy w Pythonie jako `poll_interval=3000` spowoduje, że SDK będzie czekać 50 minut, zanim przeprowadzi drugie polling.

### Przykład: Python jawny polling Midjourney

```python theme={null}
import os, time
from acedatacloud import AceDataCloud

client = AceDataCloud(api_token=os.environ["ACEDATACLOUD_API_TOKEN"])

# wait=False natychmiast uzyskuje handle
handle = client.images.generate(
    provider="midjourney",
    prompt="a cinematic photo of a banana wearing a tuxedo",
    wait=False,
)
print("task_id", handle.id)

t0 = time.time()
result = handle.wait(poll_interval=3.0, max_wait=180.0)
print("elapsed_s", round(time.time() - t0, 1))
print("status", result.get("response", result).get("status"))
print("images", [it.get("image_url") for it in (result.get("response", result).get("data") or [])])
```

Cały kod wykonuje:

1. `images.generate(..., wait=False)` przesyła `prompt` do API Midjourney, natychmiast uzyskując `handle`, bez blokowania.
2. `handle.wait(poll_interval=3.0, max_wait=180.0)` wewnętrznie co 3 sekundy wykonuje POST do `/midjourney/tasks`, aż `status` zmieni się na `succeeded` lub `failed`, lub całkowity czas przekroczy 180 sekund, zgłaszając `TimeoutError`.
3. Po zakończeniu `result["response"]["data"]` zazwyczaj zawiera 4 obrazy (domyślnie 2x2 grid w Midjourney).

### Przykład: TypeScript jawny polling

```ts theme={null}
import { AceDataCloud } from '@acedatacloud/sdk';

const client = new AceDataCloud();

// wait: false natychmiast uzyskuje handle
const handle: any = await client.images.generate({
  provider: 'midjourney',
  prompt: 'a cinematic photo of a banana wearing a tuxedo',
  wait: false,
});
console.log('task_id', handle.id);

const t0 = Date.now();
const result: any = await handle.wait({ pollInterval: 3000, maxWait: 180_000 });
console.log('elapsed_s', ((Date.now() - t0) / 1000).toFixed(1));
const response = result.response ?? result;
console.log('status', response.status);
console.log('images', (response.data ?? []).map((it: any) => it.image_url));
```

### Wybór między synchronizacją a zadaniami asynchronicznymi

Jeśli twój dostawca sam w sobie generuje obrazy synchronnie (NanoBanana / Flux / Seedream), **nie przekazuj `wait`**:

```python theme={null}
# ✅ Zalecane
res = client.images.generate(provider="nano-banana", prompt="...")
url = res["data"][0]["image_url"]

# ❌ Przykład negatywny: spowoduje, że SDK wewnętrznie będzie przeprowadzać polling /nano-banana/tasks, marnując RTT
res = client.images.generate(provider="nano-banana", prompt="...", wait=True)
```

Metoda oceny jest prosta: jeśli w dokumentacji API docelowego **nie ma** pary `task_id` + `/tasks`, to jest to generacja synchronna; w odpowiedzi generacji synchronnej pole `data` już zawiera ostateczny wynik.

### Protokół wewnętrzny TaskHandle

`TaskHandle.get()` wywołuje:

```http theme={null}
POST {API_BASE}/<provider>/tasks
Authorization: Bearer {token}
Content-Type: application/json

{"id": "<task_id>", "action": "retrieve"}
```

Odpowiedź ma jednolitą strukturę:

```json theme={null}
{
  "task_id": "...",
  "trace_id": "...",
  "response": {
    "status": "pending | running | succeeded | failed",
    "data": [...]
  }
}
```

SDK obsługuje również **stare odpowiedzi bez zewnętrznego opakowania `response`** — odczytuje bezpośrednio górny poziom `status`, więc przełączanie między nowymi a starymi odpowiedziami nie wpływa na kod biznesowy.

## II. SSE odpowiedź strumieniowa (chat.completions)

`chat.completions.create(stream=True)` jest obecnie jedynym interfejsem strumieniowym w SDK (strumienie audio / wideo jeszcze nie są obsługiwane). Styl iteracji w trzech językach jest różny:

| Język | Iteracja | Mechanizm anulowania |
| - | - | - |
| TypeScript | `for await (const chunk of stream)` | `AbortController` przekazany do fetch |
| Python | `for chunk in client.openai.chat.completions.create(..., stream=True)` | Wystarczy wyjść z pętli (połączenie zamykane automatycznie przez SDK) |
| Go | `chunks, errs := ...CreateStream(ctx, req)` → `for chunk := range chunks` | Anuluj `context.Context` |

### TypeScript

```ts theme={null}
import { AceDataCloud } from '@acedatacloud/sdk';

const client = new AceDataCloud();

const stream: any = await client.openai.chat.completions.create({
  model: 'gpt-4o-mini',
  messages: [{ role: 'user', content: 'Policz od 1 do 5 oddzielając spacjami. Tylko liczby.' }],
  max_tokens: 30,
  temperature: 0,
  stream: true
});

let chunks = 0;
let collected = '';
for await (const chunk of stream) {
  chunks++;
  const delta = chunk?.choices?.[0]?.delta?.content;
  if (delta) collected += delta;
}
console.log('chunks', chunks);
console.log('collected', collected);
```

真实运行结果：

```text theme={null}
total_elapsed_ms 2616
first_chunk_ms 2481
chunks 13
collected 1 2 3 4 5
```

### Python

```python theme={null}
import os
from acedatacloud import AceDataCloud

client = AceDataCloud(api_token=os.environ["ACEDATACLOUD_API_TOKEN"])

chunks = 0
collected = []
for chunk in client.openai.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "Policz od 1 do 5 oddzielając spacjami. Tylko liczby."}],
    max_tokens=30,
    temperature=0,
    stream=True,
):
    chunks += 1
    delta = (chunk.get("choices") or [{}])[0].get("delta", {}).get("content")
    if delta:
        collected.append(delta)
print("chunks", chunks)
print("collected", "".join(collected))
```

真实运行结果：

```text theme={null}
total_elapsed_ms 2111
first_chunk_ms 2104
chunks 12
collected 1 2 3 4 5
```

### Go

```go theme={null}
chunks, errs := client.OpenAI().Chat().Completions().CreateStream(ctx, adc.ChatCompletionRequest{
    Model:     "gpt-4o-mini",
    Messages:  []map[string]any{{"role": "user", "content": "Policz od 1 do 5 oddzielając spacjami. Tylko liczby."}},
    MaxTokens: 30,
})
cnt := 0
collected := ""
for chunk := range chunks {
    cnt++
    if ch, ok := chunk["choices"].([]any); ok && len(ch) > 0 {
        if d, ok := ch[0].(map[string]any)["delta"].(map[string]any); ok {
            if s, ok := d["content"].(string); ok {
                collected += s
            }
        }
    }
}
if e, ok := <-errs; ok && e != nil {
    log.Println("stream_err", e)
}
fmt.Println("chunks", cnt, "collected", collected)
```

真实运行结果：

```text theme={null}
total_elapsed_ms 1816
first_chunk_ms 1633
chunks 13
collected 1 2 3 4 5
```

### 流式 chunk 的结构

每一帧 chunk 都是一个 OpenAI 兼容的 `chat.completion.chunk`：

```json theme={null}
{
  "id": "chatcmpl-...",
  "object": "chat.completion.chunk",
  "model": "gpt-4o-mini",
  "choices": [
    {
      "index": 0,
      "delta": {
        "content": " 3"
      },
      "finish_reason": null
    }
  ]
}
```

* 第一个 chunk 通常带 `delta.role: "assistant"` 但 `content` 为空。
* 中间的 chunk 每个带 `delta.content`，可以直接拼接。
* 最后一个 chunk `delta` 为空、`finish_reason` 是 `stop` / `length` / `content_filter`。

### 中途取消

| 语言 | 取消方式 |
| - | - |
| TypeScript | W `create()` wywołaniu przekaż `signal: abortController.signal`, wywołaj `abortController.abort()` |
| Python | `break` wyjdź z pętli `for`, SDK w `__exit__` zamyka strumień HTTPx |
| Go | Na `ctx` przekazanym w `NewClient` wywołaj `cancel()`, kanał `chunks` natychmiast się zamknie |

提前取消已经计费的 token —— 取消时刻之前生成的 token 还是会按实际消耗扣费。

## 三、超时和重试

三种 SDK 共享同一套重试策略：

| 触发条件 | 行为 |
| - | - |
| HTTP 408 / 409 / 429 / 5xx | Domyślnie ponów 2 razy, wykładnicze opóźnienie 1s → 2s → 4s |
| 网络层错误（DNS、连接被拒、TLS 失败） | To samo |
| 401 / 403 / 404 / 422 | Nie ponawiaj, bezpośrednio zgłoś odpowiedni typ błędu |
| 流式（`stream=True`）请求 | **Nie ponawiaj**——pierwsza ramka już została wysłana, więc nie można jej odtworzyć |
| 显式 `timeout` 触发 | Zgłoś `APITimeoutError`（Python）/ `TimeoutError`（TS）/ `context.DeadlineExceeded`（Go） |

要禁用重试：构造客户端时传 `max_retries=0` / `maxRetries: 0` / `WithMaxRetries(0)`。

异步任务（TaskHandle）的轮询本身不受 `max_retries` 影响——它的循环是业务级的而不是 HTTP 级的，靠 `max_wait` 控制总时长。

## 四、常见陷阱

1. **同步 provider 不要传 `wait`**：NanoBanana / Flux / Seedream 都是同步生成，强行 `wait=True` 会让 SDK 去轮询一个根本不会更新的 `tasks` 接口。
2. **TaskHandle 单位差异**：Python 是秒、TS 是毫秒，跨语言移植时一定换算。
3. **`wait=True` 仍可能 `TimeoutError`**：响应必须满足 `status in ('succeeded','failed')` 才会退出循环；如果 provider 用了别的字段名，需要业务代码自己 `handle.get()` 解析。
4. **流式取消**：取消前生成的 token 已经计费。
5. **同一 process 内复用 client**：SDK 自带连接池，频繁 `new AceDataCloud()` / `AceDataCloud()` 会让 TLS 握手成为瓶颈。

## 了解更多

* 📘 [TypeScript SDK 接入教程](https://platform.acedata.cloud/documents/sdk-typescript)
* 🐍 [Python SDK 接入教程](https://platform.acedata.cloud/documents/sdk-python)
* 🟦 [Go SDK 接入教程](https://platform.acedata.cloud/documents/sdk-go)
* 🔌 [SDK + X402 支付钩子](https://platform.acedata.cloud/documents/sdk-x402-payment)
* 📦 [SDK monorepo 源码](https://github.com/AceDataCloud/SDK)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.