Streaming
Streaming mengirim jawaban sedikit demi sedikit selagi model menulis, sehingga aplikasimu bisa menampilkan teks langsung tanpa menunggu seluruh balasan selesai. Pakai streaming untuk antarmuka chat, untuk jawaban panjang, dan untuk model yang memerlukan waktu penalaran lebih lama. Ketiga endpoint chat mendukungnya: Chat completions, Messages, dan Responses.
Aktifkan streaming
Section titled “Aktifkan streaming”Setel "stream": true di body request. Response bertipe konten text/event-stream, dan setiap event mengikuti format endpoint yang kamu panggil.
| Endpoint | Frame | Stream berakhir dengan |
|---|---|---|
POST /v1/chat/completions | hanya baris data: | data: [DONE] |
POST /v1/messages | baris event: dan baris data: | event message_stop |
POST /v1/responses | baris event: dan baris data: | event response.completed atau response.failed |
Semua stream mengikuti aturan yang sama:
- Satu frame adalah satu event diikuti satu baris kosong. Baris
data:berisi objek JSON, kecuali barisdata: [DONE]di akhir stream Chat completions yang bukan JSON. Periksa[DONE]sebelum men-decode. - Baris yang diawali
:adalah komentar. kenari mengirim: keepalivesaat model diam cukup lama, misalnya selama prompt panjang atau saat model bernalar. Abaikan baris ini. SDK resmi sudah mengabaikannya. - Error yang terjadi sebelum stream dimulai, seperti key salah, request tidak valid, saldo kurang, atau kena batas laju, kembali sebagai error HTTP biasa dengan body JSON. Lihat Error.
- Setelah event pertama terkirim, status HTTP sudah 200. Kegagalan sesudahnya datang sebagai event di dalam stream, seperti dijelaskan untuk tiap format di bawah.
Field dan event pada tiap format API
Section titled “Field dan event pada tiap format API”| Chat completions | Messages | Responses | |
|---|---|---|---|
| Teks jawaban | delta.content | text_delta di content_block_delta | response.output_text.delta |
| Penalaran | delta.reasoning dan delta.reasoning_content, sebelum teks | blok thinking dengan event thinking_delta | item reasoning dengan event response.reasoning_summary_text.delta |
| Panggilan fungsi | delta.tool_calls, argumen bertahap | blok tool_use dengan event input_json_delta | item function_call dengan event response.function_call_arguments.delta |
| Pemakaian token | usage di chunk terakhir, bila jumlah token tidak nol | message_start dan message_delta | response.completed |
| Alasan selesai | finish_reason | stop_reason di message_delta | status di response.completed |
| Gagal setelah dimulai | chunk berisi objek error, lalu [DONE] | event error | event response.failed |
Penalaran dialirkan di ketiga format, mendahului teks jawaban. Hanya model penalaran yang menghasilkannya, dan pada sebagian request model bernalar sangat sedikit atau tidak sama sekali. Di Chat completions kamu bisa menghilangkannya dari stream dengan "reasoning": {"exclude": true}. Tokennya tetap dihasilkan dan ditagih. Untuk mengatur seberapa banyak model bernalar, lihat Penalaran.
Stream Chat completions
Section titled “Stream Chat completions”Setiap chunk adalah chat.completion.chunk dengan id yang sama dan model yang mengulang model yang kamu minta.
curl -N https://kenari.id/v1/chat/completions \ -H "Authorization: Bearer $KENARI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "step-3-7-flash:free", "stream": true, "messages": [{ "role": "user", "content": "Hitung dari 1 sampai 3." }] }'Flag -N mematikan buffering curl, jadi setiap event tercetak saat tiba. Stream-nya tampak seperti ini, dipersingkat:
data: {"id":"4f1c9a2e7b3d5a60","object":"chat.completion.chunk","created":1790000000,"model":"step-3-7-flash:free","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}
data: {"id":"4f1c9a2e7b3d5a60","object":"chat.completion.chunk","created":1790000000,"model":"step-3-7-flash:free","choices":[{"index":0,"delta":{"content":"1, 2"},"finish_reason":null}]}
data: {"id":"4f1c9a2e7b3d5a60","object":"chat.completion.chunk","created":1790000000,"model":"step-3-7-flash:free","choices":[{"index":0,"delta":{"content":", 3"},"finish_reason":null}]}
data: {"id":"4f1c9a2e7b3d5a60","object":"chat.completion.chunk","created":1790000000,"model":"step-3-7-flash:free","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":14,"completion_tokens":6,"total_tokens":20}}
data: [DONE]- Chunk pertama membawa
"role": "assistant". Tambahkan setiapdelta.contentsesudahnya untuk menyusun jawaban. - Penalaran datang di
delta.reasoningdandelta.reasoning_content, dengan teks yang sama di kedua nama. - Panggilan fungsi dibuka oleh satu chunk yang membawa
index,id,type, danfunction.namepanggilan itu, denganargumentskosong. Chunk berikutnya denganindexyang sama membawa potonganfunction.arguments. Gabungkan potongannya, lalu urai JSON-nya. Panggilan berakhir denganfinish_reasonbernilaitool_calls. Lihat Function calling. - Chunk terakhir berisi
deltakosong,finish_reason, danusagehanya bila jumlah token tidak nol.usage.prompt_tokens_detailsditambahkan hanya bila token cache di atas 0. Chunk usage milik kenari sendiri tidak bergantung padastream_options, jadi kamu tidak perluinclude_usage. Tidak ada chunk usage terpisah dengan arraychoiceskosong. - Bila server tool kenari mencari di web, satu chunk lagi dengan
delta.sourcesdatang tepat sebelum[DONE]. - Kegagalan setelah dimulai datang sebagai chunk dengan objek
errortanpachoices, diikuti[DONE]:
data: {"error":{"message":"the model's provider interrupted the response before it finished. send the request again as a new request","type":"upstream_error","param":null,"code":"stream_interrupted"}}
data: [DONE]Dengan SDK Python OpenAI:
import osfrom openai import OpenAI
client = OpenAI(base_url="https://kenari.id/v1", api_key=os.environ["KENARI_API_KEY"])
stream = client.chat.completions.create( model="step-3-7-flash:free", messages=[{"role": "user", "content": "Hitung dari 1 sampai 3."}], stream=True,)for chunk in stream: if chunk.choices and chunk.choices[0].delta.content: print(chunk.choices[0].delta.content, end="", flush=True) if chunk.usage: print("\ntoken:", chunk.usage.total_tokens)Dengan SDK JavaScript OpenAI:
import OpenAI from "openai";
const client = new OpenAI({ baseURL: "https://kenari.id/v1", apiKey: process.env.KENARI_API_KEY,});
const stream = await client.chat.completions.create({ model: "step-3-7-flash:free", messages: [{ role: "user", content: "Hitung dari 1 sampai 3." }], stream: true,});for await (const chunk of stream) { const text = chunk.choices[0]?.delta?.content; if (text) process.stdout.write(text);}Stream Messages
Section titled “Stream Messages”Setiap frame punya baris event: yang menamai event dan baris data: berisi JSON. Base URL untuk SDK Anthropic adalah https://kenari.id, seperti dijelaskan di Messages.
| Event | Arti |
|---|---|
message_start | Membuka pesan dengan id, model, dan usage awal. |
content_block_start | Membuka blok pada index. Tipenya text, tool_use (dengan id dan name), atau thinking. |
content_block_delta | Potongan blok: text_delta, input_json_delta, thinking_delta, atau signature_delta. |
content_block_stop | Menutup blok. |
message_delta | delta.stop_reason dan usage akhir, dengan output_tokens dan field cache bila ada. |
message_stop | Akhir stream. |
ping | Sinyal bahwa koneksi masih aktif, dari provider. Abaikan. |
error | Pesan gagal setelah dimulai. Tidak ada message_stop sesudahnya. |
Stream singkat:
event: message_startdata: {"type":"message_start","message":{"id":"4f1c9a2e7b3d5a60","type":"message","role":"assistant","model":"step-3-7-flash:free","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":14,"output_tokens":0}}}
event: content_block_startdata: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}
event: content_block_deltadata: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"1, 2, 3"}}
event: content_block_stopdata: {"type":"content_block_stop","index":0}
event: message_deltadata: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":14,"output_tokens":6}}
event: message_stopdata: {"type":"message_stop"}- Saat model bernalar, blok
thinkingdatang lebih dulu, dengan teksnya di eventthinking_deltayang disusulsignature_delta. Signature-nya bisa kosong. Kirim blok itu kembali tanpa diubah di giliran berikutnya. - Panggilan fungsi adalah blok
tool_useyanginput-nya dimulai sebagai{}. Argumennya datang sebagai stringpartial_jsondi eventinput_json_delta. Gabungkan, lalu urai JSON-nya saat blok ditutup. Pesan berakhir denganstop_reasonbernilaitool_use. - Kegagalan setelah dimulai adalah event
error.error.type-nyaapi_erroratauinvalid_request_error. Bacaerror.messagedan anggap stream sudah selesai.
event: errordata: {"type":"error","error":{"type":"api_error","message":"the model's provider interrupted the response before it finished. send the request again as a new request"}}Dengan SDK Python Anthropic:
import osimport anthropic
client = anthropic.Anthropic(base_url="https://kenari.id", api_key=os.environ["KENARI_API_KEY"])
with client.messages.stream( model="step-3-7-flash:free", max_tokens=512, messages=[{"role": "user", "content": "Hitung dari 1 sampai 3."}],) as stream: for text in stream.text_stream: print(text, end="", flush=True) message = stream.get_final_message() print("\ntoken:", message.usage.output_tokens)Dengan SDK TypeScript Anthropic:
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({ baseURL: "https://kenari.id", apiKey: process.env.KENARI_API_KEY,});
const stream = client.messages.stream({ model: "step-3-7-flash:free", max_tokens: 512, messages: [{ role: "user", content: "Hitung dari 1 sampai 3." }],});stream.on("text", (text) => process.stdout.write(text));const message = await stream.finalMessage();console.log("\ntoken:", message.usage.output_tokens);Stream Responses
Section titled “Stream Responses”Setiap frame punya baris event: dan baris data:. Setiap event punya type dan sequence_number yang bertambah mulai dari 0.
| Event | Arti |
|---|---|
response.created | Event pertama. Membawa id response, model, dan status bernilai in_progress. |
response.output_item.added | Sebuah item dimulai pada output_index: reasoning, message, atau function_call. |
response.reasoning_summary_part.added | Item reasoning membuka satu bagian ringkasannya. |
response.reasoning_summary_text.delta | Potongan teks penalaran. |
response.reasoning_summary_text.done | Teks penalaran lengkap. |
response.reasoning_summary_part.done | Bagian ringkasan selesai. |
response.output_text.delta | Potongan teks jawaban. |
response.function_call_arguments.delta | Potongan argumen fungsi. |
response.function_call_arguments.done | String argumen lengkap. |
response.output_item.done | Item selesai dan membawa isi lengkapnya. |
response.completed | Event terakhir. Membawa output lengkap dan usage. |
response.failed | Response gagal setelah dimulai. |
kenari tidak mengirim response.in_progress, response.content_part.added, response.content_part.done, response.output_text.done, atau response.incomplete. Response yang terpotong batas token berakhir dengan response.completed yang status-nya incomplete.
- Penalaran datang sebagai satu item
reasoningsebelum message atau function call apa pun. Teksnya mengalir lewat eventresponse.reasoning_summary_text.delta. - Panggilan fungsi adalah item
function_call.arguments-nya mengalir lewatresponse.function_call_arguments.delta, danresponse.function_call_arguments.donemembawa string lengkapnya. response.completedmemuatusageakhir, termasukinput_tokens_details.cached_tokens.- Kegagalan setelah dimulai adalah event
response.failed, denganresponse.error.codedanresponse.error.message. Tidak ada event sesudahnya.
event: response.faileddata: {"type":"response.failed","response":{"id":"resp_4f1c9a2e7b3d5a60","status":"failed","model":"step-3-7-flash:free","error":{"code":"stream_interrupted","message":"the model's provider interrupted the response before it finished. send the request again as a new request"}},"sequence_number":4}Dengan SDK Python OpenAI:
import osfrom openai import OpenAI
client = OpenAI(base_url="https://kenari.id/v1", api_key=os.environ["KENARI_API_KEY"])
stream = client.responses.create( model="step-3-7-flash:free", input="Hitung dari 1 sampai 3.", stream=True,)for event in stream: if event.type == "response.output_text.delta": print(event.delta, end="", flush=True)Dengan SDK JavaScript OpenAI:
import OpenAI from "openai";
const client = new OpenAI({ baseURL: "https://kenari.id/v1", apiKey: process.env.KENARI_API_KEY,});
const stream = await client.responses.create({ model: "step-3-7-flash:free", input: "Hitung dari 1 sampai 3.", stream: true,});for await (const event of stream) { if (event.type === "response.output_text.delta") process.stdout.write(event.delta);}Batasan dan jebakan
Section titled “Batasan dan jebakan”- Stream tidak bisa dilanjutkan. Setelah error, kirim request lagi sebagai request baru. Status sudah 200 saat stream dimulai, jadi logika retry yang bereaksi pada error HTTP tidak melihat kegagalannya.
- Jangan memecah stream berdasarkan potongan jaringan. Satu potongan bisa memuat beberapa event atau separuh event. Baca per frame, atau serahkan ke SDK.
- Panggilan fungsi belum berupa JSON yang valid sampai potongan terakhirnya tiba. Urai setelah blok atau item ditutup.
EventSourcedi browser hanya mengirim requestGET. Pakaifetchdengan readable stream, atau SDK.- kenari mengirim
Cache-Control: no-cache, no-transformdanX-Accel-Buffering: no. Bila kamu memasang proxy sendiri di depannya, matikan buffering response di sana juga, atau event akan tiba bergelombang. - Pakai streaming untuk jawaban panjang. Request tanpa streaming menahan koneksi tetap terbuka sampai seluruh jawaban siap, dan proxy atau load balancer di antara kamu dan kenari bisa menutupnya lebih dulu.