Use OpenAI’s APIs in Flutter without Plugins

Scott Hatfield
11 min readJul 30, 2022

--

Introduction: OpenAI 🤝 Flutter

One of the most transformative technologies in development today, and one that most would agree will play an increasingly important role in the years to come, is machine learning or, colloquially, “artificial intelligence.” Machine learning is an expansive technology with many, many applications. One particular domain within the machine learning field is natural language processing (NLP) and one of the companies at the forefront of this technology is OpenAI. OpenAI is best known for its GPT-3 language model which is designed to produce human-like text in a variety of scenarios.

This is an example of the GPT-3 “completions” endpoint, performed in the OpenAI Playground.

In this article, we will be learning how to use OpenAI’s natural language processing APIs in your Flutter apps without relying on third-party libraries or any dependencies beyond plugins developed by the Flutter and Dart teams. This will enable you to use the powerful machine learning tools provided by OpenAI in your Flutter web, mobile, and desktop apps.

With Flutter, we can develop apps for nearly app devices incorporating OpenAI’s ML technologies.

So, whether you are building a smart virtual assistant, a content moderation system, a text prediction input, a sentiment analysis service, or, as is the case with the example we will cover in this tutorial, a useless work of satire, by the end of this article you will at least be able to send requests to the OpenAI’s machine learning systems and get responses back. What you do with those responses is up to you.

Let’s get started.

Example Project: Partly Windy 🌦️

Actually, before we get started, let’s get acquainted with the example Flutter app we will be using in this article.

We will be making a Flutter app that leverages OpenAI text completion to give us the weather forecast. No more messing with rain gages and those spinning wind speed gadgets, we are going to get our weather forecasts right from the burning heart of computer science achievement, machine learning. The app is named “Partly Windy.”

This app is a satirical comment on the tendency for current machine learning technologies to answer seemingly simple questions with wildly inaccurate, and often nonsensical, responses. However, this app will give us the opportunity to explore making requests and receiving responses from OpenAI’s APIs.

In this article, we will be focusing in specifically on the systems used to implement an OpenAI API layer. You can check out some of the other articles about Partly Windy for discussions of other parts of this app.

Alright, now let’s get started.

OpenAI API Integration Overview 📁

Before diving into the code, it will probably be useful to get a broad overview of the architecture we will be using to integrate OpenAI API calls into our Flutter app, like having a picture on the puzzle box before starting to assemble the pieces. Throughout the remainder of this article, we will work on building four main files:

  • We will create a simple file to store your OpenAI API key used to authenticate API calls to OpenAI endpoints. We will make sure to exclude this file from version control to protect the API key.
  • We will create a class to represent API requests sent to OpenAI endpoints.
  • We will create a class to represent API responses received from OpenAI endpoints.
  • We will create a class containing methods and information used to interact with the OpenAI APIs. This is the class that the rest of the app will use to interact with OpenAI’s ML models.
We will create classes to represent OpenAI API requests, responses, and endpoints, plus a file used to store your API key.

In terms of overall project organization, there is no right answer. In the Partly Windy example project, all these files are stored in the same directory, located at lib/openai.

For this project, all the files related to the OpenAI API integration are stored in lib/openai.

Step 1: Create an OpenAI Account 👋

The first exciting step in building a Flutter app powered by OpenAI’s cutting edge machine learning technologies… is to take care of a bit of paperwork. Machine learning engineers don’t come cheap so before you can use the OpenAI APIs, you will need to sign up for an account and set up billing. The good news is that, for basic experimentation and learning, the costs for using the APIs are quite low.

So, to sign up for your OpenAI account, head over to the OpenAI website and simply follow along with the signup process.

Step 2: Create an API Request Class 🌐

Let’s start by setting up a class used to represent a request that we will send to OpenAI. This is a useful method that can be used for most RESTful APIs and it starts by reading over the documentation for the endpoint you want to use. OpenAI provides API documentation on their website: https://beta.openai.com/docs/api-reference/completions/create.

The parameters used in the “completions” endpoint will be the basis for creating the CompletionsRequest class. Note that this class will be used to represent just the body of the API request. We will handle the request headers in a different class.

{
"model": "text-curie-001",
"prompt": "Say this is a test",
"max_tokens": 6,
"temperature": 0,
"top_p": 1,
"n": 1,
"stream": false,
"logprobs": null,
"stop": "\n"
}

First of all, create a new Dart file to hold the class representing the API request. In Partly Windy, this file is named “completions_request.dart.”

Then, inside this file, create a new class named “CompletionsRequest.” The class will contain fields for each of the parameters used in the OpenAI completions endpoint listed above. Among these parameters, some are required and must be included in the API request, while others are optional. In the CompletionsRequest class, the required parameters will use non-nullable types while the optional ones will use nullable types (denoted by a ? after the type declaration).

There is also one method we will include in this class. Since we can’t just send the API request in Dart format, we will include a function to convert a CompletionsRequest object to a JSON-formatted String. We will send this String in the actual completions API call.

To turn a CompletionsRequest object into a JSON-formatted String, we will first convert all the non-null fields into a Map<String, dynamic> object. Then we will use Dart’s convert library to turn the Map into a JSON String. Then we will return the resulting String.

With that function in place, the CompletionsRequest class is complete.

Step 3: Create an API Response Class 📨

We will follow a very similar process to create a class to represent responses received from the OpenAI completions endpoint. We will once again start by reading over the completions endpoint documentation to understand the format of the responses returned from the API, and the fields those responses are expected to contain.

{
"id": "cmpl-uqkvlQyYK7bGYrRHQ0eXlWi7",
"object": "text_completion",
"created": 1589478378,
"model": "text-curie-001",
"choices": [
{
"text": "\n\nThis is a test",
"index": 0,
"logprobs": null,
"finish_reason": "length"
}
],
"usage": {
"prompt_tokens": 5,
"completion_tokens": 6,
"total_tokens": 11
}
}

We will create a new file to contain the class we are about to complete. In the Partly Windy app, this file is called “completions_response.dart.”

Inside this file, create a new class called “CompletionsResponse.” Just like we did for the class representing API requests, the CompletionsResponse class will contain fields for each of the parameters we expect to be returned in the completions API responses. However, we want to make sure that our app will not crash in the event that, for whatever reason, the API response does not contain one or more expected fields. Therefore, we will make all the fields in this class nullable so that we can gracefully handle these fields being missing or containing unexpected null values.

You will notice that I actually included one extra field in this class, the firstCompletion field. This field is not actually included in the OpenAI completions API response, but because of the way the Partly Windy app uses the API responses, it is useful to give it a quick way to reference the first completion returned by the API.

We will also need one factory constructor (which is like a method that returns an instance of a class) in this CompletionsResponse class. The actual response received by the app from the OpenAI API is obviously not formatted as a Dart class. Instead, it comes in as a JSON-formatted String. Therefore, we will need to create a method to convert the response String into a CompletionsResponse object. We will implement the JSON to CompletionsResponse method in a factory constructor.

So now we can check the CompletionsResponse class off the TODO list.

Step 4: Create a File to Store the API Key (and exclude it from version control) 🔑

Let’s take a short break from building classes and make a very simple file instead, but one with an extremely important caveat. To authenticate your OpenAI API requests, you will require an API key. This is part of the reason we needed to sign up for an OpenAI account.

Get started by creating a new file alongside the other API-related files we’ve been working on so far that will eventually store your API key. You can name this file anything you would like. As an example, I named the one for the Partly Windy app “api_key.dart.”

Now, this file is going to contain your super secret OpenAI API key so you need to take some precautions to protect this key. If somebody were to steal it, they could use the key to send API requests to OpenAI and you will get billed for it. Even worse than that, a nefarious individual could use your API key for some kind of dastardly purpose and you would get blamed for it. So, once again, make sure you protect your OpenAI API key.

There are a number of ways to protect your API key that are covered in a bunch of different articles. For now, since this is just a demo project, we will just be making sure to exclude the file containing the key from version control. To do this, open the .gitignore file in the root directory of your project. You will already have content in this file so at the end, add the path to the file you just created to store the API key.

# API authentication
/lib/openai/api_key.dart

With this line added, Git will not track changes to this file and it will not push the file to GitHub if you host your repository there. Now that we made sure to exclude the API key file from version control, we can add the API key to this file.

On the OpenAI website, you can find your API key under your profile: https://beta.openai.com/account/api-keys

You can get your OpenAI API key from the OpenAI website.

Next to your secret key, tap the Copy button. Then, create a new String variable in api_key.dart called “openAIAPIKey” and paste in your key.

Create a variable to store your secret key.

So now, when we need to use the secret key in other parts of the code, we can just import api_key.dart and reference this variable.

Step 5: Create an OpenAI API Class 🦾

We have one more class to make and it is the one that knits all the other parts we’ve made so far together. We have classes that represent the API requests and responses, plus a file to hold the API key used for authentication. So now we need one more class that will use these other parts to communicate with the OpenAI APIs.

Create a new file in the same place as the others called “completions_api.dart.” Inside this file, create a new class called CompletionsAPI.

We will once again turn to the OpenAI documentation to set up the fields in this class. We need two pieces of information. First we will need the API endpoint, which is the URL to which the app will send the information contained within the CompletionsRequest objects. This same section of the documentation also lets us know that this is a POST request.

POST https://api.openai.com/v1/completions

Since this endpoint will not change, we will create a static field in the CompletionsApi class to store it as a String.

Next up, we will need the headers used for the endpoint. The CompletionsRequest class contains all the information needed in the request body, but the headers are always the same, regardless of the body content. In the OpenAI documentation, you can see the headers in cURL format:

-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_API_KEY' \

Just like the API endpoint, these headers will remain the same for all API requests, so we will define them as a static field in the CompletionsApi class. In Dart we will define these headers in a Map<String, String>.

Then there is one other small bit of preparation to complete. We will use a Dart library to send the RESTful API request, the http library. Follow the instructions provided by the http library documentation to install and import the library.

Install and import the Dart http library

Next up, we finally come to the method that does all the hard work for interacting with the OpenAI completions API. In the CompletionsApi class, we will create a method to send a REST API request to OpenAI. The method uses the following process to retrieve a response from the ML model:

  1. Create a new CompletionsRequest object.
  2. Send the CompletionsRequest, converted to JSON, to OpenAI and get a response back.
  3. Check to make sure the API call was successful.
  4. Create and return a new CompletionsResponse object.
A method to get a “forecast” from the OpenAI completions endpoint.

In the Partly Windy repository, you will find a number of enhancements to this basic method that add more variety to the responses the app receives from OpenAI. The app includes a list of prompts that the getNewForecast method chooses among randomly. This adds some variety to the responses the OpenAI ML model generates. The app also generates a random temperature between 0.6 and 0.9. The higher the temperature, the more “creative” the responses become. Randomly varying the temperature also helps give more variety to the responses.

Step 6: Using the Completions API 🌤️

At this point we have a fully functional integration for the OpenAI completions API in our Flutter app. The only step remaining is to use this new capability. Since the getNewForecast method returns a Future, the easiest way to use the method is using a FutureBuilder widget.

Parting Thoughts 🏁

In this article we integrated OpenAI’s ML-powered APIs into a Flutter app without using any third-party plugins. The only plugin used was the Dart http library. The Partly Windy example app integrates the OpenAI APIs by including classes to represent requests to these APIs, responses received from these APIs, and the API calls themselves. Authentication for these APIs uses an API token obtained after signing up for an account on the OpenAI website.

You could build on the concepts discussed in this article in myriad different ways. Machine learning technologies are already responsible for massive changes in our world and societies and the pace of change will only increase as the technology becomes more and more sophisticated. Natural language processing technologies, like the ones used in this article, present exciting ways to introduce intuitive interactivity into Flutter apps.

Thanks very much for reading and happy Fluttering!

--

--

Scott Hatfield

I like to take big, complicated projects, and break them down into simple steps that anybody can understand. https://toglefritz.com/