nikkie-ftnextの日記

イベントレポートや読書メモを発信

OpenAI DevDayで発表された「Assistants API」、新しい概念たちをつかむため、Overviewのドキュメントに沿って素振り

はじめに

未来は意外と近くにある。nikkieです。

OpenAI DevDay キーノートの高揚が続いています。
今回はDevDayで発表&リリースされた新概念「Assistants API」についての素振りの模様です。

目次

Assistants APIのOverview

キーノート中のAssistants APIのデモはこちら。
https://www.youtube.com/live/U9mJuUkhUzk?si=5w6Jg7_Vohx7Wo8Z&t=2028
「これ、どうやって作ってるんだろう」とめちゃくちゃ興味をそそられました

今回参照するのはこちらのドキュメント。

冒頭にAssistants APIを使う流れがまとまっています1
APIが提供するリソースを統合して使う模様です。

  1. Assistantを作る
    • カスタムインストラクションを定義
    • モデルを選択
    • ツールを有効化
  2. Threadを作る(ユーザとの会話の始まり)
  3. ユーザが質問するときはThreadにMessagesを追加する
  4. AssistantをThreadで実行し(Run)、応答を得る
    • 4では自動的に関連するツールを呼び出す

このドキュメントではPythonコードでstep-by-step integrationが示されているので、手を動かしていきます。

動作環境

環境変数OPENAI_API_KEYを設定した上で、Pythonの対話モードを起動します。

>>> import openai
>>> client = openai.OpenAI()

複数の新概念を掴みたかったので、スクリプトで動かすのは先送りしました。

1. Assistant作成

ドキュメントの英語の例を日本語にして試していっています。

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

2. Thread作成

>>> thread = client.beta.threads.create()

3. ThreadにMessages追加

>>> message = client.beta.threads.messages.create(
...     thread_id=thread.id,
...     role="user",
...     content="方程式 `3x + 11 = 14` を解く必要があります。手伝ってくれますか ?"
... )

Threadに追加したMessagesは client.beta.threads.messages.list(thread.id) で確認できそうでした。
ref: https://platform.openai.com/docs/api-reference/messages/listMessages

ThreadにMessagesを入れていくのと、ThreadのRunとを分けるという設計になっているのですね。

4. Run(実行して、応答を得る)

Runを作って実行をAPI側に投げておく

>>> run = client.beta.threads.runs.create(
...     thread_id=thread.id,
...     assistant_id=assistant.id,
...     instructions="このユーザをJane Doeと呼びかけてください。プレミアムアカウントのユーザです。"
... )

createの返り値のstatusはqueuedです(実行待ち)

>>> run.status
'queued'

status

The status of the run, which can be either queued, in_progress, requires_action, cancelling, cancelled, failed, completed, or expired. (レスポンスのドキュメントより)

Runの完了を確認する

>>> run = client.beta.threads.runs.retrieve(
...     thread_id=thread.id, run_id=run.id
... )
>>> run.status
'completed'

応答を取得

Run完了後にMessagesを取得すると、AssistantからのMessageも含まれます

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

レスポンスの型を確認すると

>>> type(messages)
<class 'openai.pagination.SyncCursorPage[ThreadMessage]'>

SyncCursorPage
https://github.com/openai/openai-python/blob/v1.1.1/src/openai/pagination.py#L58

>>> for i, m in enumerate(messages.data):
...   print(i, m.role, m.content[0].text.value)
0 assistant 方程式 `3x + 11 = 14` の解は `x = 1` です。お役に立ててよかったです!他に何かお手伝いできることはありますか?
1 assistant もちろんです。方程式 `3x + 11 = 14` を解くために、まずは式から定数項を左辺に移動します。そして、それを係数で割ることでxを求めることができます。以下で計算します。
2 user 方程式 `3x + 11 = 14` を解く必要があります。手伝ってくれますか?
  • 新しいMessage(Assistantによる応答)が先頭に入るようです(スタックみたい)
  • userのMessageもassistantのMessageも混ざって入っています

Runで実行された各ステップを確認できる!

You can also retrieve the Run Steps of this Run if you'd like to explore or display the inner workings of the Assistant and its tools.(Overviewのドキュメント)

開発者ダッシュボードで見られるログはRunのstepsを表示しているのだと思います。

>>> steps = client.beta.threads.runs.steps.list(thread_id=thread.id, run_id=run.id)
>>> for i, s in enumerate(steps.data):
...   print(i, s.step_details)
0 MessageCreationStepDetails(message_creation=MessageCreation(message_id='msg_WS9Id4fjazIi2iNIjPDvJerG'), type='message_creation')
1 ToolCallsStepDetails(tool_calls=[CodeToolCall(id='call_i395iRf1or8ieLxwwzNGNn0M', code_interpreter=CodeInterpreter(input="from sympy import symbols, Eq, solve\n\n# xをシンボルとして定義\nx = symbols('x')\n\n# 方程式を定義\nequation = Eq(3*x + 11, 14)\n\n# 方程式を解く\nsolution = solve(equation, x)\nsolution", outputs=[CodeInterpreterOutputLogs(logs='[1]', type='logs')]), type='code_interpreter')], type='tool_calls')
2 MessageCreationStepDetails(message_creation=MessageCreation(message_id='msg_fcptkxYBmX7fXlfBtUsXuubu'), type='message_creation')

Code interpreterを実行したことが確認できます!
message_idについて

  • step 2のmessage_idは message 1(もちろんです)と一致する
    • step 2によりmessage 1が生成されたと理解できそう
  • step 1でCode interpreter実行
  • step 0のmessage_idは message 0(解は x = 1)と一致

Assistantは今回、もちろんです -> Code interpreter -> 解の出力 と動いたということですね。

まとめ:Assistants API

現時点の理解です。

  1. Assistantを作る(client.beta.assistants.create
  2. Threadを作る(client.beta.threads.create
  3. ユーザが質問するときはThreadにMessagesを追加する(client.beta.threads.messages.create
  4. AssistantをThreadで実行し(Run)、応答を得る
    • 実行を投げる client.beta.threads.runs.create
    • statusが完了となったか確認のために取得 client.beta.threads.runs.retrieve
      • 完了するまでポーリングするような実装になりそう
    • 完了した後はAssistantのMessageを取得 client.beta.threads.messages.list
    • Assistantの動きを取得 client.beta.threads.runs.steps.list

Playgroundでの操作と同様の操作をopenai-pythonライブラリを使ってできるようになったと思います。

終わりに

Assistants APIをOverviewのドキュメントに沿って素振りしました。
Assistant、Thread、Messages、Runと新しい概念は掴んだ気がします。
続く「How Assistants work」で詳細まで理解できそうです4

完全に理解と言っていないのは、このコード片からはまだアプリケーションを組み上げられないと感じるからです。
例えばAssistantの作成は一度限りでいいですし、ユーザごとにThreadを作る必要があります。
Playgroundの興味深い使い方例が流れてくるので、なにか一つを実装してみるとかでしょうか。
完全に理解してから、キーノートの旅行アプリのようにUI操作をさせてみたいですねー

また、Pydanticを使ったHTTPクライアントの実装の仕方など、ソースを覗いた中にも発見がありました


  1. At a high level, a typical integration of the Assistants API has the following flow:
  2. DevDay以降リリースが早く、執筆時最新は1.2.0です。
  3. overrideはプログラミングでは「上書き」ですが、辞書的には「重視する」。Code interpreterを使わなくなるわけではなかったので、意味合いとしては差分を追加なのかもしれません
  4. npakaさんによるまとめがあります(感謝)