祝 Azure OpenAI Service GA! ⑥「あの」キャラクターを ChatGPT で LINE に召喚してみる

1/16 に GA (General Availability: 一般提供) になった [Azure OpenAI Service](https://azure.microsoft.com/ja-jp/products/cognitive-services/openai-service) は、Azure 上で OpenAI が稼働するサービスです。Cognitive Services の一員として & Web API として利用できる、Studio と呼ばれる GUI が用意されており、テストや調整などが可能です。

今回は、Azure OpenAI Service にも用意されている ChatGPT を使って、伝説の「カ〇ル君」を ChatGPT で再現し、Azure Functions を使って LINE Messaging API と接続してみます。

> 3/10 現在、GA したとはいえ、Azure OpenAI Service を利用するには [Web から利用申請](https://customervoice.microsoft.com/Pages/ResponsePage.aspx?id=v4j5cvGGr0GRqy180BHbR7en2Ais5pxKtso_Pz4b1_xUOFA5Qk1UWDRBMjg0WFhPMkIzTzhKQ1dWNyQlQCN0PWcu&culture=ja-jp&country=jp)が必要です。**Text and Code Model** (GPT-3) と **Dall-E 2** の申し込みができますが、とりあえず **Text and Code Model** だけ申請するのがおススメ！ChatGPT は Text Model の権限があれば利用できます。また、申請時には有効な Azure サブスクリプション (のID) が必要です。

> Azure OpenAI Service 自体は GA ですが、3/10~ 利用可能になった ChatGPT は現在プレビュー中です。

# 準備

- [Azure OpenAI Studio からお試し](https://qiita.com/annie/items/ce7214c6d683b9442382) の記事を参考に、
    - Azure OpenAI Service のプレビュー申込 & Azure Portal からのサービス作成を行い、キーとエンドポイントをローカルに保存しておきます。
    - ChatGPT モデルの **gpt-35-turbo** をデプロイしておきます。

- [LINE Developer 登録 & Messaging API を使う準備](https://zenn.dev/mochan_tk/books/line-aws-paypay-handson-doc/viewer/2-create-line-messaging-api-channel) を参考に、LINE Developer アカウントの登録＆Messaging API チャネルの作成を行います。作成したチャネルの Messaging API 設定から **チャネルアクセストークン** を生成してコピー、ローカルに保存しておきます。

- Rest API を POST 送信できるツール
    - こちらでは [Postman](https://www.postman.com/) を使います
- IP トンネル(ローカル IP アドレス を一時的に公開できる)ツール
    - こちらでは [ngrok](https://ngrok.com/) を使います (参考: [アカウントが必要になったngrokの利用準備手順](https://zenn.dev/protoout/articles/47-ngrok-setup-2022))
    - Node.JS を使う場合は localtunnel (参考: [localtunnelでローカルサーバーを一時的に公開する](https://www.geeklibrary.jp/counter-attack/localtunnel/))なども使いやすいかと思いますのでお好みで。

- 開発環境
    - 今回は Azure Functions を使って Web API アプリを稼働させます。お好みですが、以下の開発ツール＆環境で動作確認をしています;
        - Windows 11 (x64 10.0.22621)
        - C#
            - Visual Studio Enterprise 2022 (x64) (v17.5.4)
        - Node.JS & Python
            - Visual Studio Code (v1.77.1)
            - Node.JS (v18.13.0)
- サンプルコード
    - [GitHub/ayako/AzureOpenAIAppSamples/ChatGPTFunc-LINEApi/ChatGPTFunc-LINEApi.zip](https://github.com/ayako/AzureOpenAIAppSamples/blob/main/ChatGPTFunc-LINEApi/ChatGPTFunc-LINEApi.zip) にアクセスし、ローカル環境にダウンロード＆展開しておきます。

# 手順

## Azure OpenAI Studio からチャットボットの挙動を調整する

[Azure OpenAI Studio](https://oai.azure.com) を開き、Azure OpenAI Service を作成した (or 利用するためのロールを持っている) アカウントでサインインします。

ChatGPT モデルの **gpt-35-turbo** がデプロイ済みであれば、**[ChatGPT プレイグラウンド]** が表示されるので、こちらをクリックします。

![スクリーンショット 2023-04-10 211615.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117583/85d396eb-b899-cbfe-90df-d3dddbcf25c4.png)

今回は、**アシスタントのセットアップ** の機能を使って、予め回答の方法を指示します。
**システムメッセージ** の欄に指示を入力します。
ここでは、

- Microsoft 365 に関する質問に答える
- 最初に挨拶して、ユーザーに質問を促す

ということを指示しています。
入力したら、**[変更の保存]** をクリックして保存します。

![スクリーンショット 2023-04-24 193939.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117583/8fc9f88f-e6ca-950c-fb6b-7150080883f8.png)

> **システムメッセージを更新しますか？** というダイアログが表示されたら、**[続行]** をクリックして保存します。
![スクリーンショット 2023-04-10 212112.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117583/447af48e-121e-ba46-ebfd-3bf09ada53cf.png)

システムメッセージの通りに返答するか、**[チャットセッション]** で入力して確認します。

![スクリーンショット 2023-04-24 194000.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117583/73487dc7-ba59-02b9-dc7e-85b5bbe31ca3.png)

> ["しま〇ろう BOT" を ChatGPT で作ってみる: Azure OpenAI Studio からチャットボットの挙動を調整する](https://qiita.com/annie/items/b816119dde120b09bbca#azure-openai-studio-%E3%81%8B%E3%82%89%E3%83%81%E3%83%A3%E3%83%83%E3%83%88%E3%83%9C%E3%83%83%E3%83%88%E3%81%AE%E6%8C%99%E5%8B%95%E3%82%92%E8%AA%BF%E6%95%B4%E3%81%99%E3%82%8B) の記事を参考に、お好みの回答を返すように調整してください。

## Azure OpenAI Studio から Rest API POST に必要な方法を確認する

Azure OpenAI Studio の ChatGPT プレイグラウンドで、一旦チャットをクリアしてから、1回質問を入力 → 回答された状態で **コードの表示** をクリックします。

![スクリーンショット 2023-04-24 204204.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117583/cf8a366a-f413-b602-2842-660d4c3ca3b8.png)

サンプルコードが表示されます。サンプルコードの種類を **curl** に変更して、Rest API でアクセスするのに必要な情報を確認します。一番上部に書かれている URL がアクセスする Rest API のURL となります。こちらをコピーしてローカルに保存しておきます。
また、Header に必要な情報を確認します。Rest API の種類は **POST**、Headerには、**ContentType** (application/json) と **api-key** (Azure Portal で取得してローカルに保存しておいたキー) が必要であることが分かります。

![スクリーンショット 2023-04-24 204336.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117583/aedb08f8-6545-a7b6-55e9-5d26c4e90d5c.png)

さらに、サンプルコードの種類を **json** に変更して、Rest API のリクエスト本体 (Body) となる JSON を確認します。**[コピー]** をクリックして、ローカルに保存しておきます。

![スクリーンショット 2023-04-24 204338.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117583/2f02534d-6a8d-7056-c487-6f58ab432a8c.png)

> JSON は以下のフォーマットになっていることが確認できます。
> ChatGPT からの回答は **assistant** として表示されています。ChatGPT に回答を生成することを指示したい場合は、この **assistant** の回答部分をブランクにして Rest API にリクエストします。(後で再度解説します)
>
> ```json
> {
>   "messages": [
>     {
>       "role": "system",
>       "content": ### システムメッセージ ###
>     },
>     {
>       "role": "user",
>       "content": ### ユーザーの質問 ###
>     },
>     {
>       "role": "assistant",
>       "content": ### ChatGPTからの返答 ### 
>     }
>   ],
>   "temperature": 0.7,
>   "top_p": 0.95,
>   "frequency_penalty": 0,
>   "presence_penalty": 0,
>   "max_tokens": 800,
>   "stop": null
> }
> ```

## Rest API ツール から Azure OpenAI Service にリクエスト送信して挙動を確認する

Rest API ツール (ここでは Postman) を開き、情報をセットします。

- Type: **POST**
- URL: **これまでの手順でローカルに保存しておいた URL** (https://XXXXXXX.openai.azure.com/openai/....)
- Header: 
    - Content-Type: **application/json**
    - api-key: **Azure Portal からコピーしておいた Azure OpenAI サービスのキー**

![スクリーンショット 2023-04-10 215242.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117583/d3ae5592-9fe7-e8a1-927a-bb2fc10c6c12.png)

Request Body の部分は、これまでの手順でローカルに保存しておいた JSON をひとまずコピーし、**assistant** の回答部分を "" (ブランク) にします。

例えば、このような内容の JSON になります。

```json
{
  "messages": [
    {
      "role": "system",
      "content": "あなたはMicrosoft 365(Microsoft Office)に関する質問に回答する、イルカのキャラクターです。最初に挨拶して、Microsoft 365に関する質問をユーザーに促してください。"
    },
    {
      "role": "user",
      "content": "Powerpointで図形を整列させる方法"
    },
    {
      "role": "assistant",
      "content": ""
    }
  ],
  "temperature": 0.7,
  "top_p": 0.95,
  "frequency_penalty": 0,
  "presence_penalty": 0,
  "max_tokens": 800,
  "stop": null
}
```

![スクリーンショット 2023-04-24 193702.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117583/121ba1bc-4a05-4291-aeca-3f7c2c122e3f.png)

情報が入力できたら、**POST** してみます。
Response Body は JSON で表記されて、**Choices** > **message** に返答が書かれています。**"role" : "assistant"** になっているのを確認、**"content"** が実際の返答となります。思ったような返答が返されていればOKです。

![スクリーンショット 2023-04-24 193740.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117583/82e36596-9731-6ccd-7468-5edacb9a0b8f.png)

> ChatGPT からの回答は Rest API リクエストの度に生成されているので、毎回 (多少) 異なる回答になります。

## Azure Functions のサンプルコードに Azure OpenAI Service および LINE Messaging API の情報をセットする

C# | Node.JS | Python のいずれかのサンプルコードを選び、以下の部分を修正します。

### C#

ChatGPTFunc.cs の 20~23 行目を、上記の手順でローカルに保存しておいた内容で修正します。

- **aoaiUrl** : Azure Portal からコピーした **Azure OpenAI Service の URL**
- **aoaiKey** : Azure Portal からコピーした **Azure OpenAI Service キー** 
- **aoaiModelName** : Azure OpenAI Studio で作成した **gpt-35-turbo のモデル名**
- **lineApiToken** : LINE Messaging API のページから取得した **チャネルアクセストークン**

> **Azure OpenAI Service の URL** と **gpt-35-turbo のモデル名** は Azure OpenAI Studio からコピーした **RestAPI URL** から取得できます。
> RestAPI URL は、https://**Azure OpenAI Service URL**/openai/deployments/**gpt-35-turbo のモデル名**/chat/completions?api-version=2023-03-15-preview というフォーマットになっています。

```ChatGPTFunc.cs
private static readonly string aoaiUrl = "https://YOUR_AOAI_SERVICE.openai.azure.com/";
private static readonly string aoaiKey = "YOUR_AOAI_KEY";
private static readonly string aoaiModelName = "YOUR_gtp-35-turbo_NAME";
private static readonly string lineApiToken = "YOUR_LINE_API_TOKEN";
```

さらに、ChatGPTFunc.cs の 60 行目を、上記の手順で調整したシステムメッセージに入れ替えます。

```ChatGPTFunc.cs
new ChatMessage(ChatRole.System, @"あなたはMicrosoft 365(Microsoft Office)に関する質問に回答する、イルカのキャラクターです。最初に挨拶して、Microsoft 365に関する質問をユーザーに促してください。"),
```

### Node.js

ChatGPTFunc/index.js の 1~4 行目にある以下の項目を、上記の手順でローカルに保存しておいた内容で修正します。

- **aoai_url** : Azure OpenAI Studio からコピーした **RestAPI URL**
- **aoai_key** : Azure Portal からコピーした **Azure OpenAI Service キー**
- **lineApi_token** : LINE Messaging API のページから取得した **チャネルアクセストークン**

```ChatGPTFunc/index.js
const aoai_url = 'https://YOUR_AOAI_SERVICE.openai.azure.com/openai/deployments/YOUR_gtp-35-turbo_NAME/chat/completions?api-version=2023-03-15-preview';
const aoai_key = 'YOUR_AOAI_KEY';
const lineapi_url = 'https://api.line.me/v2/bot/message/reply';
const lineapi_token = 'YOUR_LINE_API_TOKEN';
```

さらに、ChatGPTFunc/index.js の 47 行目を、上記の手順で調整したシステムメッセージに入れ替えます。

```ChatGPTFunc/index.js
async function postToAOAI(question) {
    var req_body = {
      "messages": [
        {
          "role": "system",
          "content": "あなたはMicrosoft 365(Microsoft Office)に関する質問に回答する、イルカのキャラクターです。最初に挨拶して、Microsoft 365に関する質問をユーザーに促してください。"
        },
```

### Python

function_app.py の 5~12 行目にある以下の項目を、上記の手順でローカルに保存しておいた内容で修正します。

- **openai.api_base** : Azure Portal からコピーした **Azure OpenAI Service の URL**
- **openai.api_key** : Azure Portal からコピーした **Azure OpenAI Service キー** 
- **lineapi_token** : LINE Messaging API のページから取得した **チャネルアクセストークン**

```function_app.py
import openai
openai.api_type = "azure"
openai.api_base = "https://YOUR_AOAI_SERVICE.openai.azure.com/"
openai.api_version = "2023-03-15-preview"
openai.api_key = "YOUR_AOAI_KEY"

import requests;
lineapi_url = 'https://api.line.me/v2/bot/message/reply'
lineapi_token = 'YOUR_LINE_API_TOKEN'
```

さらに、
- 46 行目にある engine の値を Azure OpenAI Studio で作成した **gpt-35-turbo のモデル名** に修正します。
- 50 行目にあるシステムメッセージ上記の手順で調整したシステムメッセージに入れ替えます。

> **gpt-35-turbo のモデル名** は Azure OpenAI Studio からコピーした **RestAPI URL** から取得できます。
> RestAPI URL は、https://*Azure OpenAI Service の URL*/openai/deployments/**gpt-35-turbo のモデル名**/chat/completions?api-version=2023-03-15-preview というフォーマットになっています。

```function_app.py
def postToAOAI(question):
    response = openai.ChatCompletion.create(
        engine = "YOUR_gtp-35-turbo_NAME",
        messages = [
            {
                "role":"system",
                "content":"あなたはMicrosoft 365(Microsoft Office)に関する質問に回答する、イルカのキャラクターです。最初に挨拶して、Microsoft 365に関する質問をユーザーに促してください。"
            },
```

## Azure Functions のサンプルコードをローカルで実行して、LINE Messaging API に接続 & LINE アプリから質問してみる

修正した C# | Node.JS | Python の Azure Functions アプリをローカルで起動します。
IP トンネルツール (ここでは ngrok) を使って、外部からアクセスできるようにします。

> LINE Messaging API では、ユーザーから入力があった場合、以下のようなフォーマットで Rest API リクエストが送信されます。このサンプルコードでは、このようなリクエストを受信して、Azure OpenAI Service で回答を生成し、LINE Messaging API へ適切なフォーマットで回答を送信します。
> 
> ```
> [
>   {
>     "type": "message",
>     "message": {
>       "type": "text",
>       "id": "00000000000000",
>       "text": "ユーザーからの入力"
>     },
>     "webhookEventId": "00000000000000000000000000",
>     "deliveryContext": {
>       "isRedelivery": false
>     },
>     "timestamp": 1680000000000,
>     "source": {
>       "type": "user",
>       "userId": "U00000000000000000000000000000000"
>     },
>     "replyToken": "00000000000000000000000000000000",
>     "mode": "active"
>   }
> ]
> ```

Azure Functions と IP トンネルツールを起動したままで、LINE Developer コンソールで作成したチャネルの Messaging API を設定します。
**Webhook設定** で、**Webhookの利用** を ON にしてから、Webhook URL を設定します。
**Webhook URL** には、**IP トンネルツールで公開したURL + /api/ChatGPTFunc** を入力して、**[更新]** をクリックします。

![スクリーンショット 2023-04-24 233405.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117583/0b16b375-cf4d-3337-cb03-7f1debb4e8aa.png)

Webhook URL の **[検証]** をクリックして、**成功** と表示されるのを確認してください。

![スクリーンショット 2023-04-24 233417.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117583/4d2e5d41-f565-223d-fff7-8584d2bbe5b0.png)

![スクリーンショット 2023-04-24 233431.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117583/90a1b0f8-3cf0-47e1-0c26-e5067d9573cb.png)

LINE チャネルの **Messaging API 設定** に表示される QR コードを LINE アプリで読み取り、チャネルにアクセスします。

![スクリーンショット 2023-04-24 233449.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117583/219b8a47-decc-c3c2-c6c9-67ed290c0129.png)

LINE アプリからメッセージや質問を入力して、返答が返ってきたら作成完了です。

![Image.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117583/126a4a5f-44df-533d-7a68-f744e413822b.jpeg)