Artificial Intelligence Tickets with Budibase and OpenAI
Ticketing systems are some of the most common internal tools there are. They’re central to all kinds of ITSM, HR, finance, ops, and other workflows. However, they can also introduce huge amounts of tedious admin work.
This includes basic tasks like categorizing and prioritizing issues, assigning tasks, directing users to self-service resources, and handling communications.
Today, we’re checking out how we can use artificial intelligence to drive more efficient ticketing workflows.
Specifically, we’re going to see how Budibase’s OpenAI integration makes it a breeze to build advanced, custom AI-assisted ticketing tools.
By the end of this guide, you’ll have a fully functional solution that you can modify to your specific requirements.
Let’s start with the basics.
What is an AI ticketing system?
Ticketing systems allow users to submit information about requests, issues, incidents, bugs, or cases.
Service desk colleagues can then view, manage, and respond to tickets.
The idea is to create a centralized platform for receiving and handling service requests according to consistent rules and processes.
This supports a whole range of related workflows. This includes data collection, categorizing and routing issues, prioritizing tickets, scheduling tickets, communicating with stakeholders, and recording outcomes.
An AI ticketing system uses artificial intelligence to assist with these with the goal of providing faster, more effective resolutions to service users. This could be user-facing tasks, such as recommending knowledge base articles or even providing a chatbot.
Alternatively, we might leverage AI for back-end tasks, like categorizing tickets or routing them to relevant service agents.
Check out our round-up of the top ServiceNow alternatives to learn more.
Why use AI for ticketing?
So, how does artificial intelligence improve our ticketing workflows?
We can think about this at two levels. The first is efficiency. That is, we can use AI to perform many tasks that would otherwise require our service agents’ attention. In turn, this means we can provide faster resolutions with lower labor costs.
The second is service quality.
In addition to providing faster responses, AI-powered ticketing can be leveraged to improve outcomes. For instance, providing highly personalized responses at scale or using insights to predict future service needs.
That leads us to…
What are we building?
We’re building a powerful artificial intelligence ticketing system for handling ITSM requests. The goal is to empower users to submit tickets using natural language. We’ll then use OpenAI to categorize and prioritize submissions.
That way, users have a fast, easy way to input ticket data. We’re even going to add translation capabilities to provide multilingual ticketing.
We’re using Budibase to build our UIs, data layer, and automation rules. We’ll start by using our low-code database to set up a data model for our ticketing system.
We’ll then use the dedicated OpenAI integration in our automation builder to create prompts based on our users’ submissions to categorize, translate, and, if required, translate our tickets.
Then, we’re going to build interfaces for service users and service desk colleagues to submit, view, and manage tickets, leveraging Budibase’s autogenerated CRUD UIs and built-in role-based access control.
Here’s what our artificial intelligence ticketing system will look like when we’re done.
You might also enjoy our guide to open-source help desk software .
Let’s start building.
Building an AI ticketing system in 5 steps
The first thing you’ll want to do is sign up for a free Budibase account to start building as many apps as you want. We offer a cloud-based product, but today we’re going to use a self-hosted instance so that we can access the OpenAI integration.
We’ll start by creating a new application. We can import an existing app dump or use one of Budibase’s pre-built templates, but today, we’re going to start from scratch.
When we choose this option, we’ll be prompted to give our app a name that will also be used to generate a URL slug.
We’ll call ours AI Ticketing System.
1. Setting up our ticketing data
Once we’ve done this, we’ll be prompted to choose a data source for our application. Budibase offers dedicated connectors for querying relational databases, NoSQL tools, data warehouses, spreadsheets, APIs, and more.
Budibase acts as a proxy to query external data sources without storing them in our platform.
However, we’re going to import a CSV into BudibaseDB. When we choose this option, we’re prompted to give our data table a name and select a file to upload. We’re going to call our table Tickets.
Here’s the CSV data we’re using.
When we upload this, we’ll be prompted to configure the data types of each individual column. Some of our columns can be left as the default option of Text, but we’ll need to make the following changes:
- Description - Long-Form Text,
- Status - Options,
- Priority - Options,
- Date Created - Date,
- Date Updated - Date,
- Category - Options,
- Comments - Long-Form Text,
- Translated Comments - Long Form Text.
Here’s what this should look like.
Once our data is imported, we can edit the schema and values with a spreadsheet-like experience in Budibase’s Data section.
Before we move on, we need to make a few more minor tweaks.
A second ago, we set three of our columns to the Options type. However, for this to work, we need to set the available options for each.
We can do this by hitting Edit Column.
The options we’re setting for each column are:
- Status - Open, Closed, In-Progress,
- Priority - High, Medium, Low,
- Category - Hardware, Software, Network, Security, Account, Service Request, Other.
Adding translation columns
The last change we’re going to make to our data model is adding a couple of extra columns that will allow us to handle multi-lingual ticketing.
First, we’ll use the plus icon to add a new column with the Boolean type and call it Translated.
Then, we’ll add a JSON column and call it Ticket Translation.
We’ll need to define the schema of the JSON objects we’re going to store. Hit Open Schema Editor and we’ll add two strings called Title and Description.
And that’s our data model ready to go.
2. Connecting to OpenAI
Next, we can start building our AI-driven ticketing logic. Budibase offers a dedicated integration for the OpenAI API, making it easy to send prompts from directly within our platform.
In order to utilize this, you’ll need to add your API key as an environment variable within your Budibase installation.
Check out our OpenAI docs to learn more.
Creating an automation
Head to the Automation section. Here, we’ll create a new rule and call it New Ticket. We have a choice of several triggers, including user actions, webhooks, database events, and chron expressions.
Today, we’re going to choose App Action.
We can set our trigger to accept arguments whenever it’s initiated. We’re going to set three variables called rowID, title, and description. Later, we’ll bind these to corresponding values from a given row in our database from our UI.
Writing prompts
Next, we’ll hit the plus icon to add an automation action and choose OpenAI.
This action block offers two fields: Prompt, where we can write our ChatGPT prompt, and Model, where we can choose a specific LLM. We’re leaving our Model set to the default option of GPT-3.5 Turbo.
Before we start writing our prompt, let’s remind ourselves what we want ChatGPT to do.
Based on the Title and Description that users submit within their tickets, we want to assign the Category, Priority, and Language attributes. If the ticket is submitted in a language other than English, we’ll also populate the Translated Ticket JSON object and set Translated to true.
We want our response to provide this information as a JSON blob so that we can use it later in our automation run.
Start by hitting the lightning bolt icon beside Prompt to open the bindings drawer. Here, we can access all of the data our action is exposed to, including the arguments that were passed to our trigger.
The first thing we want to do in our prompt is provide the Title and Description, and give context to what these are using the following statement:
The following text is the title and description field from an IT ticket: Title: {{ trigger.fields.title }}, Description: {{ trigger.fields.description }}.
We’ll then add a statement to translate the ticket if it’s not already in English.
The following text is the title and description field from an IT ticket: Title: {{ trigger.fields.title }}, Description: {{ trigger.fields.description }}.
If a ticket is submitted in a language other than English, translate it to English before proceeding.
The first attribute we want to set is our ticket’s category. We’ll do this by providing the same options that we defined in our data model earlier.
The following text is the title and description field from an IT ticket: Title: {{ trigger.fields.title }}, Description: {{ trigger.fields.description }}.
If a ticket is submitted in a language other than English, translate it to English before proceeding.
Use this to decide if the ticket's category should be Hardware, Software, Security, Network, Account, Service Request, or Other.
Of course, we could provide more detailed logic for how to choose the category, but for demo purposes, we’ll keep it simple.
Hit Save, and we’ll test what we have so far by providing the information from one of our existing rows.
And we can see that ChatGPT is returning “account” which is the appropriate category for a password reset ticket.
We can carry on making adjustments to our prompt and testing the responses as necessary.
We’ll also add a statement with some basic logic on setting a priority level.
The following text is the title and description field from an IT ticket: Title: {{ trigger.fields.title }}, Description: {{ trigger.fields.description }}.
If a ticket is submitted in a language other than English, translate it to English before proceeding.
Use this to decide if the ticket's category should be Hardware, Software, Security, Network, Account, Service Request, or Other.
Also, provide a priority based on how many employees it is likely to affect - High, Medium, or Low.
And again, we’ll test this out.
However, our response now includes the rationale for why a particular priority level was chosen. We don’t want this, so we’ll need to add a statement on how to format our response.
The following text is the title and description field from an IT ticket: Title: {{ trigger.fields.title }}, Description: {{ trigger.fields.description }}.
If a ticket is submitted in a language other than English, translate it to English before proceeding.
Use this to decide if the ticket's category should be Hardware, Software, Security, Network, Account, Service Request, or Other.
Also, provide a priority based on how many employees it is likely to affect - High, Medium, or Low.
Response should be parsable key/value pairs.
Here’s what our new response looks like.
Lastly, we’re going to add a statement to provide the English version of the submitted Title and Description, the original language, and a boolean value for whether or not the submission was translated.
The following text is the title and description field from an IT ticket: Title: {{ trigger.fields.title }}, Description: {{ trigger.fields.description }}.
If a ticket is submitted in a language other than English, translate it to English before proceeding.
Use this to decide if the ticket's category should be Hardware, Software, Security, Network, Account, Service Request, or Other.
Also, provide a priority based on how many employees it is likely to affect - High, Medium, or Low.
When a ticket is translated, return the title and description as translatedTitle and translatedDescription. Provide a boolean value for whether or not the original ticket was translated. Call this translated. Also, return the original language and call it language.
The response should be parsable key/value pairs.
Now, we’ll test this out with a non-English submission.
And we can see that OpenAI has correctly identified German as the original language and translated our submission into English.
Assigning values based on our response
Once we’re happy with our prompt, we need to add an action that will add the values it generated to the original row in our tickets table. We’ll do this by adding an Update Row action and setting the Row ID setting to the corresponding value from our trigger.
We need to assign values to the Priority, Category, Ticket Translation, Translated, and Language fields, based on the response from our ChatGPT prompt.
The easiest way to do this is using the JSON.parse() JavaScript method to access the individual values. Start by hitting the lightning bolt icon next to the priority field and selecting JavaScript. Here, we’ll add the following code.
1var jsonString = $("steps.1.response")
2
3var ticketObject = JSON.parse(jsonString);
4
5// Access individual values
6
7var priority = ticketObject.priority;
8
9return priority
We’ll use very similar code for our remaining fields. Here’s what this will look like for our Category.
1var jsonString = $("steps.1.response")
2
3var ticketObject = JSON.parse(jsonString);
4
5// Access individual values
6
7var category = ticketObject.category;
8
9return category
Language:
1var jsonString = $("steps.1.response")
2
3var ticketObject = JSON.parse(jsonString);
4
5// Access individual values
6
7var language = ticketObject.language;
8
9return language
Translated:
1var jsonString = $("steps.1.response")
2
3var ticketObject = JSON.parse(jsonString);
4
5// Access individual values
6
7var translated = ticketObject.translated;
8
9return translated
And Ticket Translation:
1const jsonString = $("steps.1.response");
2
3// Parse the JSON string into a JavaScript object
4
5const ticketObject = JSON.parse(jsonString);
6
7const translatedTitle = ticketObject.translatedTitle;
8
9const translatedDescription = ticketObject.translatedDescription;
10
11// Create a JSON object directly
12
13const jsonObject = {
14
15 Title: translatedTitle,
16
17 Description: translatedDescription
18
19};
20
21// Return the jsonObject
22
23return jsonObject
We’ll then test this out with the data from one of our real rows and confirm that it has executed as expected.
Translating comments
We’re going to add a second automation rule that will handle translations for comments from our service agents.
Our data model contains a column called Comments, where service agents can submit extra information about the tickets, and one called Translated Comments, where we can provide these in the ticket’s original language.
We’ll start by creating a new rule called Translate Comments, again using an App Action Trigger. We’ll set our trigger arguments to rowId, language, and comment.
We’ll then add an OpenAI action with the following prompt.
Translate the following response from English into {{ trigger.fields.language }}
{{ trigger.fields.comment }}
Then, we’ll add an update row action, setting our Row ID to {{ trigger.fields.rowID }} and our Translated Comments to {{ steps.1.response }}.
As ever, we can test this to confirm that it worked.
3. Building a ticketing form
Now, we’re ready to start building user interfaces for our artificial intelligence ticketing system.
Head to the Design tab, and we’ll be shown a few options for how we want to create our first screen, including several options for autogenerating layouts based on connected data tables.
The first thing we want to build is a screen where service users can submit tickets. So, we’re going to select the Form layout. We’ll then be asked which data table we want to point this at, although our app only has one table anyway.
We then need to choose a form type. We want to create a new row.
When we’re asked to choose an access role, we’ll leave this set to the default option, Basic.
The Form layout generated a working data collection form based on the schema of whichever table we select.
Here’s what this looks like out of the box.
However, we don’t need to display form fields for most of our table’s columns. We’re going to start by deselecting everything except Title and Description using the sliders on the right-hand side.
We’ll also replace our form Title with something more descriptive.
Under Styles, we’ll also set our Button Position to Top.
Lastly, since our form UI only offers subset of our tables fields, we’ll need to populate the rest of these automatically. Some of these can be added by triggering our New Ticket automation, but the rest will need to be added manually.
Start by opening the actions drawer for our Save button. Under the Save Row action, we’ll hit the Add Column button to populate values for the Status and Date Created fields. We’ll set Status to Open and Date Created to the following JavaScript expression.
1var date = new Date();
2
3return date;
However, we also want to add a record of who created the ticket. Back in the Data section, we’ll add a Single User column called Created By. This will link rows in our Tickets table to Budibase’s internal Users table.
Then, under our Save Row button action, we’ll bind this new column to {{ Current User._id }}.
Next, we’ll add a button action to trigger our New Ticket automation. We’ll use bindings to set the rowID to the _id of the row we just saved and the title and description to the corresponding values from our form.
Eventually, we’re going to display this form in a modal UI, so we’ll also add a Close Screen Modal action.
Lastly, we’ll publish our app and submit a row of data to confirm that our form behaves as expected.
Note that automations won’t run in our app preview, only in the live application.
And we’ll check that this works in our Data section.
Viewing previous submissions
Next, we’re going to add a screen where service users can view their ticket submissions. Start by hitting the plus icon to add a new screen. This time, we’re choosing the option for a table with detailed side-panels.
Again, we’ll choose our Tickets table and leave the access role set to Basic. This will output a working CRUD UI based on our table’s schema.
However, as we said a second ago, we only want to display tickets that were created by the current user. So, we’ll add a filtering expression on our Table Block component, setting the Created By attribute to {{ Current User.globalId }}.
Now, we can only see the records that are associated with our user account.
Next, we’ll tidy up our table by deselecting any columns that are lower priority.
Currently, if a user hits the Create Row button, it will open a form in a side panel. However, we just created a custom form that we want to display in a modal UI. We’ll start by opening the actions drawer for our button and deleting the existing action with the X icon.
We’ll then add a Navigate To action, point this at our form screen, and select the option to use a modal.
We’ll also set our button’s display text to something more descriptive.
Lastly, we can access a side panel for editing entries by clicking on any of our table’s rows. We’re going to make a few tweaks to this. Specifically, we’ll set the Type to View, update our Title, and deselect any fields that aren’t relevant to service users.
That’s it for our service user screens.
4. Adding admin screens
Next, we want to add an equivalent screen where service desk colleagues can view and respond to all tickets.
We’ll start by adding another screen with the same Table layout, only this time we’ll set our minimum access role to Power.
We’ll start by repeating the process of reducing the display columns, and then we’ll delete the Create New button entirely.
Adding searchability and filtering
Next, we want to make it easy for service agents to find specific tickets. To do this, we’ll use a component called a Dynamic Filter, which allows users to set complex filtering expressions from the front end.
To do this, we’ll first need to add a component called a Data Provider. This accepts a data source and exposes other components on the screen to it. We’re setting ours to our Tickets table.
We’ll then set our Table block to the output of our Data Provider.
And we’ll add our Dynamic FIlter alongside our Heading, pointing it at our Data Provider.
Now, we can use this to create custom filtering expressions in our app’s front end.
Updating our edit form
Lastly, we’re going to make some key changes to the edit form on this screen, too.
Specifically, we want to make some fields editable while others will be read-only. We’re also going to use Budibase’s custom conditionality rules to display the translated submission for non-English tickets instead of the original values.
We’ll start by editing our Edit Row Form block to hide the Translated Comments, Translated Ticket.Title, and Translated Ticket.Description fields.
We’ll then update our title.
Next, we’ll select the Disabled option on all form fields except for Status, Category, Priority, and Comments.
Now, our service desk colleagues will be able to edit certain fields but only view others.
The last piece of functionality we want to add is dynamically displaying either the original ticket submission or the translated version, depending on which language it was submitted in.
The easiest way to do this is to start by ejecting our Form Block exposing its underlying components.
Now, we can see each of the individual fields that make up our form.
Select the Title field and open the conditions drawer.
Here, we can create rules to hide, display, or update the native settings of our components based on any of the data they’re exposed to.
To start, we’ll create a rule that updates the Field component to Ticket Translation.Title when {{ Repeater.Tickets.Translated }} equals True.
Then, we’ll add a second rule that updates the Label setting to Title (Translated From {{ Repeated.Tickets.Language }}) when the same condition is met.
We’ll also repeat this process for the description field.
To wrap up, we’re going to make a couple of tweaks to our Save button actions, just like we did before. Specifically, we want to populate the Date Updated value and trigger our Translate Comments automation.
So, we’ll start by opening the Save Row action and hitting Add Column. Just like we did earlier, we’re going to set Date Updated to the following JavaScript.
1var date = new Date();
2
3return date;
Then, we’ll add a Trigger Automation action pointed at our Translate Comments rule. We’ll bind the rowID argument to {{ Repeater.Tickets._id }}, the language to {{ Repeater.Tickets.Language }}, and the comment to {{ Form.Fields.Comments }}.
We’ll publish and open our app to confirm this works by adding a comment to our ticket in English.
And back on our service user screen, we can see that our comment has been translated back into the original ticket language.
5. Design tweaks and publishing
From a functional point of view, our artificial intelligence ticketing system is ready to go.
However, before we push it live for users, we’re going to make a few final design adjustments.
First of all, each group of users can only really access a single screen. So, we can remove the links from our nav bar.
To do this, head to Navigation and remove each individual link using the X icon.
Here’s how this should look.
Next, under Screen, we’ll head to Theme and select Midnight.
While we’re here, we’ll also update our default accent colors to match the Budibase brand.
When we’re happy, we’ll hit Publish one final time to push our app live.
Here’s a reminder of what our finished artificial intelligence ticketing system looks like.
Budibase is the open-source, low-code platform that empowers IT teams to turn data into action.
Check out our features overview to learn more.