こんにちは、CCCMKホールディングスTECH LABの井上です。
前回の記事からの積み残しであった、GPTsのActionsを使ってみました。
GPTsから見た今後のChatGPTへの期待 - CCCMKホールディングス TECH Labの Tech Blog
社内でも「簡単にデータの取得ができれば嬉しい」という声もあったこともあり、今回実現した内容は「自然言語でのテーブル情報の取得」です。 これ自体はpanadsA等Iを使ってもできるのですが、今回はAPIの機能はデータ抽出処理のみの前提で検討しました。 また、SQL+DBで実装したかったのですが、セキュリティ上の都合によりpandas+csvでの実装としました。
全体像としては以下の図のようになっています。

流れを簡単に説明すると
①プロンプトで指示する
②プロンプトからChatGPTにいい具合のpandasでの抽出処理を作ってもらう
③作った処理をAPIで受取って実行する
④処理結果を返す
⑤表示する
実行結果は以下のようになりました。

ちゃんと売上、店舗マスタ、商品マスタをjoinしてくれて動いてくれました。
以下がAPIの処理、インストラクション、スキーマの内容となります。
(1) APIの処理
pandasの処理を文字列で受け取りexec()で実行します。csvは最初に全て読み込んでおきます。
execについてはインジェクション攻撃などのリスクがあるので利用には注意が必要ですが、
今回は動作検証目的なので特に対応は施さずに実行します。
from flask import Flask import pandas as pd df_customer = pd.read_csv("customer.csv") #顧客情報 df_product = pd.read_csv("product.csv") #商品マスタ df_sales = pd.read_csv("sales.csv") #売上情報 df_sales_detail = pd.read_csv("sales_detail.csv") #売上明細 df_store = pd.read_csv("store.csv") #店舗マスタ app = Flask(__name__) @app.route('/dynamic', methods=['POST']) def post_dynamic(): data = request.get_json() # JSONデータの取得 dynamic_data = data['proc'] # 'proc'キーの値を取得 local_vars = {} exec(dynamic_data, globals(), local_vars) # 受取った処理実行 res = local_vars.get('res', 'Error: res not defined') res = res.to_json(orient='records') return str(res) if __name__ == '__main__': app.run(debug=True)
(2) インストラクション
一番苦労したのはインストラクションです。出来上がったものだけを見るとたいしたことないのですが、
なかなか思った通りの結果が出ませんでした。
このbotは必ずカスタムAPIを呼び出します。
処理の手順は以下の通りです。
1.プロンプトを理解する
2.プロンプトの指示に従って優秀なプログラマーに「プログラマーのタスク」を実行してもらう
3.2.で作られたコードをAPI(PostPythonCode)に渡す
4.処理されて戻ってきたデータを表形式で出力する
### プログラマーのタスク ###
下記のようにpandasに各CSVをデータフレームに読み込んだプログラムがあります。
このプログラムはプロンプトの指示に従って処理を行ってその結果を戻すAPIです。
プロンプトの指示に従って{variable_process}のpythonのコードを出力してください。
処理した最終結果のデータフレームはresという変数名に格納するようにコードを記述してください。
* コードが複数行になることもあることを考慮してください。
ここで生成された処理はexec(variable_process)として実行するために文字列として取り扱うことが必要です。
APIに引き渡すパラメータは{variable_process}となります。
また結果は人間に分かり易いようにコードでなく名称で表示されるようにしてください。
* 集計をするときのカラムの定義は「カラム定義ファイル.xlsx」にあるので必ず参照してください。ここに定義されたカラム以外は使わないでください。
### プログラム ###
import pandas as pd
# 下記のデータフレームが既に存在しているものとする
# df_customer #顧客情報
# df_product #商品マスタ
# df_sales #売上情報
# df_sales_detail #売上明細
# df_store #店舗マスタ
'''
{variable_process}
'''
return res
###
この中の指示で効果があったと考えるのは以下のものです。
・変数を#処理内容#から{variable_process}に変更したこと
・exec(variable_process)として使うことを明示したこと
・### プログラム ###の中のデータフレームの定義をコメント文の説明にしたこと。(pd.read_csv("**.txt")の処理の記述だと暴走をしてしまいました)
・特に気を付けてほしい指示文を""で強調したこと
やってみて改めてプロンプトとの面倒臭さ重要さが実感できました。
またKnowledgeとしてカラム定義書をアップロードしています。こちらはデータフレームのカラムを記述したものです。最初はカラムIDのみで日本語の説明を入れてなかったのですが、追加したことで動きが大きく改善されました。
■customer.csv (顧客マスタ)
user_id ユーザID
gender 性別
age 年齢
name 名前
area 居住地域
■product.csv (商品マスタ)
product_id 商品ID
product_name 商品名
genre_l 大ジャンル
genre_m 中ジャンル
genre_s 小ジャンル
■sales.csv (売上情報)
sales_id セールスID
user_id ユーザID
date 売上日
store_id 店舗ID
amount 売上金額
■sales_detail.csv (売上明細)
sales_id セールスID
detail_id 明細ID
product_id 商品ID
amount 売上金額
■store.csv (店舗マスタ)
store_id 店舗ID
store_nm 店舗名称
(3) スキーマ
Actionsのスキーマはこれといった特別なところはありませんが、 躓いた点としてレスポンスのcontentがapplication/jsonになってることに気づけずエラーが出たことぐらいです。 こちらはtext/plainにすることで解消しました。
{
"openapi": "3.0.0",
"info": {
"title": "Example API",
"version": "1.0.0"
},
"servers": [
{
"url": "https://dummy.net",
"description": "test of gpts actions"
}
],
"paths": {
"/dynamic": {
"post": {
"summary": "Dynamic Data Processing Endpoint",
"description": "Processes dynamic Python code and returns data.",
"operationId": "PostPythonCode",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"proc": {
"type": "string"
}
},
"required": [
"proc"
]
}
}
}
},
"responses": {
"200": {
"description": "Successful response",
"content": {
"text/plain": {
"schema": {
"type": "string"
}
}
}
},
"400": {
"description": "Invalid request"
},
"500": {
"description": "Server error"
}
}
}
}
}
}
まとめ
GPTsで簡単に外部のAPIを呼び出せるということが実感できました。
今後ChatGPTがAIのちょっとしたローコードツールに発展していく世界もあるのかな、と思います。
OpenAIのお家騒動とかあって心配になったこともありましたが、また来年のアップデートが楽しみです。