Sari la conținut
Streaming

Streaming

Setează stream la true și răspunsul sosește ca server-sent events pe măsură ce îl scrie modelul: mai întâi raționamentul, apoi răspunsul, apoi un ultim chunk cu motivul de finalizare și utilizarea. Folosește-l ori de câte ori un om așteaptă textul și pentru răspunsuri lungi.

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

SDK-urile OpenAI îți dau chunk-urile unul câte unul. Citește fiecare chunk după ce conține: o bucată de raționament, o bucată de răspuns, un apel de instrument sau sfârșitul răspunsului.

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

Cum este încadrat un stream

  • Răspunsul are statusul 200 și header-ele content-type: text/event-stream și cache-control: no-cache.
  • Fiecare eveniment este o linie care începe cu data: și este urmată de o linie goală. Textul de după data: este un chunk JSON. Nu există linii event: sau id:.
  • O linie care începe cu două puncte, precum : ping, este un comentariu care ține conexiunea deschisă. Sari-o.
  • Stream-ul se termină cu data: [DONE]. Un stream care se oprește fără el este incomplet.

Chunk-urile în ordine

Fiecare chunk este un obiect 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
    }
  ]
}

Toate chunk-urile unui stream poartă același id, created și model. choices conține întotdeauna o singură intrare. delta conține ce este nou, iar finish_reason este null până la ultimul chunk.

Cheie în delta Tip Descriere Trimis de
role string Întotdeauna assistant. Sosește o singură dată, la începutul stream-ului. Modele open-weight găzduite, shannon-2-lite, shannon-2-pro
reasoning_content string O bucată din raționamentul modelului. Unește bucățile în ordine. Modele open-weight găzduite, familia Shannon 3, shannon-1.6-pro, shannon-coder-1
content string O bucată din răspuns. Unește bucățile în ordine. Toate modelele
tool_calls array Un apel de instrument complet. Vezi Delta-urile apelurilor de instrumente. Toate modelele

Mai întâi vine raționamentul, apoi răspunsul, apoi eventualele apeluri de instrumente. Citește un chunk după cheile din delta, nu după locul lui în stream: un delta poate conține role împreună cu prima bucată de text, iar ultimul este gol.

Un stream întreg așa cum circulă. Pentru a ține liniile scurte, id, object, created și model sunt omise din fiecare chunk aici.

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]

Delta-uri de raționament

Un model care raționează își trimite raționamentul în delta.reasoning_content înaintea răspunsului. Ține cele două texte separate: afișează raționamentul ca parte separată, pliabilă, sau omite-l.

Modelele open-weight găzduite nu trimit raționament când reasoning_effort este off și nici când cererea are un response_format.

shannon-2-lite și shannon-2-pro raționează și ele înainte de a răspunde. Pe acest endpoint trimit linii de comentariu : thinking cât timp raționează, apoi răspunsul.

reasoning_content nu se află în modelele tipizate ale SDK-urilor OpenAI. În Python citește-l cu getattr(delta, "reasoning_content", None); în JavaScript, delta.reasoning_content funcționează așa cum este.

Delta-uri ale apelurilor de instrumente

Un apel de instrument sosește întreg: un chunk per apel, al cărui delta conține apelul cu șirul arguments complet. index numerotează apelurile răspunsului de la 0. Codul care unește arguments bucată cu bucată funcționează neschimbat, pentru că unește o singură bucată.

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

După apeluri, ultimul chunk are finish_reason tool_calls.

Când cererea are tools, shannon-1.6-lite, shannon-1.6-pro și shannon-coder-1 trimit textul răspunsului ca un singur delta, la finalul răspunsului.

Ultimul chunk

Ultimul chunk dinaintea lui data: [DONE] are un delta gol, finish_reason și usage al cererii, toate în același chunk. Fiecare stream îl are; nu ai nevoie de 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
  }
}

La modelele open-weight găzduite, usage are și prompt_tokens_details.cached_tokens și completion_tokens_details.reasoning_tokens.

După o cerere cu web_search: true a cărei căutare a găsit ceva, ultimul chunk poartă și sources, iar un chunk dinaintea lui poartă marcajele de citare ca delta.annotations. Căutare web integrată

finish_reason Descriere Trimis de
stop Modelul și-a terminat răspunsul sau a apărut un șir stop. Toate modelele
tool_calls Răspunsul conține unul sau mai multe apeluri de instrumente. Toate modelele
length Răspunsul a fost tăiat la limita de ieșire. Familia Shannon 3, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1

Câmpurile lui usage sunt descrise la endpoint. Chat Completions

Linii keep-alive

Cât timp modelul lucrează și nu are încă nimic de trimis, stream-ul conține linii de comentariu. Ele nu conțin date. Un parser SSE le sare singur; codul care citește linii brute trebuie să sară fiecare linie care începe cu două puncte.

Linie Trimis de Când
: ping Familia Shannon 3, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 La fiecare 15 secunde.
: keepalive Modele open-weight găzduite La fiecare 15 secunde cât timp modelul lucrează.
: thinking shannon-2-lite, shannon-2-pro Cât timp modelul raționează.

Erori în interiorul unui stream

După ce un stream a început, statusul său este 200, deci un eșec sosește în interiorul stream-ului: un chunk cu un membru error în locul lui 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]

După chunk-ul de eroare urmează totuși ultimul chunk, cu finish_reason și usage, și data: [DONE]. Verifică în fiecare chunk existența lui error. Fără această verificare, un răspuns care a eșuat la jumătate pare complet.

Tip Cod Mesaj Când
api_error The model backend failed to answer. Please retry. Modelul a eșuat sau nu a scris nimic. Trimite cererea din nou.
rate_limit_error Shannon routes are temporarily busy. Please retry. Modelul este ocupat în acest moment. Așteaptă câteva secunde și trimite cererea din nou.
invalid_request_error context_length_exceeded Conversația este mai lungă decât fereastra de context a modelului. Scurtează-o înainte să o trimiți din nou. Textul mesajului variază.

Modelele open-weight găzduite trimit membrul error doar cu type și message.

SDK-urile OpenAI pentru Python și JavaScript ridică un chunk de eroare ca eroare API în timp ce iterezi, deci învelește bucla în gestionarea ta obișnuită a erorilor.

Codurile de status, corpul erorii și ce trebuie reîncercat au pagina lor. Gestionare erori

Închiderea conexiunii

Închiderea conexiunii oprește livrarea, nu cererea. Modelul scrie răspunsul până la capăt, iar cererea este taxată ca și cum l-ai fi citit integral. Ca să plătești mai puțin, cere mai puțin: setează un max_tokens mai mic la un model care îl aplică.

Un stream se poate termina devreme și pe drumul către tine, de exemplu la o întrerupere de rețea sau în timp ce un server este actualizat. Atunci se oprește fără ultimul chunk și fără data: [DONE]. Tratează acel răspuns ca incomplet și trimite cererea din nou.

Citirea unui stream fără SDK

Un cititor corect face cinci lucruri: trimite erorile care sosesc înaintea stream-ului către gestionarea sa de erori, împarte corpul în linii, sare liniile de comentariu, se oprește la data: [DONE] și verifică în fiecare chunk existența lui 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))

Exemplul Python folosește pachetul requests. Exemplul JavaScript rulează pe Node.js 18 sau mai nou.

Stream-urile celorlalte formate

Endpoint-urile Responses și Messages fac și ele streaming. Frame-urile lor conțin o linie event: cu numele evenimentului și o linie data: cu JSON-ul lui. Fiecare pagină își listează evenimentele în ordine.

În stream Chat CompletionsResponsesMessages
Frame-uri Linii data:Linii event: și data:Linii event: și data:
Raționamentul sosește în delta.reasoning_contentresponse.reasoning_summary_text.deltathinking_delta
Textul răspunsului sosește în delta.contentresponse.output_text.deltatext_delta
Utilizarea sosește în Ultimul chunkresponse.completedmessage_delta
Stream-ul se termină cu Ultimul chunk, apoi data: [DONE]response.completedmessage_stop