Skip to Content

Hello SAP Community,

Today I wanted to share the experience I had with a demo that I prepared to test the integration between Amazon Alexa and SAP, it was really interesting to be able to show people in 3 events, one in October 2017 at the ASUG Colombia congress, another in the SAP Summit Medellín in February 2018 and the last one in the Amazon Cloud Experience in Bogota last July, all we can do with these 2 platforms (Amazon Alexa, and SAP) that enable us to develop applications of conversational agents, which can easily be integrate with each other using REST technology, before going to the details of my demo, I would like to show you some photos of the three events in which I was sharing my experience with this integration.

ASUG Colombia October 2017

SAP Summit Medellín February 2018

Amazon Cloud Experience July 2018

Well, already entering the subject, I tell you what the demo is about: the objective of this demo is to prove how easy and fast you can build applications with conversational agents, my demo is a sales data query application and budget using voice, to develop and configure this application requires the following components:

  1. SAP Cloud Platform.
  2. SAP Cloud Connector.
  3. Servicio odata provisioning
  4. Servicios odata from ERP side.
  5. Amazon Alexa Skill configuration.
  6. Amazon Lambda functions.

Basic knowledge of javascript programming is required, specifically in Node js, also in ABAP language, odata services, in addition to the management of the SAP and Amazon cloud platforms.

The first thing I did was to create an odata service with a simple selection to one of the SD information structures where the company’s sales and budget are stored, for this you must enter the SEGW transaction, where you create the project, this service will then be added to the service odata provisioning of the SCP to take it to the cloud since our data is on-premise.

This is the code of the odata service that gets the sales and budget.

method VENTASSET_GET_ENTITYSET.
 DATA: ls_filter_select_options TYPE /iwbep/s_mgw_select_option,
       ls_select_option TYPE /iwbep/s_cod_select_option.
 DATA: l_it_so_periodo TYPE RANGE OF spmon,
       l_wa_so_periodo LIKE LINE OF l_it_so_periodo,
       l_it_so_oficina TYPE RANGE OF vkbur,
       l_wa_so_oficina LIKE LINE OF l_it_so_oficina,
       l_it_so_grupoart TYPE RANGE OF matkl,
       l_wa_so_grupoart LIKE LINE OF l_it_so_grupoart,
       l_it_so_vrsio TYPE RANGE OF s700-vrsio,
       l_wa_so_vrsio LIKE LINE OF l_it_so_vrsio.

 LOOP AT it_filter_select_options INTO ls_filter_select_options.
  IF ls_filter_select_options-property EQ 'Periodo'.
    LOOP AT ls_filter_select_options-select_options INTO ls_select_option.
      l_wa_so_periodo-sign = ls_select_option-sign.
      l_wa_so_periodo-option = ls_select_option-option.
      l_wa_so_periodo-low = ls_select_option-low.
      l_wa_so_periodo-high = ls_select_option-high.
      APPEND l_wa_so_periodo TO l_it_so_periodo.
    ENDLOOP.
  ENDIF.
  IF ls_filter_select_options-property EQ 'Oficina'.
    LOOP AT ls_filter_select_options-select_options INTO ls_select_option.
      l_wa_so_oficina-sign = ls_select_option-sign.
      l_wa_so_oficina-option = ls_select_option-option.
      l_wa_so_oficina-low = ls_select_option-low.
      l_wa_so_oficina-high = ls_select_option-high.
      APPEND l_wa_so_oficina TO l_it_so_oficina.
    ENDLOOP.
  ENDIF.
  IF ls_filter_select_options-property EQ 'GrupoArticulos'.
    LOOP AT ls_filter_select_options-select_options INTO ls_select_option.
      l_wa_so_grupoart-sign = ls_select_option-sign.
      l_wa_so_grupoart-option = ls_select_option-option.
      if ls_select_option-low <> ''.
        l_wa_so_grupoart-low = ls_select_option-low.
        l_wa_so_grupoart-high = ls_select_option-high.
        APPEND l_wa_so_grupoart TO l_it_so_grupoart.
      endif.
    ENDLOOP.
  ENDIF.
  IF ls_filter_select_options-property EQ 'Version'.
   LOOP AT ls_filter_select_options-select_options INTO ls_select_option.
      l_wa_so_vrsio-sign = ls_select_option-sign.
      l_wa_so_vrsio-option = ls_select_option-option.
      l_wa_so_vrsio-low = ls_select_option-low.
      l_wa_so_vrsio-high = ls_select_option-high.
      APPEND l_wa_so_vrsio TO l_it_so_vrsio.
    ENDLOOP.
  ENDIF.
 ENDLOOP.

 IF l_it_so_vrsio IS INITIAL.
    l_wa_so_vrsio-sign = 'I'.
    l_wa_so_vrsio-option = 'EQ'.
    l_wa_so_vrsio-low = '000'.
    APPEND l_wa_so_vrsio TO l_it_so_vrsio.
    l_wa_so_vrsio-low = '999'.
    APPEND l_wa_so_vrsio TO l_it_so_vrsio.
 ENDIF.

 IF l_it_so_grupoart IS NOT INITIAL.
  SELECT spmon, matkl, vkbur, vrsio, SUM( netwr ) AS netwr, SUM( zzpesoneto ) AS zzpesoneto
  FROM s700 INTO CORRESPONDING FIELDS OF TABLE @et_entityset
  WHERE spmon IN @l_it_so_periodo
    AND vkbur IN @l_it_so_oficina
    AND vrsio IN @l_it_so_vrsio
    AND matkl IN @l_it_so_grupoart
  GROUP BY spmon, matkl, vkbur, vrsio.
 ELSE.
  SELECT spmon, vkbur, vrsio, SUM( netwr ) AS netwr, SUM( zzpesoneto ) AS zzpesoneto
  FROM s700 INTO CORRESPONDING FIELDS OF TABLE @et_entityset
  WHERE spmon IN @l_it_so_periodo
    AND vkbur IN @l_it_so_oficina
    AND vrsio IN @l_it_so_vrsio
  GROUP BY spmon, vkbur, vrsio.
 ENDIF.
endmethod.

In summary what this service does is return sales and budget records, 000 version is used for sales, and version 999 for the budget. Here we see the filters by Period, office, items group and version.

Once my odata service was ready, I proceeded to publish it in the SAP Cloud Platform using the odata provisioning service, for this the connection between SCP and the ERP must already be configured using the SAP Cloud Connector.

This is a screenshot of the configuration of the cloud connector that is in the same network of my SAP on-premise servers, as you see this port 8000 map, for the purposes of the demo I left it that way, but really for security it must have port 8443 that It is a safe port.

In SCP we see it this way:

Once we guarantee the connection between SCP and our on-premise ECC system, we can connect our previously created odata service. For this we go to the Odata Provisioning service.

In configure service we access the destination that we created for the connection to our on-premise system, this tells us exactly which server we are going to connect to, the cloud connector is a global connection, but the destination is the connection to our specific system.

We are ready to add the odata service that we created on-premise, we click on Go to service where we will see the service already added.

To add a service simply click on the Register button:

Select the destination created to access the on-premise server and click on the magnifying glass where it will bring you the services created, select it, click on Register and you have already published the service in SAP Cloud Platform.

We test the service by accessing the URL of the service with some example selection parameters, and the output is a response in JSON format.

Up to here the SAP part, now we are going to move to the Amazon part, where also in the cloud they are configured and deploy the application that is developed in node js. To understand a bit what I am going to show, I would like to explain it in less technical terms, what we are going to do next is on the one hand to configure the skill (this is what is called in the Amazon language) which is what will allow us to ask the questions and alexa responds to us in a dialogue called multiturn since I start asking something, and if alexa does not have the complete information, she will proceed to ask me for that information that is needed to be able to finally interact with our service published in SCP, on the other hand we have a application developed in node js, which will be responsible for receiving alexa’s skill information to obtain the necessary parameters to interact with SAP, and execute the respective query. The response that is returned by our service in JSON format is interpreted by the node application js, and using the libraries of the Alexa voice service, the order is given to “say” what we have just consulted, organizing in the most convenient way our answer.

Amazon Alexa Skill configuration.

The configuration of the skill is done in the Amazon Web Services developer console at the developer.amazon.com site, then we enter Alexa where we can create the skill. In this case the skill is already created and the name that I put is Sales, we enter it by clicking on Edit.

As you can see the intention that I created is called GetSalesAndBudget, within the Sales skill where I entered I can create different intentions that are nothing more than what the person has “intention” to consult Alexa for example an intention may be to know the sales and budget, but another intention could be I want to know what percentage I grew in sales between 1 year and another comparing the same month, each intention is going to have some obligatory and optional slots (parameters). In short, for my skill I only have one intention and is to know the sales and budget of a given period.

Below I have the types of slots that is like a data type, where I also have the possibility to define some domains as is the case of the office slot, of the City type (I created several cities), here we can see that I can map names to codes which means that within my function node js deployed in lambda I will be able to interpret as code the city that I ask Alexa.

Finally we go to the option Endpoint where we can specify the id of our lambda function that we will see in the next section, there we must have loaded the handler code of our skill, Alexa will contact this function passing the information of the slots, and this function will connect to SAP, get the data in json format and proceed to send the result back to Alexa with what you should “say”.

Amazon Lambda functions

Lambda is an Amazon service with a serverless philosophy (console.aws.amazon.com/lambda) which allows executing code without having to deploy a server infrastructure in the cloud. What we did here was deploy our source code implemented in node js, this program was developed locally (on my laptop) using sublime text 3, at the end for the deploy the file is compressed and loaded to the lambda function. The title of the function is getSalesAndBudget, there I click to enter to edit the function.

In the upper part of the next page appears the ARN which is the id of the function that we must place in the endpoint section of the configuration of the skill. Here we configure the trigger of our function that would be a call from our voice service alexa, additionally you can see a connection between our function and Amazon CloudWatch Logs, in this portal we could see all the logs generated each time our function is executed, even those that we ourselves generate using the javascript console.log instruction.

Below we can load our application node js with the Load button.

Regarding the application of node js I’m going to put code snippet where you can find the key parts of the handling of the skill and its interaction with SAP. GetSalesAndBudget is the main function, this function handles the intention to know sales and budget.

'GetSalesAndBudget': function () {
        //delegate to Alexa to collect all the required slot values
        var filledSlots = delegateSlotCollection.call(this);

        var oficina=this.event.request.intent.slots.oficina.value;
        var periodo=this.event.request.intent.slots.periodo.value;
        var fieldsRequested=this.event.request.intent.slots.fieldsRequested.value;

        var grupoArt = isSlotValid(this.event.request, "grupoArt");
        
        if (this.event.request.dialogState === "COMPLETED"){
           oficinaId = this.event.request.intent.slots.oficina.resolutions.resolutionsPerAuthority[0].values[0].value.id;
           grupoArtId = "";
           if (this.event.request.intent.slots.grupoArt.resolutions){
            grupoArtId = this.event.request.intent.slots.grupoArt.resolutions.resolutionsPerAuthority[0].values[0].value.id;
           }
           fieldsRequestedId = this.event.request.intent.slots.fieldsRequested.resolutions.resolutionsPerAuthority[0].values[0].value.id;
           periodoSAP = periodo.substr(0, 4) + periodo.substr(5,2);
           
           getDataFromSAP(oficinaId, periodoSAP, fieldsRequestedId, grupoArtId, function(salesValue, budgetValue){
             console.log("Grupo de articulo: " + grupoArt);
             if (!grupoArt){
                grupoArtPartText = "";
                connectorAnd = "";
              }else{
                connectorAnd = " and ";
                grupoArtPartText = " items group " + grupoArt; 
              }
             switch(fieldsRequestedId){
              case "1": //Solo ventas
               speechOutput = "The total sales value for sales office " + oficina + connectorAnd + grupoArtPartText + " on " + periodo + " is " + salesValue + " millions of colombian pesos";
               break;
              case "2": //Solo presupuesto
               speechOutput = "The total budget value for sales office " + oficina + connectorAnd + grupoArtPartText + " on " + periodo + " is " + budgetValue + " millions of colombian pesos";
               break;   
              case "3": //Ventas y presupuesto
               speechOutput = "For sales office " + oficina + ", " + grupoArtPartText + " and month " + periodo + " the total budget value is " + budgetValue + " millions of colombian pesos, and total sales value is " + salesValue + " millions of colombian pesos";
               break;   
             }
             this.response.speak(speechOutput);
             this.emit(":responseReady");
           }.bind(this)); 
           
           //console.log("Grupo de articulo " + grupoArt);
           console.log("Id de la oficina " + this.event.request.intent.slots.oficina.resolutions.resolutionsPerAuthority[0].values[0].value.id);
        }
    },

This is the function that connects with SAP using the call to an odata service of odata provisioning published in SAP Cloud Platform.

function getDataFromSAP(pOficinaId, pPeriodoSAP, pFieldsRequestedId, pGrupoArtId, callback){
  var url = "https://gwaas-pXXXXXXtrial.hanatrial.ondemand.com/odata/SAP/ZSD_XXXXXX_SRV;v=1";
  var entitySet = "/VentasSet";
  var format = "&$format=json";
  var version = "";
  var args = {
                headers: { 
                           "Authorization": "Basic XXXXXXXXXXXXX" 
                         }
  };
  
  if (pFieldsRequestedId === "1"){ 
       version = " and Version eq '000'";
  } else if (pFieldsRequestedId === "2") {
       version = " and Version eq '999'";
  } //Si toma valor 3 que significa leer ventas y ppto la version va vacia
  
  console.log("Requested fields " + pFieldsRequestedId + " Version " + version);
  var filtro = "/?$filter=" + escape("Periodo eq '" + pPeriodoSAP + "' and Oficina eq '" + pOficinaId + "' and GrupoArticulos eq '" + pGrupoArtId + "'" + version);

  client = new Client(); 

  console.log("Url: " + url + entitySet + filtro + format);

  client.get(url + entitySet + filtro + format, args, function(data, response){
    var totalSales = 0;
    var totalBudget = 0; 
    console.log(data.d.results);
    data.d.results.forEach(function(data){
      if (data.Version === "000"){
        totalSales = (Number(data.ValorNeto) / 1000000).toFixed(0).toString();
      } else {
        totalBudget = (Number(data.ValorNeto) / 1000000).toFixed(0).toString();
      }
    });  
    callback(totalSales,totalBudget);
  });  
}

I hope it helps developers who want to venture with these technologies that offer us both Amazon and SAP to easily create very eye-catching applications using hybrid environments.

Last, I will left you with my demostration made in one of the events I was.

Greetings to all.

Jhon Jairo.

To report this post you need to login first.

1 Comment

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

  1. Rajni Yadav

    Hello,

    It was a very informative blog. We tried replicating the above example, but we are not able to connect SAP system. Can you please explain the argument passed for basic authentication? We are getting an HTML file in the response even though we passing the format as json with Odata provisioning link.

    Regards

    Rajni yadav

    (0) 

Leave a Reply