自動応答Linebotを作ったメモ。流行りのOpenAIも使ってみた。
(機能追加次第追記・修正していく予定です)
サーバ:Raspberry Pi4 ModelB + Ubuntu 22.04.2
プログラム:python,flask
外部連携:ngrok,webhook
デフォルトではOpenAIで応答し、LINEメッセージの先頭に"twitter XXX" や "wiki XXX"を入れることでそれぞれのサービスからツイートや記事を検索して返してくれます。
「調べモノbot」みたいなもんですね。
※2023/8,"twitter"は"X"になり、searchAPIの使用が有料となってしまいました...
AIの返答で他サービスとの連携はできないので機能を追加するのはいいのですが、wikiは若干役割が被ります。が!wikipediaって記事内の関連用語からサーフィンするの楽しいですよね?アレはチャットではできないからいいんです!(強引)
①LINEBOT作成
メッセージを送る為のチャットボットを用意します。AI応答の設定に合わせてキャラクターを作ってもいいかも。アカウント作成方法は割愛。
LINE Developersの設定画面から
払い出されたアクセストークンとチャネルシークレットを確認しておきます。
チャネルアクセストークン
LINEDevelopers⇒MessagingAPI設定⇒チャネルアクセストークン
チャネルシークレット
LINEDevelopers⇒チャネル基本設定⇒チャネルシークレット
webhookの設定は手順の最後に行います。
②TwitterAPI登録
TwitterのAPIを使用するためにデベロッパー登録が必要です。
③OpenAI登録
OpenAIはとてもシンプルです。キー1つだけ。
現状OpenAIは従量課金(初回トライアル3ヶ月は$18分までは無料)のため、ここで課金制限をかけられます。
④YoutubeAPI登録
Youtube Search APIを使用。こちらもかなりシンプルです。
⑤必要なパッケージのインストール
プログラムを動かすサーバ(ここではローカルのUbuntu)で諸々必要なパッケージをインストールしておきます。
以下コマンド実行
apt-get install python-pip ##pythonパッケージ
pip install line-bot-sdk ##LINEBOT
pip install flask ##Flask
pip install openai ##OpenAIのAPI
pip install tweepy ##TwitterAPIライブラリ
pip install wikipedia ##WikipediaAPIライブラリ
pip install google-api-python-client ##GoogleAPIライブラリ
※本記事執筆時(2023/4)のバージョン
line-bot-sdk 2.3.0
Flask 2.2.2
openai 0.27.2
tweepy 4.13.0
wikipedia 1.4.0
google-api-python-client 2.102.0
pip list
コマンドで確認できる。
⑥環境変数を書いておく
コードの中にAPIキーベタ書きはNGなので、OSの環境変数として定義しておきましょう。
root@host01:~# cat ~/.bash_profile
export YOUR_CHANNEL_ACCESS_TOKEN=XXXXXX ##①で確認
export YOUR_CHANNEL_SECRET=XXXXXX ##①で確認
export TWITTER_API_KEY=XXXXXX ##②で確認
export TWITTER_API_SECRET_KEY=XXXXXX ##②で確認
export TWITTER_ACCESS_TOKEN=XXXXXX ##②で確認
export TWITTER_ACCESS_TOKEN_SECRET=XXXXXX ##②で確認
export OPENAI_ACCESS_TOKEN=XXXXXX ##③で確認
export YOUTUBE_API_KEY=XXXXXX ##④で確認
export FLASK_APP=/opt/line-bot/main.py ##応答プログラム置き場
root@host01:~#
⑦応答プログラムのコード実装
メインとなる応答プログラムのコードです。
twitter,wikiの文字列で引っ掛けてそれぞれのAPIを実行し、それ以外はすべてAIから応答させるようにします。
# FlaskとLinebotライブラリのインポート
from flask import Flask, request, abort
from linebot import (
LineBotApi, WebhookHandler
)
from linebot.exceptions import (
InvalidSignatureError
)
from linebot.models import (
MessageEvent, TextMessage, TextSendMessage, ImageSendMessage,
)
# OS,正規表現,Twitter,wikipedia,youtube,OpenAIライブラリのインポート
import os
import re
import tweepy
import wikipedia
import openai
from apiclient.discovery import build
app = Flask(__name__)
## APIキーの定義。OSの環境変数に入れておく
## LINEBOTとwebhook用キー定義
YOUR_CHANNEL_ACCESS_TOKEN = os.environ["YOUR_CHANNEL_ACCESS_TOKEN"]
YOUR_CHANNEL_SECRET = os.environ["YOUR_CHANNEL_SECRET"]
line_bot_api = LineBotApi(YOUR_CHANNEL_ACCESS_TOKEN)
handler = WebhookHandler(YOUR_CHANNEL_SECRET)
## Twitter用キー定義
TWITTER_API_KEY = os.environ["TWITTER_API_KEY"]
TWITTER_API_SECRET_KEY = os.environ["TWITTER_API_SECRET_KEY"]
TWITTER_ACCESS_TOKEN = os.environ["TWITTER_ACCESS_TOKEN"]
TWITTER_ACCESS_TOKEN_SECRET = os.environ["TWITTER_ACCESS_TOKEN_SECRET"]
auth = tweepy.OAuthHandler(TWITTER_API_KEY, TWITTER_API_SECRET_KEY)
auth.set_access_token(TWITTER_ACCESS_TOKEN, TWITTER_ACCESS_TOKEN_SECRET)
api = tweepy.API(auth)
## Wiki検索時の言語セット
wikipedia.set_lang("ja")
## Youtube用キー定義
YOUTUBE_API_KEY = os.environ["YOUTUBE_API_KEY"]
youtube = build('youtube', 'v3', developerKey=YOUTUBE_API_KEY)
## OpenAI用キー定義
openai.api_key = os.environ["OPENAI_ACCESS_TOKEN"]
## OpenAI回答用変数定義 #過去の会話を理解させる場合に使用
#assist1 = ""
## Linebotに送られたメッセージ処理
@app.route("/", methods=['POST'])
def callback():
# get X-Line-Signature header value
signature = request.headers['X-Line-Signature']
# get request body as text
body = request.get_data(as_text=True)
app.logger.info("Request body: " + body)
# handle webhook body
try:
handler.handle(body, signature)
except InvalidSignatureError:
print("Invalid signature. Please check your channel access token/channel secret.")
abort(400)
return 'OK'
## メッセージが "wikipedia XXX"または "wiki XXX"の場合、wikipedia記事の検索を実行する
## メッセージが "twitter XXX"または "ツイート XXX"の場合、tweet検索を実行して5件表示する
## メッセージが "youtube XXX または" "ユーチューブ XXX"の場合、動画検索を実行して5件表示する
## 上記以外の場合はChatGPTから応答する
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
send_message = event.message.text
match = re.search("(wiki|wikipedia) (.+)", send_message.lower())
match1 = re.search("(twitter|ツイート) (.+)", send_message.lower())
match2 = re.search("(youtube|ユーチューブ) (.+)", send_message.lower())
if match:
query = match.group(2)
try:
wikipedia_page = wikipedia.page(query)
# wikipedia.page()の処理で、ページ情報が取得できれば、以下のようにタイトル、リンク、サマリーが取得できる。
wikipedia_title = wikipedia_page.title
wikipedia_url = wikipedia_page.url
wikipedia_summary = wikipedia.summary(query)
reply_message = '【' + wikipedia_title + '】\n' + wikipedia_summary + '\n\n' + '【詳しくはこちら】\n' + wikipedia_url
# ページが見つからなかった場合
except wikipedia.exceptions.PageError:
reply_message = '【' + query + '】\nについての情報は見つかりませんでした。'
# 曖昧さ回避に引っかかった場合
except wikipedia.exceptions.DisambiguationError as e:
disambiguation_list = e.options
reply_message = '以下の候補から近いものを再入力してください。\n\n'
for word in disambiguation_list:
reply_message += '・' + word + '\n'
line_bot_api.reply_message(
event.reply_token,
TextSendMessage(reply_message)
)
elif match1:
query = match1.group(2)
tweets = api.search_tweets(q=query, count=5, result_type="mixed", include_entities=True)
reply_message = ""
for tweet in tweets:
reply_message += "=====================\n" + tweet.user.name + "\n@" + tweet.user.screen_name + "\n" + tweet.created_at.strftime('%Y/%m/%d %H:%M:%S') + "\n=====================\n" + tweet.text + "\n" + "https://twitter.com/" + tweet.user.screen_name + "/status/" + tweet.id_str + "\n\n\n\n"
if reply_message == "":
reply_message = "該当するツイートは見つかりませんでした。"
line_bot_api.reply_message(
event.reply_token,
TextSendMessage(text=reply_message)
)
elif match2:
query = match2.group(2)
search_response = youtube.search().list(
q=query,
type='video',
part='id,snippet',
maxResults=5 # 検索結果の最大数を指定
).execute()
reply_message = ""
for search_result in search_response.get('items', []):
video_title = search_result['snippet']['title']
video_url = "https://www.youtube.com/watch?v=" + search_result['id']['videoId']
reply_message += '【' + video_title + '】\n' + video_url + '\n\n'
if reply_message == "":
reply_message = "該当する動画は見つかりませんでした。"
line_bot_api.reply_message(
event.reply_token,
TextSendMessage(text=reply_message)
)
else:
# global assist1 # assist1をグローバル変数にする。#過去の会話を理解させる場合に使用
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
# {"role": "system", "content": "任意の設定を書く。例:占い師になって下さい。"}, # AIに人格を与える場合に使用
# {"role": "assistant", "content": assist1}, # 過去の会話を理解させる場合に使用
{"role": "user", "content": send_message},
]
)
# assist1 = response["choices"][0]["message"]["content"] # assist1を更新 過去の会話を理解させる場合に使用
line_bot_api.reply_message(
event.reply_token,
TextSendMessage(text=response["choices"][0]["message"]["content"]),
)
if __name__ == '__main__':
app.run()
assist1
を有効にすることでAI応答の回答を記憶させ"会話"ができるようにもできますが、
使用トークン数が増大するのと、複数人で使うことを考えると一問一答形式の方がいいと思います。
また、ここでは指定していませんが"system", "content":
のセクションで人格を与えることができるようです。しかも結構細かいめに↓
作成したLINEBOTのキャラクターに合わせた設定にすると面白いかもしれません。オラ悟空みたいなしゃべり方にしてぇぞ!!
⑧ngrokの用意
元々"heroku"というPaaSのサービスを利用するつもりでしたが、有料化してしまったのとラズパイが手に入ったのでローカルで"ngrok"を使うことにしました。
簡単なWebサーバーならサクッと立ち上げることが出来る優れモノですが、ローカルサーバが外部に公開されることになるので注意が必要です。
ダウンロード、コマンド配置
wget https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-arm.zip
unzip ngrok-stable-linux-arm.zip
mv ngrok /usr/local/bin/
バージョン確認
root@host01:~$ ngrok version
ngrok version 2.3.40
root@host01:~$
ngrokアカウント認証
ngrokを実行すると公開用のURLが払い出されますが、デフォルトだと2時間で無効になります。
永久に有効にするには以下からアカウント登録し、Authキーを使うことでプロセスが落ちるまではURLが変わりません。
※アカウントを登録したとしても、Freeプランだとngrokプロセスが落ちると再度URLが新規に払い出されます。
authキー取得
認証の設定
ngrok authtoken {取得したAuthToken}
ngrok.yml
という設定ファイルに、コマンドラインのオプションや通信の設定をtunnelsセクションに指定することで実行時のパラメータを省くことができます。
root@host01:~# cat .ngrok2/ngrok.yml
authtoken: {取得したAuthToken}
root@host01:~#
⑨プログラムをバックグラウンド実行/URL公開
flaskコマンドでプログラムを実行し、ngrokコマンドで外部公開。
バックグラウンドでサーバからログアウトしてもプロセスを実行し続けるようにします。
flaskとngrokのポートは合わせるようにしましょう。
root@host01:~#
root@host01:~# nohup flask run --reload --port 8080 &
[1] 1832
root@host01:~# nohup ngrok http 8080 &
[2] 1835
root@host01:~# curl http://localhost:4040/api/tunnels | grep ngrok
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 1028 100 1028 0 0 277k 0 --:--:-- --:--:-- --:--:-- 334k
{"tunnels":[{"name":"command_line","uri":"/api/tunnels/command_line","public_url":"https://xxxx.ngrok-free.app","proto":"https","config":{"addr":"http://localhost:8080","inspect":true},"metrics":{"conns":{"count":249,"gauge":0,"rate1":1.(略)
root@host01:~#
ngrokで公開されたURLはcurlでトンネル確認して行います。
"public_url"
の値がwebhookのURLとなり、これをLINE Developersのwebhookの設定に入れます。
ちなみに止めたい時はプロセスをkillすればOKです。
⑩Webhook連携
公開したURLをLINE Developersでwebhook設定します。
"検証"をクリックすると疎通テストができます。"成功"と出ればOK.
完成!
これで完成です。さっそくLINEメッセージを送ってみましょう!
wikipedia
Youtube
OpenAI
当然自宅ラズパイなのでこいつが落ちると動かなくなります(既読スルーされる)。bot自体はまだまだ色々できそうなのでやってみたいですね。
以上ぅ
参考