Pretakanje
Nastavite stream na true in odgovor prispe kot dogodki, ki jih pošilja strežnik (server-sent events), medtem ko ga model piše: najprej razmišljanje, nato odgovor, nato zadnji kos z razlogom zaključka in porabo (usage). Uporabite ga vedno, ko oseba čaka na besedilo, in za dolge odgovore.
POST https://api.shannon-ai.com/v1/chat/completions
SDK-ji OpenAI vam dajejo kose enega za drugim. Vsak kos preberite po tem, kar nosi: košček razmišljanja, košček odgovora, klic orodja ali konec odgovora.
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
}' Kako je tok oblikovan
- Odgovor ima status
200ter glavicontent-type: text/event-streamincache-control: no-cache. - Vsak dogodek je ena vrstica, ki se začne z
data:in ji sledi prazna vrstica. Besedilo zadata:je en kos JSON. Vrsticevent:aliid:ni. - Vrstica, ki se začne z dvopičjem, kot je
: ping, je komentar, ki ohranja povezavo odprto. Preskočite jo. - Tok se konča z
data: [DONE]. Tok, ki se ustavi brez tega, je nepopoln.
Kosi po vrsti
Vsak kos je objekt 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
}
]
} Vsi kosi enega toka nosijo isti id, created in model. choices vedno vsebuje en vnos. Njegov delta vsebuje, kar je novega, finish_reason pa je null do zadnjega kosa.
| Ključ v delta | Tip | Opis | Pošlje |
|---|---|---|---|
role | string | Vedno assistant. Prispe enkrat, na začetku toka. | Gostovani modeli z odprtimi utežmi, shannon-2-lite, shannon-2-pro |
reasoning_content | string | Košček razmišljanja modela. Koščke združite po vrsti. | Gostovani modeli z odprtimi utežmi, družina Shannon 3, shannon-1.6-pro, shannon-coder-1 |
content | string | Košček odgovora. Koščke združite po vrsti. | Vsi modeli |
tool_calls | array | En popoln klic orodja. Glejte Delte klicev orodij. | Vsi modeli |
Najprej pride razmišljanje, nato odgovor, nato morebitni klici orodij. Kos preberite po ključih v njegovem delta, ne po mestu v toku: delta lahko vsebuje role skupaj s prvim koščkom besedila, zadnji pa je prazen.
Celoten tok, kakor potuje. Da so vrstice kratke, so id, object, created in model tu iz vsakega kosa izpuščeni.
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] Delte razmišljanja
Model, ki razmišlja, pošlje svoje razmišljanje v delta.reasoning_content pred odgovorom. Obe besedili ločite: razmišljanje prikažite kot ločen del, ki ga je mogoče zložiti, ali pa ga izpustite.
Gostovani modeli z odprtimi utežmi ne pošljejo razmišljanja, kadar je reasoning_effort off, in ne, kadar zahtevek vsebuje response_format.
Tudi shannon-2-lite in shannon-2-pro razmišljata, preden odgovorita. Na tej končni točki medtem pošiljata komentarne vrstice : thinking, nato odgovor.
reasoning_content ni v tipiziranih modelih SDK-jev OpenAI. V Pythonu ga preberite z getattr(delta, "reasoning_content", None); v JavaScriptu delta.reasoning_content deluje takoj.
Delte klicev orodij
Klic orodja prispe celoten: en kos na klic, katerega delta vsebuje klic s popolnim nizom arguments. index šteje klice odgovora od 0. Koda, ki arguments združuje košček za koščkom, deluje nespremenjena, ker združi en košček.
{
"tool_calls": [
{
"index": 0,
"id": "call_3d9a7c1e5b2f4a60c8e1d7f09b24a6c5",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"Paris\"}"
}
}
]
} Po klicih ima zadnji kos finish_reason tool_calls.
Kadar zahtevek vsebuje tools, shannon-1.6-lite, shannon-1.6-pro in shannon-coder-1 pošljejo besedilo odgovora kot en delta ob koncu odgovora.
Zadnji kos
Zadnji kos pred data: [DONE] ima prazen delta, finish_reason in usage zahtevka, vse v istem kosu. Vsak tok ga ima; stream_options ne potrebujete.
{
"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
}
} Pri gostovanih modelih z odprtimi utežmi ima usage tudi prompt_tokens_details.cached_tokens in completion_tokens_details.reasoning_tokens.
Po zahtevku z web_search: true, katerega iskanje je kaj našlo, zadnji kos nosi tudi sources, en kos pred njim pa nosi oznake navedkov kot delta.annotations. Vgrajeno spletno iskanje
| finish_reason | Opis | Pošlje |
|---|---|---|
stop | Model je končal svoj odgovor ali pa se je pojavil niz stop. | Vsi modeli |
tool_calls | Odgovor vsebuje enega ali več klicev orodij. | Vsi modeli |
length | Odgovor je bil odrezan na omejitvi izhoda. | Družina Shannon 3, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 |
Polja usage so opisana pri končni točki. Chat Completions
Vrstice za ohranjanje povezave
Medtem ko model dela in še nima kaj poslati, tok nosi komentarne vrstice. Ne vsebujejo podatkov. Razčlenjevalnik SSE jih preskoči sam; koda, ki bere surove vrstice, mora preskočiti vsako vrstico, ki se začne z dvopičjem.
| Vrstica | Pošlje | Kdaj |
|---|---|---|
: ping | Družina Shannon 3, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 | Vsakih 15 sekund. |
: keepalive | Gostovani modeli z odprtimi utežmi | Vsakih 15 sekund, medtem ko model dela. |
: thinking | shannon-2-lite, shannon-2-pro | Medtem ko model razmišlja. |
Napake znotraj toka
Ko se tok začne, je njegov status 200, zato napaka prispe znotraj toka: kos s članom error tam, kjer bi bil 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] Za kosom z napako še vedno sledita zadnji kos, s finish_reason in usage, ter data: [DONE]. V vsakem kosu preverite error. Brez tega preverjanja videti odgovor, ki je odpovedal na pol poti, popoln.
| Tip | Koda | Sporočilo | Kdaj |
|---|---|---|---|
api_error | The model backend failed to answer. Please retry. | Model je odpovedal ali ni napisal ničesar. Pošljite zahtevek znova. | |
rate_limit_error | Shannon routes are temporarily busy. Please retry. | Model je v tem trenutku zaseden. Počakajte nekaj sekund in zahtevek pošljite znova. | |
invalid_request_error | context_length_exceeded | Pogovor je daljši od kontekstnega okna modela. Skrajšajte ga, preden ga pošljete znova. Besedilo sporočila se razlikuje. |
Gostovani modeli z odprtimi utežmi pošljejo člana error samo s type in message.
SDK-ja OpenAI za Python in JavaScript kos z napako med iteracijo sprožita kot napako API, zato zanko ovijte v svoje običajno obravnavanje napak.
Statusne kode, telo napake in kaj ponoviti imajo svojo stran. Obravnava napak
Zapiranje povezave
Zaprtje povezave ustavi dostavo, ne zahtevka. Model odgovor napiše do konca, zahtevek pa se obračuna, kot da bi ga prebrali v celoti. Če želite plačati manj, zahtevajte manj: nastavite nižji max_tokens pri modelu, ki ga upošteva.
Tok se lahko na poti do vas konča tudi predčasno, na primer ob prekinitvi omrežja ali med posodabljanjem strežnika. Takrat se ustavi brez zadnjega kosa in brez data: [DONE]. Tak odgovor obravnavajte kot nepopoln in zahtevek pošljite znova.
Branje toka brez SDK-ja
Pravilen bralnik naredi pet stvari: napake, ki prispejo pred tokom, preusmeri v svoje obravnavanje napak, telo razdeli na vrstice, preskoči komentarne vrstice, ustavi se pri data: [DONE] in v vsakem kosu preveri 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); Primer v Pythonu uporablja paket requests. Primer v JavaScriptu teče na Node.js 18 ali novejšem.
Tokovi drugih formatov
Končni točki Responses in Messages prav tako pretakata. Njuni okvirji nosijo vrstico event: z imenom dogodka in vrstico data: z njegovim JSON. Vsaka stran navaja svoje dogodke po vrsti.
| V toku | Chat Completions | Responses | Messages |
|---|---|---|---|
| Okvirji | Vrstice data: | Vrstice event: in data: | Vrstice event: in data: |
| Razmišljanje prispe v | delta.reasoning_content | response.reasoning_summary_text.delta | thinking_delta |
| Besedilo odgovora prispe v | delta.content | response.output_text.delta | text_delta |
| Poraba (usage) prispe v | Zadnji kos | response.completed | message_delta |
| Tok se konča z | Zadnji kos, nato data: [DONE] | response.completed | message_stop |