Responses API Chat Completions OpenAI API API設計

Responses API と Chat Completions:どちらを土台に作るか

OpenAI の Responses API と Chat Completions のフィールドガイド。リクエスト構造、出力アイテム、状態保持、推論、ツール、そしてそれぞれのエンドポイントが正解になる場面を解説する。

AveMujica API 3 分で読めます

今日から新しい統合を始めるなら、土台には Responses API(POST /v1/responses)を選び、Chat Completions(POST /v1/chat/completions)は既に動いている既存コードのために残しておくのがよい。この2つのエンドポイントは同じ中心的な課題――プロンプトを送ってモデルの返答を得る――を解くが、会話のモデル化の仕方が異なる。Chat Completions はひとつのターンをフラットな messages のリストとして扱い、choices 配列を返す。Responses はひとつのターンを型付きの input アイテムのリストとして扱い、メッセージ・推論・ツール呼び出しを並べて格納できる型付きの output 配列を返す。この構造上の違いこそが本質であり、どちらのエンドポイントがあなたのワークロードに合うかを決める。

本稿では、リクエスト構造・出力構造・状態・推論・ツールの観点で両者を比較し、当てずっぽうに頼らずに適用できる判断ルールを示す。何かを非推奨にするわけではない。Chat Completions は引き続きサポートされており、依然としてプロバイダ横断で最も移植性の高い形である。

リクエスト構造:messages と input

Chat Completions は messages 配列を期待する。各要素は role(system、user、assistant、tool)と content を持つ。ターンに関するすべて――指示、履歴、ツール結果――は、そのひとつのリストに貼り合わされる。

from openai import OpenAI
client = OpenAI(base_url="https://your-gateway.example.com/v1", api_key="AVE_GATEWAY_API_KEY")

completion = client.chat.completions.create(
    model="gpt-5.6",
    messages=[
        {"role": "system", "content": "You are a terse assistant."},
        {"role": "user", "content": "Name three uses for an API gateway."},
    ],
)
print(completion.choices[0].message.content)

Responses はこれらの関心事を分離する。トップレベルのガイダンスは instructions に移り、ターンの内容は input に移る。input はプレーンな文字列か、型付きアイテムの配列のどちらかを受け付ける。

response = client.responses.create(
    model="gpt-5.6",
    instructions="You are a terse assistant.",
    input="Name three uses for an API gateway.",
)
print(response.output_text)

すぐに気づいておくべき点が2つある。第一に、response.output_text はテキスト出力を連結してくれる便利なアクセサで、completion.choices[0].message.content の経路を置き換える。第二に、instructions はチェーンされたターン間で持続しない――previous_response_id で会話を継続する場合、前のレスポンスの instructions は引き継がれないため、毎ターン送り直すことになる。

出力構造:choices と output アイテム

Chat Completions は choices 配列を返す。n パラメータを使えば複数の候補補完を要求でき、それぞれが別々の choice だった。Responses は n を廃止し、生成をひとつだけ返すが、その生成は アイテム から成る output 配列であり、各アイテムはそれぞれの type を持つ。モデルが 返す アイテムは次のとおり。

  • message — アシスタントのテキスト(および拒否)
  • reasoning — 推論の要約、または暗号化された推論トークン
  • function_call — モデルが実行を求めるツール呼び出し

ここに何が欠けているかに注目してほしい。function_call_output はモデルが返すアイテムではない。これはツール結果をモデルに手渡すために自分で構築して送り返す 入力 アイテムで、call_id で紐づけられる。2つのリストを別物として保つこと――読み取る出力アイテムと、書き込む入力アイテム――が、よくある混乱の元を断つ。呼び出しとその結果は、やり取りの反対側に存在するからだ。

すべての output エントリをメッセージだと決めつけるのは、移行時の最も多い失敗だ。推論モデルは message の前に reasoning アイテムを出すことがあり、ツールを使うターンではユーザーに見えるテキストが一切ない function_call を出すことがある。インデックス 0 が答えだと仮定せず、type ごとに output を反復処理しよう。

for item in response.output:
    if item.type == "message":
        print(item.content[0].text)
    elif item.type == "function_call":
        print("tool wanted:", item.name, item.arguments)

利用量の会計も語彙が変わる。Chat Completions は prompt_tokens と completion_tokens を報告するが、Responses は usage オブジェクトに input_tokens と output_tokens を報告する。トークン数をパースするダッシュボードは両方の形を扱う必要があり、それらをひとつの会計ビューに正規化するゲートウェイがあれば、すべてのクライアントが2つのコードパスを抱えずに済む。

状態:クライアント管理の履歴とサーバ保存のスレッド

Chat Completions では会話は自分のものだ。呼び出しのたびに messages 履歴の全体を送り直し、リクエストがモデルに必要なすべてを運ぶ。このクライアント管理型は推論しやすく、どんなプロキシ経由でもルーティングしやすい。それが業界共通語になった大きな理由でもある。OpenAI がやり取りのコピーを 保持する かどうかは、エンドポイントとは別の問題で、store 設定とアカウントのデフォルト(ロギングのためストレージを有効化しうる)に支配されるのであって、Chat Completions が本質的に「ステートレス」だからではない。上流に何も残さないことを保証しなければならないなら、エンドポイントが何も保持しないと仮定するのではなく、明示的に store: false を送ろう。

Responses は 既定で保存される。各呼び出しは id を返し、履歴を送り直す代わりに次のリクエストで previous_response_id を渡してスレッドを継続できる。状態管理には3つの方法がある。

  1. 保存済みレスポンスへチェーンするための previous_response_id、
  2. 直前の output アイテムを手動で input に戻す、
  3. より長寿命なスレッドのための Conversations API。

保存は便利機能であって義務ではない。ゼロデータ保持のワークフローで望むように、store: false を設定してオプトアウトできる。プランニングで重要な課金上の一点:previous_response_id でチェーンした場合でも、チェーン中の以前の入力トークンはすべて入力トークンとして課金される。チェーンはバイト列をワイヤー越しに送り直す手間を省くだけで、以前のコンテキストを無料にはしない。

推論:Responses が抜きん出る場所

Responses を選ぶ最も明快な理由は推論モデルだ。推論が第一級の output アイテム型であるため、API は呼び出し間で推論を捨てるのではなく、ターンをまたいで推論状態を保持できる。ステートレス構成では、store: false を include: ["reasoning.encrypted_content"] と一緒に送ることで暗号化推論を要求できる。API は暗号化された推論トークンを返し、それを次のターンで渡し返す。内容はメモリ内で復号され、その後破棄される。Chat Completions には推論のための型付きスロットが等価に存在しないため、推論モデルはターン間でその連続性を失う。

製品がマルチステップ推論、推論モデルが駆動するツールループ、あるいは長時間のエージェント実行に依存するなら、Responses はそれのために設計されたエンドポイントだ。関係するパラメータの詳しい解説は 推論モデルの API パラメータ を参照。

ツールと構造化出力

両エンドポイントとも関数呼び出しと構造化出力をサポートするが、フィールド名が異なる。

関心事Chat CompletionsResponses
エンドポイント/v1/chat/completions/v1/responses
ターン内容messages[]input(文字列またはアイテム)
システムプロンプトsystem メッセージinstructions
結果テキストchoices[0].message.contentoutput_text
構造化出力response_formattext.format
ツール結果の紐づけtool メッセージ上の tool_call_idfunction_call_output と call_id
複数候補n廃止(単一生成)
ネイティブツール手動配線tools: [{ "type": "web_search" }]

text.format と response_format の入れ替えは見落としやすく、誤った方を送ると分かりにくいエラーを生む。Structured Outputs では、モデルをスキーマに拘束するためにスキーマ上で明示的に strict: true を設定しよう。(別途ある strict 省略のフォールバック――strict 強制を試み、互換性のないスキーマでは非 strict に降格する――は Responses の 関数/ツール 定義に適用されるのであって、text.format の構造化出力には適用されない。両者を混同しないこと。)JSON Schema 出力に依存するなら、バリデーションコードを移植する前に JSON Schema による構造化出力 を読んでほしい。

ツールループは実際にどう違うか

ツール呼び出しの往復こそ、2つの形が最も違って感じられる場所なので、並べて見る価値がある。Chat Completions では、モデルは tool_calls を含むアシスタントメッセージを返し、あなたはそれを実行してから、対応する tool_call_id を持つ role: "tool" のメッセージを追加する。

# Chat Completions: append a tool result as a message
messages.append({"role": "tool", "tool_call_id": call.id, "content": result})

Responses では、モデルは function_call アイテムを発行し、あなたは結果を call_id で紐づけた function_call_output アイテムとして――メッセージとしてではなく――返す。

# Responses: append a typed function_call_output item
client.responses.create(
    model="gpt-5.6",
    previous_response_id=response.id,
    input=[{
        "type": "function_call_output",
        "call_id": call.call_id,
        "output": result,
    }],
)

紐づけキーは Responses では call_id、Chat Completions では tool_call_id という名前で、対応する id なしに結果を送るのは頻出のエラー源だ。Responses は呼び出しとその結果を別々の型付きアイテムとして保つため、エージェントループはより素直に読める。アシスタントメッセージの中からツールの意図を取り出す必要が一切ない。プロバイダ横断で移植可能なツール定義には、依然として独自の互換レイヤが必要だ。

適用できる判断ルール

これで思い悩む必要はない。表を使おう。

状況エンドポイント
新規統合、特にエージェントや推論モデルResponses
既存で動いており、新要件のない Chat Completions コードChat Completions を維持
今日、多数のプロバイダ横断で最大の移植性が必要Chat Completions(広くミラーされた形)
サーバ管理の会話状態やネイティブツールが欲しいResponses
ゼロ保持で暗号化推論の連続性が欲しいstore: false + 暗号化推論を用いた Responses

ゲートウェイはこの選択を両側で安価にする。ひとつの base URL の背後で、Chat Completions トラフィックと Responses トラフィックを同じ上流へ流し、プロバイダを一度だけ登録し、各モデルが何をサポートしいくらかかるかを公開の 料金ページ に掲載できる。後でエンドポイントを移行しても、クライアント側の変更は小さく、ルーティングはそのまま残る。手順を追った経路は Chat Completions から Responses API への移行 で扱う。

ストリーミングは両方で動くが、イベントは異なる

両エンドポイントとも Server-Sent Events でストリーミングするが、イベントの語彙が異なる。Chat Completions は choices[].delta を持つ chat.completion.chunk オブジェクトをストリームし、リテラルの data: [DONE] で終端する。Responses は response.created、response.output_text.delta、response.completed のような セマンティックイベント をストリームし、各イベントが自分が何であるかを名乗るためルーティングしやすい。トークン単位の UI を作るなら、SSE による LLM レスポンスのストリーミング が両形式のパースを解説している。

FAQ

Responses が登場した今、Chat Completions は非推奨か?

いいえ。Chat Completions は引き続きサポートされるエンドポイントであり、OpenAI 以外のプロバイダ横断で依然として最も広くミラーされたリクエスト形だ。OpenAI が終息対象に挙げたのは Assistants API であって、Chat Completions ではない。新しいエージェント的な作業を Responses の上に作るのは、その構造化出力アイテムと推論サポートのためであって、Chat Completions がなくなるからではない。

Chat Completions の n のように、Responses API から複数の候補回答を得られるか?

単一の n パラメータでは得られない――Responses は呼び出しごとにひとつの生成を返す。複数候補が必要なら、複数リクエストを送る。Chat Completions のコードが n > 1 に依存していた場合、これは考慮すべき本物の挙動変更だ。

previous_response_id はトークンの請求額を減らすか?

履歴を送り直さなくなるためワイヤー上のバイト数は減るが、課金される入力トークンは減らない。チェーン中の以前の入力トークンはすべて入力トークンとして課金される。チェーンはコスト最適化ではなく、使い勝手とレイテンシの機能だと捉えよう。実際にコンテキストコストを削るには、OpenAI・Claude・Gemini のプロンプトキャッシュ を見てほしい。

マルチプロバイダ構成はどちらのエンドポイントに標準化すべきか?

多数のプロバイダへルーティングするなら、今日でも Chat Completions が最も移植性の高い共通分母であり、Responses はより豊かな OpenAI ネイティブの面だ。ゲートウェイなら両方を保持できる。各クライアントが既に話す形を公開し、チャネルごとに翻訳すればよい。

ひとつのワークフローで両方を走らせる

リスクの低いワークフローをひとつ選び、ゲートウェイ経由で2回走らせよう――一度は Chat Completions 呼び出しとして、一度は Responses 呼び出しとして――そして output/choices の形、トークン会計、レイテンシをゲートウェイの利用ログで比較する。その1回の並置は、どんな仕様の要約よりも、どちらのエンドポイントがあなたのコードに合うかを教えてくれる。

参考文献