まだOpenAI使ったことないの？この記事で全員ハンズオンさせてやんよ！

# 目次

1. はじめに
2. 今回作成するシステムの概要
3. Azure OpenAI セットアップ
4. Azure DevOps の Azure Repos をセットアップ
5. Next.js でフロントエンド構築
6. Azure Static Web Apps へ Pipelines を用いて Deploy
7. 動作確認
8. お片付け

## はじめに
![azureopenai_logo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326114/2bd5f8ff-49fa-a9a3-8910-677da87c2f5b.png)

昨今ちまたで話題の OpenAI。chatGPT はさらっと触ったけど、API までは触ってないなぁ…という方向けのハンズオン 🖐️ となります。
この記事の目標としては、OpenAI を触ってみたい全てのアゲアゲエンジニアがハンズオン出来ることです。
セットアップで詰まるところはどんどんコメント欄に質問していただいたら、がんがん返していきますので、ご遠慮なく質問してください！

### では、Let's ハンズオン！

## 今回作成するシステムの概要

今回作成するシステムは Azure 上で作成します。
下の図をご覧ください。
![zentai.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326114/830ed38c-3653-358a-c4cc-ece986e1c36e.png)

以下の様な技術を使います。

- Azure OpenAI
- Azure Static Web Apps
- Azure DevOps
- Azure Repos
- Azure Pipelines
- Next.js(TypeScript)

盛りだくさんのハンズオンですが、頑張っていきましょう！

## Azure OpenAI セットアップ

まずは Azure OpenAI のセットアップです。全体図で言うとここの部分です。
![openai_set.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326114/f863d6a6-4795-b473-4f27-b495fffc52a6.png)


まずは、Azure 上で動作させるので、何がともあれ Azure の無料アカウントを作成してみましょう！
[Azure の無料アカウントを使ってクラウドで構築](https://azure.microsoft.com/ja-jp/free)

Microsoft アカウントを作成して Azure へログインすれば OK です。

こんな感じの画面が出てきたでしょうか。
![azure_main.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326114/794795fa-f87e-b5de-853d-e6db7335d5c8.png)

何がともあれ、リソースグループを作成しましょう！
上の検索窓に`リソースグループ`と入力して検索してください。
![resouce_gr.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326114/6310a1d8-81b3-d89c-d82a-e1aa809613ab.png)

リソースグループの画面に進んだら、左上らへんにある作成をクリックして、リソースグループを作成しましょう！
次へを進んで、タグは別になくて大丈夫です。
![make_rg.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326114/6dd75935-d904-e2eb-8e90-ab65cb394aab.png)

作成できましたか？

![rg_fin.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326114/e87e784b-abf1-f46f-fd2d-f71c86239818.png)

ここまで作成できたら、また検索窓に`OpenAI`と入力して Azure OpenAI の画面を開いてください。

また、左上らへんにある作成ボタンをクリックです。
リソースグループは先ほど作成したものを指定し、あとは下の画像のように設定してください。
![make_ai.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326114/996d2a42-e0dd-e7a4-a7b4-da401efb6112.png)

ネットワークは`インターネットを含むすべてのネットワークがこのリソースにアクセスできます。`を選択してください。
タグは特に何もつけなくて OK です。

作成ボタンをクリックしてください。デプロイが進行するはずです。(5 分ぐらいでしょうか。)
デプロイが完了すれば、リソースへ移動です。

次はモデルを Deploy する必要があります。

画面左上らへんにある`Azure OpenAI Studioへ移動`をクリックしてください。

![azureopenaistudio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326114/478e099a-e8c1-ed06-8dfa-36004b3699cd.png)

新しいデプロイの作成をクリック → 新しいデプロイの作成をクリックしてください。

以下の様な画面が出てくると思うので、いい感じに入力して作成をクリック

![modeldeploy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326114/57316e01-5d5e-3366-24a0-370b1c27fc0d.png)

このデプロイはすぐ完了します。
下の画面のようになりましたか？

![deploysuccess.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326114/8a8e4557-acf1-150b-ea2f-3c911308f980.png)

ここまで来れば、AzureOpenAI のセットアップは完了です！
次に進みましょう！

:::message
初回はこのような表示が出ている可能性があります。

![aoai_error.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326114/23308dbd-89c1-7f81-c9b9-154a06246170.png)


その場合は表示されているリンクをクリックすると、Microsoft へリクエストを送ることができます。

![request_access.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326114/39970974-6475-a71a-3285-3aa24f296278.png)


ゆうじろうは数時間ぐらいで許可されたので、気長に待ちましょう。

:::

## Azure DevOps の Azure Repos をセットアップ

次は Azure DevOps に Repos を作成してコードを管理する場所を作ります。
全体図で言うとここです。

![devops_next_set.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326114/5e9a4921-40e7-ed68-624b-4a057c37a0e9.png)

まずは Azure DevOps へアクセス
ゆうじろうは DevOps の画面見ただけでワクワクします

[AzureDevOps](https://azure.microsoft.com/ja-jp/products/devops/?nav=min)

![devops.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326114/1feddcfc-23a4-752f-3357-2a7db3e896e6.png)

次にプロジェクトの作成です。

サインインして`+ New Project` からプロジェクトを作成してください。

![devops_create.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326114/25ed57b3-ae64-0555-fc49-500d5fc3a79d.png)


無事、project が create されたら、Welcome to the Project と暖かく迎えてくれます。

![create_repo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326114/2343cd38-6ff8-7680-3e12-be3c68925810.png)


(ロゴかわいいですよね)

まずはコードを管理するリポジトリを作成します。
画面左側の Repos をクリックしてください。

以下のリポジトリの url が表示されるので、コピーしてローカル環境へ git clone です。

![create_repo_gitclone.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326114/e6d5c8d6-1728-e5a1-4db3-7aacd98ec485.png)


```bash
git clone [リポジトリのurl]
```

これでローカル環境へリポジトリをクローンできれば Azure Repos の環境構築は完了です。

## Next.js でフロントエンド構築

ここからはローカル環境で Next.js の環境構築をします。

![next_set.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326114/6b4319bd-6ed5-2f80-fd14-11e89f274b11.png)


まずは、ローカルへ clone したディレクトリへ移動してください。

```bash
cd yujiro-handson-devops
```

中に入れたら容赦なく Next.js のプロジェクトを create です。

```bash
create-next-app . --ts
```

色々聞かれるので、とりあえず設定

```bash
 ➜  tech-blog git:(main) ✗ create-next-app . --ts
✔ Would you like to use ESLint with this project? … No / Yes
✔ Would you like to use Tailwind CSS with this project? … No / Yes
✔ Would you like to use `src/` directory with this project? … No / Yes
✔ Use App Router (recommended)? … No / Yes
✔ Would you like to customize the default import alias? … No / Yes
✔ What import alias would you like configured? … @/*
Creating a new Next.js app in /Users/s.y/dev/05.next/tech-blog.

Using npm.

Initializing project with template: default-tw


Installing dependencies:
- react
- react-dom
- next
- typescript
- @types/react
- @types/node
- @types/react-dom
- tailwindcss
- postcss
- autoprefixer


added 119 packages, and audited 120 packages in 24s

21 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
Success! Created tech-blog at /Users/s.y/dev/05.next/tech-blog
```

success!と言われれば OK です。

では、VSCode を開いて`next.config.js`に追記しましょう。

```javascript
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  output: 'standalone', // 追記
};

module.exports = nextConfig;
```

output: 'standalone'を追記しました。

> Static Web Apps にデプロイするために Next.js アプリを構成するには、Next.js プロジェクトのスタンドアロン機能を有効にします。
> この手順では、Next.js プロジェクトのサイズを小さくして、Static Web Apps のサイズ制限を下回るようにします。
> 詳細については、スタンドアロンに関するセクション[https://learn.microsoft.com/ja-jp/azure/static-web-apps/deploy-nextjs-hybrid#enable-standalone-feature]を参照してください。

では、せっかくの Next.js なので、サーバサイドレンダリングも追加してみましょう。
pages/index.tsx は以下のコードとなります。

```typescript
import axios from 'axios';
import { useState } from 'react';

export default function Home() {
  const [isLoading, setIsLoading] = useState<boolean>(false);
  const [content, setContent] = useState<string>('');
  const [message, setMessage] = useState<string>('');

  const getAzData = async () => {
    setIsLoading(true);
    try {
      console.log('start');
      const res = await axios.post('api/azopenai', { message });
      setContent(res.data[0].message.content);
    } catch (err) {
      console.log('🚀 ~ file: index.tsx:32 ~ getAzData ~ err:', err);
    }
    setIsLoading(false);
  };
  return (
    <main className={`flex min-h-screen flex-col items-center p-24`}>
      <h1>ネガティブなことを言ってみてください</h1>

      <label
        htmlFor="message"
        className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
      >
        Your message
      </label>
      <textarea
        id="message"
        onChange={(e) => setMessage(e.target.value)}
        rows={4}
        className="block p-2.5 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500"
        placeholder="Write your thoughts here..."
      ></textarea>
      <button
        onClick={getAzData}
        type="button"
        className="my-5 py-2.5 px-5 mr-2 mb-2 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-200"
      >
        ポジティブ変換
      </button>
      {isLoading ? (
        <div role="status">
          <svg
            aria-hidden="true"
            className="w-8 h-8 mr-2 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600"
            viewBox="0 0 100 101"
            fill="none"
            xmlns="http://www.w3.org/2000/svg"
          >
            <path
              d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
              fill="currentColor"
            />
            <path
              d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
              fill="currentFill"
            />
          </svg>
          <span className="sr-only">Loading...</span>
        </div>
      ) : (
        <div>
          {content === '' ? (
            <div></div>
          ) : (
            <div className="block max-w-sm p-6 bg-white border border-gray-200 rounded-lg shadow hover:bg-gray-100">
              <p className="font-normal text-gray-700">{content}</p>
            </div>
          )}
        </div>
      )}
    </main>
  );
}
```

axios を使用するので、install しておきましょう。

```bash
npm install axios
```

src/pages/api/azopenai.ts を作成してください。
コードは以下となります。

```typescript
import { getAzOpenAIData } from '@/models/azopenai/azopenaiApplicationService';
import type { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  try {
    const message = req.body.message;
    const data = await getAzOpenAIData(message);
    res.json(data);
  } catch (error) {
    console.log('🚀 ~ file: hello.ts:13 ~ error:', error);
  }
}
```

次は OpenAI へアクセスする関数を作成します。
以下二つのフォルダとファイルを作成してください。

> src/models/azopenai/azopenaiApplicationService.ts
> src/models/azopenai/azopenaiRepository.ts

それぞれのコードは以下です。

azopenaiApplicationService.ts

```typescript
import { AzOpenaiRepository } from './azopenaiRepository';

export const getAzOpenAIData = async (message: string) => {
  try {
    const repo = new AzOpenaiRepository();
    return await repo.getAzOpenAIData(message);
  } catch (err) {
    return err;
  }
};
```

azopenaiRepository.ts

```typescript
import { AzureKeyCredential, OpenAIClient } from '@azure/openai';

export class AzOpenaiRepository {
  async getAzOpenAIData(message: string) {
    console.log('start', process.env.AZURE_OPENAI_ENDPOINT!);
    return new Promise(async (resolve, reject) => {
      const endpoint = process.env.AZURE_OPENAI_ENDPOINT!;
      const azureApiKey = process.env.AZURE_OPENAI_API_KEY!;
      const deploymentId = process.env.AZURE_OPENAI_DEPLOYMENT_ID!;
      const content = `
      ${message}
      #上記をポジティブなフィードバックに変換して
      `;
      try {
        const messages = [
          { role: 'system', content: 'You are a helpful assistant.' },
          {
            role: 'user',
            content,
          },
        ];
        const client = new OpenAIClient(
          endpoint,
          new AzureKeyCredential(azureApiKey)
        );

        const result = await client.getChatCompletions(deploymentId, messages);
        resolve(result.choices);
      } catch (error: any) {
        console.log(
          '🚀 ~ file: openaiRepository.ts:29 ~ AzOpenaiRepository ~ returnnewPromise ~ error:',
          error
        );
        reject(error);
      }
    });
  }
}
```

以下もインストールしておきましょう！

```bash
npm install @azure/openai
```

環境変数は.env.local に記載です。
ルート階層に`.env.local`を作成してください。

.env.local

```
AZURE_OPENAI_ENDPOINT=https://xxxxxxxxxxxxxxxxx/
AZURE_OPENAI_API_KEY=xxxxxxxxxxxxxxxxx
AZURE_OPENAI_DEPLOYMENT_ID=xxxxxxxxxxxxxxxxx
```

では 3 つの環境変数を取得しましょう。
AZURE_OPENAI_ENDPOINT と AZURE_OPENAI_API_KEY は
Azure へログイン →OpenAI と検索 → 作成した OpenAI を選択 → リソース管理 → キーとエンドポイントの箇所を参照です。

![key_endpoint.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326114/68b7d8a0-903c-4037-8f6c-c3117bde8964.png)

AZURE_OPENAI_DEPLOYMENT_ID は
Azure OpenAI Studio→ デプロイの箇所からデプロイ名をコピーしてきてください。

ここまで実装して、実行した時にエラーがなければ OK です。

```bash
npm run dev
```

![run_dev.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326114/7db5e841-f083-9704-0591-dac9f78f96b7.png)


問題なければ Azure DevOps の Repos へ push しましょう。
Azure DevOps に戻ったら以下のように code が push されていれば OK です。

![repos.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326114/f61ba2e9-5ef8-3678-598c-07a8074276c0.png)

## Azure Static Web Apps へ Pipelines を用いて Deploy

では、Azure Static Web Apps へ Pipeline よりホスティングです。
全体図で言うとこの部分です。

![pipeline_set.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326114/4e485ba5-5275-c596-4d30-f3f63c7ac0b5.png)


Azure へログインして、Azure Static Web Apps を作成しましょう。
![azurestaticwebapps.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326114/23273026-51d0-d166-8c56-42ddf3732659.png)


East Asia でデプロイの詳細はその他を入力

![setting.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326114/af69d5df-b0e1-b789-5ab6-7322b56b9108.png)


タグ → 確認及び作成より作成してください。

リソースに移動して、デプロイトークンの管理からデプロイトークンをコピーしておいてください。

では、Azure DevOps に戻り、Azure Pipeline をクリックし、create pipeline をクリック

Azure Repos Git を選択
![reposgit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326114/3aa5c9b7-ca41-9000-4366-7d7e8125920b.png)


リポジトリを選択したら、Starter Pipeline を選択

![stater.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326114/696a171f-b64e-9a4e-1d3e-4fa646ce5c36.png)


yml ファイルへ AzureStaticWebApps の箇所を追記

```yml
# Starter pipeline
# Start with a minimal pipeline that you can customize to build and deploy your code.
# Add steps that build, run tests, deploy, and more:
# https://aka.ms/yaml

trigger:
  - main

pool:
  vmImage: ubuntu-latest

steps:
  - script: echo Hello, world!
    displayName: 'Run a one-line script'

  - script: |
      echo Add other tasks to build, test, and deploy your project.
      echo See https://aka.ms/yaml
    displayName: 'Run a multi-line script'

  # Static Web Appsのデプロイ
  - task: AzureStaticWebApp@0
    inputs:
      app_location: '/'
      api_location: ''
      output_location: '.next'
    env:
      azure_static_web_apps_api_token: $(deployment_token)
      AZURE_OPENAI_ENDPOINT: $(AZURE_OPENAI_ENDPOINT)
      AZURE_OPENAI_API_KEY: $(AZURE_OPENAI_API_KEY)
      AZURE_OPENAI_DEPLOYMENT_ID: $(AZURE_OPENAI_DEPLOYMENT_ID)
```

画面右上の Variables→New Variables より Name に`deployment_token`と入力し、value へ先ほどコピーしたデプロイメントトークンを貼り付けて OK→save をクリック

![deploytoken.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326114/368170d1-1794-08a8-beed-e5be36619698.png)


あとは`Save and Run`を実行して、神に Deploy が成功することを祈りましょう。

![deploying.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326114/f9611649-2bdc-fd5a-f331-b284c91abcae.png)


ざわ...
ざわ...

...

#### success!!!

![Azure]![deploysuccess.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326114/cd9de442-32ac-f0cf-a8c0-f1176e109ee3.png)


では、Azure の Azure Static Web Apps に戻って 構成の箇所に環境変数を追加しましょう。

![env.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326114/dc738fe6-bd23-8011-4b31-20ef986e24ae.png)


そして、URL にアクセスしてみましょう！

![url.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326114/ff3e5cfe-b222-97ba-42a3-22e423cd1cc3.png)


無事に Deploy されていることを確認して作業は完了です！

## 動作確認

ここまでできたら後はネガティブをアプリへぶつけるだけです。

思いのたけをテキストボックスに入れてボタンをクリックしてみてください。

OpenAI がポジティブに変換していい感じに返事をしてくれます！

![run_dev.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326114/f2e7bba7-f89d-e354-97fb-8117b61e6f8d.png)

できましたか？

なかなか盛りだくさんの内容なので、詰まっちゃった方はぜひコメント欄にてどしどし質問してくださいね！

## お片付け

最後にずっと残してて課金されないようにお片付けです。

以下、３つの操作を実行すれば OK です。

- Azure OpenAI Studio からデプロイの削除
- Azure よりリソースグループの削除
- Azure DevOps よりプロジェクトの削除

しっかりお片付けして本日は終わりです。

お疲れ様でした。