はじめに
2025年に入り、OpenAI APIは大きな転換点を迎えました。2025年3月に Responses API が正式リリースされ、これまで別々に存在していた Chat Completions API の会話機能と Assistants API のツール統合機能が1つのエンドポイントに集約されました。従来の Assistants API は2026年8月26日をもって廃止予定です。
また、モデルの出力を JSON スキーマに確実に準拠させる Structured Outputs は、Responses API との組み合わせで特に力を発揮します。2024年8月のリリース以降、エージェント的なワークフローやデータ抽出パイプラインの信頼性を大きく向上させています。
本記事では、まず OpenAI API の全体像を把握した上で、この2つのトピックに絞って仕様と実装方法を解説します。
情報ソースについて
本記事の仕様・性能データは OpenAI 公式ドキュメント(developers.openai.com/api)、公式マイグレーションガイド(developers.openai.com/api/docs/guides/migrate-to-responses)、および公式ブログ(openai.com/index/introducing-structured-outputs-in-the-api)を参照しています。
1. APIの全体像
OpenAI API の主要なカテゴリは2025年時点で以下のように整理されています。
| カテゴリ | エンドポイント | 位置付け |
|---|---|---|
| Responses API | POST /v1/responses |
新規プロジェクト推奨。エージェント向け統合インターフェース |
| Chat Completions API | POST /v1/chat/completions |
引き続きサポート継続(廃止予定なし) |
| Realtime API | WebRTC / WebSocket / SIP | リアルタイム双方向音声・テキスト |
| Embeddings | POST /v1/embeddings |
ベクトル検索・RAG |
| Images | POST /v1/images/generations |
画像生成・編集 |
| Audio | POST /v1/audio/transcriptions |
音声認識・TTS |
OpenAIは新機能の主要投入先として Responses API を位置付けており、Assistants API については2026年8月26日の廃止が正式に決定しています(出典:公式マイグレーションガイド)。Chat Completions API は廃止予定なく引き続きサポートされますが、新規プロジェクトでは Responses API を選ぶのが現在の推奨です。
Structured Outputs はこの表のいずれかに並ぶような独立したエンドポイントではなく、Responses API と Chat Completions API の両方で利用できる出力フォーマット制御のオプション機能です。前者では text.format、後者では response_format パラメータで指定します。本記事では特に Responses API との組み合わせに焦点を当てて解説します。
2. Responses API
2.1 概要
Responses API は、Chat Completions API の後継かつ Assistants API の機能を統合した新しいプリミティブです。2025年3月に一般提供(GA)されました。
最も大きな変化として、サーバーサイドで会話状態を保持できるようになったことが挙げられます。従来の Chat Completions では毎回のリクエストに全会話履歴を含める必要がありましたが、Responses API では previous_response_id を渡すだけで続きを取得できます。
2.2 基本リクエスト
import OpenAI from "openai";
const client = new OpenAI();
const response = await client.responses.create({
model: "gpt-4o",
input: "東京の天気を教えて",
});
console.log(response.output_text); // output_text ヘルパーで直接テキスト取得
2.3 output_text ヘルパー
output_text は OpenAI SDK が提供する便利プロパティで、API仕様そのものではありません。
Responses API の生レスポンスでは、テキストは次のようにネストされた構造の中にあります。
response.output[0].content[0].text
output_text はこのパスを辿る処理をSDKがまとめたもので、内部的には output 配列の中から type: "message" かつ content に type: "output_text" を持つ最初の要素のテキストを返します。
// この2つは同じ結果になる
console.log(response.output_text);
console.log(response.output[0].content[0].text);
ただし、ツール呼び出しが発生した場合など output 配列の先頭がテキストメッセージではないケースでは期待通りに動作しないことがあります。ツールを併用するエージェント的なコードでは、output 配列を直接ループして type を確認するほうが堅牢です。
for (const item of response.output) {
if (item.type === "message") {
for (const block of item.content) {
if (block.type === "output_text") {
console.log(block.text);
}
}
}
}
2.4 入力メッセージの role
input に配列を渡すと、各メッセージに role を指定できます。role はモデルに対して「誰がこの発言をしているか」を伝えるもので、3種類あります。
| role | 意味 | 典型的な使い方 |
|---|---|---|
system |
システム(開発者)からの指示 | モデルの振る舞い・口調・制約を定義する。会話の最初に1件だけ置くのが一般的 |
user |
エンドユーザーからの入力 | ユーザーの発言・質問を表す |
assistant |
モデル自身の過去の発言 | マルチターン会話で前の応答を履歴として渡す際に使う |
const response = await client.responses.create({
model: "gpt-4o",
input: [
{
role: "system",
content: "あなたは親切な日本語アシスタントです。簡潔に答えてください。",
},
{
role: "user",
content: "JavaScriptの配列メソッドを教えて",
},
],
});
input に文字列を直接渡した場合(2.2のサンプル)は、その文字列が role: "user" のメッセージとして扱われます。モデルの動作を細かく制御したい場合や system 指示を与えたい場合は配列形式を使ってください。
2.5 レスポンス構造
Chat Completions の choices と異なり、output 配列で返します。
{
"id": "resp_68af4030...",
"object": "response",
"created_at": 1756315696,
"model": "gpt-4o",
"output": [
{
"id": "msg_68af4033...",
"type": "message",
"status": "completed",
"role": "assistant",
"content": [
{
"type": "output_text",
"text": "東京の天気は晴れです。"
}
]
}
],
"usage": {
"input_tokens": 15,
"output_tokens": 12,
"total_tokens": 27
}
}
2.6 マルチターン会話
Responses API でマルチターン会話を実現する方法は2つあります。
① input に配列で履歴を渡す
Chat Completions API からある従来の方式です。会話履歴をクライアント側で保持・管理し、毎回のリクエストに全メッセージを含めて送信します。
const history = [
{ role: "system", content: "親切なアシスタントです。" },
{ role: "user", content: "東京の天気を教えて" },
{ role: "assistant", content: "東京は今日晴れです。" },
];
const response = await client.responses.create({
model: "gpt-4o",
input: [...history, { role: "user", content: "明日はどうですか?" }],
});
状態はサーバーに残らないため、特定のメッセージを削除したり要約して圧縮したりと、履歴を自由に加工できるのが利点です。ただし会話が長くなるほど入力トークンが増加し、コストとレイテンシの両面で負担になります。
② previous_response_id を渡す(Responses API 専用)
Responses API で新たに導入されたサーバーサイド状態管理の方式です。直前のレスポンス ID を渡すだけで、OpenAI のサーバーが会話履歴を引き継ぎます。
// 1回目
const response1 = await client.responses.create({
model: "gpt-4o",
input: "東京の天気を教えて",
});
// 2回目:previous_response_id だけで文脈が引き継がれる
const response2 = await client.responses.create({
model: "gpt-4o",
input: "明日はどうですか?",
previous_response_id: response1.id,
});
クライアントは履歴全体を保持・送信する必要がなく、リクエストのサイズを小さく保てます。また、o1・o3 などの推論モデルを使う場合は推論トークン(thinking tokens)がターン間で保持されるため、連続した思考が必要なタスクで精度が上がるという大きな利点もあります。
この方式を使うには、レスポンスがサーバーに保存されている必要があります。store パラメータのデフォルトは true ですが、store: false に設定するとIDによる参照ができなくなる点に注意してください。
使い分けの基準
| 状況 | 推奨する方式 |
|---|---|
| 履歴を途中で加工・編集したい(RAG結果の注入、古いメッセージの削除など) | ① 配列渡し |
| 推論モデル(o1・o3など)でマルチターンの複雑な問題を解かせたい | ② previous_response_id |
| シンプルなチャットで履歴管理の手間を省きたい | ② previous_response_id |
| Chat Completions API を使っている | ① 配列渡し(previous_response_id は Responses API 専用) |
永続的な会話管理が必要な場合は Conversations API との連携も可能です。
2.7 組み込みツール
Responses API の最大の利点の1つが、追加インフラなしで使える組み込みツール群です。
const response = await client.responses.create({
model: "gpt-4o",
input: "OpenAI の最新ニュースを調べて要約して",
tools: [
{ type: "web_search_preview" },
{ type: "file_search" },
{ type: "code_interpreter" },
],
});
| ツール | 用途 |
|---|---|
web_search_preview |
ChatGPT と同等のWeb検索 |
file_search |
アップロードファイルへのRAG検索 |
code_interpreter |
コード実行・データ分析 |
computer_use |
コンピューター操作エージェント |
mcp |
サードパーティMCPサーバーへの接続 |
MCP(Model Context Protocol)連携
Connectors は OpenAI がメンテナンスする Google Workspace や Dropbox などの人気サービス向け MCP ラッパーで、Remote MCP servers はリモート MCP プロトコルを実装した公開インターネット上の任意のサーバーです(出典:OpenAI Connectors and MCP ガイド)。
Responses API は Streamable HTTP または HTTP/SSE トランスポートプロトコルをサポートするリモート MCP サーバーと連携できます。ツールを指定すると、API はまずサーバーから利用可能なツール一覧を取得し(mcp_list_tools)、モデルがその中から必要なツールを呼び出します。
基本的な接続例(出典:OpenAI Using tools ガイド)
const response = await client.responses.create({
model: "gpt-4o",
input: "2d6のダイスを振って結果を教えて",
tools: [
{
type: "mcp",
server_label: "dice_server", // サーバーの識別名(任意)
server_url: "https://example.com/mcp", // MCP サーバーの URL
require_approval: "never", // ツール呼び出しを自動承認
},
],
});
console.log(response.output_text);
require_approval による承認制御
デフォルトではすべてのツール呼び出しに開発者の明示的な承認が必要です。require_approval で動作を制御できます(出典:OpenAI Connectors and MCP ガイド)。
const response = await client.responses.create({
model: "gpt-4o",
input: "MCP仕様のトランスポートプロトコルについて教えて",
tools: [
{
type: "mcp",
server_label: "deepwiki",
server_url: "https://mcp.deepwiki.com/mcp",
require_approval: {
never: {
// この2つのツールは承認不要、それ以外は承認が必要
tool_names: ["ask_question", "read_wiki_structure"],
},
},
},
],
});
require_approval の値 |
動作 |
|---|---|
"never" |
すべてのツール呼び出しを自動承認 |
{ never: { tool_names: [...] } } |
指定したツールのみ自動承認、それ以外は承認が必要 |
| 省略(デフォルト) | すべてのツール呼び出しに承認が必要 |
認証が必要なサーバーへの接続
MCP サーバーが認証を要求する場合は headers パラメータでトークンを渡します。
const response = await client.responses.create({
model: "gpt-4o",
input: "データを取得して",
tools: [
{
type: "mcp",
server_label: "my_server",
server_url: "https://my-mcp-server.example.com/mcp",
require_approval: "never",
headers: {
Authorization: `Bearer ${process.env.MCP_ACCESS_TOKEN}`,
},
},
],
});
ツール一覧のキャッシュ: MCP サーバーからのツール一覧取得(
mcp_list_tools)はリクエストごとに発生しますが、previous_response_idを使ったマルチターン会話では前のレスポンスにツール一覧が含まれているため、再取得がスキップされます(出典:OpenAI Cookbook: MCP Tool Guide)。
2.8 主なリクエストパラメータ
| パラメータ | 型 | 説明 |
|---|---|---|
model |
string | 使用するモデル名(例:gpt-4o) |
input |
string / array | テキストまたはマルチモーダルな入力 |
previous_response_id |
string | 前の会話IDを指定してマルチターン |
tools |
array | 使用するツールの定義 |
text.format |
object | 構造化出力の指定(後述) |
stream |
boolean | ストリーミング有効化 |
store |
boolean | レスポンスをサーバーに保存するか(デフォルト: true) |
reasoning_effort |
string | 推論深度の調整(low / medium / high) |
background |
boolean | バックグラウンドモードで非同期実行 |
2.9 Chat Completions との対応表
| 機能 | Chat Completions | Responses API |
|---|---|---|
| 会話状態管理 | クライアント側で全履歴を保持 | サーバーサイドで管理(previous_response_id) |
| Web検索 | 別途実装が必要 | 組み込みツール(web_search_preview) |
| ファイル検索・RAG | 別途実装が必要 | 組み込みツール(file_search) |
| コード実行 | 別途実装が必要 | 組み込みツール(code_interpreter) |
| MCP接続 | 非対応 | リモートMCPをネイティブサポート |
| 推論トークン保持 | ターン間で破棄 | 保持可能 |
output_text ヘルパー |
なし | あり |
| 構造化出力の指定先 | response_format |
text.format |
| 新機能投入 | 限定的 | 主要投入先 |
2.10 推論モデル(o シリーズ)
OpenAI には GPT シリーズとは別に、推論モデル(Reasoning Models) と呼ばれるモデル群があります。回答を生成する前に内部で段階的な思考プロセス(Chain-of-Thought)を実行するのが特徴で、この内部思考は reasoning tokens(推論トークン) としてカウントされますが、最終的な出力には含まれません。
数学・コーディング・論理推論・複雑な分析といった多段階の推論を要するタスクで GPT-4o を大きく上回る精度を発揮します。一方で思考に時間がかかるためレイテンシは高く、コストも高めです。
2025年時点の主なモデルは以下のとおりです。
| モデル | 特徴 |
|---|---|
o1 / o1-mini |
初代推論モデルシリーズ |
o3 / o3-mini |
高精度・高性能な後継シリーズ |
o4-mini |
コストと性能のバランスを取ったモデル |
推論の深さは reasoning_effort パラメータ(2.8参照)で調整できます。low はレイテンシとコストを抑えた軽量な推論、high は最大限の精度を発揮する深い推論です。
const response = await client.responses.create({
model: "o3",
input: "この数列の一般項を求めてください:1, 1, 2, 3, 5, 8, 13, ...",
reasoning_effort: "high",
});
また、previous_response_id を使ったマルチターン会話(2.6②参照)では、推論トークンがターン間で保持されます。同じ問題を複数ターンにわたって掘り下げる場合に、毎回ゼロから思考し直すのではなく前のターンの推論を引き継ぐため、精度と効率の両面で有利です。
3. Structured Outputs(構造化出力)
3.1 概要と背景
LLMの出力を確実にJSON形式で返させることは、アプリケーション統合の重要な課題でした。OpenAIはこれを以下のように段階的に解決してきました。
JSON mode(旧機能) は構文的に正しいJSONを生成することを保証しますが、スキーマへの準拠は保証しません。指定したフィールドが欠落したり、余分なフィールドが追加されたりするリスクがありました。
Structured Outputs は2024年8月にリリースされ、開発者が指定したJSONスキーマへの完全準拠を保証します(出典:OpenAI公式ブログ)。
OpenAI社内の評価(eval)によると、gpt-4o-2024-08-06 は Structured Outputs を使った複雑なJSONスキーマへの準拠率が100%に達しており、gpt-4-0613 の40%未満と比較して大幅に向上しています(出典:OpenAI公式ブログ)。
3.2 仕組み
OpenAI APIは、指定された JSON Schema をコンテキストフリー文法(CFG)に変換することで構造化出力を実現しています。この文法によってサンプリング時に生成可能なトークンを制約し、スキーマ準拠を強制します。このため、新しいスキーマを初めて送信した際は文法の前処理コストとして追加レイテンシが発生しますが、同じスキーマでの2回目以降のリクエストにはこのペナルティはかかりません。
注意(Fine-tuned モデル): Fine-tuned モデルでは、新しいスキーマを使った最初のリクエストに追加レイテンシが発生します。その後の同スキーマでのリクエストには発生しません。他のモデルにはこの制限はありません。(出典:Structured Outputs ガイド)
3.3 利用形態:2つの方法
Structured Outputs は API 上で2つの形式で提供されています。
1つ目は Function calling(ツール)経由 で、strict: true を関数定義内に設定することで有効になります。これはすべての gpt-4-0613 以降のモデルで利用可能です。これはアプリケーションとモデルの機能をつなぐ用途に適しています(例:データベースクエリ関数へのアクセス)。
2つ目は response_format / text.format パラメータ経由 で、json_schema を指定することでモデルがユーザーに構造化された形式で応答する場合に適しています(例:数学チュートリアルUIのように異なる部分を別々に表示したい場合)。
3.4 実装例(Responses API)
Responses API では response_format から text.format に移動しています(出典:公式マイグレーションガイド)。
スキーマの description について
JSON Schema の各フィールドに description を付与することを強く推奨します。description はモデルへの指示として機能し、そのフィールドに何を入れるべきかをモデルが正しく判断するための手がかりになります。特に explanation や output のような汎用的な名前のフィールドは、description がないとモデルが意図を誤解しやすくなります。
Zod を使う場合は .describe("...") で、JSON Schema を直接書く場合はプロパティオブジェクト内の "description" キーで指定します。
Zod を使ったスキーマ定義(推奨)
import OpenAI from "openai";
import { zodResponseFormat } from "openai/helpers/zod";
import { z } from "zod";
const client = new OpenAI();
const Step = z.object({
explanation: z.string().describe("その計算ステップで何をしているかの説明"),
output: z.string().describe("そのステップの計算結果(式や数値)"),
});
const MathResponse = z.object({
steps: z.array(Step).describe("解法のステップをリストで列挙する"),
final_answer: z.string().describe("方程式の最終的な答え(例:x = -3.75)"),
});
const response = await client.responses.parse({
model: "gpt-4o",
input: [
{ role: "system", content: "数学の家庭教師です。ステップごとに解説してください。" },
{ role: "user", content: "8x + 7 = -23 を解いて" },
],
text: { format: zodResponseFormat(MathResponse, "math_response") },
});
const result = response.output_parsed;
console.log(result.final_answer);
for (const step of result.steps) {
console.log(step.explanation, "→", step.output);
}
JSON Schema を直接指定する場合
const response = await client.responses.create({
model: "gpt-4o",
input: [
{ role: "system", content: "数学の家庭教師です。" },
{ role: "user", content: "8x + 7 = -23 を解いて" },
],
text: {
format: {
type: "json_schema",
name: "math_response",
strict: true,
schema: {
type: "object",
description: "方程式の解法を段階的に示すレスポンス",
properties: {
steps: {
type: "array",
description: "解法のステップをリストで列挙する",
items: {
type: "object",
properties: {
explanation: {
type: "string",
description: "その計算ステップで何をしているかの説明",
},
output: {
type: "string",
description: "そのステップの計算結果(式や数値)",
},
},
required: ["explanation", "output"],
additionalProperties: false,
},
},
final_answer: {
type: "string",
description: "方程式の最終的な答え(例:x = -3.75)",
},
},
required: ["steps", "final_answer"],
additionalProperties: false,
},
},
},
});
注意: Responses API での正しいフィールドは
text.formatです。古いresponse_formatキーは Responses API では非推奨となっています(出典:OpenAI Developer Community)。
3.5 Function Calling での Structured Outputs
ツール呼び出しに Structured Outputs を適用する場合は strict: true を関数定義に追加します。
const response = await client.responses.create({
model: "gpt-4o",
input: "注文 #12345 の配送日を教えてください",
tools: [
{
type: "function",
name: "get_delivery_date",
description: "注文の配送予定日を取得する",
strict: true,
parameters: {
type: "object",
properties: {
order_id: { type: "string" },
},
required: ["order_id"],
additionalProperties: false,
},
},
],
});
制限: Structured Outputs を Function Calling に使用する場合、
parallel_tool_callsはfalseに設定する必要があります。
3.6 Structured Outputs が保証すること・しないこと
| 項目 | 状態 | 補足 |
|---|---|---|
| JSON構文の正しさ | ✅ 保証 | — |
| 指定スキーマへの準拠 | ✅ 保証(strict: true時) |
— |
| 必須フィールドの存在 | ✅ 保証 | — |
| enumに指定した値の使用 | ✅ 保証 | — |
| 事実的な正しさ | ❌ 非保証 | スキーマと無関係な入力に対してはハルシネーションが発生しうる |
| 安全ポリシーの適用外 | ❌ 非保証 | 安全上の理由でモデルが refusal を返すことがある |
refusal の処理例:
const response = await client.responses.parse({
model: "gpt-4o",
input: [/* ... */],
text: { format: zodResponseFormat(MathResponse, "math_response") },
});
if (response.output[0].content[0].type === "refusal") {
console.log("モデルが拒否しました:", response.output[0].content[0].refusal);
} else {
const result = response.output_parsed;
}
3.7 strict モードのスキーマ制約
strict: true モードでは JSON Schema の一部機能が制限されます(出典:Structured Outputs ガイド)。
additionalProperties: falseが必須- すべてのプロパティを
required配列に含める必要あり - ルートオブジェクトへの
anyOfの直接使用は不可 oneOf、anyOfなどの組み合わせには制約あり
スキーマと型定義の乖離を防ぐため、Zod のネイティブサポートを使ったSDK経由での利用が公式に強く推奨されています。
4. Realtime API
2025年に一般提供(GA)された Realtime API は、WebRTC・WebSocket・SIP の3方式で接続できる低レイテンシの双方向音声・テキストストリーミングのためのAPIです。ブラウザから直接つなぐ音声エージェントや、電話システム(PBX)との統合といったリアルタイム対話のユースケースに特化しており、Responses API とは用途が明確に分かれています。詳細は公式 Realtime API ガイドを参照してください。
5. ストリーミング
Responses API は Server-Sent Events(SSE)形式でのストリーミングに対応しており、長い応答を逐次受け取ることができます。
const stream = await client.responses.stream({
model: "gpt-4o",
input: "宇宙の始まりについて詳しく教えて",
});
for await (const event of stream) {
if (
event.type === "response.output_text.delta" &&
event.delta
) {
process.stdout.write(event.delta);
}
}
Structured Outputs とストリーミングを組み合わせることも可能で、その場合は最終的に完全なスキーマ準拠JSONが返ります。
6. HTTPレスポンスヘッダー
APIレスポンスにはデバッグ・レート監視に有用なヘッダーが含まれます。
| ヘッダー | 内容 |
|---|---|
x-request-id |
リクエストの一意ID(サポート問い合わせ時に必要) |
x-ratelimit-limit-requests |
現在適用されているRPM上限 |
x-ratelimit-limit-tokens |
現在適用されているTPM上限 |
x-ratelimit-remaining-requests |
残りリクエスト数 |
x-ratelimit-remaining-tokens |
残りトークン数 |
x-ratelimit-reset-requests |
RPMリセットまでの時間 |
x-ratelimit-reset-tokens |
TPMリセットまでの時間 |
クライアント側からリクエストIDを指定したい場合は X-Client-Request-Id ヘッダーを付与できます。
7. レート制限
レート制限は組織・プロジェクト単位(ユーザー単位ではない)で適用されます。
- RPM(Requests Per Minute): 1分あたりのリクエスト数
- TPM(Tokens Per Minute): 1分あたりのトークン数
使用ティアはアカウントの累積支払い額と使用履歴に基づいて自動的に昇格します。429 Too Many Requests が返った場合は指数バックオフでリトライすることが推奨されています(出典:Rate Limits ガイド)。
import retry from "async-retry";
async function callApiWithBackoff(params) {
return retry(
async () => {
return await client.responses.create(params);
},
{
retries: 6,
minTimeout: 1000,
maxTimeout: 60000,
randomize: true,
}
);
}
8. バージョン安定性とモデルのピン留め
gpt-4o などのエイリアスは定期的に新スナップショットへ更新されるため、同じプロンプトでも出力が変わる可能性があります。本番環境では gpt-4o-2024-08-06 のようにスナップショット名をピン留めし、更新の際は必ず評価(eval)を実施することが推奨されています。
まとめ
| ポイント | 内容 |
|---|---|
| 新規プロジェクト | Responses API を使う(POST /v1/responses) |
| Chat Completions | 引き続きサポート継続。急いで移行する必要はない |
| Assistants API | 2026年8月26日廃止。Responses API へ移行推奨 |
| 構造化出力 | strict: true + additionalProperties: false で100%スキーマ準拠 |
| Responses API での指定 | response_format ではなく text.format を使う |
| リアルタイム音声 | Realtime API(WebRTC / WebSocket / SIP)を参照 |
| レート制限対策 | レスポンスヘッダーを監視し、指数バックオフでリトライ |
| モデルバージョン | 本番はスナップショット名をピン留めしてevalを実施 |
参考リンク
- Responses API マイグレーションガイド — 公式マイグレーションガイド
- Structured Outputs ガイド — 公式Structured Outputsガイド
- Introducing Structured Outputs in the API — リリース時の公式ブログ
- Realtime API ガイド — リアルタイム双方向音声・テキストの詳細
- Rate Limits ガイド — レート制限の詳細
- OpenAI Changelog — APIの更新履歴
