- Post History
- Subscribe to RSS Feed
- Mark as New
- Mark as Read
- Bookmark
- Subscribe
- Printer Friendly Page
- Report Inappropriate Content
2 hours ago
Agentic AI and GraphQL
This is a guide for anyone interested in the intersection of Agentic AI and GraphQL, two powerful technologies and how they can be used with the Now platform.
We will start with the assumption that you have no experience with either and give you the need-to-know behind the technologies and how they are used to unleash the power of workflows on the Now platform. By Part 5 you will be a pro! We will go into the background behind both GraphQL and Agentic AI but if you are interested in more information I would suggest reading the introduction from the GraphQL Org before starting this just to get some familiarity with terms and some of the benefits of GraphQL versus REST.
Inspiration from this article series was drawn from Setting up and Testing your first GraphQL API Tutorial by @Jon G Lind.
Note: Initially we planned for this article to have a narrower focus strictly on leveraging Agentic AI, but the use case assumes GraphQL knowledge and thus turned from 2 parts into 5. If you already are a GraphQL pro you can skip straight to part 4!
Part 2 GraphQL Beyond the Basics
Part 3 GraphQL Security & Testing
Part 4 Agentic AI and GraphQL Basics
Part 5 Agentic AI and GraphQL Advanced
Binding with the @source directive
The @source directive is described here:
@source: Maps a GraphQL field to the value of a property of the parent object. If the field has a separate resolver script, the system uses the record that it resolves to instead of the parent object.
What does this mean? Let us walk through a simple use case with hard-coded values. Update the script of your API to look like this
schema {
query: Query
}
type Query {
getUser(userID: String): User
}
type User {
userID: String! @source(value: "user_name")
name: String! @source(value: "name")
email: String! @source(value: "email")
}We are also going to update the hard-coding of our Get User resolver function
(function process(/*ResolverEnvironment*/ env) {
return {
user_name: "david.loo",
name: "David Loo",
email: "david.loo@example.com"
};
})(env);Go ahead and head back to the GraphQL Explorer (System Web Services > GraphQL > GraphQL Explorer) and test this out. Remember to use the re-fetch if necessary. The result should look like this
The interesting thing here is that we were able to remap userID to the value of user_name. This allows for us to map and expose things the way we want which is surprisingly powerful in the context of API versioning to support stability.
Creating Our First Real Query
To do our first real query we are going to have to update the user type to be able to bind values to the return from a GlideRecord query using the code snippet below. We are binding to either the value or display value of the field. We also made another small change to make the userID parameter of getUser a mandatory argument.
schema {
query: Query
}
type Query {
getUser(userID: String!): User
}
type User {
userID: String! @source(value: "user_name.value")
name: String! @source(value: "name.display_value")
email: String! @source(value: "email.value")
}The fields we are using either .value or .display_value should work but I varied them just to show that either option works. Now we just need to update our Get User resolver script to query the User table be adding a GlideRecord query and returning the GlideRecord object. One thing you will need is the userID passed from the getUser function. To get that you need to use the env parameter that is passed into the resolver function. You can do it like so
var userID = env.getArguments().userID;env.getArguments() returns the arguments passed by the client in the GraphQL query for the field that this resolver is handling. Once you are done if you want to check, validate against the code snippet in the spoiler
(function process(/*ResolverEnvironment*/ env) {
var userID = env.getArguments().userID;
var userGR = new GlideRecord('sys_user');
userGR.get('user_name', userID);
return userGR;
})(env);It can be seen here that the solution is simple and effective. It's worth testing out a few use cases so you can see how GraphQL communicates different things:
- Invoking getUser without passing an argument
- Invoking getUser and passing an integer
- Invoking getUser and passing a nonsense string value
- Invoking getUser and passing in "david.loo"
Expanding on Our Query
This is going to be a long section but it is time to walk through the power of the @source directive as we evolve our example and start to see the power of GraphQL! We are going to create a new Department type and update our User type like so
schema {
query: Query
}
type Query {
getUser(userID: String!): User
}
type User {
userID: String! @source(value: "user_name.value")
name: String! @source(value: "name.display_value")
email: String! @source(value: "email.value")
manager: User! @source(value: "manager.value")
department: Department @source(value: "department.value")
}
type Department {
name: String! @source(value: "name.display_value")
departmentHead: User @source(value: "dept_head.value")
}So now the User type references itself for the manager property and it references the Department type. So we will need a new Resolver for Department but first we will need to update our Get User Resolver so that it can work with our new manager property. Can you see why?
An important item to note here is the exciting capability that exists in this technology. Recall we originally designed this for user_names so our API would work with an external system. We should keep it that way and not expose sys_id as a parameter. To account for that logic we will need to use another method. Update your Get User Resolver like so
(function process(/*ResolverEnvironment*/ env) {
var userID = env.getArguments().userID;
var userSysID = env.getSource();
var userGR = new GlideRecord('sys_user');
userGR.setLimit(1);
if(userID) {
userGR.addQuery('user_name', userID);
}
if (userSysID) {
userGR.addQuery('sys_id', userSysID);
}
userGR.query();
return userGR;
})(env);
The new method env.getSource() returns the parent object in the execution chain i.e. the result returned by the resolver one level up. It is necessary for resolving nested fields where the value depends on the parent object. And because of that we are able to reuse the same function and if an argument is passed we know it is a user_name and otherwise we know it is a sys_id. That is why we are going to do a more normal example as well for Department, so create a new Resolver and name it "Get Department" and the code for it is quite simple
(function process(/*ResolverEnvironment*/ env) {
//example if we allowed for querying Departments directly
//var sysID = env.getArguments().sysID || env.getSource();
var sysID = env.getSource();
var grDepartment = new GlideRecord('cmn_department');
grDepartment.get(sysID);
return grDepartment;
})(env);This only way this resolver is used is as a nested field, otherwise we would be using the commented code right above it to allow for direct usage as well. Okay last step we need to create 3 Resolver Mapping records for the Manager, Department, and Department Head
Time to test! Now remember when you update your query you have to send a valid request i.e. you cannot just ask for the manager, you will need to ask for the manager's name or something. Try playing around with it and testing all the resolver mappings to make sure they work. I would suggest using "david.loo" as he has a Department, Manager, etc. populated. If you run into errors try simplification to figure out where it is at. For an example on how deep you can go, check out the spoiler below
Part 3 GraphQL Security & Testing
After completing this part, you should be comfortable using @source, env, and understanding the flexibility/reusability of Resolvers by leveraging Resolver Mappings. Part 3 GraphQL Security & Testing will cover security and testing from an external tool.