Reactive Message Agent

From Agent Factory

Jump to: navigation, search

Contents

Introduction

In addition to the deliberative agent architectures that underpin the AFAPL and AFAPL2 agent oriented programming languages, Agent Factory also supports the creation of reactive agents via the Reactive Message Agent (RMA) architecture. This architecture is based on the simple example architecture that is presented as part of the Architecture / Interpreter Development Guide, and is used to implement the Robust FIPA Architecture infrastructure.

Overview of Architecture

The architecture is intended to support the fabrication of reactive agents. Specifically, it supports handling of:

  • Messages received from other agents that are handled by a MessageHandler; and
  • Events received from internal components that are handled by an EventHandler.

As is shown in the schematic below, these are augmented with a simple map-based memory is provided to allow information to persist and be shared across different message and event handlers.

In this architecture, the RMA Agent monitors an internal inbox (implemented by a core component of ALL agents known as the FIPAMessageQueue) for new messages. On receipt of a new message, the currently loaded message handlers are checked until either an appropriate message handler is located or there is no suitable message handler (in this latter case, the agent sends back a not-understood message). If an appropriate message handler is located, then it is invoked.

The message handler is able to do some combination of the following:

  • generate an appropriate response message
  • generate an internal event, which is passed to the agents event queue
  • perform operations on platform services
  • interact with some other component that is external to the core agent architecture
  • access or update the agents shared memory

In addition to handling incoming messages, the agent also handles events that are added to the event queue. These events may be generated by either a message handler or some other external (to the architecture) component. These events are processed in the same way as messages: a filter is used to check the relevant of each loaded event handler to the event, and each event handler is checked until either no suitable event handler is found (in which case, the event is ignored), or an event handler is found and executed. The event handler may do any combination of the operations identified for message handlers.

In contrast with languages such as AFAPL, RMA-style agents are purely Java entities and are created by extending the com.agentfactory.rma.ReactiveMessageAgent class which is provided as part of the RMA Development Kit, instances of the resultant agent implementations are then created by specifying relevant Java class as is discussed in both the Architecture / Interpreter Development Guide and the Application Deployment Guide.

Message Handlers

Message handlers are used to react to messages that are received from other agents. Logically, a message handler consists of two parts:

  • A filter part that checks whether the handler should be used to handle a given message, and
  • A handler part that specifies what the agent should do if the filter determines that a given message is relevant to the handler.

Message handlers are implemented by subclassing the com.agentfactory.rma.MessageHandler class.

For example, if you wish to develop an agent that implements a "ping-pong" style protocol (often used as part of a health monitoring system), where some agent sends a "ping" request and your agent must respond by informing the sender of "pong". The response part of this "ping-pong" protocol would be implemented using a message handler of the form:

   public class PingRequestHandler extends MessageHandler {
           super(agent, "request");
       }
           
       @Override
       public boolean filter(Message message) {
           return message.getProtocol().equals("ping-pong") &&
                   message.getContent().equals("ping");
       }
   
       @Override
       public boolean act(Message message) {
           StringMessage response = StringMessage.newInstance();
           response.setSender(agent.getAgentID());
           response.getReceivers().add(message.getSender());
           response.setPerformative("inform");
           response.setProtocol(message.getProtocol());
           response.setContent("pong");
           response.setConversationId(message.getConversationId());
           return send(response);
       }
   }

As can be seen, the filter(...) method checks that the protocol name is correct and that the content has the correct format. Here, the filter performs an exact match, however, where the content takes a more complex format, it is more usual for only a partial match may be used here. Should this filter return true, then the act(...) method is invoked. This method is responsible for actually handling the incoming message. While the example above simply constructs and sends a response, other more complex message handlers may perform some form of analysis and construct different response messages, or decide do to respond. In the case where the handler cannot handle the received message, the act(...) method should return false. This will cause the RMA agent to check if another Message Handler can handle the message, and if none is found, a not-understood message will be returned to the sender.

This Hessage Handler would typically be associated with the agent when it is created, as follows

   public class TestAgent extends ReactiveMessageAgent {
       public TestAgent(String name) {
           super(name);
       
           addMessageHandler(new PingRequestHandler(this));
       }
   }

Event Handlers

Similarly, event handlers are used to react to events that are generated by non-agent components that are linked to an agent. Logically, an event handler consists of two parts:

  • A filter part that checks whether the handler should be used to handle a given event, and
  • A handler part that specifies what the agent should do if the filter determines that a given event is relevant to the handler.

Event handlers are implemented by sub-classing the com.agentfactory.rma.EventHandler class.

By way of illustration the trivial example below uses an event handler to update a counter until it reaches 10.

   public class TestEventHandler extends EventHandler {
       public TestEventHandler(ReactiveMessageAgent agent) {
           this.agent = agent;
       }
   
       @Override
       public boolean filter(Event event) {
           return event.getType().equals("test");
       }
   
       @Override
       public void handle(Event event) {
           Integer value = (Integer) event.getData();
           System.out.println("Iteration: " + value.intValue());
           if (value.intValue() < 10) {
               value = new Integer(value.intValue() + 1);
               agent.addEvent(new Event("test", value));
           }
       }
   }

This event handler handles an event of type "test" and upon detection of such an event, extracts the integer value stored in the data part of the event, prints out the value, and if the value is less than 10, generates a new event of type "test", where the data is the integer value incremented by 1. This behaviour requires an initial event which can be declared when the associated agent is created.

   public class TestAgent extends ReactiveMessageAgent {
       public TestAgent(String name) {
           super(name);
       
           addEventHandler(new TestEventHandler(this));
           addEvent(new Event("test", new Integer(0)));
       }
   }

As can be seen, events are passed to the agent via the addEvent(...) method which is provided as part of the core RMA agent architecture implementation.

Shared Memory

The RMA Agent provides a shared memory that can be used to persist and share information between handlers. This memory takes the form of a map that associates String keys with Object values, allowing data to be stored in any appropriate format. The shared memory can be directly accessed within the agent implementation class via the "memory" field. For handlers, the memory can be accessed via the getMemory() method that is provided by the RMA Agent Architecture class.

For example, our example event handler could store the current value of the counter in memory as follows:

  public class TestEventHandler extends EventHandler {
      public TestEventHandler(ReactiveMessageAgent agent) {
          this.agent = agent;
      }
  
      @Override
      public boolean filter(Event event) {
          return event.getType().equals("test");
      }
  
      @Override
      public void handle(Event event) {
          Integer value = (Integer) agent.getMemory().get("counter");
          System.out.println("Iteration: " + value.intValue());
          if (value.intValue() < 10) {
              agent.getMemory().put("counter", new Integer(value.intValue() + 1));
              agent.addEvent(new Event("test", null));
          }
      }
  }

In our revised implementation, we could initialise the counter as follows:

  public class TestAgent extends ReactiveMessageAgent {
      public TestAgent(String name) {
          super(name);
      
          addEventHandler(new TestEventHandler(this));
          memory.put("counter", new Integer(0));
          addEvent(new Event("test", null));
      }
  }

Platform Services

The RMA Agent supports binding to and unbinding from platform services via associated methods bindToService(...) and unbindFromService(...) that are inherited from the generic agent class com.agentfactory.platform.core.Agent.

Lesson 1: Basic Examples

To get things started, this example shows how to create a simple "hello world" agent. To create a Reactive Message Agent, you must extend the com.agentfactory.rma.ReactiveMessageAgent class:

 import com.agentfactory.platform.AgentPlatform;
 import com.agentfactory.rma.ReactiveMessageAgent;

 public class HelloWorldAgent extends ReactiveMessageAgent {
     public HelloWorldAgent(String name, AgentPlatform platform) {
         super(name, platform);
        
         System.out.println("Hello World!");
     }
 } 

As can be seen in the above code, our first implementation is very simple, as the text "Hello World!" is printed out when the constructor is invoked (i.e. when the agent is created).

A more complex solution, but one which actually makes use of the Reactive Message Agent architecture is to use the built in event handling mechanism. To do this, we must create an Event Handler. For this example, we will implement the event handler as an inline class:

 import com.agentfactory.platform.AgentPlatform;
 import com.agentfactory.rma.Event;
 import com.agentfactory.rma.EventHandler;
 import com.agentfactory.rma.ReactiveMessageAgent;

 public class HelloWorldAgent extends ReactiveMessageAgent {
   public HelloWorldAgent(String name, AgentPlatform platform) {
       super(name, platform);
       
       addEventHandler(new EventHandler() {
           @Override
           public boolean filter(Event event) {
               return event.getType().equals("HW");
           }
 
           @Override
           public void handle(Event event) {
               System.out.println("Hello World!");
           }
       });

       addEvent(new Event("HW"));
   }
 }

Notice that, in the above solution, the HelloWorldEventHandler is added to the agent, and then an event of type "HW" is added to the agents event queue. The result is that, on the first iteration of the Reactive Message Agents execution cycle, the "HW" event is handled by this event handler, and "Hello World!" is printed out.

The advantage of this approach over the simpler approach used first, is that, should we wish to print out "Hello World!" again, all that we need to do is add another "HW" event to the agents event queue.

Finally, to run this application, you need to create a Main method:

package com.agentfactory.rma;

import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Map<String, String> design = new HashMap<String, String>();
        design.put("hello", "com/agentfactory/rma/HelloWorldAgent.java");
        new RMADebugConfiguration("test", design).configure();
    }
}

This deployment configuration allows you two instantiate only RMA agents. The second line of the main method declares that there will be one agent called "hello" that is an instance of the class com.agentfactory.rma.HelloWorldAgent. The notion used to identify the class in this example uses a .java extension, which is handled by the core AFSE implementation and results in an instance of the corresponding class being created.

To see what happens, simply click on the "step" button. This will result in the text "Hello World!" being displayed in the Java standard output console.