Integrating JADE and Jess

Author: Henrique Lopes Cardoso (University of Porto) - hlc@fe.up.pt
Date: March 2007

Source: http://jade.tilab.com/doc/tutorials/jade-jess/jade_jess.html


Index

  1. Introduction
  2. Developing JADE agents with Jess
  3. Implementation choices
  4. Acknowledgements

1. Introduction

This brief tutorial explains how to integrate a Jess component into a JADE agent.

From the Jess homepage: Jess is a rule engine and scripting environment written entirely in Sun's Java language by Ernest Friedman-Hill at Sandia National Laboratories in Livermore, CA. Using Jess, you can build Java software that has the capacity to "reason" using knowledge you supply in the form of declarative rules. Jess is small, light, and one of the fastest rule engines available.

Jess was originally conceived as a tool for building expert systems. In the multi-agent systems world, it can be used as a decision component of an agent, which is implemented in a declarative way (see Figure 1).

Figure 1. An agent and its environment: JADE and Jess integration (adapted from Russel & Norvig, "Artificial Intelligence: A Modern Approach", Prentice Hall).

The rest of the tutorial is concerned with the implementation of a Jess decision module and its integration with the code of a JADE agent. This tutorial does not deal with why you should use Jess, does not describe in detail the functioning of the Jess engine, nor does it cover Jess installation issues. For this, read the Jess documentation.

This tutorial is based on JADE 3.4.1 and Jess 7.0p1, although it may be applicable to earlier versions of these tools.


2. Developing JADE agents using Jess

Before integrating both tools, it is essential to keep in mind some of the main issues concerning the functioning of both JADE agents and Jess engines. Also, there are a number of different approaches that enable you to choose the amount of the agent program that you wish to implement in Jess.

2.1 Important considerations

First of all, it is worth remembering that, in principle, a JADE agent is single-threaded. This means that one must be careful if an agent ought to be able to interact with its environment while reasoning and making decisions. This is not Jess related, though. Whether or not you use Jess, this is an issue to take into account.

Being a rule engine, Jess includes a special class called Rete, which implements the rule-based inference engine. To embed Jess in a Java application (such as a JADE agent), you simply need to create a jess.Rete object and manipulate it appropriately. Now, one of the methods included in this class that allow you to run the inference engine is Rete.run(). This method will make the engine consecutively fire applicable rules, and will return only when there are no more rules to fire, that is, when the engine stops; therefore, meanwhile the calling thread will be blocked. (If you want to understand how rules are applied, take a look at Jess documentation links.)

Now, if we block the calling thread, we block the entire single-threaded agent. This may be relevant or not, depending on how long the rule-based reasoning will take. Fortunately, the jess.Rete class includes another run method that allows us to specify the maximum number of cycles the engine should run. This will allow us to interleave the Jess-based reasoning with other agent activities.

2.2 Implementing a Jess behaviour

Taking into account the above considerations, the proposed implementation consists of embedding an instance of the Jess engine inside a behaviour. Since we want the agent to be able to continuously reason, we implement a CyclicBehaviour whose action will consist of running the Jess engine. However, we will be careful not to block other agent's behaviours for a considerable amount of time.

The following code snippet shows the implementation:

 1 class JessBehaviour extends CyclicBehaviour {
 2     // the Jess engine
 3     private jess.Rete jess;
 4     // maximum number of passes that a 
run of Jess can execute before giving control to the agent
 5     private static final int MAX_JESS_PASSES = 1;
 6     
 7     JessBehaviour(Agent agent, String jessFile) {
 8         super(agent);
 9         // create a Jess engine
10         jess = new jess.Rete();
11         // load the Jess file
12         try {
13             // open the Jess file
14             FileReader fr = new FileReader(jessFile);
15             // create a parser for the file
16             jess.Jesp j = new jess.Jesp(fr, jess);
17             // parse the input file into the engine
18             try {
19                 j.parse(false);
20             } catch (jess.JessException je) {
21                 je.printStackTrace();
22             }
23             fr.close();
24         } catch (IOException ioe) {
25             System.err.println("Error loading Jess file - 
engine is empty");
26         }
27     }
28     
29     public void action() {
30         // to count the number of Jess passes
31         int executedPasses = -1;
32         // run jess
33         try {
34             // run a maximum number of steps
35             executedPasses = jess.run(MAX_JESS_PASSES);
36         } catch (JessException je) {
37             je.printStackTrace();
38         }
39         // if the engine stopped, block this behaviour
40         if(executedPasses < MAX_JESS_PASSES)
41             block();
42             // the behaviour shall be unblocked by a call to restart()
43     }
44     
..     ...
64 } // end JessBehaviour class

The behaviour's constructor (lines 7-27) starts by creating an instance of a Jess engine (line 10), and then loads a previously created Jess code file, using a Jess parser (lines 16, 19). The behaviour execution (lines 29-43) runs the Jess engine indicating the maximum number of passes it should run (line 35). The run method employed returns the actual number of executed passes, which may be from zero up to the value of the argument. After running the engine, these numbers (the maximum and the actual executed passes) are compared (line 40) to determine if the engine stopped because there were no more rules to fire. If so, this behaviour is blocked (line 41), since a subsequent call to run (in the next round-robin behaviour execution) would have no effect at all.

This implementation assumes that you will have a way of asserting new information into the Jess engine, and a way of restarting the JessBehaviour (see comment at line 42). One way of doing this is by providing access to the JessBehaviour class instance, and implementing appropriate methods that will be responsible for both tasks (asserting information and waking). Here is an example method for the JessBehaviour class:

45     boolean addFact(String jessFact) {
46         // assert the fact into the Jess engine
47         try {
48             jess.assertString(jessFact);
49         } catch(JessException je) {
50             return false;
51         }
52         // if blocked, wake up!
53         if(!isRunnable()) restart();
54         // message asserted
55         return true;
56     }
57     

The method takes a Jess fact construct as an argument and asserts it into Jess (line 48). (The assertString(String) method is one of the methods available in jess.Rete to make assertions.) The behaviour is then restarted if it was blocked (line 53). This will have the effect of executing the behaviour (and running the Jess engine) in the next round-robin opportunity. You might also want to consider implementing methods that, instead of receiving directly the Jess fact as a String, receive something else (e.g. an ACLMessage) and assemble the Jess code themselves. This makes sense if you want to isolate Jess usage inside JessBehaviour. One of those methods could be:

58     boolean newMsg(ACLMessage msg) {
59         String jf = ... // use msg to assemble a Jess construct
60         // "feed" Jess engine
61         return addFact(jf);
62     }
63     

Line 59 is where you would build the Jess construct to be asserted into Jess. This construct would be a function of the ACLMessage parameter, e.g. based on the message content.

A typical (although not mandatory) means of integrating this approach with the interacting facet of an agent would be to implement behaviours that handle incoming messages and that make use of JessBehaviour methods for asserting corresponding facts into the Jess engine. Here is a snippet for an example message handling behaviour:

 1 class MsgListening extends CyclicBehaviour {
 2     // a reference to the JessBehaviour instance
 3     private JessBehaviour jessBeh;
 4     
 5     MsgListening(Agent agent, JessBehaviour jessBeh) {
 6         super(agent);
 7         // save reference to the JessBehaviour instance
 8         this.jessBeh = jessBeh;
 9     }
10     
11     public void action() {
12         MessageTemplate mt = ... // some template
13         ACLMessage msg = myAgent.receive(mt);
14         if (msg != null) {
15             // put into Jess engine
16             if(jessBeh.newMsg(msg))
17                 ... // do something
18             else
19                 ... // do something else
20         } else
21             block();
22     }
23     
24 } // end MsgListening class

The behaviour's constructor (lines 5-9) stores a reference to the JessBehaviour instance (line 8). The behaviour execution (lines 11-22) gets an incoming message (line 13) and adds it to the Jess engine by invoking an appropriate method (line 16).


3. Implementation choices

The integration of JADE and Jess can be done in a number of different ways (see the "Jess Application Design" section of the Jess manual for possible Jess and Java integrations). In this section a few options are highlighted.

3.1 From percepts to Jess

Looking back at Figure 1, so far we have dealt with implementing part of the agent's program using Jess, and the provision of information coming from the agent's percepts (i.e., messages) to the Jess engine. Even in this part of the process, we could have a tighter integration of JADE and Jess by letting the Jess engine access the whole ACL message structure. There are two ways to do that.

One way is to define a Jess ACLMessage template that maps the ACLMessage class. Something like:

(deftemplate ACLMessage
    (slot communicative-act)
    (slot sender)
    (multislot receiver)
    (slot content)
    (slot reply-with)
    (slot in-reply-to)
    ...)

This way, we only need a method that translates an ACLMessage object into a Jess construct for an ACLMessage fact. Jess rules could then directly process these facts. Caution must be taken when converting the content slot. This is because in an ACL message you can have a content as a string including spaces, which will not qualify as a valid slot value in Jess (see the Jess language basics). A solution might be surrounding the content string with double quotes ("), and escaping embedded quote symbols (\").

Another more interesting and simple approach would be to take advantage of the Jess-Java integration, which allows Jess to access any Java object (read the Jess manual on shadow facts for details). This way, we no longer need to assemble, in Java, a Jess construct from an ACLMessage object. You define an appropriate Jess template for the class:

(deftemplate ACLMessage
    (declare(from-class ACLMessage)))

You can add an ACLMessage object to the Jess engine working memory by using Rete.add(ACLMessage), and access its beans directly inside Jess rules. Something like:

(defrule incomming-msg
    (ACLMessage (sender ?s))
    =>
    (printout t "Just received a message from " (?s getLocalName) crlf))

Unfortunately, ACLMessage objects that include a string content (which will happen most of the times, since using content objects is not FIPA compliant) misbehave when asserting them as shadow facts in Jess. I believe this is due to the non-compliance of this class with the standard design patters of JavaBeans used in java.beans.Introspector (and through it in Jess). The content parameter is defined as a StringBuffer, while its getter/setter methods are implemented as returning/getting a String.

To solve this issue, you may have to implement an ACLMessageBeanInfo class. An alternative solution is to extend the ACLMessage class and override the use of the content parameter. Something like:

public class MyACLMessage extends ACLMessage {
    private String content;
    
    MyACLMessage(int perf) {
        super(perf);
    }
    
    public String getContent() {
        return content;
    }
    
    public void setContent(String content) {
        this.content = content;
    }
}

You then base your ACLMessage Jess template in the MyACLMessage class (at from-class). You may, however, loose some of the functionality of the current ACLMessage implementation. It might also be useful to add a constructor that takes an ACLMessage as an argument, to be used when you want to assert the message into Jess (unless all your agents use MyACLMessage).

3.2 From Jess to actions

The acting part of the process illustrated in Figure 1, where an action consists of sending a message, is discussed in this section.

The example included in the "Embedding Jess in a Java Application" section of the Jess manual shows how to get the results from the inference engine after it stops. A similar approach could be implemented in the JessBehaviour.action() method: after the Jess engine stops, a procedure for collecting specific types of facts that represent the output of the reasoning process would be executed, and those facts could then be used, e.g., to create a reply message. However, this would not take advantage of a tighter integration with JADE.

Message sending can also be done from Jess itself. Again depending on the intended level of integration, there are at least two ways of accomplishing this task. One way is to make use of Jess user-functions. This feature enables you to implement, in Java, a function that is to be invoked in your Jess code. You could, therefore, implement a jess.Userfunction for allowing message sending from Jess:

 1 public class JessSend implements jess.Userfunction {
 2     private Agent myAgent;
 3     
 4     public JessSend(Agent a) {
 5         myAgent = a;
 6     }
 7     
 8     // Function name to be used in Jess
 9     public String getName() {
10         return ("send");
11     }
12     
13     // Called when (send ...) is executed at Jess
14     public Value call(ValueVector vv, Context context) 
throws JessException {
15         // get function arguments
16         ... vv.get(...
17         ...
18         
19         // prepare message to send
20         ACLMessage msg = new ACLMessage(...);
21         msg.set...
22         ...
23         
24         // send the message
25         myAgent.send(msg);
26         
27         return Funcall.TRUE;
28     }
29     
30 } // end JessSend class

And add this function to the Jess engine at the JessBehaviour constructor:

jess.addUserfunction(new JessSend(myAgent));

Whenever Jess finds a (send ...) function call, it will use the above JessSend.call(ValueVector, Context) method (lines 14-28). Inside it, and depending on whatever information your implementation passes to the send function, you get the function's arguments (lines 15-17) and use them somehow to compose a message to send (lines 20-22). Depending on your choices from section 3.1, you could pass the send function a Jess ACLMessage construct or even an ACLMessage object.

Another way of sending messages from Jess, with increasing JADE-Jess mixture, is to grant it access to the Agent object, using Rete.add(Agent). The Agent.send(ACLMessage) method is then directly invokeable from Jess code.


4. Acknowledgements

Part of the code presented in this tutorial is based on the Jess example currently provided with JADE. Credits go to Fabio Bellifemine.