Penstriman
Tetapkan stream kepada true dan balasan tiba sebagai server-sent events semasa model menulisnya: penaakulan dahulu, kemudian jawapan, kemudian chunk terakhir dengan sebab selesai dan usage. Gunakannya apabila ada orang yang menunggu teks, dan untuk balasan yang panjang.
POST https://api.shannon-ai.com/v1/chat/completions
SDK OpenAI memberikan chunk satu demi satu. Baca setiap chunk mengikut apa yang dibawanya: sebahagian penaakulan, sebahagian jawapan, panggilan alat, atau penghujung 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 dibingkaikan
- Balasan mempunyai status
200dan headercontent-type: text/event-streamsertacache-control: no-cache. - Setiap peristiwa ialah satu baris yang bermula dengan
data:dan diikuti baris kosong. Teks selepasdata:ialah satu chunk JSON. Tiada barisevent:atauid:. - Baris yang bermula dengan titik bertindih, seperti
: ping, ialah komen yang mengekalkan sambungan terbuka. Langkaunya. - Stream berakhir dengan
data: [DONE]. Stream yang berhenti tanpanya tidak lengkap.
Chunk mengikut urutan
Setiap chunk ialah 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 sentiasa memegang satu entri. deltanya memegang apa yang baharu, dan finish_reason ialah null sehingga chunk terakhir.
| Kunci dalam delta | Jenis | Penerangan | Dihantar oleh |
|---|---|---|---|
role | string | Sentiasa assistant. Ia tiba sekali, pada permulaan stream. | Model open-weight yang dihoskan, shannon-2-lite, shannon-2-pro |
reasoning_content | string | Sebahagian daripada penaakulan model. Gabungkan bahagian-bahagian itu mengikut urutan. | Model open-weight yang dihoskan, keluarga Shannon 3, shannon-1.6-pro, shannon-coder-1 |
content | string | Sebahagian daripada jawapan. Gabungkan bahagian-bahagian itu mengikut urutan. | Semua model |
tool_calls | array | Satu panggilan alat yang lengkap. Lihat Delta panggilan alat. | Semua model |
Penaakulan datang dahulu, kemudian jawapan, kemudian sebarang panggilan alat. Baca chunk mengikut kunci dalam deltanya, bukan mengikut tempatnya dalam stream: delta boleh memegang role bersama bahagian teks yang pertama, dan yang terakhir kosong.
Seluruh stream seperti yang dihantar. Supaya baris kekal pendek, id, object, created dan model tidak dimasukkan dalam setiap 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 penaakulan
Model yang bernalar menghantar penaakulannya dalam delta.reasoning_content sebelum jawapan. Pisahkan kedua-dua teks itu: tunjukkan penaakulan sebagai bahagian berasingan yang boleh dilipat, atau tinggalkannya.
Model open-weight yang dihoskan tidak menghantar penaakulan apabila reasoning_effort ialah off, dan tidak juga apabila permintaan mempunyai response_format.
shannon-2-lite dan shannon-2-pro juga bernalar sebelum menjawab. Pada endpoint ini, ia menghantar baris komen : thinking semasa berbuat demikian, dan kemudian jawapan.
reasoning_content tiada dalam model bertaip SDK OpenAI. Dalam Python, bacanya dengan getattr(delta, "reasoning_content", None); dalam JavaScript, delta.reasoning_content berfungsi seadanya.
Delta panggilan alat
Panggilan alat tiba secara keseluruhan: satu chunk bagi setiap panggilan, yang deltanya memegang panggilan dengan rentetan arguments yang lengkap. index menomborkan panggilan dalam balasan bermula dari 0. Kod yang menggabungkan arguments sedikit demi sedikit berfungsi tanpa perubahan, kerana ia menggabungkan satu bahagian.
{
"tool_calls": [
{
"index": 0,
"id": "call_3d9a7c1e5b2f4a60c8e1d7f09b24a6c5",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"Paris\"}"
}
}
]
} Selepas panggilan, chunk terakhir mempunyai finish_reason tool_calls.
Apabila permintaan mempunyai tools, shannon-1.6-lite, shannon-1.6-pro dan shannon-coder-1 menghantar teks jawapan sebagai satu delta pada penghujung balasan.
Chunk terakhir
Chunk terakhir sebelum data: [DONE] mempunyai delta kosong, finish_reason dan usage permintaan, semuanya dalam chunk yang sama. Setiap stream mempunyainya; 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 yang dihoskan, usage juga mempunyai prompt_tokens_details.cached_tokens dan completion_tokens_details.reasoning_tokens.
Selepas permintaan dengan web_search: true yang carian untuknya menemui sesuatu, chunk terakhir turut membawa sources, dan satu chunk sebelumnya membawa tanda petikan sebagai delta.annotations. Carian Web Terbina dalam
| finish_reason | Penerangan | Dihantar oleh |
|---|---|---|
stop | Model menamatkan jawapannya, atau rentetan stop muncul. | Semua model |
tool_calls | Balasan memegang satu atau lebih panggilan alat. | Semua model |
length | Balasan dipotong pada had output. | Keluarga Shannon 3, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 |
Medan usage diterangkan bersama endpoint. Chat Completions
Baris keep-alive
Semasa model bekerja dan belum ada apa-apa untuk dihantar, stream membawa baris komen. Ia tidak memegang data. Penghurai SSE melangkaunya sendiri; kod yang membaca baris mentah mesti melangkau setiap baris yang bermula dengan titik bertindih.
| Baris | Dihantar oleh | Bila |
|---|---|---|
: ping | Keluarga Shannon 3, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 | Setiap 15 saat. |
: keepalive | Model open-weight yang dihoskan | Setiap 15 saat semasa model bekerja. |
: thinking | shannon-2-lite, shannon-2-pro | Semasa model bernalar. |
Ralat di dalam stream
Apabila stream telah bermula, statusnya 200, jadi kegagalan tiba di dalam stream: chunk dengan ahli error di tempat choices sepatutnya 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] Selepas chunk ralat, chunk terakhir, dengan finish_reason dan usage, dan data: [DONE] masih menyusul. Semak setiap chunk untuk error. Tanpa semakan itu, balasan yang gagal di pertengahan kelihatan lengkap.
| Jenis | Kod | Mesej | Bila |
|---|---|---|---|
api_error | The model backend failed to answer. Please retry. | Model gagal, atau tidak menulis apa-apa. Hantar semula permintaan. | |
rate_limit_error | Shannon routes are temporarily busy. Please retry. | Model sibuk pada masa ini. Tunggu beberapa saat dan hantar semula permintaan. | |
invalid_request_error | context_length_exceeded | Perbualan lebih panjang daripada tetingkap konteks model. Pendekkannya sebelum menghantar semula. Teks mesej berbeza-beza. |
Model open-weight yang dihoskan menghantar ahli error dengan type dan message sahaja.
SDK Python dan JavaScript OpenAI menimbulkan chunk ralat sebagai ralat API semasa anda mengulang, jadi bungkus gelung itu dalam pengendalian ralat biasa anda.
Kod status, badan ralat dan apa yang perlu dicuba semula mempunyai halamannya sendiri. Pengendalian Ralat
Menutup sambungan
Menutup sambungan menghentikan penghantaran, bukan permintaan. Model menulis balasan sehingga habis dan permintaan dicaj seolah-olah anda telah membacanya semua. Untuk membayar kurang, minta kurang: tetapkan max_tokens yang lebih rendah pada model yang menggunakannya.
Stream juga boleh berakhir awal dalam perjalanan kepada anda, contohnya apabila rangkaian terputus atau semasa server dikemas kini. Ia kemudian berhenti tanpa chunk terakhir dan tanpa data: [DONE]. Anggap balasan itu tidak lengkap dan hantar semula permintaan.
Membaca stream tanpa SDK
Pembaca yang betul melakukan lima perkara: ia menghantar ralat yang tiba sebelum stream kepada pengendalian ralatnya, memecahkan badan kepada baris, melangkau baris komen, berhenti pada data: [DONE], dan menyemak 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); Sampel Python menggunakan pakej requests. Sampel JavaScript berjalan pada Node.js 18 atau lebih baharu.
Stream bagi format lain
Endpoint Responses dan Messages juga melakukan streaming. Bingkainya membawa baris event: dengan nama peristiwa dan baris data: dengan JSON-nya. Setiap halaman menyenaraikan peristiwanya mengikut urutan.
| Dalam stream | Chat Completions | Responses | Messages |
|---|---|---|---|
| Bingkai | Baris data: | Baris event: dan data: | Baris event: dan data: |
| Penaakulan tiba dalam | delta.reasoning_content | response.reasoning_summary_text.delta | thinking_delta |
| Teks jawapan tiba dalam | delta.content | response.output_text.delta | text_delta |
| Usage tiba dalam | Chunk terakhir | response.completed | message_delta |
| Stream berakhir dengan | Chunk terakhir, kemudian data: [DONE] | response.completed | message_stop |