Přeskočit na obsah
Streamování

Streamování

Nastavte stream na true a odpověď přichází jako server-sent events, zatímco ji model píše: nejdřív uvažování, pak odpověď, nakonec poslední chunk s důvodem ukončení a využitím. Používejte to, kdykoli na text čeká člověk, a u dlouhých odpovědí.

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

SDK OpenAI vám dávají chunky jeden po druhém. Každý chunk čtěte podle toho, co nese: kousek uvažování, kousek odpovědi, volání nástroje, nebo konec odpovědi.

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

Jak je stream rámován

  • Odpověď má status 200 a hlavičky content-type: text/event-stream a cache-control: no-cache.
  • Každá událost je jeden řádek, který začíná data: a následuje prázdný řádek. Text za data: je jeden chunk JSON. Žádné řádky event: ani id: nejsou.
  • Řádek, který začíná dvojtečkou, například : ping, je komentář, který udržuje spojení otevřené. Přeskočte jej.
  • Stream končí data: [DONE]. Stream, který se zastaví bez něj, je neúplný.

Chunky v pořadí

Každý chunk je objekt 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
    }
  ]
}

Všechny chunky jednoho streamu nesou stejné id, created a model. choices vždy obsahuje jednu položku. Její delta obsahuje, co je nové, a finish_reason je null až do posledního chunku.

Klíč v delta Typ Popis Odesílá
role string Vždy assistant. Přichází jednou, na začátku streamu. Hostované modely s otevřenými váhami, shannon-2-lite, shannon-2-pro
reasoning_content string Kousek uvažování modelu. Kousky spojte v pořadí. Hostované modely s otevřenými váhami, rodina Shannon 3, shannon-1.6-pro, shannon-coder-1
content string Kousek odpovědi. Kousky spojte v pořadí. Všechny modely
tool_calls array Jedno kompletní volání nástroje. Viz Delty volání nástrojů. Všechny modely

Nejdřív přichází uvažování, pak odpověď, pak případná volání nástrojů. Chunk čtěte podle klíčů v jeho delta, ne podle jeho místa ve streamu: delta může obsahovat role spolu s prvním kouskem textu a poslední je prázdná.

Celý stream tak, jak putuje. Aby řádky zůstaly krátké, jsou zde z každého chunku vynechány id, object, created a model.

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]

Delty uvažování

Model, který uvažuje, posílá své uvažování v delta.reasoning_content před odpovědí. Oba texty držte odděleně: uvažování zobrazte jako samostatnou, sbalitelnou část, nebo je vynechte.

Hostované modely s otevřenými váhami neposílají žádné uvažování, když je reasoning_effort off, ani když požadavek obsahuje response_format.

shannon-2-lite a shannon-2-pro také uvažují, než odpoví. Na tomto endpointu při tom posílají komentářové řádky : thinking a potom odpověď.

reasoning_content není v typovaných modelech SDK OpenAI. V Pythonu jej čtěte přes getattr(delta, "reasoning_content", None); v JavaScriptu delta.reasoning_content funguje tak, jak je.

Delty volání nástrojů

Volání nástroje přichází celé: jeden chunk na volání, jehož delta obsahuje volání s kompletním řetězcem arguments. index čísluje volání odpovědi od 0. Kód, který arguments spojuje po kouscích, funguje beze změny, protože spojuje jediný kousek.

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

Po voláních má poslední chunk finish_reason tool_calls.

Když požadavek má tools, posílají shannon-1.6-lite, shannon-1.6-pro a shannon-coder-1 text odpovědi jako jednu deltu na konci odpovědi.

Poslední chunk

Poslední chunk před data: [DONE] má prázdnou delta, finish_reason a usage požadavku, vše ve stejném chunku. Má jej každý stream; stream_options nepotřebujete.

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

U hostovaných modelů s otevřenými váhami má usage také prompt_tokens_details.cached_tokens a completion_tokens_details.reasoning_tokens.

Po požadavku s web_search: true, jehož vyhledávání něco našlo, nese poslední chunk také sources a jeden chunk před ním nese značky citací jako delta.annotations. Vestavěné webové vyhledávání

finish_reason Popis Odesílá
stop Model dokončil odpověď, nebo se objevil řetězec stop. Všechny modely
tool_calls Odpověď obsahuje jedno nebo více volání nástrojů. Všechny modely
length Odpověď byla ukončena na limitu výstupu. Rodina Shannon 3, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1

Pole usage jsou popsána u endpointu. Chat Completions

Keep-alive řádky

Zatímco model pracuje a zatím nemá co poslat, stream nese komentářové řádky. Neobsahují žádná data. Parser SSE je přeskakuje sám; kód, který čte surové řádky, musí přeskočit každý řádek začínající dvojtečkou.

Řádek Odesílá Kdy
: ping Rodina Shannon 3, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 Každých 15 sekund.
: keepalive Hostované modely s otevřenými váhami Každých 15 sekund, dokud model pracuje.
: thinking shannon-2-lite, shannon-2-pro Zatímco model uvažuje.

Chyby uvnitř streamu

Jakmile stream začne, jeho status je 200, takže selhání přichází uvnitř streamu: chunk s členem error tam, kde by byly 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]

Po chunku s chybou ještě následuje poslední chunk s finish_reason a usage a data: [DONE]. U každého chunku kontrolujte error. Bez této kontroly vypadá odpověď, která selhala v půlce, jako kompletní.

Typ Kód Zpráva Kdy
api_error The model backend failed to answer. Please retry. Model selhal, nebo nic nenapsal. Pošlete požadavek znovu.
rate_limit_error Shannon routes are temporarily busy. Please retry. Model je v tuto chvíli zaneprázdněn. Počkejte několik sekund a pošlete požadavek znovu.
invalid_request_error context_length_exceeded Konverzace je delší než kontextové okno modelu. Zkraťte ji, než ji pošlete znovu. Text zprávy se liší.

Hostované modely s otevřenými váhami posílají člena error jen s type a message.

SDK OpenAI pro Python a JavaScript vyvolají chunk s chybou jako chybu API při iteraci, proto smyčku obalte svým obvyklým zpracováním chyb.

Stavové kódy, tělo chyby a co opakovat mají vlastní stránku. Zpracování chyb

Zavření spojení

Zavření spojení zastaví doručování, ne požadavek. Model dopíše odpověď do konce a požadavek se účtuje, jako byste ji přečetli celou. Chcete-li platit méně, žádejte méně: nastavte nižší max_tokens u modelu, který jej uplatňuje.

Stream může také skončit předčasně na cestě k vám, například při přerušení sítě nebo při aktualizaci serveru. Pak se zastaví bez posledního chunku a bez data: [DONE]. Takovou odpověď považujte za neúplnou a pošlete požadavek znovu.

Čtení streamu bez SDK

Správný čtenář dělá pět věcí: chyby, které přijdou před streamem, předá svému zpracování chyb, rozdělí tělo na řádky, přeskočí komentářové řádky, zastaví se na data: [DONE] a u každého chunku kontroluje 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))

Ukázka v Pythonu používá balíček requests. Ukázka v JavaScriptu běží na Node.js 18 nebo novějším.

Streamy v jiných formátech

Endpointy Responses a Messages také streamují. Jejich rámce nesou řádek event: s názvem události a řádek data: s jejím JSON. Každá stránka uvádí své události v pořadí.

Ve streamu Chat CompletionsResponsesMessages
Rámce Řádky data:Řádky event: a data:Řádky event: a data:
Uvažování přichází v delta.reasoning_contentresponse.reasoning_summary_text.deltathinking_delta
Text odpovědi přichází v delta.contentresponse.output_text.deltatext_delta
Využití (usage) přichází v Poslední chunkresponse.completedmessage_delta
Stream končí Poslední chunk, potom data: [DONE]response.completedmessage_stop