11
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Amazon BedrockとLangChainとAWS LambdaでLINEチャットボットを作成する

Last updated at Posted at 2023-10-09

LLMとLangChainを好きに触れる環境が欲しかったので、Amazon Bedrockを使用してチャットボットを作成しました。LLMはClaude2を使用しています。
UIとしてはLINEチャネルを使用します(作らなくて済むので)。LINEから送られたメッセージをそのままAPI Gateway-Lambda-Bedrockと素通しして応答します。チャットの履歴はLangChainの機能でDynamoDBに格納します。

2023/10/11
DynamoDBのテーブル名に誤記があったので修正しました

2023/10/16
boto3とlangchainのバージョンを上げたところプログラム内でboto3を呼ぶ必要が無くなったのでプログラムとrequirements.txtを修正しました

Bedrockの価格は以下をご確認ください。

動作イメージ
https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3573242/b3c811dc-1e83-c9d7-865c-0f4015c2df82.jpeg https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3573242/b6083115-bc32-070a-1214-68ba589a93d1.jpeg

アーキテクチャ
line_architecture.gif

目次

  1. LINEチャネルの作成
  2. AWS環境の前準備
  3. ローカル環境の準備
  4. Dockerイメージの作成
  5. Lambda関数の作成
  6. DynamoDBの設定
  7. API Gatewayの設定
  8. LINEの設定

LINEチャネルの作成

下記を参考にLINEチャネルを作成します。MessagingAPIを使用します。

チャネルアクセストークンとチャネルシークレットを後で使用します。
Webhook URLは最後に設定します。

AWS環境の前準備

  • バージニア北部(us-east-1)リージョンのClaude2を使用可能にします。マネコンからRequestしてください

2023/10/9現在 LLMの選択肢が多いバージニア北部リージョンを使用します

  • Bedrockアクセス用のIAMポリシーの作成
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Statement1",
            "Effect": "Allow",
            "Action": "bedrock:*",
            "Resource": "*"
        }
    ]
}
  • IAMユーザーの作成とcredential(アクセスキー、シークレットアクセスキー)の発行
  • 同IAMユーザーに対するECRポリシー(AmazonEC2ContainerRegistryFullAccess)のアタッチ
  • 同IAMユーザーでバージニア北部(us-east-1)リージョンにECR(Amazon Elastic Container Registry)のプライベートリポジトリを作成(line_bedrock_chat)

ローカル環境の準備

  • Amazon CLIのインストール (上述のcredentialを設定)
  • Docker Desktopのインストール
  • 作業用フォルダの作成

Dockerイメージの作成

LangChainが巨大でLambdaの250MB制限を超える為、コンテナイメージを使用したLambda関数の作成を行う必要があります。

ローカル環境の作業用フォルダに以下の3ファイルを作成します。

line_bedrock_chat.py
import os
import json
from langchain.llms import Bedrock
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain
from langchain.memory import ConversationBufferMemory
from langchain.memory.chat_message_histories import DynamoDBChatMessageHistory
import base64
import hashlib
import hmac
from linebot import LineBotApi
from linebot.models import TextSendMessage

def handler(event, context):
    
    # LINE用 初期処理
    LINE_CHANNEL_ACCESS_TOKEN = os.environ['LINE_CHANNEL_ACCESS_TOKEN']
    CHANNEL_SECRET = os.environ['LINE_CHANNEL_SECRET']
    LINE_BOT_API = LineBotApi(LINE_CHANNEL_ACCESS_TOKEN)

    # LINEからの正規リクエスト以外は終了
    x_line_signature = event["headers"].get("x-line-signature") or event["headers"].get("X-Line-Signature")
    req_body_str = event["body"]
    hash = hmac.new(CHANNEL_SECRET.encode('utf-8'),
        req_body_str.encode('utf-8'), hashlib.sha256).digest()
    signature = base64.b64encode(hash)
    if signature != x_line_signature.encode(): raise Exception

    # リクエストの取得
    req_body = json.loads(event['body'])

    # チャット履歴をDynamoDB.SessionTableに格納する
    # SessionIdにはLINEのUserIdを使用する(個人単位でチャット履歴を管理する)
    session_id = req_body['events'][0]['source']['userId']
    message_history = DynamoDBChatMessageHistory(table_name="SessionTable", session_id=session_id)
    memory = ConversationBufferMemory(
        memory_key="chat_history", chat_memory=message_history, return_messages=True
    )

    # LINEからのメッセージを受信
    if req_body['events'][0]['type'] == 'message':
        if req_body['events'][0]['message']['type'] == 'text':
            query = req_body['events'][0]['message']['text']

            # promptの定義
            prompt = PromptTemplate(
                input_variables=["chat_history","Query"],
                template="""あなたは優秀なAIアシスタントです。名前はChatClaudeです。\\n\\n{chat_history}\\nHuman: {Query}\\nAssistant:"""
            )

            # LLMの定義
            llm = Bedrock(
                model_id='anthropic.claude-v2', # Claude2
                model_kwargs={'max_tokens_to_sample': 4096} # Outputの最大トークン
            )
            
            # LLMChainの定義
            llm_chain = LLMChain(memory=memory,prompt=prompt,llm=llm)

            # LLMChainの実行
            answer = llm_chain.predict(Query=query)

            # Bedrockの回答をLINEに返却
            replyToken = req_body['events'][0]['replyToken']
            LINE_BOT_API.reply_message(replyToken, TextSendMessage(text=answer))

    return {'statusCode': 200, 'body': json.dumps('Reply ended normally.')}
Dockerfile
FROM public.ecr.aws/lambda/python:3.11

# Copy requirements.txt
COPY requirements.txt ${LAMBDA_TASK_ROOT}

# Copy function code
COPY line_bedrock_chat.py ${LAMBDA_TASK_ROOT}

# Install the specified packages
RUN pip install -r requirements.txt

# Set the CMD to your handler (could also be done as a parameter override outside of the Dockerfile)
CMD [ "line_bedrock_chat.handler" ]  
requirements.txt
boto3==1.28.63
langchain==0.0.313
line-bot-sdk==3.5.0
  • Claude2高いよ!という方はclaude-v2claude-instant-v1に置き換えてください
  • ECRのコンテナイメージはPython3.11としました。Python 3.10で構築した時には古いBoto3を参照し続けるという事象がありました
  • boto3とlangchainはバージョンアップが速いので最新が出ている場合は最新で試してみてください
  • LLMに与えているpromptはtemplateが全てなのでカスタマイズしたい場合は変更してみてください

作成が終わったら該当フォルダに移動して、Docker Desktopが起動した状態で以下のコマンドを実行します。${ACCOUNTID}はご自身のAWSアカウントIDに置き換えてください。

aws ecr get-login-password | docker login --username AWS --password-stdin ${ACCOUNTID}.dkr.ecr.us-east-1.amazonaws.com
docker build -t line_bedrock_chat.py .
docker tag line_bedrock_chat.py:latest ${ACCOUNTID}.dkr.ecr.us-east-1.amazonaws.com/line_bedrock_chat:latest
docker push ${ACCOUNTID}.dkr.ecr.us-east-1.amazonaws.com/line_bedrock_chat:latest

Lambda関数の作成

バージニア北部リージョンにコンテナイメージでLambda関数の作成を行います。

コンテナイメージは先ほど作成したリポジトリのlatestを選択します。

作成が終わったら幾つか設定を行います。

  • 設定-一般設定-タイムアウトを2分に変更 ※回答が揃ってから返信する為、少し時間がかかります
  • 設定-環境変数にLINE_CHANNEL_ACCESS_TOKENLINE_CHANNEL_SECRETを設定。値は冒頭のLINE Developersから取得します。
  • 設定-アクセス権限からLambdaの実行ロールに対して以下を設定します。
    • 前準備で作成したBedrockのポリシーのアタッチ
    • AmazonDynamoDBFullAccessポリシーのアタッチ(フルじゃなくても良いですが)

これでLambdaの設定は完了です。

DynamoDBの設定

バージニア北部リージョンにテーブル名 SessionTable、パーテーションキー SessionId で作成します。
このテーブルにチャット内容がユーザー単位で全て保存されますので、削除は適宜ご対応ください。

API Gatewayの設定

バージニア北部リージョンで

  • APIの作成 → REST API → 構築 → API名に任意の値を入力してAPIの作成
  • 以下のように設定して保存(ポップアップはOKしてください)

Lambdaプロキシ統合の使用は重要です。リクエストのRAWデータをそのままProxyしないとLINEからのリクエストの正当性確認(LINEの署名の確認)がNGになります

  • APIのデプロイ
  • URLをコピーしておきます。

LINEの設定

  • LINE DeveloperのWebhook URLにAPI GatewayのURLを設定します
  • QRコードからご自身のLINEにチャネル登録します

これで冒頭のように使用可能になります。

最後に

本当はLangChainのAgent機能を使用したWeb検索等を行いたかったのですが、Claude2ではどうしてもpromptが正しく生成されない(場合がある)ので一旦中断しました。LangChainのUpdateかBedrockAgentに期待しています。

書いた後にこちらを読んで気付きましたがLINE周りはもう少し頭の良い実装が出来ましたね。
そのうち直すかも。 今のままの方が処理をそのまま書いているので可読性の観点からこのままとしておきます。

11
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?