OpenAIクラスを読む③

# 目次
1. はじめに
2. 環境(変更)
3. 内容
4. Assistantsクラス
5. Threadsクラス
6. おわりに

# 1. はじめに
この記事はOpenAIのAPIを触るにあたって、勉強した内容(2025年4月当時)を共有する目的で書いています。内容はすぐ変わります。
OpenAI公式の[API Refernce](https://platform.openai.com/docs/api-reference/introduction)を読む方が参考になります。
対象や環境などは[初回の記事](https://qiita.com/mocisot/items/1227effd02ca967f5558)をご覧ください。
# 2. 環境(変更)
* バージョン : openai 1.75.0
# 3. 内容
1. Assistantsクラス
    * 概要
    * createメソッド
    * retrieveメソッド
    * updateメソッド
    * listメソッド
    * deleteメソッド
1. Threadsクラス
    * 概要
    * createメソッド
    * messagesメソッド
    * runsメソッド
    * まとめ
# 4. Assistantsクラス
### 概要
Assistantsクラスは2025年4月当時でβ版の機能です。
このクラスはAssistantクラスを管理するクラスと考えて問題ないでしょう。
Assistantクラスはassistant.pyから確認することができます。このクラスはメソッドが存在しないため、データをまとめるためだけのクラスとなっています。中身を以下にまとめます。
* ```id``` : 識別するための文字列
* ```created_at``` : 作成された時間
* ```description``` : Assistantの概要(使用用途など)
* ```instructions``` : Assistantへの回答の指示や性格など
* ```metadata``` : タグ付けに使用できるもの(あまり分かっていません)
* ```model``` : 回答の際に使用されるモデル(バージョン)
* ```name``` : Assistantの名前
* ```tools``` : Assistantにチャット以外の機能を扱わせる
* ```temperature``` : 回答のバラつき具合

以上を見て持ってほしいAssistantのイメージは**ChatGPTで新しいチャットを開き、一番最初に設定を書き並べたもの**です。

### createメソッド

初めにcreateメソッドについて見ていきます。assistants.pyの57~77行目を引用します。
```python
    def create(
        self,
        *,
        model: Union[str, ChatModel],
        description: Optional[str] | NotGiven = NOT_GIVEN,
        instructions: Optional[str] | NotGiven = NOT_GIVEN,
        metadata: Optional[Metadata] | NotGiven = NOT_GIVEN,
        name: Optional[str] | NotGiven = NOT_GIVEN,
        reasoning_effort: Optional[ReasoningEffort] | NotGiven = NOT_GIVEN,
        response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN,
        temperature: Optional[float] | NotGiven = NOT_GIVEN,
        tool_resources: Optional[assistant_create_params.ToolResources] | NotGiven = NOT_GIVEN,
        tools: Iterable[AssistantToolParam] | NotGiven = NOT_GIVEN,
        top_p: Optional[float] | NotGiven = NOT_GIVEN,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
    ) -> Assistant:
```
先ほど解説したAssistantクラスの中身が殆ど登場しており、返り値もAssistantクラスです。

### retrieveメソッド

次にretrieveメソッドについて見ていきます。ただ、[前回](https://qiita.com/mocisot/items/4b3954c7c5f8d6fe137d)と同様にretrieveの日本語訳から分かる通りですので、特に語ることはありません。assistants.pyの182~192行目を引用します。
```python
    def retrieve(
        self,
        assistant_id: str,
        *,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
    ) -> Assistant:
```
ここから分かる通り、assistant_idを指定して該当するAssistantを返します。

### updateメソッド
次にupdateメソッドですが、コードは省きます。これも名前から推測される通り、**指定したidを持つAssistantクラスの更新**に用いられます。見たい方は同ファイルの216~384行目に書かれていますのでご覧ください。

### listメソッド
次にlistメソッドについて見ていきます。これは同ファイルの386~399行目に書かれていますので引用します。
```python
    def list(
        self,
        *,
        after: str | NotGiven = NOT_GIVEN,
        before: str | NotGiven = NOT_GIVEN,
        limit: int | NotGiven = NOT_GIVEN,
        order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
    ) -> SyncCursorPage[Assistant]:
```
ここから分かるのは、SyncCursorPage[Assistant]が返り値として設定されていることです。
では、このSyncCursorPageについて見ていきます。pagination.pyの62~92行目に書かれています。
```python
class SyncCursorPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
    data: List[_T]
    has_more: Optional[bool] = None

    @override
    def _get_page_items(self) -> List[_T]:
        data = self.data
        if not data:
            return []
        return data

    @override
    def has_next_page(self) -> bool:
        has_more = self.has_more
        if has_more is not None and has_more is False:
            return False

        return super().has_next_page()

    @override
    def next_page_info(self) -> Optional[PageInfo]:
        data = self.data
        if not data:
            return None

        item = cast(Any, data[-1])
        if not isinstance(item, CursorPageItem) or item.id is None:
            # TODO emit warning log
            return None

        return PageInfo(params={"after": item.id})
```
ここで、```_T```とありますが、これは任意の型を指すそうです。

### deleteメソッド
最後にdeleteメソッドですが、これも名前の通り、Assistantを削除するためのメソッドです。
assistants.pyの451~483行目に書かれています。
ここで、deleteメソッドの返り値として指定されているAssistantDeletedクラスについて見てみます。assistant_deleted.pyの10~15行目を引用します。
```python
class AssistantDeleted(BaseModel):
    id: str

    deleted: bool

    object: Literal["assistant.deleted"]
```
これを見ると**deletedをFalseにしたり**、**objectにLiteral["assistant"]を入れたり**してみるとどうなるのかという疑問が生じると思います。
そのようなコードを書き実験してみたところ、当然のごとく削除したAssistantは使えなくなりました。
**間違っても遊び半分で削除しないようにしましょう。**
ただし、作りすぎることによるリスクもありますので、管理はしっかりしましょう。

# 5. Threadsクラス

### 概要
Threadsクラスは2025年4月当時でβ版の機能です。
このクラスもThreadクラスを管理するクラスと考えて問題ないでしょう。
Threadクラスはthread.pyから確認することができます。こちらもAssistantクラスと同様にデータをまとめるクラスになっています。中身を以下にまとめます。

* ```id``` : 識別するための文字列
* ```created_at``` : 作成された時間
* ```metadata``` : タグ付けに使用できるもの(あまり分かっていません)
* ```tool_resources``` : ファイルの分析などに用いる

### createメソッド
初めにcreateメソッドについて見ていきます。threads.pyの92~104行目を引用します。
```python
    def create(
        self,
        *,
        messages: Iterable[thread_create_params.Message] | NotGiven = NOT_GIVEN,
        metadata: Optional[Metadata] | NotGiven = NOT_GIVEN,
        tool_resources: Optional[thread_create_params.ToolResources] | NotGiven = NOT_GIVEN,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
    ) -> Thread:
```
ここで重要なのは先ほど紹介した通り、Threadクラスにmessagesという要素はないはずなのに引数には存在していることです。
つまり、**ThreadとMessageは別の概念**ということです。

### messagesメソッド
次にmessagesメソッドについて見ていきます。同ファイルの70,71行目に書かれています。
```python
    def messages(self) -> Messages:
        return Messages(self._client)
```
Messagesクラスを引数に持つという簡単なものですね。
このクラスもcreate,retrieve,update,list,deleteとよく見るメソッドが並んでいます。
それでは、Messagesクラスのcreateメソッドについて見ていきます。messages.pyの53~117行目に書かれています。53~67行目を引用します。
```python
    def create(
        self,
        thread_id: str,
        *,
        content: Union[str, Iterable[MessageContentPartParam]],
        role: Literal["user", "assistant"],
        attachments: Optional[Iterable[message_create_params.Attachment]] | NotGiven = NOT_GIVEN,
        metadata: Optional[Metadata] | NotGiven = NOT_GIVEN,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
    ) -> Message:
```
ここから分かるのは、Messageクラスが返り値として設定されていることです、
このMessageクラスについて見ていきます。message.pyの41~103行目に書かれています。
中身を以下にまとめます。
* ```id``` : 識別するための文字列
* ```assistant_id``` : 回答に使用したAssistantのID
* ```attachments``` : toolの使用した結果などの出力に必要
* ```completed_at``` : 回答が完了した時間
* ```content``` : メッセージの内容
* ```created_at``` : メッセージが作成された時間
* ```run_id``` : 実行に割り当てられているID
* ```thread_id``` : 回答に使用したThreadのID
* ```role``` : Messageの作成元

つまり、messagesメソッドは**Messageクラスとそれを管理するメソッドをまとめたクラスを扱うメソッド**です。

### runsメソッド
次にrunsメソッドについて見ていきます。
threads.pyの70~71行目に書かれています。
```python
    def runs(self) -> Runs:
        return Runs(self._client)
```
こちらもRunsクラスを引数に持つものですね。
このクラスはいつものメソッドに加えてsteps,cancel,streamメソッドなどを持っています。
Runsクラスのcreateメソッドについて見ていきます。runs.pyの86~533行目に書かれています。86~114行目を引用します。
```python
    def create(
        self,
        thread_id: str,
        *,
        assistant_id: str,
        include: List[RunStepInclude] | NotGiven = NOT_GIVEN,
        additional_instructions: Optional[str] | NotGiven = NOT_GIVEN,
        additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | NotGiven = NOT_GIVEN,
        instructions: Optional[str] | NotGiven = NOT_GIVEN,
        max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN,
        max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN,
        metadata: Optional[Metadata] | NotGiven = NOT_GIVEN,
        model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN,
        parallel_tool_calls: bool | NotGiven = NOT_GIVEN,
        reasoning_effort: Optional[ReasoningEffort] | NotGiven = NOT_GIVEN,
        response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN,
        stream: Optional[Literal[False]] | NotGiven = NOT_GIVEN,
        temperature: Optional[float] | NotGiven = NOT_GIVEN,
        tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN,
        tools: Optional[Iterable[AssistantToolParam]] | NotGiven = NOT_GIVEN,
        top_p: Optional[float] | NotGiven = NOT_GIVEN,
        truncation_strategy: Optional[run_create_params.TruncationStrategy] | NotGiven = NOT_GIVEN,
        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
        # The extra values given here take precedence over values defined on the client or passed to this method.
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
    ) -> Run:
```
ここから分かるのはthread_idとassistant_idが引数、Runクラスが返り値として設定されていることです。
このRunクラスについて見ていきます。run.pyの83~245行目に書かれています。
中身を以下にまとめます。

* ```id``` : 識別するための文字列
* ```assistant_id``` : 実行に使用されるAssistantのID
* ```created_at``` : 実行が作成された時間
* ```instructions``` : その実行のみ特別な処理を行いたい場合に設定
* ```metadata``` : タグ付けに使用できるもの
* ```model``` : 回答に使用したモデル
* ```thread_id``` : 実行に使用されるThreadのID
* ```temperature``` : 回答のバラつき度合い

つまり、runsメソッドは**Runクラスとそれを管理するメソッドをまとめたクラスを扱うメソッド**です。

### まとめ
Threadsクラスは```Thread```クラスとそれに付随するクラスを扱うクラスです。
以下のように扱います。
1. threads.create()でThreadクラスを作成
2. threads.messages.create()でMessageクラスを作成
3. runs.create()でRunクラスを生成
4. Runクラスに使用するAssistantとThreadを設定
5. 実行時にthread_idが設定された未返信のmessageを取得
6. 返答を生成
7. Threadに返答が追加

# 6. おわりに

今回でOpenAIのAPIの勉強した内容は以上になります。
改善点がありましたらご教示くださると幸いです。