Hoppa till innehållet
Streaming

Streaming

Sätt stream till true så kommer svaret som server-sent events medan modellen skriver det: först resonemanget, sedan svaret, sedan en sista chunk med avslutsorsaken och usage. Använd det alltid när en person väntar på texten, och för långa svar.

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

OpenAI-SDK:erna ger dig chunkarna en och en. Läs varje chunk efter vad den bär: en bit resonemang, en bit av svaret, ett verktygsanrop eller svarets slut.

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

Hur en ström är inramad

  • Svaret har status 200 och headrarna content-type: text/event-stream och cache-control: no-cache.
  • Varje händelse är en rad som börjar med data: och följs av en tom rad. Texten efter data: är en JSON-chunk. Det finns inga event:- eller id:-rader.
  • En rad som börjar med kolon, som : ping, är en kommentar som håller anslutningen öppen. Hoppa över den.
  • Strömmen slutar med data: [DONE]. En ström som stannar utan den är ofullständig.

Chunkar i ordning

Varje chunk är ett chat.completion.chunk-objekt:

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

Alla chunkar i en ström bär samma id, created och model. choices innehåller alltid en post. Dess delta innehåller det som är nytt, och finish_reason är null till den sista chunken.

Nyckel i delta Typ Beskrivning Skickas av
role string Alltid assistant. Den kommer en gång, i början av strömmen. Hostade open-weight-modeller, shannon-2-lite, shannon-2-pro
reasoning_content string En bit av modellens resonemang. Foga ihop bitarna i ordning. Hostade open-weight-modeller, Shannon 3-familjen, shannon-1.6-pro, shannon-coder-1
content string En bit av svaret. Foga ihop bitarna i ordning. Alla modeller
tool_calls array Ett komplett verktygsanrop. Se Delta för verktygsanrop. Alla modeller

Resonemanget kommer först, sedan svaret, sedan eventuella verktygsanrop. Läs en chunk efter nycklarna i dess delta, inte efter dess plats i strömmen: ett delta kan innehålla role tillsammans med den första textbiten, och det sista är tomt.

En hel ström så som den färdas. För att hålla raderna korta är id, object, created och model utelämnade i varje chunk här.

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]

Resonemangsdeltan

En modell som resonerar skickar sitt resonemang i delta.reasoning_content före svaret. Håll de två texterna isär: visa resonemanget som en separat, hopfällbar del, eller utelämna det.

De hostade open-weight-modellerna skickar inget resonemang när reasoning_effort är off, och inte heller när begäran har ett response_format.

shannon-2-lite och shannon-2-pro resonerar också innan de svarar. På den här endpointen skickar de kommentarsraderna : thinking medan de gör det, och sedan svaret.

reasoning_content finns inte i OpenAI-SDK:ernas typade modeller. I Python läser du det med getattr(delta, "reasoning_content", None); i JavaScript fungerar delta.reasoning_content som det är.

Delta för verktygsanrop

Ett verktygsanrop kommer helt: en chunk per anrop, vars delta innehåller anropet med hela arguments-strängen. index numrerar svarets anrop från 0. Kod som fogar ihop arguments bit för bit fungerar oförändrad, eftersom den fogar ihop en enda bit.

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

Efter anropen har den sista chunken finish_reason tool_calls.

När begäran har tools skickar shannon-1.6-lite, shannon-1.6-pro och shannon-coder-1 svarstexten som ett enda delta i slutet av svaret.

Den sista chunken

Den sista chunken före data: [DONE] har ett tomt delta, finish_reason och begärans usage, allt i samma chunk. Varje ström har den; du behöver inte stream_options.

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

På de hostade open-weight-modellerna har usage också prompt_tokens_details.cached_tokens och completion_tokens_details.reasoning_tokens.

Efter en begäran med web_search: true vars sökning fann något bär den sista chunken också sources, och en chunk före den bär citeringsmarkörerna som delta.annotations. Inbyggd webbsökning

finish_reason Beskrivning Skickas av
stop Modellen avslutade sitt svar, eller en stop-sträng dök upp. Alla modeller
tool_calls Svaret innehåller ett eller flera verktygsanrop. Alla modeller
length Svaret klipptes vid outputgränsen. Shannon 3-familjen, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1

Fälten i usage beskrivs tillsammans med endpointen. Chat Completions

Keep-alive-rader

Medan modellen arbetar och ännu inte har något att skicka bär strömmen kommentarsrader. De innehåller inga data. En SSE-parser hoppar över dem själv; kod som läser råa rader måste hoppa över varje rad som börjar med kolon.

Rad Skickas av När
: ping Shannon 3-familjen, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 Var 15:e sekund.
: keepalive Hostade open-weight-modeller Var 15:e sekund medan modellen arbetar.
: thinking shannon-2-lite, shannon-2-pro Medan modellen resonerar.

Fel inne i en ström

När en ström har startat är dess status 200, så ett fel kommer inne i strömmen: en chunk med en error-medlem där choices annars skulle stå.

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]

Efter felchunken följer ändå den sista chunken, med finish_reason och usage, och data: [DONE]. Kontrollera varje chunk efter error. Utan den kontrollen ser ett svar som misslyckades halvvägs komplett ut.

Typ Kod Meddelande När
api_error The model backend failed to answer. Please retry. Modellen misslyckades, eller skrev ingenting. Skicka begäran igen.
rate_limit_error Shannon routes are temporarily busy. Please retry. Modellen är upptagen just nu. Vänta några sekunder och skicka begäran igen.
invalid_request_error context_length_exceeded Konversationen är längre än modellens kontextfönster. Förkorta den innan du skickar den igen. Meddelandetexten varierar.

De hostade open-weight-modellerna skickar error-medlemmen med endast type och message.

OpenAI:s Python- och JavaScript-SDK:er kastar en felchunk som ett API-fel medan du itererar, så linda in loopen i din vanliga felhantering.

Statuskoder, felbodyn och vad som kan försökas igen har en egen sida. Felhantering

Att stänga anslutningen

Att stänga anslutningen stoppar leveransen, inte begäran. Modellen skriver svaret till slut och begäran debiteras som om du hade läst allt. För att betala för mindre, be om mindre: ange ett lägre max_tokens på en modell som tillämpar det.

En ström kan också ta slut i förtid på väg till dig, till exempel vid ett nätverksavbrott eller medan en server uppdateras. Den stannar då utan den sista chunken och utan data: [DONE]. Behandla det svaret som ofullständigt och skicka begäran igen.

Läsa en ström utan SDK

En korrekt läsare gör fem saker: den skickar fel som kommer före strömmen till sin felhantering, delar upp bodyn i rader, hoppar över kommentarsrader, stannar vid data: [DONE] och kontrollerar varje chunk efter 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-exemplet använder paketet requests. JavaScript-exemplet körs på Node.js 18 eller senare.

Strömmar i de andra formaten

Endpointsen Responses och Messages streamar också. Deras frames bär en event:-rad med händelsens namn och en data:-rad med dess JSON. Varje sida listar sina händelser i ordning.

I strömmen Chat CompletionsResponsesMessages
Frames data:-raderevent:- och data:-raderevent:- och data:-rader
Resonemanget kommer i delta.reasoning_contentresponse.reasoning_summary_text.deltathinking_delta
Svarstexten kommer i delta.contentresponse.output_text.deltatext_delta
Usage kommer i Den sista chunkenresponse.completedmessage_delta
Strömmen slutar med Den sista chunken, sedan data: [DONE]response.completedmessage_stop