logo

Are you need IT Support Engineer? Free Consultant

Building a Procurement Agent for SAP S/4HANA Cloud…

  • By Sanjay
  • 06/06/2026
  • 7 Views


TL;DR: This is the second post of a three-part series on building a custom AI agent for Joule, specialized in creating purchase requisitions in SAP S/4HANA Cloud Private Edition through a single prompt. In this second part, we walk through the steps to build Joule Skills and a technique for item parsing using SAP Build Process Automation. Part 1 can be found here.

 

Evandropramos_0-1780668167665.Png

 

A Joule Skill is a modular capability that SAP Joule can invoke to execute a specific task on your behalf. It is a packaged “action” that defines:

  • Which business intent it addresses (e.g., “get allocation runs”, “list projects”);
  • Which data and systems it needs to call (APIs, services);
  • What the expected inputs and outputs are;
  • And how to execute with proper security and governance.

With Skills, in addition to answering questions, Joule can execute tasks across enterprise systems in a predictable and controlled manner.

 

Typical Structure of a Joule Skill

Here are some common elements in the structure of a Joule Skill built in Joule Studio:

  • Intent and description: the business outcome being delivered.
  • Input parameters: what Joule needs to receive (e.g., period, project ID). Skills primarily work with text (string) values for input parameters.
  • Backend action: integration and queries to APIs of SAP or external systems (through Actions).
  • Output schema: the format of the result to be returned to the user or to the next steps.

 

Joule Skill vs. Joule Agent

Compared to classical developments, Joule Skills can be seen as encapsulated functions with deterministic behavior. What does this mean? Given the same input, the expected outcome of executing a Joule Skill will always be the same.

Unlike Joule Agents, which include a reasoning step, Joule Skills follow a defined logical path, with predictable behavior and consistent results when the same input data is provided. They are tools defined through a low-code platform that will be consumed by AI agents.

Joule Agents, by contrast, are flexible and variable. They run on top of an LLM (Large Language Model) and do not follow a fixed logical path. They use AI reasoning to decide what to do, based on the context available to them. They can plan, reason, and dynamically solve problems using the tools at their disposal. They are defined through the system prompt, which will be covered in more detail in the final part of this series.

 

The Importance of Descriptions

Often, when developing a custom object in the ABAP environment, descriptions are an afterthought. How many times have you come across a structure in the data dictionary with technical field names that could not be inferred, and incomplete or barely meaningful descriptions?

When developing Joule Skills and Agents, this changes: descriptions matter. They help the agent, during the reasoning phase, to better understand what they are working with. As a result, detailed descriptions are highly welcome – both for the people who will maintain your artifacts in the future and to help the Agent achieve a higher success rate during execution.
Important: Use precise, clear English descriptions, since user requests – even when expressed in another language – are translated internally into English.

 

Now that you understand what differentiates a Joule Agent from a Joule Skill, let’s briefly discuss how our building blocks will relate to each other and the call flow between them. The simplified diagram below illustrates the flow:

Simplified Message Flow Through ComponentsSimplified message flow through components

 

 

In the proposed scenario, when sending a single prompt, the business user will already provide the items and their respective fields. The Joule Agent will receive this prompt and, based on its definitions, split the information between the header and items, then invoke our Joule Skill to request the creation of the purchase requisition.

As mentioned, Joule Skill works with input parameters in text format. Inside it, however, the received text must be transformed into a list using an item-parsing technique. There are several ways to do this. In our case, we used a Process built with SAP Build Process Automation.

In this automation, the available script tasks were used to parse the items, to call the Products API, and subsequently to map the values back to the ISO unit of measure required by the Purchase Requisition creation API.

The items, now mapped and enriched with their corresponding units of measure, are returned to the Joule Skill, which is then ready to call our Action to create the purchase requisition. After the SAP S/4HANA execution, the PR object is returned and captured by the Joule Skill. Finally, the flow returns the ID of the generated purchase requisition.

The Joule Agent can then capture this information and present it to the business user as a message.

 

Roles: developers.

Although they are not strictly objects created by Joule Studio, both Processes and Automations are part of the SAP Build suite and can be reused inside Joule Skills. This allows previously designed flows and automations to also be integrated with Joule’s extended capabilities.

In our example, we need a Process for one specific reason: the purchase requisition creation Action expects items in a tabular format. Therefore, the string carrying the PR item information must be converted into rows. We will use a script task to demonstrate this case.

For didactic purposes, we will also use the Process to incorporate information from the products API and assign the corresponding ISO unit of measure. This information will also be obtained through a script.

 

To create a Process Automation project, access the SAP Build Lobby. Then, click Create. Select Automated Process and then Process. Add a name and a description for your project, review it, and confirm the creation.

Creating A Process Automation ProjectCreating a Process Automation project

 

First, we configure the Trigger. This Process will be invoked from a Joule Skill, so it must work via an API call. Add a name and a description.

Adding A Process TriggerAdding a process trigger

 

For the case of Purchase Requisition creation, we will need a data type containing all the fields we expect to send to our Action. To create this structure, return to the newly created project and choose Create > Data Type, then provide the name and the description.

Creating A Data TypeCreating a data type

 

In our use case, we created a flat structure similar to the one in the API. The fields included in the structure were:

Name

Type

List

Required

Plant

String

No

Yes

BaseUnit

String

No

No

Material

String

No

Yes

DeliveryDate

Date

No

Yes

RequestedQuantity

String

No

Yes

PurchaseRequisitionItem

String

No

Yes

PurchaseRequisition

String

No

Yes

ZJoule

String

No

No

Note: Remember that the ZJoule field was enabled through key user extensibility. If you are reproducing this example, keep in mind that this field will not exist unless you enable it beforehand. Execution will not be impacted if you have not done so – simply skip this field in all the steps.

 

Back to the Process, we will define the input/output parameters and the custom variables. Process Inputs and Outputs are the parameters received and returned by the Process. Custom variables are variables that exist only during the execution of the automation.

To access this section, click on an empty area and then on the << button.

Editing Process ParametersEditing Process parameters

 

For our scenario, we defined the parameters below:

Parameter type

Name

Description

Type

Required

List

Process Input

Purchase Requisition Items – In

Purchase requisition items – trigger

String

Yes

No

Process Output

Purchase Requisition Items

Parsed purchase requisition items

Purchase Requisition Item

Yes

Yes

Custom Variable

Purchase requisition items

Purchase requisitions items

Purchase Requisition Item

N/A

Yes

Custom Variable

Product filter

Product filter string

String

N/A

No

A few additional comments:

  • “Purchase Requisition Items – In” is the parameter that contains all the items inferred by the Agent, in string format.
  • “Purchase Requisition Items” is the output parameter that will hold the items in list format. Note that we activated the List property for this parameter.
  • “Purchase requisition items” and “Product filter” are, respectively, the variable in which the items will be processed and enriched, and the variable that holds a filter string in the format expected by the OData service.

 

Now, we will start building the process flow. After the trigger, click the + icon to add a step and select the Script Task option. Here, we set the name (“Map purchase requisition items”) and click the Edit Script button to open the editor. Then we insert the JavaScript code below, which performs the item mapping and builds the product filter:

// Purchase requisition items
// Parsing items (string to JSON)
var lines = JSON.parse($.context.startEvent.purchaseRequisitionItemsIn);
var index = 0;

// Function formatDate – receives a date object and returns a string with format “yyyy-mm-dd”
function formatDate(date) {
    var yyyy = date.getFullYear();
    var mm = String(date.getMonth() + 1);
    var dd = String(date.getDate());
    if (mm.length < 2) mm = '0' + mm;
    if (dd.length < 2) dd = '0' + dd;
    return yyyy + '-' + mm + '-' + dd;
}

// From parsed array, map values to the custom variable purchaseRequisitionItems
$.context.custom.purchaseRequisitionItems = lines.map(function(line) {
    index += 10;

    return {
        PurchaseRequisition: "",
        PurchaseRequisitionItem: index.toString(),
        Material: line.Material,
        Plant: line.Plant,
        DeliveryDate: formatDate(new Date(line.DeliveryDate)),
        RequestedQuantity: parseFloat(line.RequestedQuantity).toString(),
        BaseUnit: line.BaseUnit,
        ZJoule: line.ZJoule
    };
});

// Build filter string and set value to variable productFilter
function buildFilterString(lines) {
      var filter = "Product eq '" + lines[0].Material + "'";
      for (var i = 1; i < lines.length; i++) {
          filter += " or Product eq '" + lines[i].Material + "'";
      }
      return filter;
}

$.context.custom.productFilter = buildFilterString(lines);

 Important: The JavaScript code provided must be compatible with ECMAScript 5.1.

 

Creating A Script TaskCreating a script task

Note: If you are not familiar with creating scripts, refer to the page Create and Configure a Script Task

Next, the API to retrieve product/material details will be called. We pick the Action for products created in part 1. When you select it, a new dependency will be added to your project.

Adding An Action To Your Process FlowAdding an Action to your Process flow

 

 

You will need to create a destination variable (which will be requested when testing and deploying this Process) and the input parameters. We assigned the variable Product filter as the value mapped to the $filter parameter.

 

Then we add a second script that looks up the result in the product list and maps the ISO unit of measure to the Purchase Requisition Items variable.

  var sourceList = $.context.action_get_Product_1.result.value;
  var targetList = $.context.custom.purchaseRequisitionItems;

  // Build a lookup map from Product -> BaseISOUnit
  var sourceMap = {};
  for (var i = 0; i < sourceList.length; i++) {
      var sourceItem = sourceList[i];
      sourceMap[sourceItem.Product] = sourceItem.BaseISOUnit;
  }

  // Map BaseISOUnit to BaseUnit in the target list
  for (var j = 0; j < targetList.length; j++) {
      var targetItem = targetList[j];
      var material = targetItem.Material;
      if (sourceMap.hasOwnProperty(material)) {
          targetItem.BaseUnit = sourceMap[material];
      }
  }

 

Adding The Second Script TaskAdding the second script task

 

The last step in building the Process is mapping its output. When you click End, the side menu displays the Process Outputs section. Here, the value of the Purchase Requisition Items variable will be transferred to the result. Since both structures are of the same type, all fields will be automatically filled in.

Mapping The Process OutputMapping the Process output

 

The Process is now ready to be saved, released, and deployed to the Easy Purchase Requisition environment. This sequence can be executed by clicking Save and then Release. A pop-up will open, where you can enter the version details. Once released, you can click the generated version and select Deploy, then choose your environment and the destination used internally by the API (in our example, EPRProductV4).

 

Saving, Releasing, And Deploying  A ProcessSaving, releasing, and deploying a Process

 

Back at the lobby, you must publish your automation. If you do not, it will not be available to your Joule Skill. To publish, select the Process, navigate to the Versions tab, click the “” button on your Released version, and choose the option Publish to Library.

Publishing A ProcessPublishing a Process

 

Roles: functional consultants and developers.

The item-parsing prerequisites have been met by the automation we just generated. Now we can proceed with the creation of a Joule Skill.

First, we go to the project. From the SAP Build Lobby, choose Create > Joule Agent and Skill.

Creating A Joule Agent And Skill Project  From Sap Build LobbyCreating a Joule Agent and Skill project from SAP Build lobby

 

Provide a name and a description for your project.

Project Name And DescriptionProject name and description

If you need additional information on how to proceed with project creation, refer to the official documentation.

 

When the project opens, select Create > Joule Skill.

Creating A Joule SkillCreating a Joule Skill

 

In the pop-up, give your new capability a meaningful name and a detailed description of the purpose it covers. Remember: descriptions help the agent identify the most relevant case for a request from your business user. You should include information about the goal and the parameters expected for its execution.

Joule Skill - Initial ParametersJoule Skill – initial parameters

 

For our use case, we set the name to “Create a Purchase Requisition” and the description to “A skill to create any type of purchase requisition, based on the PR type and items described through a single prompt, including custom fields (e.g. ZJoule)”.

 

Structuring a Joule Skill is very similar to structuring a Process or an Automation in SAP Build. You will have access to a low-code tool to structure the process flow. To learn everything you can do in a Joule Skill, refer to the documentation Create a Joule Skill.

 

First, we need to define the behavior of the Joule Skill. By clicking anywhere on the background and then on the << button, the configurations will be displayed. In the General tab, you will find the name and description proposed earlier. If necessary, you can also correct or enhance them. There are also two switches:

  • Allow Joule to generate a response: when enabled, this option allows Joule to extract information from the skill and turn it into a response. If you decide not to enable it, you will need to configure the “Send Message” steps to deliver the response. For more information on managing responses, refer to “Send a Response”.
  • Allow skill to be started directly by a user: when enabled, this option allows the capability to be invoked directly by the user, i.e., without an agent or another capability acting as an intermediary. When this option is disabled, the Joule Skill must necessarily have at least one output parameter.

General Tab - Joule SkillGeneral tab – Joule Skill

 

For this use case, we used:

  • Allow Joule to generate a response: enabled. This will allow Joule to capture the capability’s return and compose its own response.
  • Allow skill to be started directly by a user: disabled. Since there is a step that captures and stringifies the items with a specific structure, it is not advisable to allow the user to invoke the skill directly. We will delegate to the Joule Agent the task of formatting the parameters and invoking the capability.

 

On the Parameters tab, just as we did for the Process, we define the input and output parameters and variables.

Setting Input And Output ParametersSetting input and output parameters

 

In our procurement example, the parameters were configured according to the table below:

Parameter type

Name

Description

Type

Required

List

Skill Input

Purchase Requisition Items

Purchase requisition items as a stringified array

String

Yes

No

Skill Input

Purchase Requisition Type

Purchase requisition type

String

Yes

No

Skill Input

Purchase Requisition Description

Purchase requisition description

String

No

No

Skill Input

Purchase Requisition Header Note

Purchase requisition header note

String

No

No

Skill Output

Purchase Requisition ID

Generated purchase requisition ID

String

No

No

Skill Variable

Purchase Requisition ID

Purchase requisition ID

String

N/A

Yes

 

After completing the parameter configuration, we will create the flow to be executed. To do so, click the + symbol and insert the steps, mapping the inputs and outputs of each activity type.

 Adding A Process Task To Joule SkillAdding a Process task to Joule Skill

 

In our example, we started by inserting the Process Easy Purchase Requisition – Item Parsing in order to transform and enrich the items described by the input parameter. We requested that execution be awaited until completion so that the process could return its output, containing a list of items with their respective ISO units of measure mapped.

The table below summarizes the configuration applied to this step:

Parameter type

Name

Value

General

Step Name

Map Items

General

Wait for Output Result

true

Input

Purchase Requisition Items – In

Skill Inputs > Purchase requisition items

 

Adding An Action CallAdding an Action call

 

Mapping Action ParametersMapping Action parameters

 

Next, we inserted the Action (API) call to create a purchase requisition. Similar to what was done in the Process for the products Action, here we mapped the inputs and outputs. We also defined an environment variable that holds the destination name to be used by the API call.

The following table describes this mapping:

Parameter type

Name

Value

General

Step Name

Create a purchase requisition

General

Destination Value

DESTINATION_PR

Input

_PurchaseRequisitionItem (Bind List)

Map items > list – Purchase Requisition Items

Input

_PurchaseRequisitionItem > BaseUnitISOCode

Map items > list – Purchase Requisition Items > BaseUnit

Input

_PurchaseRequisitionItem > DeliveryDate

Map items > list – Purchase Requisition Items > DeliveryDate

Input

_PurchaseRequisitionItem > Material

Map items > list – Purchase Requisition Items > Material

Input

_PurchaseRequisitionItem > Plant

Map items > list – Purchase Requisition Items > Plant

Input

_PurchaseRequisitionItem > PurchaseRequisition

Map items > list – Purchase Requisition Items > PurchaseRequisition

Input

_PurchaseRequisitionItem > PurchaseRequisitionItem

Map items > list – Purchase Requisition Items > PurchaseRequisitionItem

Input

_PurchaseRequisitionItem > RequestedQuantity

Map items > list – Purchase Requisition Items > RequestedQuantity

Input

_PurchaseRequisitionItem > ZJoule

Map items > list – Purchase Requisition Items > ZZ1_ZJoule_PRI

Input

PurchaseRequisition

Custom variables > Purchase requisition ID

Input

PurchaseRequisitionType

Skill Inputs > Purchase requisition type

Input

PurchaseReqnDescription

Skill Inputs > Purchase requisition description

Input

PurReqnHeaderNote

Skill Inputs > Purchase requisition header note

 

Setting Variables And Skill OutputSetting variables and Skill output

 

The last step assigns the resulting purchase requisition ID value, generated through the API call, to a local variable using the Set Variables activity.

Section

Name

Value

General

Step Name

Set purchase requisition ID

General > Set Custom Variable

Purchase requisition ID

Create a purchase requisition > result > PurchaseRequisition

 

Then we set the value of this same variable as our Skill Output.

Mapping The Skill OutputMapping the Skill output

 

Click Save to store your Joule Skill. You don’t need to release or deploy it right now, since we are still creating the Joule Agent in part 3.

 

If you have made it this far and followed the example, you now know how to:

  • Identify the structure of a Joule Skill.
  • Distinguish a Joule Skill from a Joule Agent (deterministic vs. reasoning).
  • Create a Process or an Automation and use these objects to support item parsing.
  • Create a project for Joule Skills and Agents and start building a Joule Skill.
  • Consume Actions and build a logical flow for your custom capability, mapping inputs and outputs.

Always remember the importance of properly describing the capability and its parameters to facilitate the agent’s inference process.

 

In the next and final post in this series, we will cover the creation of the Joule Agent. There, we will use the artifacts from this guide to enable the Joule Agent to perform the desired operation. We will also walk through the process of making your agent available, allowing it to coexist with the standard Joule capabilities in SAP S/4HANA Cloud Private Edition. Thanks for reading, and see you there!

 

Thanks,
Brought to you by the SAP S/4HANA RIG Team



Source link

Leave a Reply

Your email address will not be published. Required fields are marked *