OpenAI Assistants APIでのPythonコード取得方法 コードインタープリターtips

<br>
前回投稿しましたGPTの新APIでコードインタープリターを利用する方法の続きです。

https://qiita.com/nekoniii3/items/7128e1d0cec06e3ff6a4

といっても残りはPythonコードの取得だけです。
（私が知らないだけでもっと奥深い機能がありそうですが…）

# コードインタープリター少しおさらい

コードインタープリター（Code Interpreter）は簡単に言えば
### **GPTがPythonでプログラミングする**

<br>というものでユーザからの問いかけでプログラミングが必要だとGPTが判断すれば、自身でコードを生成しバックグラウンドで実行。ユーザにその結果を提供します。

特に前回の通り、グラフなんかを簡単に描いてその画像を渡してくれて
**コードの生成・実行はバックグラウンドで行われる**ので、ユーザはPythonの知識がなくても何ら問題がないところがいいところです。

ただPythonを利用する人は何らかのアルゴリズムやちょっとした小技なんかを頼めばプログラムしてくれて便利なので、今回はそのバックグラウンドで行わているコードの取得方法を紹介します。

# データ変換を頼んでみる
今回はCSV形式のデータファイルをJSON形式のファイルに変換するのをやってもらいます。
こういうのさっとやってもらえると便利です。

前回と同じようなところは説明を省きます。

```Python:OpenAI Client作成
from openai import OpenAI
import os

os.environ["OPENAI_API_KEY"] = 'sk-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'

client = OpenAI()
```


```Python:CSVファイルのアップ
file_response = client.files.create(
    purpose="assistants",
    file=open("HIKAKIN2023.csv","rb"),
)

# IDを取得
file_id = file_response.id
```

```CSV:※CSVの中身（ヒカキン氏の2023年の動画の再生回数順データ）
Date,Title,View,Like,Comment
2023-02-04 20:30,風船飛ばしたら負け #Shorts,497162657,4982543,6384
2023-02-16 19:41,プレゼントゴムチャレンジ #Shorts #partygame #challenge,17108955,285850,2089
2023-02-26 20:19,カードゼロで水かけられるゲームw  #Shorts #partygame #challenge,15854422,214597,1688
2023-03-06 19:00,ハズレをえらぶな！  #Shorts,14415890,241939,1332
2023-08-01 19:00,20億円のヒカキン新居ハウスツアー！超巨大室内温水プール&庭付きの
…
…
```

```Python:スレッドの作成
thread = client.beta.threads.create()
```

```Python:スレッドへメッセージのセット
# 問い合わせ内容をセット
prompt = "次のcsvファイルのデータをjson形式のデータに直して下さい。"

message = client.beta.threads.messages.create(
    thread_id=thread.id,
    role="user",
    content=prompt,
    file_ids=[file_id]
)
```

```Python:RUN作成（アシスタントへ問い合わせ）
# アシスタントIDセット
assistant_id = "asst_******************"

run = client.beta.threads.runs.create(
  thread_id=thread.id,
  assistant_id=assistant_id
)
```
※アシスタントの作り方は前回を参照下さい。

```Python:RUNの状態を取得・確認
run = client.beta.threads.runs.retrieve(
  thread_id=thread.id,
  run_id=run.id
)

print(run.status)
```
```:表示結果
complete
```
※completeになるまで取得・確認を繰り返す

```Python:messages取得
messages = client.beta.threads.messages.list(
  thread_id=thread.id,
  order = "asc",
)
```

```Python:messages表示
for msg in messages:

    print(msg.role + "：")

    if msg.role == "assistant":

        for content in msg.content:

            file_id = ""
            assist_msg = ""

            cont_dict = content.model_dump()  # 辞書型に変換

            cont_text = cont_dict.get("text")
            
            if cont_text is not None:

                assist_msg = cont_text.get("value")

                print(assist_msg)

                # 注釈（参照ファイル）ががある場合取得
                if len(cont_text.get("annotations")) > 0:

                    annotations = cont_dict.get("text").get("annotations")

                    # ※今回は参照ファイル1つだけ取得
                    ant = annotations[0]

                    if ant.get("file_path") is not None:

                        # 参照ファイルのID取得
                        file_id = ant.get("file_path").get("file_id")

                        if ant.get("text") is not None:

                            # ファイル形式（拡張子）取得
                            ext = "." + ant.get("text")[ant.get("text").rfind('.') + 1:]

                            print(f"file_id:{file_id} ファイル形式:{ext}")

    else:

        print(msg.content[0].text.value)
```
```:出力結果
user：
  次のcsvファイルのデータをjson形式のデータに直して下さい。
assistant：
  CSVファイルのデータをJSON形式に変換しました。[こちら](sandbox:/mnt/data/youtube_data.json)からJSONファイルをダウンロードできます。
  .png fileid：
  .json fileid：file-tV7RhtY6D1mH6oPelAnrImHk
```


```Python:ファイルのダウンロード
res_file_id = 'file-tV7RhtY6D1mH6oPelAnrImHk'

retrieve_file = client.files.with_raw_response.retrieve_content(res_file_id)
content = retrieve_file.content

with open(file_id + ".json", 'wb') as f:
    f.write(content)
```

```:作成されたJSONファイル（file-tV7RhtY6D1mH6oPelAnrImHk.json）
[{"Date":"2023-02-04 20:30","Title":"\u98a8\u8239\u98db\u3070\u3057\u305f\u3089\u8ca0\u3051 #Shorts","View":497162657,"Like":4982543,"Comment":6384},{"Date":"2023-02-16 19:41","Title":"\u30d7\u30ec\u30bc\u30f3\u30c8\u30b3\u3099\u30e0\u30c1\u30e3\u30ec\u30f3\u30b7\u3099 #Shorts #partygame #challenge","View":17108955,"Like":285850,"Comment":2089},{"Date":"2023-02-26 20:19","Title":"\u30ab\u30fc\u30c9\u30bc\u30ed\u3067\u6c34\u304b\u3051\u3089\u308c\u308b\u30b2\u30fc\u30e0w  #Shorts #partygame #challenge","View":15854422,"Like":214597,"Comment":1688},{"Date":"2023-03-06 19:00","Title":"\u30cf\u30ba\u30ec\u3092\u3048\u3089\u3076\u306a\uff01  #Shorts","View":14415890,"Like":241939,"Comment":1332},{"Date":"2023-08-01 19:00","Title":"20\u5104\u5186\u306e\u30d2\u30ab\u30ad\u30f3\u65b0\u5c45\u30cf\u30a6\u30b9\u30c4\u30a2\u30fc\uff01\u8d85\u5de8\u5927\u5ba4\u5185\u6e29\u6c34\u30d7\u30fc\u30eb&\u5ead\u4ed8\u304d\u306e\u5bb6","View":11671539,"Like":276396,"Comment":18076},{"Date":"2023-03-28 18:30","Title":"2\u6cca3\u65e5\u97d3\u56fd\u65c5\u884c\u30675000\u4e07\u30a6\u30a9\u30f3\u4f7f\u3044\u5207\u308c\u308b\u307e\u3067\u5e30\u308c\u307e\u305b\u3093w\u3010\u30d2\u30ab\u30ad\u30f3TV\u30b9\u30da\u30b7\u30e3\u30eb\u3011","View":10253044,"Like":99752,"Comment":5120},…]

```

"Title"の日本語がちょっとおかしくなっていますが、それは一旦置いておいてこんな感じでGPTがデータを色々操作してくれます。ここまでは前回と同様の手順です。

ではここから、このデータ変換処理をしたコードを取得したいと思います。


# runs.stepsからコードの取得
GPTからの返答メッセージはclient.beta.threads.messagesから取得しましたが、それに至ったPythonコードは**client.beta.threads.runs.steps**から取得できます。

```Python:run_stepの取得
# thread.idとrun.idをセット
run_steps = client.beta.threads.runs.steps.list(
    thread_id=thread.id,
    run_id=run.id
)
```
```Python:run_stepの表示
for run_step in run_steps:

    print(run_step)
```
```:出力結果（長すぎるのでoutputs削除）
RunStep(id='step_rJsNzas8HY8vPD2y6LXy7GkL', assistant_id='asst_kVgHpOPgjGmVvgFlXnzMjjFN', cancelled_at=None, completed_at=1702295819, created_at=1702295810, expired_at=None, failed_at=None, last_error=None, metadata=None, object='thread.run.step', run_id='run_poPLLJ4EydDDuoa6dh7lMve9', status='completed', step_details=ToolCallsStepDetails(tool_calls=[CodeToolCall(id='call_C8IqmTHDHdCKZHLdZfk6x5Hb', code_interpreter=CodeInterpreter(input="import pandas as pd\r\n\r\n# Load the CSV file into a DataFrame\r\nfile_path = '/mnt/data/file-zrOruMNZETdk9KgzUNOAL01h'\r\ndata = pd.read_csv(file_path)\r\n\r\n# Convert the DataFrame to JSON format\r\njson_data = data.to_json(orient='records')\r\njson_data", outputs=[]), type='code_interpreter')], type='tool_calls'), thread_id='thread_BQVkHYJOvZboQ40WlCfrHEit', type='tool_calls', expires_at=None)
RunStep(id='step_zZtKn1uswdAW4j7ztap7veMR', assistant_id='asst_kVgHpOPgjGmVvgFlXnzMjjFN', cancelled_at=None, completed_at=1702295823, created_at=1702295819, expired_at=None, failed_at=None, last_error=None, metadata=None, object='thread.run.step', run_id='run_poPLLJ4EydDDuoa6dh7lMve9', status='completed', step_details=ToolCallsStepDetails(tool_calls=[CodeToolCall(id='call_0hYCWvHpq3mKEEYvvTD5r0Aa', code_interpreter=CodeInterpreter(input="# Let's write the JSON data to a file\r\njson_file_path = '/mnt/data/video_data.json'\r\n\r\nwith open(json_file_path, 'w') as json_file:\r\n    json_file.write(json_data)\r\n\r\njson_file_path", outputs=[CodeInterpreterOutputLogs(logs="'/mnt/data/video_data.json'", type='logs')]), type='code_interpreter')], type='tool_calls'), thread_id='thread_BQVkHYJOvZboQ40WlCfrHEit', type='tool_calls', expires_at=None)
RunStep(id='step_EowGIhckDLnh2gdWXu9ig6jv', assistant_id='asst_kVgHpOPgjGmVvgFlXnzMjjFN', cancelled_at=None, completed_at=1702295824, created_at=1702295823, expired_at=None, failed_at=None, last_error=None, metadata=None, object='thread.run.step', run_id='run_poPLLJ4EydDDuoa6dh7lMve9', status='completed', step_details=MessageCreationStepDetails(message_creation=MessageCreation(message_id='msg_CNspAXAGF4yvMeMBWNi0NcYK'), type='message_creation'), thread_id='thread_BQVkHYJOvZboQ40WlCfrHEit', type='message_creation', expires_at=None)
```

このrun_stepsはGPTの脳内（変な表現ですが）での思考の形跡のようなもので、今回は中に3要素あるので3回のステップでユーザの提起した問題の解決に至ったようです。

ただ中にはユーザへメッセージを表示するためのものなど、不要なものもあるのでこの中からソースコードを含んだものだけを取り出す用にします。
<br>

```Python:ToolCallsStepDetailsのインポート
from openai.types.beta.threads.runs import ToolCallsStepDetails
```

コードを含むかはrun_step内の**step_details**が**ToolCallsStepDetails**かどうかで判定するためimportしておきます。（「何それ」と言われても、そうなっているから致し方ないです…）

あとはisinstanceで判定してToolCallsStepDetailsならtool_calls[0].code_interpreter.inputからコードの中身を取得します。

```Python:コードの表示
for run_step in run_steps:

    if isinstance(run_step.step_details, ToolCallsStepDetails):

        print(run_step.step_details.tool_calls[0].code_interpreter.input)
        print("\n---------------------------------------------------\n")

```
```:出力結果
import pandas as pd

# Load the CSV file into a DataFrame
file_path = '/mnt/data/file-zrOruMNZETdk9KgzUNOAL01h'
data = pd.read_csv(file_path)

# Convert the DataFrame to JSON format
json_data = data.to_json(orient='records')
json_data

---------------------------------------------------

# Let's write the JSON data to a file
json_file_path = '/mnt/data/video_data.json'

with open(json_file_path, 'w') as json_file:
    json_file.write(json_data)

json_file_path

---------------------------------------------------

```

これで内部で行われたPythonコードを見ることができます。
やっていたことはpandasで読み込んでto_jsonで吐き出すだけという中々賢い方法。
（1行ずつ読み込むなんてことはしないんですね…）

※tool_calls[0]と1要素だけ取得していますが、もしかしたら複数の可能性が無きにしも非ずです…

あと最後にJSONの日本語がおかしいのを直してもらいます。

# データの日本語化
日本語が先頭に\uがつく[Unicodeエスケープ](https://una.soragoto.net/topics/12.html)というのになっているので、これが解消されるようにGPTにお願いするだけです。

```
prompt = "日本語で表示したいので、Unicodeエスケープを日本語に直してもらえますか？"

message = client.beta.threads.messages.create(
    thread_id=thread.id,
    role="user",
    content=prompt,
)
```
※この後RUN作成以降～コード取得は今までの手順と同じです。

```:出力結果
user
  日本語で表示したいので、Unicodeエスケープを日本語に直してもらえますか？
assistant：
  Unicodeエスケープを日本語に変換したJSONファイルを作成しました: [youtube_data_jp.json](sandbox:/mnt/data/youtube_data_jp.json)
  .png fileid：
  .json fileid：file-76ke1mO6kzs5gQBTwBbSaXle
```
```:JSONファイル
[{"Date":"2023-02-04 20:30","Title":"風船飛ばしたら負け #Shorts","View":497162657,"Like":4982543,"Comment":6384},{"Date":"2023-02-16 19:41","Title":"プレゼントコ&#12441;ムチャレンシ&#12441; #Shorts #partygame #challenge","View":17108955,"Like":285850,"Comment":2089},{"Date":"2023-02-26 20:19","Title":"カードゼロで水かけられるゲームw#Shorts #partygame #challenge","View":15854422,"Like":214597,"Comment":1688},{"Date":"2023-03-06 19:00","Title":"ハズレをえらぶな！#Shorts","View":14415890,"Like":241939,"Comment":1332},{"Date":"2023-08-01 19:00","Title":"20億円のヒカキン新居ハウスツアー！超巨大室内温水プール&庭付きの家","View":11671539,"Like":276396,"Comment":18076},{"Date":"2023-03-28 18:30","Title":"2泊3日韓国旅行で5000万ウォン使い切れるまで帰れませんw【ヒカキンTVスペシャル】","View":10253044,"Like":99752,"Comment":5120},{"Date":"2023-08-22 19:00","Title":"YOASOBI「アイドル」 Official Music Video HIKAKIN Ver.","View":9938152,"Like":346767,"Comment":36102},{"Date":"2023-02-12 17:01","Title":"風船飛ばしたら負け #Shorts #short #shortsvideo","View":9742595,"Like":144576,"Comment":800},{"Date":"2023-01-10 20:30","Title":"閉校になる母校にサプライズで登場してみた","View":7441993,"Like":160749,"Comment":6004},{"Date":"2023-05-25 19:30","Title":"まるおともふこがゾンビ化してしまいました…","View":7144517,"Like":113396,"Comment":5650},{"Date":"2023-07-02 21:00","Title":"もう限界です。強制休暇へ…【2023】","View":6569411,"Like":113670,"Comment":4829},{"Date":"2023-01-21 18:00","Title":"ポーズ被るかチャレンジ【相性チェックゲーム】 #shorts#andTEAM #hikakin","View":6360048,"Like":133838,"Comment":717},{"Date":"2023-04-27 20:00","Title":"ヒカキンのカップラーメン『みそきん』が5/9発売！初のブランド『HIKAKIN PREMIUM』立ち上げました。","View":6347919,"Like":164126,"Comment":11668},{"Date":"2023-08-06 20:00","Title":"【ドッキリ】20億円の新居に何も知らない家族呼んでみたw【サプライズ】","View":6248746,"Like":107925,"Comment":4621},{"Date":"2023-09-09 19:00","Title":"【ご報告】全治３ヶ月","View":5915653,"Like":120430,"Comment":12127},{"Date":"2023-05-08 21:00","Title":"【明日発売】超巨大みそきんYouTuberの家に突撃出前してみたwww","View":5891416,"Like":76236,"Comment":3941},{"Date":"2023-06-28 20:30","Title":"ヒカキンの熱40度超えルーティーン【高熱密着24時】","View":5691435,"Like":106348,"Comment":6898},{"Date":"2023-08-13 20:00","Title":"20億円の新居にまるお&もふこ連れてってみた反応【ヒカキン猫】","View":5663811,"Like":110202,"Comment":4626},{"Date":"2023-01-15 21:00","Title":"【大食い対決】24時間絶食 vs 24時間爆食のわんこそは&#12441;バトル【ヒカキン vs デカキン】","View":5549969,"Like":58333,"Comment":2275},{"Date":"2023-04-27 20:39","Title":"ヒカキンのラーメンが出来ました #みそきん","View":5501252,"Like":188862,"Comment":3088},{"Date":"2023-06-11 19:15","Title":"【お金無限】クレーンゲームでピンクのモノvs水色のモノどちらが多く取れるのか兄弟対決！【ヒカキン vs セイキン】","View":5127495,"Like":46497,"Comment":1639},{"Date":"2023-01-18 21:00","Title":"1000万円のポケカ福袋開封したら中身ありえない内容www【ポケモンカード】","View":4966721,"Like":54592,"Comment":2849},{"Date":"2023-04-27 20:39","Title":"ヒカキンのラーメンが出来ました #みそきん#ラーメン #shorts","View":4945614,"Like":194458,"Comment":3101},{"Date":"2023-09-03 19:00","Title":"Balloon Popping Challenge Race #game #Shorts","View":4948648,"Like":122405,"Comment":1421},{"Date":"2023-08-30 20:45","Title":"【総額？？？万円】20億円の新居に高級家電爆買いしてみた【開封レビュー】","View":4930453,"Like":68682,"Comment":2505},{"Date":"2023-06-18 19:00","Title":"風船飛ばしたら負け #Shorts","View":4863142,"Like":68278,"Comment":605},{"Date":"2023-03-09 19:30","Title":"【お金無限】カ&#12441;シャホ&#12442;ン3000台を本気で回し続けたら当たりまくりwww【ガシャポンのデパート】","View":4634900,"Like":42377,"Comment":1675},{"Date":"2023-07-29 18:00","Title":"今までありがとう。次の家へ。","View":4501411,"Like":80432,"Comment":5691},{"Date":"2023-07-07 20:15","Title":"【神回】ハズレなし当たりロッカー400個!?鹿児島の1000円ガチャ100回やってみたら大当たり連発!!!【10万円分】","View":4500784,"Like":45495,"Comment":2304},{"Date":"2023-09-07 20:00","Title":"【お金無限爆買い】ドンキ貸切にして20億円新居の日用品爆買いしてみた！【MEGAドン・キホーテ】","View":4296673,"Like":67605,"Comment":2534},{"Date":"2023-11-26 21:00","Title":"【強制休暇】日本一の豪華客船飛鳥Ⅱの200万円Sロイヤルスイート海の旅【ヒカキンTVスペシャル】","View":4129017,"Like":75090,"Comment":3816},{"Date":"2023-04-21 20:27","Title":"初めて活動お休みさせていたた&#12441;きます","View":4104509,"Like":132146,"Comment":9441},{"Date":"2023-10-23 19:00","Title":"ポーズ被るかチャレンジ!!【兎田ぺこら & ヒカキン】#兎田ぺこら #ヒカキン","View":4090382,"Like":122372,"Comment":3005},{"Date":"2023-03-12 21:15","Title":"【中身1000万超え】玄関に3ヶ月ためた超大量のダンボール全部開封www","View":4049688,"Like":44171,"Comment":1654},{"Date":"2023-05-23 20:00","Title":"黒歴史がバズってるので助けてください。。。","View":3716130,"Like":108494,"Comment":5227},{"Date":"2023-08-16 19:00","Title":"【15万円分】地元新潟ドンキの1000円&#12316;3000円ガチャ100回引いてみたら…【超豪華】","View":3504682,"Like":46135,"Comment":2698},{"Date":"2023-05-19 19:30","Title":"【お詫ひ&#12441;】みそきんについてのこ&#12441;報告","View":3497746,"Like":112861,"Comment":7148},{"Date":"2023-02-28 19:30","Title":"【超豪華回】第3回ダーツで刺さったところのラーメン食べに行く『47都道府県ラーメンダーツの旅』","View":3489342,"Like":56257,"Comment":3601},{"Date":"2023-04-14 19:00","Title":"【史上最大】超大量の魚介類で180cmの超巨大舟盛り作ったら美味すぎた…【ヒカキン×きまぐれクック コラボ】","View":3401619,"Like":41677,"Comment":1628},{"Date":"2023-05-05 20:00","Title":"【移動距離300km】家族にみそきんを発売前に出前してみたwww【新幹線】","View":3345915,"Like":63144,"Comment":3111},{"Date":"2023-08-25 19:00","Title":"YOASOBI「アイドル」 Official Music Video HIKAKIN Ver. メイキング","View":3317607,"Like":76144,"Comment":3446},{"Date":"2023-12-01 20:30","Title":"【9年ぶり】２代目ソファーが家にやってきた！【新居3箇所大改造】","View":3277250,"Like":71351,"Comment":3083},{"Date":"2023-09-29 19:00","Title":"【お見舞い】骨折中に20億円新居に初めて\"あの方\"が来てくれた…【感動】","View":3248925,"Like":54681,"Comment":3425},{"Date":"2023-01-29 20:00","Title":"【ランキング】マクト&#12441;ナルト&#12441;全商品業務用フライヤーで揚け&#12441;てみたw【マック】【ハッピーセット】","View":3210069,"Like":43205,"Comment":2229},{"Date":"2023-09-10 19:00","Title":"【ガチ折れ】骨折ト&#12441;ッキリを何も知らない家族にしてみたらwww","View":3197832,"Like":69840,"Comment":4313},{"Date":"2023-08-09 20:30","Title":"時は来た…みそきん8月10日から再販！(売り切れ御免)","View":3109545,"Like":72674,"Comment":9498},{"Date":"2023-09-16 21:10","Title":"許さねぇ。【骨折中】","View":3105181,"Like":82793,"Comment":5454},{"Date":"2023-08-31 21:15","Title":"【BBQ】20億円の新居で高級食材バーベキュー&花火したら最高すぎた…","View":3068321,"Like":52608,"Comment":2654},{"Date":"2023-10-03 21:00","Title":"【数千万円】20億円新居に????万円分の高級家具購入してヤバいことに!!!","View":3042605,"Like":57447,"Comment":3345},{"Date":"2023-07-25 19:30","Title":"【お金無限】東京最大級423台のクレーンゲームテーマパーク多摩の国で景品とりまくってみた【ヒカキン&セイキン】","View":3052318,"Like":33718,"Comment":1300}]
```

```:出力コード
# Convert the JSON data to a Python object and then back to a JSON string with Japanese characters
import json

# Load the JSON data
json_data_object = json.loads(json_data)

# Convert the Python object back to a JSON string with Japanese characters
json_data_jp = json.dumps(json_data_object, ensure_ascii=False, indent=2)

json_data_jp

---------------------------------------------------

# Save the JSON data with Japanese characters to a file
json_file_path_jp = '/mnt/data/youtube_data_jp.json'
with open(json_file_path_jp, 'w') as json_file_jp:
    json_file_jp.write(json_data_jp)

json_file_path_jp

---------------------------------------------------
```

# おわりに
今更ですが12/13まで[コードインタープリターは無料](https://openai.com/pricing)となっており、いよいよβ版ではなくなりそうで、多少勝手が変わるかもしれません…

そこまでガラリとは変わらないとは思いますが、変更があればまた記事にできればと思います！
<br>
また宣伝ですが、アプリもまだ無料で動かせるので是非試してみて下さい。

https://huggingface.co/spaces/nekoniii3/GPT_Chat_Code_Interpreter

動画でのAssistants APIの紹介もあります。

https://www.youtube.com/watch?v=tFmedAM1FM8