[初心者向け] Next.js で初めての OpenAI API アプリを作ってみた(Tailwind CSS、Docker 利用)

OpenAI API と React(Next.js) を利用した簡単な入門アプリの作り方を、サンプルコード付きでご紹介しています。
2023.03.20

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、アノテーション テクニカルサポートの Shimizu です。

昨今の OpenAI ブームに乗り、私も何かを始めてみたくなりました。
そこで、ちょうど勉強中だったフロントエンド開発環境(Next.js + Tailwind CSS + Docker)と OpenAI API を利用して、AI が質問に答えてくれるだけのシンプルな入門アプリを、実作業 30 分ほどで作ってみました。

完成品イメージ

ここでは筆者の備忘も兼ねて、一連の手順を記事にしてみました。
以下のような方の参考になれば幸いです。

  • OpenAI API で最小限の動くアプリを作り、仕組みを理解したい
  • Next.js(React)や Tailwind CSS などフロントエンドの流行りのフレームワークに触れてみたい

それでは、いってみましょう!

step1. 事前準備

まず、事前に下記を用意しましょう。

(1) OpenAI のアカウント登録、API シークレットキーの取得
筆者は下記のサイトを参考にさせていただきました。

OpenAI | APIキーを取得する方法 | ONE NOTES

※ なお API キーの取得は無料ですが、API の使用は有料です。
アカウント登録時に $18 分のクレジットが付与されるため、その範囲内なら無料で使用できますが、それを超えると課金対象となることに注意しましょう。

(2) PC 上で Docker コマンドを実行できる環境
筆者の場合は Windows の WSL2(Ubuntu)上で Docker を動かしています。 詳しい手順は下記の記事を参考にしてください。

Rancher Desktopをインストールしてみた | DevelopersIO

上記(1)(2)の用意ができたら、次のステップに進みましょう。

step2. Docker コンテナを起動し Next.js のプロジェクトを作成する

まずは作業PCのターミナルから下記のコマンドを実行し、Alpine Linux のコンテナを起動します。

# コンテナを起動
docker run --name nextjs-test -p 3000:3000 -itd alpine /bin/sh
# 起動したコンテナのIDを確認し、コンテナ内にログインする
docker ps -a
docker exec -it コンテナ ID /bin/sh

ここから先はコンテナ内で node.js をインストールし、Next.js のプロジェクトを作成していきます。

※そのままターミナルで操作してもよいですが、すでに VSCode を導入済みの場合は下記リンクを参考に、VSCodeからコンテナに接続してターミナルで操作すると便利です。

既存のDocker開発環境をVS CodeのRemote Developmentで開発できるようにしてみた | DevelopersIO

いずれかの方法でコンテナ内にログインし、下記を実行します。

# 作業ディレクトリ /app を作成して移動する
mkdir /app
cd /app
# node.js と npm をインストールし、バージョン確認する
#(本記事の執筆時点では nodejs v18.14.2 / npm 9.1.2)
apk add --no-cache nodejs
apk add --update nodejs npm
node -v
npm -v
# Next.js プロジェクトの作成
npx create-next-app

Next.js プロジェクトの作成時にオプションの入力を促されますが、筆者は下記のように設定しました。
特に変更の必要がなければ、同じ設定にして進みましょう。

✔ What is your project named? … my-nextjs-pj01
✔ Would you like to use TypeScript with this project? … No
✔ Would you like to use ESLint with this project? … No
✔ Would you like to use `src/` directory with this project? … Yes
✔ Would you like to use experimental `app/` directory with this project? … No
? What import alias would you like configured? › 入力せずに進む

Next.js プロジェクトの作成が完了したら、下記を実施します。

# 作成したプロジェクトのディレクトリに移動
cd my-nextjs-pj01
# js から API 通信を行うためのパッケージをインストール
npm install axios
# 開発サーバーの起動
npm run dev

しばらく待って開発サーバーが起動したら、ブラウザで http://localhost:3000 にアクセスしてみましょう。 下記のような Next.js プロジェクトの初期ページが表示されるはずです。

それでは次のステップでは Next.js プロジェクトの初期ページを編集して、実際に OpenAI に接続するアプリを作成していきます。

step3. OpenAI アプリの作成と動作確認(サンプルコードあり)

Next.js の初期ページはプロジェクトフォルダ内の「src > pages > index.js」になります。
下記のサンプルコード1を丸ごとコピーして index.js に貼り付け、12行目の const API_KEY = の部分のみ実際に取得した OpenAPI のシークレットキーに置き換えましょう。

※注意:OpenAI の API シークレットキーは、外部に公開しないようにしましょう!
今回はローカル環境でのテストのみのためコードに直接キーを記述していますが、作成したアプリを外部公開したり Git にコードを push する場合は必ず API キーを削除して環境変数へ格納するなどの実装をしましょう。

サンプルコード1: index.js の内容

import { useState } from "react";
import Head from 'next/head'
import axios from "axios";

export default function Form() {
  const [prompt, setPrompt] = useState("");
  const [model, setModel] = useState("text-davinci-002");
  const [response, setResponse] = useState("");

  const handleSubmit = async (e) => {
    e.preventDefault();
    const API_KEY = '前準備で取得した OpenAI の APIキー';
    const URL = "https://api.openai.com/v1/engines/" + model + "/completions";
    try {
      const response = await axios.post(
        URL,
        {
          prompt: prompt,
          max_tokens: 200
        },
        {
          headers: {
            "Content-Type": "application/json",
            Authorization: `Bearer ${API_KEY}`,
          },
        }
      );
      setResponse(response.data.choices[0].text);
    } catch (error) {
      console.log(error);
    }
  };

  return (
    <div>
      <Head>
        <title>Next.js で作る初めての OpenAI アプリ</title>
        <meta name="description" content="AI がどんな質問にも答えます" />
      </Head>
      <h1>Next.js で作る初めての OpenAI アプリ</h1>
      <h2>質問:</h2>
      <form onSubmit={handleSubmit}>
        <p>Prompt:</p>
          <textarea
            placeholder="質問してください"
            value={prompt}
            onChange={(e) => setPrompt(e.target.value)}
          />
        <p>Model:</p>
          <select value={model} onChange={(e) => setModel(e.target.value)}>
            <option value="text-davinci-002">Davinci</option>
            <option value="text-davinci-001">Davinci-codex</option>
            <option value="text-curie-001">Curie</option>
            <option value="text-babbage-001">Babbage</option>
            <option value="text-ada-001">Ada</option>
          </select>
        <br />
        <button type="submit">質問する</button>
      </form>
      <h2>答え:</h2>      
      <p>{response}</p>
    </div>
  );
}

index.js に上記の内容を貼り付けて保存すると自動でブラウザのリロードがかかり、表示された Next.js の初期ページが切り替わるはずです。
(自動で切り替わらない場合は、F5 キー等で手動リロードを試行しましょう)

実はこれだけで、OpenAI に接続する準備は完了です。
「Prompt」の欄に質問文を入れて「質問する」ボタンをクリックし、「答え:」の欄に AI からの回答が返ってくることを確認しましょう。
(うまく動作しない場合は、しばらく待って再試行するか、API キーの間違い等を確認してみてください)

なお「Model」は、AI の人格のようなものです。
同じ質問でも Model を変更して再度質問することで、回答が変わることを確認してみましょう。

これで、OpenAI API を利用した最小限のアプリが作れました!

なおここでは詳しく触れませんが、OpenAI API にはモデル選択の他にも、AI 回答の精度や方向性を定義するさまざまなパラメータが存在します。 コードを少し追加すれば、これらのパラメータを細かく調整して AI からの回答を得ることもできます。

もし興味があれば、下記の記事でどのようなパラメータがあるか確認してみてください。

OpenAI Playground を試してみた | DevelopersIO

それでは次のステップでは、Next.js にCSSフレームワーク「Tailwind CSS」を導入して、よりアプリらしい見た目に整えてみます。

step4. Tailwind CSS を利用して見栄えをよくする(サンプルコードあり)

まずターミナルで ctrl + c キーを押して開発サーバーを停止し、Next.js に Tailwind CSS のプラグインを導入します。 下記の公式ドキュメントの通りに進めます。

Install Tailwind CSS with Next.js - Tailwind CSS

# ターミナルで下記のコマンドを実行
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

上記コマンドを実行すると、Next.js プロジェクトフォルダの直下に tailwind.config.js ファイルが生成されるので、下記の内容で上書きします。

tailwind.config.js の内容:

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./app/**/*.{js,ts,jsx,tsx}",
    "./pages/**/*.{js,ts,jsx,tsx}",
    "./components/**/*.{js,ts,jsx,tsx}",
 
    // Or if using `src` directory:
    "./src/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

併せて、globals.css ファイル(デフォルト設定なら src > styles フォルダの配下にあります)の先頭に、下記の 3 行を追記します。

globals.css の先頭に追記:

@tailwind base;
@tailwind components;
@tailwind utilities;

以上で Tailwind CSS を使用するための準備は完了です。
それでは先ほど作成したアプリに Tailwind CSS を適用して、見栄えを良くしてみます。

下記のサイトに Tailwind CSS でスタイリングされたフォームのUIが用意されているので、そこから必要なパーツを持ってきます。

Form Layouts - Official Tailwind CSS UI Components

下記の画面のように「Code : React」を選択すると、React 用のコードが取得できます。

Tailwind CSS の特徴であるクラス名の記述(className="" の部分)が呪文のように長いため、最初はどこをどのように変えればよいか分かりにくいと思います。

今回は筆者が見栄えを整えたサンプルコードを記載しますので、そのままコピーして先ほどの index.js に貼り付けてみてください。 (先ほどと同様、const API_KEY = の部分は実際の API キーに置き換えます)

サンプルコード2: index.js の内容(Tailwind CSS 適用)

import { useState } from "react";
import Head from 'next/head'
import axios from "axios";

export default function Form() {
  const [prompt, setPrompt] = useState("");
  const [model, setModel] = useState("text-davinci-002");
  const [response, setResponse] = useState("");

  const handleSubmit = async (e) => {
    e.preventDefault();
    const API_KEY = '前準備で取得した OpenAI の APIキー';
    const URL = "https://api.openai.com/v1/engines/" + model + "/completions";
    try {
      const response = await axios.post(
        URL,
        {
          prompt: prompt,
          max_tokens: 200     
        },
        {
          headers: {
            "Content-Type": "application/json",
            Authorization: `Bearer ${API_KEY}`,
          },
        }
      );
      setResponse(response.data.choices[0].text);
    } catch (error) {
      console.log(error);
    }
  };

  return (
    <div>
    
      <Head>
        <title>Next.js で作る初めての OpenAI アプリ</title>
        <meta name="description" content="AI がどんな質問にも答えます" />
      </Head>

      <h1 className="text-3xl font-bold leading-3 text-gray-900 m-5">Next.js で作る初めての OpenAI アプリ</h1>

      <div className="md:grid md:grid-cols-3 md:gap-6">
        <div className="mt-5 p-4 md:col-span-2 md:mt-0">
          <form onSubmit={handleSubmit}>
            <div className="shadow sm:overflow-hidden sm:rounded-md">
              <div className="space-y-6 bg-white px-4 py-5 sm:p-6">
                <div>
                  <label htmlFor="Prompt" className="block text-sm font-medium leading-6 text-gray-900">
                    質問文
                  </label>
                  <div className="mt-2">
                    <textarea
                      rows={3}
                      className="mt-1 px-2 block w-full rounded-md border-0 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:py-1.5 sm:text-sm sm:leading-6"
                      placeholder="ここに質問を入れてください"
                      value={prompt}
                      onChange={(e) => setPrompt(e.target.value)}
                    />
                  </div>
                </div>

                <div className="col-span-6 sm:col-span-3">
                    <label htmlFor="model" className="block text-sm font-medium leading-6 text-gray-900">
                      モデルを選ぶ
                    </label>
                    <select
                      className="mt-2 block w-full rounded-md border-0 bg-white py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                      value={model} 
                      onChange={(e) => setModel(e.target.value)}
                    >
                      <option value="text-davinci-002">Davinci</option>
                      <option value="text-davinci-001">Davinci-codex</option>
                      <option value="text-curie-001">Curie</option>
                      <option value="text-babbage-001">Babbage</option>
                      <option value="text-ada-001">Ada</option>
                    </select>
                </div>

              </div>

              <div className="bg-gray-50 px-4 py-3 sm:px-6">
                <button
                  type="submit"
                  className="inline-flex justify-center rounded-md bg-indigo-600 py-2 px-3 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-500"
                >
                  質問する
                </button>
              </div>

            </div>
          </form>
        </div>

        <div className="mt-4 mr-4 shadow sm:overflow-hidden sm:rounded-md">
          <div className="bg-white px-4 py-5 sm:p-6">
              <h2 className="text-base font-semibold leading-6 text-gray-900">質問の答え</h2>
              <p className="mt-1 text-sm text-gray-600">{response}</p>
          </div>
        </div>

      </div>  

    </div>
  );
}

index.js を上記の内容に置き換えたら、再度ターミナルから npm run dev を実行して開発サーバーを起動し、ブラウザで http://localhost:3000 にアクセスしてみましょう。
すると、先ほどと同じアプリですがより見栄えが洗練され、アプリらしくなったことが確認できます。

これで、Next.js を使った最初の OpenAI アプリが完成しました。
お疲れさまでした!

さいごに

いかがでしたでしょうか。
今回は最短で OpenAI API の仕組みを理解するためのシンプルなアプリですが、アイデア次第で色々と面白いものが作れそうです。 筆者も今後さらに高度で実用的なアプリ作りにチャレンジし、また機会があれば記事にしたいと思います。

この記事が皆様の OpenAI アプリ開発を始める一助となれば幸いです!