OpenAI APIのAssistants APIを使用して、ファイルのやり取りを含むアシスタントを作成してみた。

2024.02.23

こんちには。

データアナリティクス事業本部 機械学習チームの中村(nokomoro3)です。

今回は、OpenAI APIのAssistants APIを使用して、ファイルのアップロードやダウンロードなどのやり取りを含むアシスタントを作ってみたいと思います。

Code Interpreterとは

Code Interpreterはサンドボックス化された実行環境でPythonコードを記述し、実行することができます。

Assistants APIを使用してこのCode Interpreterが有効なアシスタントを作成することができます。

このツールは、多様なデータやフォーマットのファイルを処理し、データやグラフの画像を含むファイルを生成でき、 アシスタントが自身が実行に失敗するコードを生成した場合、コードの実行が成功するまで別のコードの実行を試みるなど、ある程度自律的に動作をすることができます。

Assistants APIを使用してCode Interpreterを使う方法は以下に記載されています。

また、私も過去に以下の記事を書いています。

Assistantにファイルを渡す

AssitantはCode Interpreterなどを使用する際に、ファイルからデータを読み込むことができます。

これは、大量のデータをアシスタントに提供したい場合や、ユーザが分析用に自分のファイルをアップロードできるようにしたい場合に便利です。

ファイルの渡し方としては、Assistantレベルの渡し方、Threadレベルの渡し方などがあります。

ファイルのサイズは最大512MBまでで、.csv.json.pdfなどの拡張子に対応しています。

対応しているファイルのMIME typesは以下を参照ください。

Assistantからファイルを受け取る

APIのCode Interpreterは、イメージやCSV、PDFの生成など、ファイルの出力も行います。

Code Interpreterでファイルが出力されると、レスポンスのfile_idでこのファイルの中身をAPI経由でダウンロードできます。

from openai import OpenAI

client = OpenAI()

image_data = client.files.content("file-abc123")
image_data_bytes = image_data.read()

with open("./my-image.png", "wb") as file:
    file.write(image_data_bytes)

またファイルのリンクは、メッセージ上は別の文字列で表現されているため、ユーザに公開可能なリンクに上記のファイルをダウンロードして配置して、公開可能なリンクでメッセージを置き換える必要があります。

以下の[Download Shuffled CSV File](sandbox:/mnt/data/shuffled_file.csv)の部分が該当の置き換えるべき対象です。こちらには、start_indexとend_indexを使ってアクセスすることができます。

{
  "id": "msg_3jyIh3DgunZSNMCOORflDyih",
  "object": "thread.message",
  "created_at": 1699073585,
  "thread_id": "thread_ZRvNTPOoYVGssUZr3G8cRRzE",
  "role": "assistant",
  "content": [
    {
      "type": "text",
      "text": {
        "value": "The rows of the CSV file have been shuffled and saved to a new CSV file. You can download the shuffled CSV file from the following link:\n\n[Download Shuffled CSV File](sandbox:/mnt/data/shuffled_file.csv)",
        "annotations": [
          {
            "type": "file_path",
            "text": "sandbox:/mnt/data/shuffled_file.csv",
            "start_index": 167,
            "end_index": 202,
            "file_path": {
              "file_id": "file-oSgJAzAnnQkVB3u7yCoE9CBe"
            }
          }
          // ...

詳細は後述しますが、以下もご参照ください。

使ってみた

初期設定

openaiライブラリをインストールします。今回はopenai==1.10.0を使用します。

pip install openai

ライブラリのインポートは以下です。

from openai import OpenAI
from openai.types.file_object import FileObject
import time
import pathlib

発行したAPIキーを設定し、クライアントをインスタンス化します。

api_key = "{発行したOPENAI APIキー}"

client = OpenAI(
    api_key=api_key
)

APIキーの発行方法は以下を参照ください。

ファイル登録

まずはAssistantに参照させるファイルを登録します。

ローカルフォルダにcsvファイルを準備しておきます。ファイル自体は以下のレポジトリから拝借致しました。

ファイル登録のコードは以下です。

files = []

for file_name in [
    "jaffle-shop/raw_customers.csv",
    "jaffle-shop/raw_orders.csv",
    "jaffle-shop/raw_payments.csv"
]:
    file = client.files.create(
        file=open(file_name, "rb"),
        purpose='assistants'
    )
    files.append(file)

実行後に登録済みのファイル情報を確認してみます。

files: list[FileObject] = files
for file in files:
    print(file.model_dump_json(indent=2))

# {
#   "id": "file-NjPcqnSI1rcFIrGkMz83DoNb",
#   "bytes": 1302,
#   "created_at": 1708665885,
#   "filename": "raw_customers.csv",
#   "object": "file",
#   "purpose": "assistants",
#   "status": "processed",
#   "status_details": null
# }
# {
#   "id": "file-nYt31owJ3APz6jaTNmRJ0gRG",
#   "bytes": 2723,
#   "created_at": 1708665886,
#   "filename": "raw_orders.csv",
#   "object": "file",
#   "purpose": "assistants",
#   "status": "processed",
#   "status_details": null
# }
# {
#   "id": "file-qdS80m8mXAy8xnzV9CqlJ7se",
#   "bytes": 2560,
#   "created_at": 1708665886,
#   "filename": "raw_payments.csv",
#   "object": "file",
#   "purpose": "assistants",
#   "status": "processed",
#   "status_details": null
# }

Assistant作成

Assistantを作成します。

file_idsに登録したファイルのIDリストを渡します。またコードを実行させるために、toolsにCode Interpreterを設定します。

assistant = client.beta.assistants.create(
    name="cm-nakamura-assistant-2024-02-23",
    tools=[{"type": "code_interpreter"}],
    model="gpt-4-turbo-preview",
    file_ids=[file.id for file in files]
)

print(assistant.model_dump_json(indent=2))

# {
#   "id": "asst_H24X1ae9Pdx7Ufceqdkj5GVa",
#   "created_at": 1708665918,
#   "description": null,
#   "file_ids": [
#     "file-NjPcqnSI1rcFIrGkMz83DoNb",
#     "file-nYt31owJ3APz6jaTNmRJ0gRG",
#     "file-qdS80m8mXAy8xnzV9CqlJ7se"
#   ],
#   "instructions": null,
#   "metadata": {},
#   "model": "gpt-4-turbo-preview",
#   "name": "cm-nakamura-assistant-2024-02-23",
#   "object": "assistant",
#   "tools": [
#     {
#       "type": "code_interpreter"
#     }
#   ]
# }

Thread作成

Assistantとやり取りするためのThreadを作成します。

thread = client.beta.threads.create()

print(thread.model_dump_json(indent=2))

# {
#   "id": "thread_naDHVrafHHDXYjfifmrlkbJc",
#   "created_at": 1708665932,
#   "metadata": {},
#   "object": "thread",
#   "tool_resources": []
# }

メッセージの投入

こちら側のAssistantに対する依頼をThreadのメッセージとして投入します。

(この時点ではAssistantの処理はまだ実行されません)

client.beta.threads.messages.create(
    thread_id=thread.id,
    role="user",
    content="raw_payments.csvから各payment_method毎のamountの総和を求めてそれをcsvファイルにしてください。"
)

Threadのメッセージの中身を確認してみます。

messages = client.beta.threads.messages.list(thread_id=thread.id)

print(messages.model_dump_json(indent=2))

# {
#   "data": [
#     {
#       "id": "msg_RZqCf6cKGOaTPuXmHsLxMyKn",
#       "assistant_id": null,
#       "content": [
#         {
#           "text": {
#             "annotations": [],
#             "value": "raw_payments.csvから各payment_method毎のamountの総和を求めてそれをcsvファイルにしてください。"
#           },
#           "type": "text"
#         }
#       ],
#       "created_at": 1708665958,
#       "file_ids": [],
#       "metadata": {},
#       "object": "thread.message",
#       "role": "user",
#       "run_id": null,
#       "thread_id": "thread_naDHVrafHHDXYjfifmrlkbJc"
#     }
#   ],
#   "object": "list",
#   "first_id": "msg_RZqCf6cKGOaTPuXmHsLxMyKn",
#   "last_id": "msg_RZqCf6cKGOaTPuXmHsLxMyKn",
#   "has_more": false
# }

runの作成

runを作成することで、Assistantの処理を実行することができます。

run = client.beta.threads.runs.create(
    thread_id=thread.id,
    assistant_id=assistant.id,
)

print(run.model_dump_json(indent=2))

# {
#   "id": "run_ZSPs8As0Vh4RvvyYwEtkrA3t",
#   "assistant_id": "asst_H24X1ae9Pdx7Ufceqdkj5GVa",
#   "cancelled_at": null,
#   "completed_at": null,
#   "created_at": 1708665964,
#   "expires_at": 1708666564,
#   "failed_at": null,
#   "file_ids": [
#     "file-NjPcqnSI1rcFIrGkMz83DoNb",
#     "file-nYt31owJ3APz6jaTNmRJ0gRG",
#     "file-qdS80m8mXAy8xnzV9CqlJ7se"
#   ],
#   "instructions": null,
#   "last_error": null,
#   "metadata": {},
#   "model": "gpt-4-turbo-preview",
#   "object": "thread.run",
#   "required_action": null,
#   "started_at": null,
#   "status": "queued",
#   "thread_id": "thread_naDHVrafHHDXYjfifmrlkbJc",
#   "tools": [
#     {
#       "type": "code_interpreter"
#     }
#   ],
#   "usage": null
# }

ru作成直後は"status": "queued"となっていることが分かります。

実行待ち

runのステータスを確認しながら完了を待ちます。以下がステータスを確認するためのコードです。

def check_status(thread_id: str, run_id: str):
    """ステータスをチェックする"""
    run = client.beta.threads.runs.retrieve(
        thread_id=thread_id,
        run_id=run_id
    )
    print(f"{run.status=}")
    if run.status == "completed" or run.status == "failed":
        return True
    else:
        return False

10秒単位で完了をチェックします。

while True:
    # 状態をチェック
    if check_status(thread_id=thread.id, run_id=run.id):
        print("completed.")
        break

    print("waiting...")
    # ステータスが完了状態になるまで一定時間待機
    time.sleep(10)

# run.status='in_progress'
# waiting...
# run.status='in_progress'
# waiting...
# run.status='in_progress'
# waiting...
# run.status='in_progress'
# waiting...
# run.status='in_progress'
# waiting...
# run.status='in_progress'
# waiting...
# run.status='completed'
# completed.

レスポンスの確認

完了後、Assistantが作成したレスポンスはメッセージの内容から確認します。

分かりやすいようにロール名とテキストのみを抽出しています。

messages = client.beta.threads.messages.list(thread_id=thread.id, order="asc")

# print(messages.model_dump_json(indent=2))
for message in messages.data:
    print(f"{message.role}: {message.content[0].text.value}\n")

# user: raw_payments.csvから各payment_method毎のamountの総和を求めてそれをcsvファイルにしてください。
# 
# assistant: ファイル`raw_payments.csv`が存在しないため、エラーが発生しています。アップロードされたファイルを確認して、該当するファイルを処理するようにします。アップロードされたファイル名を確認し、それに応じて処理を行います。
# 
# assistant: アップロードされたファイルは以下の三つですが、正しいファイル名が指定されていませんでした。
# 
# - `/mnt/data/file-NjPcqnSI1rcFIrGkMz83DoNb`
# - `/mnt/data/file-nYt31owJ3APz6jaTNmRJ0gRG`
# - `/mnt/data/file-qdS80m8mXAy8xnzV9CqlJ7se`
# 
# これらのファイルの中で、`raw_payments.csv`に相当するものを探し、該当する操作を行います。まず、これらのファイルの内容を確認します。
# 
# assistant: `raw_payments.csv`に相当するファイルは `/mnt/data/file-qdS80m8mXAy8xnzV9CqlJ7se` です。このファイルには、支払い方法 (`payment_method`) ごとに、金額 (`amount`) が記載されています。
# 
# このファイルを用いて各支払い方法ごとの金額の総和を計算し、結果を新たなCSVファイルに保存します。
# 
# assistant: 各支払い方法ごとの金額の総和を計算し、その結果をCSVファイルに保存しました。結果は以下のリンクからダウンロードできます:
# 
# [total_amount_by_payment_method_correct.csv](sandbox:/mnt/data/total_amount_by_payment_method_correct.csv)

ファイル名が登録時に指定できていなかったため、該当ファイルがどれか判断するところから実行されていることがわかります。

また結果にファイルが含まれる場合、ファイルの中身自体は開発者がAPI経由で取得してユーザに公開できる場所に配置し、元のテキストをそのリンクに置き換える必要があります。

そのための必要な情報は、以下のannotationsにレスポンスとして含まれています。

print(messages.data[-1].content[0].text.model_dump_json(indent=2))

# {
#   "annotations": [
#     {
#       "end_index": 168,
#       "file_path": {
#         "file_id": "file-mWILO7iIBIyGnqmvfCADjXSl"
#       },
#       "start_index": 108,
#       "text": "sandbox:/mnt/data/total_amount_by_payment_method_correct.csv",
#       "type": "file_path"
#     }
#   ],
#   "value": "各支払い方法ごとの金額の総和を計算し、その結果をCSVファイルに保存しました。結果は以下のリンクからダウンロードできます:\n\n[total_amount_by_payment_method_correct.csv](sandbox:/mnt/data/total_amount_by_payment_method_correct.csv)"
# }

ダウンロードにはfile_idを使用します。

result = messages.data[-1].content[0].text
result_file_id = result.annotations[0].file_path.file_id
result_file_name = pathlib.Path(result.annotations[0].text).name

with open(result_file_name, "wb") as f:
    f.write(client.files.content(result_file_id).read())

今回ダウンロードされたファイルは以下です。

payment_method,amount
bank_transfer,41100
coupon,18500
credit_card,87100
gift_card,20500

置き換えるべきテキストはannotationsにstart_index、end_indexがあるため、そちらを参照してユーザに公開するリンクに置き換えます。

display_message = result.value[:result.annotations[0].start_index] \
    + "ユーザに公開するリンク" \
    + result.value[result.annotations[0].end_index:]

print(display_message)

# 各支払い方法ごとの金額の総和を計算し、その結果をCSVファイルに保存しました。結果は以下のリンクからダウンロードできます:
#
# [total_amount_by_payment_method_correct.csv](ユーザに公開するリンク)

Code Interpreterのstepsを確認

前述のレスポンスは得られた結果のみとなりますので、Code Interpreterが実際にどのようなコードを実行したかまではわかりません。

それを確認するためには、stepsをみる必要があります。

以下のようにrunのstepsを確認すると、実際に実行されたコードやコードの出力が確認できます。

run_steps = client.beta.threads.runs.steps.list(
    thread_id=thread.id,
    run_id=run.id,
    order='asc'
)

# print(run_steps.model_dump_json(indent=2))
for i in [i.step_details for i in run_steps.data]:
    if i.type == "tool_calls":
        for j in i.tool_calls:
            if j.type == "code_interpreter":
                print("="*80)
                print("tool_calls - code_interpreter")
                print("="*80)
                print("\nINPUT:>\n")
                print(j.code_interpreter.input)
                print("\nOUTPUT:>\n")
                print(j.code_interpreter.outputs)
    elif i.type == "message_creation":
        print("="*80)
        print("message_creation")
        print("="*80)
        message_id = i.message_creation.message_id
        message = client.beta.threads.messages.retrieve(message_id=message_id, thread_id=thread.id)
        print(message.content[0].text.value)

# ================================================================================
# tool_calls - code_interpreter
# ================================================================================
# 
# INPUT:>
# 
# import pandas as pd
# 
# # ファイルパスを指定
# file_path = '/mnt/data/raw_payments.csv'
# 
# # CSVファイルを読み込む
# payments = pd.read_csv(file_path)
# 
# # 各payment_methodごとのamountの総和を求める
# total_amount_by_method = payments.groupby('payment_method')['amount'].sum().reset_index()
# 
# # ファイル名を指定してCSVに保存
# output_file_path = '/mnt/data/total_amount_by_payment_method.csv'
# total_amount_by_method.to_csv(output_file_path, index=False)
# 
# output_file_path
# 
# OUTPUT:>
# 
# [CodeInterpreterOutputLogs(logs='---------------------------------------------------------------------------\nFileNotFoundError                         Traceback (most recent call last)\nCell In[1], line 7\n      4 file_path = \'/mnt/data/raw_payments.csv\'\n      6 # CSVファイルを読み込む\n----> 7 payments = pd.read_csv(file_path)\n      9 # 各payment_methodごとのamountの総和を求める\n     10 total_amount_by_method = payments.groupby(\'payment_method\')[\'amount\'].sum().reset_index()\n\nFile ~/.local/lib/python3.8/site-packages/pandas/util/_decorators.py:311, in deprecate_nonkeyword_arguments.<locals>.decorate.<locals>.wrapper(*args, **kwargs)\n    305 if len(args) > num_allow_args:\n    306     warnings.warn(\n    307         msg.format(arguments=arguments),\n    308         FutureWarning,\n    309         stacklevel=stacklevel,\n    310     )\n--> 311 return func(*args, **kwargs)\n\nFile ~/.local/lib/python3.8/site-packages/pandas/io/parsers/readers.py:586, in read_csv(filepath_or_buffer, sep, delimiter, header, names, index_col, usecols, squeeze, prefix, mangle_dupe_cols, dtype, engine, converters, true_values, false_values, skipinitialspace, skiprows, skipfooter, nrows, na_values, keep_default_na, na_filter, verbose, skip_blank_lines, parse_dates, infer_datetime_format, keep_date_col, date_parser, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, encoding_errors, dialect, error_bad_lines, warn_bad_lines, on_bad_lines, delim_whitespace, low_memory, memory_map, float_precision, storage_options)\n    571 kwds_defaults = _refine_defaults_read(\n    572     dialect,\n    573     delimiter,\n   (...)\n    582     defaults={"delimiter": ","},\n    583 )\n    584 kwds.update(kwds_defaults)\n--> 586 return _read(filepath_or_buffer, kwds)\n\nFile ~/.local/lib/python3.8/site-packages/pandas/io/parsers/readers.py:482, in _read(filepath_or_buffer, kwds)\n    479 _validate_names(kwds.get("names", None))\n    481 # Create the parser.\n--> 482 parser = TextFileReader(filepath_or_buffer, **kwds)\n    484 if chunksize or iterator:\n    485     return parser\n\nFile ~/.local/lib/python3.8/site-packages/pandas/io/parsers/readers.py:811, in TextFileReader.__init__(self, f, engine, **kwds)\n    808 if "has_index_names" in kwds:\n    809     self.options["has_index_names"] = kwds["has_index_names"]\n--> 811 self._engine = self._make_engine(self.engine)\n\nFile ~/.local/lib/python3.8/site-packages/pandas/io/parsers/readers.py:1040, in TextFileReader._make_engine(self, engine)\n   1036     raise ValueError(\n   1037         f"Unknown engine: {engine} (valid options are {mapping.keys()})"\n   1038     )\n   1039 # error: Too many arguments for "ParserBase"\n-> 1040 return mapping[engine](self.f, **self.options)\n\nFile ~/.local/lib/python3.8/site-packages/pandas/io/parsers/c_parser_wrapper.py:51, in CParserWrapper.__init__(self, src, **kwds)\n     48 kwds["usecols"] = self.usecols\n     50 # open handles\n---> 51 self._open_handles(src, kwds)\n     52 assert self.handles is not None\n     54 # Have to pass int, would break tests using TextReader directly otherwise :(\n\nFile ~/.local/lib/python3.8/site-packages/pandas/io/parsers/base_parser.py:222, in ParserBase._open_handles(self, src, kwds)\n    218 def _open_handles(self, src: FilePathOrBuffer, kwds: dict[str, Any]) -> None:\n    219     """\n    220     Let the readers open IOHandles after they are done with their potential raises.\n    221     """\n--> 222     self.handles = get_handle(\n    223         src,\n    224         "r",\n    225         encoding=kwds.get("encoding", None),\n    226         compression=kwds.get("compression", None),\n    227         memory_map=kwds.get("memory_map", False),\n    228         storage_options=kwds.get("storage_options", None),\n    229         errors=kwds.get("encoding_errors", "strict"),\n    230     )\n\nFile ~/.local/lib/python3.8/site-packages/pandas/io/common.py:701, in get_handle(path_or_buf, mode, encoding, compression, memory_map, is_text, errors, storage_options)\n    696 elif isinstance(handle, str):\n    697     # Check whether the filename is to be opened in binary mode.\n    698     # Binary mode does not support \'encoding\' and \'newline\'.\n    699     if ioargs.encoding and "b" not in ioargs.mode:\n    700         # Encoding\n--> 701         handle = open(\n    702             handle,\n    703             ioargs.mode,\n    704             encoding=ioargs.encoding,\n    705             errors=errors,\n    706             newline="",\n    707         )\n    708     else:\n    709         # Binary mode\n    710         handle = open(handle, ioargs.mode)\n\nFileNotFoundError: [Errno 2] No such file or directory: \'/mnt/data/raw_payments.csv\'\n', type='logs')]
# ================================================================================
# message_creation
# ================================================================================
# ファイル`raw_payments.csv`が存在しないため、エラーが発生しています。アップロードされたファイルを確認して、該当するファイルを処理するようにします。アップロードされたファイル名を確認し、それに応じて処理を行います。
# ================================================================================
# tool_calls - code_interpreter
# ================================================================================
# 
# INPUT:>
# 
# # アップロードされたファイル名のリストを作成
# uploaded_files = [
#     '/mnt/data/file-NjPcqnSI1rcFIrGkMz83DoNb',
#     '/mnt/data/file-nYt31owJ3APz6jaTNmRJ0gRG',
#     '/mnt/data/file-qdS80m8mXAy8xnzV9CqlJ7se'
# ]
# 
# # アップロードされたファイル名を確認
# uploaded_files
# 
# OUTPUT:>
# 
# [CodeInterpreterOutputLogs(logs="['/mnt/data/file-NjPcqnSI1rcFIrGkMz83DoNb',\n '/mnt/data/file-nYt31owJ3APz6jaTNmRJ0gRG',\n '/mnt/data/file-qdS80m8mXAy8xnzV9CqlJ7se']", type='logs')]
# ================================================================================
# message_creation
# ================================================================================
# アップロードされたファイルは以下の三つですが、正しいファイル名が指定されていませんでした。
# 
# - `/mnt/data/file-NjPcqnSI1rcFIrGkMz83DoNb`
# - `/mnt/data/file-nYt31owJ3APz6jaTNmRJ0gRG`
# - `/mnt/data/file-qdS80m8mXAy8xnzV9CqlJ7se`
# 
# これらのファイルの中で、`raw_payments.csv`に相当するものを探し、該当する操作を行います。まず、これらのファイルの内容を確認します。
# ================================================================================
# tool_calls - code_interpreter
# ================================================================================
# 
# INPUT:>
# 
# # アップロードされたファイルの内容を確認(先頭の数行を表示)
# for file_path in uploaded_files:
#     print(f"Contents of {file_path}:")
#     try:
#         df = pd.read_csv(file_path)
#         print(df.head(), '\n')
#     except Exception as e:
#         print(f"Error reading {file_path}: {e}\n")
# 
# OUTPUT:>
# 
# [CodeInterpreterOutputLogs(logs='Contents of /mnt/data/file-NjPcqnSI1rcFIrGkMz83DoNb:\n   id first_name last_name\n0   1    Michael        P.\n1   2      Shawn        M.\n2   3   Kathleen        P.\n3   4      Jimmy        C.\n4   5  Katherine        R. \n\nContents of /mnt/data/file-nYt31owJ3APz6jaTNmRJ0gRG:\n   id  user_id  order_date     status\n0   1        1  2018-01-01   returned\n1   2        3  2018-01-02  completed\n2   3       94  2018-01-04  completed\n3   4       50  2018-01-05  completed\n4   5       64  2018-01-05  completed \n\nContents of /mnt/data/file-qdS80m8mXAy8xnzV9CqlJ7se:\n   id  order_id payment_method  amount\n0   1         1    credit_card    1000\n1   2         2    credit_card    2000\n2   3         3         coupon     100\n3   4         4         coupon    2500\n4   5         5  bank_transfer    1700 \n\n', type='logs')]
# ================================================================================
# message_creation
# ================================================================================
# `raw_payments.csv`に相当するファイルは `/mnt/data/file-qdS80m8mXAy8xnzV9CqlJ7se` です。このファイルには、支払い方法 (`payment_method`) ごとに、金額 (`amount`) が記載されています。
# 
# このファイルを用いて各支払い方法ごとの金額の総和を計算し、結果を新たなCSVファイルに保存します。
# ================================================================================
# tool_calls - code_interpreter
# ================================================================================
# 
# INPUT:>
# 
# # 正しいファイルパスを指定してデータをロード
# correct_file_path = '/mnt/data/file-qdS80m8mXAy8xnzV9CqlJ7se'
# payments_correct = pd.read_csv(correct_file_path)
# 
# # 各payment_methodごとのamountの総和を計算
# total_amount_by_method_correct = payments_correct.groupby('payment_method')['amount'].sum().reset_index()
# 
# # 結果をCSVファイルに保存
# output_correct_file_path = '/mnt/data/total_amount_by_payment_method_correct.csv'
# total_amount_by_method_correct.to_csv(output_correct_file_path, index=False)
# 
# output_correct_file_path
# 
# OUTPUT:>
# 
# [CodeInterpreterOutputLogs(logs="'/mnt/data/total_amount_by_payment_method_correct.csv'", type='logs')]
# ================================================================================
# message_creation
# ================================================================================
# 各支払い方法ごとの金額の総和を計算し、その結果をCSVファイルに保存しました。結果は以下のリンクからダウンロードできます:
# 
# [total_amount_by_payment_method_correct.csv](sandbox:/mnt/data/total_amount_by_payment_method_correct.csv)

raw_payments.csvの読み込みに失敗したため、ファイルの先頭を確認してraw_payments.csvが今あるファイルのいずれかなのか把握するなど、挙動を確認することができます。

ファイルが決まった後は、pandasのgroupbyを使って所望の結果を得ていることが分かります。

リソースの削除

リソースは残っていると費用が掛かるケースがありますので削除しておきましょう

resp = client.beta.threads.delete(thread_id=thread.id)
print(resp)

# ThreadDeleted(id='thread_naDHVrafHHDXYjfifmrlkbJc', deleted=True, object='thread.deleted')
for file in files:
    resp = client.files.delete(file_id=file.id)
    print(resp)

# FileDeleted(id='file-NjPcqnSI1rcFIrGkMz83DoNb', deleted=True, object='file')
# FileDeleted(id='file-nYt31owJ3APz6jaTNmRJ0gRG', deleted=True, object='file')
# FileDeleted(id='file-qdS80m8mXAy8xnzV9CqlJ7se', deleted=True, object='file')
resp = client.beta.assistants.delete(assistant_id=assistant.id)
print(resp)

# AssistantDeleted(id='asst_H24X1ae9Pdx7Ufceqdkj5GVa', deleted=True, object='assistant.deleted')

まとめ

いかがでしたでしょうか。更にAssistants APIについての理解が深まりました。

本記事が皆様のご参考になれば幸いです。