[OpenAI] API出力の型を自由に!：json schemaの設定方法(Python)

## 本記事を執筆した背景

以前までは、OpenAIのAPIを叩いた結果の型がわからず、Webアプリ開発などに利用しにくいことが課題でした。
しかし2024年8月6日に新しく出たモデルである`gpt-4o-2024-08-06`を利用することで出力結果の型を設定出来るようになりました。
しかしながら、参考となる文献が公式ドキュメントを含めてもまだ十分に整っていない状態です。本記事では実装方法を詳しく、丁寧に説明します

## 本記事の内容
- `gpt-4o-2024-08-06`を使用し、`JSON`形式で設定する
- OpenAIの呼び出し方法を1つずつ説明する
- `Python`を用いて実装(他の言語も同様の実装となるので言語による制約はないと思います。)

- 結果だけを確認したい方は、[5. 実行](#5-実行)を参照して下さい


## 目次

- [1. OpenAIのAPIキーの発行](#1-openaiのapiキーの発行)
- [2. APIの呼び出し](#2-apiの呼び出し)
- [3. messageの設定](#3-messageの設定)
- [4. formatの設定](#4-formatの設定)
- [5. 1~4を組み合わせて実行](#5-実行)




## 1. OpenAIのAPIキーの発行

1. 下記のOpenAIのサイトからAPIの発行をして下さい
1. https://openai.com/ja-JP/api


## 2. APIの呼び出し

1. [1.APIキーの発行](#1-openaiのapiキーの発行)で取得したAPIキーを`OPENAI_API_KEY`に入力して下さい
1. `messages`には、要求する内容を入力します
1. `format`には後述する、`response_fomat`を記述する必要があります
1. `model`は`gpt-4o-2024-08-06`を採用することで、`response_format`を設定することが出来ます
    
```python
from openai import OpenAI

# chatgptのAPIを叩き回答を取得する関数
def get_chat_completion(messages, format):
    # OpenAIインスタンス生成
    client = OpenAI(
        api_key="OPENAI_API_KEY", #発行したAPI_KEYを入力して下さい。
    )
    # API呼び出し
    chat_completion = client.chat.completions.create(
        messages=messages,  # 要求する内容を入力
        response_format=format,  # フォーマットの設定
        model="gpt-4o-2024-08-06",
    )
    # データ取得
    return chat_completion.choices[0].message.content


    
```
    
## 3. messageの設定

- AIの役割や、プロンプトを設定します
    - `system`: AIの役割を設定
    - `user`: 要求するプロンプトを設定
    
```python
messages = [
    {
        "role": "system",
        "content": "あなたは優秀な科学者です。",
    },  # AIの役割を設定します。(設定することで精度が上がるそうです。)
    {
        "role": "user",
        "content": "共同研究の研究テーマとその内容を提案してください。研究者1:リファクタリングの精度向上, 研究者2:機械学習を用いたブラックホールの検知, 研究者3: 太陽活動の周期性",
    },
]   
```
    
## 4. formatの設定
- 今回はフロントエンドにデータを渡したいので、JSON形式に設定を行います
- 以下のようにして実装することが出来ます。(共同研究の研究テーマを生成する関数を作成しました)
- 期待するレスポンスの型は以下です
     - `common_content`
    - `themes`
        - `theme`: “テーマ名”, `content`: “テーマの内容”
        - `theme`: “テーマ名”, `content`: “テーマの内容”
        - `theme`: “テーマ名”, `content`: “テーマの内容”
        - …
    
```python
def generate_research_themes() -> Dict[str, Any]:
    """json_schema format for research collaboration response."""
    response_format = {
        "type": "json_schema",  # json_schemaを設定
        "json_schema": {
            "name": "research_collaboration_response",
            "strict": True,
            "schema": {  # schmaにてデータ型を設定
                "type": "object",  # データ型を指定。今回はオブジェクト型(辞書)
                "properties": {
                    "common_content": {
                        "type": "string"
                    },  # keyを"common_content",valueを文字列
                    "themes": {  # keyを"themes", valueを配列
                        "type": "array",  # 配列の中身の設定
                        "items": {  # 配列のときはitemsとして要素の構造を設定
                            "type": "object",  # 配列の要素はオブジェクト型(辞書)
                            "properties": {
                                "theme": {
                                    "type": "string"
                                },  # 辞書のキーはtheme,値は文字列
                                "content": {
                                    "type": "string"
                                },  # 辞書のキーはcontent,値は文字列
                            },
                            "required": [
                                "theme",
                                "content",
                            ],  # requiredで必須項目を指定
                            "additionalProperties": False,
                        },
                    },
                },
                "required": ["common_content", "themes"],  # requiredで必須項目を指定
                "additionalProperties": False,
            },
        },
    }
    return response_format

```
上記のように、データ構造を設定する際にキーなどを明示的に設定することで適切な型にして結果を出力してくれます。

## 5. 実行

**コピペ&APIキーを設定すれば実行することが出来ます**

```Python
from typing import Any, Dict

from openai import OpenAI


# chatgptのAPIを叩き回答を取得する関数
def get_chat_completion(messages, format):
    # OpenAIインスタンス生成
    client = OpenAI(
        api_key="OPENAI_API_KEY",  # 発行したAPIキーを設定して下さい。
    )
    # API呼び出し
    chat_completion = client.chat.completions.create(
        messages=messages,  # 要求する内容を入力
        response_format=format,  # フォーマットの設定
        model="gpt-4o-2024-08-06",
    )
    # データ取得
    return chat_completion.choices[0].message.content


def generate_research_themes() -> Dict[str, Any]:
    """json_schema format for research collaboration response."""
    response_format = {
        "type": "json_schema",  # json_schemaを設定
        "json_schema": {
            "name": "research_collaboration_response",
            "strict": True,
            "schema": {  # schmaにてデータ型を設定
                "type": "object",  # データ型を指定。今回はオブジェクト型(辞書)
                "properties": {
                    "common_content": {
                        "type": "string"
                    },  # keyを"common_content",valueを文字列
                    "themes": {  # keyを"themes", valueを配列
                        "type": "array",  # 配列の中身の設定
                        "items": {  # 配列のときはitemsとして要素の構造を設定
                            "type": "object",  # 配列の要素はオブジェクト型(辞書)
                            "properties": {
                                "theme": {
                                    "type": "string"
                                },  # 辞書のキーはtheme,値は文字列
                                "content": {
                                    "type": "string"
                                },  # 辞書のキーはcontent,値は文字列
                            },
                            "required": [
                                "theme",
                                "content",
                            ],  # requiredで必須項目を指定
                            "additionalProperties": False,
                        },
                    },
                },
                "required": ["common_content", "themes"],  # requiredで必須項目を指定
                "additionalProperties": False,
            },
        },
    }
    return response_format


# プロンプトの設定
messages = [
    {"role": "system", "content": "あなたは優秀な科学者です。"},
    {
        "role": "user",
        "content": "共同研究の研究テーマとその内容を2つ提案してください。研究者1:リファクタリングの精度向上, 研究者2:機械学習を用いたブラックホールの検知, 研究者3: 太陽活動の周期性",
    },
]

# OPENAIのAPI呼び出し
response = get_chat_completion(messages, generate_research_themes())
print(response)
```
    
### 出力結果
期待通りにJSON形式で取得することが出来ました。
        
```json
{
    "common_content": "この共同研究では、各研究者の専門分野を連携させ、リファクタリング技術、機械学習、天文学における新たな知見を追求することを目指します。",
    "themes": [
        {
            "theme": "リファクタリングと機械学習を用いたブラックホール信号の最適化検出",
            "content": "このテーマでは、研究者1のリファクタリング技術を活用して、研究者2のブラックホール検知アルゴリズムを精緻化します。既存のブラックホールシグナル検出モデルにおけるコードの品質を向上させ、計算効率を高めることで、検出精度をさらに上げることを目指します。機械学習モデルのパフォーマンスを最適化し、誤検出の削減および効率的なデータ処理を実現します。また、新たなアルゴリズムを開発することで、より多様なブラックホールシグナルの検出を可能にします。",
        },
        {
            "theme": "太陽活動の周期性に基づく機械学習モデルのパフォーマンス改善",
            "content": "このテーマでは、太陽活動のデータから得られる周期性を活用して、研究者2と研究者3が共同で新たな機械学習モデルを開発します。研究者3の専門である太陽の周期性を理解し、それを活用して研究者2のブラックホール検出アルゴリズムを自然災害予測システムに応用します。また、太陽活動が地球環境およびデータセンターのパフォーマンスに与える影響を予測するためのモデルを構築し、リファクタリングを用いてコードの効率を最大限に引き出します。これにより、より早期の異常検知が可能になり、リアルタイムで有効な対策が取れる技術を開発します。",
        },
    ],
}
```

#### (補足)
messagesで無理な要求した際には、`theme`や`content`にN/Aなどと記載がありました
無理な要求をしてもデータの型は変わらなかったので、安心して利用することができます

### おわりに
AIを用いたWebアプリケーションの開発をしており、フォーマットを設定することが出来るようになり、アプリケーション開発の拡張性が大幅に上がったと感じています。非常に便利で素晴らしい技術なので、是非使いこなしてほしいです!
不備や質問等ありましたら連絡して下さい!


### 参考文献

#### OpenAI公式ドキュメント
https://platform.openai.com/docs/guides/structured-outputs/introduction