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.