رفتن به محتوا
استریم

استریم

stream را روی true بگذارید تا پاسخ هم‌زمان با نوشتن مدل، به‌صورت server-sent events برسد: ابتدا استدلال، سپس پاسخ، و در پایان یک chunk آخر با دلیل پایان و میزان مصرف. هر وقت کسی منتظر متن است و برای پاسخ‌های بلند از آن استفاده کنید.

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

SDKهای OpenAI chunkها را یکی‌یکی به شما می‌دهند. هر 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ی که بدون آن متوقف شود ناقص است.

chunkها به ترتیب

هر 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
    }
  ]
}

همه chunkهای یک stream id، created و model یکسان دارند. choices همیشه یک مورد دارد. delta آن آنچه را تازه است دارد و finish_reason تا آخرین chunk برابر null است.

کلید در delta نوع توضیحات ارسال‌شده توسط
role string همیشه assistant. یک بار، در ابتدای stream، می‌رسد. مدل‌های open-weight میزبانی‌شده، shannon-2-lite، shannon-2-pro
reasoning_content string یک تکه از استدلال مدل. تکه‌ها را به ترتیب به هم بچسبانید. مدل‌های open-weight میزبانی‌شده، خانواده Shannon 3، shannon-1.6-pro، shannon-coder-1
content string یک تکه از پاسخ. تکه‌ها را به ترتیب به هم بچسبانید. همه مدل‌ها
tool_calls array یک فراخوانی کامل ابزار. «deltaهای فراخوانی ابزار» را ببینید. همه مدل‌ها

ابتدا استدلال می‌آید، سپس پاسخ، سپس هر فراخوانی ابزار. 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]

deltaهای استدلال

مدلی که استدلال می‌کند، استدلال خود را پیش از پاسخ در delta.reasoning_content می‌فرستد. این دو متن را جدا نگه دارید: استدلال را به‌صورت بخشی جداگانه و جمع‌شدنی نشان دهید، یا آن را نمایش ندهید.

مدل‌های open-weight میزبانی‌شده وقتی 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 مستقیم کار می‌کند.

deltaهای فراخوانی ابزار

فراخوانی ابزار کامل می‌رسد: برای هر فراخوانی یک chunk، که delta آن فراخوانی را با رشته کامل 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
  }
}

در مدل‌های open-weight میزبانی‌شده، usage همچنین prompt_tokens_details.cached_tokens و completion_tokens_details.reasoning_tokens را دارد.

پس از درخواستی با web_search: true که جستجویش چیزی پیدا کرده باشد، آخرین chunk همچنین sources دارد و یک chunk پیش از آن نشانه‌های ارجاع را به شکل delta.annotations دارد. جستجوی وب داخلی

finish_reason توضیحات ارسال‌شده توسط
stop مدل پاسخ خود را تمام کرد، یا یک رشته stop ظاهر شد. همه مدل‌ها
tool_calls پاسخ شامل یک یا چند فراخوانی ابزار است. همه مدل‌ها
length پاسخ در حد خروجی بریده شد. خانواده Shannon 3، shannon-1.6-lite، shannon-1.6-pro، shannon-coder-1

فیلدهای usage همراه endpoint توضیح داده شده‌اند. Chat Completions

خط‌های keep-alive

تا وقتی مدل مشغول کار است و هنوز چیزی برای ارسال ندارد، stream خط‌های کامنت می‌فرستد. این خط‌ها داده ندارند. پارسر SSE خودش از آن‌ها رد می‌شود؛ کدی که خط‌های خام را می‌خواند باید هر خطی را که با دو نقطه شروع می‌شود رد کند.

خط ارسال‌شده توسط زمان
: ping خانواده Shannon 3، shannon-1.6-lite، shannon-1.6-pro، shannon-coder-1 هر 15 ثانیه.
: keepalive مدل‌های open-weight میزبانی‌شده هر 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 گفتگو از پنجره زمینه مدل بلندتر است. پیش از ارسال دوباره آن را کوتاه کنید. متن پیام متغیر است.

مدل‌های open-weight میزبانی‌شده عضو error را فقط با type و message می‌فرستند.

SDKهای OpenAI برای Python و JavaScript هنگام پیمایش، chunk خطا را به‌صورت خطای API بالا می‌اندازند، پس حلقه را در مدیریت خطای معمول خود بپیچید.

کدهای وضعیت، بدنه خطا و اینکه چه چیزی را دوباره امتحان کنید صفحه خودشان را دارند. مدیریت خطا

بستن اتصال

بستن اتصال تحویل را متوقف می‌کند، نه درخواست را. مدل پاسخ را تا انتها می‌نویسد و درخواست طوری محاسبه می‌شود که انگار همه آن را خوانده‌اید. برای پرداخت کمتر، کمتر بخواهید: روی مدلی که 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 یا بالاتر اجرا می‌شود.

streamها در قالب‌های دیگر

endpointهای Responses و Messages هم stream می‌کنند. فریم‌های آن‌ها یک خط event: با نام رویداد و یک خط data: با JSON آن دارند. هر صفحه رویدادهای خود را به ترتیب فهرست کرده است.

در stream Chat CompletionsResponsesMessages
فریم‌ها خط‌های 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