GPT4 / GPT3.5でJSONモードを利用して、必ずJSONを生成してもらう

寝て起きたらいつのまにか世界が変わっているAI界隈ですが、OpenAI Dev Dayにていくつかの新しい機能が発表されていました。

JSONモードについての解説です。[shi3zさんの記事](https://note.com/shi3zblog/n/nd72e0269dc3f
)にも書かれていますが、ここではOPENAIのドキュメントからいくつかの補足、変更とJSONモード利用の注意点を加えています。公式の解説は、わかりにくいのですが以下にあります。

[https://platform.openai.com/docs/guides/text-generation/json-mode](https://platform.openai.com/docs/guides/text-generation/json-mode)

# Colab環境で試してみる
google colabでコピー＆ペーストで実行したい人はこちらをどうぞ。

### ライブラリのインストールと環境変数の設定
``` Shell
!pip install openai
%env OPENAI_API_KEY=<YOUR_API_KEY_HERE>
```
colabでは`%env`で環境変数を設定します。ローカルでは、.envファイルにAPIキーを記載して読み込むなどしてください。

### コードを実行する
``` Python
from openai import OpenAI

client = OpenAI()

def gpt(content):
    messages = [
            {"role": "system", "content": "あなたは返答をすべてJSON形式で出力します。"}, 
            #システムメッセージにJSONという文字列を含めます。
            {"role": "user", "content": content},
        ]
    response = client.chat.completions.create(
        model="gpt-4-1106-preview", 
        #使用できるモデルは、「gpt-4-1106-preview」「gpt-3.5-turbo-1106」(2023年11月8日現在)
        response_format={"type":"json_object"}, 
        #レスポンスフォーマットに"json_object"を指定する
        messages=messages,
        temperature=0,
    )
    return response.choices[0].message.content

gpt("マイケル・ジャクソンのヒットソングを教えてください。")
```

補足：
いつのまにか`client = OpenAI()`として初期化するようになっていました。APIキーは、例のように環境変数`OPENAI_API_KEY`を設定するか、以下のようにしてクライアントに渡すことができます。

``` Python
client = OpenAI(
    # defaults to os.environ.get("OPENAI_API_KEY")
    api_key="My API Key",
)
```
[OpenAI Python API library](https://github.com/openai/openai-python)

### 返却結果
``` JSON
{\n  "hit_songs": [\n    {\n      "title": "Billie Jean",\n      "release_year": 1982\n    },\n    {\n      "title": "Thriller",\n      "release_year": 1982\n    },\n    {\n      "title": "Beat It",\n      "release_year": 1982\n    },\n    {\n      "title": "Man in the Mirror",\n      "release_year": 1987\n    },\n    {\n      "title": "Smooth Criminal",\n      "release_year": 1987\n    },\n    {\n      "title": "Black or White",\n      "release_year": 1991\n    },\n    {\n      "title": "Bad",\n      "release_year": 1987\n    },\n    {\n      "title": "The Way You Make Me Feel",\n      "release_year": 1987\n    },\n    {\n      "title": "Don\'t Stop \'Til You Get Enough",\n      "release_year": 1979\n    },\n    {\n      "title": "Rock with You",\n      "release_year": 1979\n    }\n  ]\n}
```
このようにして、マイケル・ジャクソンの代表作とリリース年がJSON形式で返されました。しかし、ビリー・ジーンのリリースは1982年でしょうか？
あくまでJSON形式での結果の返却を保証する機能であり、正確な内容を得るには、今までと変わりなく外部データを使って回答の精度をあげていく必要があります。openAIのAPIを利用したEmbeddingに関しては、以下に記事を書いています。

https://qiita.com/kuromiya123/items/01c2807288c055f017b9


# JSONモードの注意点
JSONモード利用に関する注意点をまとめます。

* 利用できるモデルは、`gpt-4-1106-preview` もしくは `gpt-3.5-turbo-1106`です。
* response_formatに `{ type: "json_object" }`を設定すると、JSONモードが有効になります。
* JSONモードを使うときは、"常に"(always instruct)JSONを出力するようモデルに指示しなくてはなりません。例えば、システムメッセージを利用します。
* 会話がトークン数の上限を超えて処理が止まった例などでは、モデルが返却するJSONが部分的なものになる可能性があります。処理のなかに組み込む場合、finish_reasonをチェックした方がよいでしょう。`response.choices[0].finish_reason`
* JSONモードは、出力が特定のスキーマにマッチすることを保証しません。JSONとして正しく書かれており、エラーなくJSON形式でパースされることだけを保証します。

参考：[OpenAI: JSON mode](https://platform.openai.com/docs/guides/text-generation/json-mode)