Closed5

GPT-5の Preamble を試す

kun432kun432

GPT=5のプロンプトガイドを読みながら、気になったのが「Preamble」。

https://zenn.dev/link/comments/34e0cc2a167f49

公式ドキュメントだとこちら

https://platform.openai.com/docs/guides/latest-model#preambles

Preambleは、GPT-5がツールや関数を呼び出す前に生成する短いユーザー可視の説明文で、その意図や計画を説明します(例:「このツールを呼び出す理由」)。これらは、思考の連鎖の後に表示され、実際のツール呼び出しの前に配置されます。これにより、モデルの推論プロセスが透明化され、デバッグの容易性、ユーザー信頼性、および細粒度の制御性が向上します。

GPT-5が各ツール呼び出し前に「思考を声に出す」ことで、Preamble はツール呼び出しの精度(および全体的なタスクの成功率)を向上させつつ、推論のオーバーヘッドを増大させません。Preamble を有効にするには、システムまたは開発者向けの指示を追加します。例えば: 「ツールを呼び出す前に、なぜそのツールを呼び出すのか説明してください」。GPT-5は、指定された各ツール呼び出しの前に簡潔な理由を付加します。モデルはツール呼び出しの間にも複数のメッセージを出力する場合があり、特に推論が最小限の場合や遅延に敏感なユースケースにおいて、インタラクション体験を向上させます。

Preamble の詳細については、GPT-5プロンプトクックブックを参照してください。

なんとなく雰囲気はわかるんだけど、実際の動きを見てみたいので、試してみる。

kun432kun432

Colaboratoryで。

パッケージインストール

!pip install -U openai
出力
Successfully installed openai-1.99.9

APIキーを環境変数にセット

from google.colab import userdata
import os

os.environ["OPENAI_API_KEY"] = userdata.get('OPENAI_SHARED_API_KEY')

で、Preambleはツールを使う場合に使用するものみたいなので、ツールを使ったシンプルなサンプルで試してみる。一応、GPT-5 は Responses API推奨みたいなのだけど、Responsed APIまだあんまり慣れていないし、既存のコードでも使えるか?も確認したいので、Chat Completions APIで。

まずはPreambleなしで、ダミーの天気予報ツールを使う例。

from openai import OpenAI
import json

client = OpenAI()

def get_current_temperature(location, unit="fahrenheit"):
    """与えられた地域の現在の気温を取得する"""
    if "tokyo" in location.lower():
        return json.dumps({"location": "Tokyo", "temperature": "10", "unit": unit})
    elif "san francisco" in location.lower():
        return json.dumps({"location": "San Francisco", "temperature": "72", "unit": unit})
    elif "paris" in location.lower():
        return json.dumps({"location": "Paris", "temperature": "22", "unit": unit})
    else:
        return json.dumps({"location": location, "temperature": "unknown"})

get_current_temperature_schema = {
    "type": "function",
    "function": {
        "name": "get_current_temperature",
        "description": "Get the current temperature",
        "parameters": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "The city and state, e.g. San Francisco, CA",
                },
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "The temperature unit to use. Infer this from the users location.",
                },
            },
            "required": ["location", "unit"],
        },
    }
}

system_proopt = """
あなたは親切な日本語のアシスタントです。ユーザの質問に答えるのがあなたの仕事です。
"""

messages = [
    {
        "role": "system",
        "content": system_proopt,
    },
    {
        "role": "user",
        "content": "東京の天気を教えて。"
    },
]

response = client.chat.completions.create(
    model="gpt-5",
    messages=messages,
    tools=[get_current_temperature_schema],
    tool_choice="auto",  
)

print(json.dumps(response.model_dump(), indent=2))

実行結果

出力
{
  "id": "chatcmpl-C4JFWWTf4StNWBTZVZwixgtbIwvQa",
  "choices": [
    {
      "finish_reason": "tool_calls",
      "index": 0,
      "logprobs": null,
      "message": {
        "content": null,
        "refusal": null,
        "role": "assistant",
        "annotations": [],
        "audio": null,
        "function_call": null,
        "tool_calls": [
          {
            "id": "call_kmwysQIbCjFque5ZRPxO7Bgu",
            "function": {
              "arguments": "{\"location\":\"Tokyo, JP\",\"unit\":\"celsius\"}",
              "name": "get_current_temperature"
            },
            "type": "function"
          }
        ]
      }
    }
  ],
  "created": 1755143342,
  "model": "gpt-5-2025-08-07",
  "object": "chat.completion",
  "service_tier": "default",
  "system_fingerprint": null,
  "usage": {
    "completion_tokens": 287,
    "prompt_tokens": 201,
    "total_tokens": 488,
    "completion_tokens_details": {
      "accepted_prediction_tokens": 0,
      "audio_tokens": 0,
      "reasoning_tokens": 256,
      "rejected_prediction_tokens": 0
    },
    "prompt_tokens_details": {
      "audio_tokens": 0,
      "cached_tokens": 0
    }
  }
}

ツール呼び出しレスポンスで関数に渡す引数が.choices[0].message.tool_calls で返されるので、あとはこれを元に関数実行、その実行結果を同じツール呼び出しIDで返せば、それを踏まえたテキスト回答が得られるというのがFunction Callingの流れ。

システムプロンプトに Preamble のためのプロンプトを追加する。これはGPT-5プロンプトガイドに記載のものを日本語化しただけ。

system_proopt = """
あなたは親切な日本語のアシスタントです。ユーザの質問に答えるのがあなたの仕事です。

<tool_preambles>
- ツールを呼び出す前に、必ずユーザーの目的を親しみやすく、明確で簡潔に言い換えること。
- その後、すぐにこれから行う各論理ステップを構造化して説明すること。
- ファイル編集などの作業を実行する際は、各ステップを簡潔かつ順序立てて説明し、進捗を明確に示すこと。
- 最後に、事前に立てた計画とは別に、実際に完了した作業をまとめて報告すること。
</tool_preambles>
"""

messages = [
    {
        "role": "system",
        "content": system_proopt,
    },
    {
        "role": "user",
        "content": "東京の天気を教えて。"
    },
]

response = client.chat.completions.create(
    model="gpt-5",
    messages=messages,
    tools=[get_current_temperature_schema],
    tool_choice="auto",  
)

print(json.dumps(response.model_dump(), indent=2, ensure_ascii=False))

結果

出力
{
  "id": "chatcmpl-C4JPoprgLCsDKurlHOGgr4iP3N1p2",
  "choices": [
    {
      "finish_reason": "tool_calls",
      "index": 0,
      "logprobs": null,
      "message": {
        "content": "ご要望の確認: 東京の現在の天気(少なくとも気温)を知りたい、という理解で合っていますか?\n\nこれからの進め方:\n- 日本の利用という前提で、温度単位は摂氏(℃)にします。\n- ツールで「東京」の現在の気温を取得します。\n- 取得した結果をわかりやすくお伝えし、追加で必要な情報(降水や風など)があれば伺います。",
        "refusal": null,
        "role": "assistant",
        "annotations": [],
        "audio": null,
        "function_call": null,
        "tool_calls": [
          {
            "id": "call_GjzZsWiVz3HpM7FUp4SESmS4",
            "function": {
              "arguments": "{\"location\":\"Tokyo, Japan\",\"unit\":\"celsius\"}",
              "name": "get_current_temperature"
            },
            "type": "function"
          }
        ]
      }
    }
  ],
  "created": 1755143980,
  "model": "gpt-5-2025-08-07",
  "object": "chat.completion",
  "service_tier": "default",
  "system_fingerprint": null,
  "usage": {
    "completion_tokens": 662,
    "prompt_tokens": 352,
    "total_tokens": 1014,
    "completion_tokens_details": {
      "accepted_prediction_tokens": 0,
      "audio_tokens": 0,
      "reasoning_tokens": 512,
      "rejected_prediction_tokens": 0
    },
    "prompt_tokens_details": {
      "audio_tokens": 0,
      "cached_tokens": 0
    }
  }
}

関数に渡す引数がこれまで通り返されると同時に、.choices[0].message.content にテキストが返されていて、これがPreambleのメッセージと思われる。

kun432kun432

チャットループで使う場合

from openai import OpenAI
import json

client = OpenAI()

def get_current_temperature(location, unit="fahrenheit"):
    """与えられた地域の現在の気温を取得する"""
    if "tokyo" in location.lower():
        return json.dumps({"location": "Tokyo", "temperature": "10", "unit": unit})
    elif "san francisco" in location.lower():
        return json.dumps({"location": "San Francisco", "temperature": "72", "unit": unit})
    elif "paris" in location.lower():
        return json.dumps({"location": "Paris", "temperature": "22", "unit": unit})
    else:
        return json.dumps({"location": location, "temperature": "unknown"})

get_current_temperature_schema = {
    "type": "function",
    "function": {
        "name": "get_current_temperature",
        "description": "Get the current temperature",
        "parameters": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "The city and state, e.g. San Francisco, CA",
                },
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "The temperature unit to use. Infer this from the users location.",
                },
            },
            "required": ["location", "unit"],
        },
    }
}

tool_schemas = [get_current_temperature_schema]
tool_mapping = {
    "get_current_temperature": get_current_temperature,    
}

system_proopt = """
あなたは親切な日本語のアシスタントです。ユーザの質問に答えるのがあなたの仕事です。

<tool_preambles>
- ツールを呼び出す前に、必ずユーザーの目的を親しみやすく、明確で簡潔に言い換えること。
- その後、すぐにこれから行う各論理ステップを構造化して説明すること。
- ファイル編集などの作業を実行する際は、各ステップを簡潔かつ順序立てて説明し、進捗を明確に示すこと。
- 最後に、事前に立てた計画とは別に、実際に完了した作業をまとめて報告すること。
</tool_preambles>
"""

messages = [
    {
        "role": "system",
        "content": system_proopt,
    },
]

try:
    while True:
        user_input = input("ユーザ: ")
        if user_input.lower() in {"exit", "quit", "q"}:
            print("\nチャットを終了しました。")
            break

        # 初回リクエスト
        messages.append(
            {
                "role": "user",
                "content": user_input
            }
        )
        first_response = client.chat.completions.create(
            model="gpt-5",
            messages=messages,
            tools=tool_schemas,
            tool_choice="auto"
        ).model_dump(exclude_unset=True) # OpenAIのレスポンスオブジェクトで返ってくるので辞書にdump
        first_response_message = first_response["choices"][0]["message"]

        if "tool_calls" in first_response_message:
            messages.append(first_response_message)

            # preambleがあれば出力
            if "content" in first_response_message and first_response_message['content'] is not None:
                print(f"AI(Preamble): {first_response_message['content']}")

            tool_calls = first_response_message["tool_calls"]
            for tool_call in tool_calls:
                function_name = tool_call["function"]["name"]
                function_to_call = tool_mapping[function_name]
                print(f"DEBUG: ({function_name}ツール実行)")
                function_args = json.loads(tool_call["function"]["arguments"])
                function_response = function_to_call(**function_args)
                print(f"DEBUG: ({function_name}ツール実行結果: {function_response})")
                messages.append(
                    {
                        "tool_call_id": tool_call["id"],
                        "role": "tool",
                        "name": function_name,
                        "content": function_response,
                    }
                )
            second_response = client.chat.completions.create(
                model="gpt-5",
                messages=messages,
            ).model_dump(exclude_unset=True)
            second_response_message = second_response["choices"][0]["message"]

            messages.append(
                {
                    "role": "assistant",
                    "content": second_response_message['content']
                }
            )
            print(f"AI: {second_response_message['content']}")

        else:
            messages.append(
                {
                    "role": "assistant",
                    "content": first_response_message['content']
                }
            )
            print(f"AI: {first_response_message['content']}")

except KeyboardInterrupt:
    print("\nチャットを終了しました。")

実行結果はこんな感じ。

出力
ユーザ: おはよう!
AI: おはようございます!今日も一日よろしくお願いします。何かお手伝いできることはありますか?
ユーザ: 東京の天気を教えて。
AI(Preamble): ご要望の整理: 東京の現在の天気(少なくとも現在の気温)を知りたい、ということですね。

これからの進め方:
1) 東京の現在の気温を摂氏で取得します。
2) 結果をお伝えし、必要なら降水や風、予報などの追加情報の希望を伺います。
DEBUG: (get_current_temperatureツール実行)
DEBUG: (get_current_temperatureツール実行結果: {"location": "Tokyo", "temperature": "10", "unit": "celsius"})
AI: 現在の東京の気温は約10°Cです。
- 降水の有無や体感温度、風、今後の予報などもお知らせできます。必要な情報を教えてください。
ユーザ: 少し寒いね、わかった、ありがとう。
AI: そうですね、少し肌寒いですね。外出の際は上着やマフラーで暖かくしてくださいね。ほかにお手伝いできることがあれば教えてください。
ユーザ: q

チャットを終了しました。

ツール実行前にPreambleメッセージが返されているのがわかる。

kun432kun432

ストリーミングとかだとより意味がありそうな気がする。

kun432kun432

ちなみに以前のモデル、例えばgpt-4oではPreambleは使えない。まあGPT-5の新機能なので、当然といえば当然なのかもだけど。

このスクラップは1ヶ月前にクローズされました