【備忘録】Next.js×OpenaiAPIの使い方

## はじめに
この記事は、OpenAIAPIを使ったwebアプリを作成している途中で、公式ドキュメントと検索でヒットする物の間に乖離があったため、備忘録としてまとめたものです。

開発絶賛勉強中の私が書いたものであるため、必ずしもベストプラクティスではないことに注意しください！

もっと良いディレクトリ構成やAPIの呼び出し方などがあれば教えていただけると幸いです！

## 動作確認環境
- Next.js 13.4.5
- React 18.2

## 公式ドキュメント
公式ドキュメントにはnode.js向けのapiの叩き方として次のような方法が掲載されています。

OpenAIクラスをインポートして、インスタンス化したものを使うという感じですね。

今回はこれを骨組みにします。

```typescript:公式Doc
import OpenAI from "openai";
const openai = new OpenAI();
const completion = await openai.chat.completions.create({
    model: "gpt-4o",
    messages: [
        {"role": "user", "content": "write a haiku about ai"}
    ]
});
```
https://platform.openai.com/docs/overview?lang=node.js

## 外部APIはサーバから呼び出す
クライアントからでもコードを書けば外部APIを使うことはできますが**セキュリティ**の観点からサーバ側で処理するべきみたいです。

➡　**Next.jsが提供するRouteHandlersを使い、中間APIを作成する**

## ディレクトリ構成
今回説明するコードのディレクトリ構成です。慣習から逸れている部分がありましたら指摘していただけると幸いです！
```
project-root/
│
├── .env                    
├── .gitignore              
│
├── app/                    
│   ├── api/                
│   │   └── openai/         
│   │       └── route.ts  
│   │
│   ├──utils/           
│   │   └── callOpenai.tsx  
│   │
│   └── test.tsx
│
└──  types.tsx                 
          
```

## 準備
### インストール
```terminal
npm install openai
```
### APIキーの作成
下のページから発行できます。詳しくは添付した記事を参照してください！

https://openai.com/index/openai-api/

https://qiita.com/kurata04/items/a10bdc44cc0d1e62dad3

### APIキーの登録
プロジェクト直下の.envファイルに次のようにAPIキーを貼りつけます

::: note warn
.envファイルが.gitignoreにあることを確認する
:::
```typescript
OPENAI_API_KEY="sk-..."
```
## OpenAIAPIを直接叩く部分（中間API)の作成
`app/api/openai/route.ts`を作成し、サーバーサイドで動く物を実装します。
※エラーハンドリングは割愛してあります

### 各ディレクトリ、ファイル名について
| 名前 | 説明 |
|:-:|:-:|
| app  | appディレクトリ  |
|  api | 慣習、apiに関連するものをまとめる  |
|openai|自由、APIのサービス名にしておくのが無難|
|route.ts(.js)|必須、他の名前だとエラー|
```typescript:route.ts
import {NextResponse,NextRequest} from 'next/server';
import {OpenAI} from 'openai';
import type {openaiTypes} from '@/types'; //{model:string; system:string; prompt:string;}

const openai = new OpenAI({ apiKey:process.env.OPENAI_API_KEY});//.envファイルからAPIキーを取得する

export async function POST(req:NextRequest){
    const {model,system,prompt}=(await req.json()) as openaiTypes;//クライアントサイドからのリクエストを取得   
    
    const response=await openai.chat.completions.create({
        model:model, //モデル
        messages:[
            {
                role:'system', //システムプロンプト
                content:system,
            },
            {
                role:'user', //プロンプト
                content:prompt,
            },
        ],   
    });
    
    return NextResponse.json(response);//クライアントサイドにに出力を返す
}
```
## クライアント側の実装
今作成した中間APIを呼び出すコードを実装します。`fetch`を使い、エンドポイントは`api/openai`とします。エンドポイントについては`api/route.tsが入ったディレクトリ名`となっています。

今回は複数のモデル、複数のシステムプロンプトを使いたかったので、`CallOpenai(model,system,prompt)`のように呼び出せる関数を作成しました。

ファイルは`app/utils/callOpenai.tsx`に作成してあります。これについては慣習から逸れている可能性があります。

**コメントにて教えていただけるとありがたいです！**


```typescript:callOpenai.tsx
export default async function CallOpenai(model: string,system:string,prompt: string) {
    const response = await fetch('api/openai', {　//エンドポイントに注意
        //この辺はお決まりのやつ
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({
            model: model,//モデル
            system:system,//システムプロンプト
            prompt: prompt,//プロンプト         
        }),
    });
    const data = await response.json();レスポンスをjson形式で受け取る
    return data.choices[0].message;//ここに最新の出力が格納されている(公式doc参照)
}
```
## 呼び出してみる
↓こんな感じで呼び出せます！
```typescript
import CallOpenai from '@/features/callOpenai';

async function test(){
    const model = "gpt-3.5-turbo";
    const system = "Hello";
    const prompt = "How are you?";
    const response = await CallOpenai(model,system,prompt);
    console.log(response);//=>Hello How are you?
}
```

## まとめ
OpenAIAPIの使い方について、半年ほど前の記事と比較しても変わっていたため、公式ドキュメントの大切さを痛感しました

様々なご指摘コメントにてお待ちしております！！！

## 参考
https://platform.openai.com/docs/overview?lang=node.js

https://qiita.com/asagohan2301/items/6ddd90845a710a5478f2

https://zenn.dev/umi_mori/books/next-js-typescript/viewer/creating-echo-api

https://qiita.com/siwa/items/df9c7579c3deb5f62704