Srautinė transliacija
Nustatykite stream į true, ir atsakymas ateis kaip serverio siunčiami įvykiai, kol modelis jį rašo: pirmiausia mąstymas, paskui atsakymas, paskui paskutinis gabalas su baigimo priežastimi ir naudojimu. Naudokite tai visada, kai tekstą laukia žmogus, ir ilgiems atsakymams.
POST https://api.shannon-ai.com/v1/chat/completions
OpenAI SDK gabalus pateikia po vieną. Kiekvieną gabalą skaitykite pagal tai, ką jis neša: mąstymo dalį, atsakymo dalį, įrankio kreipinį arba atsakymo pabaigą.
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
}' Kaip suskirstytas srautas
- Atsakymas turi būseną
200ir antraštescontent-type: text/event-streambeicache-control: no-cache. - Kiekvienas įvykis yra viena eilutė, prasidedanti
data:, po kurios eina tuščia eilutė. Tekstas podata:yra vienas JSON gabalas. Eilučiųevent:arid:nėra. - Eilutė, prasidedanti dvitaškiu, pavyzdžiui,
: ping, yra komentaras, palaikantis ryšį atvirą. Praleiskite ją. - Srautas baigiasi
data: [DONE]. Srautas, sustojęs be jo, yra neužbaigtas.
Gabalai iš eilės
Kiekvienas gabalas yra objektas 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
}
]
} Visi vieno srauto gabalai turi tą patį id, created ir model. choices visada turi vieną įrašą. Jo delta talpina tai, kas nauja, o finish_reason yra null iki paskutinio gabalo.
| Raktas delta objekte | Tipas | Aprašymas | Siunčia |
|---|---|---|---|
role | string | Visada assistant. Ateina vieną kartą, srauto pradžioje. | Talpinami atvirų svorių modeliai, shannon-2-lite, shannon-2-pro |
reasoning_content | string | Modelio mąstymo dalis. Dalis sujunkite iš eilės. | Talpinami atvirų svorių modeliai, Shannon 3 šeima, shannon-1.6-pro, shannon-coder-1 |
content | string | Atsakymo dalis. Dalis sujunkite iš eilės. | Visi modeliai |
tool_calls | array | Vienas pilnas įrankio kreipinys. Žr. Įrankių kreipinių delta. | Visi modeliai |
Pirmiausia eina mąstymas, paskui atsakymas, paskui įrankių kreipiniai. Gabalą skaitykite pagal jo delta raktus, o ne pagal vietą sraute: delta gali talpinti role kartu su pirmąja teksto dalimi, o paskutinė yra tuščia.
Visas srautas taip, kaip jis keliauja. Kad eilutės būtų trumpesnės, čia iš kiekvieno gabalo praleisti id, object, created ir model.
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] Mąstymo delta
Modelis, kuris mąsto, savo mąstymą prieš atsakymą siunčia delta.reasoning_content. Du tekstus laikykite atskirai: mąstymą rodykite kaip atskirą suskleidžiamą dalį arba praleiskite.
Talpinami atvirų svorių modeliai mąstymo nesiunčia, kai reasoning_effort yra off, ir nesiunčia, kai užklausoje yra response_format.
shannon-2-lite ir shannon-2-pro taip pat mąsto prieš atsakydami. Šiame galiniame taške, kol mąsto, jie siunčia komentarų eilutes : thinking, o paskui atsakymą.
reasoning_content nėra OpenAI SDK tipizuotuose modeliuose. Python kalboje skaitykite jį su getattr(delta, "reasoning_content", None); JavaScript kalboje delta.reasoning_content veikia toks, koks yra.
Įrankių kreipinių delta
Įrankio kreipinys ateina visas: vienas gabalas kiekvienam kreipiniui, kurio delta talpina kreipinį su visa arguments eilute. index sunumeruoja atsakymo kreipinius nuo 0. Kodas, kuris arguments jungia dalimis, veikia nepakitęs, nes jungia vieną dalį.
{
"tool_calls": [
{
"index": 0,
"id": "call_3d9a7c1e5b2f4a60c8e1d7f09b24a6c5",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"Paris\"}"
}
}
]
} Po kreipinių paskutinis gabalas turi finish_reason tool_calls.
Kai užklausoje yra tools, shannon-1.6-lite, shannon-1.6-pro ir shannon-coder-1 atsakymo tekstą siunčia kaip vieną delta atsakymo pabaigoje.
Paskutinis gabalas
Paskutinis gabalas prieš data: [DONE] turi tuščią delta, finish_reason ir užklausos usage, visa tai tame pačiame gabale. Jį turi kiekvienas srautas; stream_options nereikia.
{
"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
}
} Talpinamuose atvirų svorių modeliuose usage taip pat turi prompt_tokens_details.cached_tokens ir completion_tokens_details.reasoning_tokens.
Po užklausos su web_search: true, kurios paieška ką nors rado, paskutiniame gabale taip pat yra sources, o vienas gabalas prieš jį talpina citavimo žymeklius kaip delta.annotations. Žiniatinklio paieška
| finish_reason | Aprašymas | Siunčia |
|---|---|---|
stop | Modelis baigė atsakymą arba pasirodė stop eilutė. | Visi modeliai |
tool_calls | Atsakyme yra vienas ar daugiau įrankių kreipinių. | Visi modeliai |
length | Atsakymas nutrauktas pasiekus išvesties ribą. | Shannon 3 šeima, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 |
usage laukai aprašyti galinio taško puslapyje. Chat Completions
Palaikymo eilutės
Kol modelis dirba ir dar nėra ką siųsti, sraute eina komentarų eilutės. Jose duomenų nėra. SSE analizatorius jas praleidžia pats; kodas, skaitantis neapdorotas eilutes, turi praleisti kiekvieną eilutę, prasidedančią dvitaškiu.
| Eilutė | Siunčia | Kada |
|---|---|---|
: ping | Shannon 3 šeima, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 | Kas 15 sekundžių. |
: keepalive | Talpinami atvirų svorių modeliai | Kas 15 sekundžių, kol modelis dirba. |
: thinking | shannon-2-lite, shannon-2-pro | Kol modelis mąsto. |
Klaidos sraute
Kai srautas jau prasidėjo, jo būsena yra 200, todėl nesėkmė ateina sraute: gabalas su nariu error ten, kur būtų 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] Po klaidos gabalo vis tiek eina paskutinis gabalas su finish_reason ir usage bei data: [DONE]. Kiekviename gabale tikrinkite error. Be šios patikros atsakymas, nepavykęs viduryje, atrodo užbaigtas.
| Tipas | Kodas | Pranešimas | Kada |
|---|---|---|---|
api_error | The model backend failed to answer. Please retry. | Modelis nepavyko arba nieko neparašė. Siųskite užklausą dar kartą. | |
rate_limit_error | Shannon routes are temporarily busy. Please retry. | Modelis šiuo metu užimtas. Palaukite kelias sekundes ir siųskite užklausą dar kartą. | |
invalid_request_error | context_length_exceeded | Pokalbis ilgesnis už modelio konteksto langą. Prieš siųsdami iš naujo, jį sutrumpinkite. Pranešimo tekstas skiriasi. |
Talpinami atvirų svorių modeliai narį error siunčia tik su type ir message.
OpenAI Python ir JavaScript SDK, kol iteruojate, klaidos gabalą iškelia kaip API klaidą, todėl apgaubkite ciklą savo įprastu klaidų tvarkymu.
Būsenos kodai, klaidos turinys ir ką kartoti turi savo puslapį. Klaidų tvarkymas
Ryšio uždarymas
Uždarius ryšį sustoja pristatymas, bet ne užklausa. Modelis atsakymą parašo iki galo, o užklausa apmokestinama taip, tarsi būtumėte perskaitę viską. Norėdami mokėti mažiau, prašykite mažiau: modeliui, kuris jį taiko, nustatykite mažesnį max_tokens.
Srautas taip pat gali baigtis anksčiau pakeliui pas jus, pavyzdžiui, nutrūkus tinklui arba atnaujinant serverį. Tada jis sustoja be paskutinio gabalo ir be data: [DONE]. Tokį atsakymą laikykite neužbaigtu ir siųskite užklausą dar kartą.
Srauto skaitymas be SDK
Teisingas skaitytuvas daro penkis dalykus: prieš srautą atėjusias klaidas perduoda savo klaidų tvarkymui, padalija turinį į eilutes, praleidžia komentarų eilutes, sustoja ties data: [DONE] ir kiekviename gabale tikrina 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 pavyzdys naudoja paketą requests. JavaScript pavyzdys veikia su Node.js 18 ar naujesne.
Kitų formatų srautai
Galiniai taškai Responses ir Messages taip pat siunčia srautu. Jų kadruose yra eilutė event: su įvykio pavadinimu ir eilutė data: su jo JSON. Kiekvienas puslapis išvardija savo įvykius iš eilės.
| Sraute | Chat Completions | Responses | Messages |
|---|---|---|---|
| Kadrai | data: eilutės | event: ir data: eilutės | event: ir data: eilutės |
| Mąstymas ateina | delta.reasoning_content | response.reasoning_summary_text.delta | thinking_delta |
| Atsakymo tekstas ateina | delta.content | response.output_text.delta | text_delta |
| Naudojimas ateina | Paskutiniame gabale | response.completed | message_delta |
| Srautas baigiasi | Paskutinis gabalas, tada data: [DONE] | response.completed | message_stop |