OpenAI AssistantsのKnowledge Retrievalを活用してPDFの内容を要約するAPIを作ってみる

# 背景
- OpenAI のAssistantsを使ってみたい. 特に[Knowledge Retrieval](https://platform.openai.com/docs/assistants/tools/knowledge-retrieval).
- Assistants使ってPDFをアップロードすると500文字程度の要約を返すAPIを作ってみる
- ↓Assistantsの処理の流れ. FILE付きのPOSTリクエストが始まり、処理が終わると要約をResponseする.

![スクリーンショット 2024-01-15 16.55.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20307/1d98487a-eb10-c7f8-a4c9-8da91cb35747.png)

# OpenAIのコンソールからAssistant作成する
- APIコールされるたびにAssistant作成する必要はないのであらかじめ作っておく
- OpenAIのコンソールのAssistantタブ -> Create
- 以下のように設定
  - Instructionsに任意の指示
  - RetrievalをONにする
  - ThreadにFileを紐づけたいのでここではFILEを指定しない
- 作成したAssistantのIDをメモ

![スクリーンショット 2024-01-15 17.06.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20307/05663e11-5f6d-7cd4-7160-fdee5ce84832.png)


# API実装

- Flaskで実装

※動作検証目的なのでエラーハンドリングやセキュリティの考慮はされていません

```python
from flask import Flask, request, jsonify
import os
from openai import OpenAI
from pathlib import Path
import time

app = Flask(__name__)

# アップロードされるファイルの保存先ディレクトリ
api_key = os.environ.get('DEFAULT_API_KEY', '')
client = OpenAI(api_key=api_key)

@app.route('/upload', methods=['POST'])
def upload_file():
    # POSTリクエストでファイルが送られてきたか確認
    if 'file' not in request.files:
        return jsonify({'message': 'No file part in the request'}), 400

    file = request.files['file']

    # ファイル名が空かどうかチェック
    if file.filename == '':
        return jsonify({'message': 'No file selected for uploading'}), 400

    if file:
        # ファイルをサーバーに一時保存
        filename = './' + file.filename
        file.save(filename)

        # OpenAIのstorageにファイルをupload
        file = client.files.create(file=Path(filename), purpose='assistants')

        # スレッドの作成
        thread = client.beta.threads.create(
            messages=[{
                "role": "user",
                "content": "商品詳細情報のPDFです",
                "file_ids": [file.id]
            }]
        )

        # 実行
        # あらかじめ作成したアシスタントIDを使う
        print('===start run===')
        run = client.beta.threads.runs.create(
            thread_id=thread.id,
            assistant_id='YOUR_ASSISTNT_ID',
        )

        # Runが完了するまで繰り返す
        for i in range(100):
            print(f'={i+1}回目=')
            run = client.beta.threads.runs.retrieve(
                thread_id=thread.id,
                run_id=run.id
            )
            if run.status == 'completed':
                break
            else:
                time.sleep(3)

        # 要約テキストを返す
        messages = client.beta.threads.messages.list(thread_id=thread.id)
        for message in messages:
            msg = message.content[0].text.value
            return msg

if __name__ == '__main__':
    app.run(debug=True)

```

# テスト

## 資料
経済産業省の "生成 AI 時代の DX 推進に必要な人材・スキルの考え方" を使ってみます
https://www.meti.go.jp/press/2023/08/20230807001/20230807001-b-1.pdf

## POST

```bash
curl -X POST -F "file=@genai.pdf" http://localhost:5000/upload
```

## 結果
以下がレスポンスです。400文字弱と文字数は少し少ないですが、PDFを参考にした要約ができていることが確認できます。

*この文書は、デジタル時代の人材政策に関して、生成AIの影響を中心に議論している報告書のようです。内容は、生成AIによってもたらされる社会へのインパクトや、日本におけるデジタルトランスフォーメーション（DX）の現状を含んでおり、デジタル人材の育成や必要なスキルなどについても触れています。経済産業省は、デジタル田園都市国家構想を通じて地方のDXを加速させるためのデジタル人材の育成に注力し、デジタルスキル標準の整備や育成プラットフォームの構築などを行っています。また、DXの進展に伴う人材需要の高まりに対し、企業内での取り組みが進んでいるものの、まだ追いついていないとの現状が述べられ、特に地方におけるデジタル人材獲得の困難さが指摘されています。この問題への対応として、経済産業省は有識者を集めた検討会を開催し、デジタル時代に即した人材政策の検討を進めているとされています。*


# OpenAI Assistantsについて

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

Assistantsの重要な3つの概念

- Assistant: 特定の目的のために作成される、OpenAIのモデルとツール群を利用するAI
  - 使用できるツールは現在は以下の3つ:
      - Code Interpreter: サンドボックス環境でPythonコードを書いて様々なタスクを実行します
      - Knowledge Retrieval: 後述
      - Function Calling: 関数を渡すと呼び出す関数とその引数を返してくれます. Assistantでは関数を実行します.
- Thread: Assistantとユーザー間の会話履歴。スレッドはメッセージを保存し、モデルを呼び出す際にコンテキストに収めるために自動的にメッセージの切り詰めを行います
- Run: AssistantとThreadの組み合わせの実行用オブジェクト

# Knowledge Retrieval
モデルの外部からのファイルによって知識を追加する機能です。ファイルがアップロードされ、アシスタントに渡されると、OpenAIは自動的にドキュメントをチャンクに分割し、埋め込みをインデックス化して保存し、ユーザーのクエリに答えるための関連コンテンツを検索するベクター検索を実装します。

## ファイルの制約
- 最大20ファイルをAssistantに付与できる
- ファイルの最大サイズは512MB
- 512MBのサイズ上限に加え、1ファイル最大2,000,000tokenの上限もある
- 組織の合計ファイルサイズは最大100GB

## 価格

Retrievalの料金は、Assistantごとに日額0.20ドル/GBです。単一のファイルIDを複数のアシスタントに添付する場合、Retrievalが有効なときには、Assistantごとに日額の料金が発生します。例えば、Retrievalを有効にした状態で、同じ1GBのファイルを異なる2つのAssistantに添付した場合、料金が2倍（1日あたり 2 x 0.20ドル）請求されます。

さらに、メッセージに添付されたファイルは、アシスタントごとに課金されます。例えば、Retrievalを有効にした状態で、独自のファイルを1つずつ持つ10のメッセージ（合計10個のユニークなファイル）を含むスレッドでアシスタントを実行すると、全10ファイルに対して日額GBあたりの料金が発生します（アシスタント自体に添付されたファイルの料金に加えて）。

# まとめ
AssistantのKnowlege Retrievalを使った簡単なアプリケーションを実装し、Assistantの概要を学びました。非常に簡単にファイルのRetrival機能が実装できました。Code InterpriterやFunction Callingのtoolもあるので、OpenAIモデルだけを利用するのであればLangChainなど使わなくてもAssistantで十分だなという印象です。今はベータ版ですが、さらに今後開発が進んで行き、Retrieval機能も精度が上がったり、他のtoolが実装されていったりするはずなので楽しみです。