Streaming
Sæt stream til true, og svaret ankommer som server-sent events, mens modellen skriver det: først ræsonnement, derefter svaret, til sidst et chunk med afslutningsårsagen og usage. Brug det, når et menneske venter på teksten, og til lange svar.
POST https://api.shannon-ai.com/v1/chat/completions
OpenAI-SDK'erne giver dig chunks et ad gangen. Læs hvert chunk efter det, det indeholder: et stykke ræsonnement, et stykke af svaret, et værktøjskald eller svarets slutning.
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
}' Hvordan en stream er opbygget
- Svaret har status
200og headernecontent-type: text/event-streamogcache-control: no-cache. - Hver hændelse er én linje, der starter med
data:og efterfølges af en tom linje. Teksten efterdata:er ét JSON-chunk. Der er ingenevent:- ellerid:-linjer. - En linje, der starter med et kolon, f.eks.
: ping, er en kommentar, der holder forbindelsen åben. Spring den over. - Streamen slutter med
data: [DONE]. En stream, der stopper uden den, er ufuldstændig.
Chunks i rækkefølge
Hvert chunk er et 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
}
]
} Alle chunks i én stream har samme id, created og model. choices indeholder altid én post. Dens delta indeholder det nye, og finish_reason er null indtil det sidste chunk.
| Nøgle i delta | Type | Beskrivelse | Sendt af |
|---|---|---|---|
role | string | Altid assistant. Den ankommer én gang, i starten af streamen. | Hostede open-weight-modeller, shannon-2-lite, shannon-2-pro |
reasoning_content | string | Et stykke af modellens ræsonnement. Sæt stykkerne sammen i rækkefølge. | Hostede open-weight-modeller, Shannon 3-familien, shannon-1.6-pro, shannon-coder-1 |
content | string | Et stykke af svaret. Sæt stykkerne sammen i rækkefølge. | Alle modeller |
tool_calls | array | Ét komplet værktøjskald. Se Deltaer for værktøjskald. | Alle modeller |
Ræsonnement kommer først, derefter svaret, derefter eventuelle værktøjskald. Læs et chunk efter nøglerne i dets delta, ikke efter dets plads i streamen: en delta kan indeholde role sammen med det første stykke tekst, og den sidste er tom.
En hel stream, som den rejser. For at holde linjerne korte er id, object, created og model udeladt i hvert chunk her.
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] Ræsonnement-deltaer
En model, der ræsonnerer, sender sit ræsonnement i delta.reasoning_content før svaret. Hold de to tekster adskilt: vis ræsonnementet som en separat, sammenfoldelig del, eller udelad det.
De hostede open-weight-modeller sender intet ræsonnement, når reasoning_effort er off, og heller ikke når anmodningen har et response_format.
shannon-2-lite og shannon-2-pro ræsonnerer også, før de svarer. På dette endpoint sender de kommentarlinjer : thinking, mens de gør det, og derefter svaret.
reasoning_content findes ikke i OpenAI-SDK'ernes typede modeller. I Python læses det med getattr(delta, "reasoning_content", None); i JavaScript virker delta.reasoning_content, som det er.
Deltaer for værktøjskald
Et værktøjskald ankommer helt: ét chunk per kald, hvis delta indeholder kaldet med den komplette arguments-streng. index nummererer svarets kald fra 0. Kode, der samler arguments stykke for stykke, virker uændret, fordi den samler ét stykke.
{
"tool_calls": [
{
"index": 0,
"id": "call_3d9a7c1e5b2f4a60c8e1d7f09b24a6c5",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"Paris\"}"
}
}
]
} Efter kaldene har det sidste chunk finish_reason tool_calls.
Når anmodningen har tools, sender shannon-1.6-lite, shannon-1.6-pro og shannon-coder-1 svarteksten som én delta i slutningen af svaret.
Det sidste chunk
Det sidste chunk før data: [DONE] har en tom delta, finish_reason og anmodningens usage, alt sammen i samme chunk. Hver stream har det; du behøver ikke 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 hostede open-weight-modeller har usage også prompt_tokens_details.cached_tokens og completion_tokens_details.reasoning_tokens.
Efter en anmodning med web_search: true, hvis søgning fandt noget, bærer det sidste chunk også sources, og ét chunk før det bærer citationsmarkørerne som delta.annotations. Indbygget websøgning
| finish_reason | Beskrivelse | Sendt af |
|---|---|---|
stop | Modellen afsluttede sit svar, eller en stop-streng optrådte. | Alle modeller |
tool_calls | Svaret indeholder ét eller flere værktøjskald. | Alle modeller |
length | Svaret blev afskåret ved outputgrænsen. | Shannon 3-familien, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 |
Felterne i usage er beskrevet sammen med endpointet. Chat Completions
Keep-alive-linjer
Mens modellen arbejder og endnu intet har at sende, har streamen kommentarlinjer. De indeholder ingen data. En SSE-parser springer dem selv over; kode, der læser rå linjer, skal springe hver linje over, der starter med et kolon.
| Linje | Sendt af | Hvornår |
|---|---|---|
: ping | Shannon 3-familien, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 | Hvert 15. sekund. |
: keepalive | Hostede open-weight-modeller | Hvert 15. sekund, mens modellen arbejder. |
: thinking | shannon-2-lite, shannon-2-pro | Mens modellen ræsonnerer. |
Fejl inde i en stream
Når en stream er startet, er dens status 200, så en fejl ankommer inde i streamen: et chunk med et error-medlem, hvor choices ellers ville 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 fejl-chunket følger stadig det sidste chunk, med finish_reason og usage, og data: [DONE]. Kontrollér hvert chunk for error. Uden den kontrol ser et svar, der fejlede halvvejs, komplet ud.
| Type | Kode | Besked | Hvornår |
|---|---|---|---|
api_error | The model backend failed to answer. Please retry. | Modellen fejlede eller skrev intet. Send anmodningen igen. | |
rate_limit_error | Shannon routes are temporarily busy. Please retry. | Modellen er optaget i øjeblikket. Vent nogle sekunder, og send anmodningen igen. | |
invalid_request_error | context_length_exceeded | Samtalen er længere end modellens kontekstvindue. Forkort den, før du sender igen. Beskedens tekst varierer. |
De hostede open-weight-modeller sender error-medlemmet kun med type og message.
OpenAI's Python- og JavaScript-SDK'er rejser et fejl-chunk som en API-fejl, mens du itererer, så pak løkken ind i din sædvanlige fejlhåndtering.
Statuskoder, fejlens body og hvad der kan prøves igen, har deres egen side. Fejlhåndtering
Lukning af forbindelsen
At lukke forbindelsen stopper leveringen, ikke anmodningen. Modellen skriver svaret til ende, og anmodningen afregnes, som om du havde læst det hele. For at betale mindre skal du bede om mindre: sæt en lavere max_tokens på en model, der anvender den.
En stream kan også slutte for tidligt på vej til dig, f.eks. ved et netværksbrud eller mens en server opdateres. Den stopper så uden det sidste chunk og uden data: [DONE]. Behandl det svar som ufuldstændigt, og send anmodningen igen.
Læsning af en stream uden et SDK
En korrekt læser gør fem ting: den sender fejl, der ankommer før streamen, til sin fejlhåndtering, deler body'en i linjer, springer kommentarlinjer over, stopper ved data: [DONE] og kontrollerer hvert chunk for 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-eksemplet bruger pakken requests. JavaScript-eksemplet kører på Node.js 18 eller nyere.
Streams i de andre formater
Endpointsene Responses og Messages streamer også. Deres rammer har en event:-linje med hændelsens navn og en data:-linje med dens JSON. Hver side opregner sine hændelser i rækkefølge.
| I streamen | Chat Completions | Responses | Messages |
|---|---|---|---|
| Rammer | data:-linjer | event:- og data:-linjer | event:- og data:-linjer |
| Ræsonnement ankommer i | delta.reasoning_content | response.reasoning_summary_text.delta | thinking_delta |
| Svarteksten ankommer i | delta.content | response.output_text.delta | text_delta |
| Usage ankommer i | Det sidste chunk | response.completed | message_delta |
| Streamen slutter med | Det sidste chunk, derefter data: [DONE] | response.completed | message_stop |