Building Multi-Agent Systems with JADE


 

  1. Overview
  2. Getting started
  3. Administering the JADE agent platform
  4. Identifying the building blocks for an agent programming
  5. Your first agent program
  6. Understanding and programming behaviours
  7. Defining ontologies
  8. Building agent with integrated GUI
  9. Exploring mobility
  10. What about intelligence with JADE?
  11. Developping real application systems with JADE
  12. Resources and links
  13.  

1. Overview

 

2. Getting started

 

3. Administering the JADE agent platform

 

4. Identifying the building blocks for an agent programming

 

5. Your first agent program

 

6. Programming behaviours

 

7. Defining ontologies

Beyond the communicative acts which JADE provides support to in compliance with FIPA specifications, for agents to be able to communicate in a way that makes sens for them, they must share the same language, vocabulary and protocols. In most of the cases, you will not need to define a new language for your application since the support provided by JADE with the implementation of the the codec class for the FIPA-SLn languages is complete and flexible enough to respond to your needs.

However, beside the support provided by the framework, you must define your own vocabulary and semantic for the content of the communication between your agents. This requires ultimately the definition of an ontology, i.e, the terminology and the semantic of objects involved in your agents space of communication. In fact, JADE provides three ways to implement communication between agents. The first and basic way consists of using string to represent the content of messages, as discussed previously when designing Your first program. This is convenient when the content of messages is atomic data, but not in the case of abstract concepts that need to be expressed as meta-data. In such cases, using string representation will require to parse the content of messages, which operation can be cumbersome and source of errors. The second way is based on the use of Java objects as the content of messages. Some JADE developpers will find this way easier and convenient in particular when dealing with application where agents evolve in a local and homogenous network environment and where they are built generally into the same language. Whereas the third way involves the definition of an ontology, which can be viewed as an interface that enables agents communicating in a specific language to be compliant with FIPA Agent Communication Language specifications, for the purpose of interoperability with other agent systems.

In our Bank example that follows, we show in a first step how it is possible to implement communication between agents using Java objects for the content of messages. In a second step, we show through the same example how Java objects can be converted for designing an ontology with the support for ontology provided by JADE.

7.0 The bank example

In this example, two agents implement a service of savings account. One, implemented by the BankServerAgent class, acts as a server and the other, implemented by the BankClientAgent class acts as client. The two classes implement the BankVocabulary interface that contains the list of terms that constitute the specific language of the agents. The bank directory consists of 3 subdirectories that contain the codes source for the 3 versions of the example. The first version of the example shows how to implement communication between agents using Java objects. The second version shows how to implement the communication between agents using an ontology. And the third version shows how to integrate a graphical user interface to an agent.

The conversation between the two agents follows a very simple protocol. To create an account or to make an operation, the client agent sends a REQUEST message to the server agent. The server agent responds with an INFORM after processing the request or with an NOT_UNDERSTOOD if it can not decode the content of the message. To query information about a specific account, the client agent sends a QUERY to the server agent which responds with an INFORM after processing the query or with a NOT_UNDERSTOOD if it can not decode the content of the message.

7.1 Using Java objects as content of messages

In the same file as the BankServerAgent class, are defined as external classes, a set of java objects that represent the semantics of concepts involved in our agents communication space. These objects are:

When designing the classes of your java objects, make sure to implement the java.io.Serializable interface, otherwhise the serialization of the content of the messages before their sending will fail. To set the content of a message using java objects, you just call on the message object, the method setContentObject(...) of the ACLMessage class that throws a java.io.IOException, to which you pass in argument your java object. Let's take a look of how this is done by examining the example of our client agent requesting the server agent to make an operation.

The MakeOperation class goes:

class MakeOperation implements BankVocabulary, java.io.Serializable {

private String accountId;
private int type;
private float amount;

public String getAccountId() {
return accountId;
}

public int getType() {
return type;
}

public float getAmount() {
return amount;
}

public void setAccountId(String accountId) {
this.accountId = accountId;
}

public void setType(int type) {
this.type = type;
}

public void setAmount(float amount) {
this.amount = amount;
}

}

First, we create an instance of the MakeOperation object and set it's necessary attributes, as illustrated by the following fragment of code picked from the requestOperation() and sendMessage(...) methods in the BankClientAgent class:

void requestOperation() {
...
MakeOperation mo = new Operation();

mo.setAccountId(acc.getId());

mo.setType(command);
mo.setAmount(amount);

sendMessage(ACLMessage.REQUEST, mo);
}

void sendMessage(int performative, Object content) {

ACLMessage msg = new ACLMessage(performative);
try {
msg.setContentObject((java.io.Serializable)content);
msg.addReceiver(server);

send(msg);

...
}
catch (Exception ex) { ex.printStackTrace(); }
}

On the other side, the server receives and decode the content of the message as implemented in the inner classes ReceiveMessages and HandleOperation of the BankServerAgent class:

class ReceiveMessages extends CyclicBehaviour {
public ReceiveMessages(Agent a) {
super(a);
}

public void action() {
ACLMessage msg = receive();
if (msg == null) { block(); return; }
try {
Object content = msg.getContentObject();

switch (msg.getPerformative()) {
case (ACLMessage.REQUEST):
if (action instanceof CreateAccount)
addBehaviour(new HandleCreateAccount(myAgent, msg));
else if (content instanceof MakeOperation)
addBehaviour(new HandleOperation(myAgent, msg));

...
}
}

class HandleOperation extends OneShotBehaviour {

ACLMessage request;

public HandleOperation(Agent a, ACLMessage request) {
super(a);
this.request = request
}

public void action() {

try {
Operation op = (Operation) request.getContentObject();
ACLMessage reply = request.createReply();
// Process the operation
Object result = processOperation(op);
...
}
catch (Exception ex) { ex.printStackTrace(); }
}
}
}

7.2 Defining an ontology for the content of messages

An application-specific ontology describes the elements that can be used as content of agent messages.An ontology is composed of two parts, a vocabulary that describe the terminology of concepts used by agents in their space of communication, and the nomenclature of the relationships between these concepts, and that describe their semantic and structure. You implement an ontology for your application by extending the class Ontology predefined in JADE and adding a set of element schemas describing the structure of concepts, actions, and predicates that are allowed to compose the content of your messages. You may also extend directly the basic ontology classes BasicOntology and ACLOntology. But if you choose to extend the Ontology class, you indirectly extends these basic classes since they are also extended by the Ontology class.

In the version 2 of the bank example, we defined the BankOntology class that our two agents use to communicate in place of the java objects that we discussed previously. We do not "throw away" our java objects that remain in fact valid, but instead of using them directly in the content of messages, we just wrapp them into specifics terms and concepts defined within the BankOntology class. To do that, we just modified slightly our java classes by making them implement the appropriate interfaces provided by JADE. In fact, when defining an ontology you will generally deal with the three interfaces Concept, AgentAction and Predicate. The corresponding classes to be used in your ontology class are respectively the ConceptSchema, AgentActionSchema and PredicateSchema.

Examining the hierarchy of these classes, we see that the AgentActionSchema class inherits from the ConceptSchema class which in turn is a subclass of the TermSchema class. While the PredicateSchema class inherits from the ContentElementSchema class.

java.lang.Object
|
+--jade.content.schema.ObjectSchema
|
+--jade.content.schema.ObjectSchemaImpl
|
+--jade.content.schema.TermSchema
|
+--jade.content.schema.ConceptSchema
|
+--jade.content.schema.AgentActionSchema

java.lang.Object
|
+--jade.content.schema.ObjectSchema
|
+--jade.content.schema.ObjectSchemaImpl
|
+--jade.content.schema.ContentElementSchema
|
+--jade.content.schema.PredicateSchema

An important point to know is when to use one or another of these ontology objects. To briefly explain that, let's examine these three examples:

    1. Agent A requests agent B to perform a specific task. According to FIPA, the content of the message that A sends to B must be an "action", i.e., a tuple which slots are the identifier of agent that is requested to perform the action (here agent B) and a descriptor respresenting the task to be performed (here the requested task). In JADE, the task will be defined by a java object implementing the AgentAction interface and the action will be an instance of the class Action to which you pass in arguments the AID of agent B and the object describing the task to be performed.
    2. Agent A asks agent B if a given proposition is true. According to FIPA, the content of the message must be the object representing the proposition to check. In JADE a proposition can be defined by a java object implementing the interface Predicate.
    3. Now let's take an example closer to our bank example: suppose the client agent requests the server agent to perform an action consisting of making a deposit on a given account. To do this, we defined the class MakeOperation describing the action to be performed. Since it is an action the MakeOperation class implements the AgentAction interface. On the server side, once it has processed the information, it replies by sending back together with the requested action wrapped in a Result object, an Account object representing the result of processing the operation. As the Account object is not an agent action neither a proposition, we simply defined it as a concept by implementing the interface Concept.

Besides these three interfaces that allow you to define the abstracts objects of your application ontology, JADE also provides support for defining atomic elements that constitute generally the slots of the abstract concepts, such as String, Integer, Float and so on. The support for these atomic types of objects is provided through the class PrimitiveSchema and handled by the BasicOntology class.

Applying these principles, the java objects previously defined in the version 1 of the bank example are modified as follows:

Comparativeley with the version 1, only one class disappeared. This is the OperationList class which is of no use now since the Result class (which is provided by JADE) that we use to hold the result of actions that ares performed by the server agent contains already a List object attribute.

Now let's see step by step how to put together all these pieces of puzzle to define an application-specific ontology by examining the example of making an operation.

Step 1: you define the vocabulary of your agents communication space. In the BankVocabulary interface, we have the following lines of code that define the terminology involved in the concept of making an operation:

public interface BankVocabulary {

...

public static final String MAKE_OPERATION = "MakeOperation";
public static final String MAKE_OPERATION_TYPE = "type";
public static final String MAKE_OPERATION_AMOUNT = "amount";
public static final String MAKE_OPERATION_ACCOUNTID = "accountId";

...

}

Step 2: you define the java class that specifies the structure and semantic of the object MakeOperation.

public class MakeOperation implements AgentAction {

private int type;
private float amount;
private String accountId;

public int getType() {
return type;
}

public float getAmount() {
return amount;
}

public String getAccountId() {
return accountId;
}

public void setType(int type) {
this.type = type;
}

public void setAmount(float amount) {
this.amount = amount;
}

public void setAccountId(String accountId) {
this.accountId = accountId;
}
}//End MakeOperation

A java class defining an ontology object must necessarily include the set and get methods that allow to access the attributes of the class that must be declared private. Take care when choosing the attributes names of your object and their corresponding get and set methods. In fact, you can not choose any name you like at this step because they must imperatively match (case insensitive) the names that you gave to these attributes when defining the vocabulary. for example in the vocabulary, we decided that the name for the element MAKE_OPERATION_TYPE is "type". So in the java class, the name of the attribute must be type and the corresponding get and set methods must be getType() and setType(). This is an important point to be aware about because during the operation of filling the content of the message, the content manager first reads the attribute type that it associates with the class MakeOperation. It then searches in the ontology under the MakeOperation schema, for the element identified in the vocabulary by the name "type". If it cannot find such an element it throws an exception.

Step 3: you define the schema of the object. In the BankOntology class we find these lines of code that specifiy the schema of the concept MakeOperation:

public class BankOntology extends Ontology implements BankVocabulary {

// ----------> The name identifying this ontology
public static final String ONTOLOGY_NAME = "Bank-Ontology";

// ----------> The singleton instance of this ontology
private static Ontology instance = new BankOntology();

// ----------> Method to access the singleton ontology object
public static Ontology getInstance() { return instance; }

// Private constructor
private BankOntology() {

super(ONTOLOGY_NAME, BasicOntology.getInstance());

try {

// ------- Add Concepts

...

// ------- Add AgentActions

...

// MakeOperation
add(as = new AgentActionSchema(MAKE_OPERATION), MakeOperation.class);
as.add(MAKE_OPERATION_TYPE, (PrimitiveSchema) getSchema(BasicOntology.INTEGER), ObjectSchema.MANDATORY);
as.add(MAKE_OPERATION_AMOUNT, (PrimitiveSchema) getSchema(BasicOntology.FLOAT), ObjectSchema.MANDATORY);
as.add(MAKE_OPERATION_ACCOUNTID, (PrimitiveSchema) getSchema(BasicOntology.STRING), ObjectSchema.MANDATORY);

...
}
catch (OntologyException oe) {
oe.printStackTrace();
}
}
}// BankOntology

Note that the constructor of your ontology class must be defined with private access and include the static public method getInstance() that your agent program calls to get a reference to the singleton instance of your ontology class.

In the class AgentActionSchema, you have a set of add(...) methods, some of which are inherited from the ConceptSchema class that it extends. These methods allow you to add to the schema of the object that you are defining the elements that will be used by the content manager as slots when filling the content of messages. In our example, we used the add() method that takes three arguments, the name of the slot to be added, the schema of this slot and the optionality. The optionlaity can take two values: MANDATORY indicating that the slot cannot have a null value, or OPTIONAL indicating that it can have a null value. The consequence is that if you specify MANDATORY, then this element must be imperatively provided when setting the values of corresponding attributes in the java class. On the other hand if you specify OPTIONAL, you are allowed to not provide a value for this slot.

Step 4: you are now ready to use the ontology for the content of your agents messages. To set the content of a message using an ontology, you must first register with the agent's content manager, the ontology and the language that will be used for assembling and parsing(or coding and decoding) the content of messages. In our example we use the codec language which is implemented in JADE through the class SLCodec and the ontology is naturally our BankOntology: In the BankClientAgent class in the directory Bank-2-Onto, you find these lines of code that illustrate how to register the language and ontology:

public class BankClientAgent extends Agent implements BankVocabulary {

...
private Codec codec = new SLCodec();
private Ontology ontology = BankOntology.getInstance();

protected void setup() {

// Register language and ontology
getContentManager().registerLanguage(codec);
getContentManager().registerOntology(ontology);

...
}

...

}//class BankClientAgent

To use the ontoloy when composing your message, you first set the attributes of your java object. Then you specify within the message instance, the language and ontology that it complies to. you then obtain a reference to the ContentManager object by calling the method getContentManager() of the Agent class. Finally you call the fillContent(...) method of the ContentManager object to which you pass in arguments the message and the content that it will be filled with. This is done through the following lines of code:

public class BankClientAgent extends Agent implements BankVocabulary {

...

void requestOperation() {

....
MakeOperation mo = new MakeOperation();
mo.setType(command);
mo.setAmount(amount);
mo.setAccountId(acc.getId());

sendMessage(ACLMessage.REQUEST, mo);
}

...

void sendMessage(int performative, AgentAction action) {

...
ACLMessage msg = new ACLMessage(performative);
msg.setLanguage(codec.getName());
msg.setOntology(ontology.getName());

try {
getContentManager().fillContent(msg, new Action(server, action));
msg.addReceiver(server);
send(msg);
...
}
catch (Exception ex) { ex.printStackTrace(); }
}
}//End BankClientAgent

At the server side, you follow the same steps to receive and extract the content of the message. The server agent must also register its content manager with the same language and ontology. Then, obtaining a reference to the content manager object it calls its method extractContent(...) to which it passes in argument the message to be extracted. It then casts the extracted content with the java class that it was expecting. Once it has the java object, it can finally retrieve the content of the slots by calling the get methods provided in the java class of the object. This is illustrated through the following lines of code in the BankServerAgent class:

public class BankServerAgent extends Agent implements BankVocabulary {

...
private Codec codec = new SLCodec();
private Ontology ontology = BankOntology.getInstance();

...

protected void setup() {

// Register language and ontology
getContentManager().registerLanguage(codec);
getContentManager().registerOntology(ontology);

...
}

...

class ReceiveMessages extends CyclicBehaviour {

public ReceiveMessages(Agent a) {

super(a);
}

public void action() {

ACLMessage msg = receive();
if (msg == null) { block(); return; }
try {
ContentElement content = getContentManager().extractContent(msg);
Concept action = ((Action)content).getAction();

switch (msg.getPerformative()) {

case (ACLMessage.REQUEST):

...

if (action instanceof CreateAccount)
addBehaviour(new HandleCreateAccount(myAgent, msg));
else if (action instanceof MakeOperation)
addBehaviour(new HandleOperation(myAgent, msg));

...
break;

...
}
catch(Exception ex) { ex.printStackTrace(); }
}
}// End ReceiveMessages

...

class HandleOperation extends OneShotBehaviour {

private ACLMessage request;

HandleOperation(Agent a, ACLMessage request) {

super(a);
this.request = request;
}

public void action() {

try {
ContentElement content = getContentManager().extractContent(request);
MakeOperation mo = (MakeOperation)((Action)content).getAction();

//Process the operation
Object obj = processOperation(mo);
//Send the reply
...
}
}
catch(Exception ex) { ex.printStackTrace(); }
}
}// End HandleOperation

...

}// End BankServerAgent

 

8. Building agent with integrated GUI

 

9. Exploring mobility

 

10. What about intelligence with JADE?

 

11. Developping real application systems with JADE

 

12. Resources and links