DokumentasiReferensi API

Streaming

Token bertahap lewat SSE dan penanda akhirnya.

Ringkasan

Set stream: true dan server mengirim jawaban bertahap lewat Server-Sent Events dengan Content-Type: text/event-stream.

POST/v1/chat/completionsbutuh API key

Bentuk potongannya mengikuti SDK OpenAI: delta, bukan pesan utuh yang dikirim berulang.

Permukaan Anthropic (POST /v1/messages) mengalirkan isi yang sama, tapi dengan nama event Anthropic (message_start, content_block_delta, message_delta, …) dan penanda akhir message_stop, bukan data: [DONE]. Lihat Format Anthropic.

Bentuk frame

text/event-stream
data: {"choices":[{"delta":{"role":"assistant"},"finish_reason":null,"index":0,"logprobs":null}],"created":1790075504,"id":"333e12313c324eefb2c16019504c53de","model":"deepseek-flash-v4.1","object":"chat.completion.chunk","usage":null}

data: {"choices":[{"delta":{"content":"Hi"},"finish_reason":null,"index":0,"logprobs":null}],"created":1790075504,"id":"333e12313c324eefb2c16019504c53de","model":"deepseek-flash-v4.1","object":"chat.completion.chunk","usage":null}

data: {"choices":[{"delta":{"function_call":{"arguments":"","name":""},"role":"assistant"},"finish_reason":"stop","index":0,"logprobs":null}],"created":1790075504,"id":"333e12313c324eefb2c16019504c53de","model":"deepseek-flash-v4.1","object":"chat.completion.chunk","usage":{"prompt_tokens":2792,"completion_tokens":2,"total_tokens":2794,"cached_tokens":640,"reasoning_tokens":0,"prompt_tokens_details":{"accepted_prediction_tokens":0,"audio_tokens":0,"cached_tokens":640,"reasoning_tokens":0,"rejected_prediction_tokens":0},"completion_tokens_details":{"accepted_prediction_tokens":0,"audio_tokens":0,"cached_tokens":0,"reasoning_tokens":0,"rejected_prediction_tokens":0}}}

data: {"choices":[],"object":"chat.completion.chunk","usage":{"completion_tokens":2,"prompt_tokens":792,"prompt_tokens_details":{"cached_tokens":640},"total_tokens":853},"created":1790075504,"model":"deepseek-flash-v4.1"}

data: [DONE]

Setiap frame satu baris data: berisi JSON, dipisahkan baris kosong.

Dua hal di contoh di atas sengaja tidak dirapikan, karena begitulah yang benar-benar dikirim server. Pertama, frame finish_reason membawa delta yang tidak kosong, termasuk objek function_call berisi string kosong, bahkan pada request yang tidak mengirim tool sama sekali. Kedua, frame itu bukan frame terakhir.

Format id juga tidak seragam. Pada streaming, id berupa string heksadesimal polos, sementara pada response non-streaming bentuknya chatcmpl-…. Perlakukan id sebagai opaque. Jangan diurai, jangan diandalkan polanya, dan jangan dijadikan kunci apa pun. Frame terakhir tidak membawa id sama sekali.

Frame terakhir, dan usage mana yang mengikat

Setelah frame finish_reason masih ada satu frame lagi: choices kosong, isinya hanya usage.

Satu perbedaan lain yang perlu kamu sadari: bentuk blok usage pada streaming tidak seragam. Pada contoh di atas, frame finish_reason membawa kelima field yang didokumentasikan di Chat completions plus dua blok rincian (prompt_tokens_details, completion_tokens_details).

Frame terakhir membawa himpunan field yang berbeda: hanya prompt_tokens, completion_tokens, total_tokens, dan prompt_tokens_details, tanpa cached_tokens maupun reasoning_tokens di tingkat atas.

Jadi jangan mengasumsikan satu bentuk yang tetap saat streaming. Baca field yang kamu butuhkan secara defensif, dan ambil angka yang mengikat dari frame terakhir. Response non-streaming sebaliknya selalu membawa tepat lima field yang sama: prompt_tokens, completion_tokens, total_tokens, cached_tokens, dan reasoning_tokens, tanpa blok rincian.

Penanda akhir

Aliran diakhiri baris data: [DONE].

Penalaran pada streaming

Pada model yang bernalar, reasoning_content datang lebih dulu di delta, baru content. Kalau model tidak sempat menghasilkan content sama sekali, penalarannya dikirim sebagai content pada frame yang sebelum frame finish_reason, jadi klien yang berhenti di frame terakhir tetap menerimanya.

Satu pengecualian, sama seperti di jalur non-streaming: kalau model memanggil tool, penalarannya tidak disalin ke content. Jadi pada aliran yang isinya panggilan tool, frame content memang tidak akan pernah datang, dan menunggunya berarti menggantung. Yang menandai aliran itu selesai adalah finish_reason: "tool_calls", bukan frame content. Lihat bagian berikutnya.

Panggilan tool pada streaming

Kalau kamu mengirim tools dan model memilih memanggilnya, delta.tool_calls datang bertahap, bukan sebagai satu object utuh.

Frame pertama membawa id dan name, dengan arguments masih kosong. Frame-frame setelahnya hanya membawa potongan arguments beserta index. Jadi gabungkan potongannya per index, lalu parse sekali di akhir; memparse tiap potongan akan gagal karena tidak ada satu pun yang utuh.

Frame terakhir membawa finish_reason: "tool_calls" beserta usage, dan aliran tetap ditutup data: [DONE]. Contoh frame aslinya ada di Pemanggilan tool.

Error di tengah aliran

Selama status line belum terkirim, kegagalan masih bisa dijawab sebagai JSON biasa dengan status yang benar, jadi kegagalan sebelum token pertama tidak datang sebagai aliran.

Setelah aliran mulai, status 200 sudah terkirim dan tidak bisa ditarik kembali. Kalau ada kegagalan di titik itu, error dikirim sebagai frame terakhir, lalu aliran tetap ditutup dengan data: [DONE]. Karena itu klien yang baik memeriksa isi frame, bukan hanya status HTTP-nya.

Kalau klien berhenti di tengah

Kalau koneksimu putus sebelum aliran selesai, server berhenti menarik data dari model. Tidak ada token yang terus dibakar untuk output yang tidak akan pernah dibaca siapa pun.

Request seperti itu dicatat sebagai dibatalkan klien, bukan sebagai kegagalan model. Pembedaan itu bukan detail internal: kegagalan model memengaruhi catatan kesehatan model, sementara pembatalan oleh klien tidak. Mencampur keduanya akan membuat statistik kami salah.

Satu pengecualian yang berbeda, dan hanya berlaku pada output yang benar-benar kosong: kalau model tidak menghasilkan content maupun reasoning_content sama sekali, request itu dianggap gagal. Itu bukan pembatalan, dan biayanya mengikuti aturan yang sama seperti kegagalan lain: dilepas penuh kalau tidak ada pemakaian yang tercatat, ditagihkan kalau tokennya sudah diproses.

Cek streaming