Skip to Content

Chatbots is one of the hot “topics de jour” – everyone seems to want one, or work on one, or wish they had one. In this blog, I’ll show how we can create a simple chatbot interacting with SAP Netweaver Gateway OData services. I’ll mention a couple of chatbot platforms, and show an example created with one of these, but this is not an approval or recommendation of specific tools or platforms to the detriment of others. My aim is just to show the technical orchestration of OData services from SAP (actually, we’ll touch on both XSJS services on SAP-CP and classical ABAP-driven OData), and point out a few of the “traps” you might stumble into along the way.

Prerequisites: a rudimentary knowledge of chatbots, Javascript and XSJS, some knowledge of http requests, ABAP Gateway services, OData, and… hey, let’s just get started, shall we? I’ll try to explain stuff along the way. And if I don’t, or only do it in a half-baked manner, feel free to ask!

Disclaimer

This is not a complete, fully working, super-adapted mega-cool chatbot capable of answering any question you may want to throw at it – but more an example of how to stitch some chatbot-building components together. Hopefully, you’ll be able to walk away with enough learnings to do something similar yourself.

Right. Let’s get started.

Scenario: you walk into the office one fine morning, fire up your PC, and start the process of checking on your purchase orders. After having inquired about the fifth one, and halfway through your second coffee, you stop for a moment. A thought has hit your caffeine-frenzied brain. “Hum. Wouldn’t it be great if I could just ask someone instead of doing this tedious task myself”.

Someone – or something. This is a typical scenario for a conversational app – a chatbot.

We’ll create a chatbot that can retrieve information about purchase orders. We will ask the chatbot questions such as “what are my current purchase orders”, or “what is the estimated delivery date of purchase order number X”. (Well, actually, to keep it simple, we’ll stick to the first question for now; I have a distinct feeling this blog will be long enough as it is – but I’ll provide you some clues as to the second question as well).

Here are the components we need:

  1. An OData service
  2. A chatbot framework
  3. SAP Cloud Platform
  4. Some nice developer tools – like Google Postman

Step 1: Creating an OData service

We’ll start by creating an OData service for – purchase orders. Of course, we can create a service for anything we want – if your use case is different, fine! Also, if you’re already in possession of a service, fine. Use it!

ABAP OData services are created using the Gateway Service Builder, transaction SEGW (or “Segway”, as I lovingly call it). Describing how to do this is a bit outside the scope of this blog, so I’ll only touch lightly on it. In our case, we’ll start by looking at the Business Object Repository, or transaction BAPI. Here, we can search for any business object in our system, and check for existing methods for information retrieval.

We’ve found our desired object – PurchaseOrder – and selected a suitable method.

(At this point, if you’re a real nitpicker, you’ll notice that the method – GetDetail – has been replaced by GetDetail1 as of NW 7.02. It’s not relevant for our use case, but clearly shows a lack of attention to detail on behalf of the author…)

Anyway, testing our function shows some results (I’ve used an existing PO number which i conveniently retrieved from table EKKO as input parameter).

Now, all we have to do is fire up our Segway, uhm, SEGW, and generate a new service based on the related function module, BAPI_PO_GETDETAIL.

We use the SEGW wizard to create the necessary Entity Type from the function module:

Then, we select which of the function return parameters we need in our service:

This is clearly overkill, by the way – but, hey, I was trying to create a very generic PO service which will be capable of retrieving, well, everything.

I’ll skip the rest of the entity set creation process; suffice to say that you need to remember setting key properties for all the “nodes” in this structure, and also make sure you define relevant fields as “nullable” and search/sortable. The first thing is particularly important, as you will otherwise end up with error messages if some fields are returned empty when calling the service.

But as a seasoned Gateway developer you probably already knew that, right?

In the end, we have our model and data provider classes:

The remaining work here is to redefine the GET_ENTITYSET (and GET_ENTITY) methods of the data provider class – again, this should be well known to Gateway service developers. Honestly, you didn’t come here for that, you came here for the chatbot magic!

Finally, we register the new service on our Gateway box – which may well be a separate system.

I’ll just show a final Gateway screenshot – that of the test of our new service using the Gateway client:

 

Some more details:

Great – we have our service up and running. Now, on to the fun stuff – the chatbot.

Step 2: creating the chatbot

For this example, we’ll be using one of the more popular chatbot frameworks out there: API.ai. There are pros and cons with this chatbot framework, as with most of them – we’ll be encountering one of the minor issues in a little while. For now, let’s focus on building the chatbot.

API.ai is running in our browser – no need to install anything locally. Just go to API.ai and behold the beauty of the chatbot editor:

This might be a bit to take in for you if you’re new to chatbots. Let’s stop, take a deep breath, and explain.

Chatbots usually work with “intents” and “entities”. The Intents are the things our users want to do, like “order a plane ticket”, or “find the nearest restaurant”. The Entities are like the parameters – or attributes – of these intents. An Entity for “order a plane ticket” might be “date”, or “destination”. Usually, the chatbot will try to retrieve all the entities related to an intent, before committing an action, like ordering the plane ticket for you. If built properly, it will ask questions until it has all the information it needs – or get stuck along the way if it fails to understand the user.

For our chatbot, we have defined two intents: GET_PO_INFO and GET_PO_LIST. (In addition, as you can see, we have added a bunch of standard intents from the API.ai libraries – these are the ones in lowercase. This allows our chatbot to cover off-topics, such as when people start talking about other subjects like “you’re really funny”, or “you must be the most boring chatbot ever”.

In API.ai, the intent looks like this:

Here, I have typed a few variations of the way a user could be expressing his/her desire to get a list of purchase orders. For the sake of this example, I’ve restricted myself to a few utterings – usually, you’ll find your users are very creative in their attempts to phrase an intent…

Further down the screen, we can see the remaining attributes of this intent:

There’s not much here, really. As mentioned, API.ai gives you the option to add entities to your intent. If this was, for instance, a FIND_ME_A_RESTAURANT intent, we could have added the entities “MEAL_TYPE” and “LOCATION”, and set these as “required” above. This would have forced the chatbot to ask questions like “what type of food do you want”, and “what is your preferred location”. You can make your conversation as complex as you want – but for our purpose, we will not need any entities. We only want to get some purchase orders!

(Note: API.ai as a framework is, as I think I’ve stated already, quite easy to learn. Please forgive me for not providing an API.ai 101 course here – you should be able to pick up the basics quite easily).

What we want to focus on is that little blue checkbox in the above image: the one that says “Use webhook”.

This is where it gets cool!

Now, we have to find a way for our chatbot to make a request to SAP Gateway.

This is done in API.ai using webhooks. A webhook is basically a web service – you call it with some parameters, and get a reply back. Our Gateway service will serve as our webhook. We’ll call it with a specific user ID, and get a list of purchase orders back. What could possibly go wrong?

First things first. Notice the word “Fulfillment” just above the blue checkbox? This is API.ai’s term for “something that comes back as a response from a webhook”, more or less. The Fulfillment tab in our chatbot editor is where we define the actual web service call and handle the result. Let’s click on “Fulfillment” on the left menu bar:

This brings us to the Fulfillment screen (logically enough):

Here, we only need to insert the URL for our Gateway service!

 

Step 3: where we get stuck

Ha! You really said “only need to…” ??

Think again.

I spoke about pros and cons, right? This chatbot builder is a charm to work with for setting up the chatbot conversation, but when it comes to using web services… well, let’s say, it’s a bit “picky”.

API.ai web calls must adhere by very strict rules. From the documentation:

Right. This is not exactly what our Purchase Order service returns, in terms of format.

What’s worse: it’s probably not going to be very easy to cajole our service into returning anything remotely adhering to this format.

Nor is it desirable, really. You’d want your OData services to return data in adherence to the standard format provided by the protocol itself, not tweak it into a chatbot-friendly native format only suitable for one chatbot framework.

Time for reflection.

Here, other frameworks, like Kore.ai, has the flexibility to just execute a service call, then handle the response in any way you want, say, programmatically.

 

Step 4: where we do some serious thinking

Well, we’ve come a fair way. We cannot just give up, no? Let’s have another coffee, take a deep breath again, and think.

We can’t format the response internally in API.ai, Nor can we (easily) do it on the Gateway side (and, as mentioned, we shouldn’t, really, because that means creating a separate set of services for chatbots in addition to the services for regular consumption by, say, UI5).

What we can do, however, is re-format the service reply somewhere else.

All we have to do is throw SAP Cloud Platform into the mix, and have it do the job for us.

Building an XSJS service on SAP-CP allows us to do the service call to Gateway from SAP-CP, then call the XSJS service from API.ai. The XSJS service will take the “standard” OData response from Gateway, and re-format it for consumption in API.ai.

What a brilliant and somewhat complex idea.

Again, as mentioned, some other chatbot frameworks would allow us to do this internally. But API.ai is easy to use, easy to deploy, and one of the “big ones” out there, so let’s stick with it for now.

Step 5: where we try to get a little less stuck

Let’s quickly create a middle man in SAP-CP. A negotiator of sorts. A translator. A nice piece of logic that receives calls from our chatbot, passes them on to Gateway, receives a reply, and re-formats it to pass it back to API.ai.

We need the following ingredients:

  1. An xshttpdest file describing our Gateway URL
  2. An XSJS service with some Javascript logic doing the “negotiating” part
  3. The ordinary .xsaccess and .xsapp files needed for XS services
  4. Some work in the XS Admin client, in order to set the authentication parameters

We start by setting up a service called GetPOList.xsjs and a gateway.xshttpdest file in a suitable package on SAP_CP:

The contents of the xshttpdest file is basically a reflection of the connection parameters already present in the “destination” configuration on SAP-CP for our Gateway system:

The reason why we need to create this file is because XSJS services cannot work with existing destinations defined on SAP-CP. We need to re-define the destination manually in each XSJS project – inside the same folder as our service files.as can be seen above. Then, we refer to the xshttpdest file in our XSJS code, when creating the connection.

On to our XSJS service. Here is the code (I’ve slightly altered the connection parameters):

try {
 
    switch ($.request.method) {
    case $.net.http.GET:
 
      $.response.setBody(JSON.stringify("GET OK - but that is not doing anything useful here"));
      break;
 
    case $.net.http.POST:
    case $.net.http.PUT:
 
      //Reading the destination properties
      var destination = $.net.http.readDestination("InsertYourPackageNameHere.PurchaseOrderChatbot.services", "gateway");
      //Creating HTTP Client
      var client = new $.net.http.Client();
      //Creating Request
      var request = new $.web.WebRequest($.net.http.GET, "/PURCHASE_ORDERSet?$format=json");
      request.headers.set("SAP-Connectivity-SCC-Location_ID", "DEVLANDSCAPE");
      request.contentType = "application/json";
      client.request(request, destination);

 
      var gwResponse = client.getResponse().body.asString();
      var JSONObj = JSON.parse(gwResponse);
      var botResponse;
 
      if (JSONObj.d.results.length > 0) {
        botResponse = "Your latest Purchase orders are: ";
 
        for (var i = 0; i < JSONObj.d.results.length; i++) {
          botResponse += " ";
          botResponse += JSONObj.d.results[i].Poheader.PoNumber;
        }
      } else {
          botResponse = "You do not seem to have any active Purchase Orders!";
      }
 
    $.response.status = $.net.http.OK;
    $.response.contentType = "application/json";
    $.response.setBody(JSON.stringify({
      "speech": botResponse,
      "displayText": botResponse
    }));
 
    break;

  default:
    $.response.status = $.net.http.METHOD_NOT_ALLOWED;
    $.response.setBody("Wrong request method");
 
    break;
  }
 
} catch (e) {
    $.response.setBody("Failed to execute: " + e.toString());
}

What we basically do here, apart from the usual check on request method (GET vs POST/PUT), is simply do a call to our Gateway-based service. If it succeeds, we parse the results – extracting the PO numbers, and build a response which is sent back to the API.ai chatbot, adhering to the proper format.

Now, all we have to do is call the XSJS service from our API.ai chatbot.

Ah – one more thing: We need to set the authentication level for our service using the XS Admin tool. Invoke the tool, navigate to the package where the xshttpdest file is, select the file, and set “Public” for now – you can add authentication to your heart’s content later:

That’s it on the SAP_CP side!

OK, at this stage in my exploratory journey, I of course tested the XSJS service properly, but I’ll spare you the tedious results of that particular part. Suffice to say the results look more or less like this:

Step 6: Finally, back in chatbot-land…

Now, all we have to do is add the URL to our XSJS “go-between” service in our API.ai webhook:

Step 7: The moment of (ugly) truth

Ok. We’re finally there. We’ve created a Gateway service, a chatbot, a re-formatting XSJS service on SAP-CP, and stitched it all together.

Will it work?

Let’s try. API.ai allows for easy testing of the chatbot, out-of-the-box (or, more specifically, inside-the-browser):

Yes!! We did it!

It’s alive!

Sort of.

Is it nice? Well, not really. We could have spent some more time on the formatting of the reply from the XSJS service. And we conveniently “forgot” to include the user name in the service call – all we do here, really, is retrieve the 10 first PO’s from the data base.Not to mention the fact that we haven’t created the intent for retrieving a single PO, nor the Gateway service method to handle such requests.

But, all in good time.

What we have achieved, is creating a simple chatbot that interacts with SAP ERP, using Gateway services. We have also explored using SAP-CP as a “reformatter”, since the chatbot tool lacked the necessary functionality to handle “raw” service responses.

It’s a start.

 

Wait! Wait! He said he’d show us something about the second intent as well!

Yeah, right, I did. I was actually going to go to bed, but you just reminded me. OK, here we go. I’ll show you the way, then it’s up to you to finish the job. OK?

The GET_PO_INFO intent basically retrieves information about one specific PO. Here it is, as implemented in API.ai:

 

API.ai automatically resolves the word “first” into a system entity, @sys.ordinal. This is cool!

Let’s try straight away if this works in a test dialog:

Nice! We can even click the JSON button and look at the conversation context:

This is brilliant. It means we have an easy way to deduce which purchase order our user wants more info about.

Note: the “context” is chatbot slang for “everything I’ve deduced from whatever the user has typed into me from the start of the conversation until now”, like entity values, intents, actions and so on. It also holds the result of any web service calls. Here, we see that the “ordinal” is deduced as “2”, which kind of gives us an inkling as to which PO the user wants more info about.

Or, to re-phrase it: if our intent is “GET_PO_INFO”, and the ordinal is “2”, we will need to call the service for specific PO’s with the second PO number from the list we already retrieved.

And that, ladies and gentlemen, I leave to you. It should be a walk in the park, really!

 

Thanks for sticking out this far – hope you had a few ideas. Let me know if you run into issues – I’m happy to help!

 

PS: feel free to comment – particularly if you have found cooler or easier solutions!

 

 

To report this post you need to login first.

30 Comments

You must be Logged on to comment or reply to a post.

  1. Chandra Shekhar Agarwal

    Its nice and helpful blog to develop chatbot. Thank you.

    Do we have any SAP based chatbot framework available openly? API.AI looks 3rd party framework and when we need to develop some SAP business application, we don’t use 3rd party tools.

     

    (0) 
    1. Trond Stroemme Post author

      Hi Chandra,

      from what I know, SAP is working on a native chatbot framework to be published some time next year. As of now, we have to content ourselves to use external frameworks. Basically, any chatbot framework that can do web requests should work nicely. We’ve tried Microsoft, IBM Watson, Kore, and API.ai so far for our internal PoC’s at my company. Of course, the native SAP framework will probably be preferred for SAP chatbots whenever it arrives.

      Regards,

      Trond

      (0) 
  2. Former Member

    Hi Trond,

    This post is really useful!

    I am a user researcher from SAP Labs in Palo Alto, and our team(SAP Global Design Applications) is working on the SAP native chatbot framework.

    Would be very appreciated if you can have a quick chat with the team about your bot building process!

    Thanks,

    Hsin-Yen

    (0) 
    1. Trond Stroemme Post author

      Hi Francois,

      I used some existing code as a template. This code basically treats POST and PUT requests as the same type of request, as you can see. It doesn’t matter whether you use one or the other (there’s no explicit “break” after the check on POST).

      Regards,

      Trond

      (0) 
  3. Former Member

     

    Hi Trond,

     

    It was an amazing post, I followed it very properly from beginning, but unfortunately my xsjs url is not being accepted as a webhook. I tried every thing but I get this error : “Webhook call failed. Error: 403 Forbidden”. Though my xsjs file is running in the browser and showing the json.

    Could you please help me with this.

     

    Thanks,

    Abhinav

    (0) 
        1. harish vyas

          Hi Abhinav,

           

          For me it got resolved by making the project PUBLIC in XSADMIN.

          Earlier I was trying to provide usename and password in Fulfillment section and NOT making my project PULIC and at that time it didn’t worked.

          Additionally, in Header Parameter in Fulfilment, have to make an entry x-csrf-token = unsafe (this is what I read in one other blog).

          Regards,

          Harish

          (0) 
  4. harish vyas

    Hi Trond,

     

    Firstly will like to thank you for posting this blog. Real helpful in understanding the concepts and learning the basics.

    For learning purpose, I am trying to integrate XSJS (created in trial version of SAP HANA CP) with API.AI (by providing URL to XSJS file). Additionally I have also entered Username and Password in fulfillment section. Inside XSJX file I am not writing any code to trigger ODATA, but populating the JSON structure with some dummy data in XSJS file only.

     

    XSJS file link: 

    https://XXXXXtrial.hanatrial.ondemand.com/CHATBOT_1/myLibrary.xsjs

     

    Content of XSJS file: 

    $.response.status = $.net.http.OK;
    $.response.contentType = “application/json”;
    $.response.setBody(JSON.stringify({
    “speech”: “Test”,
    “displayText”: “Test”
    }));

     

    Output of XSJS file, when run independently:

    {“speech”:”Test”,”displayText”:”Test”}

     

    Inside my FULFILLMENT section in API.AI, when I entered  below link along with username/password:

    • httpS://XXXXXtrial.hanatrial.ondemand.com/CHATBOT_1/myLibrary.xsjs

    I am getting below error in JSON

    “status”: {
    “code”: 206,
    “errorType”: “partial_content”,
    “errorDetails”: “Webhook call failed. Error: 403 Forbidden“,
    “webhookTimedOut”: false
    },

    I am getting below error in JSON

    “status”: {
    “code”: 206,
    “errorType”: “partial_content”,
    “errorDetails”: “Webhook call failed. Error: Webhook response was empty.“,
    “webhookTimedOut”: false
    },

     

    Can the issue be because of authentication level that needs to be set for our service using the XS Admin tool.

    Request you to kindly suggest what can be the issue behind this.

     

    Regards,

    Harish

    (0) 
    1. Former Member

      Hi Harish,

      Did you get it to work already? I am also interested in this chatbot possibility, but I do not get it to work because I am not allowed to use the XS Admin Tools.

      Additionally, I did create a trial account to play around with, so do you have a manual with the steps you have made  to set up your HANA Trial version? Because I have no idea what to there myself.

      Greetings,

      JP

      (0) 
      1. harish vyas

        Hi Jurre,

        Please navigate to User Administration and assign roles related to XS admin i.e. sap.hana.xs.admin.roles.*

        Once you assign these roles to your ID, please log-off and login again and use the URL https://XXXXXXtrial.hanatrial.ondemand.com/sap/hana/xs/admin/

        Here you can go to your Package –> “Security & Authentication” tab  –> make the Package Public.

        Additionally, in Header Parameter in Fulfilment, have to make an entry x-csrf-token = unsafe (this is what I read in one other blog).

        Regards,

        Harish

        (0) 
        1. Former Member

          Hi,

          I have created xsjs service. When running the chatbot by the providing the xsjs url in Fulfillment,adding header parameters  x-csrf-token = unsafe and making the package public, its giving the following error :

          “status”: {
          “code”: 206,
          “errorType”: “partial_content”,
          “errorDetails”: “Webhook call failed. Error: Request timeout.”,
          “webhookTimedOut”: true
          },

          When I run the url independently its giving the output.

          Please provide the solution.

           

           

          (0) 
            1. Former Member

              Hi Harish,

              Thanks for your response.

              Here is the xsjs url :http://xxxxxxx:80/test/Services/demo.xsjs

              Its gives output as

              {"speech":"Test","displayText":"Test"}
              
              .xsjs file content
              
              var destination_package = "test.Services";
              
              var destination_name = "test12";
              try {
               var dest = $.net.http.readDestination(destination_package, destination_name);
               var client = new $.net.http.Client();
               
               var req = new $.web.WebRequest($.net.http.GET, "?origins=Frankfurt&destinations=Cologne&mode=driving&language=en-US&sensor=false"); 
               client.request(req, dest);
               var response = client.getResponse(); 
               var botResponse=response.body.asString();
               
               $.response.status = $.net.http.OK;
               $.response.contentType = "application/json";
               $.response.setBody(JSON.stringify({
               "speech": "Test",
               "displayText": "Test"
               }));
               
               
              } catch (e) {
               $.response.contentType = "text/plain";
               $.response.setBody(e.message);
              }
              
              Destination .xshttpdest content:
              
              host = "maps.googleapis.com";
              port = 80; 
              pathPrefix = "/maps/api/distancematrix/json";
              authType = none;
              useSSL = false;
              
              First I tried to return the response from the service but as it was giving timeout error so replaced the service data with simple text as "Test" in speech and displaytext but still the timeout error exists.
              
              
              (0) 
  5. Prasenjit Singh Bist

    Great to see your post again Trond, after a long time and as always great explanation. I was looking for such a solution. But I am wondering why the ABAP OData service is then crippled why can do i need to get CP in between because both are Odatav4 if CP supports API.ai format what is the problem  with net weaver stack? Could you please provide some insight on the integration aspect.

     

    Regards,

    Prasenjit

     

    (0) 
    1. Trond Stroemme Post author

      Hi Prasenjit,

       

      the reason for using SAP-CP was to re-format the reply from the OData service to conform with the required format of API.ai.

      Now, API.ai has changed – they are now called DialogFlow and I believe the external call options are more elaborate. I haven’t tried, but it should be easier to consume OData services now.

      Regards,

      Trond

      (0) 
  6. Former Member

    Unable to get response from .xsjs file for Dialogflow. I have created .xsjs file in hcp trial version and trying to connect to SAP Backend Server to get Odata response back. Although everytime I run the file I receive error “HttpClient.request: request failed: internal error occurred “Failed to send request to socket…rc = -1”. Could you please help resolving this?

    (0) 
    1. manoj thatha

      Hi Shilpa,

       

      Were you able to resolve the Failed to send request to socket…rc = -1. I am getting the same error. Your Help is really appreciated. I was trying this from past 2 days nothing worked out.

       

      Thanks,

      Manoj

      (0) 
  7. Former Member

    Hi Trond,

    I have created xsjs service but when I paste the service url in webhook and try to run the chatbot it gives empty response.The json response from the bot gives below error:

    “status”: {
    “code”: 206,
    “errorType”: “partial_content”,
    “errorDetails”: “Webhook call failed. Error: Webhook response was empty.”,
    “webhookTimedOut”: false
    },

     

    (0) 
  8. Former Member

    Hi Trond,

    I am trying a pass parameters from the dialogflow chatbot in the .xsjs service but the service is reading only parameter name not its value from the bot.

    Can you help to know how to pass parameter from bot to service?

     

    (0) 
  9. Former Member

    Hi Trond,

    I am done with the PO HEADER part(GET_PO_LIST). Now I am trying to link up GET_PO_INFO with the GET_PO_LIST, so that I can get the Item details of a particular PO. And I am unable to link those intents together because In FULFILLMENT  I can only put the XSJS for HEADER part.

    Could you please help me to solve it. Eagerly Waiting for your help.

     

    (0) 
  10. Former Member

    Hi Trond,

    I followed it from beginning.But I was stuck at step 5.

    I have created a destination File and xsjs file.

    I mapped the system in Cloud Connector with Virtual Host and Port and created destination in SCP

    When I check the connection in SCP it’s working fine

    Below is my Destination Path in SCP and http Destination File


    But when I call the xsjs application url I am getting the below error.

    Access denied to system sfd:8000. In case this was a valid request, ensure to expose the system correctly in your cloud connector
    
    Could you please help me to solve this.
    
    
    
    (0) 
  11. Sagar Prabhu

    Hi,

    I am done with the PO HEADER part(GET_PO_LIST). Now I am trying to link up GET_PO_INFO with the GET_PO_LIST, so that I can get the Item details of a particular PO. And I am unable to link those intents together because In FULFILLMENT  I can only put the XSJS for HEADER part.

    Please help me how to get Items details for perticular PO Number.

     

    (0) 

Leave a Reply