8
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LINEBOT でClaude 3 に問い合わせできるようにしてみる (AmazonBedrock)

Posted at

今回作成する成果物

  • AmazonBedrock (Claude 3 Sonnet)にLINEBOTから質問をできるようにする

参考サイト

構成図

LINEBOT×Claude_アーキテクチャー図.jpg

前提

  • AmazonBedrockでClaude 3 Sonnetが利用できるアカウントをすでに所有している
  • AWSのリージョンは、バージニア北部(us-east-1)を利用する
  • LINEアカウントを持っている状態

必要な工程

  • LINEBOTの作成
  • AWS側の準備(APIGateway、lambda作成)
  • APIGatewayとLINEBOTの繋ぎこみ
  • 動作確認

LINEBOTの作成

LINE Developerに接続し、LINEBOTを作成していきます。

右上の「Log in to Console」から進んでいきます。
image.png

LINEアカウントでログインを押します
image.png

ログインします。
image.png

ログイン後、ProvidersでCreateボタンを押します
image.png

BOTのNameを入れ、Createを押します
image.png

Message API チャンネルを作っていきます
image.png

必要事項を入力します
Company or owner's country or region: Japan
Channel name:LINEBOTの名前 (※7日間変更できません)
Channel description:チャンネルの説明
Category: 適切なもの
Subcategory: 適切なもの
Email address: 自分のメールアドレス

image.png

確認画面が出るので、OKで進める

登録が終わったら、
MessageAPIのタブの一番下にある、
Channel access token (long-lived)を発行し、メモしておく
image.png
image.png

Auto-reply messagesも有効となっているため、無効化する
LINEBOTを友達追加した際に、送信するメッセージや、受信したメッセージへ自動応答する設定
初期値だと、
「メッセージありがとうございます!申し訳ありませんが、このアカウントでは個別のお問い合わせを受け付けておりません。次の配信までお待ちください」
と応答してしまうため、OFFとする

image.png
image.png

lambdaの準備

ランタイム: Python 3.12
アーキテクチャ: arm64
IAMロールの権限: AmazonBedrockへの権限、CloudWatchLogsへの権限

import os
import json
from linebot import LineBotApi
from linebot.models import TextSendMessage
import boto3
import datetime

# 環境変数からLINE Botのチャネルアクセストークンを取得
LINE_CHANNEL_ACCESS_TOKEN = os.environ['LINE_CHANNEL_ACCESS_TOKEN']
# チャネルアクセストークンを使用して、LineBotApiのインスタンスを作成
LINE_BOT_API = LineBotApi(LINE_CHANNEL_ACCESS_TOKEN)

# Amazon BedRock Claude 3 Sonnetクライアントを作成
bedrock_runtime = boto3.client(service_name='bedrock-runtime', region_name='us-east-1')

def lambda_handler(event, context):
    try:
        # LINEからメッセージを受信
        if event['events'][0]['type'] == 'message':
            # メッセージタイプがテキストの場合
            if event['events'][0]['message']['type'] == 'text':
                # リプライ用トークン
                replyToken = event['events'][0]['replyToken']
                # 受信メッセージ
                messageText = event['events'][0]['message']['text']

                model_id='anthropic.claude-3-sonnet-20240229-v1:0'
                max_tokens=1000

                body = json.dumps(
                    {
                        "anthropic_version": "bedrock-2023-05-31",
                        "max_tokens": max_tokens,
                        "messages": [{"role": "user", "content": messageText}]
                    }  
                )

                # Amazon BedRock Claude 3 Sonnetに質問を送信
                response = bedrock_runtime.invoke_model(body=body, modelId=model_id)
                response_body = response['body'].read().decode('utf-8')
                
                # Amazon BedRockからの応答の中身のみを取り出す
                response_text = json.loads(response_body)['content'][0]['text']

                print("受信したメッセージ:", messageText)
                print("Amazon BedRockからの応答:", response_text)

                # メッセージを返信(Amazon BedRock Claude 3 Sonnetからの応答の中身を返す)
                LINE_BOT_API.reply_message(replyToken, TextSendMessage(text=response_text))

    # エラーが起きた場合
    except Exception as e:
        print(f"エラー: {e}")
        return {'statusCode': 500, 'body': json.dumps(f'Exception occurred: {str(e)}')}

    return {'statusCode': 200, 'body': json.dumps('Reply ended normally.')}

LINEBOTSDKのLayerも必要なので、
ローカル環境 または、Cloud9等で、layer用のZIPを作成する

pip install line-bot-sdk -t python

Layer設定が完了したら、lambdaの設定タブで以下を変更する
一般設定 タイムアウト:10分
環境変数 LINE_CHANNEL_ACCESS_TOKENにChannel access token(メモしたやつ)を入力

image.png

image.png

image.png

一旦、ここでlambdaの動作チェックを行います。
テストイベントに以下のjsonを入れ、実行します。
500エラーは発生しますが、ひとまずBedrockから回答が来れば、Lambdaの設定は問題ありません。

{
  "events": [
    {
      "type": "message",
      "message": {
        "type": "text",
        "text": "テストが見えていますか?"
      },
      "replyToken": "123456789",
      "mode": "active"
    }
  ]
}

image.png

APIGateway作成

APIGatewayを以下の要領で作成します。
プロトコル: REST
エンドポイント: Regional

image.png
image.png

メソッドの作成でPOSTを設定します。
その際、外部からたくさんのリクエストが来ると困るため、LINEからの通信のみ許可します。
HTTPリクエストヘッダーの設定で、x-line-signatureを追加し必須とします。
x-line-signatureが付与されていない通信が大量に来たとしても、APIGatewayで拒否できます。

image.png
image.png

画面が戻ったら、APIをデプロイを押します
image.png

初回デプロイなので、ステージがありません。
今回はprodとします。
※実際はなんでもOKです。のちほど出てくるWEBhookのURLになります。
image.png

ステージの画面に戻ったら、URLをメモしておきます。
image.png

ここまで来たら、AWS側の設定は完了です。
LINE developerに戻り、APIGatewayとLINEBOTの連携設定を行います。

LINEBOTとAPIGateway連携

Webhook settingsにメモしたAPIGatewayのURLを入力する。
入力後、Use webhookをONにする。
image.png

これで設定完了
あとは、QRコードを読み取って、Claudeとやり取りができるか確認します
image.png

動作確認

image.png
いろいろ間違ってますが、ひとまず応答してくれました

IaC

AWSTemplateFormatVersion: '2010-09-09'
Parameters:
  NameTag:
    Type: String
    Description: 'Common resource name'
    Default: 'LineBOT'
  LINEBOTChannelAccessToken:
    Type: String
    Description: 'ChannelAccessToken for LINEBOT'
    NoEcho: true

Resources:
  linebotFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub '${NameTag}-lambda'
      Runtime: python3.12
      Handler: index.lambda_handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        ZipFile: !Sub |
          import os
          import json
          from linebot import LineBotApi
          from linebot.models import TextSendMessage
          import boto3
          import datetime

          # 環境変数からLINE Botのチャネルアクセストークンを取得
          LINE_CHANNEL_ACCESS_TOKEN = os.environ['LINE_CHANNEL_ACCESS_TOKEN']
          # チャネルアクセストークンを使用して、LineBotApiのインスタンスを作成
          LINE_BOT_API = LineBotApi(LINE_CHANNEL_ACCESS_TOKEN)

          # Amazon BedRock Claude 3 Sonnetクライアントを作成
          bedrock_runtime = boto3.client(service_name='bedrock-runtime', region_name='us-east-1')

          def lambda_handler(event, context):
              try:
                  # LINEからメッセージを受信
                  if event['events'][0]['type'] == 'message':
                      # メッセージタイプがテキストの場合
                      if event['events'][0]['message']['type'] == 'text':
                          # リプライ用トークン
                          replyToken = event['events'][0]['replyToken']
                          # 受信メッセージ
                          messageText = event['events'][0]['message']['text']

                          model_id='anthropic.claude-3-sonnet-20240229-v1:0'
                          max_tokens=1000

                          body = json.dumps(
                              {
                                  "anthropic_version": "bedrock-2023-05-31",
                                  "max_tokens": max_tokens,
                                  "messages": [{"role": "user", "content": messageText}]
                              }  
                          )

                          # Amazon BedRock Claude 3 Sonnetに質問を送信
                          response = bedrock_runtime.invoke_model(body=body, modelId=model_id)
                          response_body = response['body'].read().decode('utf-8')

                          # Amazon BedRockからの応答の中身のみを取り出す
                          response_text = json.loads(response_body)['content'][0]['text']

                          print("受信したメッセージ:", messageText)
                          print("Amazon BedRockからの応答:", response_text)

                          # メッセージを返信(Amazon BedRock Claude 3 Sonnetからの応答の中身を返す)
                          LINE_BOT_API.reply_message(replyToken, TextSendMessage(text=response_text))

              # エラーが起きた場合
              except Exception as e:
                  print(f"エラー: {e}")
                  return {'statusCode': 500, 'body': json.dumps(f'Exception occurred: {str(e)}')}

              return {'statusCode': 200, 'body': json.dumps('Reply ended normally.')}
      MemorySize: 512
      Timeout: 600
      Architectures: 
        - arm64
      Environment:
        Variables:
          LINE_CHANNEL_ACCESS_TOKEN: !Ref LINEBOTChannelAccessToken
      Tags:
        - Key: Name
          Value: !Ref NameTag

  LambdaPermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !GetAtt linebotFunction.Arn
      Action: lambda:InvokeFunction
      Principal: apigateway.amazonaws.com
      SourceArn: !Sub arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ApiGateway}/*/POST/
    DependsOn:
      - ApiGateway

  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: AmazonBedrock
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action: bedrock:InvokeModel
                Resource: !Sub 'arn:aws:bedrock:${AWS::Region}::foundation-model/anthropic.claude-3-sonnet-20240229-v1:0'
      Tags:
        - Key: Name
          Value: !Ref NameTag

  ApiGateway:
    Type: AWS::ApiGateway::RestApi
    Properties:
      Name: !Sub '${NameTag}-APIGateway'
      EndpointConfiguration:
        Types:
          - REGIONAL
      Tags:
        - Key: Name
          Value: !Ref NameTag
    DependsOn:
      - linebotFunction

  ApiGatewayRootMethod:
    Type: AWS::ApiGateway::Method
    DependsOn:
      - LambdaPermission
    Properties:
      RestApiId: !Ref ApiGateway
      ResourceId: !GetAtt ApiGateway.RootResourceId
      HttpMethod: POST
      AuthorizationType: NONE
      RequestParameters:
        method.request.header.x-line-signature: true
      MethodResponses:
        - StatusCode: '200'
          ResponseModels:
            application/json: Empty
      Integration:
        Type: AWS
        IntegrationHttpMethod: POST
        Uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${linebotFunction.Arn}/invocations'
        PassthroughBehavior: WHEN_NO_MATCH
        ContentHandling: CONVERT_TO_TEXT
        TimeoutInMillis: 29000
        CacheNamespace: !GetAtt ApiGateway.RootResourceId
        IntegrationResponses:
          - StatusCode: '200'

  ApiGatewayDeployment:
    Type: AWS::ApiGateway::Deployment
    DependsOn:
      - ApiGatewayRootMethod
    Properties:
      RestApiId: !Ref ApiGateway

  ApiGatewayStage:
    Type: AWS::ApiGateway::Stage
    DependsOn:
      - ApiGatewayDeployment
    Properties:
      RestApiId: !Ref ApiGateway
      DeploymentId: !Ref ApiGatewayDeployment
      StageName: prod
      Tags:
        - Key: Name
          Value: !Ref NameTag

なお、上記のymlに、 途中で作成した、LINEBOTSDKのlayer設定 が必要です。
パラメータ LINEBOTChannelAccessTokenには、LINEDeveloper内で取得した、
Channel access token (long-lived)の内容を入力してください。

最後に

LINEBOT×AWS×Claude 3 Sonnetで、LINEBOTを作ってみました。
LINE経由で、問い合わせできるのは、楽だなと思って作ってみましたが、誰かの役に立てばうれしいです。

なお、Amazon Bedrock (Claude 3 Sonnet Edition)だと、モデル提供ベンダーやAWSに問い合わせの履歴を見られることもないですが、
LINEは、トークのやり取りについて、情報収集を行っているため、機微な情報をやり取りする場合は、注意が必要かもしれません。

実際に、BOTを作るタイミングで、以下の同意画面が出ています
image.png
利用用途に応じて、適切にご利用ください!

ではまた!

8
7
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
8
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?