Summary
- この記事は以下の記事の追加投稿です。
上記記事の
- 2-6. [応用シナリオ] Code interpreter(python版 & sql版)の実装詳細
にあたります。
この投稿で扱う自作アプリ
- Live demo:https://tomohiku-gpt-web.azurewebsites.net/
- Source code:https://github.com/tomohikue/ExpertImitater
- License:MIT なので、使えそうであればご自由にご利用ください
※ この記事で説明する Code Interprete を実装しており、コードも確認できます。
はじめに
ここで説明するのはOpenAI社が7月頃にリリースしたCode Interpreterのコンセプトを参考にしたPythonやSQLのコードを生成&実行するアプリです。OpenAI社のCode Interpreterを初めてみた時、そのアイディアに感動してその直後から2日間くらい他の事をそっちのけで、夢中になって実装しました。そしてその後、Python版だけでなく、SQLやExcelマクロVBAなどいろいろなパターンを試しました。これらの実装方法の詳細はOpenAI社からは公開されてなかったので、僕なりの設計で作っています。僕なりの設計にはなりますが、使える実装になったと考えておりますので、その仕組みと実装の説明を行います。
2-6. [応用シナリオ] Code interpreter(python版 & sql版)の実装詳細
2-6-1. 実装の考え方
処理の流れ
- OpenAIモデルを使ったCode Interpreterは、現状はOpenAIが実装方法を公開しておらず、かつ歴史が浅いため定番の開発手法が確立されていません。
- そのため、世の中にある類似アプリではそれぞれ違った実装がされています。
- この章で紹介するCode Interpreterの実装は、現時点において最適解と考えられる実装がされています
画面イメージ
CSVをアップロードするシナリオ
自分の保持しているCSVを、都度都度アップロードして使うシナリオ。
事前にCSVをプリセットしておくシナリオ
組織内で頻繁に利用されるCSVをプロセットしておくシナリオ。
RDBのデータをSQLで取得するシナリオ
CSVをプリセットしておくシナリオと画面イメージはほぼ同じのため、省略する。
- コードは、SQLとPythonの2つが生成される
- SQLはデータ取得に使われ、Pythonはビジュアル作成に使われる
2-6-2. 実装コードの説明
Pythonコードを生成させるプロンプト
以下のようなプロンプトをCSVデータなどから作り、GPT-4に投げる。
プロンプト全文
次の条件を満たしたpythonのコードを作成してください。
- コードの要件
- 2002年以降の米ドル、英ポンド、ユーロ、3つの為替をそれぞれの年あたり平均価格を集計してグラフで見せて。
- コードで順守すべき規約
- コードの出力フォーマットに関しての規約
- def python_function(df: pd.DataFrame) -> str:で定義されるFunction内のコードを出力してください。入力パラメータのdfはデータセット済みのpandasのデータフレームです。
- 出力するコードは必ずpython_function関数内だけにしてください。複数の関数を作らないでください。
- コードは分割して出力せず、1つのインラインとして出力してください。
- コード内に記述するコメントやラベル名も全て日本語で出力してください。
- コード内でバッククォートは使わないでください。バッククォートにより、Markdownが崩れる場合はあるためです。例:「```」
- このコードはインタラクティブなターミナルでは実行されないため、printなどの関数を使わず、変数名を指定しての出力は行わないでください。例:df.head()など
- コード内コメントに関しての規約
- コード内コメントは可能な限り詳しく説明してください。
- コード内コメントはプログラマーではない人でも利用できるよう専門的な用語はできるだけ少なくしてください。'データフレーム'は'入力データ'と呼んでください。
- コード内コメントは体言止めは使わず、リズムの良い文章にしてください。
- エラーハンドリングに関しての規約
- コードをエラーが発生していもエラーハンドリングはしないでください。try文は使用禁止です。
- コードでエラーが発生した場合はraise Exceptionで例外をスローしてください。
- 次のエラーが発生しやすいことが実績からわかっています。発生しないように考慮をしてください。
- Error1: Cannot mask with non-boolean array containing NA / NaN values
- Error2: Can only use .dt accessor with datetimelike values
- Error3: Could not convert
- Error4: 'DataFrame' object has no attribute 'append'
- pythonの 'DataFrame' object から 'append' attributeは削除されていますので、使わないでください。
- Pythonのコードは型定義を省略せず、型を意識してコードを考えてください。特に値の移動時に型変換が必要な場合はエラーが発生しないように回避するコードを加えてください。
- コード内でtriple-quoted string literalは使わないでください。
- コード内でpandasのdt アクセサを使用する場合は、列が datetime 型であることを確認してから使用してください。確認するコード例:if pd.api.types.is_datetime64_any_dtype(df['date']):
- importするライブラリに関する規約
- pandasはバージョン1.3.2で利用できる関数のみをコードにしてください。
- matplotlibはバージョン3.4.2で利用できる関数のみをコードにしてください。
- tabulateはバージョン0.8.9で利用できる関数のみをコードにしてください。
- XlsxWriterはバージョン1.4.3で利用できる関数のみをコードにしてください。
- ファイルの出力に関する規約
- ファイルの出力先はtempディレクトリ配下にしてください。tempディレクトリは既に存在する前提で考慮ください。
- Excelファイルを作成する場合は'with pd.ExcelWriter('temp\output.xlsx', engine='xlsxwriter') as writer:'を使ってください。
- matplotlibを使ったグラフに関する規約
- matplotlibを使ってグラフを作成する場合は'plt.show()'関数を使ってください。
- コード内でpadasのplot()を使った場合は必ずplt.show()もコード内に出力してください。
- matplotlibのグラフは凡例やラベルの数が多すぎると文字が重なり、読めなくなります。そのため、グラフや表は最大20件までのラベルになるようにデータ量を減らして作成してください。20件を超える場合は、削減する条件を明示してください。例:データ量が多いため上位20件にデータ を絞ってグラフ化しています。
- グラフや表は最大20件までの列データになるようにデータ量を減らして作成してください。
- グラフや表を作成するために利用したデータはto_excel()を使って'temp\output.xlsx'に出力してください。実行例:df_after_processing.to_excel('temp\output.xlsx', index=False)。Excelファイルを作成する理由は、グラフや表が正しいことを確認するために元となったデータをExcelで参照するためです。
- return値のoutputに関する規約
- returnは関数で最後に1つのみにしてください。
- return値のoutputに記載する内容は次のとおり。処理の成功時は処理内容の説明を詳細に記載してください。処理のエラー時はエラー内容を詳細に説明してください。また出力ファイル名は記載してはいけません。
- 表を出力の場合はprint関数は利用せず、markdown形式で回答を作成し、outputという名前の変数に格納してください。回答はoutputという名前の変数1つにまとめて格納してください。
- データフレームをoutputに格納する場合は.to_string()ではなく、.to_markdown()を使ってください。例:output = df.to_markdown()。Markdownで作成する理由はWeb画面上でHTMLに変換して質問者に提示する際、表や段落などがMarkdown形式の方が見やすいからです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74428/6d29a0bc-4aa6-021d-d7b8-2921538d657b.png)
- Input parameter 'df: pd.DataFrame' data layout
| | 0 |
|:-----------|:---------------|
| 日付 | datetime64[ns] |
| 米ドル | float64 |
| 英ポンド | float64 |
| ユーロ | float64 |
| カナダドル | float64 |
| スイスフラン | float64 |
| スウェーデンクローネ | float64 |
| デンマーククローネ | float64 |
| ノルウェークローネ | float64 |
| オーストラリアドル | float64 |
| ニュージーランドドル | float64 |
| 南アフリカランド | float64 |
| バーレーンディナール | float64 |
| 香港ドル | float64 |
| インドルピー | float64 |
| フィリピンペソ | float64 |
| シンガポールドル | float64 |
| タイバーツ | float64 |
| クウェートディナール | float64 |
| サウジアラビアリアル | float64 |
| UAEディルハム | float64 |
| メキシコペソ | float64 |
| インドネシアルピア | float64 |
| 台湾ドル | float64 |
- Input parameter 'df: pd.DataFrame' data types
| | 日付 | 米ドル | 英ポンド | ユーロ | カナダドル | スイスフラン | スウェーデンクローネ | デンマーククローネ | ノルウェークローネ | オーストラ リアドル | ニュージーランドドル | 南アフリカランド | バーレーンディナール | 香港ドル | インドルピー | フィリピンペソ | シンガポールドル | タイバーツ | クウェートディナール | サウジアラビアリア ル | UAEディルハム | メキシコペソ | インドネシアルピア | 台湾ドル |
|---:|:---------------------------------------------------|:----------------|:----------------|:----------------|:----------------|:----------------|:----------------|:----------------|:----------------|:----------------|:----------------|:----------------|:----------------|:----------------|:----------------|:----------------|:----------------|:----------------|:----------------|:----------------|:----------------|:----------------|:----------------|:----------------|
| 63 | <class 'pandas._libs.tslibs.timestamps.Timestamp'> | <class 'float'> | <class 'float'> | <class 'float'> | <class 'float'> | <class 'float'> | <class 'float'> | <class 'float'> | <class 'float'> | <class 'float'> | <class 'float'> | <class 'float'> | <class 'float'> | <class 'float'> | <class 'float'> | <class 'float'> | <class 'float'> | <class 'float'> | <class 'float'> | <class 'float'> | <class 'float'> | <class 'float'> | <class 'float'> | <class 'float'> |
- Input parameter 'df: pd.DataFrame' data desctibe
| | 米ドル | 英ポンド | ユーロ | カナダドル | スイスフラン | スウェーデンクローネ | デンマーククローネ | ノルウェークローネ | オーストラリアドル | ニュージーランドドル | 南アフリカランド | バーレーンディナール | 香港 ドル | インドルピー | フィリピンペソ | シンガポールドル | タイバーツ | クウェートディナール | サウジアラビアリアル | UAEディルハム | メキシコペソ | インドネシアルピア | 台湾ドル |
|:------|----------:|---------:|---------:|-----------:|----------:|-------------:|------------:|------------:|------------:|-------------:|-----------:|-------------:|-----------:|------------:|------------:|-----------:|------------:|-------------:|-------------:|-----------:|-----------:|------------:|------------:|
| count | 5246 | 5246 | 5246 | 5246 | 5246 | 5246 | 5246 | 5246 | 5246 | 5246 | 5246 | 5246 | 5246 | 5246 | 5246 | 5246 | 5246 | 5246 | 5246 | 5246 | 5246 | 5246 | 5246 |
| mean | 107.847 | 166.431 | 130.941 | 89.298 | 103.688 | 13.6557 | 17.5837 | 15.1177 | 84.0253 | 74.282 | 11.1241 | 286.447 | 13.8524 | 1.9547 | 2.20642 | 75.7914 | 3.15218 | 367.412 | 28.7641 | 29.3689 | 7.75177 | 0.981874 | 3.45823 |
| std | 14.3932 | 30.902 | 14.529 | 9.77905 | 17.9646 | 1.73289 | 1.94742 | 2.34644 | 8.878 | 9.31677 | 3.61682 | 38.2328 | 1.82366 | 0.439816 | 0.236486 | 10.2796 | 0.400264 | 42.2918 | 3.83411 | 3.91935 | 2.13331 | 0.203711 | 0.454073 |
| min | 75.76 | 117.54 | 94.23 | 70.45 | 76.16 | 10.43 | 12.67 | 9.37 | 56.56 | 45.28 | 5.65 | 201.17 | 9.76 | 1.39 | 1.74 | 58.14 | 2.42 | 274.63 | 20.21 | 20.63 | 4.31 | 0.65 | 2.51 |
| 25% | 101.63 | 141.083 | 121.775 | 82.05 | 87.86 | 12.36 | 16.36 | 13.3 | 78.64 | 67.07 | 8.06 | 269.775 | 13.1 | 1.6 | 2 | 65.93 | 2.77 | 346.022 | 27.1 | 27.67 | 5.9 | 0.83 | 3.24 |
| 50% | 109.385 | 157.43 | 130.64 | 86.415 | 101.355 | 13.26 | 17.55 | 14.67 | 83.32 | 75.6 | 9.945 | 290.415 | 14.03 | 1.79 | 2.2 | 76.82 | 3.18 | 367.65 | 29.18 | 29.78 | 7.18 | 0.92 | 3.5 |
| 75% | 116.895 | 191.65 | 138.183 | 96.12 | 114.53 | 14.84 | 18.55 | 16.6675 | 90.695 | 81.26 | 13.79 | 310.885 | 15.02 | 2.42 | 2.36 | 81.59 | 3.45 | 398.998 | 31.19 | 31.8375 | 9.7 | 1.15 | 3.67 |
| max | 150.26 | 250.57 | 169.62 | 124.98 | 166.5 | 18.49 | 22.75 | 21.8 | 107.66 | 97.61 | 19.69 | 398.67 | 19.14 | 3.07 | 2.81 | 107.82 | 4.18 | 483.99 | 40 | 40.91 | 14.81 | 1.47 | 4.68 |
- Input parameter 'df: pd.DataFrame' data head
| | 日付 | 米ドル | 英ポンド | ユーロ | カナダドル | スイスフラン | スウェーデンクローネ | デンマーククローネ | ノルウェークローネ | オーストラリアドル | ニュージーランドドル | 南アフリカランド | バーレーンディナール | 香港ドル | インドルピー | フィリピンペソ | シンガポールドル | タイバーツ | クウェートディナール | サウジアラビアリアル | UAEディルハム | メキシコペソ | インドネシアルピア | 台湾ドル |
|---:|:--------------------|-------:|-------:|-------:|--------:|---------:|-------------:|------------:|------------:|------------:|-------------:|-----------:|-------------:|-------:|---------:|----------:|-----------:|--------:|-------------:|-------------:|-----------:|---------:|------------:|-------:|
| 0 | 2002-04-01 00:00:00 | 133.15 | 189.79 | 116.12 | 83.48 | 79.28 | 12.87 | 15.63 | 15.08 | 71.14 | 58.8 | 11.76 | 353.65 | 17.07 | 2.73 | 2.61 | 72.21 | 3.07 | 434.14 | 35.52 | 36.26 | 14.81 | 1.37 | 3.82 |
| 1 | 2002-04-02 00:00:00 | 133.2 | 191.78 | 117.18 | 83.38 | 80.15 | 13 | 15.77 | 15.24 | 71.02 | 58.86 | 11.78 | 353.78 | 17.08 | 2.73 | 2.62 | 72.18 | 3.06 | 435.01 | 35.53 | 36.27 | 14.8 | 1.38 | 3.82 |
| 2 | 2002-04-03 00:00:00 | 133.2 | 191.26 | 116.96 | 83.65 | 80.02 | 12.95 | 15.75 | 15.23 | 71.14 | 58.81 | 11.89 | 353.78 | 17.08 | 2.74 | 2.62 | 72.12 | 3.04 | 436.58 | 35.53 | 36.27 | 14.77 | 1.37 | 3.82 |
| 3 | 2002-04-04 00:00:00 | 133.1 | 191.13 | 117.15 | 83.72 | 80.18 | 12.93 | 15.77 | 15.33 | 70.81 | 58.52 | 12.02 | 353.52 | 17.06 | 2.73 | 2.61 | 72.26 | 3.05 | 435.11 | 35.5 | 36.24 | 14.75 | 1.37 | 3.82 |
| 4 | 2002-04-05 00:00:00 | 132.3 | 189.74 | 116.32 | 82.96 | 79.47 | 12.87 | 15.65 | 15.22 | 70.33 | 58.36 | 11.83 | 351.39 | 16.96 | 2.71 | 2.6 | 71.85 | 3.04 | 432.21 | 35.29 | 36.03 | 14.66 | 1.37 | 3.8 |
Pythonコードを生成させるプロンプトの回答を加工する
上記のプロンプトにて返ってきた回答を加工し、Pythonコードが実行できるようにする
GPT-4からの回答文
以下の関数`python_function`は、指定されたデータフレーム`df`から2010年以降の為替データを抽出し、米ドル、英ポンド、ユーロの各通貨について、各年の平均価格を計算します。その結果をグラフ化し、PNGファイルとして保存します。また、処理した データフレームをExcelファイルとして保存します。
```python
import pandas as pd
import matplotlib.pyplot as plt
def python_function(df: pd.DataFrame) -> str:
# 日付列をdatetime型に変換
df['日付'] = pd.to_datetime(df['日付'])
# 2010年以降のデータを抽出
df = df[df['日付'].dt.year >= 2010]
# 年ごとにデータをグループ化し、各通貨の平均価格を計算
df_grouped = df.groupby(df['日付'].dt.year).mean()[['米ドル', '英ポンド', 'ユーロ']]
# 平均価格をグラフ化
df_grouped.plot(kind='line', title='年平均為替価格(2010年以降)')
plt.xlabel('年')
plt.ylabel('為替価格')
plt.legend(title='通貨')
# グラフをPNGファイルとして保存
plt.savefig('temp/output.png')
# 処理したデータフレームをExcelファイルとして保存
with pd.ExcelWriter('temp/output.xlsx', engine='xlsxwriter') as writer:
df_grouped.to_excel(writer)
# 処理内容と結果の説明を出力
output = """
以下の処理を行いました:
1. データフレームから2010年以降の為替データを抽出しました。
2. 米ドル、英ポンド、ユーロの各通貨について、各年の平均価格を計算しました。
3. 計算結果をグラフ化し、PNGファイル('temp/output.png')として保存しました。
4. 処理したデータフレームをExcelファイル('temp/output.xlsx')として保存しました。
"""
return output
``
この関数は、米ドル、英ポンド、ユーロの各通貨の年平均為替価格をグラフ化し、その結果を'image/output.png'という名前のPNGファイルとして保存します。また、処理したデータフレームを'output.xlsx'という名前のExcelファイルとして保存します。
生成されたPythonコードを実行するコード説明
以下のPythonコードで、GPT-4が生成したコードを実行する
Pythonコード全文
def exec_python_code(pycode:str,df:pd.DataFrame) -> str:
res:str = ""
try:
if pycode == None:
res = "コードが生成できませんでした。"
else:
exec(pycode,globals())
# 関数の存在確認
if callable(python_function):
res = python_function(df=df)
return res
else:
return "コードが生成できませんでした。"
except Exception as e:
res:str = f"Error message: {e}"
return res
def get_openai_res(question:str,temperature_value:float) -> str:
# OpenAI APIを呼び出してpythonコードを生成
return "created python code"
def get_retry_openai_res(question:str,temperature_value:float) -> str:
# OpenAI APIを呼び出してpythonコードを修正
return "fixed python code"
for i in range(3): # 3回までリトライする
if i == 0:
output = ""
pycode = get_openai_res(query,temperature_value)
else:
pycode = get_retry_openai_res(pycode,output,query,temperature_value)
# pythonコードを実行する関数の呼び出し
output = exec_python_code(pycode,df)
# outputに"Error"文言が含まれていない場合はループを抜ける
if "Error message:" not in output:
break
else:
print("生成されたコードがエラーのため、生成をリトライして再実行します")
Pythonコードを実行でエラーになった時にコードをGPT-4に修正させるプロンプト
GPT-4で生成したコードはたまにエラーになる(経験則だが5回に1回程度)。そのため、エラーになった場合は、GPT-4にエラーを修正される依頼をする。
以下のその際のプロンプト。
プロンプト全文
Pythonコードを実行するとエラーになりました。
実行したPythonコードとErrorメッセージを確認し、Pythonコードがエラーにならないように修正してください。
- 実行したPythonコード
```python
def python_function(df: pd.DataFrame) -> str:
# 2002年以降のデータに絞る
df = df[df['日付'].dt.year >= 2002]
# 年ごとの平均価格を計算
df['年'] = df['日付'].dt.year
df_avg = df.groupby('年').mean()
# グラフを作成
fig, ax = plt.subplots(figsize=(10, 6))
df_avg[['米ドル', '英ポンド', 'ユーロ']].plot(ax=ax)
ax.set_xlabel('年')
ax.set_ylabel('為替レート')
ax.set_title('為替レートの年平均価格')
plt.legend()
plt.savefig('temp\output_520.png', bbox_inches='tight')
return '為替レートの年平均価格のグラフを表示しました。'
plot_exchange_rate(df)
``‘
- Errorメッセージ
``‘
Error message: name 'plot_exchange_rate' is not defined
``‘
上記のPythonコードはGPT-4により生成しました。その時に使ったプロンプトを以下に記載します。コード修正の参考にしてください。
- コード生成に使用したプロンプト
``‘
- コードの要件
‐ 2010年以降の米ドル、英ポンド、ユーロ、それぞれの年あたり平均価格を集計してグラフで見せて。
-コードの要件の補足
‐ コードの要件が曖昧な表現の場合は、あなたが最適だと考える要件を想定し、それに置き換えて実行してください。またあなたが想定した要件について必ずコードのコメント内に記載してください。
- コードで順守すべき規約
----途中省略----
``‘
2-6-3. 実装の考慮ポイント
Pythonやライブラリのバージョン
- GTP-4は、2021年9月までの情報しか保持していないため、Pythonやライブラリもそのバージョンまでにする必要があります。
- これはGPT-4 Turboでも同じです。GPT-4 Turboは2023年4月まで学習しているとされていますが、コード生成に関して重点的に学習しているのは2021年9月までです。GPT-4 Turboに聞いてみてください、教えてくれます。
- Code Interpreterで実行される可能性のあるPythonライブラリは事前にPip InstallでInstallしておく必要があります。
よって、Versionは以下に設定します。
- python本体 Version: 3.9.7
- Import library Versionは以下
numpy==1.21.2
pandas==1.3.2
matplotlib==3.4.2
japanize-matplotlib==1.1.3
tabulate==0.8.9
openpyxl==3.0.7
xlsxwriter==1.4.3
このPythonのVersionを固定しないといけない問題ですが、ソフトウェア業界において重大な問題だと考えています。GPT-4は現状このバージョンだということはGithub CopilotやGPT-4を使ったコード支援ツールも同じだということです。なので、コード生成の恩恵を受けたいプログラマーは、Pythonやその他の言語においてもバージョンを上げないということです。
言語のバージョンが固定になってしまうと、新しい言語バージョンを作っても誰も使わない、お金にならないということですよね?だとすると言語のバージョンアップがスローペースになっていってしまい、それに引きずられてソフトウェア業界全体のバージョンアップがしにくくなるということが予想されます。
ちなみに僕はしばらくPythonのバージョンは上げないつもりです。新しいバージョンの機能は使いたいですが、それよりコード生成の精度が高いほうが恩恵が大きいので。本当は業界全体のために新しいモノを使っていくべきなのですが、「やはりコード生成の精度の方が。。。」。ジレンマですね。
速やかにコード生成AIの学習元のVersionもUpdateしてくれることを願っています。
たった1つのAIプロダクトなのに、業界に与える影響が大きすぎる。。。
PandasのDataframeでの日付型の取り扱い
PandasのDataframeでの日付型が、デフォルトだと想定どおりにCastされてくれないため、補正してやらないといけない。
- CSVからPandasのDataframeに値をセットした場合、日付型でセットされてほしい項目もString型でセットされる。そのため、以下のようなコードで日付型にCastする。
- 型を正確にGPT-4に伝えることは、コード生成において重要事項であるため、適切なCastを行う必要がある。
■ 次のコードで行っているCast
- テキストデータが、日付型に適した文字列だったら、Time型かDatetime型にキャストする。
- Date型(日付のみの型)はOpenaiが生成したコードでエラーになりやすいため、Datatime型にする。
def optimize_df_types(df:pd.DataFrame) -> pd.DataFrame:
# str型の項目に対して日付型に変換できるかどうかをチェックし、変換する
for col in df.select_dtypes(include=['object']).columns:
try:
if ':' in df[col].iloc[0]:
if len(df[col].iloc[0]) == 5: # 5桁の場合は秒を補完する
df[col] = df[col].apply(lambda x: str(x) + ':00' if len(str(x)) == 5 else x)
df[col] = pd.to_datetime(df[col]).dt.time
print(f'{col}: Converted to time')
elif len(df[col].iloc[0]) == 8:
df[col] = df[col].apply(lambda x: str(x) if len(str(x)) == 8 else x)
df[col] = pd.to_datetime(df[col]).dt.time
print(f'{col}: Converted to time')
elif '/' in df[col].iloc[0] or '-' in df[col].iloc[0]:
df[col] = pd.to_datetime(df[col])
print(f'{col}: Converted to datetime')
else:
print(f'{col}: Should not converted to datetime')
else:
this_year:str = datetime.now().strftime("%Y")
if '/' in df[col].iloc[0]:
df[col] = df[col].apply(lambda x: this_year + '/' + str(x) if len(str(x)) == 5 else x) # 5桁の場合は年を補完する
df[col] = pd.to_datetime(df[col])
print(f'{col}: Converted to date')
elif '-' in df[col].iloc[0]:
df[col] = df[col].apply(lambda x: this_year + '-' + str(x) if len(str(x)) == 5 else x) # 5桁の場合は年を補完する
df[col] = pd.to_datetime(df[col])
print(f'{col}: Converted to date')
else:
print(f'{col}: Should not converted to datetime')
except ValueError:
print(f'{col}: Cannot be converted')
return df
コードで実行された処理内容を、利用者に提示する
- Pythonコードを読める利用者はPythonコードの参照で、加工内容を知ることができるが、読めない方に向けてもできる限り処理内容を伝える考慮をする。
- Pythonコードに記載されたコメントを抽出し、画面に表示することで利用者に処理内容を提示する。
- Pythonコードが読める人向けにPythonコードもデフォルトは隠した状態で表示できるようにする。
2-6-4. インフラでの考慮ポイント
Code Interpreterの実行環境(パフォーマンス観点)
- 利用者増えるとPythonコードの実行環境の負荷が大きくなる
- その負荷を受けるハードウェアをどこで受け止めるかを考慮する。
2-6-5. pythonより大量データの扱うシナリオ。SQL&Pythonコード生成シナリオ
大量データを扱うために
- 通常のデータベースで扱うようなデータは、大量である場合が多く、一般的なPython環境では、処理性能&レスポンス性能が不十分であることが多い
- そのため、「パフォーマンスがデータ量に依存する処理」である抽出&加工処理は、それを得意とするプラットフォームを利用するのが望ましい
■ データ取得はSQL、ビジュアライズはPythonと役割分担をすると大量データを扱える
SQLコードを生成させるプロンプト
SQLコードの生成プロンプトは、Pythonのように長い規約を書かなくても品質の高いコードが生成される。※SQLは30年くらいからほぼコマンドが変わってないからだと思われる。
SQL全文
SQLのコードを以下の条件に合わせて作成してください。
- 要件
業者別の商品の販売金額を集計し、金額の多い順に10件取り出し、グラフ化してください。
- DBMS
PostgreSQL
- table name
purchasing.販売情報
- data rayout
| | ORDINAL_POSITION | COLUMN_NAME | DATA_TYPE | CHARACTER_MAXIMUM_LENGTH |
|---:|-------------------:|:---------------|:----------------------------|---------------------------:|
| 0 | 1 | 注文id | integer | nan |
| 1 | 2 | リビジョン番号 | smallint | nan |
| 2 | 3 | ステータス番号 | smallint | nan |
| 3 | 4 | 従業員番号 | integer | nan |
| 4 | 5 | 氏名 | text | nan |
| 5 | 6 | 業者id | integer | nan |
| 6 | 7 | 業者名 | character varying | 50 |
| 7 | 8 | 販売方法id | integer | nan |
| 8 | 9 | 販売方法 | character varying | 50 |
| 9 | 10 | 注文日 | timestamp without time zone | nan |
| 10 | 11 | 販売日 | timestamp without time zone | nan |
| 11 | 12 | 販売価格 | numeric | nan |
| 12 | 13 | 税額 | numeric | nan |
| 13 | 14 | 送料額 | numeric | nan |
SQLコードを生成させるプロンプトの回答からSQLを取り出す
画面ビジュアルを作るためにPythonコードを生成させるプロンプト
SQLで処理する部分をPythonでは生成されないようにプロンプトを作る
Pythonコード全文
次の条件を満たしたpythonのコードを作成してください。
- コードの要件
- 業者別の商品の販売金額を集計し、金額の多い順に10件取り出し、グラフ化してください。
- コードで順守すべき規約
- コードの出力フォーマットに関しての規約
- def python_function(df: pd.DataFrame) -> output:str:で定義されるFunction内のコードを出力してください。 入力パラメータのdfはデータセット済みのpandasのデータフレームです。 返却 値のoutputには処理内容と結果の説明を出力してください。
- コード内にはCSVを取り込む処理は含めず、データフレームの作成の処理はいりません。すでにdf = pd.DataFrame(data)を実行し、データフレームのdfという名前の変数にデータが入っている前提でコードを作成してください。
- 出力するコードはpython_function関数のみで、python_function関数は呼び出すコードを出力しないでください。
- コードは分割して出力せず、1つのインラインとして出力してください。
- コード内に記述するコメントやラベル名も全て日本語で出力してください。
- コード内でバッククォートは使わないでください。バッククォートにより、Markdownが崩れる場合はあるため。例:「```」
- このコードはインタラクティブなターミナルでは実行されないため、printなどの関数を使わず、変数名を指定しての出力は行わないでください。例:df.head()など
- エラーハンドリングに関しての規約
- コードをエラーが発生していもエラーハンドリングはしないでください。try文は使用禁止です。
- コードでエラーが発生した場合はraise Exceptionで例外をスローしてください。
- 次のエラーが発生しやすいことが実績からわかっています。発生しないように考慮をしてください。
- Error1: Cannot mask with non-boolean array containing NA / NaN values
- Error2: Can only use .dt accessor with datetimelike values
- Error3: Could not convert
- Error4: 'DataFrame' object has no attribute 'append'
- pythonの 'DataFrame' object から 'append' attributeは削除されていますので、使わないでください。
- Pythonのコードは型定義を省略せず、型を意識してコードを考えてください。特に値の移動時に型変換が必要な場合はエラーが発生しないように回避するコードを加えてください。
- コード内でtriple-quoted string literalは使わないでください。
- コード内でpandasのdt アクセサを使用する場合は、列が datetime 型であることを確認してから使用してください。確認するコード例:if pd.api.types.is_datetime64_any_dtype(df['date']):
- importするライブラリに関する規約
- pandasはバージョン1.3.2で利用できる関数のみをコードにしてください。
- matplotlibはバージョン3.4.2で利用できる関数のみをコードにしてください。
- tabulateはバージョン0.8.9で利用できる関数のみをコードにしてください。
- XlsxWriterはバージョン1.4.3で利用できる関数のみをコードにしてください。
- ファイルの出力に関する規約
- ファイルの出力先はtempディレクトリ配下にしてください。tempディレクトリは既に存在する前提で考慮ください。
- Excelファイルを作成する場合は'with pd.ExcelWriter('temp\output.xlsx', engine='xlsxwriter') as writer:'を使ってください。
- matplotlibを使ったグラフに関する規約
- matplotlibを使ってグラフのためのpngファイルを作成する場合は'plt.savefig('temp\output.png')'関数を使ってください。
- コード内でpadasのplot()を使った場合は必ずplt.savefig('output.png')もコード内に出力してください。
- matplotlibのグラフは凡例やラベルの数が多すぎると文字が重なり、読めなくなります。そのため、グラフや表は最大20件までのラベルになるようにデータ量を減らして作成してください。20件を超える場合は、削減する条件を 明示してください。例:データ量が多いため上位20件にデータを絞ってグラフ化しています。
- グラフや表は最大20件までの列データになるようにデータ量を減らして作成してください。
- グラフや表を作成するために利用したデータはto_excel()を使って'temp\output.xlsx'に出力してください。実行例:df_after_processing.to_excel('temp\output.xlsx', index=False)。Excelファイルを作成する理由は、グラフや表が正しいことを確認するために元となったデータをExcelで参照するためです。
- return値のoutputに関する規約
- returnは関数内に1つのみです。
- return値のoutputに記載する内容は次のとおり。処理の成功時は処理内容の説明を詳細に記載してください。処理のエラー時はエラー内容を詳細に説明してください。
- 表を出力の場合はprint関数は利用せず、markdown形式で回答を作成し、outputという名前の変数に格納してください。回答はoutputという名前の変数1つにまとめて格納してください。
- データフレームをoutputに格納する場合は.to_string()ではなく、.to_markdown()を使ってください。例:output = df.to_markdown()。Markdownで作成する理由はWeb画面上でHTMLに変換して質問者に提示する際、表や段落などがMarkdown形式の方が見やすいからです。
- Input parameter 'df: pd.DataFrame' の呼び出し元のコード
```python
# PostgreSQLに接続
conn = psycopg2.connect(
host="posgre.postgres.database.azure.com",
port="5432",
dbname="adventureworks",
user="user@posgre",
password="Password1234"
)
# カーソルを取得
cur = conn.cursor()
sqlcode ='''
SELECT
"業者名",
SUM("販売価格") AS total_sales_amount
FROM
purchasing."販売情報"
GROUP BY
"業者名"
ORDER BY
total_sales_amount DESC
LIMIT 10;
'''
# SQLクエリを実行
cur.execute(sqlcode)
# 結果を取得
result = cur.fetchall()
df = pd.DataFrame(result)
# 列名を自動的に取得
columns = [desc[0] for desc in cur.description]
df.columns = columns
# PostgreSQLとの接続を切断
cur.close()
conn.close()
output:str = python_function(df)
``‘
- Input parameter 'df: pd.DataFrame' data rayout
| | 0 |
|:-------------------|:-------|
| 業者名 | object |
| total_sales_amount | object |
- Input parameter 'df: pd.DataFrame' data types
| | 業者名 | total_sales_amount |
|---:|:--------------|:--------------------------|
| 7 | <class 'str'> | <class 'decimal.Decimal'> |
- Input parameter 'df: pd.DataFrame' data head
| | 業者名 | total_sales_amount |
|---:|:----------------------------------|---------------------:|
| 0 | Superior Bicycles | 4.5559e+06 |
| 1 | Professional Athletic Consultants | 3.05877e+06 |
| 2 | Chicago City Saddles | 3.02911e+06 |
| 3 | Jackson Authority | 2.55324e+06 |
| 4 | Vision Cycles, Inc. | 2.51374e+06 |
2-6-6. コードの生成品質の問題点
1. コード生成により起こる問題点
- 要件どおりに処理が生成されていない
- エラーになる
- 上記の問題が発生する要因
- 要件の伝え方(プロンプト)が悪い
- コード生成の環境が悪い
- AIが学習できていない
- 学習データがない
「2. エラーになる」問題は、上記の要因を見直せば、発生をほぼ無くせると考えられる。
2. コードを書く時にプログラマーが考えるべきこと
「1. 要件どおりに処理が生成されていない」問題に対処し、品質を上げるためには以下のようなことを考える必要がある
-
どこまでがGTP-4が知識をもっているか
-
エンジニアじゃないとできないか
-
一般利用者しか知りえないことについては、見本となるテンプレートを用意し、充分な情報を書いてもらうように促す。
2-6-7. Code Interpreterの課題点
- OpenAI本家のCode Interpreter(新Advanced Data Analysis)と自前開発の使い分け
- OpenAI本家での機能は、「汎用的な利用目的」「開発投資額が膨大のため多機能」であるため、汎用目的での利用であればOpenAI本家の方が使う方が良いという判断もある。
- 自前開発の利点は、カスタマイズ性が高い点。ただし、多機能すぎる要求はスクラッチ開発現場の開発スキルやコストでは難しいと考えられるため、特定のシナリオに特化し、開発難易度を下げる必要がある
- 画面での回答遅い
- まずGPT-4の回答が遅い。プロンプトが複雑になるため、おおむねAOAIで20~60秒、OpenAI本家で40~90秒かかる ※2023年10月時点の計測。今後大きく変わる可能性は十分あり。
- 実装によっては複数回のGPT-4呼び出しが必要になり、回数分だけ待ち時間が増える
- 処理内容に対しての信頼度をどうあげるか。利用者の意図どおりの加工になっているか。
- 前章のように、可能な限りの処理説明は付与するが、信頼度を上げるにはPythonのコードを読むしかない
- Excelのようにコード理解のスキルが利用者側に広がるまでは、時間がかかるので懸念がある
- Python実行環境のパフォーマンスへの考慮
- 他の処理に比べて、Pythonによる加工処理は負荷が大きいため、それを受け付ける仕組みやハードウェアリソースが必要
- CSVアップロードにする場合の課題
- CSVのフォーマットは利用者に委ねることになる。例えば、項目名やプロンプトに「金額」という言葉がないが、依頼文に「売上金額を教えて」と聞いてしまう人がたくさんいると考えられる。GTP-4はある程度推測して答えてくれるが、精度はインプットデータの文脈なので、利用者にもリテラシーが求められてしまう
- 本番化の世の中的な実績が浅い
- 歴史の浅い機能のため、本番化に向けておよび本番運用において、技術課題が発生する可能性が高い。都度対処していく必要がある
2-6-8. Excelを編集するマクロ的なCode Interpreter
- 以下2通りの方法でExcelのセルを編集したりコピーしたりするCode生成を行い実行させました。
- Excelのマクロとして実行するVBAコードの生成
- Excelのセルを編集するPythonコードの生成
どちらとも実用に耐える制度でのコード生成はできませんでした。
5回に1回くらい、ほぼほぼ正解のコードを書く程度の精度が限界でした。
原因は、おそらく学習データの中にあるサンプルコードが世の中にほとんど公開されてないからだと考えられます。残念な結果でした。
最後に
CodeをAIが生成してくれるようになるなんて、OpenaAIが出てくるまで想像もしませんでした。しかも、まさかこんな高い精度で。人間は曖昧に物事を言われても、曖昧のままで何となく理解してしまう不思議な生き物ですが、その曖昧さをAIを獲得するとは思っていませんでした。「二人でボールを叩き合うみたいなゲーム作って」といった曖昧な言葉でプログラミングの依頼をしても、ぜんぜん曖昧じゃないプログラムコードを書いて返してきます。
この現象を見て、曖昧なままでの会話とは「超高度な知能によるコミュニケーション」であることに気づきました。なぜなら、曖昧な部分をそれぞれが保管しあって会話しているということなので。
AIがすごいということもありますが、やっぱり人間がすごいですね。そして、こんな超高性能人間を設計した神様に感服しています。
Code Interpreterという実験テーマ、非常に面白かったです。この技術領域はGithub workspaceとか、さら高度なテクノロジーもでてきますし、もうちょっと長く楽しめそうな技術領域ですね。今回はインタプリタ―言語ばかりでしたが、次はC#などのコンパイラタイプの言語も実用性を計っていきたいと考えています。
おわり。