Tech
blog
テックブログ

2026.7.31

    (没)OpenAIのドキュメントを読み返してみる

    こちらは没です!! 今回はOpenAIのドキュメントから個人的に勉強になった部分をご紹介させていただきます。今年もOpenAIからは様々な発表がありました。この年末の機会にドキュメントを読み直してみると、個人的にいくつかの点で認識漏れに気づくことができたので、備忘録としてまとめさせていただきたいと思います。OpenAIのドキュメントのリンクをつけておりますので、気になった部分はご参照ください。

    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では変数名も参考にして情報を取得します。そのため、上例ではnamedateは共にstring型ですがそれぞれが名前と日付であることを認識して取得できます。以下のようにpydanticのFieldで書くのもおすすめです。
    class CalendarEvent(BaseModel):
        name: str = Field(description="イベント名")
        date: str = Field(description="日付")
        participants: list[str] = Field(description="参加者一覧")
    また、公式ドキュメントではStructured Outputsを使ったChain of Thoughtsの実装例がありました。
    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
    この例ではMathReasoningstepsが可変長のリストになっているため段階的な推論を行うことができます。精度の高い構造化出力を行うために、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 rando
    import 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))

    Python以外のライブラリ

    LibrariesにはPythonだけでなく、TypeScript / JavaScript、.NET、Azure OpenAI client等のライブラリが記載されています。また、Community LibrariesはOpenAI公式がサポートしているわけではないものの、Java、Go、PHPなど多くの言語でサポートされています。私は普段Pythonしか利用していないのですが、これほど多くのライブラリがサポートされていることは知りませんでした。

    終わりに

    OpenAIのドキュメントを読み直してみると、個人的に知らないことがたくさんありました。年末のあいた期間にぜひ読み直してみると新しい発見があるかもしれません。 ここまでお読みいただきましてありがとうございました。 明日はOOよりOOについて紹介いたします。