Alternative Architecture DOJO

オルターブースのクラウドネイティブ特化型ブログです。

Azure OpenAI × Next.js を用いてオリジナルチャットボット作ってみた!

こんにちは、高田です!
以前、業務で利用する機会があったり、AI-900資格の勉強中にAzure OpenAIを触れる機会があり、面白いなと興味を持ちました。
そこで今回は、シンプルなチャットボットを作成し、Azure Static Web Appsへデプロイしてみました。

ちにみに自分はミケちゃんChatというアプリ名にして、猫型のAIアシスタントと会話できるようにしました。
お魚の話題になると嬉しそうにするようにしているのですが……うん、可愛いですね!😸

必要なもの

  • Node.js(私が用いたバージョンはv20.19.0になります)
  • Azureサブスクリプション

① Azure Open AI リソースを作成し、生成AIモデルをデプロイする

まずは、Azureポータルにアクセスし、画面上部の「リソースの作成」から「Azure OpenAI」を検索します。
必要な情報を入力してリソースを作成しましょう。

≪キーとエンドポイントの取得≫

リソース作成後、左側メニューの「リソース管理」から「キーとエンドポイント」を選択します。
表示されるキーとエンドポイントの情報は、後ほどアプリケーションからAzure OpenAIに接続する際に必要となるため、安全な場所に保存しておきましょう。

≪生成AIモデルをデプロイする≫

作成したリソースに移動します。画面上部の「概要」タブから「Go to Azure AI Foundry portal」(または「Explore Azure AI Foundry portal」)をクリックして、Azure AI Foundry portalへ遷移します。

次に、チャットプレイグラウンドから「新しいデプロイの作成」を選択します。

基本モデルから」を選択すると、利用可能なAIモデルの一覧が表示されます。私の場合は、その中から「gpt-4o」モデルを選択しました。

設定を確認し「デプロイ」ボタンをクリックすると、モデルのデプロイが完了します。これでAzure OpenAIのセットアップは完了です!

デプロイしたモデルの動作確認として、画面右側のチャット入力欄に「日本の首都は?」などと質問してみましょう。適切な回答が返ってくれば、正常に機能していることが確認できます

≪サンプルコードを見てみる≫

チャットプレイグラウンドの「コードの表示」機能を使うと、アプリケーションからAzure OpenAIに接続するためのサンプルコードを確認できます。

今回はNext.jsを使用するため、JavaScriptのサンプルコードを参考に実装していきます。
なお、JavaScript以外にも、JSON、Python、C#、curl、Java、Goなど、多様な言語のサンプルコードも用意されていますので、お好みの開発言語に合わせて参照することができます。

② Next.js アプリケーションの作成

ターミナルにて、任意のディレクトリで以下のコマンドを実行しましょう。

npx create-next-app 任意のアプリ名

また今回はTypeScript 用の Azure OpenAI ライブラリを使用するので以下のコマンドからインストールします。

npm install openai @azure/openai

≪page.tsxの編集≫

インストールができましたら、src/app ディレクトリ内のpage.tsxを以下のように書き換えて、シンプルなチャット画面を作成します。

page.tsx(クリックすると展開されます)

'use client';
import { useState } from 'react';

interface Message {
  role: 'user' | 'assistant';
  content: string;
}

export default function Home() {
  // メッセージ履歴、入力値、ローディング状態を管理するステート
  const [messages, setMessages] = useState<Message[]>([]);
  const [input, setInput] = useState('');
  const [isLoading, setIsLoading] = useState(false);

  // メッセージ送信時の処理
  const handleSubmit = async () => {
    // 空文字列の場合は処理を中断
    if (!input.trim()) return;

    // ローディング状態を開始
    setIsLoading(true);
    // 新しいメッセージ配列を作成(既存のメッセージ + ユーザーの新規メッセージ)
    const newMessages: Message[] = [...messages, { role: 'user', content: input }];
    setMessages(newMessages);
    // 入力フィールドをクリア
    setInput('');

    try {
      // APIにメッセージを送信
      const response = await fetch('/api', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ messages: newMessages }),
      });

      // レスポンスを受け取り、メッセージ履歴に追加
      const data = await response.json();
      setMessages([
        ...newMessages,
        { role: 'assistant', content: data.choices[0].message.content },
      ]);
    } catch (error) {
      console.error('Error:', error);
    } finally {
      // 処理完了後、ローディング状態を解除
      setIsLoading(false);
    }
  };

  return (
    // チャットUIのメインコンテナ
    <div className="max-w-4xl mx-auto p-6">
      {/* タイトル */}
      <h1 className="text-6xl font-bold mb-4 text-center">ミケちゃんChat</h1>
      
      {/* 入力エリア */}
      <div className="space-y-4">
        {/* メッセージ入力フィールド */}
        <textarea
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="ここにメッセージを入力してね♪"
          rows={5}
          className="w-full p-3 border rounded-lg focus:ring-2 focus:ring-blue-500"
        />
        {/* 送信ボタン - ローディング中は無効化 */}
        <button
          onClick={handleSubmit}
          disabled={isLoading}
          className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 disabled:bg-gray-400"
        >
          {isLoading ? '回答生成中...' : '送信'}
        </button>
      </div>

      {/* チャット履歴表示エリア */}
      <div className="mt-6">
        <h2 className="text-xl font-semibold mb-3">チャット履歴</h2>
        {/* スクロール可能なメッセージリストコンテナ */}
        <div
          className="border rounded-lg p-4 max-h-[600px] overflow-y-auto"
        >
          {/* メッセージの繰り返し表示 */}
          {messages.map((message, index) => (
            <div
              key={index}
              className={`mb-4 p-3 rounded-lg ${
                message.role === 'user' ? 'bg-blue-100' : 'bg-gray-100'
              }`}>
              {/* メッセージの送信者表示 */}
              <div className="font-bold mb-1">
                {message.role === 'user' ? 'あなた' : 'ミケちゃん'}:
              </div>
              {/* メッセージの内容表示 - 改行を維持 */}
              <div className="whitespace-pre-wrap text-base leading-relaxed">
                {message.content}
              </div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

≪route.tsの作成≫

次に、API エンドポイントを作成します。
src/app ディレクトリ内に api ディレクトリを新規作成し、その中に route.ts ファイルを作成しましょう。
このファイルには以下のコードを記述します。

route.ts(クリックすると展開されます)

import { NextResponse } from 'next/server';
import { AzureOpenAI } from "openai";

// POSTリクエストを処理するAPI関数
export async function POST(req: Request) {
  try {
    // クライアントから送信されたメッセージを取得
    const { messages } = await req.json();

    // AIの性格を定義するシステムメッセージ
    const systemMessage = {
        role: 'system',
        content: 'あなたは猫のAIです。名前は「ミケ」です。語尾に「にゃ」をつけて話してください。親しみやすく、かわいらしい口調で話してください。お魚の話題になると特に嬉しそうにします。あなたは猫のように振る舞い、質問には猫らしい視点で答えます。'
    };
    
    // システムメッセージとユーザーメッセージを結合
    const completeMessages = [systemMessage, ...messages];
    
    // これらの環境変数を設定するか、次の値を編集する必要があります
    const endpoint = process.env.AZURE_OPENAI_ENDPOINT;  
    const apiKey = process.env.AZURE_OPENAI_API_KEY; 
    const apiVersion = "2024-05-01-preview";  
    const deployment = "gpt-4o"; // これはデプロイ名と一致している必要があります
    
    // Azure OpenAIクライアントを初期化
    const client = new AzureOpenAI({ endpoint, apiKey,  apiVersion, deployment });  

    // Azure OpenAI APIを呼び出して応答を取得
    const result = await client.chat.completions.create({  
        model: deployment,  
        messages: completeMessages,  
        max_tokens: 800,  
        temperature: 0.7,  
        top_p: 0.95,  
        frequency_penalty: 0,  
        presence_penalty: 0,  
        stop: null  
      });  
      
    // 応答をJSON形式で返す
      return NextResponse.json({
        choices: [{
            message: {
                content: result.choices[0].message.content
            }
        }]
     });
  } catch {
    // エラー発生時のエラーレスポンス
    return NextResponse.json({ error: 'Error processing request' }, { status: 500 });
  }
}

≪.envの作成≫

プロジェクトのルートディレクトリに .env ファイルを作成し、先ほどキーとエンドポイントの取得ステップで保存しておいた認証情報を以下の2つの環境変数として設定します。
これらの環境変数は、アプリケーションがAzure OpenAI APIに接続するために使用されます。

AZURE_OPENAI_API_KEY=取得したAzure OpenAIのAPIキー   
AZURE_OPENAI_ENDPOINT=取得したAzure OpenAIのエンドポイントURL

≪ローカル環境で動かしてみる≫

必要なファイルが作成できたら、ターミナルで以下のコマンドを実行し、開発サーバーを起動してアプリケーションを立ち上げましょう!

npm run dev

メッセージ入力欄に「自己紹介して!」と入力して送信ボタンをクリックすると、「ミケちゃん」が猫らしい口調で応答してくれるはずです。また、「お魚について教えて」と質問すると、特に嬉しそうな反応が返ってくるでしょう。

これで、Azure OpenAI を使ったオリジナルチャットボットの基本機能が完成しました。必要に応じてUIをカスタマイズしたり、システムメッセージを変更することで、AIの個性を変えることも可能です!

③ Azure Static Web Appsにデプロイする

せっかく作成したアプリケーションですので、Azure Static Web Appsにデプロイしましょう。
継続的デプロイ(CI/CD)を構築するために、 Next.js アプリケーションを GitHub リポジトリにプッシュしておいてください。

≪Azure Static Web Apps へのデプロイ≫

Azureポータルにアクセスし、画面上部の「リソースの作成」から「App Service」を検索します。 「+作成」ボタンより「静的 Webアプリ」を選択し、必要な情報を入力していきます。

デプロイ設定では、「デプロイの詳細」セクションで以下の設定を行います:

  • ソース:「GitHub」を選択
  • GitHub アカウントを認証し、作成した Next.js アプリケーションのリポジトリとブランチを指定

必要な情報をすべて入力したらリソースを作成しましょう。

≪GitHub Actionsの確認≫

リソースを作成すると、GitHubリポジトリに自動的にGitHub Actionsワークフローが追加されます。このワークフローファイルは、コードの変更を検知して自動的にビルドとデプロイを行う重要な役割を果たします。
以下の画像のように緑色チェックマークがつきますと成功の証になります!

≪環境変数の設定≫

作成したAzure Static Web Appsにも、アプリケーションが動作するために必要な環境変数を設定する必要があります。
左側のメニューの「設定」から「環境変数」より、.envファイルの内容を登録しましょう。

🎉完成🎉

以上の手順をすべて完了すると、Azure Static Web Apps の「概要」のURLをクリックすると、ブラウザで公開されたアプリケーションにアクセスできます!!

まとめ

Azure OpenAIを活用したオリジナルチャットボットの開発とデプロイが完了しました。
GitHubリポジトリにコードをプッシュするだけで自動的にデプロイされる環境が整ったので、今後も機能追加やUIの改善を行いながら、より高度なチャットボットに発展させていくことができますね😸✨

参考資料

クイック スタート - Azure OpenAI Service で GPT-35-Turbo と GPT-4 の使用を開始する - Azure OpenAI Service | Microsoft Learn

App Router: Getting Started | Next.js

@azure/openai - npm


サービス一覧 www.alterbooth.com cloudpointer.tech www.alterbooth.com