Μετάβαση στο περιεχόμενο
Ροή

Ροή

Ορίστε το stream σε true και η απάντηση φτάνει ως server-sent events καθώς τη γράφει το μοντέλο: πρώτα ο συλλογισμός, μετά η απάντηση, και τέλος ένα chunk με τον λόγο ολοκλήρωσης και το usage. Χρησιμοποιήστε το όποτε ένας άνθρωπος περιμένει το κείμενο, και για μεγάλες απαντήσεις.

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

Τα SDK της OpenAI σας δίνουν τα chunks ένα ένα. Διαβάστε κάθε chunk με βάση ό,τι φέρει: ένα κομμάτι συλλογισμού, ένα κομμάτι της απάντησης, μια κλήση εργαλείου ή το τέλος της απάντησης.

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

Πώς πλαισιώνεται ένα stream

  • Η απάντηση έχει κατάσταση 200 και τις κεφαλίδες content-type: text/event-stream και cache-control: no-cache.
  • Κάθε συμβάν είναι μία γραμμή που αρχίζει με data: και ακολουθείται από κενή γραμμή. Το κείμενο μετά το data: είναι ένα chunk JSON. Δεν υπάρχουν γραμμές event: ή id:.
  • Μια γραμμή που αρχίζει με άνω και κάτω τελεία, όπως το : ping, είναι σχόλιο που κρατά τη σύνδεση ανοιχτή. Προσπεράστε την.
  • Το stream τελειώνει με data: [DONE]. Ένα stream που σταματά χωρίς αυτό είναι ατελές.

Chunks με τη σειρά

Κάθε chunk είναι αντικείμενο 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
    }
  ]
}

Όλα τα chunks ενός stream φέρουν τα ίδια id, created και model. Το choices περιέχει πάντα μία εγγραφή. Το delta της περιέχει ό,τι είναι καινούργιο, και το finish_reason είναι null μέχρι το τελευταίο chunk.

Κλειδί στο delta Τύπος Περιγραφή Αποστέλλεται από
role string Πάντα assistant. Φτάνει μία φορά, στην αρχή του stream. Φιλοξενούμενα μοντέλα ανοιχτών βαρών, shannon-2-lite, shannon-2-pro
reasoning_content string Ένα κομμάτι του συλλογισμού του μοντέλου. Ενώστε τα κομμάτια με τη σειρά. Φιλοξενούμενα μοντέλα ανοιχτών βαρών, οικογένεια Shannon 3, shannon-1.6-pro, shannon-coder-1
content string Ένα κομμάτι της απάντησης. Ενώστε τα κομμάτια με τη σειρά. Όλα τα μοντέλα
tool_calls array Μία πλήρης κλήση εργαλείου. Δείτε Deltas κλήσεων εργαλείων. Όλα τα μοντέλα

Πρώτα έρχεται ο συλλογισμός, μετά η απάντηση, μετά τυχόν κλήσεις εργαλείων. Διαβάστε ένα chunk με βάση τα κλειδιά στο delta του, όχι με βάση τη θέση του στο stream: ένα delta μπορεί να περιέχει το role μαζί με το πρώτο κομμάτι κειμένου, και το τελευταίο είναι κενό.

Ένα ολόκληρο stream όπως ταξιδεύει. Για να μένουν οι γραμμές σύντομες, τα id, object, created και model παραλείπονται από κάθε chunk εδώ.

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]

Deltas συλλογισμού

Ένα μοντέλο που συλλογίζεται στέλνει τον συλλογισμό του στο delta.reasoning_content πριν από την απάντηση. Κρατήστε τα δύο κείμενα χωριστά: δείξτε τον συλλογισμό ως ξεχωριστό, αναδιπλούμενο τμήμα, ή παραλείψτε τον.

Τα φιλοξενούμενα μοντέλα ανοιχτών βαρών δεν στέλνουν συλλογισμό όταν το reasoning_effort είναι off, ούτε όταν το αίτημα έχει response_format.

Τα shannon-2-lite και shannon-2-pro συλλογίζονται επίσης πριν απαντήσουν. Σε αυτό το endpoint στέλνουν γραμμές σχολίων : thinking όσο το κάνουν, και μετά την απάντηση.

Το reasoning_content δεν υπάρχει στα τυποποιημένα μοντέλα των SDK της OpenAI. Στην Python διαβάστε το με getattr(delta, "reasoning_content", None)· στη JavaScript το delta.reasoning_content δουλεύει ως έχει.

Deltas κλήσεων εργαλείων

Μια κλήση εργαλείου φτάνει ολόκληρη: ένα chunk ανά κλήση, του οποίου το delta περιέχει την κλήση με ολόκληρο το string arguments. Το index αριθμεί τις κλήσεις της απάντησης από το 0. Ο κώδικας που ενώνει τα arguments κομμάτι κομμάτι δουλεύει αμετάβλητος, επειδή ενώνει ένα κομμάτι.

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

Μετά τις κλήσεις, το τελευταίο chunk έχει finish_reason tool_calls.

Όταν το αίτημα έχει tools, τα shannon-1.6-lite, shannon-1.6-pro και shannon-coder-1 στέλνουν το κείμενο της απάντησης ως ένα delta στο τέλος της απάντησης.

Το τελευταίο chunk

Το τελευταίο chunk πριν από το data: [DONE] έχει κενό delta, το finish_reason και το usage του αιτήματος, όλα στο ίδιο chunk. Κάθε stream το έχει· δεν χρειάζεστε 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
  }
}

Στα φιλοξενούμενα μοντέλα ανοιχτών βαρών το usage έχει επίσης prompt_tokens_details.cached_tokens και completion_tokens_details.reasoning_tokens.

Μετά από ένα αίτημα με web_search: true του οποίου η αναζήτηση βρήκε κάτι, το τελευταίο chunk φέρει επίσης sources και ένα chunk πριν από αυτό φέρει τους δείκτες παραπομπής ως delta.annotations. Ενσωματωμένη αναζήτηση ιστού

finish_reason Περιγραφή Αποστέλλεται από
stop Το μοντέλο ολοκλήρωσε την απάντησή του, ή εμφανίστηκε ένα string stop. Όλα τα μοντέλα
tool_calls Η απάντηση περιέχει μία ή περισσότερες κλήσεις εργαλείων. Όλα τα μοντέλα
length Η απάντηση κόπηκε στο όριο εξόδου. Οικογένεια Shannon 3, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1

Τα πεδία του usage περιγράφονται μαζί με το endpoint. Chat Completions

Γραμμές keep-alive

Όσο το μοντέλο δουλεύει και δεν έχει ακόμη τίποτα να στείλει, το stream φέρει γραμμές σχολίων. Δεν περιέχουν δεδομένα. Ένας parser SSE τις προσπερνά μόνος του· ο κώδικας που διαβάζει ακατέργαστες γραμμές πρέπει να προσπερνά κάθε γραμμή που αρχίζει με άνω και κάτω τελεία.

Γραμμή Αποστέλλεται από Πότε
: ping Οικογένεια Shannon 3, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 Κάθε 15 δευτερόλεπτα.
: keepalive Φιλοξενούμενα μοντέλα ανοιχτών βαρών Κάθε 15 δευτερόλεπτα όσο το μοντέλο δουλεύει.
: thinking shannon-2-lite, shannon-2-pro Όσο το μοντέλο συλλογίζεται.

Σφάλματα μέσα σε stream

Αφού ξεκινήσει ένα stream η κατάστασή του είναι 200, οπότε μια αποτυχία φτάνει μέσα στο stream: ένα chunk με μέλος error εκεί όπου θα ήταν το 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]

Μετά το chunk σφάλματος ακολουθούν ακόμη το τελευταίο chunk, με finish_reason και usage, και το data: [DONE]. Ελέγχετε κάθε chunk για error. Χωρίς αυτόν τον έλεγχο μια απάντηση που απέτυχε στη μέση φαίνεται ολοκληρωμένη.

Τύπος Κωδικός Μήνυμα Πότε
api_error The model backend failed to answer. Please retry. Το μοντέλο απέτυχε ή δεν έγραψε τίποτα. Στείλτε ξανά το αίτημα.
rate_limit_error Shannon routes are temporarily busy. Please retry. Το μοντέλο είναι απασχολημένο αυτή τη στιγμή. Περιμένετε λίγα δευτερόλεπτα και στείλτε ξανά το αίτημα.
invalid_request_error context_length_exceeded Η συνομιλία είναι μεγαλύτερη από το παράθυρο συμφραζομένων του μοντέλου. Συντομεύστε την πριν την ξαναστείλετε. Το κείμενο του μηνύματος διαφέρει.

Τα φιλοξενούμενα μοντέλα ανοιχτών βαρών στέλνουν το μέλος error μόνο με type και message.

Τα SDK Python και JavaScript της OpenAI εγείρουν ένα chunk σφάλματος ως σφάλμα API καθώς κάνετε iterate, οπότε τυλίξτε τον βρόχο στον συνηθισμένο χειρισμό σφαλμάτων σας.

Οι κωδικοί κατάστασης, το σώμα σφάλματος και τι να ξαναδοκιμάσετε έχουν τη δική τους σελίδα. Χειρισμός σφαλμάτων

Κλείσιμο της σύνδεσης

Το κλείσιμο της σύνδεσης σταματά την παράδοση, όχι το αίτημα. Το μοντέλο γράφει την απάντηση μέχρι το τέλος και το αίτημα χρεώνεται σαν να τη διαβάσατε ολόκληρη. Για να πληρώσετε λιγότερο, ζητήστε λιγότερα: ορίστε χαμηλότερο max_tokens σε μοντέλο που το εφαρμόζει.

Ένα stream μπορεί επίσης να τελειώσει νωρίς στη διαδρομή προς εσάς, για παράδειγμα σε διακοπή δικτύου ή ενώ ενημερώνεται ένας διακομιστής. Σταματά τότε χωρίς το τελευταίο chunk και χωρίς data: [DONE]. Αντιμετωπίστε αυτή την απάντηση ως ατελή και στείλτε ξανά το αίτημα.

Ανάγνωση ενός stream χωρίς SDK

Ένας σωστός αναγνώστης κάνει πέντε πράγματα: στέλνει τα σφάλματα που φτάνουν πριν από το stream στον χειρισμό σφαλμάτων του, χωρίζει το σώμα σε γραμμές, προσπερνά τις γραμμές σχολίων, σταματά στο data: [DONE] και ελέγχει κάθε chunk για 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 χρησιμοποιεί το πακέτο requests. Το παράδειγμα JavaScript τρέχει σε Node.js 18 ή νεότερο.

Streams των άλλων μορφών

Τα endpoints Responses και Messages κάνουν επίσης streaming. Τα frames τους φέρουν μια γραμμή event: με το όνομα του συμβάντος και μια γραμμή data: με το JSON του. Κάθε σελίδα απαριθμεί τα συμβάντα της με τη σειρά.

Στο stream Chat CompletionsResponsesMessages
Frames Γραμμές data:Γραμμές event: και data:Γραμμές event: και data:
Ο συλλογισμός φτάνει στο delta.reasoning_contentresponse.reasoning_summary_text.deltathinking_delta
Το κείμενο της απάντησης φτάνει στο delta.contentresponse.output_text.deltatext_delta
Η χρήση φτάνει στο Το τελευταίο chunkresponse.completedmessage_delta
Το stream τελειώνει με Το τελευταίο chunk, και μετά data: [DONE]response.completedmessage_stop