Preskoči na sadržaj
Streaming

Streaming

Postavite stream na true i odgovor stiže kao server-sent events dok ga model piše: prvo razmišljanje, zatim odgovor, zatim posljednji chunk s razlogom završetka i usage. Koristite ga kad god osoba čeka tekst, te za duge odgovore.

POST https://api.shannon-ai.com/v1/chat/completions

OpenAI SDK-ovi vam daju chunkove jedan po jedan. Čitajte svaki chunk prema onome što nosi: dio razmišljanja, dio odgovora, poziv alata ili kraj 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}")

Kako je stream uokviren

  • Odgovor ima status 200 i zaglavlja content-type: text/event-stream i cache-control: no-cache.
  • Svaki događaj je jedna linija koja počinje sa data: i iza koje slijedi prazna linija. Tekst iza data: je jedan JSON chunk. Nema linija event: ni id:.
  • Linija koja počinje dvotačkom, kao : ping, je komentar koji drži konekciju otvorenom. Preskočite je.
  • Stream završava sa data: [DONE]. Stream koji stane bez toga je nepotpun.

Chunkovi redom

Svaki chunk je objekat chat.completion.chunk:

JSON
{
  "id": "chatcmpl-5f0c1e7a9b3d4c62a8e1f07d2b46c9a3",
  "object": "chat.completion.chunk",
  "created": 1791625200,
  "model": "shannon-3",
  "choices": [
    {
      "index": 0,
      "delta": {
        "content": "Server-sent"
      },
      "finish_reason": null
    }
  ]
}

Svi chunkovi jednog streama nose isti id, created i model. choices uvijek sadrži jedan unos. Njegov delta sadrži ono što je novo, a finish_reason je null do posljednjeg chunka.

Ključ u delta Tip Opis Šalje
role string Uvijek assistant. Stiže jednom, na početku streama. Hostirani open-weight modeli, shannon-2-lite, shannon-2-pro
reasoning_content string Dio razmišljanja modela. Spojite dijelove redom. Hostirani open-weight modeli, porodica Shannon 3, shannon-1.6-pro, shannon-coder-1
content string Dio odgovora. Spojite dijelove redom. Svi modeli
tool_calls array Jedan kompletan poziv alata. Pogledajte Delte poziva alata. Svi modeli

Prvo dolazi razmišljanje, zatim odgovor, zatim eventualni pozivi alata. Čitajte chunk po ključevima u njegovom delta, a ne po njegovom mjestu u streamu: delta može sadržavati role zajedno s prvim dijelom teksta, a posljednja je prazna.

Cijeli stream onako kako putuje. Radi kraćih linija, id, object, created i model su ovdje izostavljeni iz svakog chunka.

200 text/event-stream
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 koji razmišlja šalje svoje razmišljanje u delta.reasoning_content prije odgovora. Držite ta dva teksta odvojeno: prikažite razmišljanje kao zaseban dio koji se može sklopiti, ili ga izostavite.

Hostirani open-weight modeli ne šalju razmišljanje kada je reasoning_effort off, niti kada zahtjev ima response_format.

shannon-2-lite i shannon-2-pro također razmišljaju prije nego što odgovore. Na ovom endpointu šalju linije komentara : thinking dok to rade, a zatim odgovor.

reasoning_content nije u tipiziranim modelima OpenAI SDK-ova. U Pythonu ga čitajte s getattr(delta, "reasoning_content", None); u JavaScriptu delta.reasoning_content radi kakav jest.

Delte poziva alata

Poziv alata stiže cijel: jedan chunk po pozivu, čiji delta sadrži poziv s kompletnim stringom arguments. index numeriše pozive odgovora od 0. Kod koji spaja arguments dio po dio radi neizmijenjen, jer spaja jedan dio.

delta
{
  "tool_calls": [
    {
      "index": 0,
      "id": "call_3d9a7c1e5b2f4a60c8e1d7f09b24a6c5",
      "type": "function",
      "function": {
        "name": "get_weather",
        "arguments": "{\"city\":\"Paris\"}"
      }
    }
  ]
}

Poslije poziva, posljednji chunk ima finish_reason tool_calls.

Kada zahtjev ima tools, shannon-1.6-lite, shannon-1.6-pro i shannon-coder-1 šalju tekst odgovora kao jednu deltu na kraju odgovora.

Posljednji chunk

Posljednji chunk prije data: [DONE] ima prazan delta, finish_reason i usage zahtjeva, sve u istom chunku. Svaki stream ga ima; stream_options ne treba.

JSON
{
  "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
  }
}

Na hostiranim open-weight modelima usage ima i prompt_tokens_details.cached_tokens i completion_tokens_details.reasoning_tokens.

Nakon zahtjeva s web_search: true čija je pretraga nešto našla, posljednji chunk nosi i sources, a jedan chunk prije njega nosi oznake navoda kao delta.annotations. Ugrađena web pretraga

finish_reason Opis Šalje
stop Model je završio svoj odgovor, ili se pojavio string stop. Svi modeli
tool_calls Odgovor sadrži jedan ili više poziva alata. Svi modeli
length Odgovor je prekinut na ograničenju izlaza. Porodica Shannon 3, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1

Polja usage opisana su uz endpoint. Chat Completions

Keep-alive linije

Dok model radi i još nema šta poslati, stream nosi linije komentara. One ne sadrže podatke. SSE parser ih sam preskače; kod koji čita sirove linije mora preskočiti svaku liniju koja počinje dvotačkom.

Linija Šalje Kada
: ping Porodica Shannon 3, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 Svakih 15 sekundi.
: keepalive Hostirani open-weight modeli Svakih 15 sekundi dok model radi.
: thinking shannon-2-lite, shannon-2-pro Dok model razmišlja.

Greške unutar streama

Kada stream počne, njegov status je 200, pa greška stiže unutar streama: chunk s članom error tamo gdje bi bio choices.

200 text/event-stream
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]

Nakon chunka s greškom i dalje slijede posljednji chunk, s finish_reason i usage, te data: [DONE]. Provjerite svaki chunk na error. Bez te provjere odgovor koji je propao na pola puta izgleda kompletno.

Tip Kod Poruka Kada
api_error The model backend failed to answer. Please retry. Model je zakazao, ili nije napisao ništa. Pošaljite zahtjev ponovo.
rate_limit_error Shannon routes are temporarily busy. Please retry. Model je trenutno zauzet. Sačekajte nekoliko sekundi i pošaljite zahtjev ponovo.
invalid_request_error context_length_exceeded Razgovor je duži od kontekstnog prozora modela. Skratite ga prije nego što ga ponovo pošaljete. Tekst poruke varira.

Hostirani open-weight modeli šalju član error samo s type i message.

OpenAI Python i JavaScript SDK-ovi podižu chunk s greškom kao API grešku dok iterirate, zato petlju umotajte u svoje uobičajeno rukovanje greškama.

Statusni kodovi, tijelo greške i šta ponoviti imaju vlastitu stranicu. Upravljanje greškama

Zatvaranje konekcije

Zatvaranje konekcije zaustavlja isporuku, a ne zahtjev. Model piše odgovor do kraja i zahtjev se naplaćuje kao da ste pročitali sve. Da platite manje, tražite manje: postavite niži max_tokens na modelu koji ga primjenjuje.

Stream se može i prijevremeno prekinuti na putu do vas, na primjer pri prekidu mreže ili dok se server ažurira. Tada staje bez posljednjeg chunka i bez data: [DONE]. Smatrajte takav odgovor nepotpunim i pošaljite zahtjev ponovo.

Čitanje streama bez SDK-a

Ispravan čitač radi pet stvari: greške koje stignu prije streama šalje svom rukovanju greškama, dijeli tijelo na linije, preskače linije komentara, staje na data: [DONE] i provjerava svaki chunk na 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))

Python primjer koristi paket requests. JavaScript primjer radi na Node.js 18 ili novijem.

Streamovi ostalih formata

Endpointi Responses i Messages također streamuju. Njihovi frameovi nose liniju event: s imenom događaja i liniju data: s njegovim JSON-om. Svaka stranica navodi svoje događaje redom.

U streamu Chat CompletionsResponsesMessages
Frameovi Linije data:Linije event: i data:Linije event: i data:
Razmišljanje stiže u delta.reasoning_contentresponse.reasoning_summary_text.deltathinking_delta
Tekst odgovora stiže u delta.contentresponse.output_text.deltatext_delta
Usage stiže u Posljednji chunkresponse.completedmessage_delta
Stream završava sa Posljednji chunk, zatim data: [DONE]response.completedmessage_stop