Getting excited about the future of Fluent

Adam Hutton
Tera Contributor

Hi community!  Sharing here a short post that I wrote up about the importance of Fluent.  I'd love to hear your guys' thoughts and experiences with it.  How have you been using the IDE?

 

Original post here: https://adamhutton.medium.com/servicenow-fluent-is-a-big-deal-9c63f35bc5b7?source=friends_link&sk=ed...

 

 

The ability to represent composite objects in short packages of code will change how we can use GenAI to design solutions on the platform.

A new feature from ServiceNow, released about six months ago, flew somewhat under the radar. Fluent is part of ServiceNow’s IDE in Xanadu+ and makes ServiceNow configuration records human-readable in simple typescript.

 

For an excellent, short demo of it, watch this ServiceNow Dev Program Channel video from Patrick Wilson and Jay Couture: https://www.youtube.com/live/PHFZBsbTTtI

Today, the Fluent API is fairly limited but still powerful. You can use it to create ACLs, ATFs, Business Rules, Script Includes, Roles, Records, Tables, and even Scripted REST APIs. I expect this list to grow with future releases to include more complex features.

Currently, Fluent exists in the realm of pro-code tooling, specifically within the ServiceNow SDK and IDE. Most ServiceNow platform and development teams don’t spend the majority of their time here when configuring platform features.

 

Why is this such a big deal?

Generative AI plus Composite Objects.

 

GenAI and Fluent

Here’s where it gets interesting. Since the amount of code needed to generate ServiceNow components is so short, it easily fits into an OpenAI context window — even when using the free version of OpenAI’s ChatGPT GPT-4o model.

 

In just a few minutes, I drafted a simple prompt to help me build a new table to store location data related to U.S. counties. I asked it to suggest new attributes that would be relevant. I provided sample code directly from ServiceNow’s Fluent documentation (Yokohama) and asked it to generate the code for me.

 

AdamHutton_0-1740843868468.png

 

I had to adjust the prompt a few times, mainly to ensure it used the appropriate prefix for the application (the namespace for my PDI, in this case).

 

The first attempt was close but not perfect. Surprisingly, it was quite good — just needed a few minor tweaks, which I could easily adjust through prompt refinement.

 

 

import { Table, StringColumn, ReferenceColumn, IntegerColumn, DecimalColumn, ChoiceColumn, DateTimeColumn } from "@servicenow/sdk/core";

export const x_9076_enh_county = Table({
    name: 'x_9076_enh_county',
    label: 'County',
    schema: {
        x_9076_enh_locale_county_name: StringColumn({
            label: 'County Name',
            maxLength: 255,
            unique: true,
            mandatory: true
        }),
        x_9076_enh_locale_county_abbreviation: StringColumn({
            label: 'County Abbreviation',
            maxLength: 10
        }),
        x_9076_enh_locale_state: ReferenceColumn({
            label: 'State',
            mandatory: true,
            reference: 'x_9076_enh_locale_state_province'
        }),
        x_9076_enh_locale_fips_code: StringColumn({
            label: 'FIPS Code',
            maxLength: 5,
            unique: true
        }),
        x_9076_enh_locale_population: IntegerColumn({
            label: 'Population',
            mandatory: false
        }),
        x_9076_enh_locale_land_area: DecimalColumn({
            label: 'Land Area (sq mi)',
            mandatory: false,
            scale: 2
        }),
        x_9076_enh_locale_timezone: ChoiceColumn({
            label: 'Timezone',
            choices: {
                eastern: { label: 'Eastern' },
                central: { label: 'Central' },
                mountain: { label: 'Mountain' },
                pacific: { label: 'Pacific' },
                alaska: { label: 'Alaska' },
                hawaii: { label: 'Hawaii' }
            }
        }),
        created_on: DateTimeColumn({
            label: 'Created On',
            auto_value: 'created',
            read_only: true
        }),
        updated_on: DateTimeColumn({
            label: 'Updated On',
            auto_value: 'updated',
            read_only: true
        })
    },
    display: 'x_9076_enh_locale_county_name',
    actions: ['create', 'read', 'update', 'delete'],
    allow_client_scripts: true,
    allow_new_fields: true,
    allow_ui_actions: true,
    allow_web_service_access: true,
    extensible: true,
    audit: true,
    text_index: true,
    attributes: {
        update_sync: true
    },
    index: [
        {
            name: 'idx_county_name',
            element: 'x_9076_enh_locale_county_name',
            unique: true
        },
        {
            name: 'idx_fips_code',
            element: 'x_9076_enh_locale_fips_code',
            unique: true
        }
    ]
});

 

 

A simple second prompt, including the error message and a quick copy-paste from the ServiceNow documentation, resolved the issues.

 

 

AdamHutton_1-1740843940697.png

 

AdamHutton_2-1740843959062.png

 

Here’s what the revised version outputted:

 

 

import { Table, StringColumn, ReferenceColumn, IntegerColumn, DecimalColumn, ChoiceColumn, DateTimeColumn } from "@servicenow/sdk/core";

export const x_9076_enh_county = Table({
    name: 'x_9076_enh_county',
    label: 'County',
    schema: {
        x_9076_enh_locale_county_name: StringColumn({
            label: 'County Name',
            maxLength: 255,
            unique: true,
            mandatory: true
        }),
        x_9076_enh_locale_county_abbreviation: StringColumn({
            label: 'County Abbreviation',
            maxLength: 10
        }),
        x_9076_enh_locale_state: ReferenceColumn({
            label: 'State',
            mandatory: true,
            reference: 'x_9076_enh_locale_state_province'
        }),
        x_9076_enh_locale_fips_code: StringColumn({
            label: 'FIPS Code',
            maxLength: 5,
            unique: true
        }),
        x_9076_enh_locale_population: IntegerColumn({
            label: 'Population'
        }),
        x_9076_enh_locale_land_area: DecimalColumn({
            label: 'Land Area (sq mi)',
            scale: 2
        }),
        x_9076_enh_locale_timezone: ChoiceColumn({
            label: 'Timezone',
            choices: {
                eastern: { label: 'Eastern' },
                central: { label: 'Central' },
                mountain: { label: 'Mountain' },
                pacific: { label: 'Pacific' },
                alaska: { label: 'Alaska' },
                hawaii: { label: 'Hawaii' }
            }
        }),
        x_9076_enh_locale_created_on: DateTimeColumn({
            label: 'Created On',
            auto_value: 'created',
            read_only: true
        }),
        x_9076_enh_locale_updated_on: DateTimeColumn({
            label: 'Updated On',
            auto_value: 'updated',
            read_only: true
        })
    },
    display: 'x_9076_enh_locale_county_name',
    actions: ['create', 'read', 'update', 'delete'],
    allow_client_scripts: true,
    allow_new_fields: true,
    allow_ui_actions: true,
    allow_web_service_access: true,
    extensible: true,
    audit: true,
    text_index: true,
    attributes: {
        update_sync: true
    },
    index: [
        {
            name: 'idx_county_name',
            element: 'x_9076_enh_locale_county_name',
            unique: true
        },
        {
            name: 'idx_fips_code',
            element: 'x_9076_enh_locale_fips_code',
            unique: true
        }
    ]
});

 

 

GPT still is far from perfect, but this is my own error. In these rows:

export const x_9076_enh_county = Table({
name: 'x_9076_enh_county',
label: 'County',
schema: {

I realized my original prompt incorrectly instructed it to name the table. That was an easy fix, and I didn’t even need to go back to GPT for it.

 

Here’s the ServiceNow IDE building the table.

 

AdamHutton_3-1740844023354.png

 

And the resulting table in the old school Studio.

AdamHutton_4-1740844049484.png

 

Yes, there’s some things wrong with this table! Very odd to create custom fields for created and updated on — but hey, it is this LLMs first try making a ServiceNow table.

This is a very rough and fast demo of using GenAI to both help me design the table and write the code to build the table. I hacked this together in an hour — so I appreciate that all could be greatly improved using better prompts and trying out different models. Think of what better tuned LLMs from Now Assist for Creators could provide!

Composite Objects

Composite Objects — think Flows and Playbooks — comprise comparatively huge payloads of XML and JSON, sometimes with one nested in the other.

First, let’s compare the typescript Fluent code to make a new table to the payload found in an old school customer update.

Here’s the Fluent code in typescript:

import { Table, StringColumn, ReferenceColumn, IntegerColumn, DecimalColumn, ChoiceColumn, DateTimeColumn } from "@servicenow/sdk/core";

export const x_9076_enh_county = Table({
name: 'x_9076_enh_county',
label: 'County',
schema: {
x_9076_enh_locale_county_name: StringColumn({
label: 'County Name',
maxLength: 255,
unique: true,
mandatory: true
}),
x_9076_enh_locale_county_abbreviation: StringColumn({
label: 'County Abbreviation',
maxLength: 10
}),
x_9076_enh_locale_state: ReferenceColumn({
label: 'State',
mandatory: true,
reference: 'x_9076_enh_locale_state_province'
}),
x_9076_enh_locale_fips_code: StringColumn({
label: 'FIPS Code',
maxLength: 5,
unique: true
}),
x_9076_enh_locale_population: IntegerColumn({
label: 'Population'
}),
x_9076_enh_locale_land_area: DecimalColumn({
label: 'Land Area (sq mi)',
scale: 2
}),
x_9076_enh_locale_timezone: ChoiceColumn({
label: 'Timezone',
choices: {
eastern: { label: 'Eastern' },
central: { label: 'Central' },
mountain: { label: 'Mountain' },
pacific: { label: 'Pacific' },
alaska: { label: 'Alaska' },
hawaii: { label: 'Hawaii' }
}
}),
x_9076_enh_locale_created_on: DateTimeColumn({
label: 'Created On',
auto_value: 'created',
read_only: true
}),
x_9076_enh_locale_updated_on: DateTimeColumn({
label: 'Updated On',
auto_value: 'updated',
read_only: true
})
},
display: 'x_9076_enh_locale_county_name',
actions: ['create', 'read', 'update', 'delete'],
allow_client_scripts: true,
allow_new_fields: true,
allow_ui_actions: true,
allow_web_service_access: true,
extensible: true,
audit: true,
text_index: true,
attributes: {
update_sync: true
},
index: [
{
name: 'idx_county_name',
element: 'x_9076_enh_locale_county_name',
unique: true
},
{
name: 'idx_fips_code',
element: 'x_9076_enh_locale_fips_code',
unique: true
}
]
});

Here’s the customer update xml payload for just creating the table.

AdamHutton_5-1740844086739.png

 

This isn’t a huge difference. The customer update record related to creating a table is fairly clear. It is compressed and not as easy to read as the typescript.

This is only part of the story though. We can’t grab just the customer update for the table, we have to grab all the other objects, too:

 

AdamHutton_7-1740844103042.png

 

I won’t paste all this in here, but here’s a quick screenshot. It is about 250 lines, but much less human-readable and much more ServiceNow-specific.

 

AdamHutton_8-1740844135867.png

 

This is just for a table creation with a few fields. Not all created objects are as friendly.

What a Flow looks like in XML, today

Let’s take the example of a simple Flow. Not too many steps, but the resulting customer update record is massive! This small, very simple flow, is almost 1000 lines of code that are largely tough to read.

 

AdamHutton_9-1740844154573.png

 

Here’s the Customer Update record.

 

AdamHutton_10-1740844171621.png

 

Where GenAI comes in

This is where it should be very clear: trying to have GenAI generate traditional XML update sets would be extremely challenging, if not impossible. A LLM would need to know a LOT about the ServiceNow payload structure. When Fluent is able to be used to describe more complex, advanced objects, the payload now becomes much more manageable (in terms of token size) and generic.

Where I’m going with all these code comparisons — the Fluent typescript is very easy to read, much shorter, and less ServiceNow-specific. That means we can have much better luck with cheap LLMs to be able to create reasonably useful elements.

As the capabilities of Fluent expand — into different object types and into being able to edit configurations, it will be exciting to see new ways to be able to work with ServiceNow code from various LLMs.

The impacts of this could be wild, from being able to create new catalog items on-demand by a helpful copilot agent to much richer capabilities from Now Assist for Creators.

 

Table API — ServiceNow Fluent (Yokohama Documentation): https://www.servicenow.com/docs/bundle/yokohama-application-development/page/build/servicenow-sdk/re...

 

ServiceNow IDE and Fluent (sdk v2) w/Patrick Wilson and Jay Couture — Creator Toolbox: https://www.youtube.com/watch?v=PHFZBsbTTtI

 

 

1 REPLY 1

ShaumikM
Tera Contributor

This doesn't work. The reference column breaks the table creation. Plus what you have there is incorrect. It's not `reference` it should be `referenceTable`. However even with that it doesn't work. It seems like ServiceNow SDK has a bug. Actually it has quit a bit of bugs. Please reach out to me and I can guide you guys on this. Thank you,.