Streaming
Setează stream la true și răspunsul sosește ca server-sent events pe măsură ce îl scrie modelul: mai întâi raționamentul, apoi răspunsul, apoi un ultim chunk cu motivul de finalizare și utilizarea. Folosește-l ori de câte ori un om așteaptă textul și pentru răspunsuri lungi.
POST https://api.shannon-ai.com/v1/chat/completions
SDK-urile OpenAI îți dau chunk-urile unul câte unul. Citește fiecare chunk după ce conține: o bucată de raționament, o bucată de răspuns, un apel de instrument sau sfârșitul răspunsului.
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
}' Cum este încadrat un stream
- Răspunsul are statusul
200și header-elecontent-type: text/event-streamșicache-control: no-cache. - Fiecare eveniment este o linie care începe cu
data:și este urmată de o linie goală. Textul de dupădata:este un chunk JSON. Nu există liniievent:sauid:. - O linie care începe cu două puncte, precum
: ping, este un comentariu care ține conexiunea deschisă. Sari-o. - Stream-ul se termină cu
data: [DONE]. Un stream care se oprește fără el este incomplet.
Chunk-urile în ordine
Fiecare chunk este un obiect 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
}
]
} Toate chunk-urile unui stream poartă același id, created și model. choices conține întotdeauna o singură intrare. delta conține ce este nou, iar finish_reason este null până la ultimul chunk.
| Cheie în delta | Tip | Descriere | Trimis de |
|---|---|---|---|
role | string | Întotdeauna assistant. Sosește o singură dată, la începutul stream-ului. | Modele open-weight găzduite, shannon-2-lite, shannon-2-pro |
reasoning_content | string | O bucată din raționamentul modelului. Unește bucățile în ordine. | Modele open-weight găzduite, familia Shannon 3, shannon-1.6-pro, shannon-coder-1 |
content | string | O bucată din răspuns. Unește bucățile în ordine. | Toate modelele |
tool_calls | array | Un apel de instrument complet. Vezi Delta-urile apelurilor de instrumente. | Toate modelele |
Mai întâi vine raționamentul, apoi răspunsul, apoi eventualele apeluri de instrumente. Citește un chunk după cheile din delta, nu după locul lui în stream: un delta poate conține role împreună cu prima bucată de text, iar ultimul este gol.
Un stream întreg așa cum circulă. Pentru a ține liniile scurte, id, object, created și model sunt omise din fiecare chunk aici.
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-uri de raționament
Un model care raționează își trimite raționamentul în delta.reasoning_content înaintea răspunsului. Ține cele două texte separate: afișează raționamentul ca parte separată, pliabilă, sau omite-l.
Modelele open-weight găzduite nu trimit raționament când reasoning_effort este off și nici când cererea are un response_format.
shannon-2-lite și shannon-2-pro raționează și ele înainte de a răspunde. Pe acest endpoint trimit linii de comentariu : thinking cât timp raționează, apoi răspunsul.
reasoning_content nu se află în modelele tipizate ale SDK-urilor OpenAI. În Python citește-l cu getattr(delta, "reasoning_content", None); în JavaScript, delta.reasoning_content funcționează așa cum este.
Delta-uri ale apelurilor de instrumente
Un apel de instrument sosește întreg: un chunk per apel, al cărui delta conține apelul cu șirul arguments complet. index numerotează apelurile răspunsului de la 0. Codul care unește arguments bucată cu bucată funcționează neschimbat, pentru că unește o singură bucată.
{
"tool_calls": [
{
"index": 0,
"id": "call_3d9a7c1e5b2f4a60c8e1d7f09b24a6c5",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"Paris\"}"
}
}
]
} După apeluri, ultimul chunk are finish_reason tool_calls.
Când cererea are tools, shannon-1.6-lite, shannon-1.6-pro și shannon-coder-1 trimit textul răspunsului ca un singur delta, la finalul răspunsului.
Ultimul chunk
Ultimul chunk dinaintea lui data: [DONE] are un delta gol, finish_reason și usage al cererii, toate în același chunk. Fiecare stream îl are; nu ai nevoie de 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
}
} La modelele open-weight găzduite, usage are și prompt_tokens_details.cached_tokens și completion_tokens_details.reasoning_tokens.
După o cerere cu web_search: true a cărei căutare a găsit ceva, ultimul chunk poartă și sources, iar un chunk dinaintea lui poartă marcajele de citare ca delta.annotations. Căutare web integrată
| finish_reason | Descriere | Trimis de |
|---|---|---|
stop | Modelul și-a terminat răspunsul sau a apărut un șir stop. | Toate modelele |
tool_calls | Răspunsul conține unul sau mai multe apeluri de instrumente. | Toate modelele |
length | Răspunsul a fost tăiat la limita de ieșire. | Familia Shannon 3, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 |
Câmpurile lui usage sunt descrise la endpoint. Chat Completions
Linii keep-alive
Cât timp modelul lucrează și nu are încă nimic de trimis, stream-ul conține linii de comentariu. Ele nu conțin date. Un parser SSE le sare singur; codul care citește linii brute trebuie să sară fiecare linie care începe cu două puncte.
| Linie | Trimis de | Când |
|---|---|---|
: ping | Familia Shannon 3, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 | La fiecare 15 secunde. |
: keepalive | Modele open-weight găzduite | La fiecare 15 secunde cât timp modelul lucrează. |
: thinking | shannon-2-lite, shannon-2-pro | Cât timp modelul raționează. |
Erori în interiorul unui stream
După ce un stream a început, statusul său este 200, deci un eșec sosește în interiorul stream-ului: un chunk cu un membru error în locul lui 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] După chunk-ul de eroare urmează totuși ultimul chunk, cu finish_reason și usage, și data: [DONE]. Verifică în fiecare chunk existența lui error. Fără această verificare, un răspuns care a eșuat la jumătate pare complet.
| Tip | Cod | Mesaj | Când |
|---|---|---|---|
api_error | The model backend failed to answer. Please retry. | Modelul a eșuat sau nu a scris nimic. Trimite cererea din nou. | |
rate_limit_error | Shannon routes are temporarily busy. Please retry. | Modelul este ocupat în acest moment. Așteaptă câteva secunde și trimite cererea din nou. | |
invalid_request_error | context_length_exceeded | Conversația este mai lungă decât fereastra de context a modelului. Scurtează-o înainte să o trimiți din nou. Textul mesajului variază. |
Modelele open-weight găzduite trimit membrul error doar cu type și message.
SDK-urile OpenAI pentru Python și JavaScript ridică un chunk de eroare ca eroare API în timp ce iterezi, deci învelește bucla în gestionarea ta obișnuită a erorilor.
Codurile de status, corpul erorii și ce trebuie reîncercat au pagina lor. Gestionare erori
Închiderea conexiunii
Închiderea conexiunii oprește livrarea, nu cererea. Modelul scrie răspunsul până la capăt, iar cererea este taxată ca și cum l-ai fi citit integral. Ca să plătești mai puțin, cere mai puțin: setează un max_tokens mai mic la un model care îl aplică.
Un stream se poate termina devreme și pe drumul către tine, de exemplu la o întrerupere de rețea sau în timp ce un server este actualizat. Atunci se oprește fără ultimul chunk și fără data: [DONE]. Tratează acel răspuns ca incomplet și trimite cererea din nou.
Citirea unui stream fără SDK
Un cititor corect face cinci lucruri: trimite erorile care sosesc înaintea stream-ului către gestionarea sa de erori, împarte corpul în linii, sare liniile de comentariu, se oprește la data: [DONE] și verifică în fiecare chunk existența lui 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); Exemplul Python folosește pachetul requests. Exemplul JavaScript rulează pe Node.js 18 sau mai nou.
Stream-urile celorlalte formate
Endpoint-urile Responses și Messages fac și ele streaming. Frame-urile lor conțin o linie event: cu numele evenimentului și o linie data: cu JSON-ul lui. Fiecare pagină își listează evenimentele în ordine.
| În stream | Chat Completions | Responses | Messages |
|---|---|---|---|
| Frame-uri | Linii data: | Linii event: și data: | Linii event: și data: |
| Raționamentul sosește în | delta.reasoning_content | response.reasoning_summary_text.delta | thinking_delta |
| Textul răspunsului sosește în | delta.content | response.output_text.delta | text_delta |
| Utilizarea sosește în | Ultimul chunk | response.completed | message_delta |
| Stream-ul se termină cu | Ultimul chunk, apoi data: [DONE] | response.completed | message_stop |