Next.jsでChatGPT APIを叩いてみる

# 記事内容
Next.js（AppRouter）を使ったアプリでChatGPTに質問して回答データを得るまでの方法の備忘録

## chatGPTの料金の計算方法

chatGPTにはいくつかモデルがあり、どのモデルを使うかで料金が変わってくる。料金プランは公式に載っている。
[OpenAIの料金プラン](https://openai.com/pricing)

例えば、gpt-4を使う場合はInputが`$0.03 / 1K tokens`、Outputが`$0.06 / 1K tokens`と記載がある。
tokensというのは以下のサイトに文字を入力すれば計算することができる。
[OpenAIトークナイザー](https://platform.openai.com/tokenizer)

同じ文字数でもローマ字か日本語かなどによってトークン数というのは変わってくる。
例えば、「あなたは誰ですか？」のトークン数は9、
回答の
「私はChatGPTと呼ばれる人工知能です。質問に答えたり、情報を提供したりするのが得意です。どのようにお手伝いできますか？」
のトークン数は57だったので、Inputが9、Outputが57となります。

それを計算式に入れると
- Inputが `0.03/1000×9=$0.00027`
- Outputが `0.06/1000×57=$0.00342`
- 合計 `$0.00369=¥0.56（$1=¥150計算）`

## APIを叩いてみる

API keyはOpenAI公式のGet startedからログインして、左メニューのAPI keysでCreate new secret keyをクリックして作成することができる。

[openai-node GitHub](https://github.com/openai/openai-node)
こちらのopenaiのnode.js用のライブラリを使う。

OpenAI パッケージのインストール
`npm install openai --save`

```ts
import OpenAI from "openai";

// OpenAI API クライアントを初期化
const openai = new OpenAI({
  apiKey: process.env.NEXT_PUBLIC_OPENAI_API_KEY,
});

const getChatgptText = async () => {
  try {
    const prompt = `chatGPTに聞きたい内容`;

    const response = await openai.chat.completions.create({
      messages: [{ role: 'user', content: `${prompt}` }],
      model: 'gpt-3.5-turbo',
      max_tokens: 200,
    });

    console.log("OpenAI Text Generation Response:", response);

    // response.data.choices[0].text に生成されたテキストが含まれます
  } catch (error) {
    console.error("OpenAI Text Generation Error:", error);
  }
};

```

上記、テストとして適当なtsxファイルでデータが取れるか試してみるとデータが取れた。
クライアントで環境変数使ってたのでセキュリティリスクのエラーが以下のように出た。

```
Error: It looks like you're running in a browser-like environment.

This is disabled by default, as it risks exposing your secret API credentials to attackers.
If you understand the risks and have appropriate mitigations in place,
you can set the `dangerouslyAllowBrowser` option to `true`, e.g.,

new OpenAI({ apiKey, dangerouslyAllowBrowser: true });

https://help.openai.com/en/articles/5112595-best-practices-for-api-key-safety
```


# 実装内容
Next.jsのAppRouterのディレクトリ構成に沿って実装していく。


Servicesディレクトリに先ほど書いたchatGPTのデータ取得処理を書いていく。

```src/services/openai.ts
import OpenAI from 'openai';

const openai = new OpenAI({
  apiKey: process.env.NEXT_PUBLIC_OPENAI_API_KEY,
  dangerouslyAllowBrowser: false, // ***注意*** クライアントサイドの実行を許可
});
export async function getChatgptAnswer(prompt: string) {
  const response = await openai.chat.completions.create({
    messages: [{ role: 'user', content: prompt }],
    model: 'gpt-3.5-turbo',
    max_tokens: 200,
  });
  const answer = response.choices[0].message?.content;
  return answer;
}

```

Servicesディレクトリは、サーバーサイドのロジックやビジネスロジックを管理するために使用される。
Next.jsのコンテキストでは、このディレクトリはAPIルートやバックエンド処理で利用される機能（例えば、外部APIへのリクエスト、データ加工、ビジネスルールの適用など）を集約する役割を担う。

サーバーサイドで環境変数を使えるようにするため`next.config.js`を修正する。
```js
/** @type {import('next').NextConfig} */
const nextConfig = {
  publicRuntimeConfig: {
    // クライアントサイドで利用する環境変数を設定する
  },
  serverRuntimeConfig: {
    // サーバーサイドで利用する環境変数を設定する
    NEXT_PUBLIC_OPENAI_API_KEY: process.env.NEXT_PUBLIC_OPENAI_API_KEY,
  },
};
module.exports = nextConfig;

```


次にapiディレクトリにエンドポイントを定義する。
```src/app/api/openai/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { getChatgptAnswer } from '@/services/openai';

export async function GET(req: NextRequest) {
  const prompt = req.nextUrl.searchParams.get('prompt');
  try {
    if (!prompt) {
      return;
    }
    const data = await getChatgptAnswer(prompt);

    return NextResponse.json({ data });
  } catch (error) {
    console.log('error', error);
    return NextResponse.json(
      { error: 'Failed to fetch data' },
      { status: 500 },
    );
  }
}

```

APIディレクトリは、クライアントからのHTTPリクエストを受け取るエンドポイントの役割を果たします。


クライアントからapiを叩いて、テスト時と同じようにchatGPTから回答を受け取ることができました。
```page.tsx
  const getChatgptText = async () => {
    await errorHandling(async () => {
      const response = await axios.get('/api/openai/', { params: { prompt } });
      setChatGptAnswer(response.data.data);
    });
  };
```