Writing Metal Lyrics with OpenAI's GPT-3 Engine, Twilio Functions, and JavaScript

September 10, 2020
Written by
Chris Hranj
Contributor
Opinions expressed by Twilio contributors are their own
Reviewed by
Diane Phan
Twilion

header - Writing Metal Lyrics with OpenAI's GPT-3 Engine, Twilio Functions, and JavaScript

As a musician/songwriter, one thing I find myself struggling with all too often is writing unique,  compelling song lyrics. Metal lyrics in particular are especially difficult for me. Writer's block is usually the issue, but even worse is when I come up with a great line to start but nothing to follow it.

Thanks to OpenAI and their GPT-3 API, this is no longer an issue. Now I can just write a few lines myself and let a trained model write the rest. Is it cheating? Probably. But is it fun? Definitely.

This post will demonstrate how to use Twilio Functions to assist in writing song lyrics by using OpenAI's GPT-3 engine and their Completion API. Here are a few examples of interesting lyrics I was able to generate using this tool:

> Cut out the heart of a living man, put it in a box made from the bones of your husband, and throw the box into the sea. The dead son will come back to life.

> Evil will be seen everywhere, laughing in the night, playing with our fears. The sorcerer, behind the black door. The master invites you to play the game.

> The Man in the Moon is my friend and I will not leave this world.

Rock star band singing and headbanging

Twilio's own Miguel Grinberg has recently published blog posts which go into detail about Open AI's GPT-3 Language Model as well as how to use it to write a chatbot using Python. His posts contain a lot of background on the OpenAI technologies and although they're not a prerequisite to this post they're definitely worth reading through.

Requirements

The following is required to complete this blog post:

  • A desktop/command line to call the Twilio Function via HTTP.
  • A Twilio account. If you are new to Twilio click here to create a free account now and receive $10 credit when you upgrade to a paid account. See the docs for more info on features and limitations of a free Twilio account.
  • An OpenAI API key. Request beta accesshere.
  • Optional: An SMS-enabled Twilio phone-number and a smartphone that can send and receive SMS so that the Twilio Function can be called from anywhere.

OpenAI Playground

The best way to get familiar with the OpenAI Completion API is via the OpenAI Playground. The playground loads your API keys automatically and includes a GUI to adjust all the parameters that the API accepts, as well as descriptions about the parameters.

I used the Playground to determine the parameter values used in the JavaScript code shown later in this post. Feel free to adjust these parameters to whatever values best suit you.

Open AI GPT-3 playground

The parameters used here were decided using trial and error, but their descriptions helped determine whether to set them relatively high or low.

  • Temperature controls the randomness of the completion. Setting it to a high value ensures that a completely different response is returned even if a prompt is reused. If an idempotent response is desired instead, set this to zero.
  • Frequency Penalty controls the likelihood of the same/similar lines being repeated in a completion. This is set pretty high to avoid repetition. However, it could make sense to lower this value if writing lyrics for a song's chorus.
  • Presence Penalty seems similar to Frequency Penalty, but is actually used to determine how likely the completion will jump to new topics. This is set somewhere in the middle so that the completion shares a similar theme overall but can still sometimes jump to new topics.

Set up a Twilio Function

Now that we've found the optimal parameters to send to the OpenAI API, the next step is to write the JavaScript code for the Twilio Function so that we can replicate this behaviour outside the OpenAPI Playground.

Twilio Functions are a great use case for a small app like this because they're cheap, secure, serverless, and scale automatically. Although the cURL command could do a lot of the same thing as the JavaScript code, using a Function has two main benefits:

  • Functions are easy to share as they can be publicly accessible (cURL would require the API key to be present)
  • It will be accessible via SMS, allowing the Function to be invoked without a laptop

Start by navigating to the Functions overview in the Twilio console. Click on the Create Service button and create a service with a relevant name such as openAI as seen in the screenshot below:

Name your service "openAI" text field

Inside this service, click on the Add + button and select Add Function. This creates a new endpoint which will be named as /lyrics and set to public. It needs to be public since the endpoint will be accessed via a cURL request and not only from Twilio.

Add new Twilio Functions "lyrics" endpoint

Next, navigate to the Settings at the bottom of the page and select the Environment Variables section of the service to add your OpenAI secret key. You can find the OpenAI secret key at the top of the Developer Quickstart.

Copy and paste the API secret key to the Value text field and name the Key as "OPENAI_SECRET_KEY", as seen in the screenshot below:

Set Twilio Functions OpenAI Secret Key

Last, navigate to Dependencies under Settings and add the latest version of the got package (11.5.2 is the latest version at the time of writing), which will be used in the Function to make an outgoing HTTP request.

Enter "got" into the Module text field and the version number "11.5.2" into the Version text field, seen below:

Add Twilio Functions Dependencies page

Handle webhooks

Now that the Function is configured, replace the code inside of the /lyrics endpoint function with the following:

const got = require('got');

exports.handler = function(context, event, callback) {
    let message = "";
    let twiml = new Twilio.twiml.MessagingResponse();
    const prompt = event["prompt"]

    got.post('https://api.openai.com/v1/engines/davinci/completions', {
            json: {
                "prompt": prompt,
                "max_tokens": 50,
                "temperature": 0.9,
                "top_p": 1,
                "presence_penalty": 0.4,
                "frequency_penalty": 0.75,
                "stop": "\n",
            },
            headers: {
                'Authorization': `Bearer ${context.OPENAI_SECRET_KEY}`,
            }
    })
    .json()
    .then(response => {
        message = response.choices[0].text
    })
    .catch(error => {
        console.log(error);
        message = "Oops! Something went wrong.";
    })
    .finally(() => {
        twiml.message(message);
        callback(null, twiml);
    });
};

The code above will execute the following:

  • Extracts a parameter from the event object called prompt.
  • Sends a POST request to the OpenAI endpoint with the necessary parameters (including prompt) and authorization headers.
  • Builds a TwiML <Message> response containing the parsed response from the OpenAI request.

Test the Twilio Function

Click Save and locate the Deploy All button at the bottom of the screen. In order to test the function, copy the full URL by clicking on the three dots on the /lyrics endpoint and select Copy URL.

Copy URL for Twilio Functions "lyrics" endpoint

Open a command line and enter a command like the one below, but replace the URL shown below with the URL copied from your own Function. Set the prompt to whatever completion string you want:

$ curl -X POST -d "prompt='hey how are you doing'" https://openai-XXXX.twil.io/lyrics
<?xml version="1.0" encoding="UTF-8"?><Response><Message> because I couldn't get a word out of my mouth. He instantly said to me 'it is okay man it happens' and then got up and walked and kept on playing football. I was just in complete awe of what he did (or didn</Message></Response>

The xmllint tool can be used to extract the OpenAI response directly from the Twiml returned by the Function, as seen here:

$ curl -X POST -d "prompt='hey how are you doing'" https://openai-XXXX.twil.io/lyrics | xmllint --format --xpath "string(//Message)" -
hello beautiful" . I know it's short , but it'll do for now. He looks like a gangster with those shades on, and he's a hottie ! .I was expecting this class to be boring , instead I met the

Bobby from King of the Hill making the "rocker" sign

Handle SMS with a Twilio Function

This section is optional but opens up an additional method of interaction with the Function. I often find inspiration for lyrics at times when I don't have access to my laptop, such as in a car or train during a commute. Luckily, it's easy to add SMS integration into a Function so that lyrics can be sent from my phone for a quick and inspirational response.

Start by purchasing a Twilio Phone Number with SMS capabilities. Then click on the Twilio phone number to see the configuration page. Scroll down and set the Messaging options to forward to your new Twilio Function.

Under "A MESSAGE COMES IN", select Function. For "Service", select the newly created Function named "OpenAI". For "Function Path", select "/lyrics".

Twilio Phone Numbers Messaging dashboard for Twilio Functions project

Next, go back to the Function code. Only one line in the Function needs to be updated to handle SMS. Delete the line shown in red and replace it with line shown in green below:

- const prompt = event["prompt"]
+ const prompt = event.Body !== undefined ? event.Body.trim() : event["prompt"]

This line now checks if there's a Body object nested in the Functions Event object. If Body is present, we can assume that this Function is being invoked by an incoming SMS message and set the prompt to that object. Otherwise, we set it to event["prompt"], which is extracted from the POST request body.

Save and deploy the Function again. It is now able to handle both POST requests as well as SMS messages.

Test it by sending an SMS to the Twilio number you just configured and set the message body to the lyrics you are working on. I tested it by sending some lyrics from the song "Bleed" by one of my favorite metal bands, Meshuggah. The result that returned was definitely pretty metal:

Example SMS conversation between user and OpenAI GPT-3

Conclusion

Rock on! Remember that this Function can be reached from anywhere that can make a POST request! Try integrating it into a Python or Node app for even more usability.

rockstar at a concert playing an electric guitar

OpenAI includes a number of other features that weren't mentioned in this post and are certainly worth exploring such as semantic search. Additionally, it's fun to play around with the different language models other than the default Davinci model and see how the responses change.

As always, thanks for reading! If you come across any issues or questions, feel free to reach out on Twitter or GitHub.