🐕

Ollama + GPT-OSS + OpenAI Python SDKでReasoning Effortの調整

に公開

Ollama + GPT-OSS + OpenAI Python SDKでReasoning Effortの調整

GPT-OSSは、OpenAIが2025年に公開したオープンウェイトの言語モデルです。現行のラインナップにはgpt-oss-120b(約1200億パラメータ)とgpt-oss-20b(約210億パラメータ)の2モデルがあり、Apache 2.0ライセンス(正確には、厳密には、"License: Apache 2.0, with a small complementary use policy."(わずかな補足ポリシーあり))で提供されています。
https://huggingface.co/blog/welcome-openai-gpt-oss#overview-of-capabilities-and-architecture

GPT-OSSシリーズは、同規模の他のオープンモデルより高い推論性能を発揮し、ツール使用やChain-of-Thought (CoT)推論にも強みを持つ点が特徴です。特に小型モデルのgpt-oss-20bは、OpenAIの内部モデル「o3-mini」と同等のベンチマーク性能を示しつつ、約16GBのメモリで動作可能なよう最適化されており、比較的低スペックでのPCやローカル環境での利用に適しています。

reasoning_effortパラメータ

GPT-OSSモデルの大きな特徴の一つが、reasoning_effort と呼ばれる推論コントロール機能です。これはモデルが回答を生成する際に割く「思考の深さ」や「手間」の度合いを調整できるオプションで、ユーザーは 低 (low)・中 (medium)・高 (high) の3段階から選択できます。reasoning_effortを下げると応答までの内部推論ステップを簡略化し、速度やコスト(消費トークン数)を抑えることができます。一方、高く設定すると、モデルはより詳細かつ丹念に推論を行い、複雑な課題に対しても入念な解答を導こうとします。

このパラメータによって、応答の詳細さと速度のトレードオフをタスクに応じて制御できますので、例えば、ユーザーからの質問が単純な場合はlowに設定して素早く短い回答を返し、難解な問題にはhighでじっくり推論させてより正確で丁寧な回答を得る、といった使い分けが可能です。

なお、GPT-OSSではChain-of-Thought(内部思考プロセス)の生成結果を取得可能です。通常のAPI経由で利用できるOpenAIのモデルでは内部の推論過程はユーザーに提示されませんが、GPT-OSSの場合はreasoning_effortに応じて内部で生成した思考内容(CoT)がストリーム出力の専用フィールド(reasoning内)として得られる設計になっています。

ローカル環境(Ollama)でのモデル実行とreasoning_effortの調整

GPT-OSSはローカルマシンで直接動作可能なよう最適化されていますが、自前のコードでモデルを回すには大規模モデル特有の手間があります。そのため本記事では、手軽にローカルLLMを実行できるOllamaを活用し、OpenAIのPython SDK(APIクライアント)を通じてGPT-OSSを操作する方法を紹介します。

Ollamaは数行のコマンドで各種LLMをダウンロード・実行できるツールで、OpenAIのChat Completion API互換のサーバ機能を持っています。つまり、Ollamaでモデルを起動すれば、OpenAI公式APIと同じリクエスト形式でローカルモデルを呼び出すことが可能です。

  1. モデルの準備: まずOllamaをインストールし、GPT-OSS 20Bモデルを取得します。

以下のページから各々の環境に従い、ダウンロードしてください。

https://ollama.com/

モデルは以下のように取得します。

ollama pull gpt-oss:20b

このコマンドにより約21億パラメータのモデル(量子化済みで約16GB)がダウンロードされます。

  1. ローカルサーバの起動: 次にOllamaでモデルをサービスとして起動します。
ollama serve

これでローカルホスト上にOpenAI互換のAPIサーバが立ち上がり、http://localhost:11434/v1 というエンドポイントでチャットリクエストを受け付けられるようになります。

  1. OpenAI SDKから接続設定: Python側では、OpenAI純正のSDK(openaiパッケージ)を使ってローカルサーバに接続します。ポイントはAPIのエンドポイントURLとAPIキーをローカル用の値に設定することです(ローカルサーバではAPIキー認証は不要ですが、OpenAI SDKの都合上ダミーの値を設定しておく必要があります)。

まず、OpenAI SDKをインストールしましょう。

pip install -U openai

その後、Pythonスクリプトを設定します。

from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:11434/v1",
    api_key="api_key"
)

# メッセージリストの準備
messages = [
    {
        "role": "system",
        "content": "You are a helpful assistant. Keep reasoning under 2500 tokens."
    },
    {
        "role": "user",
        "content": question
    }
]

reasoning_effort_options = ["low", "medium", "high"]

for effort in reasoning_effort_options:
    # 推論の実行
    completion = client.chat.completions.create(
        model="gpt-oss:20b",
        messages=messages,
        reasoning_effort=effort,
        temperature=0,
        top_p=1,
        max_tokens=20000,
    )
    print(f"--- Reasoning Effort: {effort} ---")
    print(completion.choices[0].message.content)
    print(f"reasoningの長さ={len(completion.choices[0].message.reasoning)}")
    print()

messagesには、システムメッセージを入れておきます。通常、ChatGPTのAPIなどを使う際にはよくYou are a helpful assistantなどが使われますが、今回はそれに加えてKeep reasoning under 2500 tokens.と入れておきます。
これは、モデルが出力を生成する際に、よく内部 reasoning が非常に冗長になり、本文に到達する前に数万トークン消費してしまうケースが散見されるため、あらかじめ、システムプロンプト側から抑制しておきます。

上記のようにapi_baseをローカルのURLに変更し、modelにgpt-oss:20bを指定することで、以降のopenai.ChatCompletion.create呼び出しはローカルのGPT-OSSに対して行われます。OpenAI API互換ですので、プロンプトメッセージや各種パラメータの指定も通常のChatCompletionと同様です。

reasoning_effort はChatCompletionのAPIリクエストにおける追加パラメータとして指定できます。OpenAI SDKでは公式にこのパラメータをサポートしており、例えばPythonではChatCompletion.createの引数にreasoning_effort="low"のように渡せます。

:::Warning
OpenAI公式のChatCompletion APIでは、モデルがreasoning_effortに対応していればパラメータ指定だけで制御できます。GPT-OSSをローカル実行する際も、Ollama経由であればこのフィールドを受け取りモデルに反映します。
:::

実験:low・medium・highでの出力比較

では実際に、同じ質問に対してreasoning_effortを変えると応答内容がどう変化するか確認してみましょう。ここでは例として以下のような質問をモデルに投げかけてみます。

「太郎には4人の兄と1人の妹がいる。太郎の妹には何人の兄がいるか?」

実行結果

--- Reasoning Effort: low ---
太郎の妹は、太郎と同じ兄弟姉妹を共有しています。  
太郎には兄が4人いるので、妹も兄が **4人** います。
reasoningの長さ=207

--- Reasoning Effort: medium ---
太郎の妹は、太郎と同じ兄弟を共有しています。  
太郎には「4人の兄」がいるので、妹もその4人の兄を持っています。  

**答え:4人の兄**
reasoningの長さ=372

--- Reasoning Effort: high ---
5人です。  
太郎の妹は、太郎自身と太郎の4人の兄を兄として持っています。
reasoningの長さ=1139

ご覧のとおり、reasoning_effortにより回答が変化したのが確認できます。
回答そのものについては、lowmediumでは内容自体に変化はありませんでしたが、highでは正しい答えを導いているように見受けられます。

一応、reasoningの中身を確認してみましょう。reasoningの中身についてはcompletion.choices[0].message.reasoningで取得することが可能です。

--- Reasoning Effort: low ---
We need to answer in Japanese. The problem: Taro has 4 brothers and 1 sister. How many brothers does Taro's sister have? Taro's sister has the same siblings: 4 brothers (Taro plus the other 3). So answer: 4.

--- Reasoning Effort: medium ---
We need to answer the riddle: Taro has 4 brothers and 1 sister. How many brothers does Taro's sister have? Taro has 4 brothers, so Taro's sister has the same brothers: 4. But also Taro is a brother to the sister, so she has 4 brothers. So answer: 4. But sometimes trick: Taro has 4 brothers and 1 sister. So Taro's sister has 4 brothers. So answer: 4. Provide explanation.

--- Reasoning Effort: high ---
We need to parse the problem. It's a Japanese riddle: "太郎には4人の兄と1人の妹がいる。太郎の妹には何人の兄がいるか?" Means: Taro has 4 older brothers and 1 younger sister. How many brothers does Taro's sister have? The answer: Taro's sister has 5 brothers (Taro plus the 4 older brothers). But careful: Taro has 4 older brothers, so Taro is younger than them. Taro has 1 sister. So Taro's sister has 4 older brothers plus Taro, so 5 brothers. So answer: 5. But maybe the riddle expects 5. But let's double-check: Taro has 4 older brothers and 1 sister. So Taro's sister has 4 older brothers (the same as Taro's older brothers) plus Taro. So 5. So answer: 5. But maybe the riddle is trick: Taro's sister has 4 older brothers and Taro is also a brother, so 5. So answer: 5. So we respond: 5人. But we need to respond in Japanese? The question is in Japanese. So answer in Japanese: 5人. Or "5人の兄がいる". So we can answer: 5人. But we might also explain. But the user likely expects a short answer. So we can answer: 5人. But we can also explain: Taro's sister has 4 older brothers plus Taro, so 5. So answer: 5. So we respond accordingly.

高くするにつれてreasoningが段階的に詳細になっていくことが確認できます。lowではごく簡潔に述べるだけなのに対し、mediumではもう少し丁寧に背景から掘り下げており、特に"But also Taro is a brother to the sister"と正答に直結しそうな内容も現れているのが見て取れます。
さらにhighでより理由を整理しながら、非常に詳細な説明を行っている上、"But let's double-check" とまで再確認を行なっています。言及する内容についてより深めて考えていこうとしているように見えますね。

まとめ

今回、GPT-OSS + ollama + Open AI PythonSDKで、reasoning effortの挙動を確認してみました。

ローカルLLMの活用は、チャットボットやエージェントをオンプレミス環境で動かしたい企業ニーズや、個人が自前GPUでAIを手元に置きたいといった要望に対しても柔軟に答えることができます。GPT-OSSは上記のように設定することで、OpenAI SDKからも利用できますので、これまでAPI経由で行っていた処理を閉じた環境で完結させるなども割と簡単に行うことができます。

将来的には、さらにパラメータ数を抑えつつ能力を高めた次世代モデルや、画像・音声も処理できるマルチモーダルなオープンモデルの登場も予想されます。OpenAIがGPT-OSSを皮切りに「フルオープンな強力モデル」をリリースしたことは、この分野の一つの転機とも言えるでしょう。今後もコミュニティや他企業から、GPT-OSSをベースにした派生モデルや、対抗するオープンLLMが出現する可能性があります。我々エンジニアは、そうした動向をウォッチしつつ、reasoning_effortのような優れた機能を活用して、ユースケースに最適化されたAIシステムを設計・実装していくことが求められるでしょう。

Discussion