Building Custom Joule Capabilities: Getting Started
Share

[[{“value”:”

Introduction

Joule Studio offers a guided, visual experience for building custom skills and agents that works well for most scenarios. For developers who want more flexibility or prefer working directly with YAML, a pro-code path is also supported via the Joule Studio CLI. This small series focuses on the latter approach to provide a starting point.

This post is the foundation of a multiple-part series. The posts that follow each tackle a specific capability pattern that may be interesting in real projects:

  • Blog Post 1: Role-Based Access Control: Restricting which users can invoke which scenarios and actions based on their IAS group membership and other IAS user attributes. 
  • Blog Post 2: Document Grounding (To come): Connecting Joule to enterprise document repositories so responses are grounded in your organization’s actual content on top of the available standard shipped knowledge.
  • Blog Post 3: File Upload (To come): Accepting files that users upload directly within a Joule conversation and routing those files through to your backend APIs.

Of course, I did not forget about integrating code-based agents into Joule. This has already been done by @felixbartler ‘s blog post: Joule A2A: Connect Code-Based Agents into Joule. It covers a complementary approach using the Agent-to-Agent protocol. So feel free to check that out!

The patterns covered in this series and the A2A approach are not mutually exclusive. For example, you could combine role-based access control with a code-based agent integration to restrict which users can trigger powerful backend agents, or use document grounding alongside a custom agent to enrich its responses with your organization’s content. Think of each post as a building block that you can mix and match to create more capable and production-ready Joule extensions.

Before any of that is possible, you need a working development environment. That is what this post covers.

 

Architecture: The DTA Hierarchy

Before touching any tooling, it is worth understanding how Joule organizes extensibility artifacts. Everything you build lives within a hierarchy called Design Time Artifacts, or DTAs. The levels of that hierarchy, from broadest to most granular one are:

Digital Assistant  (da.sapdas.yaml)
└── Capability     (capability.sapdas.yaml)
    └── Scenario
        └── Function
            └── Action Group
                └── Action

Digital Assistant is the top-level container. Your da.sapdas.yaml file defines which capabilities belong to your assistant and how they compose together. Think of it as a project manifest that the Joule platform reads to understand what your assistant can do. Creating your own digital assistant is also how you publish a standalone Joule experience: when you deploy a custom DA, it gets its own URL, and callers can tell from the URL which specific assistant they are talking to. This is different from the sap_digital_assistant, which is the central, unified Joule assistant that SAP ships out of the box. If you only want to extend the sap_digital_assistant rather than create a standalone assistant, you do not need a da.sapdas.yaml file at all. You can deploy your skill or agent directly and it will be picked up by the central assistant.

Capability is a self-contained unit of functionality. Each capability lives in its own folder and has its own capability.sapdas.yaml. A capability might represent “HR Leave Management” or “Invoice Processing.”

Scenario is a conversational context within a capability. This is where Joule’s skill-matching logic comes in: Joule reads your scenario’s description at runtime and uses it to decide whether the user’s message should route to this scenario. The quality of your scenario description directly affects how reliably Joule invokes your capability. For the “HR Leave Management” capability, you might have a scenario named check_leave_balance described as “User wants to check their remaining leave balance or submit a new leave request”. Joule would match that scenario when a user asks something like “How many vacation days do I have left?”

Function is the logic unit within a scenario. Functions define input parameters, response templates, and the action groups that do the actual work. Continuing the example above, the check_leave_balance scenario might contain a function named get_leave_balance that takes an employee ID as input, calls the HR backend, and formats the remaining days into a response.

Action Group is a container for one or more actions that execute together as a logical unit. Action groups let you compose multiple backend calls (for example, first authenticating, then fetching data) into a single operation.

Action is the atomic unit of work. An action calls a backend system (via a system alias), processes data, or formats a response. This is where your HTTP calls, custom scripting (SpEL or Handlebars), and response mappings live.

The namespace you use depends on your deployment target. If you are extending the central sap_digital_assistant (the unified Joule assistant), your capability must use the joule.ext namespace. If you are building a standalone Digital Assistant, you can use other namespaces as well. joule.ext is not required.

The hierarchy above covers the core files, but a DTA can include additional optional files for things like translations, input forms, and configuration schemas. The full specification, including all supported file types and their formats, is in the SAP Joule DTA Specification.

 

Prerequisites

Before installing the CLI, make sure you have the following in place.

SAP BTP Subaccount with Joule Enabled

You need an SAP BTP subaccount where Joule is activated as a service. This is not just about having a BTP account. Joule must be explicitly entitled and enabled. Work with your BTP administrator if you are unsure whether Joule is available in your subaccount.

SAP Identity Authentication Service (IAS) Tenant

Your IAS tenant handles authentication for the Joule CLI. You will configure an OpenID Connect application in IAS that allows the CLI to authenticate on your behalf. If your organization already uses IAS for BTP services, you likely already have a tenant. You just need the right permissions to create new applications within it.

Required Roles

Three roles must be assigned to your user before you can complete the full development loop:

  • end_user: Allows you to interact with Joule as a consumer, so you can test capabilities you have deployed.
  • capability_admin: Allows you to manage and deploy capabilities to your Joule instance.
  • extensibility_developer: Allows you to develop and publish custom capabilities. This is the role most commonly forgotten. Without it, deployment commands will fail with authorization errors.

Node.js

The Joule CLI requires Node.js in the version range v20.12.0 through v24.x. Verify your installation with node --version. If you manage Node versions with nvm, run nvm use 20 to switch to a supported version.

VS Code, SAP Business Application Studio, or another IDE

For local development, VS Code is a solid choice with good YAML tooling available. SAP Business Application Studio (BAS) is a good alternative, especially if you are already working within SAP BTP, since it is available directly from the BTP cockpit and comes preconfigured for SAP development scenarios.

If you want the most integrated experience, SAP also offers the Joule Studio Code Editor, a purpose-built extension for DTA development that provides inline validation, quick fixes, and autocompletion tailored specifically to Joule’s YAML schemas. It also provides a visual view of your connections to different Joule tenants, deployed assistants and deployed capabilities, as well as the option to deploy easily per single click.

 

Installing the Joule CLI

The Joule Studio CLI is distributed as an npm global package:

npm install -g @sap/joule-studio-cli

Verify the installation:

joule –version

The CLI exposes the following commands you will use throughout your development workflow:

Command Purpose
joule login Authenticate with your BTP/IAS environment
joule lint Validate DTA YAML files against their schemas
joule compile Compile capabilities into a deployable .daar artifact
joule deploy Deploy a compiled artifact to your Joule instance
joule launch Activate a deployed capability for testing
joule update Push changes to an already-deployed capability

 

Authentication: Connecting the CLI to Your Environment

Authentication is where most developers encounter their first wall. The Joule CLI authenticates through your IAS tenant via OpenID Connect. The setup involves two steps in the IAS admin console, followed by running joule login.

Step 1: Create an OpenID Connect Application in IAS

You can find the documentation about it here .

In your IAS admin console , e.g. https://<your-tenant>.accounts.ondemand.com/admin

  1. Navigate to Applications and Resources > Applications.
  2. Click Create to add a new application.
  3. Set the application type to OpenID Connect.
  4. Give it a recognizable name, such as “Joule CLI Development”.
  5. Click Create.

Step 2: Add the Cli2Joule Dependency

Still in the IAS admin console, in your newly created application:

  1. Go to the Dependencies tab.
  2. Click Add and add the dependency name as Cli2Joule.
  3. Select the das-ias Application.
  4. Select joule-dt-api as API and save.

Step 3: Generate a Secret for Authentication

  1. In the Application under Client Authentication, click Add to generate a new client secret.
  2. Select all the API Access options.
  3. Save.
  4. Copy and save the Client ID and Client Secret. You will need both during joule login.

Step 4: Run joule login

With your IAS application configured, run:

joule login

The CLI will prompt you interactively for the following:

  • Authentication URL: Your IAS tenant URL, for example https://asqo0ewqz2.accounts.ondemand.com
  • API URL: Your Joule subdomain URL, which you can find in the BTP Cockpit when you open your Joule Instance
  • Client ID: The client ID from your IAS application
  • Client Secret: The secret you generated in Step 3
  • Username: Your IAS user email
  • Password: Your IAS user password

On success, the CLI stores credentials locally and you are ready to develop. If you hit an error, the most common causes are an incorrect API URL or a missing Cli2Joule dependency. Double-check both before digging further.

 

Project Structure

A Joule extensibility project follows a specific folder convention that the CLI expects:

my-joule-project/
├── da.sapdas.yaml                        # Digital Assistant manifest
└── capabilities/
    └── my_capability/
        ├── capability.sapdas.yaml        # Capability definition
        ├── scenarios/
        │   └── main_scenario.yaml        # Scenario definitions
        └── functions/
            └── core_function.yaml        # Function definitions

The da.sapdas.yaml lives at the project root. Each capability lives in its own subfolder under capabilities/. The CLI discovers capabilities by following the folder references in da.sapdas.yaml.

 

A Concrete Example: The Northwind Products Capability

Abstract YAML schemas are easier to grasp when you can see a working example alongside them. The capability used for this first blog post queries the public Northwind OData service to look up product details by name. It is intentionally minimal: one scenario, one function, one API call. That makes it the right size to understand all the moving parts without getting lost in business logic.

Here is the full project structure for this capability:

├── da.sapdas.yaml
└── capabilities/
    └── northwind_products/
        ├── capability.sapdas.yaml
        ├── scenarios/
        │   └── get_product.yaml
        └── functions/
            └── get_product.yaml

 

The Digital Assistant Manifest

The da.sapdas.yaml at the project root registers the capability:

schema_version: 1.4.0
name: joule_dev_blog_assistant
capabilities:
– type: local
folder: ./capabilities/northwind_products

The assistant name must be unique within your tenant. The type: local entry points to a folder path relative to this file.

 

The Capability Definition

schema_version: 3.27.0

metadata:
namespace: joule.ext
name: northwind_products
version: 1.0.0
display_name: Northwind_Products
description: >
Look up product information from the Northwind OData service

system_aliases:
NorthwindService:
destination: Northwind

Two things are worth pointing out here. The namespace is joule.ext because this capability extends the central SAP Digital Assistant. If you were building a standalone assistant, you could use others namespaces as well. The system_aliases block maps the logical alias NorthwindService to the BTP destination named Northwind. Your function YAML files reference the logical alias, never the destination name directly, which keeps environment-specific URLs out of your source files.

 

The BTP Destination

The system_aliases entry references a BTP destination by name. You need to create that destination in your subaccount before deploying. Go to Connectivity > Destinations in the BTP cockpit and create a new destination with the following properties:

Property Value
Name Northwind
Type HTTP
URL https://services.odata.org/V4/Northwind/Northwind.svc
Proxy Type Internet
Authentication NoAuthentication

The name here must match the destination value in system_aliases exactly.

 

The Scenario

The scenario is what Joule reads to decide whether a user’s message should be routed to this capability. Its description is not documentation for developers. It is a routing signal for the LLM:

description: >-
Look up a product from the Northwind catalog by name. Returns the product name,
unit price, and current stock level.

slots:
– name: product_name
description: The name of the product to look up in the Northwind catalog

target:
type: function
name: get_product

response_context:
– value: $target_result.product_name
description: Name of the product
– value: $target_result.unit_price
description: Unit price of the product
– value: $target_result.units_in_stock
description: Current number of units in stock

The slots block tells Joule which parameters to extract from the user’s message before invoking the function. In this case, product_name is extracted from utterances like “give me product info for Chai.” The slot name must match a parameter name in the target function.

The target block is a reference to functions/get_product.yaml.

The response_context block maps function output variables back to human-readable labels. Joule uses these labels when it generates the response text for the user automatically. Each value must be prefixed with $target_result to reference the function’s result variables.

 

The Dialog Function

The function does the actual work: it calls the Northwind OData API, handles the success and error cases, and returns structured data for the scenario to pass back to the user:

parameters:
– name: product_name

action_groups:
– actions:
– type: api-request
method: GET
system_alias: NorthwindService
path: “/Products?$filter=contains(tolower(ProductName),tolower(‘<? product_name ?>’))&$select=ProductName,UnitPrice,UnitsInStock&$top=1”
result_variable: api_result

– condition: api_result.status_code == 200 && api_result.body.value != null && api_result.body.value.size() > 0
actions:
– type: set-variables
variables:
– name: product
value: <? api_result.body.value[0] ?>

– condition: api_result.status_code != 200 || api_result.body.value == null || api_result.body.value.size() == 0
actions:
– type: message
message:
type: text
content: “No product matching ‘<? product_name ?>’ was found in the Northwind catalog.”

result:
product_name: <? product.ProductName ?>
unit_price: <? product.UnitPrice ?>
units_in_stock: <? product.UnitsInStock ?>

A few patterns here are worth understanding:

The first action group has no condition, which means it always runs. It fires the GET request and stores the full HTTP response in api_result. That variable is then available to all subsequent action groups.

The second and third action groups each have a condition. Notice that conditions use raw SpEL expressions without any delimiters. condition: api_result.status_code == 200 is correct; condition: <? api_result.status_code == 200 ?> would cause a compile error. The <? ?> delimiters are for embedding SpEL inside string fields like path, content, and value. Conditions evaluate SpEL natively.

The result block at the bottom maps function output to the names that $target_result references in the scenario’s response_context.

The complete source for this capability, along with all other examples from this series, is available on GitHub.

 

The Deployment Process

Once your project structure is in place and authentication is configured, the development cycle could  look like this:

  1. Validate with lint

joule lint

The linter validates your YAML files against their schemas, catches structural errors, and flags missing required fields. You can run this before every compile. It saves you from cryptic errors downstream.

  • Compile

joule compile

Compiles and produces a .daar artifact ready for deployment. A clean compile means your DTAs are structurally sound.

  • Deploy 

joule deploy -c -n “northwind_products_assistant”

The -c flag compiles the capability before deploying it, reducing the need to manually run the compile command first. The -n flag assigns a name you can reference in subsequent commands. Use a descriptive name that identifies this as a test deployment.

  • Launch for testing

joule launch “northwind_products_assistant”

This activates the deployed capability and opens your Joule instance in a browser session where you can exercise it through natural language.

For deploying a specific capability to the unified Joule assistant (sap_digital_assistant) rather than a standalone assistant, use:

joule update “sap_digital_assistant” –capability-file capability.sapdas.yaml

This updates a specific capability within an existing assistant by name. Run this command from the capability folder, otherwise you will need to provide the correct relative path to capability.sapdas.yaml.

 

Seeing It Run

After compiling and deploying, the capability works in both a standalone Joule web client and when published to the unified SAP Digital Assistant, which can run for example in an S/4HANA Private Cloud Fiori Launchpad (if integrated with the Joule instance).

Standalone Joule Web Client (“northwind_assistant” , see the URL):

Result_standalone.png

The same capability published to the unified Joule assistant, embedded in an S/4HANA Private Cloud Fiori Launchpad and runs along all the other available Joule capabilities:

Result_S4PCE_Unified.png

 

What is Next

With this environment set up, you have everything you need to extend Joule with custom capabilities. The next posts each focus on one specific functionalities:

Blog Post 1: Role-Based Access Control: How to use IAS groups and other IAS attributes to gate access to scenarios and actions within your capability.

Blog Post 2: Document Grounding (To come): How to configure a Joule capability to query a connected document repository at runtime, grounding the model’s response in your organization’s actual content. 

Blog Post 3: File Upload (To come): How to enable users to upload files within a Joule conversation and have those files routed to your backend. 

Resources

 

“}]] 

 [[{“value”:”IntroductionJoule Studio offers a guided, visual experience for building custom skills and agents that works well for most scenarios. For developers who want more flexibility or prefer working directly with YAML, a pro-code path is also supported via the Joule Studio CLI. This small series focuses on the latter approach to provide a starting point.This post is the foundation of a multiple-part series. The posts that follow each tackle a specific capability pattern that may be interesting in real projects:Blog Post 1: Role-Based Access Control: Restricting which users can invoke which scenarios and actions based on their IAS group membership and other IAS user attributes. Blog Post 2: Document Grounding (To come): Connecting Joule to enterprise document repositories so responses are grounded in your organization’s actual content on top of the available standard shipped knowledge.Blog Post 3: File Upload (To come): Accepting files that users upload directly within a Joule conversation and routing those files through to your backend APIs.Of course, I did not forget about integrating code-based agents into Joule. This has already been done by @felixbartler ‘s blog post: Joule A2A: Connect Code-Based Agents into Joule. It covers a complementary approach using the Agent-to-Agent protocol. So feel free to check that out!The patterns covered in this series and the A2A approach are not mutually exclusive. For example, you could combine role-based access control with a code-based agent integration to restrict which users can trigger powerful backend agents, or use document grounding alongside a custom agent to enrich its responses with your organization’s content. Think of each post as a building block that you can mix and match to create more capable and production-ready Joule extensions.Before any of that is possible, you need a working development environment. That is what this post covers. Architecture: The DTA HierarchyBefore touching any tooling, it is worth understanding how Joule organizes extensibility artifacts. Everything you build lives within a hierarchy called Design Time Artifacts, or DTAs. The levels of that hierarchy, from broadest to most granular one are:Digital Assistant (da.sapdas.yaml)
└── Capability (capability.sapdas.yaml)
└── Scenario
└── Function
└── Action Group
└── ActionDigital Assistant is the top-level container. Your da.sapdas.yaml file defines which capabilities belong to your assistant and how they compose together. Think of it as a project manifest that the Joule platform reads to understand what your assistant can do. Creating your own digital assistant is also how you publish a standalone Joule experience: when you deploy a custom DA, it gets its own URL, and callers can tell from the URL which specific assistant they are talking to. This is different from the sap_digital_assistant, which is the central, unified Joule assistant that SAP ships out of the box. If you only want to extend the sap_digital_assistant rather than create a standalone assistant, you do not need a da.sapdas.yaml file at all. You can deploy your skill or agent directly and it will be picked up by the central assistant.Capability is a self-contained unit of functionality. Each capability lives in its own folder and has its own capability.sapdas.yaml. A capability might represent “HR Leave Management” or “Invoice Processing.”Scenario is a conversational context within a capability. This is where Joule’s skill-matching logic comes in: Joule reads your scenario’s description at runtime and uses it to decide whether the user’s message should route to this scenario. The quality of your scenario description directly affects how reliably Joule invokes your capability. For the “HR Leave Management” capability, you might have a scenario named check_leave_balance described as “User wants to check their remaining leave balance or submit a new leave request”. Joule would match that scenario when a user asks something like “How many vacation days do I have left?”Function is the logic unit within a scenario. Functions define input parameters, response templates, and the action groups that do the actual work. Continuing the example above, the check_leave_balance scenario might contain a function named get_leave_balance that takes an employee ID as input, calls the HR backend, and formats the remaining days into a response.Action Group is a container for one or more actions that execute together as a logical unit. Action groups let you compose multiple backend calls (for example, first authenticating, then fetching data) into a single operation.Action is the atomic unit of work. An action calls a backend system (via a system alias), processes data, or formats a response. This is where your HTTP calls, custom scripting (SpEL or Handlebars), and response mappings live.The namespace you use depends on your deployment target. If you are extending the central sap_digital_assistant (the unified Joule assistant), your capability must use the joule.ext namespace. If you are building a standalone Digital Assistant, you can use other namespaces as well. joule.ext is not required.The hierarchy above covers the core files, but a DTA can include additional optional files for things like translations, input forms, and configuration schemas. The full specification, including all supported file types and their formats, is in the SAP Joule DTA Specification. PrerequisitesBefore installing the CLI, make sure you have the following in place.SAP BTP Subaccount with Joule EnabledYou need an SAP BTP subaccount where Joule is activated as a service. This is not just about having a BTP account. Joule must be explicitly entitled and enabled. Work with your BTP administrator if you are unsure whether Joule is available in your subaccount.SAP Identity Authentication Service (IAS) TenantYour IAS tenant handles authentication for the Joule CLI. You will configure an OpenID Connect application in IAS that allows the CLI to authenticate on your behalf. If your organization already uses IAS for BTP services, you likely already have a tenant. You just need the right permissions to create new applications within it.Required RolesThree roles must be assigned to your user before you can complete the full development loop:end_user: Allows you to interact with Joule as a consumer, so you can test capabilities you have deployed.capability_admin: Allows you to manage and deploy capabilities to your Joule instance.extensibility_developer: Allows you to develop and publish custom capabilities. This is the role most commonly forgotten. Without it, deployment commands will fail with authorization errors.Node.jsThe Joule CLI requires Node.js in the version range v20.12.0 through v24.x. Verify your installation with node –version. If you manage Node versions with nvm, run nvm use 20 to switch to a supported version.VS Code, SAP Business Application Studio, or another IDEFor local development, VS Code is a solid choice with good YAML tooling available. SAP Business Application Studio (BAS) is a good alternative, especially if you are already working within SAP BTP, since it is available directly from the BTP cockpit and comes preconfigured for SAP development scenarios.If you want the most integrated experience, SAP also offers the Joule Studio Code Editor, a purpose-built extension for DTA development that provides inline validation, quick fixes, and autocompletion tailored specifically to Joule’s YAML schemas. It also provides a visual view of your connections to different Joule tenants, deployed assistants and deployed capabilities, as well as the option to deploy easily per single click. Installing the Joule CLIThe Joule Studio CLI is distributed as an npm global package:npm install -g @sap/joule-studio-cliVerify the installation:joule –versionThe CLI exposes the following commands you will use throughout your development workflow:CommandPurposejoule loginAuthenticate with your BTP/IAS environmentjoule lintValidate DTA YAML files against their schemasjoule compileCompile capabilities into a deployable .daar artifactjoule deployDeploy a compiled artifact to your Joule instancejoule launchActivate a deployed capability for testingjoule updatePush changes to an already-deployed capability Authentication: Connecting the CLI to Your EnvironmentAuthentication is where most developers encounter their first wall. The Joule CLI authenticates through your IAS tenant via OpenID Connect. The setup involves two steps in the IAS admin console, followed by running joule login.Step 1: Create an OpenID Connect Application in IASYou can find the documentation about it here .In your IAS admin console , e.g. https://<your-tenant>.accounts.ondemand.com/adminNavigate to Applications and Resources > Applications.Click Create to add a new application.Set the application type to OpenID Connect.Give it a recognizable name, such as “Joule CLI Development”.Click Create.Step 2: Add the Cli2Joule DependencyStill in the IAS admin console, in your newly created application:Go to the Dependencies tab.Click Add and add the dependency name as Cli2Joule.Select the das-ias Application.Select joule-dt-api as API and save.Step 3: Generate a Secret for AuthenticationIn the Application under Client Authentication, click Add to generate a new client secret.Select all the API Access options.Save.Copy and save the Client ID and Client Secret. You will need both during joule login.Step 4: Run joule loginWith your IAS application configured, run:joule loginThe CLI will prompt you interactively for the following:Authentication URL: Your IAS tenant URL, for example https://asqo0ewqz2.accounts.ondemand.comAPI URL: Your Joule subdomain URL, which you can find in the BTP Cockpit when you open your Joule InstanceClient ID: The client ID from your IAS applicationClient Secret: The secret you generated in Step 3Username: Your IAS user emailPassword: Your IAS user passwordOn success, the CLI stores credentials locally and you are ready to develop. If you hit an error, the most common causes are an incorrect API URL or a missing Cli2Joule dependency. Double-check both before digging further. Project StructureA Joule extensibility project follows a specific folder convention that the CLI expects:my-joule-project/
├── da.sapdas.yaml # Digital Assistant manifest
└── capabilities/
└── my_capability/
├── capability.sapdas.yaml # Capability definition
├── scenarios/
│ └── main_scenario.yaml # Scenario definitions
└── functions/
└── core_function.yaml # Function definitionsThe da.sapdas.yaml lives at the project root. Each capability lives in its own subfolder under capabilities/. The CLI discovers capabilities by following the folder references in da.sapdas.yaml. A Concrete Example: The Northwind Products CapabilityAbstract YAML schemas are easier to grasp when you can see a working example alongside them. The capability used for this first blog post queries the public Northwind OData service to look up product details by name. It is intentionally minimal: one scenario, one function, one API call. That makes it the right size to understand all the moving parts without getting lost in business logic.Here is the full project structure for this capability:├── da.sapdas.yaml
└── capabilities/
└── northwind_products/
├── capability.sapdas.yaml
├── scenarios/
│ └── get_product.yaml
└── functions/
└── get_product.yaml The Digital Assistant ManifestThe da.sapdas.yaml at the project root registers the capability:schema_version: 1.4.0
name: joule_dev_blog_assistant
capabilities:
– type: local
folder: ./capabilities/northwind_productsThe assistant name must be unique within your tenant. The type: local entry points to a folder path relative to this file. The Capability Definitionschema_version: 3.27.0

metadata:
namespace: joule.ext
name: northwind_products
version: 1.0.0
display_name: Northwind_Products
description: >
Look up product information from the Northwind OData service

system_aliases:
NorthwindService:
destination: NorthwindTwo things are worth pointing out here. The namespace is joule.ext because this capability extends the central SAP Digital Assistant. If you were building a standalone assistant, you could use others namespaces as well. The system_aliases block maps the logical alias NorthwindService to the BTP destination named Northwind. Your function YAML files reference the logical alias, never the destination name directly, which keeps environment-specific URLs out of your source files. The BTP DestinationThe system_aliases entry references a BTP destination by name. You need to create that destination in your subaccount before deploying. Go to Connectivity > Destinations in the BTP cockpit and create a new destination with the following properties:PropertyValueNameNorthwindTypeHTTPURLhttps://services.odata.org/V4/Northwind/Northwind.svcProxy TypeInternetAuthenticationNoAuthenticationThe name here must match the destination value in system_aliases exactly. The ScenarioThe scenario is what Joule reads to decide whether a user’s message should be routed to this capability. Its description is not documentation for developers. It is a routing signal for the LLM:description: >-
Look up a product from the Northwind catalog by name. Returns the product name,
unit price, and current stock level.

slots:
– name: product_name
description: The name of the product to look up in the Northwind catalog

target:
type: function
name: get_product

response_context:
– value: $target_result.product_name
description: Name of the product
– value: $target_result.unit_price
description: Unit price of the product
– value: $target_result.units_in_stock
description: Current number of units in stockThe slots block tells Joule which parameters to extract from the user’s message before invoking the function. In this case, product_name is extracted from utterances like “give me product info for Chai.” The slot name must match a parameter name in the target function.The target block is a reference to functions/get_product.yaml.The response_context block maps function output variables back to human-readable labels. Joule uses these labels when it generates the response text for the user automatically. Each value must be prefixed with $target_result to reference the function’s result variables. The Dialog FunctionThe function does the actual work: it calls the Northwind OData API, handles the success and error cases, and returns structured data for the scenario to pass back to the user:parameters:
– name: product_name

action_groups:
– actions:
– type: api-request
method: GET
system_alias: NorthwindService
path: “/Products?$filter=contains(tolower(ProductName),tolower(‘<? product_name ?>’))&$select=ProductName,UnitPrice,UnitsInStock&$top=1”
result_variable: api_result

– condition: api_result.status_code == 200 && api_result.body.value != null && api_result.body.value.size() > 0
actions:
– type: set-variables
variables:
– name: product
value: <? api_result.body.value[0] ?>

– condition: api_result.status_code != 200 || api_result.body.value == null || api_result.body.value.size() == 0
actions:
– type: message
message:
type: text
content: “No product matching ‘<? product_name ?>’ was found in the Northwind catalog.”

result:
product_name: <? product.ProductName ?>
unit_price: <? product.UnitPrice ?>
units_in_stock: <? product.UnitsInStock ?>A few patterns here are worth understanding:The first action group has no condition, which means it always runs. It fires the GET request and stores the full HTTP response in api_result. That variable is then available to all subsequent action groups.The second and third action groups each have a condition. Notice that conditions use raw SpEL expressions without any delimiters. condition: api_result.status_code == 200 is correct; condition: <? api_result.status_code == 200 ?> would cause a compile error. The <? ?> delimiters are for embedding SpEL inside string fields like path, content, and value. Conditions evaluate SpEL natively.The result block at the bottom maps function output to the names that $target_result references in the scenario’s response_context.The complete source for this capability, along with all other examples from this series, is available on GitHub. The Deployment ProcessOnce your project structure is in place and authentication is configured, the development cycle could  look like this:Validate with lintjoule lintThe linter validates your YAML files against their schemas, catches structural errors, and flags missing required fields. You can run this before every compile. It saves you from cryptic errors downstream.Compilejoule compileCompiles and produces a .daar artifact ready for deployment. A clean compile means your DTAs are structurally sound.Deploy joule deploy -c -n “northwind_products_assistant”The -c flag compiles the capability before deploying it, reducing the need to manually run the compile command first. The -n flag assigns a name you can reference in subsequent commands. Use a descriptive name that identifies this as a test deployment.Launch for testingjoule launch “northwind_products_assistant”This activates the deployed capability and opens your Joule instance in a browser session where you can exercise it through natural language.For deploying a specific capability to the unified Joule assistant (sap_digital_assistant) rather than a standalone assistant, use:joule update “sap_digital_assistant” –capability-file capability.sapdas.yamlThis updates a specific capability within an existing assistant by name. Run this command from the capability folder, otherwise you will need to provide the correct relative path to capability.sapdas.yaml. Seeing It RunAfter compiling and deploying, the capability works in both a standalone Joule web client and when published to the unified SAP Digital Assistant, which can run for example in an S/4HANA Private Cloud Fiori Launchpad (if integrated with the Joule instance).Standalone Joule Web Client (“northwind_assistant” , see the URL):The same capability published to the unified Joule assistant, embedded in an S/4HANA Private Cloud Fiori Launchpad and runs along all the other available Joule capabilities: What is NextWith this environment set up, you have everything you need to extend Joule with custom capabilities. The next posts each focus on one specific functionalities:Blog Post 1: Role-Based Access Control: How to use IAS groups and other IAS attributes to gate access to scenarios and actions within your capability.Blog Post 2: Document Grounding (To come): How to configure a Joule capability to query a connected document repository at runtime, grounding the model’s response in your organization’s actual content. Blog Post 3: File Upload (To come): How to enable users to upload files within a Joule conversation and have those files routed to your backend. ResourcesSAP Joule Extensibility DocumentationDesign Time Artifacts ReferenceJoule CLI ReferenceSAP Identity Authentication Service “}]] Read More Technology Blog Posts by SAP articles 

#SAPCHANNEL

By ali

Leave a Reply