Table of Contents
In this post I’ll show you how you can easily add an AI feature to your SaaS. As an example I’ll explain how I added an AI option to OpnForm.com, allowing users to generate complex forms in less than 60 seconds. The code of OpnForm is open-source, you can find it on Github. You can see a short video demo of this new feature in the tweet below.
🤖 What is GPT, ChatGPT, etc?
I don’t think we really need this section. But in case you’ve been living in a cave for the past few months, I asked ChatGPT for an explanation:
GPT stands for "Generative Pre-trained Transformer," which is a type of language model developed by OpenAI. Essentially, GPT is a computer program that has been trained on a massive amount of text data, such as books, articles, and websites, to be able to understand the structure and meaning of language. Once trained, GPT can generate new pieces of text that are similar in style and content to the data it was trained on. This makes GPT a powerful tool for a variety of applications, including natural language processing, text generation, and language translation.
OpenAI, the company behind GPT, recently released ChatGPT, a free to use website allowing to chat and play around with their latest GPT model. Everyone talked about, and it became mainstream. OpenAI even got the record for the fastest acquisition of the first million users, in only 5 days. The record was previously detained by Instagram who got their in 2.5 months.
OpenAI has made this technology accessible to everyone through its API, making it possible for developers to build on top of it. With this in mind, many developers are now exploring ways to leverage ChatGPT's capabilities to enhance their products. In fact, there are so many products released around this new technology, that someone built a chrome extension to remove these AI product from ProductHunt.
🖥️ SaaS and Data
Now if you’re reading this, it’s probably because you’re a SaaS owner, an indie hacker or at least someone interested into real-world applications of this new technology.
SaaS products usually offer an interface to interact with data, and data is structured in a particular way that we’ll call a data schema. All parts of an application interacts with this schema: your users modifies their data through your front-end, and these modifications are saved into your database through your back-end.
Let’s see a real-world example of this. OpnForm.com is an open-source form builder that I am working on. Users use it to build and share forms, just like other traditional form builders (Google forms, Typeform etc.). In this application, each form is represented by an object with various properties (title, description, list of fields and their types etc). Users can change the properties of their forms through the form editor, which is just a fancy interface to update this JSON object.
Here’s a JSON object representation of a real form on OpnForm. As you can see it has many options. Learning all of these can be tedious to new users, especially because designing clear interfaces is not easy!
{
"id": 1,
"title": "My Form",
"slug": "my-form",
"properties": [
{
"name": "Name",
"type": "text",
"hidden": false,
"required": true,
"id": "67dab344-3a0d-4b12-a7aa-a4b75fa02ff4",
"placeholder": null,
"prefill": null,
"help": null
},
{
"name": "Email",
"type": "email",
"hidden": false,
"id": "fd86b7fd-4ad6-4869-9653-2ec4a5d7107c",
"required": true,
"placeholder": null,
"prefill": null,
"help": null
},
{
"name": "Message",
"type": "text",
"hidden": false,
"multi_lines": true,
"id": "1e490648-1dd8-4a3d-8004-c3616655b922",
"required": true,
"placeholder": null,
"prefill": null,
"help": null
}
],
"created_at": "2022-08-31T20:43:35.000000Z",
"updated_at": "2022-11-17T10:53:26.000000Z",
"description": "",
"submit_button_text": "Submit",
"re_fillable": false,
"re_fill_button_text": "Fill Again",
"color": "#3B82F6",
"uppercase_labels": true,
"no_branding": false,
"hide_title": true,
"submitted_text": "<p>Thank you for reaching out! We will get back to you as soon as possible.</p>",
"dark_mode": "auto",
"logo_picture": null,
"cover_picture": null,
"custom_code": null,
"theme": "default",
"width": "centered",
"transparent_background": true,
"closes_at": null,
"closed_text": "This form has now been closed by its owner and does not accept submissions anymore.",
"use_captcha": false,
"can_be_indexed": true,
"deleted_at": null,
"creator_id": 1,
"max_submissions_count": null,
"max_submissions_reached_text": "This form has now reached the maximum number of allowed submissions and is now closed.",
"visibility": "public",
"editable_submissions": false,
"editable_submissions_button_text": "Edit submission",
"share_url": "https://opnform.com/forms/my-form",
"workspace": {
"id": 1,
"is_enterprise": true,
"is_pro": true
},
"is_pro": true,
"workspace_id": 1,
"is_closed": false,
"is_password_protected": false,
"has_password": false,
"max_number_of_submissions_reached": false,
"form_pending_submission_key": "openform-1-pending-submission-682406"
}
✨ Adding some AI magic
Now that we have a clear understanding of what we want to create, let’s get our hands dirty!
To communicate with an AI model like GPT, we just need give it an input message, also known as a prompt. Luckily, OpenAI provides us chat.openai.com, a chat interface for us to easily play around with their AI. Let’s start by asking forms-related question.
The first attempt already gives great results! ChatGPT clearly knows a lot about forms, so we can move on the next challenge. To keep things simple for us on the back-end, ChatGPT should ideally give a JSON as a response. We’d be able to parse it easily, modify it if needed, and finally sent it back to the front-end. To test its JSON capabilities, I’m just going to convert his first answer into a valid JSON object.
The result is perfect. Or almost perfect as we only need the JSON and not the first sentence. I tried to tell the AI to only give me a JSON output without anything else, but from time to time it couldn’t help itself but to talk more than we wanted. So in the code implementation, I ended up parsing its reply, to only keep the code (between
```
characters). I also realized that it happens that the produced JSON was not valid (missing quotes more often). I found a PHP library that fixes broken JSONs, and it did the work.The output keys are different from the one we use for OpnForm, so I need to ask ChatGPT to change this. While we’re at it, I also need to explain the special properties of each of our field types. I can also provide information about the form (not the fields) such as the title, the description etc. After few twists, the final prompt looks like this:
I created a form builder. Forms are represented as Json objects. Here's an example form:
```json
{
"title": "Contact Us",
"properties": [
{
"help": null,
"name": "What's your name?",
"type": "text",
"hidden": false,
"prefill": null,
"required": true,
"placeholder": null
},
{
"help": null,
"name": "Email",
"type": "email",
"hidden": false,
"prefill": null,
"required": true,
"placeholder": null
},
{
"help": null,
"name": "How would you rate your overall experience?",
"type": "select",
"hidden": false,
"select": {
"options": [
{
"id": "Below Average",
"name": "Below Average"
},
{
"id": "Average",
"name": "Average"
},
{
"id": "Above Average",
"name": "Above Average"
}
]
},
"prefill": null,
"required": true,
"placeholder": null,
},
{
"help": null,
"name": "Subject",
"type": "text",
"hidden": false,
"prefill": null,
"required": true,
"placeholder": null
},
{
"help": null,
"name": "How can we help?",
"type": "text",
"hidden": false,
"prefill": null,
"required": true,
"multi_lines": true,
"placeholder": null,
"generates_uuid": false,
"max_char_limit": "2000",
"hide_field_name": false,
"show_char_limit": false,
"generates_auto_increment_id": false
},
{
"help": null,
"name": "Have any attachments?",
"type": "files",
"hidden": false,
"prefill": null,
"placeholder": null
}
],
"description": "<p>Looking for a real person to speak to?</p><p>We're here for you! Just drop in your queries below and we'll connect with you as soon as we can.</p>",
"re_fillable": false,
"use_captcha": false,
"redirect_url": null,
"submitted_text": "<p>Great, we've received your message. We'll get back to you as soon as we can :)</p>",
"uppercase_labels": false,
"submit_button_text": "Submit",
"re_fill_button_text": "Fill Again",
"color": "#3B82F6"
}
```
The form properties can have one of the following types: 'text', 'number', 'select', 'multi_select', 'date', 'files', 'checkbox', 'url', 'email', 'phone_number'.
All form properties objects need to have the keys 'help', 'name', 'type', 'hidden', 'placeholder', 'prefill'.
For the type "select" and "multi_select", the input object must have a key "select" (or "multi_select") that's mapped to an object like this one:
```json
{
"options": [
{"id":"Option 1","name":"Option 1"},
{"id":"Pption 2","name":"Option 2"}
]
}
```
For the type "number" you can set the property "is_rating" to "true" to turn it into a star rating input.
If the form is too long, you can paginate it by adding a page break block in the list of properties:
```json
{
"name":"Page Break",
"next_btn_text":"Next",
"previous_btn_text":"Previous",
"type":"nf-page-break",
}
```
Give me the JSON code only, for the following form: "[FORM_DESCRIPTION]"
Do not ask me for more information about required properties or types, suggest me a form structure instead.
It is pretty long! Here are some explanations:
- I start by briefly giving the context, and immediately give the JSON form structure. The actual JSON form structure has more properties, but I removed all the ones that were either too complex, not relevant (form password, form closing date etc) or that I could easily generate afterwards (such as field ids). I made sure to pick an example that illustrates well various features.
- I then explain more advanced details: existing field types, fields schema definition, unique options for the various field types, how to paginate a form etc.
- Finally, I give more details about the expected output format, and give a description for the form I’m trying to generate. I added the last sentence at the end because half of the time the AI was replying to the prompt that it was missing information about the properties of the form. Adding this sentence prevented it from doing that, and force it to be creative (so that we don’t have to be).
Adjusting the prompts take time. You need multiple tries to find out new issues to fix. Now here is an example of output, after I asked for a
a job application form for a baker for a French bakery
:Here's an example JSON code for a job application form for a baker for a French bakery:
```json
{
"title": "Job Application Form - Baker",
"properties": [
{
"help": "Please enter your full name",
"name": "Name",
"type": "text",
"hidden": false,
"placeholder": "John Smith",
"prefill": null,
"required": true
},
{
"help": "Please enter your phone number",
"name": "Phone Number",
"type": "phone_number",
"hidden": false,
"placeholder": "555-555-5555",
"prefill": null,
"required": true
},
{
"help": "Please enter your email address",
"name": "Email",
"type": "email",
"hidden": false,
"placeholder": "john.smith@example.com",
"prefill": null,
"required": true
},
{
"help": "Please enter your address",
"name": "Address",
"type": "text",
"hidden": false,
"placeholder": "123 Main Street",
"prefill": null,
"required": true
},
{
"help": "Please enter your city",
"name": "City",
"type": "text",
"hidden": false,
"placeholder": "Paris",
"prefill": null,
"required": true
},
{
"help": "Please enter your state",
"name": "State",
"type": "text",
"hidden": false,
"placeholder": "Ile-de-France",
"prefill": null,
"required": true
},
{
"help": "Please enter your ZIP code",
"name": "ZIP Code",
"type": "text",
"hidden": false,
"placeholder": "75001",
"prefill": null,
"required": true
},
{
"help": "Please enter the position you are applying for",
"name": "Position",
"type": "text",
"hidden": false,
"placeholder": "Baker",
"prefill": null,
"required": true
},
{
"help": "Please enter your salary expectations",
"name": "Salary Expectations",
"type": "number",
"hidden": false,
"placeholder": "50000",
"prefill": null,
"required": true
},
{
"help": "Please enter your years of experience",
"name": "Years of Experience",
"type": "number",
"hidden": false,
"placeholder": "5",
"prefill": null,
"required": true
},
{
"help": "Please select your availability",
"name": "Availability",
"type": "select",
"hidden": false,
"placeholder": null,
"prefill": null,
"required": true,
"select": {
"options": [
{
"id": "Full-time",
"name": "Full-time"
},
{
"id": "Part-time",
"name": "Part-time"
},
{
"id": "Internship",
"name": "Internship"
}
]
}
},
{
"help": "Please provide a brief description of your baking experience",
"name": "Experience",
"type": "text",
"hidden": false,
"placeholder": "I have been a baker for 5 years and have experience...",
"prefill": null,
"required": true
},
{
"name": "Page Break",
"next_btn_text": "Next",
"previous_btn_text": "Previous",
"type": "nf-page-break"
},
{
"help": "Do you have any certifications or degrees related to baking?",
"name": "Certifications/Degrees",
"type": "checkbox",
"hidden": false,
"prefill": null,
"options": [
{
"id": "Certification 1",
"name": "Certification 1"
},
{
"id": "Certification 2",
"name": "Certification 2"
},
{
"id": "Degree 1",
"name": "Degree 1"
},
{
"id": "Degree 2",
"name": "Degree 2"
}
],
"required": false
},
{
"help": "Please provide any additional information",
"name": "Additional Information",
"type": "text",
"hidden": false,
"multi_lines": true,
"placeholder": "Additional information",
"prefill": null,
"required": false,
"max_char_limit": "2000"
},
{
"help": "Please upload your resume",
"name": "Resume",
"type": "files",
"hidden": false,
"prefill": null,
"required": true,
"placeholder": null
}
],
"description": "<p>Please fill out the following form if you are interested in applying for the position of Baker at our French Bakery. Thank you for your interest in joining our team!</p>",
"re_fillable": false,
"use_captcha": false,
"redirect_url": null,
"submitted_text": "<p>Thank you for submitting your application. We will review your information and get back to you as soon as possible.</p>",
"uppercase_labels": false,
"submit_button_text": "Submit",
"re_fill_button_text": "Fill Again",
"color": "#F6AD3B"
}
```
The result is really great! After implementing the code, I generated a form with the same prompt and obtained this cool form. I only added the cover image (because I’m French and I love bread), but the rest was entirely generated by the AI. It make a 3 pages long very specific form! Give it a try!
🕐 Dealing with delays
Getting the answer for this prompt currently takes 40 to 50 seconds. OpnForm is deployed serverless. I’m using AWS lambdas and API gateway. This last has a 30 second timeout limit for http requests. Because of this, when users type in their form description, the back-end API does not have time to query OpenAI’s API and return the result before the request times out. Because my local environment does not have this limitation, I initially implemented a simple API call to get the AI-generated form structure. Upon deployment I realized the timeout issue and changed the way it works:
- User types a form description. An API call is made to send it, and a confirmation of reception is instantly returned, along with the AI generation ID. In the background, a job is dispatched and a lambda function sends a query to OpenAI.
- The front-end uses the obtained ID to get the status of the AI generation, by calling an endpoint every 5 seconds (long polling)
- Whenever the generation is completed, it’s returned to the front-end who loads it in the form editor. And voila!
It could be acceptable to have a 45 seconds http requests, but in my case it wasn’t not a viable. Think about these things when implementing your final solution!
💸 Cost
Now the interesting part! How much does it cost? OpenAI pricing is based on the number of “tokens” in the input/output. OpenAI describes tokens as pieces of words or approximately 4 characters. The ChatGPT model I used (gpt-3.5-turbo) costs $0.002 for each 1k tokens used.
To give a concrete example, when generating the job application form for the french baker, I consumed 1653 tokens. So it cost me 0,002*1,653 = 0,003306 cents. This is ridiculously cheap, and we can hope that with time this will get lowered.
Few months ago I wanted to hire someone on Fiverr to generate some form template for OpnForm. I only tested one candidate who I paid $10 to create a single template (along with description, images etc). I ended up writing a script that uses OpenAI to generate form template. The resulting templates are much better and more complete than the one created by the freelancer, but also they only cost me each around 1 cent, or a thousand times cheaper. You can find all the generated templates here. The pictures come from Unsplash (using keywords generated by the AI).
🌍 AI chatbots everywhere?
So in the end, adding AI to any product is as easy as explaining to another engineer how you structure data in your product. I’m guessing that this technique will work better for general products such as form generation, because the dataset used to train GPT most likely contains more examples.
OpenAI recently introduced ChatGPT plugins, allowing “anyone” to add custom capabilities to their chat assistant. For instance, airlines released bots that can be used to purchase plane tickets. Although this is cool and it can be convenient in some cases, I don’t believe that in the future we’ll do everything through chat interfaces. To be honest, I think I’d rather buy a plan ticket on a regular airline website. I’m convinced though, that soon there will be much more in-product AI integrations such as the one I showed today. It just enhances the experience, by automating boring tasks, while leaving the final touches to the human.
I hope you enjoyed this post, and that it helped you on the journey of riding the AI hype wave! Feel free to give OpnForm’s AI feature a try, it’s free and you don’t need to register.
If you enjoyed this, make sure to follow me on Twitter!