Creating a simple Alexa Skill with an ICM Endpoint

In the previous article (See Creating a simple Alexa skill) I created a simple WarehouseSkill. The skill informs customers about storage data for a given product. The skill is an addon to the standard example that we used to teach in our training courses for all sorts of ICM extensions: Our warehouse use case. (With the new CaaS offering being out and more companies getting the IOM included in their installation we might have to think about a new training example.)

In that article I followed the standard approach for developing a new skill: The speech recognition device sends voice data to the Amazon cloud speech engine. The engine creates a JSON object from the voice data and sends it to an AWS lambda function, which then with the help of data from the ICM generates the JSON response. This response is sent back to the speech engine and finally as voice data to the device. (See last article for a picture.)

After I got it up and running, I asked myself: The ICM is a full fledged webserver. How hard would it be to use it directly as an endpoint omitting the intermediate AWS lambda engine server?

Alexa Skill Infrastructure with ICM Endpoint

The answer surprised me. It is not easy, it is extremely easy. The Intershop REST framework provides everything you need.

 

Creating an ICM endpoint

As a preparatory step I copied the JSON string being sent from the Amazon speech engine to JSONschemotoPOJO. Here you can generate a bunch of java classes for the later needed mapping of the JSON string to Java. As you can see I named the top class StorageRO and provided information about my package structure.

Mapping JSON structures to Java classes

I realized later that the downloaded classes can be used straightaway. But being a member of the training department and, therefore, responsible for proper use and compliance with with conventions, I did some mild enhancements:

  • I renamed all other classes also to have the ending -RO.
  • I removed some information about the order of the JSON string. I do not care about the order.
  • I made sure the classes extend com.intershop.component.rest.capi.resource.AbstractRestResource und implement com.intershop.component.rest.capi.resource.RestResource as most examples we provide do exactly that.
  • I added a public non-parameterized and other convenient constructors. (Always advisable when working with resources which might be added to the system via some kind of component declaration.)

Then I created a new cartridge (or project) and placed the classes inside. In my case I got:

Generated RO classes

 

One example RO class is IntentRO:

IntentRO (partially)

 

Now I followed the standard example we teach in class and created a resource class. To respond to Alexa requests, we only need the POST method to be implemented. I made sure the POST method consumes JSON,  creates JSON and maps the incoming request to our top level Java class (StorageRO).

public class StorageResource extends AbstractRestResource implements RestResource
{

    public StorageResource()
    {
        super();
        setCacheExpires(RestFrameworkConstants.CACHE_EXPIRY_NOCACHE);
    }

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces({ MediaType.APPLICATION_JSON, MediaType.TEXT_XML })
    public StorageRO getTotalStockCount_POST(StorageRO myStorage)
    { ... }
...
}

 

The following lines of the method are nearly self-explanatory and similar to the JavaScript code from the AWS endpoint. I had to make sure I know whether I got a LaunchRequest or StorageIntent. I asked for the name of the product from the slot. Then I constructed the response and stored in a variable, here called outText.

 

if (myStorage.getRequest().getType().equalsIgnoreCase("LaunchRequest")) {
     String outText = "";  
     outText = outText + "Welcome to your warehouse skill. I can check the storage for products. ";
     outText = outText + "You can say for example: Provide storage for product Sony.";
     myStorage = prepareResponse(myStorage, outText, false);
 }
 else {
     if (myStorage.getRequest().getType().equalsIgnoreCase("IntentRequest") &&
         (myStorage.getRequest().getIntent().getName().equalsIgnoreCase("StorageIntent"))) {
              
         String productName = myStorage.getRequest().getIntent().getSlots().getPID().getValue();
         String productSKU = getProductSKU(productName);
        
         ApplicationBO applicationBO =  ...
         ...
         ProductBORepository productBORepository = ...
               
         String outText = "Let me check for the product " + productBO.getDisplayName() + ". ";  
         boolean isFirst = true;
                
         for (WarehouseBO warehouseBO : warehouseBOs) {            
              StockBO stockBO = warehouseBO.getStockBO(productBO.getID());
              if(null != stockBO) {
                        if (isFirst) { 
                            outText = outText + "The product is available ";
                            isFirst = false;
                        }
                        else {
                            outText = outText + " and ";
                        }
                        outText = outText + stockBO.getCount() + " times in " + warehouseBO.getName();
              }
         }
          outText = outText + ".";
          outText = outText + " " + storageIntent_ending;
          myStorage = prepareResponse(myStorage, outText, true);
    }
 }
return myStorage;

Then I registered the resource class using component framework. I ran a publish, registered, deployed the cartridge and … That’s it!

 

Some recommendations

As the incoming and outgoing JSON string might become quite complex, I would suggest the following.

Always prepare for additional attributes

In all RO classes keep the lines generated by the POJO generator to catch additional attributes. Amazon might add/change attributes. Be it because of new features or the fact that the translated speech was not parsed perfectly.

@JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<>

 

Keep your outgoing JSON string small and readable

You might also want to drop some parts of the JSON string before generating classes (I did so in above example). Otherwise you might end up with much more classes than me. However, keep the classes for the second layer (RequestRO, SessionRO, ContextRO). Even in the case, you do not need the data. Just map everything inside those classes to mentioned additionalAttributes. If you do so, you can later with a simple

myStorage.setRequest(null);
myStorage.setSession(null);
myStorage.setContext(null);

quickly remove all those parts from the StorageRO object and do not send that information back to Amazon. That keeps your outgoing JSON string small and readable.

 

Use JSonRawValue

You might want to add some new JSON objects to the string quickly during development/testing. There is no need to write full RO-classes for this. Just add a JSonRawString-attribute and then fill it with whatever you like:

@JSonRawValue
public String complexObject = “shelf { \”position\” : \”left\” }”;

While it is somehow against my understanding of the Java way to write code, it works and can come in handy during development.

 

Why use the ICM as an endpoint?

The solution presented here has some charm. It is certainly cleaner, more readable (matter of taste, I know), much more flexible (especially if you develop your JS files within the AWS portal and not locally) and also more extendible. In any case, you do not need an AWS account, which might be quite interesting for making a decision at company level. You also generate the response closer to the data. If you have to combine information from many sources you can avoid making a bunch of REST calls as you have all data right next to you.

Can I give you one more reason? The Intershop7 Localization FW. If you do not hardcode the speech response into the code but rather replace it with localized keys …

Updating a speech key in the channel backoffice

… then you can manage and handle the text being read to the customer directly from the ICM backoffice!

 

 

How cool is that?

 

Join our training program,

Michael

 

 

Creating a simple Alexa Skill with an ICM Endpoint
Tagged on:         

One thought on “Creating a simple Alexa Skill with an ICM Endpoint

  • 2018-03-13 at 10:23 am
    Permalink

    Thats way cool. Love to see it in the training.

Comments are closed.