Naar de inhoud
Streaming

Streaming

Zet stream op true en het antwoord komt binnen als server-sent events terwijl het model het schrijft: eerst de redenering, dan het antwoord, dan een laatste chunk met de finish reason en het gebruik. Gebruik het altijd als iemand op de tekst wacht, en voor lange antwoorden.

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

De OpenAI-SDK's geven je de chunks een voor een. Lees elke chunk aan de hand van wat hij bevat: een stukje redenering, een stukje van het antwoord, een toolaanroep of het einde van het antwoord.

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}")

Hoe een stream is opgebouwd

  • Het antwoord heeft status 200 en de headers content-type: text/event-stream en cache-control: no-cache.
  • Elk event is één regel die met data: begint en wordt gevolgd door een lege regel. De tekst na data: is één JSON-chunk. Er zijn geen event:- of id:-regels.
  • Een regel die met een dubbele punt begint, zoals : ping, is een commentaarregel die de verbinding open houdt. Sla hem over.
  • De stream eindigt met data: [DONE]. Een stream die zonder deze regel stopt, is onvolledig.

Chunks in volgorde

Elke chunk is een chat.completion.chunk-object:

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

Alle chunks van één stream hebben dezelfde id, created en model. choices bevat altijd één item. Zijn delta bevat wat nieuw is, en finish_reason is null tot de laatste chunk.

Sleutel in delta Type Beschrijving Verstuurd door
role string Altijd assistant. Komt één keer binnen, aan het begin van de stream. Gehoste open-weight modellen, shannon-2-lite, shannon-2-pro
reasoning_content string Een stukje van de redenering van het model. Voeg de stukjes in volgorde samen. Gehoste open-weight modellen, Shannon 3-familie, shannon-1.6-pro, shannon-coder-1
content string Een stukje van het antwoord. Voeg de stukjes in volgorde samen. Alle modellen
tool_calls array Eén complete toolaanroep. Zie Delta's van toolaanroepen. Alle modellen

Eerst komt de redenering, dan het antwoord, dan eventuele toolaanroepen. Lees een chunk aan de hand van de sleutels in zijn delta, niet aan de hand van zijn plek in de stream: een delta kan role bevatten samen met het eerste stuk tekst, en de laatste is leeg.

Een hele stream zoals hij reist. Om de regels kort te houden zijn id, object, created en model hier bij elke chunk weggelaten.

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]

Redeneerdelta's

Een model dat redeneert, stuurt zijn redenering in delta.reasoning_content vóór het antwoord. Houd de twee teksten gescheiden: toon de redenering als een apart, inklapbaar deel, of laat haar weg.

De gehoste open-weight modellen sturen geen redenering als reasoning_effort off is, en ook niet als de aanvraag een response_format heeft.

shannon-2-lite en shannon-2-pro redeneren ook voordat ze antwoorden. Op dit endpoint sturen ze daarbij commentaarregels : thinking terwijl ze redeneren, en daarna het antwoord.

reasoning_content staat niet in de getypeerde modellen van de OpenAI-SDK's. Lees het in Python met getattr(delta, "reasoning_content", None); in JavaScript werkt delta.reasoning_content zoals het is.

Delta's van toolaanroepen

Een toolaanroep komt in zijn geheel binnen: één chunk per aanroep, waarvan de delta de aanroep bevat met de complete arguments-string. index nummert de aanroepen van het antwoord vanaf 0. Code die arguments stukje bij beetje samenvoegt, werkt ongewijzigd, omdat ze één stuk samenvoegt.

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

Na de aanroepen heeft de laatste chunk finish_reason tool_calls.

Als de aanvraag tools heeft, sturen shannon-1.6-lite, shannon-1.6-pro en shannon-coder-1 de antwoordtekst als één delta aan het eind van het antwoord.

De laatste chunk

De laatste chunk vóór data: [DONE] heeft een lege delta, de finish_reason en de usage van de aanvraag, alles in dezelfde chunk. Elke stream heeft hem; je hebt stream_options niet nodig.

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
  }
}

Bij de gehoste open-weight modellen heeft usage ook prompt_tokens_details.cached_tokens en completion_tokens_details.reasoning_tokens.

Na een aanvraag met web_search: true waarvan de zoekopdracht iets vond, bevat de laatste chunk ook sources, en een chunk daarvoor bevat de citatiemarkeringen als delta.annotations. Ingebouwde webzoekopdracht

finish_reason Beschrijving Verstuurd door
stop Het model heeft zijn antwoord afgemaakt, of een stop-string verscheen. Alle modellen
tool_calls Het antwoord bevat een of meer toolaanroepen. Alle modellen
length Het antwoord is bij de outputlimiet afgebroken. Shannon 3-familie, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1

De velden van usage worden bij het endpoint beschreven. Chat Completions

Keep-alive-regels

Zolang het model bezig is en nog niets te sturen heeft, bevat de stream commentaarregels. Ze bevatten geen data. Een SSE-parser slaat ze zelf over; code die ruwe regels leest, moet elke regel overslaan die met een dubbele punt begint.

Regel Verstuurd door Wanneer
: ping Shannon 3-familie, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 Elke 15 seconden.
: keepalive Gehoste open-weight modellen Elke 15 seconden terwijl het model bezig is.
: thinking shannon-2-lite, shannon-2-pro Terwijl het model redeneert.

Fouten binnen een stream

Zodra een stream is begonnen, is zijn status 200, dus een fout komt binnen in de stream: een chunk met een error-lid op de plek waar choices zou staan.

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]

Na de foutchunk volgen nog steeds de laatste chunk, met finish_reason en usage, en data: [DONE]. Controleer elke chunk op error. Zonder die controle lijkt een antwoord dat halverwege is mislukt compleet.

Type Code Melding Wanneer
api_error The model backend failed to answer. Please retry. Het model is mislukt, of heeft niets geschreven. Verstuur de aanvraag opnieuw.
rate_limit_error Shannon routes are temporarily busy. Please retry. Het model is op dit moment druk bezet. Wacht een paar seconden en verstuur de aanvraag opnieuw.
invalid_request_error context_length_exceeded Het gesprek is langer dan het contextvenster van het model. Kort het in voordat je het opnieuw verstuurt. De meldingstekst varieert.

De gehoste open-weight modellen sturen het error-lid alleen met type en message.

De OpenAI Python- en JavaScript-SDK's werpen een foutchunk als API-fout op terwijl je itereert, dus zet de lus in je gebruikelijke foutafhandeling.

Statuscodes, de foutbody en wat je opnieuw kunt proberen hebben een eigen pagina. Foutafhandeling

De verbinding sluiten

Het sluiten van de verbinding stopt de aflevering, niet de aanvraag. Het model schrijft het antwoord tot het einde en de aanvraag wordt in rekening gebracht alsof je alles had gelezen. Wil je minder betalen, vraag dan om minder: stel een lagere max_tokens in bij een model dat die toepast.

Een stream kan ook onderweg naar jou voortijdig eindigen, bijvoorbeeld bij een netwerkonderbreking of terwijl een server wordt bijgewerkt. Hij stopt dan zonder de laatste chunk en zonder data: [DONE]. Behandel dat antwoord als onvolledig en verstuur de aanvraag opnieuw.

Een stream lezen zonder SDK

Een correcte lezer doet vijf dingen: hij stuurt fouten die vóór de stream binnenkomen naar zijn foutafhandeling, splitst de body in regels, slaat commentaarregels over, stopt bij data: [DONE] en controleert elke chunk op 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))

Het Python-voorbeeld gebruikt het pakket requests. Het JavaScript-voorbeeld draait op Node.js 18 of nieuwer.

Streams van de andere formaten

De endpoints Responses en Messages streamen ook. Hun frames bevatten een event:-regel met de naam van het event en een data:-regel met de bijbehorende JSON. Elke pagina somt haar events in volgorde op.

In de stream Chat CompletionsResponsesMessages
Frames data:-regelsevent:- en data:-regelsevent:- en data:-regels
Redenering komt binnen in delta.reasoning_contentresponse.reasoning_summary_text.deltathinking_delta
Antwoordtekst komt binnen in delta.contentresponse.output_text.deltatext_delta
Gebruik komt binnen in De laatste chunkresponse.completedmessage_delta
De stream eindigt met De laatste chunk, daarna data: [DONE]response.completedmessage_stop