Streaming
Setel stream ke true dan balasan datang sebagai server-sent events saat model menuliskannya: penalaran lebih dulu, lalu jawaban, lalu chunk terakhir dengan finish reason dan usage. Gunakan kapan pun seseorang menunggu teksnya, dan untuk balasan yang panjang.
POST https://api.shannon-ai.com/v1/chat/completions
SDK OpenAI memberi Anda chunk satu per satu. Baca setiap chunk berdasarkan isinya: sepotong penalaran, sepotong jawaban, pemanggilan tool, atau akhir balasan.
from openai import OpenAI, APIError
client = OpenAI(
api_key="YOUR_API_KEY",
base_url="https://api.shannon-ai.com/v1",
)
stream = client.chat.completions.create(
model="shannon-3",
messages=[{"role": "user", "content": "Explain server-sent events in three sentences."}],
stream=True,
)
answer, reasoning, tool_calls = [], [], []
finish_reason = usage = None
try:
for chunk in stream:
if chunk.usage:
usage = chunk.usage
if not chunk.choices:
continue
choice = chunk.choices[0]
delta = choice.delta
thought = getattr(delta, "reasoning_content", None)
if thought:
reasoning.append(thought)
if delta.content:
answer.append(delta.content)
print(delta.content, end="", flush=True)
for call in delta.tool_calls or []:
tool_calls.append((call.id, call.function.name, call.function.arguments))
if choice.finish_reason:
finish_reason = choice.finish_reason
except APIError as error:
# A failure after the stream started arrives as an error chunk.
print(f"\nstream failed: {error}")
print(f"\n{finish_reason} {usage}") import OpenAI from "openai";
const client = new OpenAI({
apiKey: "YOUR_API_KEY",
baseURL: "https://api.shannon-ai.com/v1",
});
const stream = await client.chat.completions.create({
model: "shannon-3",
messages: [{ role: "user", content: "Explain server-sent events in three sentences." }],
stream: true,
});
let answer = "";
let reasoning = "";
const toolCalls = [];
let finishReason = null;
let usage = null;
try {
for await (const chunk of stream) {
if (chunk.usage) usage = chunk.usage;
const choice = chunk.choices?.[0];
if (!choice) continue;
const delta = choice.delta;
if (delta.reasoning_content) reasoning += delta.reasoning_content;
if (delta.content) {
answer += delta.content;
process.stdout.write(delta.content);
}
for (const call of delta.tool_calls ?? []) {
toolCalls.push({ id: call.id, name: call.function.name, arguments: call.function.arguments });
}
if (choice.finish_reason) finishReason = choice.finish_reason;
}
} catch (error) {
// A failure after the stream started arrives as an error chunk.
console.error("\nstream failed:", error.message);
}
console.log("\n", finishReason, usage); curl -N https://api.shannon-ai.com/v1/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "shannon-3",
"messages": [{"role": "user", "content": "Explain server-sent events in three sentences."}],
"stream": true
}' Cara stream dibingkai
- Balasan berstatus
200dengan headercontent-type: text/event-streamdancache-control: no-cache. - Setiap event adalah satu baris yang diawali
data:dan diikuti baris kosong. Teks setelahdata:adalah satu chunk JSON. Tidak ada barisevent:atauid:. - Baris yang diawali titik dua, seperti
: ping, adalah komentar yang menjaga koneksi tetap terbuka. Lewati. - Stream berakhir dengan
data: [DONE]. Stream yang berhenti tanpanya tidak lengkap.
Chunk secara berurutan
Setiap chunk adalah objek chat.completion.chunk:
{
"id": "chatcmpl-5f0c1e7a9b3d4c62a8e1f07d2b46c9a3",
"object": "chat.completion.chunk",
"created": 1791625200,
"model": "shannon-3",
"choices": [
{
"index": 0,
"delta": {
"content": "Server-sent"
},
"finish_reason": null
}
]
} Semua chunk dalam satu stream membawa id, created, dan model yang sama. choices selalu berisi satu entri. delta-nya berisi yang baru, dan finish_reason adalah null sampai chunk terakhir.
| Key di delta | Tipe | Deskripsi | Dikirim oleh |
|---|---|---|---|
role | string | Selalu assistant. Datang sekali, di awal stream. | Model open-weight hosted, shannon-2-lite, shannon-2-pro |
reasoning_content | string | Sepotong penalaran model. Gabungkan potongan secara berurutan. | Model open-weight hosted, keluarga Shannon 3, shannon-1.6-pro, shannon-coder-1 |
content | string | Sepotong jawaban. Gabungkan potongan secara berurutan. | Semua model |
tool_calls | array | Satu pemanggilan tool yang lengkap. Lihat Tool call deltas. | Semua model |
Penalaran datang lebih dulu, lalu jawaban, lalu pemanggilan tool apa pun. Baca chunk berdasarkan key di delta-nya, bukan berdasarkan posisinya dalam stream: delta dapat berisi role bersama potongan teks pertama, dan yang terakhir kosong.
Satu stream utuh sebagaimana dikirim. Agar baris tetap pendek, id, object, created, dan model dihilangkan dari tiap chunk di sini.
data: {"choices":[{"index":0,"delta":{"reasoning_content":"Three sentences:"},"finish_reason":null}]}
data: {"choices":[{"index":0,"delta":{"reasoning_content":" what it is, how it is framed, why it is used."},"finish_reason":null}]}
: ping
data: {"choices":[{"index":0,"delta":{"content":"Server-sent"},"finish_reason":null}]}
data: {"choices":[{"index":0,"delta":{"content":" events let a server push text to a client over one HTTP reply."},"finish_reason":null}]}
data: {"choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":1190,"completion_tokens":84,"total_tokens":1274}}
data: [DONE] Delta penalaran
Model yang bernalar mengirim penalarannya di delta.reasoning_content sebelum jawaban. Pisahkan kedua teks itu: tampilkan penalaran sebagai bagian terpisah yang dapat dilipat, atau abaikan.
Model open-weight hosted tidak mengirim penalaran ketika reasoning_effort adalah off, dan juga tidak ketika permintaan memiliki response_format.
shannon-2-lite dan shannon-2-pro juga bernalar sebelum menjawab. Pada endpoint ini, keduanya mengirim baris komentar : thinking selama bernalar, lalu jawabannya.
reasoning_content tidak ada di model bertipe pada SDK OpenAI. Di Python, bacalah dengan getattr(delta, "reasoning_content", None); di JavaScript, delta.reasoning_content berfungsi apa adanya.
Tool call deltas
Pemanggilan tool datang utuh: satu chunk per panggilan, yang delta-nya berisi panggilan dengan string arguments yang lengkap. index menomori panggilan dalam balasan mulai dari 0. Kode yang menggabungkan arguments potong demi potong berfungsi tanpa perubahan, karena ia menggabungkan satu potongan.
{
"tool_calls": [
{
"index": 0,
"id": "call_3d9a7c1e5b2f4a60c8e1d7f09b24a6c5",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"Paris\"}"
}
}
]
} Setelah panggilan, chunk terakhir memiliki finish_reason tool_calls.
Ketika permintaan memiliki tools, shannon-1.6-lite, shannon-1.6-pro, dan shannon-coder-1 mengirim teks jawaban sebagai satu delta di akhir balasan.
Chunk terakhir
Chunk terakhir sebelum data: [DONE] memiliki delta kosong, finish_reason, dan usage permintaan, semuanya dalam chunk yang sama. Setiap stream memilikinya; Anda tidak memerlukan stream_options.
{
"id": "chatcmpl-5f0c1e7a9b3d4c62a8e1f07d2b46c9a3",
"object": "chat.completion.chunk",
"created": 1791625200,
"model": "shannon-3",
"choices": [
{
"index": 0,
"delta": {},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 1190,
"completion_tokens": 84,
"total_tokens": 1274
}
} Pada model open-weight hosted, usage juga memiliki prompt_tokens_details.cached_tokens dan completion_tokens_details.reasoning_tokens.
Setelah permintaan dengan web_search: true yang pencariannya menemukan sesuatu, chunk terakhir juga membawa sources, dan satu chunk sebelumnya membawa tanda kutipan sebagai delta.annotations. Pencarian Web Terintegrasi
| finish_reason | Deskripsi | Dikirim oleh |
|---|---|---|
stop | Model menyelesaikan jawabannya, atau string stop muncul. | Semua model |
tool_calls | Balasan berisi satu atau lebih pemanggilan tool. | Semua model |
length | Balasan terpotong pada batas output. | Keluarga Shannon 3, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 |
Field usage dijelaskan bersama endpoint. Chat Completions
Baris keep-alive
Selama model bekerja dan belum ada yang dikirim, stream membawa baris komentar. Baris itu tidak berisi data. Parser SSE melewatinya sendiri; kode yang membaca baris mentah harus melewati setiap baris yang diawali titik dua.
| Baris | Dikirim oleh | Kapan |
|---|---|---|
: ping | Keluarga Shannon 3, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 | Setiap 15 detik. |
: keepalive | Model open-weight hosted | Setiap 15 detik selama model bekerja. |
: thinking | shannon-2-lite, shannon-2-pro | Selama model bernalar. |
Error di dalam stream
Setelah stream dimulai, statusnya 200, sehingga kegagalan datang di dalam stream: chunk dengan anggota error di tempat choices seharusnya berada.
data: {"error":{"message":"The model backend failed to answer. Please retry.","type":"api_error","code":null,"param":null}}
data: {"choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":1190,"completion_tokens":12,"total_tokens":1202}}
data: [DONE] Setelah chunk error, chunk terakhir, dengan finish_reason dan usage, serta data: [DONE] tetap menyusul. Periksa setiap chunk untuk error. Tanpa pemeriksaan itu, balasan yang gagal di tengah jalan tampak lengkap.
| Type | Code | Pesan | Kapan |
|---|---|---|---|
api_error | The model backend failed to answer. Please retry. | Model gagal, atau tidak menulis apa pun. Kirim ulang permintaan. | |
rate_limit_error | Shannon routes are temporarily busy. Please retry. | Model sedang sibuk saat ini. Tunggu beberapa detik dan kirim ulang permintaan. | |
invalid_request_error | context_length_exceeded | Percakapan lebih panjang dari jendela konteks model. Persingkat sebelum mengirim lagi. Teks pesan bervariasi. |
Model open-weight hosted mengirim anggota error hanya dengan type dan message.
SDK OpenAI Python dan JavaScript memunculkan chunk error sebagai API error saat Anda melakukan iterasi, jadi bungkus loop dengan penanganan error yang biasa Anda pakai.
Kode status, body error, dan apa yang perlu dicoba ulang ada di halamannya sendiri. Penanganan kesalahan
Menutup koneksi
Menutup koneksi menghentikan pengiriman, bukan permintaan. Model menulis balasan hingga selesai dan permintaan ditagih seolah-olah Anda telah membaca semuanya. Untuk membayar lebih sedikit, minta lebih sedikit: setel max_tokens yang lebih rendah pada model yang menerapkannya.
Stream juga dapat berakhir lebih awal di perjalanan ke Anda, misalnya karena jaringan terputus atau saat server sedang diperbarui. Stream kemudian berhenti tanpa chunk terakhir dan tanpa data: [DONE]. Anggap balasan itu tidak lengkap dan kirim ulang permintaan.
Membaca stream tanpa SDK
Pembaca yang benar melakukan lima hal: mengirim error yang datang sebelum stream ke penanganan errornya, memecah body menjadi baris, melewati baris komentar, berhenti pada data: [DONE], dan memeriksa setiap chunk untuk error.
import json
import requests
response = requests.post(
"https://api.shannon-ai.com/v1/chat/completions",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={
"model": "shannon-3",
"messages": [{"role": "user", "content": "Explain server-sent events in three sentences."}],
"stream": True,
},
stream=True,
)
# 1. Errors before the stream are plain JSON with their own status.
if response.status_code != 200:
raise RuntimeError(f"{response.status_code}: {response.text}")
answer, done = [], False
for raw in response.iter_lines(): # 2. one line at a time
line = raw.decode("utf-8")
if not line.startswith("data: "): # 3. empty lines and ": ping" comments
continue
data = line[len("data: "):]
if data == "[DONE]": # 4. the end of the stream
done = True
break
chunk = json.loads(data)
if "error" in chunk: # 5. a failure inside the stream
raise RuntimeError(chunk["error"]["message"])
delta = chunk["choices"][0]["delta"]
if delta.get("content"):
answer.append(delta["content"])
if chunk.get("usage"):
print(chunk["choices"][0]["finish_reason"], chunk["usage"])
if not done:
raise RuntimeError("the stream ended before [DONE]")
print("".join(answer)) const response = await fetch("https://api.shannon-ai.com/v1/chat/completions", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "shannon-3",
messages: [{ role: "user", content: "Explain server-sent events in three sentences." }],
stream: true,
}),
});
// 1. Errors before the stream are plain JSON with their own status.
if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);
const decoder = new TextDecoder();
let buffer = "";
let answer = "";
let done = false;
read: for await (const bytes of response.body) {
buffer += decoder.decode(bytes, { stream: true });
const lines = buffer.split("\n"); // 2. one line at a time
buffer = lines.pop(); // keep the unfinished line
for (const line of lines) {
if (!line.startsWith("data: ")) continue; // 3. empty lines and ": ping" comments
const data = line.slice(6);
if (data === "[DONE]") { // 4. the end of the stream
done = true;
break read;
}
const chunk = JSON.parse(data);
if (chunk.error) throw new Error(chunk.error.message); // 5. a failure inside the stream
const choice = chunk.choices[0];
if (choice.delta.content) answer += choice.delta.content;
if (chunk.usage) console.log(choice.finish_reason, chunk.usage);
}
}
if (!done) throw new Error("the stream ended before [DONE]");
console.log(answer); Contoh Python memakai paket requests. Contoh JavaScript berjalan di Node.js 18 atau lebih baru.
Stream format lain
Endpoint Responses dan Messages juga melakukan streaming. Frame-nya membawa baris event: berisi nama event dan baris data: berisi JSON-nya. Setiap halaman mencantumkan eventnya secara berurutan.
| Dalam stream | Chat Completions | Responses | Messages |
|---|---|---|---|
| Frame | Baris data: | Baris event: dan data: | Baris event: dan data: |
| Penalaran datang di | delta.reasoning_content | response.reasoning_summary_text.delta | thinking_delta |
| Teks jawaban datang di | delta.content | response.output_text.delta | text_delta |
| Usage datang di | Chunk terakhir | response.completed | message_delta |
| Stream berakhir dengan | Chunk terakhir, lalu data: [DONE] | response.completed | message_stop |