DEV Community

Cover image for Making a WhatsApp Crypto Chatbot with AWS Lex, CDK, CoinMarketCap, and Twilio
Davidđź’»
Davidđź’» Subscriber

Posted on

11 8 3 4 3

Making a WhatsApp Crypto Chatbot with AWS Lex, CDK, CoinMarketCap, and Twilio

What is a Chatbot?

A chatbot is a software or computer program that simulates human conversation or "chatter" through text or voice interactions.

Amazon Lex is a fully managed artificial intelligence (AI) service with advanced natural language models to design, build, test, and deploy conversational interfaces in applications.

This article aims to create an automated agent that will retrieve information about the current prices and trends of the crypto market.

Requirements

  • AWS account
  • NPM / CDK / Python
  • Coinmarketcap API key
  • Twilio account (optional)

Walkthrough

Let's create an account in CoinMarketCap to get our API Key. Feel free to choose the plan that adjusts to your use case, but the basic plan is good enough for this example.
Key
With our API Key, create a Parameter Store to store this value.

/dev/crypto-bot/COIN_MARKETCAP_API_KEY : <<your-api-key>>
Enter fullscreen mode Exit fullscreen mode

For this example, I will use a clear string for the API Key value, but it is recommended to secure the string with an encrypted AWS KMS key.
Parameter store
Now lets begin creating our project with cdk, after we configure our aws credentials, run:

cdk init sample-app --language python
Enter fullscreen mode Exit fullscreen mode

In our requirements.txt file lets add the following dependencies:

#requirements.txt
aws-cdk-lib==2.23.0
constructs>=10.0.0,<11.0.0
python-coinmarketcap==0.3
requests==2.27.1
boto3>=1.15.0
Enter fullscreen mode Exit fullscreen mode

python-coinmarketcap is a dependency that wraps Coinmarketcap API very nicely; feel free to use this dependency or to use a direct implementation which you can find here.
boto3 is a dependency that acts as the AWS SDK for Python; this will be used to access the Parameter Store previously created.

In our app.py file you can change the name of the project and the name of the stack, for this example I will name it cbot.

#app.py
import aws_cdk as cdk

from cbot.cbot_stack import CbotStack


app = cdk.App()
CbotStack(app, "cbot")

app.synth()
Enter fullscreen mode Exit fullscreen mode

Create a folder with the name of your project (cbot), and create or rename the name of the stack file (cbot_stack.py). In this stack, we will create a lambda, give that lambda access to our Parameter Store, and create a layer to add our dependencies.

from constructs import Construct
import os
import subprocess
from typing import List
from aws_cdk import (
Duration,
Stack,
aws_ssm as _ssm,
aws_lambda as _lambda,
)
class CbotStack(Stack):
def create_dependencies_layer(
self, requirements_path: str, output_dir: str
) -> _lambda.LayerVersion:
if not os.environ.get("SKIP_PIP"):
subprocess.check_call(
f"pip install -r {requirements_path} -t {output_dir}/python".split()
)
return _lambda.LayerVersion(
self, "HandlerDependencies", code=_lambda.Code.from_asset(output_dir)
)
def create_handler(
self,
layers: List[_lambda.LayerVersion],
):
crypto_search_lambda = _lambda.Function(
self,
"CryptoSearchHandler",
runtime=_lambda.Runtime.PYTHON_3_9,
handler="cbot_handler.handler",
code=_lambda.Code.from_asset("lambda"),
layers=layers,
function_name="cb-search-crypto-dev",
)
coinmarket_api_param = _ssm.StringParameter.from_string_parameter_attributes(
self, "Parameter", parameter_name="/dev/crypto-bot/COIN_MARKETCAP_API_KEY"
)
coinmarket_api_param.grant_read(crypto_search_lambda)
def __init__(
self,
scope: Construct,
construct_id: str,
requirements_path: str = "./requirements.txt",
layer_dir: str = "./.layer",
**kwargs,
) -> None:
super().__init__(scope, construct_id, **kwargs)
dependenciesLayer = self.create_dependencies_layer(
requirements_path=requirements_path, output_dir=layer_dir
)
self.create_handler(
layers=[dependenciesLayer],
)
view raw cbot_stack.py hosted with ❤ by GitHub

Create a folder named lambda, and create a cbot_handler.py. In this lambda, we will use the CoinMarketCap API, which will interact with our slot intent.

#cbot_handler.py
def search_crypto(intent_request):
    session_attributes = get_session_attributes(intent_request)
    slots = get_slots(intent_request)
    crypto = get_slot(intent_request, "crypto")
    api_key = ssm.get_parameter(Name="/dev/crypto-bot/COIN_MARKETCAP_API_KEY")
    cmc = coinmarketcapapi.CoinMarketCapAPI(api_key["Parameter"]["Value"])
    r = cmc.cryptocurrency_info(symbol=crypto.upper())
    text = repr(r.data[crypto]["description"])
    message = {"contentType": "PlainText", "content": text}
    fulfillment_state = "Fulfilled"
    return close(intent_request, session_attributes, fulfillment_state, message)
Enter fullscreen mode Exit fullscreen mode
import coinmarketcapapi
import boto3
ssm = boto3.client("ssm")
def get_slots(intent_request):
return intent_request["sessionState"]["intent"]["slots"]
def get_slot(intent_request, slotName):
slots = get_slots(intent_request)
if slots is not None and slotName in slots and slots[slotName] is not None:
return slots[slotName]["value"]["interpretedValue"]
else:
return None
def get_session_attributes(intent_request):
sessionState = intent_request["sessionState"]
if "sessionAttributes" in sessionState:
return sessionState["sessionAttributes"]
return {}
def elicit_intent(intent_request, session_attributes, message):
return {
"sessionState": {
"dialogAction": {"type": "ElicitIntent"},
"sessionAttributes": session_attributes,
},
"messages": [message] if message != None else None,
"requestAttributes": intent_request["requestAttributes"]
if "requestAttributes" in intent_request
else None,
}
def close(intent_request, session_attributes, fulfillment_state, message):
intent_request["sessionState"]["intent"]["state"] = fulfillment_state
return {
"sessionState": {
"sessionAttributes": session_attributes,
"dialogAction": {"type": "Close"},
"intent": intent_request["sessionState"]["intent"],
},
"messages": [message],
"sessionId": intent_request["sessionId"],
"requestAttributes": intent_request["requestAttributes"]
if "requestAttributes" in intent_request
else None,
}
def search_crypto(intent_request):
session_attributes = get_session_attributes(intent_request)
slots = get_slots(intent_request)
crypto = get_slot(intent_request, "crypto")
api_key = ssm.get_parameter(Name="/dev/crypto-bot/COIN_MARKETCAP_API_KEY")
cmc = coinmarketcapapi.CoinMarketCapAPI(api_key["Parameter"]["Value"])
r = cmc.cryptocurrency_info(symbol=crypto.upper())
text = repr(r.data[crypto]["description"])
message = {"contentType": "PlainText", "content": text}
fulfillment_state = "Fulfilled"
return close(intent_request, session_attributes, fulfillment_state, message)
def dispatch(intent_request):
intent_name = intent_request["sessionState"]["intent"]["name"]
response = None
return search_crypto(intent_request)
raise Exception('Intent with name ' + intent_name + ' not supported')
def handler(event, context):
response = dispatch(event)
return response
view raw cbot_handler.py hosted with ❤ by GitHub



Our project should look a bit like this:

Project structure

Run the cdk bootstrap command to prepare our account to handle cdk deployments.

cdk bootstrap
Enter fullscreen mode Exit fullscreen mode

Now we are ready to deploy our lambda into our account

cdk deploy
Enter fullscreen mode Exit fullscreen mode

Let's start with the final step, create a bot using the AWS Lex service

  • Creation method > Create a blank bot
  • Bot configuration > Name your bot
  • IAM permissions > Create a role with basic Amazon Lex permissions
  • Children’s Online Privacy Protection Act (COPPA) > No
  • Language > English (US)

Create a Welcome Intent; let's add some examples that our bot will learn and detect our intent

Sample Utterance 1
Add our bot response to the fulfillment section
Fulfilment 1
Test our bot, if we configure our intents properly it should response with our fulfillment text
Test Bot 1
Configure the text of the fallback intent, if something goes wrong this intent should catch this error and show a generic error message
Fallback
Save the intent, build and test. If you write something that we didn't add to the utterance examples, it should trigger our fallback intent.
Test Fallback
Create another intent named CryptoSearch; let's add some utterance examples to detect if a user wants to search about crypto
Crypto Intent
Add a slot. In this slot, we will capture the crypto symbol we would like to search with our CoinMarketCap API; you can define a specific type of data, but for simplicity, I will use the integrated type AMAZON.FirstName to capture the value inputed by the user. (Be sure to name it identical to our code in our lambda.)
Slot
Under Deployment in Aliases, let's add our lambda, be sure to create a production alias when you move your bot to production. For this example, we will use the default alias

Aliases

Language
Lambda Function
In the fulfillment section of our CryptoSearch intent, be sure to use our lambda function
Codehook
Save the intent, build and test our bot. Now we can retrieve information about any crypto that CoinMarketCap supports! We can even try fancy things like plotting, creating trends, or showing candle graphs.
CryptoBot
Now let's integrate our bot into social media. In this example, I will use Twillio to publish our bot into WhatsApp, create an account in Twilio and copy the SID and Authentication values.
In AWS, lex select Channels, select Twilio and create the channel integration.
Twillio
Copy the callback URL generated in our integration.
Callback
In the Twilio, dashboard, select message and set up a Twilio Sandbox for Whatsapp; here, we will copy our callback URL, which will connect the messages sent from WhatsApp to our bot in AWS Lex.
Twilio
And.. that's it! Now our bot works with WhatsApp and Lex.
WhatsApp

You can find the complete code here

References:

Sentry image

Hands-on debugging session: instrument, monitor, and fix

Join Lazar for a hands-on session where you’ll build it, break it, debug it, and fix it. You’ll set up Sentry, track errors, use Session Replay and Tracing, and leverage some good ol’ AI to find and fix issues fast.

RSVP here →

Top comments (0)

Image of Timescale

Timescale – the developer's data platform for modern apps, built on PostgreSQL

Timescale Cloud is PostgreSQL optimized for speed, scale, and performance. Over 3 million IoT, AI, crypto, and dev tool apps are powered by Timescale. Try it free today! No credit card required.

Try free