কনটেন্টে যান
স্ট্রিমিং

স্ট্রিমিং

stream কে true করুন, তাহলে মডেল লেখার সাথে সাথে উত্তর server-sent ইভেন্ট হিসেবে আসে: আগে রিজনিং, তারপর উত্তর, তারপর ফিনিশ রিজন ও usage সহ একটি শেষ চাঙ্ক। কোনো মানুষ টেক্সটের জন্য অপেক্ষা করলে এবং দীর্ঘ উত্তরের জন্য এটি ব্যবহার করুন।

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

OpenAI SDK আপনাকে চাঙ্কগুলো একে একে দেয়। প্রতিটি চাঙ্ক সে যা বহন করে তা দিয়ে পড়ুন: রিজনিংয়ের টুকরো, উত্তরের টুকরো, একটি টুল কল, বা উত্তরের শেষ।

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

স্ট্রিম কীভাবে ফ্রেম করা হয়

  • উত্তরের স্ট্যাটাস 200 এবং হেডার content-type: text/event-stream ও cache-control: no-cache।
  • প্রতিটি ইভেন্ট data: দিয়ে শুরু হওয়া একটি লাইন, তারপর একটি ফাঁকা লাইন। data: -এর পরের টেক্সট একটি JSON চাঙ্ক। কোনো event: বা id: লাইন নেই।
  • কোলন দিয়ে শুরু হওয়া লাইন, যেমন : ping, একটি মন্তব্য যা সংযোগ খোলা রাখে। এটি বাদ দিন।
  • স্ট্রিম শেষ হয় data: [DONE] দিয়ে। এটি ছাড়া থেমে যাওয়া স্ট্রিম অসম্পূর্ণ।

ক্রম অনুযায়ী চাঙ্ক

প্রতিটি চাঙ্ক একটি 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
    }
  ]
}

একটি স্ট্রিমের সব চাঙ্কে একই id, created ও model থাকে। choices-এ সবসময় একটি এন্ট্রি। এর delta-তে নতুন অংশ থাকে, এবং শেষ চাঙ্ক পর্যন্ত finish_reason হলো null।

delta-র কী টাইপ বিবরণ যে পাঠায়
role string সবসময় assistant। এটি একবারই আসে, স্ট্রিমের শুরুতে। হোস্টেড ওপেন-ওয়েট মডেল, shannon-2-lite, shannon-2-pro
reasoning_content string মডেলের রিজনিংয়ের একটি টুকরো। টুকরোগুলো ক্রম অনুযায়ী জুড়ুন। হোস্টেড ওপেন-ওয়েট মডেল, Shannon 3 পরিবার, shannon-1.6-pro, shannon-coder-1
content string উত্তরের একটি টুকরো। টুকরোগুলো ক্রম অনুযায়ী জুড়ুন। সব মডেল
tool_calls array একটি সম্পূর্ণ টুল কল। টুল কল ডেল্টা দেখুন। সব মডেল

আগে রিজনিং, তারপর উত্তর, তারপর টুল কল। চাঙ্ক পড়ুন তার delta-র কী দেখে, স্ট্রিমে তার অবস্থান দেখে নয়: একটি delta-তে প্রথম টেক্সট টুকরোর সাথে role থাকতে পারে, আর শেষটি খালি।

একটি পুরো স্ট্রিম, যেভাবে সেটি যায়। লাইন ছোট রাখতে এখানে প্রতিটি চাঙ্ক থেকে id, object, created ও 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]

রিজনিং ডেল্টা

যে মডেল রিজনিং করে সে উত্তরের আগে delta.reasoning_content-এ তার রিজনিং পাঠায়। দুটি টেক্সট আলাদা রাখুন: রিজনিং আলাদা, ভাঁজ করা যায় এমন অংশ হিসেবে দেখান, অথবা বাদ দিন।

reasoning_effort off হলে এবং রিকোয়েস্টে response_format থাকলে হোস্টেড ওপেন-ওয়েট মডেল কোনো রিজনিং পাঠায় না।

shannon-2-lite ও shannon-2-pro-ও উত্তরের আগে রিজনিং করে। এই এন্ডপয়েন্টে রিজনিংয়ের সময় তারা : thinking মন্তব্য লাইন পাঠায়, তারপর উত্তর।

reasoning_content OpenAI SDK-র টাইপ করা মডেলে নেই। Python-এ এটি পড়ুন getattr(delta, "reasoning_content", None) দিয়ে; JavaScript-এ delta.reasoning_content যেমন আছে তেমনই কাজ করে।

টুল কল ডেল্টা

টুল কল অখণ্ড আসে: প্রতি কলে একটি চাঙ্ক, যার delta-তে সম্পূর্ণ arguments string সহ কলটি থাকে। index উত্তরের কলগুলোকে 0 থেকে গোনে। যে কোড arguments টুকরো টুকরো করে জোড়ে সেটি অপরিবর্তিত কাজ করে, কারণ সে একটি টুকরোই জোড়ে।

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

কলগুলোর পরে শেষ চাঙ্কে finish_reason হলো tool_calls।

রিকোয়েস্টে tools থাকলে shannon-1.6-lite, shannon-1.6-pro ও shannon-coder-1 উত্তরের টেক্সট উত্তরের শেষে একটি ডেল্টায় পাঠায়।

শেষ চাঙ্ক

data: [DONE]-এর আগের শেষ চাঙ্কে একটি খালি delta, finish_reason এবং রিকোয়েস্টের usage থাকে, সবই একই চাঙ্কে। প্রতিটি স্ট্রিমে এটি থাকে; 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 আছে এবং যার সার্চে কিছু পাওয়া গেছে, তার পরে শেষ চাঙ্কে sources-ও থাকে, আর তার আগের একটি চাঙ্কে উদ্ধৃতি চিহ্নগুলো delta.annotations হিসেবে থাকে। ওয়েব সার্চ

finish_reason বিবরণ যে পাঠায়
stop মডেল তার উত্তর শেষ করেছে, অথবা একটি stop string দেখা গেছে। সব মডেল
tool_calls উত্তরে এক বা একাধিক টুল কল আছে। সব মডেল
length আউটপুট সীমায় উত্তর কেটে গেছে। Shannon 3 পরিবার, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1

usage-এর ফিল্ডগুলো এন্ডপয়েন্টের সাথে বর্ণিত। Chat Completions

কিপ-অ্যালাইভ লাইন

মডেল কাজ করছে কিন্তু পাঠানোর মতো কিছু নেই, এই সময়ে স্ট্রিম মন্তব্য লাইন বহন করে। এগুলোতে কোনো ডেটা থাকে না। 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 মডেল যখন রিজনিং করে।

স্ট্রিমের ভেতরের এরর

স্ট্রিম শুরু হয়ে গেলে তার স্ট্যাটাস 200, তাই ব্যর্থতা স্ট্রিমের ভেতরে আসে: যেখানে choices থাকত সেখানে error সদস্যসহ একটি চাঙ্ক।

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]

এরর চাঙ্কের পরেও finish_reason ও usage সহ শেষ চাঙ্ক এবং data: [DONE] আসে। প্রতিটি চাঙ্কে error আছে কি না দেখুন। এই যাচাই ছাড়া মাঝপথে ব্যর্থ হওয়া উত্তরও সম্পূর্ণ মনে হয়।

Type কোড বার্তা কখন
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 সহ।

OpenAI Python ও JavaScript SDK আপনি ইটারেট করার সময় এরর চাঙ্ককে API এরর হিসেবে তোলে, তাই লুপটি আপনার সাধারণ এরর হ্যান্ডলিংয়ে মুড়ে দিন।

স্ট্যাটাস কোড, এরর বডি এবং কী আবার চেষ্টা করবেন তার নিজস্ব পেজ আছে। এরর হ্যান্ডলিং

সংযোগ বন্ধ করা

সংযোগ বন্ধ করলে ডেলিভারি থামে, রিকোয়েস্ট থামে না। মডেল শেষ পর্যন্ত উত্তর লেখে এবং আপনি সবটা পড়লে যেভাবে চার্জ হতো রিকোয়েস্ট সেভাবেই চার্জ হয়। কম দিতে চাইলে কম চান: যে মডেল প্রয়োগ করে তাতে ছোট max_tokens দিন।

আপনার কাছে আসার পথে স্ট্রিম আগেই শেষ হয়ে যেতে পারে, যেমন নেটওয়ার্ক বিচ্ছিন্ন হলে বা সার্ভার আপডেটের সময়। তখন এটি শেষ চাঙ্ক ও data: [DONE] ছাড়া থেমে যায়। সেই উত্তরকে অসম্পূর্ণ ধরুন এবং রিকোয়েস্ট আবার পাঠান।

SDK ছাড়া স্ট্রিম পড়া

একটি সঠিক রিডার পাঁচটি কাজ করে: স্ট্রিমের আগে আসা এরর নিজের এরর হ্যান্ডলিংয়ে পাঠায়, বডিকে লাইনে ভাগ করে, মন্তব্য লাইন বাদ দেয়, data: [DONE]-এ থামে, এবং প্রতিটি চাঙ্কে 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 বা তার পরের ভার্সনে চলে।

অন্য ফরম্যাটের স্ট্রিম

Responses ও Messages এন্ডপয়েন্টও স্ট্রিম করে। তাদের ফ্রেমে ইভেন্টের নামসহ একটি event: লাইন এবং তার JSON সহ একটি data: লাইন থাকে। প্রতিটি পেজে তার ইভেন্ট ক্রম অনুযায়ী তালিকাভুক্ত।

স্ট্রিমে Chat CompletionsResponsesMessages
ফ্রেম data: লাইনevent: ও data: লাইনevent: ও data: লাইন
রিজনিং আসে delta.reasoning_contentresponse.reasoning_summary_text.deltathinking_delta
উত্তরের টেক্সট আসে delta.contentresponse.output_text.deltatext_delta
usage আসে শেষ চাঙ্কresponse.completedmessage_delta
স্ট্রিম শেষ হয় শেষ চাঙ্ক, তারপর data: [DONE]response.completedmessage_stop