OpenAI APIで自分だけのアシスタントを作れる!!OpenAI DevDayで発表されたAssistants APIについて試してみた。

2023.11.07

こんちには。

データアナリティクス事業本部 インテグレーション部 機械学習チームの中村です。

今回は前回の記事で書ききれなかった「Assistants API」にフォーカスして試してみます。

機能の概要

Assistants APIの発表が発表されました。(ベータ版です)

要点をまとめてみました。

  • 知識を拡張することが可能で、必要に応じてモデルやツールを呼び出してタスクを実行するAssistantsを作成することができる
  • Code InterpreterやRetrieval、Function callingなどの機能も利用可能で、これまで自作が必要であった多くの作業をOpenAIに任せ、アプリケーションの実装に注力することが可能
  • 持続的で無限に長いスレッドが使用でき、開発者はスレッドの状態管理をOpenAIに委ねることができ、コンテキスト長の制約を回避することが可能
  • 他のプラットフォームと同様、Assistants APIに渡されたデータやファイルは、モデルのトレーニングに使用されることはない
  • Assistants APIは後述する画像入力には対応していない

これでOpenAI公式の機能を使用してエージェントなどの実装が可能となりそうで、かなり影響が大きいと思います。

Assistants Playgroundでコードを書かずにAssistants APIを試すこともできます。

もちろんコードでも試すことが可能で、以下のページで詳細を確認できます。

Assistants APIを呼び出すには、BetaヘッダをHTTPリクエストのヘッダに渡す必要があります。

`OpenAI-Beta: assistants=v1`

OpenAIの公式PythonとNode.js SDKを使用している場合、これは自動的に処理されます。

今回はPythonのライブラリを使って、公式ページに沿って試して行きたいと思います。

試してみた

前提

前提として以下を実施している想定で進みます。

Step 1: Create an Assistant

まずはAssistantを作成します。

from openai import OpenAI

client = OpenAI()

assistant = client.beta.assistants.create(
    name="数学の家庭教師",
    instructions="あなたは数学の家庭教師です。コードを書いて実行し、数学の質問に答えてください。",
    tools=[{"type": "code_interpreter"}],
    model="gpt-3.5-turbo-1106"
)

いくつかパラメータを指定可能です。

  • name : 名前
  • instructions : アシスタントとモデルがどのように振る舞い、対応すべきか
  • model : GPT-3.5またはGPT-4モデルを指定可能(検索ツールはgpt-3.5-turbo-1106もしくはgpt-4-1106-previewモデルを必要)
  • tools : OpenAIによって構築されホストされているCode InterpreterとRetrievalをツールとしてサポート
  • functions : カスタム関数を定義し、Function callingと同じように使うことが可能

この例では、ツールとしてCode Interpreterを有効にし、数学の家庭教師であるアシスタントを作成しています。

Step 2: Create a Thread

次にスレッドを作成します。このスレッドにユーザのメッセージなどを追加していきます。

thread = client.beta.threads.create()

print(thread.model_dump_json(indent=2))

# {
#   "id": "thread_L60j2idBx6IbEClgz9eulqQu",
#   "created_at": 1699362441,
#   "metadata": {},
#   "object": "thread"
# }

このスレッドには会話履歴が残っていきますが、スレッドにはサイズ制限が無いという点が特徴です。

Assistants APIは、切り捨てなどの関連する最適化技術を使用して、モデルへのリクエストが最大コンテキストウィンドウ内に収まるように工夫してくれます(ユーザは意識しなくてもよい)。

Step 3: Add a Message to a Thread

スレッドにメッセージを追加します。

message = client.beta.threads.messages.create(
    thread_id=thread.id,
    role="user",
    content="I need to solve the equation `3x + 11 = 14`. Can you help me?"
)

メッセージには、ユーザーのテキストや、任意でユーザーがアップロードしたファイルを含むことが可能です。

(画像ファイルは現在サポートされていませんが、今後数ヶ月のうちにサポートを追加する予定とのこと)

スレッド内に含まれるメッセージは以下のAPIで取得することが可能です。

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

print(messages.model_dump_json(indent=2))

# {
#   "data": [
#     {
#       "id": "msg_nF1Epy6UdAwf9SGmG5rFlXyX",
#       "assistant_id": null,
#       "content": [
#         {
#           "text": {
#             "annotations": [],
#             "value": "`3x + 11 = 14`という方程式を解いて欲しいのですが、手伝ってくれますか?"
#           },
#           "type": "text"
#         }
#       ],
#       "created_at": 1699362441,
#       "file_ids": [],
#       "metadata": {},
#       "object": "thread.message",
#       "role": "user",
#       "run_id": null,
#       "thread_id": "thread_L60j2idBx6IbEClgz9eulqQu"
#     }
#   ],
#   "object": "list",
#   "first_id": "msg_nF1Epy6UdAwf9SGmG5rFlXyX",
#   "last_id": "msg_nF1Epy6UdAwf9SGmG5rFlXyX",
#   "has_more": false
# }

Step 4: Run the Assistant

次にAssistantがユーザーメッセージに応答するために、Runを作成します。

run = client.beta.threads.runs.create(
    thread_id=thread.id,
    assistant_id=assistant.id,
    instructions="ユーザー名を Jane Doe としてください。"
)

print(run.model_dump_json(indent=2))

# {
#   "id": "run_2mgu72res4Am35DVNcquWg4d",
#   "assistant_id": "asst_oD8UWLXlGBnuPOcy4jJlqDgy",
#   "cancelled_at": null,
#   "completed_at": null,
#   "created_at": 1699362442,
#   "expires_at": 1699363042,
#   "failed_at": null,
#   "file_ids": [],
#   "instructions": "ユーザー名を Jane Doe としてください。",
#   "last_error": null,
#   "metadata": {},
#   "model": "gpt-3.5-turbo-1106",
#   "object": "thread.run",
#   "required_action": null,
#   "started_at": null,
#   "status": "queued",
#   "thread_id": "thread_L60j2idBx6IbEClgz9eulqQu",
#   "tools": [
#     {
#       "type": "code_interpreter"
#     }
#   ]
# }

これにより、Assistantはスレッドを読み取り、ツールを呼び出すか、単にモデルを使用してユーザークエリに最適な回答を行うかを決定します。

その後処理が進むと、Assistantはスレッドにrole="assistant"のメッセージを追加します。

また、サンプルにあるようにオプションで、runの作成時にAssistantに追加の指示をinstructionsとして渡すことが可能です。

Step 5: Display the Assistant's Response

runは作成した状態では"status": "queued"となっており非同期に実行されますので、このrunのステータスを定期的に取得し、ステータスが完了したかどうかを確認するAPIが準備されています。

run = client.beta.threads.runs.retrieve(
  thread_id=thread.id,
  run_id=run.id
)

print(run.model_dump_json(indent=2))

# {
#   "id": "run_2mgu72res4Am35DVNcquWg4d",
#   "assistant_id": "asst_oD8UWLXlGBnuPOcy4jJlqDgy",
#   "cancelled_at": null,
#   "completed_at": null,
#   "created_at": 1699362442,
#   "expires_at": 1699363042,
#   "failed_at": null,
#   "file_ids": [],
#   "instructions": "ユーザー名を Jane Doe としてください。",
#   "last_error": null,
#   "metadata": {},
#   "model": "gpt-3.5-turbo-1106",
#   "object": "thread.run",
#   "required_action": null,
#   "started_at": 1699362442,
#   "status": "in_progress",
#   "thread_id": "thread_L60j2idBx6IbEClgz9eulqQu",
#   "tools": [
#     {
#       "type": "code_interpreter"
#     }
#   ]
# }

完了前の状態では"status": "in_progress"となっています。

完了すると以下のように"status": "completed"となります。

run = client.beta.threads.runs.retrieve(
    thread_id=thread.id,
    run_id=run.id
)

print(run.model_dump_json(indent=2))

# {
#   "id": "run_2mgu72res4Am35DVNcquWg4d",
#   "assistant_id": "asst_oD8UWLXlGBnuPOcy4jJlqDgy",
#   "cancelled_at": null,
#   "completed_at": 1699362448,
#   "created_at": 1699362442,
#   "expires_at": null,
#   "failed_at": null,
#   "file_ids": [],
#   "instructions": "ユーザー名を Jane Doe としてください。",
#   "last_error": null,
#   "metadata": {},
#   "model": "gpt-3.5-turbo-1106",
#   "object": "thread.run",
#   "required_action": null,
#   "started_at": 1699362442,
#   "status": "completed",
#   "thread_id": "thread_L60j2idBx6IbEClgz9eulqQu",
#   "tools": [
#     {
#       "type": "code_interpreter"
#     }
#   ]
# }

結果の確認は先ほどと同様にスレッドのメッセージ一覧を取得すればOKです。

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

print(messages.model_dump_json(indent=2))

# {
#   "data": [
#     {
#       "id": "msg_9eWUzkXCmllMQeRcyH6txARo",
#       "assistant_id": "asst_oD8UWLXlGBnuPOcy4jJlqDgy",
#       "content": [
#         {
#           "text": {
#             "annotations": [],
#             "value": "与えられた方程式 \\(3x + 11 = 14\\) の解は、\\(x = 1\\) です。お役に立てていれば幸いです!他に何かありますか?"
#           },
#           "type": "text"
#         }
#       ],
#       "created_at": 1699362447,
#       "file_ids": [],
#       "metadata": {},
#       "object": "thread.message",
#       "role": "assistant",
#       "run_id": "run_2mgu72res4Am35DVNcquWg4d",
#       "thread_id": "thread_L60j2idBx6IbEClgz9eulqQu"
#     },
#     {
#       "id": "msg_YnL0vN5k3tVXNr6jXs3Gd18r",
#       "assistant_id": "asst_oD8UWLXlGBnuPOcy4jJlqDgy",
#       "content": [
#         {
#           "text": {
#             "annotations": [],
#             "value": "与えられた方程式は次の通りです。\n\n\\[ 3x + 11 = 14 \\]\n\nまず、この方程式から \\(x\\) を求めるために、次の手順を実行します。\n\n1. 両辺から11を引いて、等式を変形する。\n2. 得られた式を3で割って、\\(x\\) の値を求める。\n\nこれらの手順に従って方程式を解いていきましょう。"
#           },
#           "type": "text"
#         }
#       ],
#       "created_at": 1699362443,
#       "file_ids": [],
#       "metadata": {},
#       "object": "thread.message",
#       "role": "assistant",
#       "run_id": "run_2mgu72res4Am35DVNcquWg4d",
#       "thread_id": "thread_L60j2idBx6IbEClgz9eulqQu"
#     },
#     {
#       "id": "msg_Jg6cmEcPFxDkQ4RK4z7e7kbM",
#       "assistant_id": "asst_oD8UWLXlGBnuPOcy4jJlqDgy",
#       "content": [
#         {
#           "text": {
#             "annotations": [],
#             "value": "もちろんです!与えられた方程式を解くことができます。まずは、方程式を解くための手順を見ていきましょう。"
#           },
#           "type": "text"
#         }
#       ],
#       "created_at": 1699362443,
#       "file_ids": [],
#       "metadata": {},
#       "object": "thread.message",
#       "role": "assistant",
#       "run_id": "run_2mgu72res4Am35DVNcquWg4d",
#       "thread_id": "thread_L60j2idBx6IbEClgz9eulqQu"
#     },
#     {
#       "id": "msg_nF1Epy6UdAwf9SGmG5rFlXyX",
#       "assistant_id": null,
#       "content": [
#         {
#           "text": {
#             "annotations": [],
#             "value": "`3x + 11 = 14`という方程式を解いて欲しいのですが、手伝ってくれますか?"
#           },
#           "type": "text"
#         }
#       ],
#       "created_at": 1699362441,
#       "file_ids": [],
#       "metadata": {},
#       "object": "thread.message",
#       "role": "user",
#       "run_id": null,
#       "thread_id": "thread_L60j2idBx6IbEClgz9eulqQu"
#     }
#   ],
#   "object": "list",
#   "first_id": "msg_9eWUzkXCmllMQeRcyH6txARo",
#   "last_id": "msg_nF1Epy6UdAwf9SGmG5rFlXyX",
#   "has_more": false
# }

この中にAssistantがスレッドに追加したメッセージが含まれています。あとメッセージは新しい順に並んでいることが分かります。

ちょっと見にくくなってしまったので分かりやすく表示してみます。

for message in reversed(messages.data):
    print(f"{message.role}: {message.content[0].text.value}\n")

# user: `3x + 11 = 14`という方程式を解いて欲しいのですが、手伝ってくれますか?
#
# assistant: もちろんです!与えられた方程式を解くことができます。まずは、方程式を解くための手順を見ていきましょう。
#
# assistant: 与えられた方程式は次の通りです。
#
# \[ 3x + 11 = 14 \]
#
# まず、この方程式から \(x\) を求めるために、次の手順を実行します。
#
# 1. 両辺から11を引いて、等式を変形する。
# 2. 得られた式を3で割って、\(x\) の値を求める。
#
# これらの手順に従って方程式を解いていきましょう。
#
# assistant: 与えられた方程式 \(3x + 11 = 14\) の解は、\(x = 1\) です。お役に立てていれば幸いです!他に何かありますか?

無事にAssistantに問題を解かせることができました。

まとめ

いかがでしたでしょうか。Assistants APIは従来自分で作成する必要のあったさまざまな処理について、置き換えていくことが可能な機能となっていそうです。

より複雑なケースを確認してみたい場合は以下の資料も参考にしてみてください。

私も今後これらを試していきたいと思います。

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