GPT-5の Preamble を試す
GPT=5のプロンプトガイドを読みながら、気になったのが「Preamble」。
公式ドキュメントだとこちら
Preambleは、GPT-5がツールや関数を呼び出す前に生成する短いユーザー可視の説明文で、その意図や計画を説明します(例:「このツールを呼び出す理由」)。これらは、思考の連鎖の後に表示され、実際のツール呼び出しの前に配置されます。これにより、モデルの推論プロセスが透明化され、デバッグの容易性、ユーザー信頼性、および細粒度の制御性が向上します。
GPT-5が各ツール呼び出し前に「思考を声に出す」ことで、Preamble はツール呼び出しの精度(および全体的なタスクの成功率)を向上させつつ、推論のオーバーヘッドを増大させません。Preamble を有効にするには、システムまたは開発者向けの指示を追加します。例えば: 「ツールを呼び出す前に、なぜそのツールを呼び出すのか説明してください」。GPT-5は、指定された各ツール呼び出しの前に簡潔な理由を付加します。モデルはツール呼び出しの間にも複数のメッセージを出力する場合があり、特に推論が最小限の場合や遅延に敏感なユースケースにおいて、インタラクション体験を向上させます。
Preamble の詳細については、GPT-5プロンプトクックブックを参照してください。
なんとなく雰囲気はわかるんだけど、実際の動きを見てみたいので、試してみる。
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のメッセージと思われる。
チャットループで使う場合
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メッセージが返されているのがわかる。
ストリーミングとかだとより意味がありそうな気がする。
ちなみに以前のモデル、例えばgpt-4oではPreambleは使えない。まあGPT-5の新機能なので、当然といえば当然なのかもだけど。