Build a Chatbot with GPT-3, Twilio Programmable Messaging, and JavaScript

Developer building a chatbot with gpt-3 on his computer
November 23, 2022
Written by
Reviewed by

In this article you’ll learn how to build a chatbot using GPT-3 text completion, Twilio Programmable Messaging, and JavaScript. This SMS based chatbot is going to be your new AI best friend! You’ll learn how to engineer your ideal bot by feeding a customized prompt to GPT-3.

Prerequisites

To get started with this tutorial, you’ll need the following:

Initialize your app

Open your terminal and navigate to a suitable location for your project. Create a project directory for your app called aifriend and then navigate inside it:

mkdir aifriend
cd aifriend

Then, run the following command to initialize a new Node.js project:

npm init -y

The next step is to install your dependencies by running the following command:

npm install express express-session dotenv twilio openai

express and express-session will be used to build your local server and store session data that you’ll need to keep track of the conversation. dotenv is used to access your environment variables in your app. twilio is used to integrate SMS functionality, and openai is the dependency needed to work with GPT-3.

Get your OpenAI API key

In order to build your AI friend, you’ll need an API key from OpenAI. How to retrieve this credential will be explained shortly. But first, you’ll need a protected place to store it.

Create a file called .env in the aifriend folder.

Copy the following code into your new .env file:

OPENAI_API_KEY=<your API key>

In the code above, <your API key> is a placeholder that will be replaced in the next step with your actual OpenAI key.

To get the key, navigate to OpenAI’s login/sign up page in your browser. If you already have an OpenAI account, go ahead and login. If you don’t, create one on this page. If you don’t already have an account, note that you might be prompted to validate your email address before continuing.

After you have an account and have logged in, navigate to your API keys dashboard which can be found by clicking your name in the top right corner and then selecting View API keys.

Click the button that says Create new secret key as shown in the following image, which will generate a new key that you can copy.

screenshot showing API key dashboard of Open AI"s website, with a red circle around the create new secret key button

Be sure to copy it and paste it into your .env file or another safe location right away—you won’t be able to see the complete key again.

Back in your .env file, replace <your API key> with your newly generated key from OpenAI. Save the file and close it.

Build the app

In your top-level aifriend directory, create a new file called index.js. This file is going to be where you add all the code for your app. Open this file in your favorite text editor.

Import and initialize dependencies

Import all your necessary dependencies (the ones you installed earlier in this tutorial) by pasting the following code into your new index.js file:

const express = require('express');
const session = require('express-session');
require('dotenv').config();
const { Configuration, OpenAIApi } = require('openai');
const {MessagingResponse} = require('twilio').twiml;

Beneath your imports, paste the following code to set up your Express app and initialize some of your dependencies:

const app = express();
const port = 3000;

app.use(express.urlencoded({ extended: true }));

app.use(session({
  secret: 'aifriend',
  resave: false,
  saveUninitialized: true,
  cookie: {}
}));

const configuration = new Configuration({
  apiKey: process.env.OPENAI_API_KEY,
});

const openai = new OpenAIApi(configuration);

Create your POST route

The next step is to create an Express route that will serve as an endpoint for POST requests to your app. The route will be reached whenever there is an incoming text message to your Twilio phone number; you’ll set up this webhook in a later step.

Below the existing code in index.js, paste the following route definition:


app.post('/bot', async (req, res) => {
  //app code goes here
});

Inside your new route, you’ll first need to initialize a Twilio MessagingResponse object by adding the highlighted line inside your post route:


app.post('/bot', async (req, res) => {
  const twiml = new MessagingResponse();
});

Everytime your Twilio phone number receives a message, the code inside this route will be run from the top. Without some mechanism to keep track of where the user is in their conversation with the bot, the user would end up starting the conversation over every time they tried to reply.

In this app, you’ll use sessions to maintain the user’s place in the conversation. Sessions are variables that can be persisted across the many interactions the user will have with the app. They will be managed by the express-session dependency you installed and initialized earlier in this tutorial.

All session variables can be accessed and updated via the req.session object. The four session variables that will be created and utilized in this app are as follows:

  • init, a boolean indicating whether or not the conversation has been started
  • personality, a string containing the adjectives the user provides to describe their ideal AI friend
  • name, a string containing the user’s name
  • prompt, a string containing the original prompt that will be fed to OpenAI

Check whether the user has started a conversation

Inside your route, below where you initialized the Twilio MessagingResponse object, paste the following code:

if (!req.session.init) {
  twiml.message('Welcome! Let\'s create your new AI friend! Reply with three comma separated adjectives describing your ideal friend.');
  req.session.init = true;
  res.type('text/xml').send(twiml.toString());
  return;
} 

This code checks to see if the init session variable does not exist. If it doesn’t, that means the user has not yet started a conversation with the app. In this case, the app replies with a generic message prompting the user to provide adjectives describing their ideal AI friend.

It also sets the init session variable to true so that this step will be skipped when the user replies again. It then sends the intro message back to the user and exits, making sure no code below this if-statement is run.

Check whether the user has provided adjectives

Below the code you just added, inside your route, paste the following code:

if (!req.session.personality) {
  req.session.personality = req.body.Body.toLowerCase();
  twiml.message('Great, and what is your name?');
  res.type('text/xml').send(twiml.toString());
  return;
}

Like in the previous step, this code checks to see whether the personality session variable is falsey. If the app makes it to this if-statement it means that the user has started a conversation, but hasn’t yet provided adjectives. The app won’t allow them to move on until they do.

The code inside this if-statement will set the personality session variable to the body of the incoming message and reply to the user with a prompt asking for their name. Then the app exits, so no further code will be run. Whenever the user replies again, this if-statement will be false and thus this step will be skipped (just like the first step).

Check whether the user has provided their name

Below the previous if-statement, paste the following code:

if (!req.session.name) {
  req.session.name = req.body.Body;
} 

In this code, the if-statement checks to see if the name session variable is falsey/undefined. If it is, that means the body of the incoming message must contain the user’s name, and it assigns this string to the req.session.name session variable. On future runs, this step will be skipped because the code inside the if-statement won’t run.

Notice how, unlike the two if-statements that came before it, this if-statement does not exit. That’s because once the user has reached this step, and the app has successfully recorded their name, you want the app to keep going.

Once the app has the name, it can create a prompt and feed it to OpenAI. It’s the completed text returned from OpenAI that will be sent in the reply to the user.

Create the prompt for OpenAI

Below the previous if-statement, paste the following:

if (!req.session.prompt) {
  req.session.prompt = `The following is a conversation between a human and their new AI best friend who is ${req.session.personality}. Human: Hello, my name is ${req.session.name}. AI:`;
} else {
  const reply = req.body.Body.trim();
  req.session.prompt += `Human: ${reply}. AI:`;
}

This code checks if the prompt session variable is falsey/undefined. If that’s the case, then it crafts an initial prompt that includes the user’s provided adjectives and their name and saves this prompt to the req.session.prompt session variable. Feel free to play around with this prompt, or add more back and forth replies to see what happens. Just to be sure to end the prompt with “AI: ”.

In the case that the prompt session variable already exists, that means the user is already engaged in conversation with the AI, and that the body of the incoming message is a reply to the AI. In this scenario, the incoming reply will be tacked onto the existing prompt.

Notice that in the code above, you’ve created a pattern of labeling the Human and AI responses. This pattern helps give context to the text completion algorithm so it knows what to say next.

At this point you now have a prompt (either the initial prompt, or the text of the entire conversation so far) to give to OpenAI for text completion:

Feed the prompt to OpenAI and reply to the user

Below the code above, still inside your route, paste the following code:

const response = await openai.createCompletion({
  model: "text-davinci-002",
  prompt: req.session.prompt,
  temperature: .9,
  max_tokens: 2048
});

const bot = response.data.choices[0].text.trim();
req.session.prompt += `${bot}`;
twiml.message(bot);

res.type('text/xml').send(twiml.toString());

This code sends the text completion request to OpenAI and passes along the model, prompt, temperature, and max_tokens. You can customize this request with other parameters, and play with the ones demonstrated here to see how this affects your text completion.

When the response is received it’s saved to a variable called bot which is cleaned and then sent as a reply to your user. The reply is also tacked on to the end of the existing prompt and saved in the req.session.prompt variable. This way the entire conversation up until that point is persisted and can be fed back once the user replies again.

Below your entire route, at the very bottom of the file, add the last few lines of code:

app.listen(port, () => {
  console.log(`AI friend app listening on port ${port}`)
});

Save your file. You can close it if you like, you’re done coding.

Create your webhook

Back in your terminal, make sure you’re still in your aifriend directory. Start up your local server by running the following command:

node index.js

Once you run this command, unless there’s an error in your code, you’ll see something like this in your terminal:

Screenshot showing terminal window with text "AI friend app listening on port 3000"

Leave this local server running in this terminal window, and open up a second terminal window. Navigate back to your aifriend directory. Once you’re there, run the following command to use ngrok to start a tunnel to your local server:

ngrok http 3000

This tunnel will expose your local computer to the internet, allowing you to create a webhook connection between your Twilio phone number and your app. Before you do this, make sure your OpenAI API key is saved in your .env file and not exposed in your code.

Once you run this command, ngrok will open the tunnel and provide you with a secure URL that you’ll use for your webhook. The URL is the second forwarding URL, preceded by https://, as shown circled in the following image:

Screenshot showing ngrok running in terminal with secure forwarding url circled in red

Copy this URL and paste it into a text editor or a note—anywhere where you can edit it—and add the text /bot to the end of it, so that your complete URL looks something like the following:

https://xxxx-xx-xxx-x-xxx.ngrok.io/bot

Hang on to this new URL, you’ll use it in just a second.

Open one more terminal window and paste the following command to create the webhook between your Twilio phone number and your app:

twilio phone-numbers:update <YOUR TWILIO PHONE NUMBER> \
  --sms-url <YOUR SECURE NGROK URL>

Before running the command be sure to replace <YOUR TWILIO PHONE NUMBER> with your Twilio phone number in E.164 format, including the + prefix. Also, replace <YOUR SECURE NGROK URL>` with the complete URL ending in /bot.

Run the command. At this point, your app is complete.

Test the app and chat with your new AI friend

From your personal phone, send a message to your Twilio phone number. You should receive an intro message that asks you for three adjectives describing your ideal AI friend. Then you’ll be asked for your name. At that time, the prompt created in your app will be sent to OpenAI, and a message from your new AI friend will be replied to you. Keep chatting, and have fun!

Screenshot showing SMS conversation with AI friend

I really hope you enjoy your new AI friend and that you had fun learning about OpenAI and GPT-3. If you did, try building a Discord GPT-3 chatbot with Node.js.

Ashley is a JavaScript Editor for the Twilio blog. To work with her and bring your technical stories to Twilio, find her at @ahl389 on Twitter. If you can’t find her there, she’s probably on a patio somewhere having a cup of coffee (or glass of wine, depending on the time).