Streaming
Állítsd a streamet true értékre, és a válasz server-sent eventként érkezik, miközben a modell írja: először a gondolkodás, utána a válasz, végül egy utolsó chunk a befejezés okával és a használati adatokkal. Használd, amikor ember vár a szövegre, és hosszú válaszoknál.
POST https://api.shannon-ai.com/v1/chat/completions
Az OpenAI SDK-k egyenként adják a chunkokat. Minden chunkot aszerint olvass, mit hordoz: gondolkodás egy darabját, a válasz egy darabját, eszközhívást vagy a válasz végét.
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
}' Hogyan keretezett a stream
- A válasz státusza
200, a fejlécei pedigcontent-type: text/event-streaméscache-control: no-cache. - Minden esemény egy
data:előtaggal kezdődő sor, amelyet egy üres sor követ. Adata:utáni szöveg egy JSON-chunk. Nincsenekevent:vagyid:sorok. - A kettősponttal kezdődő sor, például a
: ping, megjegyzés, amely nyitva tartja a kapcsolatot. Ugord át. - A stream a
data: [DONE]sorral ér véget. Az a stream, amely nélküle áll le, hiányos.
Chunkok sorrendben
Minden chunk egy chat.completion.chunk objektum:
{
"id": "chatcmpl-5f0c1e7a9b3d4c62a8e1f07d2b46c9a3",
"object": "chat.completion.chunk",
"created": 1791625200,
"model": "shannon-3",
"choices": [
{
"index": 0,
"delta": {
"content": "Server-sent"
},
"finish_reason": null
}
]
} Egy stream összes chunkja ugyanazt az id, created és model értéket hordozza. A choices mindig egy elemet tartalmaz. A delta azt tartalmazza, ami új, a finish_reason pedig null az utolsó chunkig.
| Kulcs a delta-ban | Típus | Leírás | Küldi |
|---|---|---|---|
role | string | Mindig assistant. A stream elején egyszer érkezik. | Hosztolt nyílt súlyú modellek, shannon-2-lite, shannon-2-pro |
reasoning_content | string | A modell gondolkodásának egy darabja. Fűzd össze a darabokat sorrendben. | Hosztolt nyílt súlyú modellek, Shannon 3 család, shannon-1.6-pro, shannon-coder-1 |
content | string | A válasz egy darabja. Fűzd össze a darabokat sorrendben. | Minden modell |
tool_calls | array | Egy teljes eszközhívás. Lásd: Eszközhívás-deltái. | Minden modell |
Először a gondolkodás jön, utána a válasz, majd az esetleges eszközhívások. A chunkot a delta kulcsai alapján olvasd, ne a stream-beli helye alapján: a delta hordozhat role mezőt az első szövegdarabbal együtt, az utolsó pedig üres.
Egy teljes stream úgy, ahogy utazik. A sorok röviden tartása végett az id, object, created és model mező itt minden chunkból ki van hagyva.
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] Gondolkodási deltái
A gondolkodó modell a gondolkodását a delta.reasoning_content mezőben küldi a válasz előtt. Tartsd külön a két szöveget: mutasd a gondolkodást külön, összecsukható részként, vagy hagyd el.
A hosztolt nyílt súlyú modellek nem küldenek gondolkodást, ha a reasoning_effort értéke off, és akkor sem, ha a kérés response_format mezőt tartalmaz.
A shannon-2-lite és a shannon-2-pro is gondolkodik a válasz előtt. Ezen a végponton eközben : thinking megjegyzéssorokat küldenek, majd a választ.
A reasoning_content nincs benne az OpenAI SDK-k típusos modelljeiben. Pythonban a getattr(delta, "reasoning_content", None) hívással olvasd; JavaScriptben a delta.reasoning_content közvetlenül működik.
Eszközhívás-deltái
Az eszközhívás egészében érkezik: hívásonként egy chunk, amelynek delta mezője a hívást a teljes arguments stringgel tartalmazza. Az index a válasz hívásait számozza 0-tól. Az arguments mezőt darabonként összefűző kód változtatás nélkül működik, mert egy darabot fűz.
{
"tool_calls": [
{
"index": 0,
"id": "call_3d9a7c1e5b2f4a60c8e1d7f09b24a6c5",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"Paris\"}"
}
}
]
} A hívások után az utolsó chunk finish_reason értéke tool_calls.
Ha a kérés tools mezőt tartalmaz, a shannon-1.6-lite, shannon-1.6-pro és shannon-coder-1 a válaszszöveget egyetlen deltában küldi a válasz végén.
Az utolsó chunk
A data: [DONE] előtti utolsó chunknak üres a delta mezője, és a kérés finish_reason és usage értékét tartalmazza, mindet ugyanabban a chunkban. Minden stream tartalmazza; nincs szükséged a stream_options mezőre.
{
"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
}
} A hosztolt nyílt súlyú modelleknél a usage a prompt_tokens_details.cached_tokens és a completion_tokens_details.reasoning_tokens mezőt is tartalmazza.
Az olyan kérés után, amely web_search: true értéket küld, és amelynek a keresése talált valamit, az utolsó chunk sources mezőt is tartalmaz, az előtte lévő chunk pedig a hivatkozási jelöléseket hordozza delta.annotations néven. Beépített webes keresés
| finish_reason | Leírás | Küldi |
|---|---|---|
stop | A modell befejezte a válaszát, vagy megjelent egy stop string. | Minden modell |
tool_calls | A válasz egy vagy több eszközhívást tartalmaz. | Minden modell |
length | A választ a kimeneti korlátnál levágták. | Shannon 3 család, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 |
A usage mezőit a végpont leírása tartalmazza. Chat Completions
Keep-alive sorok
Amíg a modell dolgozik, és még nincs mit küldenie, a stream megjegyzéssorokat hordoz. Nem tartalmaznak adatot. Az SSE-értelmező magától átugorja őket; a nyers sorokat olvasó kódnak át kell ugrania minden kettősponttal kezdődő sort.
| Sor | Küldi | Mikor |
|---|---|---|
: ping | Shannon 3 család, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 | 15 másodpercenként. |
: keepalive | Hosztolt nyílt súlyú modellek | 15 másodpercenként, amíg a modell dolgozik. |
: thinking | shannon-2-lite, shannon-2-pro | Amíg a modell gondolkodik. |
Hibák a streamen belül
Miután a stream elindult, a státusza 200, ezért a hiba a streamen belül érkezik: egy chunk error taggal ott, ahol a choices lenne.
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] A hibás chunk után még követi az utolsó chunk, a finish_reason és a usage értékkel, valamint a data: [DONE]. Minden chunkban ellenőrizd az error mezőt. Ellenőrzés nélkül a félúton megbukott válasz teljesnek látszik.
| Típus | Kód | Üzenet | Mikor |
|---|---|---|---|
api_error | The model backend failed to answer. Please retry. | A modell hibázott, vagy nem írt semmit. Küldd el újra a kérést. | |
rate_limit_error | Shannon routes are temporarily busy. Please retry. | A modell ebben a pillanatban foglalt. Várj néhány másodpercet, és küldd el újra a kérést. | |
invalid_request_error | context_length_exceeded | A beszélgetés hosszabb a modell kontextusablakánál. Rövidítsd, mielőtt újra elküldöd. Az üzenet szövege változó. |
A hosztolt nyílt súlyú modellek az error tagot csak type és message mezővel küldik.
Az OpenAI Python és JavaScript SDK-k a hibás chunkot API-hibaként dobják, amíg iterálsz, ezért a ciklust vedd körbe a szokásos hibakezeléseddel.
A státuszkódok, a hibatörzs és az újrapróbálandó esetek külön oldalon vannak. Hibakezelés
A kapcsolat bezárása
A kapcsolat bezárása a kézbesítést állítja le, nem a kérést. A modell a válasz végéig megírja a választ, és a kérést úgy számlázza, mintha végigolvastad volna. Ha kevesebbet akarsz fizetni, kevesebbet kérj: állíts alacsonyabb max_tokens értéket olyan modellen, amely érvényesíti.
A stream hozzád vezető úton korán is véget érhet, például hálózati szakadáskor vagy szerverfrissítés közben. Ilyenkor az utolsó chunk és a data: [DONE] nélkül áll le. Tekintsd hiányosnak az ilyen választ, és küldd el újra a kérést.
Stream olvasása SDK nélkül
A helyes olvasó öt dolgot tesz: a stream előtt érkező hibákat a hibakezelésébe küldi, a törzset sorokra bontja, átugorja a megjegyzéssorokat, megáll a data: [DONE] soron, és minden chunkban ellenőrzi az error mezőt.
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); A Python minta a requests csomagot használja. A JavaScript minta Node.js 18 vagy újabb verzión fut.
A többi formátum streamjei
A Responses és a Messages végpont is streamel. Framejeik egy event: sort hordoznak az esemény nevével, és egy data: sort a JSON-jával. Mindegyik oldal sorrendben felsorolja az eseményeit.
| A streamben | Chat Completions | Responses | Messages |
|---|---|---|---|
| Framek | data: sorok | event: és data: sorok | event: és data: sorok |
| A gondolkodás ebben érkezik | delta.reasoning_content | response.reasoning_summary_text.delta | thinking_delta |
| A válaszszöveg ebben érkezik | delta.content | response.output_text.delta | text_delta |
| A használati adatok ebben érkeznek | Az utolsó chunk | response.completed | message_delta |
| A stream vége | Az utolsó chunk, majd a data: [DONE] | response.completed | message_stop |