2026.7.31
(没)OpenAIのドキュメントを読み返してみる
こちらは没です!!
今回はOpenAIのドキュメントから個人的に勉強になった部分をご紹介させていただきます。今年もOpenAIからは様々な発表がありました。この年末の機会にドキュメントを読み直してみると、個人的にいくつかの点で認識漏れに気づくことができたので、備忘録としてまとめさせていただきたいと思います。OpenAIのドキュメントのリンクをつけておりますので、気になった部分はご参照ください。
ちなみに上記の設定で
引数に
指定したスキーマでの獲得に失敗した場合は
また、公式ドキュメントではStructured Outputsを使ったChain of Thoughtsの実装例がありました。
この例では
Embeddingsは複数テキストを1リクエストで処理できる
Embeddingsではテキストのベクトル化を行います。from openai import OpenAI
client = OpenAI()
queries = ["こんにちは", "ありがとう"]
response = client.embeddings.create(input=queries, model="text-embedding-ada-002")
embeddings = [response.data[i].embedding for i in range(len(queries))]
queriesを1000件まで増やして実行しても2秒ほどで完了しました。
text-embedding-3-{small, large}のモデルでは出力ベクトルの次元数を削減できる
response = client.embeddings.create(input=queries, model="text-embedding-3-large", dimensions=64)
dimensionsを指定することで出力ベクトルの次元数を削減できます。
特にRAGの用途で利用する場合、ベクトルの次元数が大きいほどベクトルストアのコストが大きくなりますので、dimensionsの設定がおすすめです。Embeddingsには「2Dでのデータ視覚化」「クラスタリング」など用途ごとの実装例が載っているので一読をおすすめします。
Reasoning Model(o1)をAPI経由で利用するときReasoning tokensは確認することができない
OpenAIのReasoning Modelであるo1シリーズは非常に高い推論能力を持っており、複雑な推論を必要とするタスクを高い精度で達成することができます。仕組みとして、モデルは直接結果を出す前にReasoning tokensという考えるための推論を行います。これはChain of Thoughtsという段階的な推論に基づいて結果を出す手法を内部的に行なっており、指示をより深く理解して結論を出すことができます。しかし、このReasoning tokensをユーザーが確認することはできません。そのため、最終的な出力が誤っていた場合の原因調査が困難です。 レスポンスには以下のような使用量を表すusageが含まれており、reasoning_tokensからトークン数を確認することができます。
usage: {
total_tokens: 1000,
prompt_tokens: 400,
completion_tokens: 600,
completion_tokens_details: {
reasoning_tokens: 500
}
}
構造化出力
構造化出力はJSON modeとStructured Outputsの2種類あります。 JSON modeが先発の機能で、その進化形がStructured Outputsになります。 JSON modeはスキーマの定義をプロンプト内に記述するため手軽に実装できますが、結果がスキーマに準拠しない可能性があります。一方、Structured OutputsはPythonで出力すべきスキーマを指定し、結果はPythonの変数として取得できます。# JSON mode
import json
import sys
from openai import OpenAI
client = OpenAI()
completion = client.chat.completions.create(
model="gpt-4o-mini",
response_format={ "type": "json_object" },
messages=[
{"role": "system", "content": "イベントの情報を抽出してください。結果は次のJSON形式で出力してください。 {{ \"name\": \"xxx\", \"date\": \"xxx\", \"participants\": [\"xxx\", \"xxx\"] }}"},
{"role": "user", "content": "アリスとボブは金曜日にサイエンスフェアに行く予定です"}
]
)
response = completion.choices[0].message.content
try:
json_response = json.loads(response)
except Exception as e:
print(e)
sys.exit()
print(json_response)
# Structured Outputs
from pydantic import BaseModel
from openai import OpenAI
client = OpenAI()
class CalendarEvent(BaseModel):
name: str
date: str
participants: list[str]
completion = client.beta.chat.completions.parse(
model="gpt-4o-2024-08-06",
messages=[
{"role": "system", "content": "イベントの情報を抽出してください"},
{"role": "user", "content": "アリスとボブは金曜日にサイエンスフェアに行く予定です"},
],
response_format=CalendarEvent,
)
math_reasoning = completion.choices[0].message
if (math_reasoning.refusal):
print(math_reasoning.refusal)
else:
print(math_reasoning.parsed)
refusalにメッセージが出力されますので、クライアントでの
Structured Outputsでは変数名も参考にして情報を取得します。そのため、上例ではnameとdateは共にstring型ですがそれぞれが名前と日付であることを認識して取得できます。以下のようにpydanticのFieldで書くのもおすすめです。
class CalendarEvent(BaseModel):
name: str = Field(description="イベント名")
date: str = Field(description="日付")
participants: list[str] = Field(description="参加者一覧")
from pydantic import BaseModel
from openai import OpenAI
client = OpenAI()
class Step(BaseModel):
explanation: str
output: str
class MathReasoning(BaseModel):
steps: list[Step]
final_answer: str
completion = client.beta.chat.completions.parse(
model="gpt-4o-2024-08-06",
messages=[
{"role": "system", "content": "You are a helpful math tutor. Guide the user through the solution step by step."},
{"role": "user", "content": "how can I solve 8x + 7 = -23"}
],
response_format=MathReasoning,
)
math_reasoning = completion.choices[0].message.parsed
MathReasoningのstepsが可変長のリストになっているため段階的な推論を行うことができます。精度の高い構造化出力を行うために、stepsのような変数を追加で設定しておく方法は汎用的に有効であるかもしれません。
Prompt Caching
プロンプトに繰り返しのコンテンツが含まれる場合、OpenAI側でPrompt Cachingが自動で適用されます。これによりレイテンシとコストの大幅な削減が見込まれます。 ユーザーとして追加の設定は必要ないのが嬉しいポイントです。注意点として、何度も似たリクエストを送る場合にはPrompt Cachingのキャッシュヒット率が高くなるように、プロンプトの前半は変更しない部分にすると良さそうです。ちなみに、Azure OpenAI ServiceにもPrompt Cachingはあります。ただしAzureサブスクリプション間でキャッシュは共有されない点に注意です。 キャッシュが効いていることは以下のようにusageで確認できます。{
"usage": {
"prompt_tokens": 2006,
"completion_tokens": 300,
"total_tokens": 2306,
"prompt_tokens_details": {
"cached_tokens": 1920
},
"completion_tokens_details": {
"reasoning_tokens": 0
}
}
}
Batch API
すぐにリクエストが完了する必要がないときはBatch APIの利用をお勧めします。これによりコストを50%削減でき、レート制限を大幅に高めてプールで実行することができます。# 1. 以下の形式でデータをjsonlファイルで用意します
import json
data = [
{"custom_id": "request-1", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "gpt-3.5-turbo-0125", "messages": [{"role": "system", "content": "You are a helpful assistant."},{"role": "user", "content": "Hello world!"}],"max_tokens": 1000}},
{"custom_id": "request-2", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "gpt-3.5-turbo-0125", "messages": [{"role": "system", "content": "You are an unhelpful assistant."},{"role": "user", "content": "Hello world!"}],"max_tokens": 1000}}
]
with open("batchinput.jsonl", "w") as f:
for obj in data:
json.dump(obj, f, ensure_ascii=False)
f.write('\n')
# 2. jsonlファイルをアップロードします
from openai import OpenAI
client = OpenAI()
batch_input_file = client.files.create(
file=open("batchinput.jsonl", "rb"),
purpose="batch"
)
# 3. バッチの作成
batch_input_file_id = batch_input_file.id
response = client.batches.create(
input_file_id=batch_input_file_id,
endpoint="/v1/chat/completions",
completion_window="24h",
metadata={
"description": "nightly eval job"
}
)
print(response)
print("バッチID: ", response.id)
# 4. バッチのステータスを確認
response = client.batches.retrieve(response.id) # 上記のバッチIDを設定
print(response)
print("ステータス: ", response.status)
print("実行結果ファイルID: ", response.output_file_id)
# 5. 結果の取得
file_response = client.files.content(response.output_file_id) # 上記の実行結果ファイルIDを設定
print(file_response.text)
# その他: バッチのキャンセル
client.batches.cancel("batch_abc123")
# その他: バッチのリストを取得
client.batches.list(limit=10)
エラーハンドリング
Error CodeにはOpenAIのリクエストに対するエラーの詳細が記載されています。例えば、Rate Limitの制限によって実行が中断されてしまった時にのみリトライ処理を実行したいといった場合は以下のように実装します。 import randoimport time
from openai import OpenAI, RateLimitError
client = OpenAI()
DELAY_BASE_TIME = 1
MAX_RETRY = 3
def chat_completion(messages: list[dict]) -> str | None:
for i in range(1, MAX_RETRY + 1):
try:
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
)
return response.choices[0].message.content
except RateLimitError as e:
delay_time = (2 ** i) * DELAY_BASE_TIME + random.random()
print(f"Retry after {delay_time:.2f} seconds")
time.sleep(delay_time)
continue
except Exception as e:
raise e
return None
messages = [
{"role": "user", "content": "Hello, how are you?"},
]
print(chat_completion(messages))