Strømming
Sett stream til true, så kommer svaret som server-sent events mens modellen skriver det: først resonnering, så svaret, så en siste chunk med finish reason og usage. Bruk det når en person venter på teksten, og for lange svar.
POST https://api.shannon-ai.com/v1/chat/completions
OpenAI-SDK-ene gir deg chunkene én for én. Les hver chunk etter det den inneholder: et stykke resonnering, et stykke av svaret, et verktøykall eller slutten på svaret.
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 strøm er bygget opp
- Svaret har status
200og headernecontent-type: text/event-streamogcache-control: no-cache. - Hver hendelse er én linje som starter med
data:og etterfølges av en tom linje. Teksten etterdata:er én JSON-chunk. Det finnes ingenevent:- ellerid:-linjer. - En linje som starter med kolon, som
: ping, er en kommentar som holder forbindelsen åpen. Hopp over den. - Strømmen ender med
data: [DONE]. En strøm som stopper uten den, er ufullstendig.
Chunker i rekkefølge
Hver 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 chunker i én strøm har samme id, created og model. choices har alltid én oppføring. Dens delta inneholder det som er nytt, og finish_reason er null til den siste chunken.
| Nøkkel i delta | Type | Beskrivelse | Sendes av |
|---|---|---|---|
role | string | Alltid assistant. Den kommer én gang, i starten av strømmen. | Hostede åpne vektmodeller, shannon-2-lite, shannon-2-pro |
reasoning_content | string | Et stykke av modellens resonnering. Slå sammen stykkene i rekkefølge. | Hostede åpne vektmodeller, Shannon 3-familien, shannon-1.6-pro, shannon-coder-1 |
content | string | Et stykke av svaret. Slå sammen stykkene i rekkefølge. | Alle modeller |
tool_calls | array | Ett komplett verktøykall. Se Verktøykall-deltaer. | Alle modeller |
Resonnering kommer først, deretter svaret, deretter eventuelle verktøykall. Les en chunk etter nøklene i delta, ikke etter plassen i strømmen: en delta kan inneholde role sammen med det første tekststykket, og den siste er tom.
En hel strøm slik den sendes. For å holde linjene korte er id, object, created og model utelatt fra hver 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] Resonnerings-deltaer
En modell som resonnerer, sender resonneringen sin i delta.reasoning_content før svaret. Hold de to tekstene atskilt: vis resonneringen som en egen, sammenleggbar del, eller utelat den.
De hostede åpne vektmodellene sender ingen resonnering når reasoning_effort er off, og ingen når forespørselen har en response_format.
shannon-2-lite og shannon-2-pro resonnerer også før de svarer. På dette endepunktet sender de kommentarlinjer : thinking mens de gjør det, og deretter svaret.
reasoning_content finnes ikke i de typede modellene i OpenAI-SDK-ene. I Python leser du den med getattr(delta, "reasoning_content", None); i JavaScript fungerer delta.reasoning_content som den er.
Verktøykall-deltaer
Et verktøykall kommer helt: én chunk per kall, der delta inneholder kallet med hele arguments-strengen. index nummererer kallene i svaret fra 0. Kode som slår sammen arguments stykke for stykke, fungerer uendret, fordi den slår sammen ett stykke.
{
"tool_calls": [
{
"index": 0,
"id": "call_3d9a7c1e5b2f4a60c8e1d7f09b24a6c5",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"Paris\"}"
}
}
]
} Etter kallene har den siste chunken finish_reason tool_calls.
Når forespørselen har tools, sender shannon-1.6-lite, shannon-1.6-pro og shannon-coder-1 svarteksten som én delta på slutten av svaret.
Den siste chunken
Den siste chunken før data: [DONE] har en tom delta, finish_reason og usage for forespørselen, alt i samme chunk. Hver strøm har den; du trenger 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 åpne vektmodellene har usage også prompt_tokens_details.cached_tokens og completion_tokens_details.reasoning_tokens.
Etter en forespørsel med web_search: true der søket fant noe, har den siste chunken også sources, og en chunk før den har siteringsmarkeringene som delta.annotations. Innebygd websøk
| finish_reason | Beskrivelse | Sendes av |
|---|---|---|
stop | Modellen fullførte svaret sitt, eller en stop-streng dukket opp. | Alle modeller |
tool_calls | Svaret inneholder ett eller flere verktøykall. | Alle modeller |
length | Svaret ble avkortet ved utdatagrensen. | Shannon 3-familien, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 |
Feltene i usage er beskrevet sammen med endepunktet. Chat Completions
Keep-alive-linjer
Mens modellen arbeider og ennå ikke har noe å sende, har strømmen kommentarlinjer. De inneholder ingen data. En SSE-parser hopper over dem av seg selv; kode som leser rå linjer, må hoppe over hver linje som starter med kolon.
| Linje | Sendes av | Når |
|---|---|---|
: ping | Shannon 3-familien, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 | Hvert 15. sekund. |
: keepalive | Hostede åpne vektmodeller | Hvert 15. sekund mens modellen arbeider. |
: thinking | shannon-2-lite, shannon-2-pro | Mens modellen resonnerer. |
Feil inne i en strøm
Når en strøm har startet, er statusen 200, så en feil kommer inne i strømmen: en chunk med et error-medlem der choices ellers ville stått.
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] Etter feil-chunken følger likevel den siste chunken, med finish_reason og usage, og data: [DONE]. Sjekk hver chunk for error. Uten den sjekken ser et svar som feilet halvveis, komplett ut.
| Type | Kode | Melding | Når |
|---|---|---|---|
api_error | The model backend failed to answer. Please retry. | Modellen feilet, eller skrev ingenting. Send forespørselen på nytt. | |
rate_limit_error | Shannon routes are temporarily busy. Please retry. | Modellen er opptatt akkurat nå. Vent noen sekunder og send forespørselen på nytt. | |
invalid_request_error | context_length_exceeded | Samtalen er lengre enn modellens kontekstvindu. Kort den ned før du sender den på nytt. Meldingsteksten varierer. |
De hostede åpne vektmodellene sender error-medlemmet med bare type og message.
OpenAI-SDK-ene for Python og JavaScript kaster en feil-chunk som en API-feil mens du itererer, så pakk løkken inn i din vanlige feilhåndtering.
Statuskoder, feilkroppen og hva som kan prøves på nytt, har sin egen side. Feilhåndtering
Lukke forbindelsen
Å lukke forbindelsen stopper leveringen, ikke forespørselen. Modellen skriver svaret til slutten, og forespørselen belastes som om du hadde lest alt. For å betale mindre må du be om mindre: sett en lavere max_tokens på en modell som tar hensyn til det.
En strøm kan også ta slutt tidlig på veien til deg, for eksempel ved et nettverksbrudd eller mens en server oppdateres. Den stopper da uten den siste chunken og uten data: [DONE]. Behandle det svaret som ufullstendig og send forespørselen på nytt.
Lese en strøm uten SDK
En korrekt leser gjør fem ting: den sender feil som kommer før strømmen, til feilhåndteringen sin, deler kroppen i linjer, hopper over kommentarlinjer, stopper ved data: [DONE] og sjekker hver 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 bruker pakken requests. JavaScript-eksemplet kjører på Node.js 18 eller nyere.
Strømmer i de andre formatene
Endepunktene Responses og Messages støtter også streaming. Rammene deres har en event:-linje med navnet på hendelsen og en data:-linje med JSON-en. Hver side lister hendelsene sine i rekkefølge.
| I strømmen | Chat Completions | Responses | Messages |
|---|---|---|---|
| Rammer | data:-linjer | event:- og data:-linjer | event:- og data:-linjer |
| Resonnering kommer i | delta.reasoning_content | response.reasoning_summary_text.delta | thinking_delta |
| Svartekst kommer i | delta.content | response.output_text.delta | text_delta |
| Usage kommer i | Den siste chunken | response.completed | message_delta |
| Strømmen ender med | Den siste chunken, deretter data: [DONE] | response.completed | message_stop |