ChatGPTのPythonAPI×Azure Functions×linebotを作りました！

## はじめに
2023年3月2日に公開されたChatGPTのAPIを使って自分好みのチャットボットが作れると話題でしたので、私も作ってみました。
意外とすぐできて、触っててめちゃくちゃ面白いです。

## 参考になったサイト
[ChatGPT公式リファレンス　Chat completions](https://platform.openai.com/docs/guides/chat)
[ChatGPT/GPT-3を使って文脈のある会話を実現する（＋LINE Bot化） - Qiita](https://qiita.com/uezo/items/34a173787c26794e462f)
[ChatGPT APIを使ったLineBotの作り方を、人格の与え方まで完全解説](https://chatgpt-lab.com/n/n55257c082a9d)
[Visual Studio Code を使用して Python 関数を作成する - Azure Functions](https://learn.microsoft.com/ja-jp/azure/azure-functions/create-first-function-vs-code-python?pivots=python-mode-configuration)
[LineボットをPythonで使ってみよう!｜もちのき研究所](https://www.mochinoki-labo.com/lbot-python-intro/)
[LINEでChatGPTを使えるようにしてみる](https://zenn.dev/nixo/articles/267e4cabe6db68)

## VSCodeのインストール
[Visual Studio Code](https://code.visualstudio.com/)
## 拡張機能のインストール
日本語化
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475719/52ad3804-40dc-7442-79ac-a26776a7897d.png)
Python
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475719/f59f6e2a-7e4a-1533-73cd-3724a37be1ff.png)
Azure Functions
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475719/27405d6d-c1b9-74cb-19b5-bcd05dc9be66.png)

## ChatGPT×Azure Functions×linebotを作る
1. workspaceのFunctionsのマークからローカルプロジェクト作成。
2. 必要設定値の設定
3. Azureへのデプロイ
流れの詳細は以下のサイトが大変参考になりました。
[Azure Functions × Python で簡単にLINE Bot を作ろう！ (using GitHub Codespaces)](https://zenn.dev/mochan_tk/articles/d01a24bb576d18)

2 必要設定値の設定について今回ChatGPT要素も追加したいので、requirements.txtと__init __.pyは以下のように置き換えました。
```tex:requirements.txt
# DO NOT include azure-functions-worker in this file
# The Python Worker is managed by Azure Functions platform
# Manually managing azure-functions-worker may cause unexpected issues

azure-functions
line-bot-sdk
openai
```

```python:__init __.py
import logging
import os
import openai
import azure.functions as func

from linebot import (
    LineBotApi, WebhookHandler
)
from linebot.exceptions import (
    InvalidSignatureError
)
from linebot.models import (
    MessageEvent, TextMessage, TextSendMessage,
)

# Azure FunctionsのApplication Settingに設定した値から取得する↓
channel_secret = os.getenv('LINE_CHANNEL_SECRET', None)
channel_access_token = os.getenv('LINE_CHANNEL_ACCESS_TOKEN', None)
openai.api_key = os.getenv('OPENAI_API_KEY', None)

line_bot_api = LineBotApi(channel_access_token)
handler = WebhookHandler(channel_secret)

def main(req: func.HttpRequest) -> func.HttpResponse:
    logging.info('Python HTTP trigger function processed a request.')

    # get x-line-signature header value
    signature = req.headers['x-line-signature']

    # get request body as text
    body = req.get_body().decode("utf-8")
    logging.info("Request body: HttpTrigger1" + body)

    # handle webhook body
    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        func.HttpResponse(status_code=400)

    return func.HttpResponse('OK')

def generate_response(message):
    # OpenAI APIを使用して返答を生成する

    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": \
             "ここにチャットボットの設定を入れられます。\
             一人称や口調、会話例、行動指針などを指定すると その通りのチャットボットになります。"
             },
            {"role": "user", "content": message}
        ]
    )
    return response.choices[0].message.content.strip()

@handler.add(MessageEvent, message=TextMessage)
def message_text(event):
    message = event.message.text
    response = generate_response(message)
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=response)
    )
```

また、3 Azureへのデプロイについて、上記サイトの通りのローカルプロジェクトからデプロイしようとすると、私の環境だとVSCode上で「WindowsじゃなくてLinuxじゃないとだめだよ」みたいなメッセージが出てデプロイできなかったです。
代わりに、VScodeのAzure Functions拡張機能の説明欄に書いてあった通り、先にAzure上でAzure Functionsのリソースを作ってからデプロイすると上手くいきました（以下画像の「Deploy to Azure」から下の手順です）。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475719/69184316-8086-473e-2d9f-556b6bf65dd3.png)

## おわりに
以上の簡単な手順でChatGPTを使ったlinebotが作れました。
皆さんも是非自分好みのチャットボットを作ってみてください！

## おまけ
ChatGPTと連携させる部分が最初わからなかったので雑にChatGPT本人に聞きましたw
結構わかりやすく説明してくれて大変助かったので以下、おまけとして私とChatGPTのやり取りを載せておきます。
長くなるので、お互いのコード部分は適宜省略しています。

```tex:ChatGPTとの問答（わたし）
いま、ChatGPTとAzure FunctionsとLinebotを連携させて、Pythonコードを書いています。
Pythonで書いたコードをAzure Functions上で実行し、Linebotと連携させることはできました。
現状、Azure Functionsを実行した状態でLinebotを起動すると、こちらが入力した文字をそのまま返します（オウム返しする）。
これを、ChatGPTのPythonのAPIを使用することでChatGPTベースのLinebotとしたいです（Azure Functionsで実行できる状態のままにしてください）。
なお、OpenAIのAPIキーの変数名は「OPENAI_API_KEY」としてください。
ちなみに、LINE_CHANNEL_SECRETなどの変数はAzure Functionsの「Application Settings」から環境変数として設定しています。よって、このコード上でOpenAIのAPIキーを変数の中身を定義する必要はありません。
（コード略）
```
```tex:ChatGPTとの問答（ChatGPT）
こんにちは！ ChatGPTとAzure FunctionsとLinebotを連携するプログラムを作成されているようですね。
ChatGPT APIを使用してLinebotを作成するためには、OpenAI APIキーが必要です。Azure FunctionsのApplication SettingからLINE_CHANNEL_SECRETなどの変数を環境変数として設定していることはすでに確認されているようですので、OpenAI APIキーも同様に環境変数として設定することができます。
以下は、ChatGPT APIを使用して返答を生成するコード例です。必要に応じて、Azure Functions上で動作するように適宜変更してください。
（コード略）
この例では、OpenAIのDavinciエンジンを使用して、与えられたメッセージに基づいて返答を生成しています。generate_response()関数は、OpenAI APIを使用して生成された返答を返します。message_text()関数では、与えられたメッセージに対してgenerate_response()を呼び出し、返答を生成し、それをLinebotの返答として返しています。
以上を参考にして、ChatGPT APIを使用してLinebotを作成することができると思います。
```
ここでChatGPTが提示してきたコードは、以下のようなコードでした。
環境変数の文脈を受け止め、その旨コメントまで入れてくれている上に、要望したものにかなり近いコードになっています。
ですが一点、今回2023年3月2日に新しく発表されたAPIは「openai.Completion.create()」ではなく「openai.ChatCompletion.create()」です。
何度か聞き直しても同じ結果だったので、2023年3月現在、ChatGPTの学習モデルは2021年9月までになっているため、この関数を知らないのだと思っています。
それにしても、APIの使い方を本人に聞けたり、かと思えば新しい方は本人も知らなかったり、この辺が面白いなぁ、ChatGPT。
```python:ChatGPTが提示したコード
pythonCopy code
import openai
from linebot.models import TextSendMessage

# OpenAI APIキーは環境変数として設定されている前提
openai.api_key = os.environ['OPENAI_API_KEY']

def generate_response(message):
    # OpenAI APIを使用して返答を生成する
    prompt = f"Q: {message}\nA:"
    response = openai.Completion.create(
        engine="davinci",
        prompt=prompt,
        max_tokens=1024,
        n=1,
        stop=None,
        temperature=0.7,
    )
    return response.choices[0].text.strip()

@handler.add(MessageEvent, message=TextMessage)
def message_text(event):
    message = event.message.text
    response = generate_response(message)
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=response)
    )

```
以下追加で気になったことを聞いています。
なお、この時はまだ使いたいのが「openai.ChatCompletion.create()」の方であることを認識おらず「openai.Completion.create()」について聞いています。

```tex:ChatGPTとの問答（わたし）
ありがとうございます。
お伺いしたいのですが、openai.Completion.createの中の以下のパラメータについてわかりやすく教えていただきたいです。
engine,prompt,max_tokens,n,stop,temperature,top_p,frequency_penalty
```
```tex:ChatGPTとの問答（ChatGPT）
もちろんです。OpenAI APIのドキュメントによると、openai.Completion.create()メソッドには以下のパラメータがあります：

- engine (必須): 使用する言語モデルの名前を指定します。
- prompt (必須): テキスト生成のために、言語モデルに与える初期のテキストを指定します。
- max_tokens: モデルに与えるトークンの最大数を指定します。
- n: 生成するテキストの数を指定します。
- stop: テキスト生成の終了条件を指定します。
- temperature: モデルの予測の多様性を制御するための温度パラメータを指定します。値が高いほど、より多様な出力が生成されます。
- top_p: モデルの予測の多様性を制御するための確率しきい値を指定します。
- frequency_penalty: モデルの生成結果に対して、同じトークンが繰り返されることを制限するためのペナルティを指定します。
- presence_penalty: モデルの生成結果に対して、出現頻度が低いトークンを選択することを制限するためのペナルティを指定します。
例えば、以下のようなパラメータを指定することができます：
（コード略）
この例では、"What is the meaning of life?"という初期テキストを使用して、Davinciエンジンを使用して1つのテキスト生成を行い、最大1024トークンを生成します。停止条件は指定せず、温度パラメータを0.5、確率しきい値を0.9、トークン重複ペナルティを0.5、出現頻度ペナルティを0.5と指定しています。
これらのパラメータは、モデルの生成結果を制御するためのものであり、目的に合わせて調整することができます。
```
```tex:ChatGPTとの問答（わたし）
「最大1024トークンを生成します」とありますが、このトークンとは何を指しているのか、小学生でもわかるようなわかりやすい説明をお願いします。
```
```tex:ChatGPTとの問答（ChatGPT）
トークンとは、文章を分割したときの最小単位を指します。文章を構成する単語や句読点、スペースなど、文字列を分割したときのそれぞれの単位のことです。例えば、「Hello, world!」という文章をトークンに分割すると、「Hello」「,」「world」「!」の4つのトークンがあります。
OpenAI APIが生成する文章は、モデルによって分析され、与えられた入力に対して適切な応答を返すために、トークン単位で生成されます。そのため、「max_tokens」パラメータは、生成される文章の最大トークン数を指定することで、APIに生成する文章の長さを制限することができます。
「最大1024トークンを生成します」とは、OpenAIのAPIが生成する文章の最大トークン数を1024トークンまでに制限するという意味です。つまり、生成される文章が長くなりすぎることを避けるための設定です。
```
わかりやすい！お見事です！