はじめに
この記事では、ChatGPTから利用するo3(現在はGPT-5 Thinking)を、APIを使ったカスタムアプリケーションで再現しようとします。
- 記事の要旨からo3と記載していますが、気になる方はo1, GPT-5 Thinking等で読み替えてください。
- 個人の理解を整理した記事ですので、内容に誤りなどあればコメントいただけると幸いです。
目次
背景
8月にGPT-5が発表されましたが、それに関連したアルトマンのこのツイートが少し話題になりました。有料のPlusプランでも、推論モデルを使うユーザーが7%しかいなかったからです。
o3を普段から活用しているユーザーたちからすると、この低さは意外でした。
機械翻訳:推論モデルを毎日利用するユーザーの割合は大幅に増加しています。例えば、無料ユーザーでは1%未満から7%に、Plusユーザーでは7%から24%に増加しました。
では、o3はどのような仕組みなのか、何が便利なのか、改めて整理してみます。
o3の仕組み
Reasoning Token
OpenAIのドキュメントでは、推論モデルは Reasoning model と記載されます。
推論モデルは Reasoning Token(推論トークン)を使い思考します。非推論モデルでは、LLMの入出力でトークン量が決まりますが、推論モデルでは入出力に加えて「思考」にもトークンがかかるということです。
問題の複雑さによっては高度な思考を行いますが、その分膨大な推論トークンが生成されます。また、推論トークンを利用するために、十分なコンテキストウィンドウを確保する必要もあります。
参考:Reasoning models - OpenAI API
ChatGPTのo3はツールを活用する
o3は推論モデルとして高度な推論プロセスを経て、行動と計画を繰り返してユーザーのクエリに回答します。特に、ChatGPT上で利用するo3は、ChatGPT上に用意された様々なツールを駆使します。例として、Web searchではLLMの学習範囲外のデータを補い、Code InterpreterはLLMが不得意とする計算を決定論的に行います。結果として、高い精度での回答を可能にしています。
OpenAIのAPIで利用できるツールの例としては以下があります。
| ツール例 | 概要 |
|---|---|
| Function calling | モデルがカスタムコードを呼び出せる(正確には、呼び出しのためのパラメータを推論できる) |
| Web search | インターネット検索の結果を生成に利用できる |
| File search | アップロードされたファイル内を検索してコンテキストとして利用できる |
| Code Interpreter | モデルが隔離された環境でコードを実行できる |
また、OpenAI公式にも以下の通り紹介されています。
OpenAI o3 および o4-mini は ChatGPT 内の全ツールにアクセスできます。API の Function Calling でユーザー独自のカスタムツールも使用できます。これらのモデルは、問題を解決するために、いつ、どのようにツールを使用するかを論理的に思考し、詳細で思慮深い答えを通常1分以内に適切な出力形式で生成するよう学習しています。
引用元:OpenAI o3 と o4-mini が登場 | OpenAI
推論プロセスの可視化
もう一つ、ChatGPT o3の特徴として、推論プロセスがUIに表示されることです。デフォルトは閉じられていますが、展開すればLLMの推論やツール利用のプロセスを確認できます。LLMの推論をトレース・デバッグするには有効です。
ChatGPTとAPIのo3は異なる
一方、APIからo3を利用する場合、ツールを利用したり推論プロセスを出力するにはリクエストパラメータの指定が必要です。例えば、tools(後述)の指定がない場合ツールは利用されないので、ChatGPTのような挙動にはなりません。
この記事では、以下の3つをo3再現の要件として、APIでも同じ挙動をさせようとしてみます。
- 推論を行う
- 自律的にツールを利用する
- 推論プロセスを出力する
OpenAI APIでのo3
推論とツール利用
さっそくですが、推論処理とツール利用で必要になるパラメータ、サンプルコードは以下の通りです。サンプルコードでは、Code Interpreter をツールとして利用するよう指示しています。
| パラメータ | 内容 |
|---|---|
tools |
モデルが呼び出せるツールを配列で指定する |
reasoning.effort |
推論の深さ。デフォルトはmedium。highを指定するとより深く推論する |
reasoning.summary |
モデルが実行した推論プロセスの要約を出力する |
response = client.responses.create(
model="o3",
instructions=(
"あなたは計算結果が必要なときは code_interpreter を使います"
),
input="13の平方根を100桁計算し、小数第20位まで教えて下さい",
reasoning={
"effort": "medium",
"summary": "auto",
},
tools=[
{"type": "code_interpreter", "container": {"type": "auto"}},
],
)
Stream 処理のハンドリング
API利用時に、クライアント側で推論プロセスを可視化するには、出力処理にも工夫が必要です。Responses API で stream=True を指定した場合、回答完了までに様々なイベントがサーバーからクライアントに送信されます。
推論プロセスを可視化するには、これらのイベントをハンドリングします。サンプルコードと合わせて紹介します。
| イベント例 | 内容 |
|---|---|
reasoning_summary_text.done |
推論の要約テキストの生成が完了した時に発行される |
code_interpreter_call_code.done |
Code Interpreter ツールを実行して、コードスニペットが確定した時に発行される |
output_text.done |
モデルのテキスト回答が確定した時に発行される |
completed |
モデルの応答が完了した際に発行される |
出典:Streaming events - OpenAI API
# リクエスト
stream = client.responses.create(
model="o3",
instructions=(
"あなたは計算結果が必要なときは code_interpreter を、"
"最新データが必要なときは web_search を使います。"
"結果には必ず根拠 URL を付けてください。"
),
input=[
{
"role": "user",
"content": "13の平方根を100桁計算し、小数第20位まで教えて下さい",
}
],
reasoning={
"effort": "medium",
"summary": "auto",
},
tools=[
# {"type": "web_search_preview_2025_03_11"},
{"type": "code_interpreter", "container": {"type": "auto"}},
],
# streamを指定
stream=True,
)
# Stream に含まれるイベントに応じて出力
for event in stream:
if event.type == "response.reasoning_summary_text.done":
print("===== 思考中です... =====\n")
print(f"{event.text}\n")
if event.type == "response.code_interpreter_call_code.done":
print("===== Code Interpreterを使い、コードを実行します =====\n")
print(event.code)
if event.type == "response.output_text.done":
print("===== 生成結果 =====\n")
print(f"{event.text}\n")
if event.type == "response.completed":
print("===== 回答が完了しました =====")
コード全体はこちら
import os
from openai import OpenAI
os.environ["OPENAI_API_KEY"] = "your-openai-api-key"
client = OpenAI()
stream = client.responses.create(
model="o3",
instructions=(
"あなたは計算結果が必要なときは code_interpreter を、"
"最新データが必要なときは web_search を使います。"
"結果には必ず根拠 URL を付けてください。"
),
input=[
{
"role": "user",
"content": "13の平方根を100桁計算し、小数第20位まで教えて下さい",
}
],
reasoning={
"effort": "medium",
"summary": "auto",
},
tools=[
# {"type": "web_search_preview_2025_03_11"},
{"type": "code_interpreter", "container": {"type": "auto"}},
],
# 途中経過を逐次受け取りたい場合は stream=True
stream=True,
)
for event in stream:
if event.type == "response.reasoning_summary_text.done":
print("===== 思考中です... =====\n")
print(f"{event.text}\n")
if event.type == "response.code_interpreter_call_code.done":
print("===== Code Interpreterを使い、コードを実行します =====\n")
print(event.code)
if event.type == "response.output_text.done":
print("===== 生成結果 =====\n")
print(f"{event.text}\n")
if event.type == "response.completed":
print("===== 回答が完了しました =====")
こちらを実行すると以下のような結果となります。各イベントに合わせたメッセージが出力されています。また、Code Interpreter で生成されたコードも出力されています。
===== Code Interpreterを使い、コードを実行します =====
from decimal import Decimal, getcontext
getcontext().prec = 110
sqrt13 = Decimal(13).sqrt()
sqrt13_str = format(sqrt13, 'f')
sqrt13_str[:120]
===== Code Interpreterを使い、コードを実行します =====
decimal_digits = sqrt13_str.split('.')[1]
decimal_digits[:100], len(decimal_digits)
===== Code Interpreterを使い、コードを実行します =====
decimal_digits[19]
===== 生成結果 =====
√13(13 の平方根)を小数点以下 100 桁まで計算すると
3.6055512754639892931192212674704959462512965738452462127104530562271669482930104452046190820184907176
となります。
・小数第 20 位(小数点の直後を 1 位と数える)の数字は「1」です。
根拠 URL(√13 の高精度展開が掲載されています):https://oeis.org/A010469
===== 回答が完了しました =====
トークン利用量の確認
推論モデルのトークン利用量は気になるところです。stream=True の場合、完了時に発行されるイベント response.completed の response に最終的なトークン利用量が含まれるので、これを出力します。
{
"type": "response.completed",
"response": {
...省略...
"usage": {
"input_tokens": 0,
"output_tokens": 0,
"output_tokens_details": {
"reasoning_tokens": 0
},
"total_tokens": 0
},
...省略...
},
...省略...
}
出典:response.completed - OpenAI API
if event.type == "response.completed":
print("===== 回答が完了しました =====")
# トークン利用量
usage = event.response.usage
final_usage = {
"input_tokens": usage.input_tokens,
"output_tokens": usage.output_tokens,
"reasoning_tokens": usage.output_tokens_details.reasoning_tokens,
"total_tokens": usage.total_tokens,
}
print("===== トークン利用量 =====")
for key, value in final_usage.items():
print(f"{key}: {value}")
APIのレート制限
実は先程のサンプルコードでは、Web search のツールをコメントアウトしていました。
tools=[
# {"type": "web_search_preview_2025_03_11"},
{"type": "code_interpreter", "container": {"type": "auto"}},
],
これは、OpenAI APIのレート制限でエラーになるのを予防するためです。
OpenAI APIでは、利用額に応じてティアが決まります。このティアに応じて、APIのレート制限が決まります。以下表の通り、Tier 1 では TPM (Tokens Per Minute) は30,000となります。
Web search と Code Interpreter の両方を使うような質問をすると、より多くのトークンが消費されます。同様に、Reasoning Token も多く消費されます。
特に、Web searchは検索結果がモデルの入力になるため、入力トークンが大量に消化されます。結果、TPM のレート制限でエラーになる可能性が高まるわけです。
実際にレート制限でエラーとなった場合、以下のようなエラーメッセージが出力されます。
openai.APIError: Rate limit reached for o3 in organization org-xxxxxxx on
tokens per min (TPM): Limit 30000, Used 24368, Requested 11344.
Please try again in 11.424s.
Visit https://platform.openai.com/account/rate-limits to learn more.
# 読みやすさのため改行を入れています。
レート制限に到達した場合、一般的にはリトライが対策として考えられます。OpenAIのドキュメントでも、クライアントからの指数バックオフ(exponential backoff)が推奨されています。しかし、推論プロセスの途中でエラーに到達すると、クライアントからリトライを挟む余地がありません。こういったケースでのレート制限の対策としては、利用額を上げてティアを上げることしか無いと考えられます。
以下表の通り、Tier 1 から Tier 2 に上げるには 初回の支払いが正常に完了してから7日以上経過しており、支払い金額が$50の場合 が条件となります。
個人利用でこれだけの利用額は躊躇するのが正直なところです。もう少しお手軽に試せると良いのですが...
Responses API
話が前後しますが、OpenAIは2025/3にResponses APIを発表しました。Chat Completions や Assistants API に馴染みの方も多いと思いますが、今後は Responses API が主流になるためこちらの利用が推奨されています。特に、Assistants API については、2026/8にサポート終了が予定されているため注意が必要です。
また、OpenAIの Reasoning best practices のドキュメントでも、Responses API の利用が推奨されていますので、新規にAPIを利用するときはこちらを採用しましょう。
参考:Assistants migration guide - OpenAI API
o3は優秀な検索エージェント
上記のように、APIでChatGPTの挙動を再現するにはハードルがあることもわかりました。その分、ChatGPTからo3(現在はGPT-5 Thinking)を活用できるのは、ありがたさを感じます。
ChatGPTから利用するo3は、複雑な調べごとを依頼しても思考しながらツールを使いつつ妥当な回答をしてくれます。個人的な印象ですが、自分で様々な検索キーワードを使ってWebを巡って辿り着いた内容と、回答内容にそこまで違いが無いように思えます。依然として回答が不十分なケースもありますが、o3は検索エージェントの優れた実装というのが、個人的な印象です。
GPT-5の登場で、問題の複雑さにより自動で「GPT-5 Thinking」が使われるようになりました。4oに比べればレスポンスが遅くなるため不満を感じるユーザーもいるでしょうが、その性能の高さを感じる人も増えるのではないでしょうか。
参考:ChatGPT o3 - The First Reasoning Agentic Model - Sid Bharath
Azure OpenAIから利用する
o3みたいな業務アプリケーションを作りたい
当初のモチベーションはこれです。
企業内でRAGアプリを構築しているケースは多いと思います。また、ChatGPT Enterpriseを業務で利用する企業も増えていると思います。特にコネクターを利用すれば、Google DriveやNotion上の企業データと接続することも可能です。
ただ、独自で開発したRAGアプリケーション内でo3のような挙動を再現したくなるケースもあるのではないでしょうか。社内情報をWeb Searchさせてしまうとセキュリティ的な懸念は生じますが、その懸念がない場合は一つの有効な手段になり得ます。
これをo3で実現する方法を考えます。
Azure OpenAIの制約
業務利用となると、Azure OpenAI (以下、AOAI) を利用するケースが多いと考えられます。但し、AOAIのResponase APIでは、特定のツールや機能は利用できません。2025/9時点で利用できないツール・機能は以下の通りです。
| 機能 | 備考 |
|---|---|
| Web Search | 公開予定の記載無し |
| マルチターン編集とストリーミング機能を利用した画像生成 | 近日公開予定 |
| 画像ファイルを直接アップロードした入力 | 近日公開予定 |
この中で、Web Search ツールのみ「近日公開予定(coming soon)」の記載が無く、またBing Search APIが廃止されることも考慮すると、当面の間は組み込みツールとして提供されないのかと勘ぐってしまいます。
社内情報から検索する用途の場合、Web Searchは不要かもしれません。ただ、Responses APIでWeb Searchを使いたい場合には、カスタム(自前)のWeb Searchツール、またはMCPサーバーを用意する等の対応が必要となります。
tools の補足
リクエストパラメータのtoolsで指定できるツールには以下の3種類があります。
| ツール名 | 内容 |
|---|---|
| 組み込みツール(Built-in tools) | OpenAI / AOAI が標準で提供するツール |
| MCPツール(MCP Tools) | サードパーティまたはカスタムのMCPサーバーが提供するツール |
| 関数呼び出し(Function calls) | ユーザーが開発したカスタムコードが提供するツール |
先ほど説明した『AOAIでWeb Searchツールが利用できない』というのは、組み込みツールとして提供されていないという趣旨です。そのため、MCPツールまたは関数呼び出しを使えば、AOAIでもWeb Searchが利用可能となります。
関数呼び出しは Function calling と記載されるドキュメントも多いので、以降はそう記載します。
AOAI でWeb Searchを利用する
OpenAI API と主要部分のコードは変わりませんが、Web Searchについてはtoolsの指定方法が変わります。今回はTavilyのリモートMCPサーバーを指定しています。
なお、単純化するためWeb Search以外に利用するツールはCode Interpreterのみとします。
TAVILY_API_KEY="your-api-key"
stream = client.responses.create(
model=DEPLOYMENT_ID,
instructions=(
"あなたは計算結果が必要なときは code_interpreter を、"
"最新データが必要なときは web_search を使います。"
"結果には必ず根拠 URL を付けてください。"
),
input="Azure OpenAI o3モデルで20万トークン使った場合の料金は日本円でいくらですか?2025/9/1の為替レートを使ってください",
reasoning={
"effort": "medium",
"summary": "auto",
},
tools=[
{"type": "code_interpreter", "container": {"type": "auto"}},
# Tavily MCPの設定
{
"type": "mcp",
"server_label": "tavily",
"server_url": f"https://mcp.tavily.com/mcp/?tavilyApiKey={TAVILY_API_KEY}",
"require_approval": "never",
},
],
stream=True,
)
for event in stream:
# MCP 関係のイベント(ドキュメント参照)
# https://platform.openai.com/docs/api-reference/responses_streaming/response/mcp_call
if event.type == "response.mcp_call_arguments.done":
print("===== MCPツールを実行します =====\n")
print(f"ツール:{event.item_id} 引数:{event.arguments}\n")
if event.type == "response.mcp_call.completed":
print("===== MCPツールの実行が完了しました =====\n")
print(f"{event.item_id}\n")
参考:Tavily MCP Server - Tavily Docs
コード全体はこちら
from openai import AzureOpenAI
ENDPOINT = "your-endpoint"
DEPLOYMENT_ID = "your-deployment-id"
AOAI_API_KEY = "your-aoai-api-key"
API_VERSION = "2025-04-01-preview"
TAVILY_API_KEY = "your-tavily-api-key"
client = AzureOpenAI(
api_key=AOAI_API_KEY,
api_version=API_VERSION,
azure_endpoint=ENDPOINT,
)
# https://platform.openai.com/docs/api-reference/responses/create
stream = client.responses.create(
model=DEPLOYMENT_ID,
instructions=(
"あなたは計算結果が必要なときは code_interpreter を、"
"最新データが必要なときは web_search を使います。"
"結果には必ず根拠 URL を付けてください。"
),
input="Azure OpenAI o3モデルで20万トークン使った場合の料金は日本円でいくらですか?2025/9/1の為替レートを使ってください",
reasoning={
"effort": "medium",
"summary": "auto",
},
tools=[
{"type": "code_interpreter", "container": {"type": "auto"}},
{
"type": "mcp",
"server_label": "tavily",
"server_url": f"https://mcp.tavily.com/mcp/?tavilyApiKey={TAVILY_API_KEY}",
"require_approval": "never",
},
],
stream=True,
)
for event in stream:
if event.type == "response.reasoning_summary_text.done":
print("===== 思考中です... =====\n")
print(f"{event.text}\n")
if event.type == "response.code_interpreter_call_code.done":
print("===== Code Interpreterを使い、コードを実行します =====\n")
print(event.code)
if event.type == "response.mcp_call_arguments.done":
print("===== MCPツールを実行します =====\n")
print(f"ツール:{event.item_id} 引数:{event.arguments}\n")
if event.type == "response.mcp_call.completed":
print("===== MCPツールの実行が完了しました =====\n")
print(f"{event.item_id}\n")
if event.type == "response.output_text.done":
print("===== 生成結果 =====\n")
print(f"{event.text}\n")
if event.type == "response.completed":
print("===== 回答が完了しました =====")
こちらを実行すると以下のような結果が出力されました。MCPによる検索とCode Interpreterを使い回答していることがわかります。
プロンプトが曖昧でしたが、前提を置きつつ回答してくれました。
===== 思考中です... =====
**Calculating costs in Yen**
I need to determine the cost in Japanese Yen for using the Azure OpenAI o3 model, which may actually refer to the gpt-4o. There's some confusion about whether o3 and gpt-4o are interchangeable. The current pricing from Microsoft shows $5 per million tokens for input and $15 for output for gpt-4o (preview). I’ll convert this to Japanese Yen using the exchange rate for September 1, 2025, to get the final cost.
===== MCPツールを実行します =====
ツール:mcp_xxx 引数:{"query": "Azure OpenAI GPT-4o pricing input output tokens", "max_results": 5, "search_depth": "basic", "topic": "news", "days": 365, "time_range": "year", "include_images": false}
===== MCPツールの実行が完了しました =====
mcp_xxx
# 略
===== Code Interpreterを使い、コードを実行します =====
rate=145.95
jpy_input=usd_cost_input*rate
jpy_output=usd_cost_output*rate
jpy_half=usd_cost_half*rate
jpy_input, jpy_output, jpy_half
===== 生成結果 =====
【前提】
1. 料金単価
Azure OpenAI Service の o3 (Global SKU, 2025-04-16)
- 入力: US$2 / 1 M tokens
- 出力: US$8 / 1 M tokens
出典: Microsoft Azure OpenAI Service 価格ページ
https://azure.microsoft.com/en-us/pricing/details/cognitive-services/openai-service/
2. 為替レート
2025/9/1 の USD/JPY 予想レート 1 USD = 145.95 JPY
出典: RoboForex 為替レポート(USDJPY forecast for 1 September 2025: 145.95)
https://roboforex.com/beginners/analytics/forex-forecast/currencies/usd-jpy-forecast-2025-09-01/
3. トークン量
200,000 tokens = 0.2 M tokens
【計算】
● すべて入力トークンの場合
US$2 × 0.2 M = US$0.40
0.40 × 145.95 JPY ≒ 58.4 円
● すべて出力トークンの場合
US$8 × 0.2 M = US$1.60
1.60 × 145.95 JPY ≒ 233.5 円
● 入力:出力 = 1:1(各 100,000 tokens)と仮定した場合
(US$2 × 0.1 M) + (US$8 × 0.1 M) = US$1.00
1.00 × 145.95 JPY = 145.9 円
【回答】
・o3 モデルで 20 万トークンをすべて「入力」に使った場合:約 58 円
・すべて「出力」に使った場合:約 234 円
・入力と出力が半々なら:約 146 円
実際の料金は、入力/出力トークンの割合やリージョン(Data Zone, Regional では数%上乗せ)により変動しますが、上記が目安となります。
なお、トークン利用量は以下の通りでした。Web Searchの結果を都度LLMが入力にしているため、Input Tokens が多くなっています。
また、10万トークンを超えていますので、OpenAI APIではレート制限に到達しそうです。
===== トークン利用量 =====
input_tokens: 114079
output_tokens: 1950
reasoning_tokens: 1088
total_tokens: 116029
Function calling 利用時の注意
Function calling で Web Searchを利用したい場合、ユーザーが作成したカスタムコードの関数を利用します。この場合、Web Search用関数の呼び出しはユーザー側のコードで行う必要があります。平たく言えば、LLMがよしなにWeb検索を実行してくれるわけではありません。
出典:Function calling - OpenAI API
ユーザー側で関数を実行するには、Stream 処理で送られるイベントをハンドルする必要があります。以下のようなイベントがあるので、これらを利用すると考えられます。
(Function calling は未検証です。ご了承ください。)
response.function_call_arguments.deltaresponse.function_call_arguments.doneresponse.custom_tool_call_input.deltaresponse.custom_tool_call_input.done
おわりに
OpenAI および Azure OpenAI のAPIを使って、ChatGPTのo3のような挙動を再現してみました。思ったより長くなってしまいましたが、この記事がどなたかの参考になれば幸いです。
読んでいただきありがとうございました。



