全体の流れ
イメージ的には下の添付画像を参考にしてください!
①まず、OpenAIのPlaygroundでAssistantsを作成しておきます。
Playgroundというのは直感的な画面でOpenAIのAPIが利用できる公式サービスです。
AssistantsというのはChatGPTに前提知識や特定の情報を与えることによって、その情報をもとにChatGPTに回答させる機能です。
②LINEの公式アカウントを作成します。
Bot的な感じで、Messaging APIで自動返信を行います。基本的に無料で使えます。ありがてぇ…!
③LINEとAssistantsを連携させます。
言語はPythonで、Lambda上で動作するようになっています。
このコード内では、LINEのAPIでユーザーからの入力メッセージを取得→OpenAIのAPIでAssistantsにメッセージを投げるという動作と、その逆で生成された回答をLINEのユーザーにフィードバックするという動作までを担当します。
ちなみに、ちょっとめんどくさくなってしまってLambdaの詳しい説明は省いています…すいません…
Assistantsの作成
✅ こちらにアクセスして、Assistantsを作成して前提知識を与えます。作成した時、左上に出てくる「asst_〇〇」がAssistants IDです。コード上からこのassistantsを呼び出す際に使います。
✅ 左下のFileから、ChatGPTに与えたい情報が入ったファイルをアップロードします。
その上のRetrievalをオンにすることによって、ChatGPTにこのファイルからの情報を参考にすることを許可できます。
✅ Instructionsには、回答の方向性を決める指示を入力しておきます。プロンプトで言うところの「あなたは教師です。小学生でもわかるように以下の文を要約してください。」みたいな部分です。
全て設定できたら保存を押して、Assistants IDをメモっておきます。
LINEアカウントの作成
普段使っている個人のLINEアカウントでコンソールにログインして、新しいチャンネルを作成します。この時、Messaging APIを選択してください。
設定する項目
作成したチャンネルの「Messaging API設定」タブで設定する項目があります。
✅ Webhook設定の項目から、Webhookの利用をオンにします。その上のURLはLambdaの関数URLを入力することになるので、Lambda関数を作成したらここに入力します。
これでLINE上でユーザーから送られてきたメッセージ情報がLambdaに送られるようになります。
✅ LINE公式アカウント機能を必要に応じて無効にします。友だち追加した時の「追加ありがとうございます!」みたいなメッセージを消したりできます。
メモしておく項目
APIを利用するために必要になるシークレットやトークンを取得します。
✅ チャネル基本設定タブから、チャネルシークレットをコピーします。
✅ Messaging API設定タブから、長期アクセストークンを発行してコピーします。
以上でLINE側の設定は完了です。続いてPythonでこれらを連携させます!
コード
こちらがPythonのコードです。先ほど取得したシークレットやトークンはLambdaの環境変数に設定しています。
Lambdaレイヤーはlinebotとopenaiを追加しています。
import json
import logging
import os
import sys
import time
import re
from openai import OpenAI
client = OpenAI()
from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError, LineBotApiError
from linebot.models import MessageEvent, TextMessage, TextSendMessage
# INFOレベル以上のログメッセージを拾うように設定
# Set to pick up log messages above the INFO level
logger = logging.getLogger()
logger.setLevel(logging.INFO)
# 環境変数からチャネルアクセストークンキー取得
# Obtain channel access token key from environment variable
CHANNEL_ACCESS_TOKEN = os.getenv('CHANNEL_ACCESS_TOKEN')
# 環境変数からチャネルシークレットキーを取得
# Get channel secret key from environment variable
CHANNEL_SECRET = os.getenv('CHANNEL_SECRET')
# それぞれ環境変数に登録されていないとエラー
# Error if not registered in the environment variable, respectively.
if CHANNEL_ACCESS_TOKEN is None:
logger.error(
'LINE_CHANNEL_ACCESS_TOKEN is not defined as environmental variables.')
sys.exit(1)
if CHANNEL_SECRET is None:
logger.error(
'LINE_CHANNEL_SECRET is not defined as environmental variables.')
sys.exit(1)
line_bot_api = LineBotApi(CHANNEL_ACCESS_TOKEN)
webhook_handler = WebhookHandler(CHANNEL_SECRET)
answer = ""
def chatgpt(line_message):
client = OpenAI(
api_key=os.getenv('OPENAI_API_KEY'),
)
# create a thread
thread = client.beta.threads.create()
my_assistant = client.beta.assistants.retrieve(os.getenv("ASSISTANT_ID"))
# create a message with the message from LINE and throw it to the thread
message = client.beta.threads.messages.create(
thread_id=thread.id,
role="user",
content=line_message,
)
# run the thread with assistants
run = client.beta.threads.runs.create(
thread_id=thread.id,
assistant_id=my_assistant.id,
instructions=my_assistant.instructions,
)
# run and wait until the status will be "completed"
while not run.status == "completed":
run = client.beta.threads.runs.retrieve(
thread_id=thread.id,
run_id=run.id
)
time.sleep(0.5)
# retrieve the message object from the assistants from the thread
messages = client.beta.threads.messages.list(
thread_id=thread.id
)
# retrieve the response message from the message object
assistant_messages = []
for thread_message in messages.data:
role = thread_message.role
if role == "assistant":
for content in thread_message.content:
content = content.text.value
assistant_messages.append({"role": role, "content": content})
print(assistant_messages[0]['content'])
return assistant_messages[0]['content']
# ユーザーからのメッセージを処理する
# Process messages from users
@webhook_handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
responce = chatgpt(event.message.text)
regex_pattern = r"【.*?】"
cleaned_message = re.sub(regex_pattern, '', responce)
# 応答トークンを使って回答を応答メッセージで送る
# Send your answer in a response message with a response token.
line_bot_api.reply_message(
event.reply_token, TextSendMessage(text=cleaned_message))
def lambda_handler(event, context):
# ヘッダーにx-line-signatureがあることを確認
# Ensure that the header has x-line-signature
if 'x-line-signature' in event['headers']:
signature = event['headers']['x-line-signature']
body = event['body']
# 受け取ったWebhookのJSON
# JSON of the received webhook
logger.info(body)
try:
webhook_handler.handle(body, signature)
except InvalidSignatureError:
# 署名を検証した結果がLINEプラットフォームからのWebhookでなければ400を返す
# If the result of verifying the signature is not a webhook from the LINE platform, 400 is returned.
return {
'statusCode': 400,
'body': json.dumps('Webhooks are accepted exclusively from the LINE Platform.')
}
except LineBotApiError as e:
# 応答メッセージを送る際LINEプラットフォームからエラーが返ってきた場合
# If an error is returned from the LINE platform when sending a response message
logger.error('Got exception from LINE Messaging API: %s\n' % e.message)
for m in e.error.details:
logger.error(' %s: %s' % (m.property, m.message))
return {
'statusCode': 200,
'body': json.dumps('Success!')
}
つまづいた点
openaiのライブラリですが、ローカルでは正常に動いているのにLambda上では以下のようなエラーが出て動きませんでした。
AWS Lambda Error on fast API: No module named ‘pydantic_core._pydantic_core’
これを解決してくれたのが、こちらのコミュニティでram3さんが作成してくれている、Lambdaレイヤー専用のカスタムライブラリを使用することで解決しました。
調べて出てきた方法をいくつか試して全然ダメだったところ、こちらを発見して救われました。もうram3さんに足向けて寝れませんね。どこにいらっしゃるのかご連絡待ってます。
コメントを残す