[PYTHON] Make a LINE WORKS bot with Amazon Lex

This is the article on the 11th day of LINEWORKS Advent Calendar 2019.

There are many services that make it easy to create chatbots, but this time I tried to develop a LINE WORKS bot using "Amazon Lex" that I had been interested in for a long time.

What is Amazon Lex?

"Amazon Lex" is an AWS service that provides a conversational interface. It seems that Lex uses the same technology used in Alexa. Not only natural language processing but also voice recognition is included.

https://aws.amazon.com/jp/lex/

Similar services include DialogFlow and Azure Bot Service and IBM. Watson Assistant

This time, I created a dialogue flow with Amazon Lex and created a bot that can talk on LINE WORKS talk.

!!!Caution!!!

__ As of 12/11/2019, Amazon Lex only supports US English, not Japanese or Tokyo region. __ This time as well, it will be a chatbot that talks in English. (When will it be available in Japanese ...) Also, the region used is Oregon.

Constitution

lex lineworks (2).png

--A serverless configuration using AWS Lambda. --Receive callbacks from LINE WORKS via API Gateway. From there, it works with Amazon Lex for interactive processing. --LINE WORKS parameters are managed in the Systems Manager parameter store. --The LINE WORKS access token is updated by Lambda, which runs regularly.

Development environment

--Language: Python 3.7.1 --Deploy: Use Serverless Framework to deploy Lambda

Implementation

1. Create a bot with Amazon Lex

This time, I created a bot using a sample according to the official tutorial below.

Bot example: BookTrip --Amazon Lex https://docs.aws.amazon.com/ja_jp/lex/latest/dg/ex-book-trip.html

Simply put, it's a chatbot that reserves cars and hotels. I chose "Book Trip" from the sample and created it.

lex bot book trip

On such a screen, you can set the intent and the dialogue flow.

Impression that anyone who has used other chatbot creation services can use it immediately. Beginners felt that it would be difficult to set from scratch.

2. Create various keys and bots from the LINE WORKS Developer Console

Log in to the LINE WORKS Developer Console and create a key or this bot.

For details, please refer to this past article.

Let's implement LINE WORKS Talk Bot in Python ~ Part 1: API authentication ~ https://qiita.com/mmclsntr/items/1d0f520f1df5dffea24b

3. LINE WORKS bot app server creation

I configured it with Lambda and implemented the runtime in Python 3.7.

The following two Lambda functions

  1. __LINE WORKS Access token regular update __ --CloudWatch Event Scheduled event (once every half day)
  2. __LINE WORKS Chatbot __ --Get messages from LINE WORKS via API Gateway and work with Amazon Lex Bot.

Below is a sample code

lambda_function.py


import json
import jwt
import requests
import urllib
import boto3
import os
from datetime import datetime
from base64 import b64encode, b64decode
import hashlib
import hmac

from requests.structures import CaseInsensitiveDict

ssm = boto3.client('ssm')
lex = boto3.client('lex-runtime')

####################################
#Systems Manager parameter store#
####################################
def get_parameter(key):
    """
Get parameters from SSM parameter store
    """
    response = ssm.get_parameters(
        Names=[
            key
        ],
        WithDecryption=True
    )
    parameters = response["Parameters"]
    if len(parameters) > 0:
        return response['Parameters'][0]["Value"]
    else:
        return ""


def put_parameter(key, value):
    """
Store parameters in SSM parameter store
    """
    response = ssm.put_parameter(
        Name=key,
        Value=value,
        Type='SecureString',
        Overwrite=True
    )


##############
# Amazon Lex #
##############
def post_text_to_lex(text, user_id, bot_name, bot_alias):
    """
Send text to Amazon Lex&Get a reply
    """
    response = lex.post_text(
        botName=bot_name,
        botAlias=bot_alias,
        userId=user_id,
        inputText=text
    )

    return response["message"]


##################
# LINE WORKS API #
##################
def get_jwt(server_list_id, server_list_privatekey):
    """
Get JWT for LINE WORKS access token
    """
    current_time = datetime.now().timestamp()
    iss = server_list_id
    iat = current_time
    exp = current_time + (60 * 60) #1 hour

    secret = server_list_privatekey

    jwstoken = jwt.encode(
        {
            "iss": iss,
            "iat": iat,
            "exp": exp
        }, secret, algorithm="RS256")

    return jwstoken.decode('utf-8')


def get_server_token(api_id, jwttoken):
    """
Get LINE WORKS access token
    """
    url = 'https://authapi.worksmobile.com/b/{}/server/token'.format(api_id)

    headers = {
        'Content-Type' : 'application/x-www-form-urlencoded; charset=UTF-8'
    }

    params = {
        "grant_type" : urllib.parse.quote("urn:ietf:params:oauth:grant-type:jwt-bearer"),
        "assertion" : jwttoken
    }

    form_data = params

    r = requests.post(url=url, data=form_data, headers=headers)

    body = json.loads(r.text)
    access_token = body["access_token"]

    return access_token


def validate_request(body, signature, api_id):
    """
LINE WORKS request verification
    """
    #Use API ID as private key
    secretKey = api_id.encode()
    payload = body.encode()

    # HMAC-Encoded with SHA256 algorithm
    encoded_body = hmac.new(secretKey, payload, hashlib.sha256).digest()
    #BASE64 encoding
    encoded_b64_body = b64encode(encoded_body).decode()

    #Comparison
    return encoded_b64_body == signature


def send_message(content, api_id, botno, consumer_key, access_token, account_id):
    """
Send LINE WORKS message
    """
    url = 'https://apis.worksmobile.com/{}/message/sendMessage/v2'.format(api_id)

    headers = {
          'Content-Type' : 'application/json;charset=UTF-8',
          'consumerKey' : consumer_key,
          'Authorization' : "Bearer " + access_token
        }

    params = {
            "botNo" : int(botno),
            "accountId" : account_id,
            "content" : content
        }

    form_data = json.dumps(params)

    r = requests.post(url=url, data=form_data, headers=headers)

    if r.status_code == 200:
        return True

    return False

######################
#Lambda function handler#
######################
def update_token_handler(event, context):
    """
LINE WORKS access token periodic update Lambda handler function
    """
    #Get LINE WORKS parameters from the SSM parameter store
    api_id = get_parameter("lw_api_id")
    server_list_id = get_parameter("lw_server_list_id")
    server_list_privatekey = get_parameter("lw_server_list_private_key").replace("\\n", "\n")
    #Get JWT
    jwttoken = get_jwt(server_list_id, server_list_privatekey)

    #Get Server token
    access_token = get_server_token(api_id, jwttoken)

    #Set Access Token in Parameter Store
    put_parameter("lw_access_token", access_token)

    return


def chat_with_lex_handler(event, content):
    """
LINE WORKS chatbot Lambda handler function
    """
    botno = os.environ.get("BOTNO")
    lex_bot_name = os.environ.get("LEX_BOT_NAME")
    lex_bot_alias = os.environ.get("LEX_BOT_ALIAS")
    #Get LINE WORKS parameters from the SSM parameter store
    api_id = get_parameter("lw_api_id")
    consumer_key = get_parameter("lw_server_api_consumer_key")
    access_token = get_parameter("lw_access_token")

    event = CaseInsensitiveDict(event)
    headers = event["headers"]
    body = event["body"]

    #Request validation
    if not validate_request(body, headers.get("x-works-signature"), api_id):
        #Illegal request
        return

    #Perth to Json
    request = json.loads(body)

    #Get sending user
    account_id = request["source"]["accountId"]

    res_content = {
        "type" : "text",
        "text" : "Only text"
    }

    #Check the contents of the received message
    request_type = request["type"]
    ## Message
    if request_type == "message":
        content = request["content"]
        content_type = content["type"]
        ## Text
        if content_type == "text":
            text = content["text"]

            #Works with Amazon Lex
            reply_txt = post_text_to_lex(text, account_id.replace("@", "a"), lex_bot_name, lex_bot_alias)

            res_content = {
                "type" : "text",
                "text" : reply_txt
            }

    #Send
    rst = send_message(res_content, api_id, botno, consumer_key, access_token, account_id)

    res_body = {
        "code": 200,
        "message": "OK"
    }
    response = {
        "statusCode": 200,
        "headers": {
            "Content-Type": "application/json"
        },
        "body": json.dumps(res_body)
    }

    return response

Please also refer to this past article. Let's implement LINE WORKS Talk Bot in Python ~ Part 2: Chatbot implementation ~ https://qiita.com/mmclsntr/items/28ba6baaf23124a53663

Source code

https://github.com/mmclsntr/lineworks-bot-with-amazon-lex

Try to move

You can talk (in English) with a chatbot created with Lex on the LINE WORKS talk as follows. LINE WORKS Lex

Summary

I was able to run the chatbot created with Amazon Lex with LINE WORKS without any problems. I think that simple inquiries can be easily realized. I hope you can speak Japanese. ..

After that, I will tune various dialogue settings with Lex and play with it.

Recommended Posts

Make a LINE WORKS bot with Amazon Lex
Make a LINE BOT (chat)
[AWS] I made a reminder BOT with LINE WORKS
Make a morphological analysis bot loosely with LINE + Flask
[Super easy] Let's make a LINE BOT with Python.
Make a LINE bot with GoogleAppEngine / py. Simple naked version
[AWS] I made a reminder BOT with LINE WORKS (implementation)
Let's make a Twitter Bot with Python!
I made a stamp substitute bot with line
Make a Twitter trend bot with heroku + Python
Python beginners decided to make a LINE bot with Flask (Flask rough commentary)
Create a LINE BOT with Minette for Python
I tried to make "Sakurai-san" a LINE BOT with API Gateway + Lambda
I made a LINE Bot with Serverless Framework!
I made a household account book bot with LINE Bot
Make a parrot return LINE Bot on AWS Cloud9
I made a LINE BOT with Python and Heroku
[For play] Let's make Yubaba a LINE Bot (Python)
Let's make a Discord Bot.
Make a fortune with Python
Make a fire with kdeplot
How to make a Cisco Webex Teams BOT with Flask
I made a LINE BOT that returns parrots with Go
Create a machine learning app with ABEJA Platform + LINE Bot
Let's make a LINE bot using various services [ngrok edition]
[Python] Make your own LINE bot
Let's make a GUI with python.
Make a sound with Jupyter notebook
How to make a slack bot
Let's make a breakout with wxPython
Make a recommender system with python
Make a filter with a django template
[LINE bot] I'm a ranger! Part 2
Let's make a graph with python! !!
Let's make a supercomputer with xCAT
Make a model iterator with PySide
Make a nice graph with plotly
Create a LINE Bot in Django
[LINE Messaging API] Create a BOT that connects with someone with Python
Tweet the weather forecast with a bot
Building a Python3 environment with Amazon Linux2
The story of making a university 100 yen breakfast LINE bot with Python
Let's make a shiritori game with Python
Make a video player with PySimpleGUI + OpenCV
How to make an artificial intelligence LINE bot with Flask + LINE Messaging API
Display Disney's waiting time with LINE bot
Creating a LINE bot ~ Creating, deploying, and launching ~
Make a rare gacha simulator with Flask
Make a partially zoomed figure with matplotlib
Make a drawing quiz with kivy + PyTorch
Let's make a voice slowly with Python
[Python] [LINE Bot] Create a parrot return LINE Bot
Make a cascade classifier with google colaboratory
Let's make a simple language with PLY 1
Monitor web page updates with LINE BOT
Make a logic circuit with a perceptron (multilayer perceptron)
Make a Yes No Popup with Kivy
Make a wash-drying timer with a Raspberry Pi
Make a GIF animation with folder monitoring
Let's make a web framework with Python! (1)
Let's make a tic-tac-toe AI with Pylearn 2