Cisco WebexとAWS LambdaでOpenAI GPT-3のチャットボットを作る

# はじめに

最近話題になっているChatGPTを使ってチャットボットを簡単に作れないかと思い試してみました。
チャットボットはCisco Webexのbotを使用し、ロジックはAWSのLambdaに記述しました。ChatGPTはAPIが用意されていないようなので、今回はGPT-3等のモデルが使えるOpenAIのAPIを使用しています。

:::note info
[OpenAI](https://openai.com/api/)のAPIを使用するには、アカウントを作成しAPIキーを取得する必要があります。今回は初回に割り当てられる$18.00分のフリートライアルを使用します。
:::

:::note info
AWSの各リソースにはそれぞれ利用料金が発生します。
:::

# 手順

1. 開発環境の準備
2. Bot logicの作成
3. Cisco Webex Botの作成
4. AWS Lambdaの準備
5. AWS API Gatewayの準備
6. CIsco Webex webhookの作成

# 開発環境の準備

AWSで開発環境として __Cloud9__ を準備します。

### AWS Cloud9

マネジメントコンソールのCloud9の作成画面から __Name__ を入力し、__Environment type__ で __New EC2 instance__ を選択します。

![Screen Shot 2022-12-07 at 21.04.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834983/6e232bd5-ea36-2db2-7396-b141b8dc4e83.png)

__Instance type__ は __t2.micro__ 、 __Platform__ は __Ubuntu Server 18.04 LTS__ を選択し、__Create__ をクリックします。作成できれば、__Open in Cloud9__ からIDEを起動させます。

![Screen Shot 2022-12-07 at 21.04.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834983/cbc821e4-ae12-a621-9b0f-f3c54135ecb8.png)

# Bot logicの作成

AWS Lambdaには、Bot logicのコードそのものと関連するライブラリをzipでパッケージにしてデプロイします。

プロジェクトディレクトリを作成し、そこにライブラリをインストールしBot logicも配置します。今回はLambdaに対応しているpython3.9を使用するので、python3.9のコンテナ上でプロジェクトディレクトリを作成しライブラリをインストールした後、そのプロジェクトディレクトリをホストOSに `docker cp` でコピーします。ホストOSでプロジェクトディレクトリにBot logicを作成し、ライブラリもまとめてzip化することでデプロイパッケージを作成します。

### 1. ライブラリのインストール

Cloud9のターミナルでpython3.9のコンテナを起動し、そのコンテナ上でプロジェクトディレクトリを作成、ライブラリをインストールします。ライブラリは __openai__ 、 __webexteamssdk__ を使用します。

__[openai](https://github.com/openai/openai-python):__ OpenAI APIへのアクセスを提供するライブラリ([ドキュメント](https://beta.openai.com/docs/api-reference?lang=python))
__[webexteamssdk](https://github.com/WebexCommunity/WebexPythonSDK):__ Cisco webex APIへのアクセスを提供するライブラリ([ドキュメント](https://webexteamssdk.readthedocs.io/en/latest/index.html))

```shell-session
cloud9 $ docker run -itd --name py3.9 python:3.9
py3.904279f704823ce0bd12d7e21e4cf81c603b4918c2c6b2205d258
cloud9 $ docker exec -it py3.9 bash
root@py3.9:/# cd
root@py3.9:~# mkdir webex_chatgpt #プロジェクトディレクトリの作成
root@py3.9:~# cd webex_chatgpt/
root@py3.9:~/webex_chatgpt# mkdir package #ライブラリをインストールするpackageディレクトリの作成
root@py3.9:~/webex_chatgpt# pip3 install --target ./package openai #openaiライブラリのインストール
Collecting openai
  Downloading openai-0.25.0.tar.gz (44 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 44.9/44.9 KB 7.0 MB/s eta 0:00:00
  Installing build dependencies ... done
  Getting requirements to build wheel ... done
  Preparing metadata (pyproject.toml) ... done
Collecting tqdm
  Downloading tqdm-4.64.1-py2.py3-none-any.whl (78 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 78.5/78.5 KB 14.1 MB/s eta 0:00:00

(snip)

root@py3.9:~/webex_chatgpt# pip3 install --target ./package webexteamssdk #webexteamssdkライブラリのインストール
Collecting webexteamssdk
  Downloading webexteamssdk-1.6.1-py3-none-any.whl (113 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 113.5/113.5 KB 12.6 MB/s eta 0:00:00
Collecting PyJWT
  Downloading PyJWT-2.6.0-py3-none-any.whl (20 kB)

(snip)


root@py3.9:~/webex_chatgpt# exit
exit
cloud9 $ docker cp py3.9:/root/webex_chatgpt ./ #コンテナ上のプロジェクトディレクトリをホストOSへコピー
cloud9 $ cd webex_chatgpt/package/
cloud9:~/environment/webex_chatgpt/package $ zip -r ../my-deployment-package.zip . #ライブラリのzip化
  adding: urllib3/ (stored 0%)
  adding: urllib3/connection.py (deflated 71%)
  adding: urllib3/connectionpool.py (deflated 73%)

(snip)

cloud9:~/environment/webex_chatgpt/package $ cd ../
cloud9:~/environment/webex_chatgpt $ touch lambda_function.py #Bot logicファイルを作成

```

### 2. Bot logic

Cloud9のIDEでlambda_function.pyというファイル名でBot logicを作成します。

```python:lambda_function.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import openai
import os
from webexteamssdk import WebexTeamsAPI, Webhook

MESSAGE_WEBHOOK_RESOURCE = "messages"
MESSAGE_WEBHOOK_EVENT = "created"
openai.api_key = os.environ["OPENAI_SECRET_KEY"]

def chatgpt(prompt, room, api):
    response = openai.Completion.create(
        model="text-davinci-003",
        prompt=prompt,
        temperature=0.6,
        max_tokens=150,
        top_p=1,
        frequency_penalty=1,
        presence_penalty=1
    )
    
    api.messages.create(
        room.id,
        markdown=response["choices"][0]["text"]
    )

def respond_to_message(webhook, api, me):
    room = api.rooms.get(webhook.data.roomId)
    message = api.messages.get(webhook.data.id)

    if message.personId == me.id:
        return "OK"
    else:
        chatgpt(message.text, room, api)
        return "OK"

def lambda_handler(event, context):
    api = WebexTeamsAPI()
    me = api.people.me()
    webhook_obj = Webhook(event["body"])

    if (webhook_obj.resource == MESSAGE_WEBHOOK_RESOURCE and webhook_obj.event == MESSAGE_WEBHOOK_EVENT):
        respond_to_message(webhook_obj, api, me)
    else:
        print(f"IGNORING UNEXPECTED WEBHOOK:\n{webhook_obj}")

    return "OK"

```

Bot logicファイルをzipパッケージに追加します。

```shell-session
cloud9:~/environment/webex_chatgpt $ zip -g my-deployment-package.zip lambda_function.py #Bot logicファイルをzipパッケージに追加
  adding: lambda_function.py (deflated 55%)
```

# Cisco Webex Botの作成

Bot logicが完成したので、[Webex for Developers](https://developer.webex.com/)で __Bot__ を作成します。

[Webex for Developers](https://developer.webex.com/)でBotを作成します。__Start Building Apps__ から作成画面へ移ります。

![Untitled.001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834983/53fbdc94-2db7-a290-6cef-83c2ccbc133a.png)

__Bot__ を選択します。

![Untitled.002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834983/22504b1d-f516-586a-b587-7172f1ab26b9.png)

__Bot name__ 、__Bot username__ を入力、__Icon__ を選択し、__Add Bot__ をクリックします。

![Untitled.003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834983/893e73a6-50d4-f7e4-92c8-4464723b3c04.png)

Botが作成できれば、 __Bot access token__ をコピーしておきます。

![Untitled.005.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834983/b945ae72-ed84-ec8b-b488-0e03c029e972.png)

# AWS Lambdaの準備

Bot logicの実行環境として __Lambda__ を準備します。

### 1. AWS Lambda関数の作成

__Lambda__ の __関数の作成__ から関数を作成します。
__一から作成__ を選択し、__関数名__ を入力、__ランタイム__ で __Python 3.9__ を選択します。

![Untitled.009.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834983/048bb849-c537-6bdb-2874-7acf95029053.png)

あとはデフォルト設定のまま __関数の作成__ をおクリックします。

![Untitled.010.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834983/08e4a8da-97f0-e5fe-b8ba-a05784aa340b.png)

関数の作成が完了すれば、__設定__ タブ から __一般設定__　の編集をクリックします。

![Untitled.012.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834983/49aeb59e-5e8f-d718-0ce6-ce1e0710c142.png)

__タイムアウト__ を1分に伸ばします。

![Untitled.013.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834983/79f503e3-cf7d-6318-b428-188b3227be9c.png)

__環境変数__ にOpenAIのサイトにログインすれば取得できる __OpenAIのAPIキー__ と、Bot作成時に取得した __Bot access token__ を設定します。これらの値はBot logicのコードから環境変数を通して参照します。
> OPENAI_SECRET_KEY: { __OpenAIのAPIキー__ }
> WEBEX_TEAMS_ACCESS_TOKEN: { __Bot access token__ }

![Untitled.014.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834983/88720755-3779-dc71-f421-2b5b16e50615.png)

### 2. デプロイパッケージのアップロード

CLoud9のターミナルからawsコマンドでデプロイパッケージをLambdaへアップロードします。

```shell-session
cloud9:~/environment/webex_chatgpt $ aws configure set default.region ap-northeast-1 #デフォルトリージョンの設定
cloud9:~/environment/webex_chatgpt $ aws lambda update-function-code --function-name webex_bot --zip-file fileb://my-deployment-package.zip
{
    "FunctionName": "webex_bot",
    "FunctionArn": "arn:aws:lambda:ap-northeast-1:************:function:webex_bot",
    "Runtime": "python3.9",
    "Role": "arn:aws:iam::************:role/service-role/webex_bot-role-q8fpg7c4",
    "Handler": "lambda_function.lambda_handler",
    "CodeSize": 43060227,
    "Description": "",
    "Timeout": 3,
    "MemorySize": 128,
    "LastModified": "2022-12-07T00:36:00.000+0000",
    "CodeSha256": "L4Gfp9QcYw+Avxgy0ofhhenTznkV6mpof9NIrokvPJs=",
    "Version": "$LATEST",
    "TracingConfig": {
        "Mode": "PassThrough"
    },
    "RevisionId": "7c584c75-77c7-4b86-a778-************",
    "State": "Active",
    "LastUpdateStatus": "InProgress",
    "LastUpdateStatusReason": "The function is being created.",
    "LastUpdateStatusReasonCode": "Creating",
    "PackageType": "Zip",
    "Architectures": [
        "x86_64"
    ],
    "EphemeralStorage": {
        "Size": 512
    }
}
```

# AWS API Gatewayの準備

LambdaのAPIとして __API Gateway__ を準備します。

__API Gateway__ の 作成画面から __REST API__ の構築を選択します。

![Untitled.015.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834983/85490825-4d25-127c-a348-162d6105ab93.png)

プロトコルは __REST__ 、APIの作成は __新しいAPI__ を選択、 __API名__ は任意の名前を入力し、 __エンドポイントタイプ__ はリージョンを選択し、 __APIの作成__ をクリックします。

![Untitled.016.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834983/0201618a-aafb-e3ae-3c4e-f011ecfe31ec.png)

__アクション__ から __リソースの作成__ をします。

![Untitled.017.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834983/93ad636b-9007-4a24-2146-d56d0f27d210.png)

__リソース名__ に __message__ を入力し __リソースの作成__ をクリックします。

![Untitled.018.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834983/f68999d9-6714-08b1-1759-328c59fd2b7a.png)

__アクション__ から __メソッドの作成__ をします。

![Untitled.019.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834983/e34839ec-1229-cb09-ece1-f2fa34e6170f.png)

__POST__ を選択します。

![Untitled.020.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834983/88a376ff-7c78-9ae7-859f-29815adf13ad.png)

__統合タイプ__ は __Lambda関数__ 、 __Lambdaプロキシ統合の使用__ を有効化、 __Lambdaリージョン__ は __ap-northeast-1__ 、 __Lambda関数__ には作成したLambda関数の名前をそれぞれ設定します。

![Untitled.022.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834983/3ed31dc2-ec8f-a1b1-ce65-b0226afc4155.png)

__アクション__ から __APIのデプロイ__ をします。

![Untitled.024.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834983/b1db092c-77af-f4cf-a49a-183aa2669807.png)

__新しいステージ__ を選択し、任意の __ステージ名__ を入力します。今回は __prod__ ステージを作成しました。

![Untitled.025.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834983/0ed45133-5be1-b614-0bc6-73d914972a87.png)

__POST__ メソッドの __呼び出しURL__ をコピーしておきます。

![Untitled.026.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834983/2725da80-8dc9-6752-5095-44a813a7a96a.png)

# CIsco Webex webhookの作成

[Webex for Developers](https://developer.webex.com/docs)で __webhook__ の準備します。

Webex for Developersにログインし、[ドキュメントページ](https://developer.webex.com/docs)の左ペインから __APIs__ => __Webex APIs__ => __Messaging__ => __Reference__ => __Webhooks__ => __POST Create a Webhook__ を選択します。

![Untitled.030.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834983/45c68cf2-a0ff-9d7b-4c73-adafd75ad318.png)

右ペインの __Webhook__ 作成画面に下記のようにパラメータを入力し __Run__ します。

<dl>
  <dt>Authorization:</dt>
  <dd><strong>Use personal access token</strong>を無効にし、Bot作成時にコピーしておいた <strong>Bot access token</strong>をペーストします。</dd>
  <dt>name:</dt>
  <dd>任意のWebhook名を入力します。</dd>
  <dt>targetUrl:</dt>
  <dd>API Gateway作成時にコピーしておいた<strong>POSTメソッドの呼び出しURL</strong>をペーストします。</dd>
  <dt>resource:</dt>
  <dd><strong>messages</strong>と入力します。</dd>
  <dt>event:</dt>
  <dd><strong>created</strong>と入力します。</dd>
</dl>

![Untitled.031.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834983/e1f1a34f-643a-0512-8cca-638641f63c4c.png)

# 実行

Webexで作成したBotに対してダイレクトメッセージを送ります。

![Screen Shot 2022-12-09 at 11.49.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834983/d221eb28-657a-4b9c-57a7-4abf75024562.png)

答えが最適かどうかは置いておいて、ちゃんと返信してくれました。\:laughing:

# まとめ

認証や、ヘッダー、ボディー、メソッドなどAPIアクセスのコードは少し煩雑になりがちですが、openaiや、webexteamssdkなどライブラリを使えばAPIアクセスのコードを非常に簡単にシンプルに作成することができました。どちらもわかりやすいドキュメントも用意されいてとても便利でした。
WebexのWebhookはアダプティブカードにも対応しているので使い道は無限大にあり、とても便利なツールとしてよく使わせてもらっています。

# 参考リンク

- [Webex for Developers](https://developer.webex.com/)
- [webexteamssdk github](https://github.com/WebexCommunity/WebexPythonSDK)
- [webexteamssdk ドキュメント](https://webexteamssdk.readthedocs.io/en/latest/index.html)
- [OpenAI](https://openai.com/api/)
- [openai github](https://github.com/openai/openai-python)
- [openai ドキュメント](https://beta.openai.com/docs/api-reference?lang=python)
- [AWS Lambda デベロッパーガイド](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/welcome.html)