Azure Bot × Teams × Azure OpenAIの環境構築① ~オウム返しするまで~

# まえがき
本記事ではAzure OpenAIはまだ登場しません。次回以降にまとめていきたいと思います。
と言っても、最初の一歩なので「ユーザの入力をAzure OpenAIに対して投げ、そのレスポンスをユーザに表示してあげる。」といった基礎的な処理を組み込むだけになりそう、、

※2023年7月8日：追記
本記事の続きを行い、別の記事にまとめました。

https://qiita.com/sakue_103/items/e74f0d887a74de3e1ea0

# Prerequisites

詳細は下記公式ドキュメントに記載があります。

https://learn.microsoft.com/ja-jp/microsoftteams/platform/toolkit/tools-prerequisites


- [Microsft開発アカウント](https://developer.microsoft.com/en-us/microsoft-365/dev-program) (無料登録可能)
- VSCode
- Azureアカウント

# Teams Toolkitの準備

https://learn.microsoft.com/ja-jp/microsoftteams/platform/toolkit/toolkit-v4/build-environments-v4

https://learn.microsoft.com/ja-jp/microsoftteams/platform/toolkit/install-teams-toolkit?tabs=vscode

今回は上記公式ドキュメントを参考に、VSCodeの拡張機能であるTeams Toolkitをインストールします。
VSCodeの拡張機能タブをクリック → 検索バーに**teams**と入力 → **install**をクリックします。
（私の環境では既にinstall済みのためinstallが表示されていません）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/b82f4c37-23e8-34bc-f65e-ca690de41b58.png)


# Teamsアプリをローカル環境に構築

## VSCode上からログイン作業

- 開発用アカウント
- Azureアカウント

それぞれにログインしておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/9634dfdc-103e-9e6a-ec35-eb72c7b259f2.png)


## 雛形アプリ作成

Teams Toolkitで雛形アプリを作成します。
Create New App → Bot → Basic Bot → TypeScriptとクリックし、アプリ名を入力します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/b3e3408a-9022-de5f-18c7-8b3053053027.png)




![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/eec71ab8-1acc-a13c-6959-b029baeb4862.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/3ba17370-26db-ee6d-5be0-3d387f5acaa6.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/bc57f5f3-ff0b-6f79-0baf-ee7454c4f8a2.png)


この作業を終えると雛形アプリが作成されます。作成されたディレクトリに移動し、メインのソースコードを少し編集します。
具体的には、```teamsBot.ts```を編集します。編集箇所は以下の通りです

- ```import MessageFactory```を追記
- onMessage()内に、ユーザの入力をそのまま返すコードを追記
    - ```await context.sendActivity(MessageFactory.text(txt))```

```ts:teamsBot.ts
import {
  TeamsActivityHandler,
  CardFactory,
  TurnContext,
  AdaptiveCardInvokeValue,
  AdaptiveCardInvokeResponse,
  MessageFactory, // 【追記】
} from "botbuilder";
import rawWelcomeCard from "./adaptiveCards/welcome.json";
import rawLearnCard from "./adaptiveCards/learn.json";
import { AdaptiveCards } from "@microsoft/adaptivecards-tools";

export interface DataInterface {
  likeCount: number;
}

export class TeamsBot extends TeamsActivityHandler {
  // record the likeCount
  likeCountObj: { likeCount: number };

  constructor() {
    super();

    this.likeCountObj = { likeCount: 0 };

    this.onMessage(async (context, next) => {
      console.log("Running with Message Activity.");

      let txt = context.activity.text;
      const removedMentionText = TurnContext.removeRecipientMention(context.activity);
      if (removedMentionText) {
        // Remove the line break
        txt = removedMentionText.toLowerCase().replace(/\n|\r/g, "").trim();
      }

      // 【追記】ユーザのチャット画面にtxtを表示 → オウム返しとなる
      await context.sendActivity(MessageFactory.text(txt))

      // Trigger command by IM text
      switch (txt) {
        case "welcome": {
          const card = AdaptiveCards.declareWithoutData(rawWelcomeCard).render();
          await context.sendActivity({ attachments: [CardFactory.adaptiveCard(card)] });
          break;
        }
        case "learn": {
          this.likeCountObj.likeCount = 0;
          const card = AdaptiveCards.declare<DataInterface>(rawLearnCard).render(this.likeCountObj);
          await context.sendActivity({ attachments: [CardFactory.adaptiveCard(card)] });
          break;
        }
        /**
         * case "yourCommand": {
         *   await context.sendActivity(`Add your response here!`);
         *   break;
         * }
         */
      }

      // By calling next() you ensure that the next BotHandler is run.
      await next();
    });

    this.onMembersAdded(async (context, next) => {
      const membersAdded = context.activity.membersAdded;
      for (let cnt = 0; cnt < membersAdded.length; cnt++) {
        if (membersAdded[cnt].id) {
          const card = AdaptiveCards.declareWithoutData(rawWelcomeCard).render();
          await context.sendActivity({ attachments: [CardFactory.adaptiveCard(card)] });
          break;
        }
      }
      await next();
    });
  }

  // Invoked when an action is taken on an Adaptive Card. The Adaptive Card sends an event to the Bot and this
  // method handles that event.
  async onAdaptiveCardInvoke(
    context: TurnContext,
    invokeValue: AdaptiveCardInvokeValue
  ): Promise<AdaptiveCardInvokeResponse> {
    // The verb "userlike" is sent from the Adaptive Card defined in adaptiveCards/learn.json
    if (invokeValue.action.verb === "userlike") {
      this.likeCountObj.likeCount++;
      const card = AdaptiveCards.declare<DataInterface>(rawLearnCard).render(this.likeCountObj);
      await context.updateActivity({
        type: "message",
        id: context.activity.replyToId,
        attachments: [CardFactory.adaptiveCard(card)],
      });
      return { statusCode: 200, type: undefined, value: undefined };
    }
  }
}
```

## ローカル環境でアプリを実行

VSCodeのデバッグ機能を使用します。EdgeやChromeでTeamsアプリが立ち上がり、開発中のコードの動作検証などを行うことができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/f472850b-c24f-e9e1-ae7d-9350eb9480d0.png)

今回私はEdgeで起動したため、Edgeで立ち上がったアプリを確認してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/6faf920b-1885-11f0-b0cc-2ddf7f1fd779.png)

オウム返しをしてくれるかと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/9610c7bd-4c7b-4231-0603-d2f6e7c28c91.png)



# Azureリソースのプロビジョニング

## Teams Toolkitから作成

Teams Toolkitでは、ボットを作成するために必要なリソース群を実質ワンクリックで作成することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/a14b725a-2b93-0dea-2137-3782fd7907ee.png)


リソースデプロイ先となるサブスクリプションを選択。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/17fb83ab-8e93-8c57-274d-fa7313a13f27.png)

リソースグループを選択。(今回は新規作成)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/165c5871-83b5-01d1-b59c-a21e1f78133c.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/9e532a38-35c6-460f-9087-abcee01a7af8.png)

リージョンを選択。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/e6758da1-8247-a4c9-bd50-7de6674215ed.png)

Provisionをクリック。するとAzure上に必要なリソース群が作成されます。便利ですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/a73e27b1-cf21-d2a5-48b8-23b07400f9cb.png)


## 作成物の確認

Azure Portalからリソースグループを確認してみます。Azure BotとApp Serviceが作成されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/363306db-0550-66a1-f170-94b2a3a081ff.png)

また、Teamsプロジェクトフォルダにある```env/.env.dev```を確認すると、作成されたAzureリソースに合わせて必要な情報が追記されています。
Azure Botの認証情報やAppServiceのリソースIDが追記されていることがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/0a9dc426-5d41-4c6c-a5fe-a45f81737d73.png)

AppServiceを見ると、Azure Botの認証情報が環境変数として設定されていることがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/4870d94e-09f8-5850-caa5-39bc657da616.png)

この段階では、AppServiceはただの箱の状態です。そのため、以降の手順で先ほど作成したオウム返しBotをデプロイします。

# TeamsアプリをAppServiceにデプロイ

Teams ToolkitからAppServiceへのアプリデプロイを実施します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/55cf8ff2-94e8-5df3-4c3e-226aa3b2b3be.png)


デプロイのログはVSCode、AppService側の両方で確認できます。AppServiceではデプロイセンターから確認可能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/e291f468-46fe-4f3b-2251-f1e8e32b786e.png)

デプロイに成功していることを確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/14af19fd-312d-6812-6033-ffeb3397e8a2.png)


# Web ChatとTeams上で動作確認する

Azure Botの画面からWebチャットでテストをクリックすると、Azure Portal上で動作確認を行えます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/37d1a9ed-036a-67ce-2683-828405cacf07.png)



また、Azure Botの画面からチャンネルを選択し、**Open in Teams**をクリックするとTeamsから動作確認を行えます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/7ec2b752-e60f-8573-c3ed-99dfeb168a3f.png)


ローカル環境で実行した時同様、オウム返しをしてくれるかと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/441c770e-26ec-f723-26d0-2b991b7af910.png)


# まとめ・次回
Teams Toolkit × Azureを使用することで10分程度でオウム返しのBotを作成することができました。
次回はこのBotにAzure OpenAIにリクエストを投げる仕組みを追加していきたいと思います。



# 参考

https://phoneappli.hatenablog.com/entry/2022/02/25/180435#%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%81%8B%E3%82%89%E3%83%AA%E3%83%AA%E3%83%BC%E3%82%B9%E3%81%B8

https://tech-lab.sios.jp/archives/28734