OpenAI の API で使える Assistants & Retrieval を Colaboratory で試してみる

:::note info
2024年4月の更新でいくつか置き換わっているので別記事を書きました。
:::

https://qiita.com/hiroyuki-inaba/items/65a79009fc815b1627dc

## 背景
OpenAI から色々発表がありました。すでに試している人も大勢いらっしゃると思いますが、個人的にまず興味を惹かれた Assistants & Retrieval を試してみることにしました。ファイルをアップロードしてその中身を使って質問への回答を生成できるようになります。

https://openai.com/blog/new-models-and-developer-products-announced-at-devday

Playground からも色々できるようになっていましたが、将来のアプリへの組み込みも考慮して、Google Colaboratory で試してみます。

:::note warn
2023/11/7 時点ではこの機能は beta 版です。その後の機能更新で、手順や動作が変わっていることはあり得ますので、あらかじめご了承ください。
:::

## 環境
* Google Colaboratory (無料プランで大丈夫です)

## やりたいこと
* PDF ファイルをアップロードし読み込ませる
* 質問に対してそのファイルの内容を使った回答を生成させる

## 検証ステップ
基本的には Assistants API のページ沿って進めれば大丈夫なのですが、やりたいことをやりきるには説明が途中で切れている感じで最後の方は多少試行錯誤です。

https://platform.openai.com/docs/assistants/overview

### 1. Colaboratory に PDF ファイルをアップロード
まずは新規にノートブックを作成しランタイムに接続します。
その後、なんでも良いのですが、今回は私が持っている楽器の取扱説明書の PDF ファイルをアップロードしました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3535779/9f66991f-f9fa-c57b-fdba-17a7802f07ac.png)

### 2. 初期設定 & ライブラリの読み込み
それぞれコードブロックを用意して実行していきます。

```python: API KEY を環境変数に設定
%env OPENAI_API_KEY=[自分の KEY に置き換え]
```

```python: ライブラリをインストール
!pip install openai
```
:::note info
2023/11/7 時点ではライブラリのインストールでエラーが出力されるのですが、この手順の操作には影響ないようなので無視しています。
:::
```python: ライブラリを読み込み client を作成
from openai import OpenAI
client = OpenAI()
```

### 3. ファイルのアップロード
まずは回答生成時に参考にさせたいファイルを登録します。

```Python
file = client.files.create(
  file=open("EWI Solo v1.4.pdf", "rb"),
  purpose='assistants'
)
print(file)
```

### 4. アシスタントの作成
次にアシスタントを作成します。実行時にどんな動作をするのかの基本設定をまとめておくような感じでしょうか。Retrieval を使う指定、使用するファイルの指定もここで行います。
```Python
my_assistant = client.beta.assistants.create(
    instructions="You are a customer support chatbot. Use your knowledge base to best respond to customer queries.",
    model="gpt-4-1106-preview",
    tools=[{"type": "retrieval"}],
    file_ids=[file.id]
)
print(my_assistant)
```
API リファレンスのページのサンプルコードだと、メソッドが```openai.beta.assistants.create```となっていたりするのもありますが、ここでは初期設定のところで client としているので置き換えています。

https://platform.openai.com/docs/api-reference/assistants/createAssistant

ちなみに、2023/11/7 時点で対応しているモデルは 2 つのみで、それ以外を指定すると次のようなエラーが出ます。

>BadRequestError: Error code: 400 - {'error': {'message': "The requested model 'gpt-4' cannot be used with the 'retrieval' tool. Consider using 'gpt-4-1106-preview', 'gpt-3.5-turbo-1106', or a later model.", 'type': 'invalid_request_error', 'param': 'model', 'code': 'unsupported_model'}}

### 5. スレッドの作成
一連のメッセージのやり取りを束ねるスレッドを作成します。
```Python
thread = client.beta.threads.create()
print(thread)
```

### 6. スレッドにメッセージを追加
先ほど作成したスレッドにメッセージ(質問)を追加します。
```Python
message = client.beta.threads.messages.create(
    thread_id=thread.id,
    role="user",
    content="音色のお気に入りへの登録の仕方を教えてください。"
)
print(message)
```
このタイミングで使用させたいファイルを追加で指定することもできるようです。

### 7. 処理実行
このスレッドをこのアシスタントで処理実行してね、みたいにジョブのキューに入れるような感じです。このメソッド自体は処理を受け取ったタイミングで完了してしまいます。
```Python
run = client.beta.threads.runs.create(
  thread_id=thread.id,
  assistant_id=my_assistant.id
)
print(run)
```

### 8. 処理状況確認
実際に処理が終わっているのかは定期的に確認する必要があります。処理が完了していると、スレッドに生成されたメッセージが追加されます。
```Python
run_result = client.beta.threads.runs.retrieve(
  thread_id=thread.id,
  run_id=run.id
)
print(run_result)
print(run_result.status)
```

### 9. 生成されたメッセージを取得
追加された分も含めてメッセージを取り出します。
```Python
thread_messages = client.beta.threads.messages.list(thread.id)
print(thread_messages)

for m in thread_messages.data:
  print(m.content[0].text.value)
```
次のように出力を確認できました。(例によって試行ごとに揺らぎは出ます)
```
音色をお気に入りに登録するには、セーブしたFavoriteリストにアクセスするための「お気に入り（Favorites）ボタン」を使用します。リストにプログラムをセーブする際は「セーブ(Save)ボタン」を使います【17†source】。具体的な操作手順が必要であれば、もう少し詳細に説明を続けることが可能です。もしご希望であればお知らせください。
音色のお気に入りへの登録の仕方を教えてください。
```

ファイルからの検索には時たま失敗するようで、そうすると次のようなメッセージがスレッドに格納されていたります。
>申し訳ありませんが、ファイルの検索で再度エラーが発生しました。システムの問題かもしれません。もし可能であれば、別の方法を試してみるか、少し時間をおいてから再度お試しください。それでも解決しない場合は、他のサポートオプションを検討していただく必要があるかもしれません。もしご質問がございましたら、お気軽にお聞きください。

>ファイルの検索中にエラーが発生しました。もう一度試してみますので、少々お待ちください。

## その他
今回は Colaboratory で Python で試しましたが、Playgroud からも色々と確認や操作が行えるようになっていました。この手の検証をする時に、試しに作ったアシスタントやファイルをほったらかしにしてしまいがちになりますが、掃除するのを画面からポチポチやっていくだけで綺麗にできるのは楽で良いですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3535779/a037f7f6-2263-aa71-7ce2-dcf853606e1f.png)

## 所感
日本語も動作したのは嬉しいことです。(PDF の形式によってはうまくパースできないとかあるかもしれませんが)

もともとこの手の仕組みをやろうとすると、LangChain を使って PDF を分解してベクトル化してベクトルデータベースに入れて、みたいなことやる必要がありましたが、それも不要なのはなかなかに魅力的です。

とはいえ、あれこれバラバラに作ってそれを一つにまとめてバッチジョブのようにキューに入れる感じは、やったことある人には「あーこんな感じねー」みたいで済みますが、人によってはまずここの感覚を掴むのが第一関門かもしれません。

あとは、この非同期となる結果取得のポーリングをどう美しく作るか。また、私の仕事の役割上 Slack アプリ化を試してみたりするのですが、今までよりは少し実装上の工夫が必要なところが少々悩ましく。

Limitations にストリーミング出力やポーリング不要の仕組みも「すぐにやるよ！」と書いてくれているので少々様子見もありかなと思っています。

https://platform.openai.com/docs/assistants/how-it-works/limitations