10
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

o3を再現しようとしてみる

Last updated at Posted at 2025-09-10

はじめに

この記事では、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)]
推論プロセスの例.png

ChatGPTとAPIのo3は異なる

一方、APIからo3を利用する場合、ツールを利用したり推論プロセスを出力するにはリクエストパラメータの指定が必要です。例えば、tools(後述)の指定がない場合ツールは利用されないので、ChatGPTのような挙動にはなりません。

この記事では、以下の3つをo3再現の要件として、APIでも同じ挙動をさせようとしてみます。

  • 推論を行う
  • 自律的にツールを利用する
  • 推論プロセスを出力する

OpenAI APIでのo3

推論とツール利用

さっそくですが、推論処理とツール利用で必要になるパラメータ、サンプルコードは以下の通りです。サンプルコードでは、Code Interpreter をツールとして利用するよう指示しています。

パラメータ 内容
tools モデルが呼び出せるツールを配列で指定する
reasoning.effort 推論の深さ。デフォルトはmediumhighを指定するとより深く推論する
reasoning.summary モデルが実行した推論プロセスの要約を出力する

出典:API Reference - OpenAI API

推論・ツール利用のサンプルコード
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 APIstream=True を指定した場合、回答完了までに様々なイベントがサーバーからクライアントに送信されます。

推論プロセスを可視化するには、これらのイベントをハンドリングします。サンプルコードと合わせて紹介します。

イベント例 内容
reasoning_summary_text.done 推論の要約テキストの生成が完了した時に発行される
code_interpreter_call_code.done Code Interpreter ツールを実行して、コードスニペットが確定した時に発行される
output_text.done モデルのテキスト回答が確定した時に発行される
completed モデルの応答が完了した際に発行される

出典:Streaming events - OpenAI API

推論・ツール利用+Stream処理のサンプルコード
# リクエスト
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.completedresponse に最終的なトークン利用量が含まれるので、これを出力します。

response.completed の event
{
  "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となります。

image.png

出典:o3 - OpenAI API

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の場合 が条件となります。
個人利用でこれだけの利用額は躊躇するのが正直なところです。もう少しお手軽に試せると良いのですが...

image.png

出典:Usage tiers - OpenAI API

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 公開予定の記載無し
マルチターン編集とストリーミング機能を利用した画像生成 近日公開予定
画像ファイルを直接アップロードした入力 近日公開予定

出典:Azure OpenAI Responses API

この中で、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) ユーザーが開発したカスタムコードが提供するツール

出典:API Reference - OpenAI API

先ほど説明した『AOAIでWeb Searchツールが利用できない』というのは、組み込みツールとして提供されていないという趣旨です。そのため、MCPツールまたは関数呼び出しを使えば、AOAIでもWeb Searchが利用可能となります。

関数呼び出しは Function calling と記載されるドキュメントも多いので、以降はそう記載します。

AOAI でWeb Searchを利用する

OpenAI API と主要部分のコードは変わりませんが、Web Searchについてはtoolsの指定方法が変わります。今回はTavilyのリモートMCPサーバーを指定しています。

なお、単純化するためWeb Search以外に利用するツールはCode Interpreterのみとします。

AOAIでWeb Searchを使うサンプルコード
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検索を実行してくれるわけではありません。

image.png

出典:Function calling - OpenAI API

ユーザー側で関数を実行するには、Stream 処理で送られるイベントをハンドルする必要があります。以下のようなイベントがあるので、これらを利用すると考えられます。
(Function calling は未検証です。ご了承ください。)

  • response.function_call_arguments.delta
  • response.function_call_arguments.done
  • response.custom_tool_call_input.delta
  • response.custom_tool_call_input.done

出典:API Reference - OpenAI API

おわりに

OpenAI および Azure OpenAI のAPIを使って、ChatGPTのo3のような挙動を再現してみました。思ったより長くなってしまいましたが、この記事がどなたかの参考になれば幸いです。

読んでいただきありがとうございました。

参考にさせていただいた記事

10
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?