OpenAIのfunction callingを使って構造化されたダミーデータを生成する

# はじめに

OpenAI APIのfunction callingを使って、構造化されたダミーデータを生成してみました。

function callingによる構造化のよくある事例は、元になるテキストとデータ仕様を与えて、データ仕様に沿った情報を抽出する操作ですが、今回は、元になるテキストは与えず、データ仕様だけ与えて、そのデータ仕様に合致した構造化データをfunction callingで生成する、ということをやってみます。

以下のコードを実行してみたところ、指定した個数のダミーデータを得ることができました。

```Python
import openai
from openai_function_call import OpenAISchema
from typing import List
from pydantic import Field
import os
import dotenv

dotenv.load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")

class UserDetails(OpenAISchema):
    """User Details"""
    name: str = Field(..., description="User's name")
    age: int = Field(..., description="User's age")

class Users(OpenAISchema):
    """Users"""
    users: List[UserDetails] = Field(..., description="List of users")

completion = openai.ChatCompletion.create(
    model="gpt-3.5-turbo-0613",
    functions=[Users.openai_schema],
    messages=[
        {"role": "system", "content": "与えられたスキーマに沿って架空の人物を5名、生成してください"}
    ],
    function_call={"name": "Users"}
)

user_details = Users.from_response(completion)
print(user_details)
```

```
users=[UserDetails(name='John', age=25), UserDetails(name='Emily', age=30), UserDetails(name='Michael', age=35), UserDetails(name='Sophia', age=28), UserDetails(name='William', age=32)]
```

# コード解説
ダミーのユーザーデータをChatGPTに生成させるタスクを想定します。
ユーザは名前と年齢の情報を持つとします。

function callingに渡すためのデータ構造の定義のために、[openai_function_call](https://github.com/jxnl/openai_function_call)を使います。データ構造の定義がpythonライクに直感的に書けるので便利です。

ライブラリをインストールします。

```
pip install openai-function-call python-dotenv
```

データ構造を定義します。

```Python
class UserDetails(OpenAISchema):
    """User Details"""
    name: str = Field(..., description="User's name")
    age: int = Field(..., description="User's age")
```

function callingを実行してみます。

```Python
completion = openai.ChatCompletion.create(
    model="gpt-3.5-turbo-0613",
    functions=[UserDetails.openai_schema],
    messages=[
        {"role": "system", "content": "与えられたスキーマに沿って架空の人物を5名、生成してください"}
    ],
    function_call={"name":"UserDetails"},
)

user_details = UserDetails.from_response(completion)
print(user_details)
```

```
name='Alice' age=25
```

25歳のAliceというユーザデータを構造化済みの状態で得ることができました。
しかし、1回のコールで1データしか得られないため、効率が悪いです。

そこで、UserDetailsのリストが得られるように、データ構造を修正します。修正というか、UserDetailsのリストを要素にもつ新たなクラスUsersを定義します。

```Python
class Users(OpenAISchema):
    """Users"""
    users: List[UserDetails] = Field(..., description="List of users")
```

これをfunction callingに渡して、実行します。

```Python
completion = openai.ChatCompletion.create(
    model="gpt-3.5-turbo-0613",
    functions=[Users.openai_schema],
    messages=[
        {"role": "system", "content": "与えられたスキーマに沿って架空の人物を5名、生成してください"}
    ],
    function_call={"name": "Users"}
)

user_details = Users.from_response(completion)
print(user_details)
```
```
users=[UserDetails(name='John', age=25), UserDetails(name='Emily', age=30), UserDetails(name='Michael', age=35), UserDetails(name='Sophia', age=28), UserDetails(name='William', age=32)]
```

messagesで指定した個数(5個)のuser情報を1回のコールで得ることができました。

# おわりに
function callingを使って、構造化されたダミーデータを直接生成することを試してみました。
function callingが情報抽出に使えること自体はよく知られています（[参考1](https://gihyo.jp/article/2023/07/programming-with-chatgpt-04)、[参考2](https://note.com/kaku33/n/nb6bedcf6ce41)）が、元になるテキスト無しで構造化されたダミーデータを直接生成できるかどうかは、軽く調査した範囲では事例がなく、やってみる前はよくわかりませんでした。ちゃんと調べれば誰かは試していると思うのですが、自分で試したほうが早いので、やってみたところ、すんなり生成できることがわかりました。

ダミーデータ生成自体はChatGPTの有用な使い方の1つ（[参考1](https://internet.watch.impress.co.jp/docs/column/shimizu/1520407.html)、[参考2](https://qiita.com/YosukeIto/items/82529ae7f0f91cdf4570)）ですが、構造化のためのプロンプト工夫や後処理が面倒なことが多いため、今回の方法が有用なシーンはそこそこあるような気がします。

本記事がどなたかのお役に立てば嬉しいです。