Стриминг
Поставете stream на true и одговорот пристигнува како server-sent events додека моделот го пишува: прво reasoning, потоа одговорот, па последен chunk со причината за завршување и usage. Користете го секогаш кога човек чека на текстот, и за долги одговори.
POST https://api.shannon-ai.com/v1/chat/completions
SDK-ата на OpenAI ви ги даваат chunk-овите еден по еден. Читајте го секој chunk според тоа што носи: парче reasoning, парче од одговорот, повик на алатка или крај на одговорот.
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
}' Како е обликуван streaming-от
- Одговорот има статус
200и header-иcontent-type: text/event-streamиcache-control: no-cache. - Секој настан е еден ред што започнува со
data:и по него следува празен ред. Текстот поdata:е еден JSON chunk. Нема редовиevent:илиid:. - Ред што започнува со две точки, како
: ping, е коментар што ја држи врската отворена. Прескокнете го. - Streaming-от завршува со
data: [DONE]. Streaming што запира без него е нецелосен.
Chunk-ови по редослед
Секој chunk е објект 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
}
]
} Сите chunk-ови на еден streaming го носат истиот id, created и model. choices секогаш содржи еден запис. Неговиот delta го содржи она што е ново, а finish_reason е null до последниот chunk.
| Клуч во delta | Тип | Опис | Го испраќа |
|---|---|---|---|
role | string | Секогаш assistant. Пристигнува еднаш, на почетокот на streaming-от. | Хостирани open-weight модели, shannon-2-lite, shannon-2-pro |
reasoning_content | string | Парче од reasoning-от на моделот. Спојте ги парчињата по редослед. | Хостирани open-weight модели, фамилијата Shannon 3, shannon-1.6-pro, shannon-coder-1 |
content | string | Парче од одговорот. Спојте ги парчињата по редослед. | Сите модели |
tool_calls | array | Еден комплетен повик на алатка. Видете Delta-и на повик на алатка. | Сите модели |
Прво доаѓа reasoning-от, потоа одговорот, потоа евентуални повици на алатки. Читајте го chunk-от според клучевите во неговиот delta, а не според неговото место во streaming-от: delta може да содржи role заедно со првото парче текст, а последниот е празен.
Цел streaming онака како што патува. За редовите да бидат кратки, id, object, created и model се изоставени од секој chunk овде.
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-и на reasoning
Модел што прави reasoning го испраќа својот reasoning во delta.reasoning_content пред одговорот. Чувајте ги двата текста одвоено: прикажете го reasoning-от како посебен дел што се собира, или изоставете го.
Хостираните open-weight модели не испраќаат reasoning кога reasoning_effort е off, ниту кога барањето има response_format.
shannon-2-lite и shannon-2-pro исто така прават reasoning пред да одговорат. На овој ендпоинт, додека тоа го прават, испраќаат редови-коментари : thinking, а потоа одговорот.
reasoning_content не е во типизираните модели на SDK-ата на OpenAI. Во Python прочитајте го со getattr(delta, "reasoning_content", None); во JavaScript delta.reasoning_content работи каков што е.
Delta-и на повик на алатка
Повикот на алатка пристигнува цел: еден chunk по повик, чиј delta го содржи повикот со комплетниот стринг arguments. index ги нумерира повиците на одговорот од 0. Кодот што ги спојува arguments парче по парче работи непроменет, бидејќи спојува едно парче.
{
"tool_calls": [
{
"index": 0,
"id": "call_3d9a7c1e5b2f4a60c8e1d7f09b24a6c5",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"Paris\"}"
}
}
]
} По повиците, последниот chunk има finish_reason tool_calls.
Кога барањето има tools, shannon-1.6-lite, shannon-1.6-pro и shannon-coder-1 го испраќаат текстот на одговорот како една delta на крајот од одговорот.
Последниот chunk
Последниот chunk пред data: [DONE] има празен delta, finish_reason и usage на барањето, сите во истиот chunk. Го има секој streaming; не ви треба 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
}
} На хостираните open-weight модели usage ги има и prompt_tokens_details.cached_tokens и completion_tokens_details.reasoning_tokens.
По барање со web_search: true чие пребарување нашло нешто, последниот chunk носи и sources, а еден chunk пред него ги носи ознаките за цитирање како delta.annotations. Вградено веб пребарување
| finish_reason | Опис | Го испраќа |
|---|---|---|
stop | Моделот го завршил својот одговор, или се појавил стринг stop. | Сите модели |
tool_calls | Одговорот содржи еден или повеќе повици на алатки. | Сите модели |
length | Одговорот е прекинат на излезното ограничување. | Фамилијата Shannon 3, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 |
Полињата на usage се опишани заедно со ендпоинтот. Chat Completions
Keep-alive редови
Додека моделот работи и сè уште нема што да испрати, streaming-от носи редови-коментари. Не содржат податоци. SSE парсерот сам ги прескокнува; кодот што чита сурови редови мора да го прескокне секој ред што започнува со две точки.
| Ред | Го испраќа | Кога |
|---|---|---|
: ping | Фамилијата Shannon 3, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 | На секои 15 секунди. |
: keepalive | Хостирани open-weight модели | На секои 15 секунди додека моделот работи. |
: thinking | shannon-2-lite, shannon-2-pro | Додека моделот прави reasoning. |
Грешки во streaming
Откако streaming-от ќе започне, неговиот статус е 200, па неуспехот пристигнува во streaming-от: chunk со член error таму каде што би биле choices.
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] По chunk-от со грешка сепак следуваат последниот chunk, со finish_reason и usage, и data: [DONE]. Проверувајте го секој chunk за error. Без таа проверка одговор што пропаднал на половина изгледа комплетен.
| Тип | Код | Порака | Кога |
|---|---|---|---|
api_error | The model backend failed to answer. Please retry. | Моделот пропадна, или не напиша ништо. Испратете го барањето повторно. | |
rate_limit_error | Shannon routes are temporarily busy. Please retry. | Моделот е зафатен во овој момент. Почекајте неколку секунди и испратете го барањето повторно. | |
invalid_request_error | context_length_exceeded | Разговорот е подолг од контекстниот прозорец на моделот. Скратете го пред да го испратите повторно. Текстот на пораката варира. |
Хостираните open-weight модели го испраќаат членот error само со type и message.
SDK-ата на OpenAI за Python и JavaScript го креваат chunk-от со грешка како API грешка додека итерирате, затоа завиткајте ја јамката во вашето вообичаено ракување со грешки.
Статус кодовите, телото на грешката и што да се повтори имаат своја страница. Ракување со грешки
Затворање на врската
Затворањето на врската ја запира испораката, а не барањето. Моделот го пишува одговорот до крај и барањето се наплатува како да сте го прочитале целиот. За да платите помалку, побарајте помалку: поставете понизок max_tokens на модел што го применува.
Streaming може да заврши предвреме и на патот до вас, на пример при прекин на мрежата или додека се ажурира сервер. Тогаш запира без последниот chunk и без data: [DONE]. Третирајте го тој одговор како нецелосен и испратете го барањето повторно.
Читање streaming без SDK
Исправен читач прави пет работи: ги праќа грешките што пристигнуваат пред streaming-от на своето ракување со грешки, го дели телото на редови, ги прескокнува редовите-коментари, запира на data: [DONE] и го проверува секој chunk за 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); Примерот за Python го користи пакетот requests. Примерот за JavaScript работи на Node.js 18 или понов.
Streaming на другите формати
Ендпоинтите Responses и Messages исто така стримуваат. Нивните фрејмови носат ред event: со името на настанот и ред data: со неговиот JSON. Секоја страница ги наведува своите настани по редослед.
| Во streaming-от | Chat Completions | Responses | Messages |
|---|---|---|---|
| Фрејмови | Редови data: | Редови event: и data: | Редови event: и data: |
| Reasoning-от пристигнува во | delta.reasoning_content | response.reasoning_summary_text.delta | thinking_delta |
| Текстот на одговорот пристигнува во | delta.content | response.output_text.delta | text_delta |
| Usage пристигнува во | Последниот chunk | response.completed | message_delta |
| Streaming-от завршува со | Последниот chunk, потоа data: [DONE] | response.completed | message_stop |