Streaming
Sätt stream till true så kommer svaret som server-sent events medan modellen skriver det: först resonemanget, sedan svaret, sedan en sista chunk med avslutsorsaken och usage. Använd det alltid när en person väntar på texten, och för långa svar.
POST https://api.shannon-ai.com/v1/chat/completions
OpenAI-SDK:erna ger dig chunkarna en och en. Läs varje chunk efter vad den bär: en bit resonemang, en bit av svaret, ett verktygsanrop eller svarets slut.
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
}' Hur en ström är inramad
- Svaret har status
200och headrarnacontent-type: text/event-streamochcache-control: no-cache. - Varje händelse är en rad som börjar med
data:och följs av en tom rad. Texten efterdata:är en JSON-chunk. Det finns ingaevent:- ellerid:-rader. - En rad som börjar med kolon, som
: ping, är en kommentar som håller anslutningen öppen. Hoppa över den. - Strömmen slutar med
data: [DONE]. En ström som stannar utan den är ofullständig.
Chunkar i ordning
Varje chunk är ett chat.completion.chunk-objekt:
{
"id": "chatcmpl-5f0c1e7a9b3d4c62a8e1f07d2b46c9a3",
"object": "chat.completion.chunk",
"created": 1791625200,
"model": "shannon-3",
"choices": [
{
"index": 0,
"delta": {
"content": "Server-sent"
},
"finish_reason": null
}
]
} Alla chunkar i en ström bär samma id, created och model. choices innehåller alltid en post. Dess delta innehåller det som är nytt, och finish_reason är null till den sista chunken.
| Nyckel i delta | Typ | Beskrivning | Skickas av |
|---|---|---|---|
role | string | Alltid assistant. Den kommer en gång, i början av strömmen. | Hostade open-weight-modeller, shannon-2-lite, shannon-2-pro |
reasoning_content | string | En bit av modellens resonemang. Foga ihop bitarna i ordning. | Hostade open-weight-modeller, Shannon 3-familjen, shannon-1.6-pro, shannon-coder-1 |
content | string | En bit av svaret. Foga ihop bitarna i ordning. | Alla modeller |
tool_calls | array | Ett komplett verktygsanrop. Se Delta för verktygsanrop. | Alla modeller |
Resonemanget kommer först, sedan svaret, sedan eventuella verktygsanrop. Läs en chunk efter nycklarna i dess delta, inte efter dess plats i strömmen: ett delta kan innehålla role tillsammans med den första textbiten, och det sista är tomt.
En hel ström så som den färdas. För att hålla raderna korta är id, object, created och model utelämnade i varje chunk här.
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] Resonemangsdeltan
En modell som resonerar skickar sitt resonemang i delta.reasoning_content före svaret. Håll de två texterna isär: visa resonemanget som en separat, hopfällbar del, eller utelämna det.
De hostade open-weight-modellerna skickar inget resonemang när reasoning_effort är off, och inte heller när begäran har ett response_format.
shannon-2-lite och shannon-2-pro resonerar också innan de svarar. På den här endpointen skickar de kommentarsraderna : thinking medan de gör det, och sedan svaret.
reasoning_content finns inte i OpenAI-SDK:ernas typade modeller. I Python läser du det med getattr(delta, "reasoning_content", None); i JavaScript fungerar delta.reasoning_content som det är.
Delta för verktygsanrop
Ett verktygsanrop kommer helt: en chunk per anrop, vars delta innehåller anropet med hela arguments-strängen. index numrerar svarets anrop från 0. Kod som fogar ihop arguments bit för bit fungerar oförändrad, eftersom den fogar ihop en enda bit.
{
"tool_calls": [
{
"index": 0,
"id": "call_3d9a7c1e5b2f4a60c8e1d7f09b24a6c5",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"Paris\"}"
}
}
]
} Efter anropen har den sista chunken finish_reason tool_calls.
När begäran har tools skickar shannon-1.6-lite, shannon-1.6-pro och shannon-coder-1 svarstexten som ett enda delta i slutet av svaret.
Den sista chunken
Den sista chunken före data: [DONE] har ett tomt delta, finish_reason och begärans usage, allt i samma chunk. Varje ström har den; du behöver inte 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
}
} På de hostade open-weight-modellerna har usage också prompt_tokens_details.cached_tokens och completion_tokens_details.reasoning_tokens.
Efter en begäran med web_search: true vars sökning fann något bär den sista chunken också sources, och en chunk före den bär citeringsmarkörerna som delta.annotations. Inbyggd webbsökning
| finish_reason | Beskrivning | Skickas av |
|---|---|---|
stop | Modellen avslutade sitt svar, eller en stop-sträng dök upp. | Alla modeller |
tool_calls | Svaret innehåller ett eller flera verktygsanrop. | Alla modeller |
length | Svaret klipptes vid outputgränsen. | Shannon 3-familjen, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 |
Fälten i usage beskrivs tillsammans med endpointen. Chat Completions
Keep-alive-rader
Medan modellen arbetar och ännu inte har något att skicka bär strömmen kommentarsrader. De innehåller inga data. En SSE-parser hoppar över dem själv; kod som läser råa rader måste hoppa över varje rad som börjar med kolon.
| Rad | Skickas av | När |
|---|---|---|
: ping | Shannon 3-familjen, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 | Var 15:e sekund. |
: keepalive | Hostade open-weight-modeller | Var 15:e sekund medan modellen arbetar. |
: thinking | shannon-2-lite, shannon-2-pro | Medan modellen resonerar. |
Fel inne i en ström
När en ström har startat är dess status 200, så ett fel kommer inne i strömmen: en chunk med en error-medlem där choices annars skulle stå.
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] Efter felchunken följer ändå den sista chunken, med finish_reason och usage, och data: [DONE]. Kontrollera varje chunk efter error. Utan den kontrollen ser ett svar som misslyckades halvvägs komplett ut.
| Typ | Kod | Meddelande | När |
|---|---|---|---|
api_error | The model backend failed to answer. Please retry. | Modellen misslyckades, eller skrev ingenting. Skicka begäran igen. | |
rate_limit_error | Shannon routes are temporarily busy. Please retry. | Modellen är upptagen just nu. Vänta några sekunder och skicka begäran igen. | |
invalid_request_error | context_length_exceeded | Konversationen är längre än modellens kontextfönster. Förkorta den innan du skickar den igen. Meddelandetexten varierar. |
De hostade open-weight-modellerna skickar error-medlemmen med endast type och message.
OpenAI:s Python- och JavaScript-SDK:er kastar en felchunk som ett API-fel medan du itererar, så linda in loopen i din vanliga felhantering.
Statuskoder, felbodyn och vad som kan försökas igen har en egen sida. Felhantering
Att stänga anslutningen
Att stänga anslutningen stoppar leveransen, inte begäran. Modellen skriver svaret till slut och begäran debiteras som om du hade läst allt. För att betala för mindre, be om mindre: ange ett lägre max_tokens på en modell som tillämpar det.
En ström kan också ta slut i förtid på väg till dig, till exempel vid ett nätverksavbrott eller medan en server uppdateras. Den stannar då utan den sista chunken och utan data: [DONE]. Behandla det svaret som ofullständigt och skicka begäran igen.
Läsa en ström utan SDK
En korrekt läsare gör fem saker: den skickar fel som kommer före strömmen till sin felhantering, delar upp bodyn i rader, hoppar över kommentarsrader, stannar vid data: [DONE] och kontrollerar varje chunk efter 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-exemplet använder paketet requests. JavaScript-exemplet körs på Node.js 18 eller senare.
Strömmar i de andra formaten
Endpointsen Responses och Messages streamar också. Deras frames bär en event:-rad med händelsens namn och en data:-rad med dess JSON. Varje sida listar sina händelser i ordning.
| I strömmen | Chat Completions | Responses | Messages |
|---|---|---|---|
| Frames | data:-rader | event:- och data:-rader | event:- och data:-rader |
| Resonemanget kommer i | delta.reasoning_content | response.reasoning_summary_text.delta | thinking_delta |
| Svarstexten kommer i | delta.content | response.output_text.delta | text_delta |
| Usage kommer i | Den sista chunken | response.completed | message_delta |
| Strömmen slutar med | Den sista chunken, sedan data: [DONE] | response.completed | message_stop |