Skip to Content

The following steps will explain how to integrate SAP Leonardo Machine Learning Foundation functional services with an SAP Cloud Platform side-by-side extension using the SAP S/4HANA Cloud SDK.

Updated Oct 2018: Using SAP Leonardo Machine Learning Foundation became even more simple! Newly added LeonardoMlFoundation class is the key. See below!

Note: This post is part of a series. For a complete overview visit the SAP S/4HANA Cloud SDK Overview. Furthermore, an extended version of this blogpost is part of the book Extending SAP S/4HANA.

Goal of this Blog Post

This blog post covers the following steps:

  1. Introduce SAP Machine Learning Foundation services.
  2. Differentiate between the sandbox services offered for prototyping on API Hub (see Quickly build a prototype with SAP Leonardo Machine Learning Foundation, SAP API Business Hub, and SAP S/4HANA Cloud SDK ) from the integration using Cloud Foundry service binding used in this article.
  3. Build a microservice wrapping the translation service from SAP Leonardo Machine Learning Foundation on SAP Cloud Platform Cloud Foundry.

SAP Machine Learning Foundation services

Machine learning is currently on everybody’s mind, figuring as a driver for efficiency improvements and an enabler of wholly new solutions. Certainly the quality of machine learning services has increased tremendously in the past 10 years. Advances in hardware and data management have led to multiple breakthroughs, fueling a virtuous circle of software improvements and, in turn, further development of hardware and data management. All this was carried by ever increasing interest by the business community. Today we have reached a quality level that make machine learning applications usable for automation purposes: When you reach 75% accuracy on image classification this is a nice trick and good research result, but you still need humans to check the result of the computer and correct every fourth result. When you reach 97% you go beyond human capabilities and can actually automate, and only have an exception in every 30th result. You still need exception handling, but mind the complexity of the task and that humans are even more error-prone doing it.

This quality improvement applies particularly to lots of so called one-second-tasks, like reading text, classifying pictures, seeing similarity, translation and so forth. Some of these tasks are sufficiently generic that they can be offered as ready-to-use services, with improvements being taken care of by the supplier – in this case SAP Leonardo Machine Learning functional services on the SAP Cloud Platform leveraging SAP Leonardo Machine Learning Foundation. In this blog post the translation service will be used to translate ‘hello’ to ‘hallo’ – not a huge challenge for the service, feel free to stress it or try other services!

Outline of this post

The post will help you to set up the technical integration with between a S/4HANA side-by-side extension on the SAP Cloud Platform and a SAP Leonardo Machine Learning Foundation service. The first step will be creating a skeleton side-by-side extension project and deploying it on the SAP Cloud Platform to test the viability of your development environment and create the application’s environment on the SAP Cloud Platform to which machine learning services can be bound. Then a SAP Leonardo Machine Learning Foundation service instance will be created and bound to the application. Then finally the access to the machine learning service will be added to the Java code of the side-by-side extension.

Creating the side-by-side extension

The starting point is the generation of the hello-world application skeleton with maven (see Step 3 https://blogs.sap.com/2017/05/19/step-3-with-sap-s4hana-cloud-sdk-helloworld-on-scp-cloudfoundry/ for a detailed introduction):

mvn archetype:generate -DarchetypeGroupId=com.sap.cloud.s4hana.archetypes \
  -DarchetypeArtifactId=scp-cf-tomee -DarchetypeVersion=LATEST

The project is directly runnable after its creation: A hello world servlet is provided for quick and easy testing. This is what we will leverage now to quickly continue setting up and testing our environment. Compile and run the code for a quick initial test (in application subdirectory):

mvn package

mvn tomee:run  # or: mvn tomee:debug

The service should be accessible at http://localhost:8080/hello . Continue to deploy it to SAP Cloud Platform (in main directory, where manifest.yml is located):

cf push

The application host name will be provided by the output on the command line (mine was named: mlf-cf-blog-tomee-innovative-wonder.cfapps.sap.hana.ondemand.com, making it accessible at http://mlf-cf-blog-blog-tomee-innovative-corpulence.cfapps.sap.hana.ondemand.com/hello ).

Bind the required machine learning services

On SAP Cloud Platform you have a wide variety of services you can choose to use in your applications. There are various versions of the SAP Leonardo Machine Learning Foundation service:

  • ML Foundation
  • ML Foundation Beta
  • ML Foundation Trial Beta

These are Cloud Foundry service constructs that in the case of SAP Leonardo Machine Learning Foundation bundle a couple of machine learning services in one Cloud Foundry service construct. You can use either of them and the beta service will have more machine learning services to choose from. The trial service is a service available on the trial edition account of SAP Cloud Platform Cloud Foundry. Be aware that the beta (some are even ‘alpha’) services are not production ready and subject to changes to their API without warning and may even disappear before graduating to the production-ready non-beta service construct.

To make the machine learning services available to your application we will bind the Cloud Foundry service construct to your application. To do this go to the overview screen for your application deployed with cf push above and open the ‘Service Bindings’ screen with the menu on the left. There hit ‘Bind Service’ to bind a new instance. Here the view on a trial account after searching for ‘ml’ (2018-07-18):

Select the ML Foundation service type you want to use – here on the trial there is only ‘ml-foundation-trial-beta’ available. Then hit ‘Next’, use the standard plan, ignore the parameters screen, and choose a name on the next screen. You’re pretty free to choose any name you want. Instances will be listed with their type and name on Cloud Foundry output.

Now we can move on to integrating the service into the SAP S/4HANA side-by-side extension using SAP S/4HANA Cloud SDK you created!

Integrate the service into a SAP S/4HANA Cloud SDK side-by-side extension

Open the project skeleton generated before with your favorite IDE, and browse to the HelloWorldServlet. You can generate project files with maven, too:

# in main directory:
mvn install # required to make eclipse:eclipse or idea:idea work

mvn eclipse:eclipse

mvn idea:idea

Then copy the HelloWorldServlet to create a new servlet named TranslationServlet, for example. Also change the url path prefix from '/hello' to, for example, '/translate'.

In that new servlet you can now add access to a SAP Leonardo Machine Learning Service. The SAP S/4HANA Cloud SDK provides a helper that handles authentication and service information extraction from the environment, called LeonardoMlFoundation, creating service classes of type LeonardoMlService. Here is an instantiation for the translation service available on the beta edition of the SAP Leonardo Services:

LeonardoMlService mlService =
  LeonardoMlFoundation.create(
    CloudFoundryLeonardoMlServiceType.TRIAL_BETA,
    LeonardoMlServiceType.TRANSLATION);

The service (stub) creation is parameterized to indicate the requirement for a specific type of the SAP Leonardo Machine Learning Foundation service. First you have to select which of the different editions you are targeting, then which particular service you want to access. Available editions are:

  • TRIAL_BETA  standard services and beta services available on SAP Cloud Platform trial accounts
  • BETA  standard and beta services available on SAP Cloud Platform subscriber accounts
  • STANDARD  standard services available on SAP Cloud Platform subscriber accounts
  • TRIAL standard services available on SAP Cloud Platform trial accounts (as of 2018-10-27 edition is no longer available on trial account)

The different services are listed on the SAP API Business Hub: SAP Leonardo Machine Learning functional services. Here we use the translation service.

Besides, on the SAP API Business Hub you have the option of exploring the services in a sandbox setup – this is a quite different setup and requires different access authentication. Please don’t confuse SAP Cloud Platform Cloud Foundry service access and the SAP API Business Hub sandbox. One is the full-blown enterprise Cloud Platform, the other just an as-simple-as-possible deployment of the services.

If you later want to access a different service using the ScpCfService class, you have to edit the last parameter to access a different service:

LeonardoMlService mlService =
  LeonardoMlFoundation.create(
    CloudFoundryLeonardoMlServiceType.TRIAL_BETA,
    LeonardoMlServiceType.TRANSLATION);
// alternative 
LeonardoMlService mlService =
  LeonardoMlFoundation.create(
    CloudFoundryLeonardoMlServiceType.TRIAL_BETA,
    LeonardoMlServiceType.IMAGE_OCR);

Be aware that some of these services are made available as beta services and might be changed or disappear without notice. All services you find in the non-beta version of the SAP Leonardo Machine Learning Foundation service construct are released and their API will not be subject to this limitation.

Back to the tutorial: In this tutorial we are going to use the translation service. It is a service specialized in business-context translations provided by SAP. This service requires the requestor to abide to a specific API with defined messages for requesting a translation and the response message also conforms to the API. This machine translation service API definition on SAP API Business Hub is available for your reference. Here you see the construction of the translation-request JSON message, ready to be serialized with Google Gson:

// construct { units: [ input ], 
//             sourceLanguage: "en", targetLanguages: [ "de" ] }
List<Object> units = new ArrayList<>();
units.add(Collections.singletonMap("value", input));

Map<String, Object> entity = new HashMap<>();
entity.put("units", units);
entity.put("sourceLanguage", "en");
entity.put("targetLanguages", 
  Collections.singletonList("de"));

In the following we will call the service via service (stub) instance created by the SAP S/4HANA Cloud SDK. This shows the structure of how to use the service instance:

private void blah() {
    LeonardoMlService mlService =
            LeonardoMlFoundation.create(...);

    HttpPost postRequest = new HttpPost();

    // construct message - depends on service
    Object entity = ...

    postRequest.setEntity(new StringEntity(new Gson().toJson(entity), ContentType....));

    postRequest.setHeader("Accept", ContentType....); // probably json, but might vary depending on service

    Object result = mlService.invoke(postRequest, new Function<HttpResponse, String>()
    {
        @Override
        public String apply( HttpResponse httpResponse )
        {
            try {
                // retrieve entity content (here shown for json, but might be different depending on service)
                final String responseBody = HttpEntityUtil.getResponseBody(httpResponse);
                logger.debug(responseBody);
                ... extract result ...
                return result;
            } catch (Exception e) {
                throw new RuntimeException("Failed to retrieve response: " + e.getMessage(), e);
            }
        }
    });
    
    ... use result ...
}

Here you can see that you can concentrate on executing the main request. You don’t have to handle the OAuth authentication required when accessing Cloud Foundry services – LeonardoMlService does that for you.

After receiving the response and passing the error check, we have to parse the response according to the format documented in machine translation service API definition on SAP API Business Hub. You can see a proposed way of parsing it using Google Gson on the bottom of this article in the listing of the servlet class.

Now you can compile and run the code for a quick initial test (in application subdirectory):

mvn package

export ALLOW_MOCKED_AUTH_HEADER=true # or windows: set ALLOW_MOCKED_AUTH_HEADER=true

# VCAP_SERVICES needs mocking too if you test locally.
# Get the values for <ENTER VALUE> from the SAP Cloud Platform Cockpit in the application screen under environment variables:
export VCAP_SERVICES='{ "ml-foundation-trial-beta": [{"name": "mlf-trial-beta","instance_name": "mlf-trial-beta","binding_name": null, "credentials": {"clientid": "<ENTER VALUE>", "clientsecret": "<ENTER VALUE>", "serviceurls": { "TRANSLATION_URL": "https://mlftrial-machine-translation.cfapps.eu10.hana.ondemand.com/api/v2/text/translation", "TEXT_CLASSIFIER_URL": "https://mlftrial-text-classifier.cfapps.eu10.hana.ondemand.com/api/v2/text/classification", },     "url": "<ENTER VALUE>" }, } ] }'
# or without leading quotes on windows: set VCAP_SERVICES={ "ml-foundation-trial-beta": [{"name": "mlf-trial-beta","instance_name": "mlf-trial-beta","binding_name": null, "credentials": {"clientid": "<ENTER VALUE>", "clientsecret": "<ENTER VALUE>", "serviceurls": { "TRANSLATION_URL": "https://mlftrial-machine-translation.cfapps.eu10.hana.ondemand.com/api/v2/text/translation", "TEXT_CLASSIFIER_URL": "https://mlftrial-text-classifier.cfapps.eu10.hana.ondemand.com/api/v2/text/classification", },     "url": "<ENTER VALUE>" }, } ] }

mvn tomee:run  # or: mvn tomee:debug

The service should be accessible at http://localhost:8080/tranlsate?input=hello . You can even deploy it to SAP Cloud Platform (in main directory, where manifest.yml is located):

cf push

Be aware that also here, if you don’t set up the security environment required for production that provides tenant information (see Secure your application on SAP Cloud Platform Cloud Foundry ) you will see Error: Failed to determine cache key. and you need to mock auth headers to make your application run:

# call 'cf apps' to get a listing of your apps with their names
cf set-env <enter your app name> ALLOW_MOCKED_AUTH_HEADER true

The application host name will be provided by the output on the command line (mine was named mlf-cf-blog-tomee-innovative-wonder.cfapps.sap.hana.ondemand.com, making it accessible at http://mlf-cf-blog-tomee-innovative-wonder.cfapps.sap.hana.ondemand.com/translate?input=hello ).

The resulting microservice is most simple but hopefully very transparent and instructive. Here is a test from a browser:

This blog post has given you an introduction to starting your own machine learning integration project with SAP Cloud Platform, and the environment facilitates connecting with SAP S/4HANA easily – see SAP S/4HANA Cloud SDK tutorial step 4 to see how easy. Go make your SAP S/4HANA extensions intelligent!

PS: I hope you tried ‘Hello World’ already.

Appendix: The TranslateServlet source code.


import com.google.common.base.Strings;
import com.google.gson.Gson;
import com.sap.cloud.sdk.cloudplatform.connectivity.HttpEntityUtil;
import com.sap.cloud.sdk.cloudplatform.logging.CloudLoggerFactory;
import com.sap.cloud.sdk.services.scp.machinelearning.CloudFoundryLeonardoMlServiceType;
import com.sap.cloud.sdk.services.scp.machinelearning.LeonardoMlFoundation;
import com.sap.cloud.sdk.services.scp.machinelearning.LeonardoMlService;
import com.sap.cloud.sdk.services.scp.machinelearning.LeonardoMlServiceType;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.slf4j.Logger;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.function.Function;

@WebServlet("/translate")
public class TranslateServlet extends HttpServlet
{
    private static final long serialVersionUID = 1L;
    private static final Logger logger = CloudLoggerFactory.getLogger(TranslateServlet.class);

    @Override
    protected void doGet( final HttpServletRequest request, final HttpServletResponse response )
            throws IOException
    {
        logger.info("I am running!");
        String input = request.getParameter("input");
        if (Strings.isNullOrEmpty(input)) {
            response.setStatus(HttpStatus.SC_BAD_REQUEST);
        } else {

            try {
                LeonardoMlService mlService =
                        LeonardoMlFoundation.create(
                                CloudFoundryLeonardoMlServiceType.TRIAL_BETA,
                                LeonardoMlServiceType.TRANSLATION);

                HttpPost postRequest = new HttpPost();

                // construct { units: [ input ], sourceLanguage: "en", targetLanguages: [ "de" ] }
                List<Object> units = new ArrayList<>();
                units.add(Collections.singletonMap("value", input));

                Map<String, Object> entity = new HashMap<>();
                entity.put("units", units);
                entity.put("sourceLanguage", "en");
                entity.put("targetLanguages", Collections.singletonList("de"));

                postRequest.setEntity(new StringEntity(new Gson().toJson(entity), ContentType.APPLICATION_JSON));

                postRequest.setHeader("Accept", ContentType.APPLICATION_JSON.getMimeType());

                String translationValue = mlService.invoke(postRequest, new Function<HttpResponse, String>()
                {
                    @Override
                    public String apply( HttpResponse httpResponse )
                    {
                        try {
                            // retrieve entity content (requested json with Accept header, so should be text)
                            final String responseBody = HttpEntityUtil.getResponseBody(httpResponse);
                            logger.debug(responseBody);

                            final Map<String, Object> responseObject = new Gson().fromJson(responseBody, Map.class);
                            final List<Object> respUnits = (List<Object>) responseObject.get("units");
                            final Map<String, Object> respUnit = (Map<String, Object>) respUnits.get(0);
                            final List<Map<String, Object>> translations =
                                    (List<Map<String, Object>>) respUnit.get("translations");
                            final Map<String, Object> translation = translations.get(0);
                            return (String) translation.get("value");
                        } catch (Exception e) {
                            throw new RuntimeException("Failed to retrieve response: " + e.getMessage(), e);
                        }
                    }
                });

                response.getWriter().println(translationValue);

            } catch (Exception e) {
                logger.error("Failure: " + e.getMessage(), e);

                response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                response.getWriter().println("Error: " + e.getMessage());
            }
        }
    }
}
To report this post you need to login first.

Be the first to leave a comment

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

Leave a Reply