Lewati ke konten
kenari.

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.

Setel "stream": true di body request. Response bertipe konten text/event-stream, dan setiap event mengikuti format endpoint yang kamu panggil.

EndpointFrameStream berakhir dengan
POST /v1/chat/completionshanya baris data:data: [DONE]
POST /v1/messagesbaris event: dan baris data:event message_stop
POST /v1/responsesbaris 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 baris data: [DONE] di akhir stream Chat completions yang bukan JSON. Periksa [DONE] sebelum men-decode.
  • Baris yang diawali : adalah komentar. kenari mengirim : keepalive saat 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.
Chat completionsMessagesResponses
Teks jawabandelta.contenttext_delta di content_block_deltaresponse.output_text.delta
Penalarandelta.reasoning dan delta.reasoning_content, sebelum teksblok thinking dengan event thinking_deltaitem reasoning dengan event response.reasoning_summary_text.delta
Panggilan fungsidelta.tool_calls, argumen bertahapblok tool_use dengan event input_json_deltaitem function_call dengan event response.function_call_arguments.delta
Pemakaian tokenusage di chunk terakhir, bila jumlah token tidak nolmessage_start dan message_deltaresponse.completed
Alasan selesaifinish_reasonstop_reason di message_deltastatus di response.completed
Gagal setelah dimulaichunk berisi objek error, lalu [DONE]event errorevent 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.

Setiap chunk adalah chat.completion.chunk dengan id yang sama dan model yang mengulang model yang kamu minta.

Terminal window
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 setiap delta.content sesudahnya untuk menyusun jawaban.
  • Penalaran datang di delta.reasoning dan delta.reasoning_content, dengan teks yang sama di kedua nama.
  • Panggilan fungsi dibuka oleh satu chunk yang membawa index, id, type, dan function.name panggilan itu, dengan arguments kosong. Chunk berikutnya dengan index yang sama membawa potongan function.arguments. Gabungkan potongannya, lalu urai JSON-nya. Panggilan berakhir dengan finish_reason bernilai tool_calls. Lihat Function calling.
  • Chunk terakhir berisi delta kosong, finish_reason, dan usage hanya bila jumlah token tidak nol. usage.prompt_tokens_details ditambahkan hanya bila token cache di atas 0. Chunk usage milik kenari sendiri tidak bergantung pada stream_options, jadi kamu tidak perlu include_usage. Tidak ada chunk usage terpisah dengan array choices kosong.
  • Bila server tool kenari mencari di web, satu chunk lagi dengan delta.sources datang tepat sebelum [DONE].
  • Kegagalan setelah dimulai datang sebagai chunk dengan objek error tanpa choices, 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 os
from 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);
}

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.

EventArti
message_startMembuka pesan dengan id, model, dan usage awal.
content_block_startMembuka blok pada index. Tipenya text, tool_use (dengan id dan name), atau thinking.
content_block_deltaPotongan blok: text_delta, input_json_delta, thinking_delta, atau signature_delta.
content_block_stopMenutup blok.
message_deltadelta.stop_reason dan usage akhir, dengan output_tokens dan field cache bila ada.
message_stopAkhir stream.
pingSinyal bahwa koneksi masih aktif, dari provider. Abaikan.
errorPesan gagal setelah dimulai. Tidak ada message_stop sesudahnya.

Stream singkat:

event: message_start
data: {"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_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"1, 2, 3"}}
event: content_block_stop
data: {"type":"content_block_stop","index":0}
event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":14,"output_tokens":6}}
event: message_stop
data: {"type":"message_stop"}
  • Saat model bernalar, blok thinking datang lebih dulu, dengan teksnya di event thinking_delta yang disusul signature_delta. Signature-nya bisa kosong. Kirim blok itu kembali tanpa diubah di giliran berikutnya.
  • Panggilan fungsi adalah blok tool_use yang input-nya dimulai sebagai {}. Argumennya datang sebagai string partial_json di event input_json_delta. Gabungkan, lalu urai JSON-nya saat blok ditutup. Pesan berakhir dengan stop_reason bernilai tool_use.
  • Kegagalan setelah dimulai adalah event error. error.type-nya api_error atau invalid_request_error. Baca error.message dan anggap stream sudah selesai.
event: error
data: {"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 os
import 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);

Setiap frame punya baris event: dan baris data:. Setiap event punya type dan sequence_number yang bertambah mulai dari 0.

EventArti
response.createdEvent pertama. Membawa id response, model, dan status bernilai in_progress.
response.output_item.addedSebuah item dimulai pada output_index: reasoning, message, atau function_call.
response.reasoning_summary_part.addedItem reasoning membuka satu bagian ringkasannya.
response.reasoning_summary_text.deltaPotongan teks penalaran.
response.reasoning_summary_text.doneTeks penalaran lengkap.
response.reasoning_summary_part.doneBagian ringkasan selesai.
response.output_text.deltaPotongan teks jawaban.
response.function_call_arguments.deltaPotongan argumen fungsi.
response.function_call_arguments.doneString argumen lengkap.
response.output_item.doneItem selesai dan membawa isi lengkapnya.
response.completedEvent terakhir. Membawa output lengkap dan usage.
response.failedResponse 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 reasoning sebelum message atau function call apa pun. Teksnya mengalir lewat event response.reasoning_summary_text.delta.
  • Panggilan fungsi adalah item function_call. arguments-nya mengalir lewat response.function_call_arguments.delta, dan response.function_call_arguments.done membawa string lengkapnya.
  • response.completed memuat usage akhir, termasuk input_tokens_details.cached_tokens.
  • Kegagalan setelah dimulai adalah event response.failed, dengan response.error.code dan response.error.message. Tidak ada event sesudahnya.
event: response.failed
data: {"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 os
from 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);
}
  • 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.
  • EventSource di browser hanya mengirim request GET. Pakai fetch dengan readable stream, atau SDK.
  • kenari mengirim Cache-Control: no-cache, no-transform dan X-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.