Build an AI-powered Newsletter Generator with dust.tt and OpenAI

Imagine you are building an application for a language tutor. The tutor must send a newsletter with a short conversation in German and its translation every day to all students. The newsletter is a homework assignment, so the tutor doesn’t want to spend too much time preparing it. Let’s solve the problem with AI.

Building a newsletter generator with AI

What are we building? We will create a simple website with a text field where the tutor types the topic of the conversation. Our backend service will use dust.tt to generate the newsletter using OpenAI API, and the website will display a copy-paste-ready result.

Our application will look like this:

The application we are building
The application we are building

As you see, I’m not a UX designer ;)

We will need a few example newsletters because our AI prompt will use the few-shot in-context learning technique. We need the prompt for our AI model, dust.tt application to orchestrate interactions with the model and deploy an API. We will also need a website to display the result and a backend service to deal with CORS issues.

How to build a dust.tt application

After we log in to dust.tt for the first time, we will see an Apps page with several examples, the Providers tab and the API Keys tab. Let’s start with setting up access to OpenAI API.

Open the Providers tab and click the setup button next to the OpenAI row. You will need to provide the OpenAI API key. Save the changes and go back to the Apps screen. Now, we are ready to build the application.

Click the “New App” button. Dust.tt will display a setup page asking about the application name, description, and visibility level. Type whatever you want. You can keep the “private” visibility level. It won’t matter when we deploy the dust.tt API.

In the next screen, we can start building the application. The first thing we need is the input schema with at least one example (we can skip the examples, but it’s nice to have the option to test the application before you build the website). Click the “+ Block” button and choose the “input” element.

In the input setup page, we specify the schema by removing default fields, adding the fields we need, and defining their types. Below the schema, we can provide test examples. Write at least one example.

The input schema and examples for testing
The input schema and examples for testing

Return to the Specification tab and click the “+ Block” button again. This time, we chose a “data” block. In the data block, we will provide examples for in-context learning. Hence the schema should contain all of the fields from the input + the expected output. Below the schema, we write several examples.

Examples for in-context learning
Examples for in-context learning

Finally, we can add the LLM block. Return to the Specification tab, click “+ Block,” and choose the “llm” option. On the setup page, we choose the provider, one of the available models, the maximum number of tokens to use, and the prompt. In the prompt, we can refer to the datasets we created earlier. To test it, we can loop over the dataset of examples:

{% for e in EXAMPLES %}
TOPIC: {{e.topic}}
ANSWER:
{{e.answer}}
{% endfor %}

Of course, the prompt must also contain the instructions for GPT-3 and the user input. In my case, I wrote the following prompt:

You are a language coach for German. You write daily emails containing an example conversation (8-12 lines) and translation. Every email is a conversation about a given topic.

Examples:

{% for e in EXAMPLES %}
TOPIC: {{e.topic}}
ANSWER:
{{e.answer}}
{% endfor %}
TOPIC: {{INPUT.topic}}
ANSWER:
The LLM prompt
The LLM prompt

Testing a dust.tt application

Because we have provided value for the input dataset, we can click the “Run” button and see what the GPT-3 model will generate. Dust.tt displays the results below each block of the application. If we have multiple examples in the input dataset, dust.tt runs a separate workflow for each of them.

The test run results
The test run results

How to deploy a dust.tt application

We already have a useful application, but using it in the dust.tt UI is not convinient. Therefore, we will deploy the dust.tt application as an API endpoint and build our website on top of it.

Before we start, we need to create an API key for dust.tt. Go back to the main dashboard, and click the API Keys tab. Click the “Create new secret key” button and copy the key.

Now, you can return to the application page and click the “Deploy” button. Copy the displayed curl command. If you want, you can test the endpoint using curl.

Building a backend service for dust.tt

A web browser cannot send requests to the dust.tt API endpoint because of CORS. Therefore, we need to build a backend service. It will be our proxy between the website and the dust.tt API. We will implement a Flask service that accepts the topic of the conversation and passes the request to the dust.tt API, and returns the result.

First, we need to install the dependencies:

pip install flask requests

Now, we can implement the service. Our code needs to handle both a POST request with the conversation topic and the OPTIONS request for CORS. The code may look like this:

from flask import Flask, request, jsonify
import requests

app = Flask(__name__)

@app.route('/api', methods=['POST', 'OPTIONS'])
def api():
    if request.method == 'OPTIONS':
        headers = {
            'Access-Control-Allow-Origin': '*',
            'Access-Control-Allow-Methods': 'POST',
            'Access-Control-Allow-Headers': 'Content-Type,Authorization'
        }
        return ('', 204, headers)
    elif request.method == 'POST':
        topic = request.json['topic']
        data = {
            "specification_hash": "HERE IS THE HASH FROM THE DUST.TT CURL COMMAND",
            "config": {"MODEL": {"provider_id": "openai", "model_id": "text-davinci-003", "use_cache": True}},
            "blocking": True,
            "inputs": [{"topic": topic}]
        }
        headers = {'Authorization': 'Bearer YOUR DUST.TT API KEY', 'Content-Type': 'application/json'}
        response = requests.post('https://dust.tt/api/v1/apps/YOUR USERNAME/APPLICATION ID/runs', headers=headers, json=data)
        response_json = response.json()
        output = response_json['run']['results'][0][0]['value']['completion']['text']
        response = jsonify({'output': output})
        response.headers.add('Access-Control-Allow-Origin', '*')
        return response

if __name__ == '__main__':
    app.run()

Remember to modify the specification_hash, Authorization header, and URL. You can find the specification_hash in the curl command you copied from the dust.tt UI. The Authorization header should contain the dust.tt API key you created earlier (do not use the OpenAI key here!). You can copy the URL from the automatically generated curl command.

Building a website

In the frontend, I won’t use any frameworks. All I need is a single page with JavaScript, HTML, and CSS embedded in the HTML.

<!DOCTYPE html>
<html>
  <head>
    <title>Newsletter Writter</title>
    <style>
      input {
        display: block;
        margin: 0 auto;
        padding: 10px;
        border-radius: 10px;
        border: none;
        box-shadow: 0 0 5px gray;
        width: 60%;
      }
      h1 {
        text-align: center;
      }
      #response {
        display: block;
        margin: 0 auto;
        padding: 10px;
        width: 50%;
      }
      pre {
        white-space: pre-wrap;
        word-wrap: break-word;
      }
    </style>
  </head>
  <body>
    <h1>Newsletter Writer</h1>
    <input type="text" id="myInput" placeholder="What is the topic today?">
    <pre id="response"></pre>
    <script>
      const input = document.getElementById('myInput');
      const response = document.getElementById('response');
      input.addEventListener('keydown', (event) => {
        if (event.keyCode === 13) { // Enter key code
          event.preventDefault();
          const topic = input.value;
          response.textContent = 'Writing...';
          fetch('http://localhost:5000/api', { // Change the URL to your backend service
            method: 'POST',
            headers: {
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({ topic })
          })
          .then(response => response.json())
          .then(data => {
            let output = data.output.replace(/\n/g, '<br>');
            response.innerHTML = output + '<br>Bartosz Mikulski';
          })
          .catch(error => {
            response.textContent = 'An error occurred: ' + error.message;
          });
        }
      });
    </script>
  </body>
</html>

In the website code, we wait until the user types the topic and presses the Enter key. Then, we send a POST request to the backend service. The backend service sends a request to the dust.tt API endpoint and returns the result. We get the result, replace new lines with <br> tags, add the author name, and display it on the website.

Our website is ready! The language tutor can use it daily to generate emails, copy-paste them to the email client (or an email automation service), and send them to the students.


Do you need help building own AI-powered tool with dust.tt for your business?
You can hire me!

Older post

Get Started with ChatGPT API: A Step-by-Step Guide for Python Programmers (updated for OpenAI SDK version 1.1.1+)

A step-by-step tutorial on ChatGPT API (versions 1.1.1+) in Python. You'll also learn about prompt engineering, interactivity, optimizing API calls, and using parameters to get better results. Updated to cover the changes introduced after OpenAI DevDay 2023!

Newer post

AI-Powered Pair Programming: Enhance Your Web Development Skills with GPT-4 Assistance

Improve your coding skills and elevate your writing with GPT-4 as your AI-driven pair programming partner, guiding you through the process of building a web application that functions as a user-friendly reverse dictionary.