FastAPIで簡単なLINEボットを作成する

# はじめに
LINEを使ったチャットボットの作成方法について既に多くの情報は発信されていますが、
基本的な理解を深めるため、Messaging APIと無料のAIチャットAPIを利用し、まずは簡単なアプリを作成してみます。


# 環境、言語、ライブラリなど
LINE Developers、LINE Official Account Manager
Python FastAPI
A3RT Talk API（リクルート提供の無料API）
Ngrok
VS Code（開発環境）
Azure Contailner apps（デプロイ環境）

# 必要なアカウント
- LINE Developerアカウント
- A3RT APIキー
- Azureアカウント

# LINE Developersの設定
### LINEアカウントの作成
LINEボットを作成するためLINE Developersコンソールのアカウントを作成します。

https://developers.line.biz/ja/docs/line-developers-console/login-account/#log-in-to-line-developers-console

### Messaging API チャネルの作成

LINEボットを作成するためにはチャネルの作成が必要になります。
チャネルにはいくつか種類がありますが、LINE公式アカウントとしてチャットボットの機能を公開するのにMessaging APIチャネルの作成を行います。

※チャネルの作成前にプロバイダー（アプリ提供者）の作成を行います。

<img width="80%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3256746/466fc1c7-9b1f-90ab-6c1c-881079f3d7bb.png" >

※チャネル作成は、下記の「4. チャネルを作成する」を参考に作成してください。

https://developers.line.biz/ja/docs/messaging-api/getting-started/#using-console

※チャネルの種類については下記「チャネルを作成する」をご覧ください。

https://developers.line.biz/ja/docs/line-developers-console/overview/#creating-a-channel

### チャネルアクセストークンの発行

Messaging APIを利用してLINE上でのユーザーメッセージに外部のAPIなどから応答するため、チャネルアクセストークンの発行が必要になります。
チャネル作成後にMessaging API設定タブの一番下にある発行ボタンを押下します。

![スクリーンショット 2023-10-02 16.46.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3256746/d9c4705c-d30f-d2ce-66a0-84c51e073a4b.png)

発行した「チャネルアクセストークン」と、チャネル基本設定タブに記載のある「チャネルシークレット」は、後ほど作成するボットAPI側でMessaging API（line_bot_sdk）を利用する際に必要となります。

### LINE Official Account Managerでの設定

チャネルを作成するとチャネル名でのLINE公式アカウントが公開され、その運用管理画面である「LINE Official Account Manager」がLINE Developersコンソールと共通のアカウントで利用可能になります。

Messaging API設定タブ内の「LINE公式アカウント機能 - 応答メッセージ」の編集リンクを押すと、下記のLINE Official Account Manager画面に遷移するので、ここでメッセージの応答方式についての設定を行います。今回はWebhookの利用設定をONにし、応答メッセージをOFFに設定します。
これにより、LINEアカウント側で設定可能な応答メッセージ機能を無効にし、Webhook URLに接続したボットAPIからのメッセージ応答に切り替えることができます。

![スクリーンショット 2023-10-02 21.16.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3256746/f354b33a-850b-e3f6-dd30-43b9212bfb0f.png)


※ここではOFFとなっている「チャット」機能は、LINE Official Account Managerから手動でメッセージ送信できる機能ですが、2022年11月30日よりWebhookとの併用が可能になりました。

https://developers.line.biz/ja/news/2022/12/01/use-both-chats-and-webhooks/

※また「AI応答メッセージ」機能は2023年11月29日(水)で終了するようです。

https://manager.line.biz/announce/20224080?country=JP

### 友だち追加
Messaging API設定タブのQRコードをスマートフォンのLINEから読み込んで、今回作成した公式アカウントの友だち追加を行います。

# A3RT Talk APIの利用

今回はリクルートが提供しているAIの無料APIサービス「A3RT」Talk APIと利用します。
下記サイトより、手順に従ってAPI KEYを発行します。

https://a3rt.recruit.co.jp/product/talkAPI/
 
# ボットAPIの作成

今回はPython FastAPIを使って実装します。

https://fastapi.tiangolo.com/ja/

Messaging APIとの連携は、Python用のLINEボットSDKを利用して行います。

https://github.com/line/line-bot-sdk-python

```python:requirements.txt
fastapi
uvicorn[standard]
gunicorn
pydantic
python-dotenv
line-bot-sdk
```

インストールします。
```
pip install -r requirements.txt
```

コードは以下になります。

```python:main.py
import json, os, logging, requests
from dotenv import load_dotenv
from fastapi import FastAPI, BackgroundTasks, Header, HTTPException, Request
from pydantic import BaseModel, Field
from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import MessageEvent, TextSendMessage

app = FastAPI(
    title="LINEBOT-API-TALK-A3RT",
    description="LINEBOT-API-TALK-A3RT by FastAPI.",
    version="1.0",
)

# ApiRoot Health Check
@app.get("/")
def api_root():
    return {"message": "LINEBOT-API-TALK-A3RT Healthy!"}


load_dotenv()

# LINE Messaging APIの準備
line_bot_api = LineBotApi(os.environ["CHANNEL_ACCESS_TOKEN"])
handler = WebhookHandler(os.environ["CHANNEL_SECRET"])

# A3RT API
A3RT_TALKAPI_KEY = os.environ["A3RT_TALKAPI_KEY"]
A3RT_TALKAPI_URL = os.environ["A3RT_TALKAPI_URL"]


class Question(BaseModel):
    query: str = Field(description="メッセージ")


@app.post("/callback")
async def callback(
    request: Request,
    background_tasks: BackgroundTasks,
    x_line_signature=Header(None),
    summary="LINE Message APIからのコールバックです。"
):
    body = await request.body()
    try:
        background_tasks.add_task(
            handler.handle, body.decode("utf-8"), x_line_signature
        )
    except InvalidSignatureError:
        raise HTTPException(status_code=400, detail="Invalid signature")
    return "ok"


# LINE Messaging APIからのメッセージイベントを処理
@handler.add(MessageEvent)
def handle_message(event):
    if event.type != "message" or event.message.type != "text":
        return
    ai_message = talk(Question(query=event.message.text))
    line_bot_api.reply_message(event.reply_token, TextSendMessage(text=ai_message))

@app.post("/talk")
def talk(question: Question) -> str:
    """ A3RT Talk API
    https://a3rt.recruit.co.jp/product/talkAPI/
    """
    replay_message = requests.post(
        A3RT_TALKAPI_URL,
        {"apikey": A3RT_TALKAPI_KEY, "query": question.query},
        timeout=5,
    ).json()
    if replay_message["status"] != 0:
        if replay_message["message"] == "empty reply":
            return "ちょっとわかりません"
        else:
            return replay_message["message"]
    return replay_message["results"][0]["reply"]


# Run application
if __name__ == "__main__":
    app.run()

```


LineBotApi、WebhookHandlerのインスタンス生成時に必要なチャネルアクセストークン、チャネルシークレットを環境変数からセットします。
LINEからのリクエストの正当性の検証に使用されます。

```python
# LINE Messaging APIの準備
line_bot_api = LineBotApi(os.environ["CHANNEL_ACCESS_TOKEN"])
handler = WebhookHandler(os.environ["CHANNEL_SECRET"])
```


### callback
LINEでメッセージを送信すると、Messaging APIがWebhook URLに設定されたボットAPIのエンドポイントにPOSTリクエストを送信します。そのリクエストを非同期で処理するcallback関数として定義しています。

```python
@app.post("/callback")
async def callback(
    request: Request,
    background_tasks: BackgroundTasks,
    x_line_signature=Header(None)
):
    body = await request.body()
    try:
        background_tasks.add_task(
            handler.handle, body.decode("utf-8"), x_line_signature
        )
    except InvalidSignatureError:
        raise HTTPException(status_code=400, detail="Invalid signature")
    return "ok"

```
### handle_message

LINEから送信されたメッセージイベントを処理するhandle_message関数を定義しています。
メッセージイベントに含まれるテキストを、talk関数（A3RT TalkAPIに問い合わせ）に引き渡し、そのレスポンスを返信メッセージとしてMessaging APIへ返します。

```python
@handler.add(MessageEvent)
def handle_message(event):
    if event.type != "message" or event.message.type != "text":
        return
    ai_message = talk(Question(query=event.message.text))
    line_bot_api.reply_message(event.reply_token, TextSendMessage(text=ai_message))

```

発行しておいたAPIKEYを含め仕様に沿ってA3RT Talk APIを利用します。

```python
@app.post("/talk")
def talk(question: Question) -> str:
    """ A3RT Talk API
    https://a3rt.recruit.co.jp/product/talkAPI/
    """
    replay_message = requests.post(
        A3RT_TALKAPI_URL,
        {"apikey": A3RT_TALKAPI_KEY, "query": question.query},
        timeout=5,
    ).json()
    if replay_message["status"] != 0:
        if replay_message["message"] == "empty reply":
            return "ちょっとわかりません"
        else:
            return replay_message["message"]
    return replay_message["results"][0]["reply"]
```

コマンドラインからuvicornで起動します。

```python
uvicorn main:app --reload 
```
# ローカルでの動作確認

ローカルでの動作確認にはNgrokを利用すると便利とのこと。

https://qiita.com/poccariswet/items/24fac246f8760abfb51e

ローカルで立ち上げたポート番号に外部からアクセスできるので、ngrokに振り出されたURL（{ホスト}/callback）をLINE DevelopersのWebhookに設定することでLINE経由での動作確認が可能になります。

![スクリーンショット 2023-10-03 10.49.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3256746/abcc96ec-bf10-573a-d95e-126ae008ee81.png)

# Azure Contailner Appsへデプロイ

ホストする環境はどこでも良いのですが、今回はAzureアカウントを所有しているので、Dockerfileを作成してAzure上でコンテナアプリとしてデプロイします。

```Dockerfile.
FROM python:3.9

WORKDIR /

COPY requirements.txt .

RUN apt-get update

RUN pip install --no-cache-dir --upgrade -r requirements.txt

COPY . .

EXPOSE 8000

CMD ["gunicorn", "main:app","--timeout", "300"]
```

デプロイ方法についてはここでは割愛しますが、MSのドキュメントに記載の手順に沿って、コマンドパレットから比較的簡単に操作できるかと思います。

https://learn.microsoft.com/ja-jp/azure/container-apps/deploy-visual-studio-code

デプロイの完了後、LINE DevelopersのMessaging API設定タブのWebhook URLにコンテナアプリの概要ページに記載の「アプリケーションURL」（ボットAPIのエンドポイント）を設定します。

![スクリーンショット 2023-10-03 11.44.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3256746/b7c4bc14-f23f-f5e9-81ce-ed38508ee7e5.png)

## 使ってみる

あまり会話に手応えは感じられませんが、簡単なチャットのやりとりができました。

<img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3256746/53a96916-217e-5aa8-b0d3-4a85b5b9eee1.png" >

# おわりに

無料のAPIを使って簡単なLINEボットが作成できました。
LINEはチャットUIのフロントとして利用しやすいプラットフォームなので、リッチメニューやMessaging APIの各機能を組み合わせて、より便利なボット作成が可能だと思います。

次回は、LangChain + Azure OpenAI Serviceで、RAGの仕組みを使ったLINE FAQチャットボットの作成を行ってみたいと思います。今回は以上です。