LINEWORKS BOT とAmazon Bedrock(Claude 3)を接続してみた

## はじめに
LINEWORKSのBOTを利用して、Claude 3と会話してみたい！と思った人向け
以前、LINEで同様なことをしてます
今回は、LINEWORKS(企業向けLINE)の記事です
個人向けLINEで、利用する場合は、下記をご覧ください

https://qiita.com/UniKawazoe/items/25a5587512c322e96f59

## LINEWORKSとLINEってどう違うの？

LINEWORKSとLINEの違いについて

https://line-works.com/blog/product/line-works/

> 「LINE WORKS」は、情報や予定を共有しあって活動する、組織・チームのためのコミュニケーションツール。
> 企業や企業内プロジェクト、特定の活動をする団体などに最適です。

LINE⇒個人向け
LINEWORKS⇒企業(組織・チーム)向け
の位置づけのようです

企業にフォーカスしているため、
アカウント管理やトークの監査機能など、組織として必要な機能が実装されています
また、掲示板機能なども実装されています
プランによっては、メール機能も使えるみたいです

https://line-works.com/pricing/

## LINEWORKS BOTの作成

では、さっそくLINEWORKS BOTを作成していきます

### 作業内容について
- LINEWORKS Developerの準備
    - BOT作成
    - API作成
- AWS側のリソース作成
    - Lambda Layerの作成(Cloud9)
    - Lambda / APIGateway作成
- APIGatewayとLINEWORKSBOTの連携
- LINEWORKSでBOT公開
- 動作確認

ざっくり、こんなかんじです

### 前提
- LINEWORKSのアカウントをすでに持っている
- BOT権限を管理者から付与されている
- AWSアカウントを所有している
- AWSの利用リージョンはバージニア(us-east-1)

## LINEWORKS Developerの準備

下記のサイトにログインし、BOTとAPIの設定を行います。
https://developers.worksmobile.com/jp/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747609/fa498011-b9f3-5ae1-de0c-d2fc74ab892a.png)

ログイン後、TOP画面に戻るため、consoleへ進みます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747609/ff73aa4c-fc43-498c-84a4-bc47660bfa65.png)

### API作成
APIを作成します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747609/d6954e00-c414-984e-d974-401701fccbb7.png)


アプリ名を入力し、同意して利用するを押します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747609/2f76f3e3-f2b5-84b7-9df0-fdd45f7f326a.png)

OAuth Scopesの設定を行います。
権限はbotのみでOKです
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747609/3a4b610c-da54-9950-a3fe-ab39ee927f7d.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747609/a65fa2bf-c9b8-591d-dfcb-d19fd481c20b.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747609/b75176c2-a0b3-54ba-3cfe-22a1e89ed1ad.png)


Client ID / Client Secretをコピーします
Service Accountの発行を行います

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747609/b2a72571-52d6-9773-a1b8-ef69e50b4d69.png)


Service Accountの発行ができると、アカウントIDが表示されるので、コピーします
Private Keyも利用しますので、発行し、Key情報がダウンロードされるので、保存します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747609/e05ebdaf-02a8-e71e-ef9e-0d7b49c2edf9.png)


APIの作成はこれで終わりです。

### BOT作成

Botから登録画面へ飛びます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747609/5eb0a290-d400-71e9-d4e1-6d706bfef648.png)

Bot名、説明、管理者を指定して作成(保存)します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747609/9809cf0e-ed9e-909a-b9dd-492eb655d368.png)

## AWS側のリソース作成
### Lambda Layerの作成(Cloud9)
LambdaでLINEWORKS BOTへPOSTする為、アクセストークンを取得する必要があります。

Service Account 認証 (JWT)

https://developers.worksmobile.com/jp/docs/auth-jwt

Lambda(Python)で、JWTを取り扱うにはLayerを追加する必要があります
Layerの作り方については、別記事を作成して公開していますので、
こちらをご覧ください

https://qiita.com/UniKawazoe/items/b46a7e14e126f60f712a

### Lambda / APIGateway作成
AWS上にLambdaとAPIGatewayを作成していきます

#### Lambda

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747609/71f3c18c-e47d-c1f3-c299-6770f48e03ef.png)

:::note warn
アーキテクチャーの指定に気を付けてください
Cloud9で作ったLayer環境と同じアーキテクチャーを指定しましょう
:::

```python
import json
import os
import jwt
import boto3
from datetime import datetime
import urllib
import requests
from cryptography.hazmat.primitives.serialization import load_pem_private_key

BASE_API_URL = "https://www.worksapis.com/v1.0"
BASE_AUTH_URL = "https://auth.worksmobile.com/oauth2/v2.0"

class AuthManager:
    def __init__(self):
        self.client_id, self.client_secret, self.service_account_id, self.private_key = self._get_env_vars()

    def _get_env_vars(self):
        client_id = os.environ.get("LW_API_20_CLIENT_ID")
        client_secret = os.environ.get("LW_API_20_CLIENT_SECRET")
        service_account_id = os.environ.get("LW_API_20_SERVICE_ACCOUNT_ID")
        private_key_str = os.environ['LW_API_20_PRIVATEKEY']
        private_key_str = f"-----BEGIN RSA PRIVATE KEY-----\n{private_key_str}\n-----END RSA PRIVATE KEY-----"
        privatekey = load_pem_private_key(private_key_str.encode(), password=None)
        return client_id, client_secret, service_account_id, privatekey

    def _generate_jwt(self):
        current_time = datetime.now().timestamp()
        iss = self.client_id
        sub = self.service_account_id
        iat = current_time
        exp = current_time + (60 * 60)  # 1時間

        jws = jwt.encode(
            {
                "iss": iss,
                "sub": sub,
                "iat": iat,
                "exp": exp
            }, self.private_key, algorithm="RS256")

        return jws

    def get_access_token(self, scope):
        jws = self._generate_jwt()
        url = f'{BASE_AUTH_URL}/token'
        headers = {'Content-Type': 'application/x-www-form-urlencoded'}
        params = {
            "assertion": jws,
            "grant_type": urllib.parse.quote("urn:ietf:params:oauth:grant-type:jwt-bearer"),
            "client_id": self.client_id,
            "client_secret": self.client_secret,
            "scope": scope,
        }
        r = requests.post(url=url, data=params, headers=headers)
        return json.loads(r.text)

    def refresh_access_token(self, refresh_token):
        url = f'{BASE_AUTH_URL}/token'
        headers = {'Content-Type': 'application/x-www-form-urlencoded'}
        params = {
            "refresh_token": refresh_token,
            "grant_type": "refresh_token",
            "client_id": self.client_id,
            "client_secret": self.client_secret,
        }
        r = requests.post(url=url, data=params, headers=headers)
        return json.loads(r.text)

class MessageSender:
    def __init__(self, bot_id, user_id):
        self.bot_id = bot_id
        self.user_id = user_id

    def send_message(self, content, access_token):
        url = f"{BASE_API_URL}/bots/{self.bot_id}/users/{self.user_id}/messages"
        headers = {
            'Content-Type': 'application/json',
            'Authorization': f"Bearer {access_token}"
        }
        form_data = json.dumps(content)
        r = requests.post(url=url, data=form_data, headers=headers)
        r.raise_for_status()

def lambda_handler(event, context):
    try:
        # メッセージを受信
        if event['type'] == 'message':
            # メッセージタイプがテキストの場合
            if event['content']['type'] == 'text':
                # 受信メッセージ
                messageText = event['content']['text']
                # 送信元ユーザーID
                user_id = event['source']['userId']

                bot_id = os.environ.get("LW_API_20_BOT_ID")
                auth_manager = AuthManager()
                message_sender = MessageSender(bot_id, user_id)
                scope = "bot"
                res = auth_manager.get_access_token(scope)
                access_token = res["access_token"]

                # 利用者がBOTを利用開始したタイミングの処理(postback: startが含まれているかチェック)
                if "postback" in event['content'] and event['content']['postback'] == "start":
                    response_text = "ClaudeBOTへ問い合わせありがとうございます\nなんでも聞いてくださいね！"

                    # メッセージを返信（Amazon BedRock Claude 3 Sonnetからの応答の中身を返す）
                    content = {
                        "content": {
                            "type": "text",
                            "text": response_text
                        }
                    }
                    message_sender.send_message(content, access_token)
                else:
                    bedrock_runtime = boto3.client(service_name='bedrock-runtime', region_name='us-east-1')
                    model_id = 'anthropic.claude-3-sonnet-20240229-v1:0'
                    max_tokens = 2000

                    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']

                    # メッセージを返信（Amazon BedRock Claude 3 Sonnetからの応答の中身を返す）
                    content = {
                        "content": {
                            "type": "text",
                            "text": response_text
                        }
                    }
                    message_sender.send_message(content, access_token)

    # エラーが起きた場合
    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.')}
```

#### Layerの追加

requestsとjwt用のlayerを追加します

arn:aws:lambda:us-east-1:770693421928:layer:Klayers-p312-requests:3
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747609/899df288-29fd-99b3-8157-5c370f779041.png)

jwtのlayer作成は、[こちら](https://qiita.com/drafts/cd39629398b48fad0a25/edit#aws%E5%81%B4%E3%81%AE%E3%83%AA%E3%82%BD%E3%83%BC%E3%82%B9%E4%BD%9C%E6%88%90)で完了しているはずなので、省略します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747609/765bd92c-3cdc-75e0-af8f-717e4dabf089.png)

#### 設定
一般設定
タイムアウト:10分

アクセス権限:CloudWatchLogs、Bedrockの権限
環境変数:
| 変数名 | 内容 |
|:-:|:-:|
|LW_API_20_BOT_ID | LINEWORKS Developerで登録したBOTID |
|LW_API_20_CLIENT_ID | LINEWORKS Developer APIのClient ID |
|LW_API_20_CLIENT_SECRET | LINEWORKS Developer APIのClient Secret|
|LW_API_20_PRIVATEKEY | LINEWORKS Developer<br>APIでダウンロードしたkeyの中身※ |
|LW_API_20_SERVICE_ACCOUNT_ID | LINEWORKS Developer APIのService Account ID |

:::note warn
※プライベートキーは、
先頭(-----BEGIN PRIVATE KEY-----)と末尾(-----END PRIVATE KEY-----)を除いて、
LW_API_20_PRIVATEKEYに入れてください。
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747609/9f099e98-af73-5fb9-e770-69fb76194dbe.png)


#### APIGateway

APIGateway(REST API)を作成します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747609/66f57faa-c0a8-8c4b-4b62-a728d1e48c70.png)

メソッドを追加します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747609/0f414722-1988-09e8-8f6c-ea62e459dfa5.png)

作成したLambdaを指定します
また、HTTPリクエストヘッダーとして、X-WORKS-Signatureを必須として、指定します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747609/14709907-3519-1146-f34d-7fd062f019eb.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747609/548ee802-5c53-bbc6-1264-ad8f7f7a97ca.png)

:::note warn
HTTPリクエストヘッダーの設定は、忘れずに設定ください
不要なアタックを遮断するための設定です。
:::

作成したAPIをデプロイします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747609/1e9f02b2-452a-9eb8-d813-c2dcfec488df.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747609/a8bba3af-ef02-d795-9e84-62fcf190fc8c.png)

デプロイが完了したら、URLをコピーしておきます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747609/b270f8f7-2252-79fb-25da-1113704f463c.png)

### APIGatewayとLINEWORKSBOTの連携

APIGateway作成後に、コピーしたURLをBOTと紐づけます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747609/9f62f205-ba0b-ca82-5f58-7b2a691e75ef.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747609/b4eb9481-8d2a-0e33-82cb-6f5155aca6f5.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747609/1c9a83f6-2898-6df3-9f66-d235afa4fbcf.png)

## LINEWORKSでBOT公開
管理者画面へ行き、BOTを登録します
https://admin.worksmobile.com/service/bot

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747609/1aa411c6-263b-8345-0136-cecf3ea29115.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747609/2f45535b-1a6c-bd89-1a73-eff5f95a57b5.png)

BOTがLINEWORKSに登録できた状態

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747609/39e4587b-5cec-96c7-19ab-e4648cc4a353.png)

公開処理を行います
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747609/f9d67b09-a063-6753-f2d2-7e0f166a82d7.png)

使用権限をすべて、公開設定をONに保存します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747609/14f64c76-996a-4c59-fdcd-708ed0bdbe1f.png)

:::note warn
公開設定をONにした場合、LINEWORKSユーザー全員へ通知が行きます
ご注意ください
:::

これで、BOTとして利用者に見えるようになりました
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747609/02f648a4-3ada-86b0-a761-7eb817c051cc.png)

## 動作確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747609/5ae6fc09-de68-1562-1824-1b9ac647b751.png)
きちんと応答してくれました！(ただ、中身は間違っている)

## 今回参考にさせていただいたサイト

https://qiita.com/mmclsntr/items/5ee3b7f2422b05bc256c

https://qiita.com/mmclsntr/items/0c118f40ffe7f36cd4eb

## CloudFormation
上記の内容を、CloudFormation化しました
- 前提
    - バージニア(us-east-1)で展開
    - JWTのLayerは手動で調整が必要
    - LINEWORKS側のBOT登録作業は別途必要
    - Amazon Bedrockから、Claude 3 Sonnetへリクエストが可能な状態

```yml
AWSTemplateFormatVersion: '2010-09-09'
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      -
        Label:
          default: SYSTEMNAME
        Parameters:
          - NameTag
        Label:
          default: LINEWORKS
        Parameters:
          - LINEWORKSCLIENTID
          - LINEWORKCLIENTSECRET
          - LINEWORKSERVICEACCOUNTID
          - LINEWORKPRIVATEKEY
          - LINEWORKSBOTID
Parameters:
  NameTag:
    Type: String
    Description: 'Common resource name'
    Default: 'LINEWORKSBOT'
  LINEWORKSBOTID:
    Type: String
    Description: 'ChannelAccessToken for LINEBOT'
    Default: '01234567'
  LINEWORKSCLIENTID:
    Type: String
    Description: 'Enter the client ID issued by LINEWORKS Developer'
    Default: 'none'
  LINEWORKCLIENTSECRET:
    Type: String
    Description: 'Enter the client secret issued by LINEWORKS Developer'
    NoEcho: true
  LINEWORKPRIVATEKEY:
    Type: String
    Description: 'Enter the private key issued by LINEWORKS Developer'
    NoEcho: true
  LINEWORKSERVICEACCOUNTID:
    Type: String
    Description: 'Enter the service account ID issued by LINEWORKS Developer'
    Default: 'XXXX.serviceaccount@XXX'

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 json
          import os
          import jwt
          import boto3
          from datetime import datetime
          import urllib
          import requests
          from cryptography.hazmat.primitives.serialization import load_pem_private_key

          BASE_API_URL = "https://www.worksapis.com/v1.0"
          BASE_AUTH_URL = "https://auth.worksmobile.com/oauth2/v2.0"

          class AuthManager:
              def __init__(self):
                  self.client_id, self.client_secret, self.service_account_id, self.private_key = self._get_env_vars()

              def _get_env_vars(self):
                  client_id = os.environ.get("LW_API_20_CLIENT_ID")
                  client_secret = os.environ.get("LW_API_20_CLIENT_SECRET")
                  service_account_id = os.environ.get("LW_API_20_SERVICE_ACCOUNT_ID")
                  private_key_str = os.environ['LW_API_20_PRIVATEKEY']
                  private_key_str = f"-----BEGIN RSA PRIVATE KEY-----\n{private_key_str}\n-----END RSA PRIVATE KEY-----"
                  privatekey = load_pem_private_key(private_key_str.encode(), password=None)
                  return client_id, client_secret, service_account_id, privatekey

              def _generate_jwt(self):
                  current_time = datetime.now().timestamp()
                  iss = self.client_id
                  sub = self.service_account_id
                  iat = current_time
                  exp = current_time + (60 * 60)  # 1時間

                  jws = jwt.encode(
                      {
                          "iss": iss,
                          "sub": sub,
                          "iat": iat,
                          "exp": exp
                      }, self.private_key, algorithm="RS256")

                  return jws

              def get_access_token(self, scope):
                  jws = self._generate_jwt()
                  url = f'{BASE_AUTH_URL}/token'
                  headers = {'Content-Type': 'application/x-www-form-urlencoded'}
                  params = {
                      "assertion": jws,
                      "grant_type": urllib.parse.quote("urn:ietf:params:oauth:grant-type:jwt-bearer"),
                      "client_id": self.client_id,
                      "client_secret": self.client_secret,
                      "scope": scope,
                  }
                  r = requests.post(url=url, data=params, headers=headers)
                  return json.loads(r.text)

              def refresh_access_token(self, refresh_token):
                  url = f'{BASE_AUTH_URL}/token'
                  headers = {'Content-Type': 'application/x-www-form-urlencoded'}
                  params = {
                      "refresh_token": refresh_token,
                      "grant_type": "refresh_token",
                      "client_id": self.client_id,
                      "client_secret": self.client_secret,
                  }
                  r = requests.post(url=url, data=params, headers=headers)
                  return json.loads(r.text)

          class MessageSender:
              def __init__(self, bot_id, user_id):
                  self.bot_id = bot_id
                  self.user_id = user_id

              def send_message(self, content, access_token):
                  url = f"{BASE_API_URL}/bots/{self.bot_id}/users/{self.user_id}/messages"
                  headers = {
                      'Content-Type': 'application/json',
                      'Authorization': f"Bearer {access_token}"
                  }
                  form_data = json.dumps(content)
                  r = requests.post(url=url, data=form_data, headers=headers)
                  r.raise_for_status()

          def lambda_handler(event, context):
              try:
                  # メッセージを受信
                  if event['type'] == 'message':
                      # メッセージタイプがテキストの場合
                      if event['content']['type'] == 'text':
                          # 受信メッセージ
                          messageText = event['content']['text']
                          # 送信元ユーザーID
                          user_id = event['source']['userId']

                          bot_id = os.environ.get("LW_API_20_BOT_ID")
                          auth_manager = AuthManager()
                          message_sender = MessageSender(bot_id, user_id)
                          scope = "bot"
                          res = auth_manager.get_access_token(scope)
                          access_token = res["access_token"]

                          # 利用者がBOTを利用開始したタイミングの処理(postback: startが含まれているかチェック)
                          if "postback" in event['content'] and event['content']['postback'] == "start":
                              response_text = "ClaudeBOTへ問い合わせありがとうございます\nなんでも聞いてくださいね！"

                              # メッセージを返信（Amazon BedRock Claude 3 Sonnetからの応答の中身を返す）
                              content = {
                                  "content": {
                                      "type": "text",
                                      "text": response_text
                                  }
                              }
                              message_sender.send_message(content, access_token)
                          else:
                              bedrock_runtime = boto3.client(service_name='bedrock-runtime', region_name='us-east-1')
                              model_id = 'anthropic.claude-3-sonnet-20240229-v1:0'
                              max_tokens = 2000

                              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']

                              # メッセージを返信（Amazon BedRock Claude 3 Sonnetからの応答の中身を返す）
                              content = {
                                  "content": {
                                      "type": "text",
                                      "text": response_text
                                  }
                              }
                              message_sender.send_message(content, access_token)

              # エラーが起きた場合
              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: 
        - x86_64
      Environment:
        Variables:
          LW_API_20_BOT_ID: !Ref LINEWORKSBOTID
          LW_API_20_CLIENT_ID: !Ref LINEWORKSCLIENTID
          LW_API_20_CLIENT_SECRET: !Ref LINEWORKCLIENTSECRET
          LW_API_20_PRIVATEKEY: !Ref LINEWORKPRIVATEKEY
          LW_API_20_SERVICE_ACCOUNT_ID: !Ref LINEWORKSERVICEACCOUNTID
      Layers:
        - arn:aws:lambda:us-east-1:770693421928:layer:Klayers-p312-requests:3
      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-WORKS-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
```



## さいごに

LINEWORKSのBOTを作成してみました
jwtの部分で大分苦戦しましたが、なんとか動いて良かったです

今回調べていく中、LINEWORKSの記事自体が少なく、見つけても根拠としている、
LINEWORKS Developer側のURLがリンク切れになっていることが多かった印象です
リファレンスの管理って大変だなと感じました

さて、次はRAG周りに挑戦しようと思います。
Claudeにもっとちゃんとした知識を元に回答してくれるようにしたいと思います

それでは、また！