【Function Calling】プロンプトエンジニアリングを使ってテキストをJSON化する【ChatGPT】

:::note info
この記事は、6/26開催のAIねじ伏せプロンプト選手権で発表したLT資料を技術記事化したものになります。
:::
https://zenn.dev/algorithms/articles/b3f0072d3c3d8c

# はじめに
テキストをJSON化させるのは、**データ分析の前処理**などで利用できます。
例えば、自由記述の文章から分析で使いたいデータをJSONで抽出して、**分析可能な表形式データにしたい**などのケースがあります。

 ![スクリーンショット 2023-06-27 20.56.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234396/69b58c0c-26ab-b3a5-61c9-3e180ff3f92b.png)

# 例題1
`月収: 20万円 ~ 30万円` というテキストから、以下のような形式でJSONデータを抽出しようと思います。

```json
{
   "minimum_monthly_salary": 200000,
   "maximum_monthly_salary": 300000
}
```

## Function CallingでJSONにする

**Function Calling**を利用することで、JSON形式のデータ抽出が容易に可能です。

```python
functions = [
 {
        "name": "extract", 
        "description": "テキストからJSONを抽出する", 
        "parameters": {
            "type": "object",
            "properties": {
                "minimum_monthly_salary": { 
                    "type": "number",
                    "description": "", 
                },
                "maximum_monthly_salary": {
                    "type": "number",
                    "description": "",
                },
            },
            "required": ["minimum_monthly_salary", "maximum_monthly_salary"], 
        },
    }
]

function_call = {"name": "extract"}
response = openai.ChatCompletion.create(
            model="gpt-3.5-turbo-0613",
            messages=[
                {
                    "role": "user",
                    "content": text
                },
            ],
            functions=functions,
            function_call=function_call

    )
```


![スクリーンショット 2023-06-27 21.04.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234396/263b145c-5f00-a89c-e350-54cbd3814c52.png)

単純に**カラム名を指定しているだけ**ですが、ChatGPTが勝手に解釈をしてその形式でJSONを返してくれました。

# 例題2
続いて`年収: 500万円` というテキストから、**月収に計算**してJSON化させます。
![スクリーンショット 2023-06-27 21.07.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234396/3990323c-6a42-1385-36e8-b2c372f2a97c.png)

## 先ほどのFunction Callingをすると・・・
特に何も変更せず、先ほどのFunction CallingでJSON化させると、以下のように、よく分からない結果が返ってきました。

![スクリーンショット 2023-06-27 21.10.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234396/0442c235-c33e-b63f-9ce7-09c8a8e845a5.png)


## Chain Of Thoughtを入れてみる
![スクリーンショット 2023-06-27 21.11.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234396/ab9b0524-3dee-d56e-0a16-ac4a68a00c5d.png)

プロンプトエンジニアリングのテクニックに、**Chain Of Thought**と呼ばれるものがあります。
これはただ単純に答えを出力させるのではなく、**計算過程や思考過程を出力させることで計算精度が上がる**というものです。

シンプルにJSONに`thought_proess`(思考回路)というカラムを追加してあげます。

```python
functions = [
 {
        "name": "extract", 
        "description": "テキストからJSONを抽出する", 
        "parameters": {
            "type": "object",
            "properties": {
                "thought_process": {
                    "type": "string",
                    "description": "", 
                },
                "minimum_monthly_salary": { 
                    "type": "number",
                    "description": "", 
                },
                "maximum_monthly_salary": {
                    "type": "number",
                    "description": "",
                },
            },
            "required": ["minimum_monthly_salary", "maximum_monthly_salary", "thought_process"], 
        },
    }
]
```

その結果、きちんと12で割った計算結果が入ってきました。

![スクリーンショット 2023-06-27 21.13.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234396/1446751f-95a7-c8da-2ecb-dc8c295d7bcd.png)

## FewShotをさせる
もう一つ有効なプロンプトエンジニアリングに**Few Shot**を使いましょう。
具体例をChatGPTに与えると、それに沿ってその後の回答をしてくれます。
以下のように、年収400万円というテキストが入ってきた時に、どのようなJSONを返して欲しいのかを具体例で渡してあげます。

```python
examples = [
    {"role": "user", "content": "年収400万"},
    { "role": "assistant", "content": None, 
     "function_call": { 
         "name": "extract", 
         "arguments": '''
            {
            "minimum_monthly_salary": 333333.33,
            "maximum_monthly_salary": 333333.33,
            "thought_process": "
                We can calculate the minimum and maximum monthly salary by dividing the annual salary by 12. 
                We need to convert the amount from Japanese characters to a number."
            }''' 
      } 
    }
]

function_call = {"name": "extract"}
response = openai.ChatCompletion.create(
            model="gpt-3.5-turbo-0613",
            messages=[
                *examples,
                {
                    "role": "user",
                    "content": text
                },
            ],
            functions=functions,
            function_call=function_call
```

例で与えたように小数第2位まで計算してくれ、`thought_process`もこちらの意図通りの思考回路で計算してくれたことが記述されています。

![スクリーンショット 2023-06-27 21.17.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234396/e2a08693-bda3-6897-0162-e4703b433a08.png)

# 更なる改善
## ログを残す
AIあるあるですが、**精度100%にはなりません**。
全く想定外のアウトプットを返すことがあるので、きちんと**ログをとって改善**に繋げましょう。

![スクリーンショット 2023-06-27 21.23.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234396/9890a006-67de-925a-b7a5-8f4c95821b41.png)

## リグレッションテストをする
自由記述な文章はいろいろなパターンがあるため、うまくいかなかったものはFunctionsの定義を詳細に書いたりFew Shotの追加で対応していきます。 (このあと完全歩合制など書いてある求人が出てきたりします・・・)
このようにプロンプトを改善していると、デグレしていないか気になってきますよね。
そこで対応したいケースをカバーした**リグレッションテストを記述**することをお勧めします。
自動テスト化させると、毎回OpenAIとのAPIが発生してコストがかかるので、全く同じリクエストであれば同じレスポンスを返すキャッシュサーバを配置することでAPIコストを削減できます。

この辺は弊社プロダクトの[LangCore](https://langcore.studio.site/langcore)を活用していただくと、ログやキャッシュを行ってくれます(宣伝)

# 結論
1️⃣　Function CallingでJSONにする
2️⃣　Chain Of Thoughtで思考回路を出力させる
3️⃣　Few Shotで具体例を与えてこちらの意図を伝える
4️⃣　ログやテストで改善

# 最後に
![スクリーンショット 2023-06-27 20.23.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234396/35e32a23-fd02-77a9-c3cc-d3d0b0a2021d.png)

今回の内容は実案件で実施した内容になっています。LTでの登壇や今回の技術記事化に快く承諾していただいたフォワード様ありがとうございました。
フォワード社はかなり最先端なChatGPT APIを活用してプロダクトを作っていますので、副業等興味ある方はぜひ[Twitter](https://twitter.com/nagny)から連絡してください！

https://twitter.com/nagny