Open AIのBatchを使ってdev.toのトレンド記事を要約してくれるアプリを作った

## 作ったもの

### Windows版

https://github.com/7tsuno/dev.to-summarizer/releases/tag/1.0
dev.to-summerizer-1.0.0-setup.exe
をダウンロードしてセットアップできます


### Mac版

ADPへの登録が必要で手続きが煩雑なため、現在提供していません。

Macで動かしてみたい人は
https://github.com/7tsuno/dev.to-summarizer
をCloneして使ってみてください

## どんなアプリ？

* [dev.to](https://dev.to/)の記事をまとめて要約してくれるアプリです
    * tagと取得件数と期間を指定して、トレンド記事を取得することができます
    * 取得したトレンド記事の中で興味があるものをまとめて要約することができます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154239/21dd2cc5-8505-a58f-33fd-c7e5014fcee2.png)


## なんでつくったの？

* OpenAIの[Batch API](https://platform.openai.com/docs/api-reference/batch)を使ってみたかった
* Electronのアプリを作ってみたかった ~~本当はTauriが使いたかったけど諦めた~~
* 海外の最新トレンドを追いたかった

## 対象読者

* OpenAIのBatch APIって実際どうなの？って人
* Batchの動くサンプルが欲しい人

## 書かないこと

* 詳細な実装のはなし
* バッチ処理以外の話 (特筆すべき点がないのでカットしました)

## OpenAIのBatch API

Batch APIは、大量のリクエストを非同期に処理できるAPIです。これを使用すると、リアルタイムで答えを得られる普通のAPIとは違い、リクエストを一括で送信し、24時間以内に結果をまとめて取得できます。

今回のアプリでは、複数の記事を一気に要約させるためにBatch APIを利用しています。

ここではアプリに取り込む場合の[OpenAIのSDK](https://github.com/openai/openai-node)を使ったAPIの使い方を紹介したいと思います。

### Batch APIのメリット

- **トークンあたりの金額が普通のAPIの半額**
- 大量のリクエストを一度に送信し、結果をまとめて取得できる

### Batch APIのデメリット

- リアルタイムに結果を取得することはできない

### Batch APIの大まかな流れ

1. プロンプトをまとめたファイルをアップロードする
2. ファイルに対して処理を実行する
3. 実行結果を確認する
4. 実行結果のファイルをダウンロードする

これを一つずつ見ていきます。

---

### プロンプトをまとめたファイルをアップロードする

Batch APIのInputはファイルである必要があります。jsonlという形式のファイルに1行1プロンプトを入れていきます。この形式は、JSONを行単位で構造したファイルです。

イメージとしては↓のような感じです。他にも必要なパラメータはありますが、ここでは省略しています

```json
{"messages": [{"role": "user", "content": "1 + 1は？"}]}
{"messages": [{"role": "user", "content": "日本の首都は？"}]}
{"messages": [{"role": "user", "content": "株式会社CureAppの社長の名前は？"}]}
```


この形式のファイルを作成して、ファイルをアップロードします。

```typescript
await openai.files.create({
    file: fs.createReadStream(batchInputFilePath),
    purpose: 'batch'
})
```

また、アップロードに成功すると、OpenAIのコンソール上でStorageにファイルがアップロードされていることが分かります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154239/8137fdeb-d655-66e7-5638-4e0ed8c6aa8c.png)

---

### ファイルに対して処理を実行する

先ほどのファイルアップロードのレスポンスとしてファイルIDが取得できます。このIDに対してバッチ処理を実行するAPIを叩きます。

```typescript
await openai.batches.create({
  input_file_id: inputFile.id,
  endpoint: '/v1/chat/completions',
  completion_window: '24h'
})
```

この処理は非同期なので、この時点では結果を取得することはできません。

---

### 実行結果を確認する

先ほどのBatch処理実行のレスポンスとしてBatch IDが取得できます。このIDに対して実行結果を確認するAPIを叩きます。

```typescript
await openAI.batches.retrieve(batchId)
```

このような返却値が返ってきます。

```json
{
  "id": "batch_abc123",
  "object": "batch",
  "endpoint": "/v1/completions",
  "status": "completed",
  "output_file_id": "file-cvaTdG",
  ...
}
```

`status` は`in_progress` , `failed` , `completed`などがあり、`completed`の場合は`output_file_id`があるので、これを取得することができます。

---

### 実行結果のファイルをダウンロードする

`status` が`completed`の場合はファイルをダウンロードする処理を行うことができます。取得した`output_file_id`を利用してファイルをダウンロードします。

```typescript
await openAI.files.content(fileId)
```

取得した内容もまたjsonl形式なので、良い感じにparseして使用しましょう。



## コード

https://github.com/7tsuno/dev.to-summarizer

## おわりに

Batch API、使ってみると正直使いづらいです。ファイル作るのもparseするのもしんどいです。
ただ、非同期で問題ない処理の場合はコストを抑えるために積極的に使っていったほうが良さそうだなと思っています。
どこかの天才の人がいい感じのラッパーを作ってくれたら嬉しいですね。