Siirry sisältöön
Suoratoisto

Suoratoisto

Aseta stream arvoon true, niin vastaus saapuu server-sent events -tapahtumina sitä mukaa kuin malli kirjoittaa sitä: ensin päättely, sitten vastaus ja lopuksi viimeinen chunk, jossa on lopetussyy ja käyttötiedot (usage). Käytä sitä aina, kun ihminen odottaa tekstiä, ja pitkissä vastauksissa.

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

OpenAI-SDK:t antavat chunkit yksi kerrallaan. Lue jokainen chunk sen mukaan, mitä se sisältää: pala päättelyä, pala vastausta, työkalukutsu tai vastauksen loppu.

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

Miten striimi kehystetään

  • Vastauksen tila on 200 ja otsakkeet ovat content-type: text/event-stream ja cache-control: no-cache.
  • Jokainen tapahtuma on yksi rivi, joka alkaa merkinnällä data: ja jonka perässä on tyhjä rivi. Teksti merkinnän data: jälkeen on yksi JSON-chunk. event:- tai id:-rivejä ei ole.
  • Rivi, joka alkaa kaksoispisteellä, kuten : ping, on kommentti, joka pitää yhteyden auki. Ohita se.
  • Striimi päättyy riviin data: [DONE]. Striimi, joka pysähtyy ilman sitä, on keskeneräinen.

Chunkit järjestyksessä

Jokainen chunk on chat.completion.chunk-objekti:

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

Saman striimin kaikilla chunkeilla on sama id, created ja model. choices sisältää aina yhden alkion. Sen delta sisältää sen, mikä on uutta, ja finish_reason on null viimeiseen chunkiin asti.

Avain deltassa Tyyppi Kuvaus Lähettäjä
role string Aina assistant. Se saapuu kerran, striimin alussa. Hostatut avoimen painon mallit, shannon-2-lite, shannon-2-pro
reasoning_content string Pala mallin päättelyä. Yhdistä palat järjestyksessä. Hostatut avoimen painon mallit, Shannon 3 -perhe, shannon-1.6-pro, shannon-coder-1
content string Pala vastausta. Yhdistä palat järjestyksessä. Kaikki mallit
tool_calls array Yksi täydellinen työkalukutsu. Katso Työkalukutsun deltat. Kaikki mallit

Päättely tulee ensin, sitten vastaus ja lopuksi mahdolliset työkalukutsut. Lue chunk sen delta-kentän avainten mukaan, ei sen paikan mukaan striimissä: delta voi sisältää role-kentän yhdessä ensimmäisen tekstipalan kanssa, ja viimeinen on tyhjä.

Kokonainen striimi sellaisena kuin se kulkee. Rivien pitämiseksi lyhyinä id, object, created ja model on jätetty tässä pois jokaisesta chunkista.

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]

Päättelydeltat

Päättelevä malli lähettää päättelynsä kentässä delta.reasoning_content ennen vastausta. Pidä nämä kaksi tekstiä erillään: näytä päättely erillisenä, kutistettavana osana tai jätä se pois.

Hostatut avoimen painon mallit eivät lähetä päättelyä, kun reasoning_effort on off, eivätkä silloin, kun pyynnössä on response_format.

Myös shannon-2-lite ja shannon-2-pro päättelevät ennen vastaamista. Tässä päätepisteessä ne lähettävät : thinking-kommenttirivejä päätellessään ja sitten vastauksen.

reasoning_content ei ole OpenAI-SDK:iden tyypitetyissä malleissa. Pythonissa lue se komennolla getattr(delta, "reasoning_content", None); JavaScriptissä delta.reasoning_content toimii sellaisenaan.

Työkalukutsun deltat

Työkalukutsu saapuu kokonaisena: yksi chunk kutsua kohti, jonka delta sisältää kutsun koko arguments-merkkijonon. index numeroi vastauksen kutsut luvusta 0 alkaen. Koodi, joka yhdistää arguments-osia pala palalta, toimii muuttumattomana, koska se yhdistää yhden palan.

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

Kutsujen jälkeen viimeisessä chunkissa finish_reason on tool_calls.

Kun pyynnössä on tools, shannon-1.6-lite, shannon-1.6-pro ja shannon-coder-1 lähettävät vastaustekstin yhtenä deltana vastauksen lopussa.

Viimeinen chunk

Viimeisessä chunkissa ennen data: [DONE] on tyhjä delta, finish_reason ja pyynnön usage, kaikki samassa chunkissa. Jokaisessa striimissä se on; stream_options ei tarvita.

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

Hostatuilla avoimen painon malleilla usage sisältää myös prompt_tokens_details.cached_tokens ja completion_tokens_details.reasoning_tokens.

Kun pyynnössä on web_search: true ja sen haku löysi jotain, viimeisessä chunkissa on myös sources, ja yksi chunk sitä ennen kantaa viittausmerkinnät muodossa delta.annotations. Sisäänrakennettu verkkohaku

finish_reason Kuvaus Lähettäjä
stop Malli päätti vastauksensa, tai stop-merkkijono ilmestyi. Kaikki mallit
tool_calls Vastauksessa on yksi tai useampi työkalukutsu. Kaikki mallit
length Vastaus katkaistiin tulosteen rajalla. Shannon 3 -perhe, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1

usage-kentän kentät on kuvattu päätepisteen yhteydessä. Chat Completions

Keep-alive-rivit

Kun malli työskentelee eikä sillä ole vielä mitään lähetettävää, striimissä on kommenttirivejä. Niissä ei ole dataa. SSE-jäsennin ohittaa ne itse; koodin, joka lukee raakoja rivejä, on ohitettava jokainen kaksoispisteellä alkava rivi.

Rivi Lähettäjä Milloin
: ping Shannon 3 -perhe, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 15 sekunnin välein.
: keepalive Hostatut avoimen painon mallit 15 sekunnin välein, kun malli työskentelee.
: thinking shannon-2-lite, shannon-2-pro Kun malli päättelee.

Virheet striimin sisällä

Kun striimi on alkanut, sen tila on 200, joten virhe saapuu striimin sisällä: chunk, jossa on error-jäsen siinä, missä choices olisi.

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]

Virhechunkin jälkeen tulevat silti viimeinen chunk, jossa on finish_reason ja usage, sekä data: [DONE]. Tarkista jokaisesta chunkista error. Ilman tätä tarkistusta puolivälissä epäonnistunut vastaus näyttää täydeltä.

Tyyppi Koodi Viesti Milloin
api_error The model backend failed to answer. Please retry. Malli epäonnistui tai ei kirjoittanut mitään. Lähetä pyyntö uudelleen.
rate_limit_error Shannon routes are temporarily busy. Please retry. Malli on juuri nyt kiireinen. Odota muutama sekunti ja lähetä pyyntö uudelleen.
invalid_request_error context_length_exceeded Keskustelu on pidempi kuin mallin kontekstikoko. Lyhennä sitä ennen kuin lähetät sen uudelleen. Viestin teksti vaihtelee.

Hostatut avoimen painon mallit lähettävät error-jäsenen, jossa on vain type ja message.

OpenAI:n Python- ja JavaScript-SDK:t nostavat virhechunkin API-virheenä iteroidessasi, joten kääri silmukka tavalliseen virheenkäsittelyysi.

Tilakoodit, virheen runko ja uudelleen yritettävät asiat ovat omalla sivullaan. Virheiden käsittely

Yhteyden sulkeminen

Yhteyden sulkeminen lopettaa toimituksen, ei pyyntöä. Malli kirjoittaa vastauksen loppuun asti ja pyynnöstä veloitetaan ikään kuin olisit lukenut sen kokonaan. Jos haluat maksaa vähemmän, pyydä vähemmän: aseta pienempi max_tokens mallille, joka ottaa sen huomioon.

Striimi voi myös päättyä ennenaikaisesti matkalla sinulle, esimerkiksi verkkokatkoksen vuoksi tai palvelimen päivityksen aikana. Silloin se pysähtyy ilman viimeistä chunkia ja ilman data: [DONE]. Pidä tätä vastausta keskeneräisenä ja lähetä pyyntö uudelleen.

Striimin lukeminen ilman SDK:ta

Oikein toimiva lukija tekee viisi asiaa: se ohjaa ennen striimiä saapuvat virheet virheenkäsittelyynsä, jakaa rungon riveihin, ohittaa kommenttirivit, pysähtyy riviin data: [DONE] ja tarkistaa jokaisesta chunkista error-kentän.

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-esimerkki käyttää requests-pakettia. JavaScript-esimerkki toimii Node.js 18:lla tai uudemmalla.

Muiden muotojen striimit

Myös Responses- ja Messages-päätepisteet striimaavat. Niiden kehyksissä on event:-rivi tapahtuman nimellä ja data:-rivi sen JSON-sisällöllä. Kukin sivu listaa tapahtumansa järjestyksessä.

Striimissä Chat CompletionsResponsesMessages
Kehykset data:-rivitevent:- ja data:-rivitevent:- ja data:-rivit
Päättely saapuu delta.reasoning_contentresponse.reasoning_summary_text.deltathinking_delta
Vastausteksti saapuu delta.contentresponse.output_text.deltatext_delta
Käyttötiedot saapuvat Viimeinen chunkresponse.completedmessage_delta
Striimi päättyy Viimeinen chunk, sitten data: [DONE]response.completedmessage_stop