Gå til indhold
Streaming

Streaming

Sæt stream til true, og svaret ankommer som server-sent events, mens modellen skriver det: først ræsonnement, derefter svaret, til sidst et chunk med afslutningsårsagen og usage. Brug det, når et menneske venter på teksten, og til lange svar.

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

OpenAI-SDK'erne giver dig chunks et ad gangen. Læs hvert chunk efter det, det indeholder: et stykke ræsonnement, et stykke af svaret, et værktøjskald eller svarets slutning.

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

Hvordan en stream er opbygget

  • Svaret har status 200 og headerne content-type: text/event-stream og cache-control: no-cache.
  • Hver hændelse er én linje, der starter med data: og efterfølges af en tom linje. Teksten efter data: er ét JSON-chunk. Der er ingen event:- eller id:-linjer.
  • En linje, der starter med et kolon, f.eks. : ping, er en kommentar, der holder forbindelsen åben. Spring den over.
  • Streamen slutter med data: [DONE]. En stream, der stopper uden den, er ufuldstændig.

Chunks i rækkefølge

Hvert chunk er et 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
    }
  ]
}

Alle chunks i én stream har samme id, created og model. choices indeholder altid én post. Dens delta indeholder det nye, og finish_reason er null indtil det sidste chunk.

Nøgle i delta Type Beskrivelse Sendt af
role string Altid assistant. Den ankommer én gang, i starten af streamen. Hostede open-weight-modeller, shannon-2-lite, shannon-2-pro
reasoning_content string Et stykke af modellens ræsonnement. Sæt stykkerne sammen i rækkefølge. Hostede open-weight-modeller, Shannon 3-familien, shannon-1.6-pro, shannon-coder-1
content string Et stykke af svaret. Sæt stykkerne sammen i rækkefølge. Alle modeller
tool_calls array Ét komplet værktøjskald. Se Deltaer for værktøjskald. Alle modeller

Ræsonnement kommer først, derefter svaret, derefter eventuelle værktøjskald. Læs et chunk efter nøglerne i dets delta, ikke efter dets plads i streamen: en delta kan indeholde role sammen med det første stykke tekst, og den sidste er tom.

En hel stream, som den rejser. For at holde linjerne korte er id, object, created og model udeladt i hvert chunk her.

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]

Ræsonnement-deltaer

En model, der ræsonnerer, sender sit ræsonnement i delta.reasoning_content før svaret. Hold de to tekster adskilt: vis ræsonnementet som en separat, sammenfoldelig del, eller udelad det.

De hostede open-weight-modeller sender intet ræsonnement, når reasoning_effort er off, og heller ikke når anmodningen har et response_format.

shannon-2-lite og shannon-2-pro ræsonnerer også, før de svarer. På dette endpoint sender de kommentarlinjer : thinking, mens de gør det, og derefter svaret.

reasoning_content findes ikke i OpenAI-SDK'ernes typede modeller. I Python læses det med getattr(delta, "reasoning_content", None); i JavaScript virker delta.reasoning_content, som det er.

Deltaer for værktøjskald

Et værktøjskald ankommer helt: ét chunk per kald, hvis delta indeholder kaldet med den komplette arguments-streng. index nummererer svarets kald fra 0. Kode, der samler arguments stykke for stykke, virker uændret, fordi den samler ét stykke.

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

Efter kaldene har det sidste chunk finish_reason tool_calls.

Når anmodningen har tools, sender shannon-1.6-lite, shannon-1.6-pro og shannon-coder-1 svarteksten som én delta i slutningen af svaret.

Det sidste chunk

Det sidste chunk før data: [DONE] har en tom delta, finish_reason og anmodningens usage, alt sammen i samme chunk. Hver stream har det; du behøver ikke 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 hostede open-weight-modeller har usage også prompt_tokens_details.cached_tokens og completion_tokens_details.reasoning_tokens.

Efter en anmodning med web_search: true, hvis søgning fandt noget, bærer det sidste chunk også sources, og ét chunk før det bærer citationsmarkørerne som delta.annotations. Indbygget websøgning

finish_reason Beskrivelse Sendt af
stop Modellen afsluttede sit svar, eller en stop-streng optrådte. Alle modeller
tool_calls Svaret indeholder ét eller flere værktøjskald. Alle modeller
length Svaret blev afskåret ved outputgrænsen. Shannon 3-familien, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1

Felterne i usage er beskrevet sammen med endpointet. Chat Completions

Keep-alive-linjer

Mens modellen arbejder og endnu intet har at sende, har streamen kommentarlinjer. De indeholder ingen data. En SSE-parser springer dem selv over; kode, der læser rå linjer, skal springe hver linje over, der starter med et kolon.

Linje Sendt af Hvornår
: ping Shannon 3-familien, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 Hvert 15. sekund.
: keepalive Hostede open-weight-modeller Hvert 15. sekund, mens modellen arbejder.
: thinking shannon-2-lite, shannon-2-pro Mens modellen ræsonnerer.

Fejl inde i en stream

Når en stream er startet, er dens status 200, så en fejl ankommer inde i streamen: et chunk med et error-medlem, hvor choices ellers ville 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 fejl-chunket følger stadig det sidste chunk, med finish_reason og usage, og data: [DONE]. Kontrollér hvert chunk for error. Uden den kontrol ser et svar, der fejlede halvvejs, komplet ud.

Type Kode Besked Hvornår
api_error The model backend failed to answer. Please retry. Modellen fejlede eller skrev intet. Send anmodningen igen.
rate_limit_error Shannon routes are temporarily busy. Please retry. Modellen er optaget i øjeblikket. Vent nogle sekunder, og send anmodningen igen.
invalid_request_error context_length_exceeded Samtalen er længere end modellens kontekstvindue. Forkort den, før du sender igen. Beskedens tekst varierer.

De hostede open-weight-modeller sender error-medlemmet kun med type og message.

OpenAI's Python- og JavaScript-SDK'er rejser et fejl-chunk som en API-fejl, mens du itererer, så pak løkken ind i din sædvanlige fejlhåndtering.

Statuskoder, fejlens body og hvad der kan prøves igen, har deres egen side. Fejlhåndtering

Lukning af forbindelsen

At lukke forbindelsen stopper leveringen, ikke anmodningen. Modellen skriver svaret til ende, og anmodningen afregnes, som om du havde læst det hele. For at betale mindre skal du bede om mindre: sæt en lavere max_tokens på en model, der anvender den.

En stream kan også slutte for tidligt på vej til dig, f.eks. ved et netværksbrud eller mens en server opdateres. Den stopper så uden det sidste chunk og uden data: [DONE]. Behandl det svar som ufuldstændigt, og send anmodningen igen.

Læsning af en stream uden et SDK

En korrekt læser gør fem ting: den sender fejl, der ankommer før streamen, til sin fejlhåndtering, deler body'en i linjer, springer kommentarlinjer over, stopper ved data: [DONE] og kontrollerer hvert chunk for 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-eksemplet bruger pakken requests. JavaScript-eksemplet kører på Node.js 18 eller nyere.

Streams i de andre formater

Endpointsene Responses og Messages streamer også. Deres rammer har en event:-linje med hændelsens navn og en data:-linje med dens JSON. Hver side opregner sine hændelser i rækkefølge.

I streamen Chat CompletionsResponsesMessages
Rammer data:-linjerevent:- og data:-linjerevent:- og data:-linjer
Ræsonnement ankommer i delta.reasoning_contentresponse.reasoning_summary_text.deltathinking_delta
Svarteksten ankommer i delta.contentresponse.output_text.deltatext_delta
Usage ankommer i Det sidste chunkresponse.completedmessage_delta
Streamen slutter med Det sidste chunk, derefter data: [DONE]response.completedmessage_stop