Introduction

Since FurhatSDK is Java based, it is inherently easy to integrate third-party platforms into Furhat. In this tutorial we will show how to build a Java object using a third-party jar, where to put this object in the Furhat Skill project, how to import the new object into the Flow, and how to package it all into an uploadable package.

We presume that you have already gone through all previous tutorials in this series.

Assumptions

In this example we will assume that we have a java archive file (JAR) Jokes.jar that contains an API for connection to an online service that provides jokes.

Create a project

First make an example Furhat project Joke using the Speech Interaction template.

Create a java object

Then make a Java object that will manage the integration with Jokes.jar. We'll call it JokeGenerator.

The constructor takes the API key, which the jokes database uses to validate a request. We'll use this method later to create the object in the JokeSkill class.

The query method takes in a key which is used to do a query to the Jokes library. Using the API key as a form of verification, mimicking what may be required with an online database request, the method returns either a joke or an error string.

// TODO: import statements
public class JokeGenerator{

  private final String api_key;

  public JokeGenerator(String key){
    api_key = key;
  }

  public String getJoke(){

      // Query the jokes API through the Jokes.jar
      String answer = Jokes.query(api_key, "new  joke");
      if (answer == null) {
        return "Error: No such joke";
      } else {
        return answer;
      }
    }
  }

Configuring Skill and Flow to accept the object

There are two ways of getting your Flow to accept our object; either we create a variable in the flow or we create it in the skill and pass it on to the flow as a parameter.

Imports, parameters and variables

In Flow.xml files, there are variables and parameters. Variables are defined in the flow where as parameters are passed on to the flow. In both cases, the class must be imported before.

Creating an object variable in the flow

You can create a new instance of our JokeGenerator object by defining and initiating a new variable in the flow. We will in this case call it joker and add it to the top section of JokeFlow.xml.

Note that we don't need to import the JokeGenerator class since we assume it is in the same package. If it wouldn't be in the same package you can import it with <import class="full-package-name-for-class"/>

<!-- JokeFlow.xml -->

<var name="joker" type="JokeGenerator" value="new JokeGenerator('1234567')" />

If we do this, we have to pass in all information needed to create the object through the constructor.

Passing on an object as a parameter to the flow

We could also create the object and pass it into the flow from the Skill. This is a more common way of setting up the object for the flow, as you may need to do more setup for the object. Be aware that changes will be required in both the Skill and Flow files. First, we look at the JokeFlow.xml:

<!-- JokeFlow.xml -->

<import class="iristk.situated.Agent"/>

<param name="agent" type="SystemAgentFlow"/>
<param name="joker" type="JokeGenerator"/>

The order of the parameters matters here since the parameters needs to be passed onto the flow constructor in the same order as they are defined. This is illustrated by looking at how the above parameters are compiled to in the JokeFlow.java file:

// JokeFlow.java

public JokeFlow(iristk.situated.SystemAgentFlow agent, JokeGenerator joker) {
  this.agent = agent;
  this.joker = joker;
  initVariables(); // call to a method that sets up all variables declared via <var> statements.
}

Compiling the current flow with the changes made will cause an error in the JokeSkill class. The flow constructor is throwing an error that the called constructor does not exist. This is due to the old 1 parameter constructor no longer existing, instead our new 2 parameter constructor replaces it.

// FlowSkill.java

flow = new JokeFlow(handler.getSystemAgentFlow()); // "The constructor JokeFlow(SystemAgentFlow) is undefined"

This is easily fixed by adding a new JokeGenerator into the constructor, now accessible from within the flow.

// FlowSkill.java

flow = new JokeFlow(handler.getSystemAgentFlow(), new JokeGenerator("1234567890"));

Accessing Object in the Flow

Both ways of creating a JokeGenerator above allows JokerFlow.xml to access the object and it's methods through the joker object. Let's set up a state for Furhat to tell a joke:

<state id="Joke">
  <onentry>
    <agent:say> Here is a joke for you, <expr> joker.getJoke() </expr>. I'll be here all week.</agent:say>
    <goto state="SomeOtherState"/>
  </onentry>
</state>

<expr> is a block similar to <exec> with the difference that it evaluates as a String. From a Java viewpoint it is similar to appending either a variable or method to a String.

Placement of new Classes and Jars

Earlier we created the JokeGenerator class inside the Joke folder. Any new class you create is best put inside the src folder. Other files that you add should be in the same folder, in order to facilitate ease of packaging the Skill.

For Jar files, in our case the Jokes.jar that the skill uses, should be put into a lib folder. To link this .jar file to the Skill package you have to edit the package.xml file in the skill root.

Looking inside this file, you can see a classpath block that already has a line defining where the source, src, files are. Add a new line <lib path="lib/Jokes.jar"/>.

<!-- package.xml -->

<classpath>
  <src path="src" output="bin"/>
  <lib path="lib/Jokes.jar"/>
</classpath>

Common errors

If you forget to add the <lib path= line in package.xml then you will get a ClassNotFoundException on the classes defined by the third-party jar.

Furhat will throw a whole bunch of Path not exist errors if the path defined in package.xml does not exist. You may also see that Furhat throws a NullPointerException on the Skills screen.