はじめに
未来は意外と近くにある。nikkieです。
OpenAI DevDay キーノートの高揚が続いています。
今回はDevDayで発表&リリースされた新概念「Assistants API」についての素振りの模様です。
目次
- はじめに
- 目次
- Assistants APIのOverview
- 動作環境
- 1. Assistant作成
- 2. Thread作成
- 3. ThreadにMessages追加
- 4. Run(実行して、応答を得る)
- まとめ:Assistants API
- 終わりに
Assistants APIのOverview
キーノート中のAssistants APIのデモはこちら。
https://www.youtube.com/live/U9mJuUkhUzk?si=5w6Jg7_Vohx7Wo8Z&t=2028
「これ、どうやって作ってるんだろう」とめちゃくちゃ興味をそそられました
今回参照するのはこちらのドキュメント。
冒頭にAssistants APIを使う流れがまとまっています1。
APIが提供するリソースを統合して使う模様です。
- Assistantを作る
- カスタムインストラクションを定義
- モデルを選択
- ツールを有効化
- Code interpreter
- Retrieval
- Function calling
- Threadを作る(ユーザとの会話の始まり)
- ユーザが質問するときはThreadにMessagesを追加する
- 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()
- APIへのリクエスト
- https://platform.openai.com/docs/api-reference/threads/createThread
messages
パラメタのドキュメントによると、Messagesを渡さずにThreadを作れるし、渡して作ってもいい
- https://github.com/openai/openai-python/blob/v1.1.1/src/openai/resources/beta/threads/threads.py#L47-L58
- https://platform.openai.com/docs/api-reference/threads/createThread
- APIからのレスポンス
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と呼びかけてください。プレミアムアカウントのユーザです。"
... )
- APIへのリクエスト
- https://platform.openai.com/docs/api-reference/runs/createRun
instructions
引数でAssistantのinstructionを上書き3Override the default system message of the assistant. This is useful for modifying the behavior on a per-run basis.
- たしかにThreadをユーザごとに用意するとすれば、ユーザ名を渡してGPTの応答に含められそう
- https://github.com/openai/openai-python/blob/v1.1.1/src/openai/resources/beta/threads/runs/runs.py#L40-L55
- https://platform.openai.com/docs/api-reference/runs/createRun
- APIからのレスポンス
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 ... )
- APIへのリクエスト
- https://platform.openai.com/docs/api-reference/messages/listMessages
Returns a list of messages for a given thread.
- https://github.com/openai/openai-python/blob/v1.1.1/src/openai/resources/beta/threads/messages/messages.py#L172-L186
- https://platform.openai.com/docs/api-reference/messages/listMessages
レスポンスの型を確認すると
>>> 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
現時点の理解です。
- Assistantを作る(
client.beta.assistants.create
) - Threadを作る(
client.beta.threads.create
) - ユーザが質問するときはThreadにMessagesを追加する(
client.beta.threads.messages.create
) - 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クライアントの実装の仕方など、ソースを覗いた中にも発見がありました