Straumēšana
Iestatiet stream uz true, un atbilde ierodas kā servera sūtīti notikumi, kamēr modelis to raksta: vispirms spriešana, tad atbilde, tad pēdējā daļa ar beigu iemeslu un izmantošanas datiem. Izmantojiet to vienmēr, kad cilvēks gaida tekstu, un garām atbildēm.
POST https://api.shannon-ai.com/v1/chat/completions
OpenAI SDK dod jums daļas pa vienai. Lasiet katru daļu pēc tā, ko tā nes: spriešanas gabalu, atbildes gabalu, rīka izsaukumu vai atbildes beigas.
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
}' Kā straume tiek rāmēta
- Atbildei ir statuss
200un galvenescontent-type: text/event-streamuncache-control: no-cache. - Katrs notikums ir viena rinda, kas sākas ar
data:un kam seko tukša rinda. Teksts pēcdata:ir viena JSON daļa. Rinduevent:vaiid:nav. - Rinda, kas sākas ar kolu, piemēram,
: ping, ir komentārs, kas patur savienojumu atvērtu. Izlaidiet to. - Straume beidzas ar
data: [DONE]. Straume, kas apstājas bez tā, ir nepilnīga.
Daļas secībā
Katra daļa ir objekts 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
}
]
} Visām vienas straumes daļām ir viens un tas pats id, created un model. choices vienmēr satur vienu ierakstu. Tā delta satur to, kas ir jauns, un finish_reason ir null līdz pēdējai daļai.
| Atslēga laukā delta | Tips | Apraksts | Sūta |
|---|---|---|---|
role | string | Vienmēr assistant. Tas ierodas vienreiz, straumes sākumā. | Hostētie atvērto svaru modeļi, shannon-2-lite, shannon-2-pro |
reasoning_content | string | Modeļa spriešanas gabals. Savienojiet gabalus pēc kārtas. | Hostētie atvērto svaru modeļi, Shannon 3 saime, shannon-1.6-pro, shannon-coder-1 |
content | string | Atbildes gabals. Savienojiet gabalus pēc kārtas. | Visi modeļi |
tool_calls | array | Viens pilns rīka izsaukums. Skatiet Rīku izsaukumu delta. | Visi modeļi |
Vispirms nāk spriešana, tad atbilde, tad rīku izsaukumi. Lasiet daļu pēc atslēgām tās delta, nevis pēc tās vietas straumē: delta var saturēt role kopā ar pirmo teksta gabalu, un pēdējā ir tukša.
Visa straume tā, kā tā ceļo. Lai rindas paliktu īsas, katrai daļai šeit izlaisti id, object, created un 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] Spriešanas delta
Modelis, kas spriež, pirms atbildes sūta savu spriešanu laukā delta.reasoning_content. Turiet abus tekstus atsevišķi: rādiet spriešanu kā atsevišķu, sakļaujamu daļu vai izlaidiet to.
Hostētie atvērto svaru modeļi nesūta spriešanu, kad reasoning_effort ir off, un arī tad, ja pieprasījumam ir response_format.
shannon-2-lite un shannon-2-pro arī spriež pirms atbildes. Šajā galapunktā, kamēr tie spriež, tie sūta komentāru rindas : thinking, un pēc tam atbildi.
reasoning_content nav OpenAI SDK tipizētajos modeļos. Python to lasiet ar getattr(delta, "reasoning_content", None); JavaScript delta.reasoning_content darbojas tāds, kāds ir.
Rīku izsaukumu delta
Rīka izsaukums ierodas pilns: viena daļa katram izsaukumam, kuras delta satur izsaukumu ar pilnu arguments virkni. index numurē atbildes izsaukumus no 0. Kods, kas arguments savieno pa gabaliem, darbojas bez izmaiņām, jo savieno vienu gabalu.
{
"tool_calls": [
{
"index": 0,
"id": "call_3d9a7c1e5b2f4a60c8e1d7f09b24a6c5",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"Paris\"}"
}
}
]
} Pēc izsaukumiem pēdējai daļai ir finish_reason tool_calls.
Ja pieprasījumam ir tools, shannon-1.6-lite, shannon-1.6-pro un shannon-coder-1 sūta atbildes tekstu kā vienu delta atbildes beigās.
Pēdējā daļa
Pēdējā daļa pirms data: [DONE] ir ar tukšu delta, finish_reason un pieprasījuma usage, visu vienā daļā. Tā ir katrai straumei; stream_options nav vajadzīgs.
{
"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
}
} Hostētajiem atvērto svaru modeļiem usage ir arī prompt_tokens_details.cached_tokens un completion_tokens_details.reasoning_tokens.
Pēc pieprasījuma ar web_search: true, kura meklēšana kaut ko atrada, pēdējā daļā ir arī sources, bet viena daļa pirms tās satur citēšanas atzīmes kā delta.annotations. Tīmekļa meklēšana
| finish_reason | Apraksts | Sūta |
|---|---|---|
stop | Modelis pabeidza savu atbildi, vai parādījās stop virkne. | Visi modeļi |
tool_calls | Atbildē ir viens vai vairāki rīku izsaukumi. | Visi modeļi |
length | Atbilde tika pārtraukta pie izvades ierobežojuma. | Shannon 3 saime, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 |
usage lauki ir aprakstīti pie galapunkta. Chat Completions
Keep-alive rindas
Kamēr modelis strādā un tam vēl nav ko sūtīt, straume nes komentāru rindas. Tajās nav datu. SSE parsētājs tās izlaiž pats; kodam, kas lasa neapstrādātas rindas, jāizlaiž katra rinda, kas sākas ar kolu.
| Rinda | Sūta | Kad |
|---|---|---|
: ping | Shannon 3 saime, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 | Ik pēc 15 sekundēm. |
: keepalive | Hostētie atvērto svaru modeļi | Ik pēc 15 sekundēm, kamēr modelis strādā. |
: thinking | shannon-2-lite, shannon-2-pro | Kamēr modelis spriež. |
Kļūdas straumes iekšienē
Kad straume ir sākusies, tās statuss ir 200, tāpēc kļūme ierodas straumes iekšienē: daļa ar locekli error tur, kur būtu 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] Pēc kļūdas daļas tāpat seko pēdējā daļa ar finish_reason un usage un data: [DONE]. Pārbaudiet katru daļu, vai tajā nav error. Bez šīs pārbaudes atbilde, kas neizdevās pusceļā, izskatās pilnīga.
| Tips | Kods | Ziņojums | Kad |
|---|---|---|---|
api_error | The model backend failed to answer. Please retry. | Modelis neizdevās vai neko neuzrakstīja. Nosūtiet pieprasījumu vēlreiz. | |
rate_limit_error | Shannon routes are temporarily busy. Please retry. | Modelis šobrīd ir aizņemts. Pagaidiet dažas sekundes un nosūtiet pieprasījumu vēlreiz. | |
invalid_request_error | context_length_exceeded | Saruna ir garāka par modeļa konteksta logu. Saīsiniet to, pirms sūtāt vēlreiz. Ziņojuma teksts mainās. |
Hostētie atvērto svaru modeļi sūta locekli error tikai ar type un message.
OpenAI Python un JavaScript SDK kļūdas daļu iterēšanas laikā izceļ kā API kļūdu, tāpēc ietiniet ciklu savā ierastajā kļūdu apstrādē.
Statusa kodiem, kļūdas pamattekstam un tam, ko atkārtot, ir sava lapa. Kļūdu apstrāde
Savienojuma aizvēršana
Savienojuma aizvēršana aptur piegādi, nevis pieprasījumu. Modelis atbildi uzraksta līdz galam, un pieprasījums tiek rēķināts tā, it kā būtu izlasīta visa atbilde. Lai maksātu mazāk, pieprasiet mazāk: iestatiet zemāku max_tokens modelim, kas to piemēro.
Straume var arī beigties pāragri ceļā pie jums, piemēram, tīkla pārrāvuma dēļ vai servera atjaunināšanas laikā. Tad tā apstājas bez pēdējās daļas un bez data: [DONE]. Uzskatiet šādu atbildi par nepilnīgu un nosūtiet pieprasījumu vēlreiz.
Straumes lasīšana bez SDK
Pareizs lasītājs dara piecas lietas: kļūdas, kas ierodas pirms straumes, nosūta savā kļūdu apstrādē, sadala pamattekstu rindās, izlaiž komentāru rindas, apstājas pie data: [DONE] un katrā daļā pārbauda 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 piemērs izmanto paketi requests. JavaScript piemērs darbojas ar Node.js 18 vai jaunāku.
Citu formātu straumes
Galapunkti Responses un Messages arī straumē. Viņu kadri nes rindu event: ar notikuma nosaukumu un rindu data: ar tā JSON. Katra lapa uzskaita savus notikumus secībā.
| Straumē | Chat Completions | Responses | Messages |
|---|---|---|---|
| Kadri | data: rindas | event: un data: rindas | event: un data: rindas |
| Spriešana ierodas | delta.reasoning_content | response.reasoning_summary_text.delta | thinking_delta |
| Atbildes teksts ierodas | delta.content | response.output_text.delta | text_delta |
| Izmantošanas dati ierodas | Pēdējā daļa | response.completed | message_delta |
| Straume beidzas ar | Pēdējā daļa, tad data: [DONE] | response.completed | message_stop |