コンテンツへスキップ
ストリーミング

ストリーミング

stream を true に設定すると、モデルが書いている間、応答が server-sent events として届きます。最初に推論、次に回答、最後に終了理由と使用量を含むチャンクです。人がテキストを待っているときや、長い応答に使ってください。

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 文字列を含む呼び出しが入ります。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 として含まれます。 内蔵Web検索

finish_reason 説明 送信元
stop モデルが回答を終えたか、stop の文字列が現れました。 すべてのモデル
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 を確認してください。この確認がないと、途中で失敗した応答が完了したように見えます。

タイプ コード メッセージ 発生条件
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 会話がモデルのコンテキストウィンドウより長くなっています。再送信する前に短くしてください。メッセージのテキストは変わることがあります。

ホスト型オープンウェイトモデルは、type と message のみを持つ error メンバーを送信します。

OpenAI の Python SDK と 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
使用量の届く場所: 最後のチャンクresponse.completedmessage_delta
ストリームの終わり: 最後のチャンク、続いて data: [DONE]response.completedmessage_stop