現在、方言を話すおしゃべり猫型ロボット「ミーア」を開発中。
前回、こちらの記事で、方言で会話できるLINE botを作成した。
「ミーアじゃっど」という鹿児島弁のみに対応したLINEアカウント。
今回は、これを拡張して、LINEのリッチメニューに、各方言のアイコンを用意して、ユーザーがアイコンをクリックしたら、その方言バージョンの会話に切り替えられるようにしたいと思う。
LINEユーザーがリッチメニューから選択した方言に基づいて、GPT APIのリクエストを動的に変更し、ユーザーに合わせた応答を生成する。
LINEリッチメニューの作成(CLI)
リッチメニューのテンプレートをダウンロード
LINEリッチメニュー機能の公式記事はこちら
https://developers.line.biz/ja/docs/messaging-api/using-rich-menus/
今回は、GUIではなくCLIで作成する方法を記載する。
LINE Official Account Managerのホームタブの左サイドバーから、リッチメニューを選択し、「リッチメニューを作成」をクリック
テンプレート選択画面が開くので、1つ選択する。
[デザインガイド]をクリックして、テンプレートをダウンロードする。
全タイプのテンプレートがダウンロードされる。今回は6方言(津軽弁・大阪弁・京都弁・広島弁・博多弁・鹿児島弁)に対応したいので、「richmenu-template-guide-01.png」を使用する。
画像サイズに関して3つある
- [Large] 高解像度ディスプレイを搭載する端末向けです。容量が大きいため読み込みに時間がかかることがあります。
- [Medium] 多くの一般的な端末向けです。
- [Small] 容量を抑えることができますが、高解像度ディスプレイを搭載する端末では画質が悪くなることがあります。
今回は、Mediumで作成する。横1200px、縦810px
リッチメニューの各アイコンに固有のポストバックアクションを割り当てる
例えば、京都弁のアイコンをクリックしたら、方言として京都弁を選んだよという情報を、LINEプラットフォームから、Cloud FunctionsのURLに送信されるようにする。
ポストバックアクションを使用し、dataプロパティに、”dialect=kyoto”を紐付ける。
displayTextプロパティ(任意)は、ユーザーがリッチメニューアイコンを押した時に、ユーザーのメッセージとしてLINEのトーク画面に表示されるテキスト。「京都弁に切り替えてね」などといったテキスト文言を表示するのもありだとは思うが、今回は一旦なしで。
https://developers.line.biz/ja/reference/messaging-api/#postback-action
今回は、下記のように作成。
channel access tokenには、今回のLINE Botのchannel access tokenを設定。
curl -v -X POST https://api.line.me/v2/bot/richmenu
-H 'Authorization: Bearer {channel access token}'
-H 'Content-Type: application/json'
-d
'{
"size": {
"width": 1200,
"height": 810
},
"selected": true,
"name": "ミーア方言のリッチメニュー",
"chatBarText": "タップして開く▼",
"areas": [
{
"bounds": {
"x": 0,
"y": 0,
"width": 400,
"height": 400
},
"action": {
"type": "postback",
"data": "dialect=kyoto"
}
},
{
"bounds": {
"x": 401,
"y": 0,
"width": 400,
"height": 400
},
"action": {
"type": "postback",
"data": "dialect=osaka"
}
},
{
"bounds": {
"x": 801,
"y": 0,
"width": 400,
"height": 400
},
"action": {
"type": "postback",
"data": "dialect=hakata"
}
},
{
"bounds": {
"x": 0,
"y": 406,
"width": 400,
"height": 400
},
"action": {
"type": "postback",
"data": "dialect=tsugaru"
}
},
{
"bounds": {
"x": 401,
"y": 406,
"width": 400,
"height": 400
},
"action": {
"type": "postback",
"data": "dialect=hiroshima"
}
},
{
"bounds": {
"x": 801,
"y": 406,
"width": 400,
"height": 400
},
"action": {
"type": "postback",
"data": "dialect=kagoshima"
}
}
]
}'
ターミナルで上記コマンドを叩くと、無事リッチメニューIDがレスポンスで返ってきた。
{
"richMenuId": "richmenu-88c05..."
}
LINEリッチメニュー画像の作成
次に、上記領域に該当する画像を作成していく。
画像要件は下記
- 画像フォーマット:JPEGまたはPNG
- 画像の幅サイズ:800ピクセル以上、2500ピクセル以下
- 画像の高さサイズ:250ピクセル以上
- 画像のアスペクト比(幅÷高さ):1.45以上
- 最大ファイルサイズ:1MB
今回は、Mediumサイズ(幅1200px・高さ810px)で作成。各方言のアイコンは、デザイナーの方がとても素敵で可愛らしいアイコンを用意してくださったので、当てはめる。
Figmaで下記のように用意。最初Canvaで作成していたが、デザインの配置も絡んでくると、Figmaの方が便利と感じた。auto layout機能があるので、今回のように幅と高さが決まった中に画像を配置する場合はボタンひとつで、うまく画像当てはめてくれる。
作成した画像を mia-line-richmenu.pngというファイル名でexport。
リッチメニューに画像をアップロードして添付
先ほど、取得したリッチメニューのIDを{richMenuId}
に入れて、channel access tokenと最後にアップロードする画像のファイル名を添えて、下記コマンドをターミナルで実行。
curl -v -X POST https://api-data.line.me/v2/bot/richmenu/{richMenuId}/content
-H "Authorization: Bearer {channel access token}"
-H "Content-Type: image/png"
-T mia-line-richmenu.png
デフォルトのリッチメニューを設定
これで準備完了したため、最後にリッチメニューを表示するための設定を下記コマンドで実行。今回はデフォルトのリッチメニューを設定。
デフォルトとは、ユーザー単位ではなく、LINE BOTを友だち登録したすべてのユーザーに一律に表示するLINEリッチメニューのこと。
https://developers.line.biz/ja/reference/messaging-api/#set-default-rich-menu
curl -v -X POST https://api.line.me/v2/bot/user/all/richmenu/{richMenuId} \
-H "Authorization: Bearer {channel access token}"
リッチメニューを開くと、下記のように無事表示された。
ちなみに、LINEリッチメニューを作成する際、今回のようにCLIを利用してMessaging APIで作成したリッチメニューは、LINE Official Account Manager(GUIツール)での取得や編集ができない。
同様に、LINE Official Account Managerで作成したリッチメニューは、Messaging APIを通じての編集や取得もできない。つまり、CLIで作成したリッチメニューはGUIツールでの設定には反映されない。
先ほど作成したLINEリッチメニューは、GUIでの編集画面には表示されない。なので、どちらでリッチメニューの作成がやりやすいかを決めてから行う必要がある。
Cloud Functionsで実行するpythonコードの修正
各方言のプロンプトを別ファイル管理にする
今は、pythonの実行関数に直接プロンプトも記載しているが、すでに一方言で長文になっていて可読性が低下しているのと、方言数を増やすのでさらに可読性が低下するので、別ファイル管理にする。
def generate_response(user_message: str, history: str) -> str:
if USE_HISTORY and history:
history_section = f"これまでの会話履歴:n{history}n"
else:
history_section = ""
prompt = f"""
【ミーアじゃっどのしゃべり方について指示】
{history_section}
ユーザーからのメッセージ: {user_message}
#概要
あなたは無邪気で元気な、鹿児島弁を話す「ミーアじゃっど」としてロールプレイを行います。ミーアじゃっどになりきってください。これからのチャットでは相手に何を言われても以下の制約条件などを厳密に守ってロールプレイを行ってください。
#ミーアじゃっどのキャラクター設定
おしゃべり猫型ロボット
鹿児島弁を話す。
ユーモアがあって、ボケたり、突っ込んだりしてくれる。
どの年代の人が話し相手でも、喜んでくれるようなキャラクター。
(指示文続く)
"""
# OpenAI APIを使用して応答を生成
chatgpt_response = client.chat.completions.create(
model="gpt-3.5-turbo-1106",
messages=[{"role": "system", "content": prompt},
{"role": "user", "content": user_message}],
temperature=0.2,
)
print(chatgpt_response)
print(type(chatgpt_response))
# 応答のテキスト部分を取得
return chatgpt_response.choices[0].message.content
各方言のテキストファイルをdialect_prompt
という名前のフォルダに保存し、このフォルダを実行ファイルと同階層に配置し、その中に各方言のプロンプトが入ったテキストファイルを作成。
作成した方言のプロンプトを読み込むload_prompt
関数を作成する。
generate_response
関数にdialect
引数を追加し、load_prompt
関数を呼び出して、方言に基づいたプロンプトを取得する。
取得したプロンプトをOpenAI APIへのリクエストに組み込む。
というように実行ファイルを修正する。
def load_prompt(dialect: str) -> str:
# 方言に対応するファイルパスを構築
filename = f"dialect_prompt/{dialect}_prompt.txt"
# ファイルからプロンプト文を読み込む
with open(filename, "r", encoding="utf-8") as file:
prompt = file.read()
return prompt
def generate_response(user_message: str, history: str, dialect: str = None) -> str:
if dialect:
# 方言に基づいたプロンプトを読み込む
prompt = load_prompt(dialect)
else:
# 通常のプロンプトを使用
prompt = "ここに通常のプロンプトを設定"
if USE_HISTORY and history:
history_section = f"これまでの会話履歴:n{history}n"
else:
history_section = ""
full_prompt = f"{prompt}n{history_section}nユーザーからのメッセージ: {user_message}"
# OpenAI APIを使用して応答を生成
chatgpt_response = client.chat.completions.create(
model="gpt-3.5-turbo-1106",
messages=[{"role": "system", "content": full_prompt},
{"role": "user", "content": user_message}],
temperature=0.2,
)
print(chatgpt_response)
print(type(chatgpt_response))
# 応答のテキスト部分を取得
return chatgpt_response.choices[0].message.content
PostbackEventハンドラを追加し、どの方言かを取得し、対応する方言プロンプトを呼び出す
今回リッチメニューを追加して、postbackを追加したので、lineのwebhookにpostbackイベントも送られてくるようになった。
ポストバックイベント(例:”data”: “dialect=kyoto”)に対応するために、以下のように関数を修正する
PostbackEvent
ハンドラの追加:
- **
MessageEvent
とTextMessage
のハンドラに加えて、PostbackEvent
**を処理するハンドラを追加する。
ポストバックデータの解析:
- **
PostbackEvent
ハンドラ内で、event.postback.data
**を解析して方言(例:”kyoto”)を抽出する。
方言に基づく応答生成:
- 抽出した方言を**
generate_response
**関数に渡して、方言特有の応答を生成する。
from linebot.models import PostbackEvent
@handler.add(PostbackEvent)
def handle_postback(event: PostbackEvent):
user_id = event.source.user_id
data = event.postback.data
# dataの解析(例: 'dialect=kyoto')
dialect = data.split('=')[1] if '=' in data else None
if USE_HISTORY:
previous_messages = get_previous_messages(user_id)
history = format_history(previous_messages)
else:
history = None
# 方言に基づいた応答の生成
chatgpt_response = generate_response("", history, dialect)
reply_to_user(event.reply_token, chatgpt_response)
if USE_HISTORY:
save_message_to_db(user_id, "POSTBACK: " + data, chatgpt_response)
この変更により、generate_response
関数は、方言を指定すると、その方言に基づいたプロンプトをload_prompt
関数で読み込んで応答を生成する。
PostbackEventの場合、ユーザーはテキストをLINEに入力していないので、generate_response
関数の第一引数は””になる。
また、第三引数のdialectには、下記LINEリッチメニューのpostback actionのdataプロパティで設定した”dialect=kyoto”の値から解析した kyoto の文字列が入る(他の方言の場合はその方言の文字列)。
ユーザーがリッチメニューのボタンを押さずにテキストメッセージを送信した場合は、今まで通り@handler.add(MessageEvent, message=TextMessage)
に定義されたhandle_message
関数が呼び出される。ポストバックイベント(主にリッチメニューのボタンクリックなど)に特化した@handler.add(PostbackEvent)
は、呼び出されない。
新たに作成したコードをCloud Functionsにデプロイ
作成したファイルを、gcloud CLIを使用して、Cloud Functionsにデプロイする。
- FUNCTION_NAME:Google Cloud Platformのコンソールに表示される関数の名前であり、デプロイ時に使用される識別名。下記の場合だとline-bot
--trigger-http
:関数のトリガータイプ(この場合はHTTP)-entry-point
オプション:実行ファイル内でリクエストを処理する実際のPython関数名--source
:関数のソースコードが含まれるローカルのディレクトリへのパス
gcloud functions deploy FUNCTION_NAME --runtime python312 --trigger-http --entry-point ENTRY_POINT --source /home/user/my_function
今回、自分の場合だと、下記
gcloud functions deploy line-bot --runtime python312 --trigger-http --entry-point my_entry_point --source mia-line-bot --region asia-northeast1
最初—regionフラグをつけなかったら、デフォルトでus regionになってしまい、新規作成扱いされて、当然ながら各環境変数を新規の場合に作成していなかったのでエラーになった。
リージョンの違い: 新規に作成された関数が異なるリージョンに配置された場合、これは別の関数として扱われる。例えば、us-central1
にデプロイした関数とasia-northeast1
にデプロイした関数は異なるものとして扱われる。
というわけで、 –region asia-northeast1 をつけて再デプロイしたところ、今度は、新規ではなくupdateとして認識され、無事デプロイできた。
完成と思ったら、、方言設定維持の考慮漏れあった
画像や動画で伝えるのが難しいが
- リッチメニューで方言プロンプトのうち、鹿児島弁だけプロンプト対応したので、鹿児島弁のアイコンを押すと、鹿児島弁で返答が来た
- 他の方言はまだプロンプトを作成していないので、当然ながらリッチメニューアイコンしてもレスポンス来なかった
- テキストのみ入力したら、方言バージョンではないので標準語で応答が来た
ことを確認できた。
あとは、各方言のプロンプトをどううまく作成するかという、プロンプトエンジニアリングの問題へと帰着された。
と思ったのだが、lineリッチメニューでユーザーが方言を切り替えたときに、最初のレスポンスは方言になるが、その後、ユーザーがテキスト入力をした場合に標準語に戻ってしまうじゃんということに気づいたので、修正する。
あと、今回リッチメニューアイコンをクリックした時に、テキストを表示しない設定にしたが、「京都弁に切り替えて」などテキストがあった方が、個人的にUX的に良いなと思ったので、そちらも修正する。
続きは、こちら
コメント