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の価格は以下をご確認ください。
目次
- LINEチャネルの作成
- AWS環境の前準備
- ローカル環境の準備
- Dockerイメージの作成
- Lambda関数の作成
- DynamoDBの設定
- API Gatewayの設定
- 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ファイルを作成します。
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.')}
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" ]
boto3==1.28.63
langchain==0.0.313
line-bot-sdk==3.5.0
- Claude2高いよ!という方は
claude-v2
をclaude-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_TOKEN
とLINE_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周りはもう少し頭の良い実装が出来ましたね。
そのうち直すかも。 今のままの方が処理をそのまま書いているので可読性の観点からこのままとしておきます。