Skip to main content

簡潔な答え

3つの文:
  1. ストリーミングと非ストリーミングの違いは、完全にあなた自身のコードで決まります—リクエストボディ内の stream フィールドです。同じキー、同じモデル、同じエンドポイントでも、前後で切り替わるなら、切り替えているのはクライアントコード(またはそれをラップしている SDK / フレームワーク)です。ゲートウェイが勝手に切り替えることはありません。
  2. どちらのモードでも最終的な内容は同一で、課金も同じです。 違いは、テキストをいつ受け取るかと、どうパースするかだけです。
  3. 選び方: 画面を人間が見ている → ストリーミング; プログラムが結果を消費する(JSON のパース、バッチジョブ、ツール呼び出し) → 非ストリーミング。

一目でわかる違い

なぜ私のリクエストはストリーミングと非ストリーミングの間で切り替わるのですか?

これは最もよくある質問で、答えは お客様側の何かがそれを変えています。 以下の一覧を順に確認してください — ほぼ必ずどれかが当てはまります:
典型的なケースは、stream=config.get("stream", False) または stream=is_web_request です。異なるエントリポイントから同じ関数に異なる値が渡されるため、ログ上はモードがランダムに切り替わっているように見えます。確認方法: 実際に送信しているリクエストボディを出力し、stream フィールドを確認してください。
同じビジネスロジックでも、クライアントによって挙動が異なります:
  • OpenAI SDK chat.completions.create(): 既定では 非ストリーミング
  • client.chat.completions.stream() または with_streaming_response: ストリーミング
  • LangChain / LlamaIndex のようなラッパー: invokestream のどちらを呼ぶか、またモデルオブジェクトを生成するときに streaming=True を渡したかどうかによって異なります
  • デスクトップクライアント、エージェントツール、ワークフロープラットフォームでは、通常、設定に「ストリーミング出力」の切り替えがあり、既定値はさまざまです
確認方法: 実際にその呼び出しを行ったエントリポイントを確認してください。
Web チャット UI(ストリーミング)と夜間のバッチジョブ(非ストリーミング)の両方で同じキーを使うと、一緒に見るとランダムに見えるログが生成されます。確認方法: ユースケースごとに個別の token を作成してください — するとログも分かれて表示されます。token 管理を参照してください。
実際には stream: true を送信しているのですが、Nginx、社内ゲートウェイ、または何らかのプロキシがレスポンスを バッファリング していました — サーバーはチャンクごとに送り、プロキシがそれを保持して一度に放出したため、非ストリーミングのように見えます。確認方法: 一度プロキシをバイパスしてテストし、Nginx のバッファリングをオフにしてください(proxy_buffering off;)。この場合でも、ゲートウェイが実際にストリーミングで出力しているため、コンソールログには引き続き is_stream = true と表示されます。
特定の呼び出しが実際に何を行ったかを確認するには: コンソールログの is_stream フィールドを確認するか、ログクエリ API で一括取得してください。それが信頼できる唯一の情報源であり、印象よりはるかに確実です。

シナリオ別の選び方

ストリーミングを使う

  • チャット UI やサポートボット — ユーザーは即時フィードバックを必要とします
  • IDE プラグイン / コーディングアシスタント(Claude Code, Cursor など)
  • 長文生成(長い記事、長い翻訳、大きなコードブロック)
  • 長時間の推論モデルタスク — 少なくとも進捗が見えます
  • 生成の途中でユーザーが「停止」を押せる場所ならどこでも

非ストリーミングを使う

  • 構造化出力: json.loads()のために JSON 全体が必要です
  • function-calling / tool-call 引数のパース
  • バッチ処理、オフラインジョブ、スケジュール済みタスク
  • 最終結果だけが重要で、誰も待っていないバックエンドフロー
  • 素早い検証、デバッグ、テストケースの作成
いくつかの特別なケース:

統合の手間: 同じタスクを、両方の方法で

Claude のネイティブ形式(/v1/messages)は別のストリーミングプロトコルを使用します: Anthropic の名前付きイベント SSE(message_start / content_block_delta / message_delta など)であり、OpenAI の一様な data: チャンクではなく、usagemessage_startmessage_delta のイベントに分割されます。完全なパースガイド: Claude ネイティブ形式: ストリーミングおよび非ストリーミングのレスポンス

課金と使用量: どちらでも同じ

ストリーミングは安くも高くもなりません。 課金は token ごとで、バイトがどのように転送されるかとは関係ありません。途中で切断しても課金されますCtrl+C に到達した後、またはクライアントがタイムアウトした後でも、上流側の生成は最後まで実行され、リクエストは通常どおり課金されます。つまり、「stream を早めに切って節約する」は通用しません。
usage をめぐる2つの落とし穴:
  1. ストリーミングではデフォルトで usage は返されません。 OpenAI互換のエンドポイントでは stream_options: {"include_usage": true} を渡す必要があります。すると usage は最後のチャンクに届きます(その choices 配列は空です — インデックスで参照する前に確認してください)。これは APIYI のいくつかのモデルで動作確認済みです。
  2. API が返す usage と請求を突き合わせないでください。 特にキャッシュ関連のフィールドです。返された値は、実際に課金された内容と必ずしも一致しません。キャッシュヒットが発生したかどうかは、コンソールログ内の「cache billing details」 で判断されます。 キャッシュ課金の説明 を参照してください。
どちらの場合でも、コンソールログには各呼び出しの token 数、レイテンシ、および課金が記録されます。転送方式による違いはありません。フィールドの意味: ログ課金詳細の理解

6つのよくある誤解

そうではありません。 streaming は 最初 の token を早く届かせるだけです。全体の生成時間は短くならず、安定したデータの流れも保証されません。推論モデル(gemini-3.1-pro-previewgpt-5.6-solgpt-5.5-pro など)は、思考フェーズ中にまったく何も出力しないことがあり、その場合でもクライアントの read timeout を同じように発生させます。正しい対処は、シナリオごとに timeout 値を設定することです。詳細は API timeout を回避する方法 をご覧ください。
最初のバイトは速くなりますが、合計時間は変わりません。 同じモデルと prompt であれば、streaming と non-streaming の完了時間は概ね同じです。streaming がもたらすのは 体感上の 速さです。ユーザーは 30 秒のスピナーを見つめ続ける代わりに、1 秒以内に動きが見えます。画面を誰も見ていなければ、その価値はゼロです。
いいえ。 上の「課金と利用」をご覧ください。課金は同一で、途中で切断しても課金されます。
いいえ。 テキストチャットモデルは通常そうですが、画像生成、embedding、rerank の endpoint には streaming の概念がなく、stream を無視するか拒否します。いくつかのモデルでは、streaming 下で特定の parameter の組み合わせに追加の制約があります。判断に迷う場合は、まず non-streaming で呼び出しを動かし、その後 stream: true を追加してください。
どちらにも失敗モードがあります。
  • non-streaming のリスク: 生成中ずっと接続にデータが流れないため、プロキシ、CDN、社内ゲートウェイが idle timeout で切断することがあります。レスポンス本文が非常に大きい場合(base64 の画像出力は簡単に数十 MB に達します)、終端処理が停止する問題にも遭遇します — 転送は完了したのに戻らないリクエストログ上は完了なのにクライアントには何も届かない をご覧ください。
  • streaming のリスク: SSE をサポートしない、またはバッファリングを強制するミドルボックスと相性が悪いことです。クライアント側のパースはより複雑で、微妙な誤りを起こしやすいです。
なお、api-cf.apiyi.com(CDN エンドポイント)には約 100 秒のリクエスト上限があり、両方のモードに影響します。長いリクエストには api.apiyi.com または vip.apiyi.com を使用してください。詳細は Base URL 設定ガイド をご覧ください。
できます。ただ、自分で組み立てる必要があります。 各 chunk の delta.content を順番に連結すれば、non-streaming の message.content とまったく同じになります。組み立てたテキストが不完全に見える場合は、3 つ確認してください。finish_reason を無視していないか、data: [DONE] を受け取る前にループを抜けていないか、そして middlebox がレスポンスを途中で切っていないかです。

ストリーミングが動作しない? 4つの手順

1

リクエストボディに本当に stream: true が含まれているか確認してください

実際に送信している JSON を出力してください。ラッパーライブラリでは、「渡したつもりだった」と「実際に渡っていた」は別物であることがよくあります。
2

curl -N で直接テストしてください

上の「cURL を並べて表示」タブにあるコマンドを使って、自分のコードやプロキシをバイパスしてください。curl でチャンクが段階的に到着するなら、サーバー側は正常で、問題はクライアントまたはミドルボックスにあります。
3

ミドルボックスのバッファリングを確認してください

Nginx では proxy_buffering off; を追加してください。企業のゲートウェイやセキュリティアプライアンスは text/event-stream を全体のペイロードとしてスキャンする場合があります — ネットワーク管理者に通過を許可してもらってください。
4

パースロジックを見直してください

SSE を 1 行ずつ読み取り、空行と : で始まるコメント行をスキップし、data: [DONE] で停止してください。usage を含む最終チャンクには空の choices 配列があります — そこをインデックス参照しないでください。
ここまで進んでも答えが得られない場合は、request_id を添えてサポートにお問い合わせください — コンソールログには、その呼び出しがストリームとして処理されたかどうかに加え、総レイテンシと初回バイトまでの時間が直接表示されます。

関連ドキュメント

API タイムアウトを回避する方法

シナリオ別のタイムアウト値と、ストリーミングでは回避できない理由

Base URL 設定ガイド

エンドポイントの違いと、CDN ノードの 100 秒上限

ログには完了と表示されるのに応答がない

セグメントごとのタイミングを含む、典型的な大きい非ストリーミング応答の問題

Claude のストリーミングと非ストリーミング

Anthropic のネイティブな名前付きイベント SSE プロトコルのパース

テキスト生成 API

完全なパラメータ一覧と呼び出し例

ログ課金の詳細を理解する

is_stream を含む、コンソールログの各フィールドが意味するもの