【Ruby】OpenAI Assistants API File Search機能の実装方法

# はじめに

この記事では、`ruby-openai`という Gem を使用して OpenAI の Assistants API の File Search 機能を利用する Ruby プログラムの実装方法について解説します。


## 前提条件

* OpenAI の API を取得できること
* Assistants API の概要を知っていること
    * Assistants API 概要
        * https://platform.openai.com/docs/assistants/overview
* Assistants、 は OpenAI のコンソールから作成済みであることとします
    * OpenAI コンソール
        * https://platform.openai.com/

## 必要な環境と事前準備

### Gemのインストール

まずは、`ruby-openai`というGemをインストールします。これはOpenAIのAPIにアクセスするためのライブラリです。

```bash
gem install ruby-openai
```

### 必要なライブラリのインポート

次に、必要なライブラリをインポートします。

```ruby
require 'openai'
require 'timeout'
```

## 全体像

このプログラムでは、以下の手順で OpenAI API にアクセスし、質問に対する回答を取得します。

1. OpenAI コンソールにて Assistants 作成、Vector Store 作成
2. OpenAI のクライアントを初期化
3. スレッドを作成
4. メッセージを送信
5. 実行を開始し、ステータスを監視
6. 実行が完了したら、結果を取得

### OpenAI コンソール

Assistants と Vector Store は OpenAI のコンソールにて作成していきます。

- OpenAI コンソール
    - https://platform.openai.com/

#### Vector Store 作成

以下の添付画像の①〜⑥をクリック・入力していくことで Vector Store を作成し、File Search したいファイルを紐づけることができます。

ここでは以下のテキストを`ibm_news.txt`という名前で保存して、Vector Store に紐付けをします。

```txt
IBM moves deeper into hybrid cloud management with $6.4B HashiCorp acquisition

IBM wisely gravitated away from trying to be a pure cloud infrastructure vendor years ago, recognizing that it could never compete with the big three: Amazon, Microsoft and Google. It has since moved on to helping IT departments manage complex hybrid environments, using its financial clout to acquire a portfolio of high-profile companies.
It began with the $34 billion Red Hat acquisition in 2018, continued with the Apptio acquisition last year and it kept it going on Wednesday when the company announced that it would be acquiring cloud management vendor HashiCorp for $6.4 billion.
```

![VectorStore_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308256/48f1c1bc-a5ac-1163-34a5-e67f0840fea6.png)

![VectorStore_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308256/de9094d6-4969-4629-29c6-6ad5d1c3fb11.png)


#### Assistants 作成

以下の添付画像の①〜⑨をクリック・入力していくことで Assistants を作成し、先ほど作成した Vector Store を紐づけることができます。

![Assistants_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308256/7bd3d8e9-09b8-eafc-f75e-cb5054f5a04c.png)

![Assistants_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308256/5595f7f0-4d3b-69ea-346c-563d3f9cc5a1.png)

![Assistants_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308256/59202b92-2f1e-14f1-b730-befe8a4b7fd7.png)


### クライアントの初期化

ここから、Ruby でプログラムを実装していきます。
OpenAIのクライアントを初期化します。ここで、APIアクセストークンを使用して認証します。

```ruby
assistant_id = 'asst_**********************'
openai_access_token = '******************************************'

client = OpenAI::Client.new(
  access_token: openai_access_token,
  log_errors: true # 開発中にエラーを確認するために推奨。本番環境では推奨されません。
)
```

### スレッドの作成

次に、APIを通じてスレッドを作成します。
スレッドは、ユーザーとAIアシスタントの対話を管理するためのものです。

```ruby
response = client.threads.create
thread_id = response["id"]
```

### メッセージの送信

スレッドが作成されたら、ユーザーの質問内容をメッセージとしてスレッドに追加します。

```ruby
content = "IBMが最近買収した会社を教えてください。"
message_id = client.messages.create(
    thread_id: thread_id,
    parameters: {
        role: "user", # 手動で作成されたメッセージには必須
        content: content
    }
)["id"]
```

### 実行

メッセージを送信した後、実行を開始し、そのステータスを監視します。

```ruby
response = client.runs.create(thread_id: thread_id,
    parameters: {
        assistant_id: assistant_id
    }
)
run_id = response['id']
```

### ステータスの監視とタイムアウト処理

実行のステータスを定期的にチェックし、完了するまでループを継続します。
また、タイムアウト処理を追加し、一定時間内に完了しない場合はエラーをログに記録します。

```ruby
begin
  Timeout.timeout(120) do  # タイムアウトを120秒に設定
    # completed になるまで待つ
    while true do
      response = client.runs.retrieve(id: run_id, thread_id: thread_id)
      status = response['status']

      case status
      when 'queued', 'in_progress', 'cancelling'
        puts 'Sleeping'
        sleep 1 # 1秒待機して再度ポーリング
      when 'completed'
        break # ループを抜けて結果を報告
      when 'requires_action'
        # ツール呼び出しの処理（詳細は後述）
      when 'cancelled', 'failed', 'expired'
        puts response['last_error'].inspect
        break # または `exit`
      else
        puts "Unknown status response: #{status}"
      end
    end
  end
rescue Timeout::Error
  logger.error("Operation timed out")
  return false
end
```

### 結果の取得

実行が完了したら、スレッド内の最新のメッセージを取得し、結果を確認します。

```ruby
messages = client.messages.list(thread_id: thread_id, parameters: { order: 'desc' })
latest_message = messages["data"].first
latest_value = latest_message["content"].first["text"]["value"]
latest_value
#=> IBMは64億ドルでHashiCorpを買収しました。
```

## まとめ

Ruby で OpenAI の Assistants API File Search 機能の実装方法を紹介しました。
RAG（検索拡張生成）的な実装を OpenAI の API を使用するだけで実装できることがかなり簡単だと感じました。
`gpt-4o`でコストも低くなってAPIを使用するハードルがさらに下がったので、今後も積極的に活用していこうと思います。

---
### 会社紹介

**株式会社 Mosaica**  
最先端テクノロジーで社会課題を解決し、持続可能な未来を創造する IT カンパニー。
AI ソリューション、クラウド統合、DX 推進、経営コンサルティングなど包括的なサービスでビジネス変革を支援しています。  

詳しくは [公式サイト](https://mosaica.co.jp/) までお気軽にご相談ください。
公式サイト： https://mosaica.co.jp/