OpenAI Assistants APIをPythonで実装する方法を解説

## 1. はじめに
11月6日に開催されたOpenAI DevDayにおいて発表されたAssistants APIをPythonで呼び出す方法について解説します。Assistants APIは、独自のアプリケーション内でAIアシスタントを構築するための強力なAPIです。このAPIを使えば、カスタムの指示に基づいたアシスタントを作成し、様々なモデルやツール、そして外部の知識を利用してユーザーの問い合わせに応答することが可能になります。特に`Code Interpreter`、`Retrieval(外部知識を用いた検索)`, `Function calling(関数呼び出し)`などのツールがサポートされており、今後はさらに多くのツールが追加予定らしいです。

この記事では、Assistants APIを利用してPythonでアシスタントを作成し実行するための基本的なステップを紹介します。

具体的には、
1. カスタムの指示を定義しモデルを選択することでアシスタントをAPI上で作成
2. ユーザーが会話を開始した際のスレッド作成
3. ユーザーの質問に応じたスレッドへのメッセージ追加
4. アシスタントをスレッド上で実行して出力を取得

これらをコード例とともに説明します。

※ただし、このAPIはベータ版であるため、仕様が変更される可能性があります。

Pythonプログラミングに慣れている方であれば、このガイドに沿って簡単に自分だけのAIアシスタントを作成することができるでしょう。それでは、Assistants APIを使用してPythonで動作するAIアシスタントを構築する方法を見ていきましょう。

【追記】
`Code Interpreter`を有効にしたファイル入出力を扱う記事は[こちら](https://qiita.com/sh237/items/f8720660be462c93b78c) (Assistants APIのPythonでの実装が分からない場合には先に本記事をご覧ください。)

## 2.  準備
このセクションでは、OpenAIのAssistants APIを使用するために必要な準備作業について説明します。これには、Python環境の設定、必要なライブラリのインストール、そしてAPIキーの設定が含まれます。

#### 2.1 Python SDKのアップグレード
まずはじめに、OpenAIのPython SDKを最新バージョン(version 1.2)にアップグレードする必要があります。コマンドラインで以下のコマンドを実行してください。

```bash
pip install --upgrade openai
```

#### 2.2 必要なライブラリのインストール
次に、openai以外に必要なライブラリをインストール・インポートします。以下のコードをPythonスクリプトに追加してください。
```python:ライブラリのimport
import os
import time
import requests

import openai
from openai import OpenAI
```

#### 2.3 APIキーの設定
APIキーは、OpenAIのサービスへのアクセスに必須の認証情報です。セキュリティのため、APIキーは環境変数に格納し、コード内でそれを読み込む方法を推奨します。以下のコードスニペットを使用して、環境変数からAPIキーを取得し、APIクライアントを初期化します。

```python
# APIキーの設定
api_key = os.environ.get("OPENAI_API_KEY")
client = OpenAI(api_key=api_key)
```

#### 2.4 アシスタントのパラメータ
Assistants APIでは、アシスタントがユーザーのメッセージに応答する方法を構成するためにいくつかのパラメータを設定できます。

- **Instructions** : 従来のカスタムインストラクションです。アシスタントとモデルがどのように振る舞い、応答するかを定義します。
- **Model** : GPT-3.5やGPT-4を含むさまざまなモデルを指定できます。`Retrieval`には、`gpt-3.5-turbo-1106`および `gpt-4-1106-preview`モデルが必要です。
- **Tools** : OpenAIが構築およびホストしている`Code Interpreter`や`Retrieval`などのツールをサポートします。

## 3.実装
このセクションでは、Assistants APIを使用して、カスタムアシスタントを作成し、それを利用してユーザーの質問に応答する方法を具体的に説明します。
ここでは、[OpenAI公式Docs](https://platform.openai.com/docs/assistants/overview)と同様に、Code Interpreterを有効にした数学チューターのアシスタントを作成していきます。

#### 3.1 アシスタントの作成
最初のステップとして、特定の指示とモデルを持つ新しいアシスタントを作成します。以下のコードは、数学の問題に答えるための数学チューターとして機能するアシスタントを作成しています。
```python

assistant = client.beta.assistants.create(
    name="Math Tutor",
    instructions="You are a personal math tutor. Write and run code to answer math questions.",
    model="gpt-4-1106-preview"
)
assistant_id = assistant.id
print("assistant_id:", assistant_id)
```
ここで、`assistant_id`は後でスレッドを実行する際に使用します。

#### 3.2 スレッドの作成とメッセージの追加
アシスタントが作成された後、ユーザーとの対話を管理するためのスレッドを作成します。以下のコードは、新しいスレッドを作成し、ユーザーの質問をメッセージとして追加しています。

ここで、ユーザーは数学の問題「3x + 11 = 14」という方程式を解くための助けを求めています。

```python
thread = client.beta.threads.create()
thread_id = thread.id
print("thread_id:", thread_id)

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?"
)
```
ここで、`thread_id`は後でメッセージを取得する際に使用します。

#### 3.3 スレッドの実行とステータスの確認
次に、スレッドを実行し、アシスタントからのレスポンスを待ちます。以下のコードでは、スレッドを実行し、ステータスが`completed`になるまで一定時間毎にステータスの確認をしています。
```python
run = client.beta.threads.runs.create(
    thread_id=thread_id,
    assistant_id=assistant_id,
    instructions="Please address the user as Jane Doe. The user has a premium account."
)
run_id = run.id
print("run_id:", run_id)

completed = False
while not completed:
    run = client.beta.threads.runs.retrieve(thread_id=thread_id, run_id=run_id)
    print("run.status:", run.status)
    if run.status == 'completed':
        completed = True
    else:
        time.sleep(5)
```

#### 3.4 スレッド内のメッセージと注釈の処理
スレッドの実行が完了した後、スレッド内のすべてのメッセージを表示し、注釈を付ける処理を行います。アシスタントによって生成されたメッセージには、リスポンスの配列内に注釈が含まれる場合があります。これらの注釈は、メッセージ内のテキストをどのように注釈付けすべきかに関する情報を提供します。 

##### アノテーション(注釈)の種類 
メッセージには以下の二種類の注釈が含まれることがあります。 
1. `file_citation`: `Retrieval`によって作成されるファイル引用は、アシスタントがレスポンスを生成するために使用した特定のファイル内の特定の引用を参照します。 
2. `file_path`: `Code Interpreter`によって作成されるファイルパス注釈には、ツールによって生成されたファイルへの参照が含まれます。 

注釈がメッセージオブジェクト内に存在する場合、テキスト内にはモデルによって生成された読みにくい文字列（例：​​やsandbox:/mnt/data/file.csvなど）が見られることがあり、これらの文字列を注釈の情報で置き換える必要があります。 以下のコードは、スレッド内のメッセージを取得し、各メッセージに含まれる注釈を処理する方法を示しています。
```python
messages = client.beta.threads.messages.list(thread_id=thread_id)

messages_value_list = []
for message in messages:
    message_content = message.content[0].text

    annotations = message_content.annotations

    citations = []

    # アノテーションを反復処理し、脚注を追加
    for index, annotation in enumerate(annotations):
        # テキストを脚注で置き換える
        message_content.value = message_content.value.replace(annotation.text, f' [{index}]')

        # アノテーションの種類毎に引用を収集
        if (file_citation := getattr(annotation, 'file_citation', None)):
            cited_file = client.files.retrieve(file_citation.file_id)
            citations.append(f'[{index}] {file_citation.quote} from {cited_file.filename}')
        elif (file_path := getattr(annotation, 'file_path', None)):
            cited_file = client.files.retrieve(file_path.file_id)
            citations.append(f'[{index}] Click <here> to download {cited_file.filename}')

    # ユーザーに表示する前に、メッセージの末尾に脚注を追加
    message_content.value += '\n' + '\n'.join(citations)
    messages_value_list.append(message_content.value)
``` 
このコードでは、メッセージ内の特定のテキストを、注釈に基づいた情報（例えばファイルの引用やファイルパス）で置き換えることにより、可読性を高めています。

※ 今回の例では注釈が付いたりファイルが出力されたりはしないはずなので、厳密にはこの処理は不要です。

## 4. 結果
コードの実行が完了した後、生成されたメッセージとアシスタントによるレスポンスを確認します。これにより、アシスタントがユーザーの質問にどのように応答したかを確認できます。

#### 4.1 結果の表示
以下のコードを使用して、アシスタントによって生成されたメッセージのリストを表示します。
```python
print("messages_list:", messages_value_list)
```

#### 4.2 結果の内容
実際に表示されたメッセージは次の通りです。
```
messages_list: ["Of course, Jane Doe. To solve the equation `3x + 11 = 14`, you need to find the value of `x` that makes the equation true. Let's solve for `x` step by step:

Starting with the equation:

`3x + 11 = 14`

First, you want to isolate the term containing `x` on one side of the equation. To do this, you'll need to remove the `11` from the left side. You can do this by subtracting `11` from both sides of the equation. This gives:

`3x + 11 - 11 = 14 - 11`

Simplifying both sides of the equation:

`3x = 3`

Now, you have `3x` equals `3`. To solve for `x`, divide both sides of the equation by `3` to isolate `x`:

`3x / 3 = 3 / 3`

This simplifies to:

`x = 1`

So the solution to the equation `3x + 11 = 14` is `x = 1`."

, 'I need to solve the equation `3x + 11 = 14`. Can you help me?']
```
正しい答えを段階的に生成できていることが分かります。

このように、Assistants APIはユーザーの具体的な質問に対して有用で理解しやすい回答を提供することが可能です。ユーザーのニーズに応じたカスタマイズされたレスポンスは、アシスタントをさまざまな用途で有効に活用する鍵となります。

## 5. まとめ
この記事では、OpenAIのAssistants APIをPythonを用いて呼び出す方法を説明しました。

今回は触れませんでしたが、Assistants APIではアシスタントにファイルの情報を持たせたり、メッセージの入力としてファイルを用いることも可能です。これらの機能は、データを扱う複雑なシナリオや、特定のファイル形式に依存するタスクに特に有効です。

`Code Interpreter`によるファイル入出力を扱う記事(続編)は以下
[OpenAI Assistants API(Code Interpreter)をPythonで実装する方法を解説](https://qiita.com/sh237/items/f8720660be462c93b78c)

最後に、Assistants APIは柔軟で強力なツールであり、ユーザーのニーズに応じた様々な応答を生成することが可能です。このAPIを使用することで、特定の業務を自動化したり、ユーザーエクスペリエンスを向上させたりすることができます。今後もOpenAIが提供する新しい機能やツールに注目し、さらなる技術革新を目指していきましょう。