Introducción
Este tutorial esta destinado a aquellos que desean dar sus primeros pasos con MadKit. Es verdad que ya están los tutoriales propios a MadKit, el solo problema es que la mayoría de los documentos están en Ingles, y algunos en Francés.
En este documento no voy a entrar en detalles sobre que son los agentes o los sistemas multi-agentes (SMA). Para una (muy) buena discusión sobre el tema recomiendo libro de Jacques Ferber.
Este tutorial supone ciertos conocimientos previos:
- Agentes
- Java: No es necesario ser un experto en el tema para seguir el tutorial (de hecho, yo estoy muy lejos de serlo). Pero un comprensión básica de como compilar el código, que es el CLASSPATH, etc. son necesarios.
- Madkit: Nada, justamente la idea es que nunca trabajaste con Madkit. Si ya desarrollaste sistemas con MadKit, este tutorial te va a parecer demasiado simple. Pero si estas a la busqueda de otros temas en madkit que no encontraste, mandame un mail… en una de esas puedo ayudar.
Instalación
En el momento de la ultima versión oficial es la 4 (hace apenas unos días). Existen varias formas de instalar Madkit:
- Red: esta es la forma mas recomendada. Las razones son diversas, una de las primeras es que podes mantener la instalación al día. Supongo que la segunda es que podes instalar madkit por partes. Es decir, seleccionar los plugins que necesitas e instalarlos, sin necesidad de descargar todo un zip de mas de 20 Mb. Claro, el problema mas importe es que hace falta un conexión a internet pero supongo que se estas leyendo esto, no es tu problema ;).
- Zip: También podes descargar un Zip e instalar apartir de este archivo. Este método también te permite mantener tu instalación al día. Pero cada vez hace falta descargar mas de 20 Mb., lo cual no es siempre evidente.
- Fuentes: Madkit esta bajo GPL. Por lo tanto podes obtener las fuentes del madkit del cvs de sourceforge y compilarlas.
Para compilar las fuentes podes utilizar Apache Ant.
Sea cual sea el método que elijas, están descriptos en detalle en la documentación del Plugin Manager
En el resto de este documento voy a considerar que el directorio de base de la instalación de Madkit es $MADKIT
Principios de MadKit
MadKit es una plataforma para la implementación de Sistemas Multi-Agentes. Esta basada sobre el modelo AGR de Ferber y Gutknecht. AGR en un modelo organizacional basado sobre los principios de Agente, Grupo y Rol.
Este documento no intenta ser una introducción a AGR, ni a los modelos organizacionales para los SMA. Pero vamos a describir solamente las bases para poder comprender el funcionamiento del SMA que vamos a desarrollar y el porque de ciertas elecciones.
El modelo AGR
El modelo AGR esta basado en las nociones de Agente, Grupo y Rol.

- Agente
- Es una entidad capaz de comunicar atreves de los roles que juegan en los diferentes grupos
- Grupo
- Role
Estructura general de la Plataforma
La arquitectura general de la plataforma puede ser representada de la forma siguiente (imagen del MadKit Development Guide)

El componente principal es el Kernel (o micro-kernel). El kernel provee soporte para:
- Control de grupos y roles locales
- Manejo del ciclo de vida de los Agentes
- Despacho de Mensajes locales
Implementación de Agentes en Madkit
La clase de base para un agente en Madkit es el AbstractAgent. Esta clase ofrece las primitivas básicas de comunicación (enviar y recibir mensajes), gestión de grupos y roles, etc.
La segunda clase importante es Agent. A diferencia de la primera, esta clase contiene un Thread para la ejecución del agente.
Todo agente en Madkit es una extensión de una de estas dos clases (para ser estrictos, todos son extensiones de AbstractAgent).
Para no discutir “en el aire” de agentes, grupos, etc. Vamos a utilizar el clásico ejemplo de Madkit…. NO.. no es el “hola mundo”… sino el “PingPong”. El código original fue escrito por Olivier Gutknecht.
El código completo se encuentra en $MADKIT/plugins/demos/src/madkit/demos/PingPong.java. Por ahora vamos a (re-)desarrollar el mismo agente poco a poco para introducir el funcionamiento de un agente en madkit.
Antes de comenzar a implementar nuestro agente, veamos que queremos que haga. Vamos a simular un partido de ping-pong entre dos agentes. Para ello, vamos a crear un grupo y un rol para que los agentes puedan “encontrarse”. Una vez que tengamos dos agentes dispuestos a jugar, vamos a intercambiar mensajes entre ellos para simular el envío de la pelota.
Ciclo de vida de un agente
Para nuestro ejemplo, vamos a utilizar la clase Agent, que dará a nuestro agente su propio thread de ejecución. Aunque podríamos utilizar AbstractAgent, esto demandaría la creación de un Scheduler para su ejecución (Tema de otro documento, algún día).
El ciclo de vida de un agente (usando la clase ), requiere la implementación de tres metodos. El primero es el método activate() que es llamado en el momento de la inicialización del agente. El segundo es live() donde se define el comportamiento del agente. El tercero es end() llamado a la finalización del agente.
De estos tres metodos, solo live() es obligatorio (si queremos que nuestro agente haga algo). Los otros dos (activate y end) tienen una implementación (vacía) por “default” en AbstractAgent.
Preparemos entonces nuestra clase para PingPong.
import madkit.kernel.*;
public class PingPong extends Agent{
public void activate(){}
public void live(){}
public void end(){}
} |
Este código nos da una “carcasa” vacía para implementar nuestro agente. Ahora tenemos que completarla.
Gestión de grupos y Roles
Un modelo organisational
Madkit es una plataforma para implementa modelos Organizacionales. Y por ello el medio de contacto de los agentes son los roles que juegan en los grupos a los que pertenecen.
Para que nuestro agente pingpong pueda encontrar un otro jugador nos hace falta crear un grupo. En Madkit, los grupos y los roles son definidos usando “Strings” (cadenas de caracteres). Atención: el grupo “MiGrupo” no es el mismo que “Migrupo”. Los Strings son sensibles a las mayúsculas y minúsculas!
Para crear un grupo podemos usar dos metodos:
int createGroup(boolean d, String g, String desc, GroupIdentifier gi)
Este método cuenta con 4 parametros. El primero (boolean d) indica si grupo es distribuido o no. Madkit permite la interconexión de kernels en red. Este parametro indica al kernel si deseamos que los otros kernels de la red sean notificados de los cambios en el grupo (creación / eliminación de roles, etc). Mas adelante veremos un poco mas de la distribución en red. Un dato importante: Todas las operaciones que vamos a ver (envío de mensajes, pedido de roles, etc) son transparentes para el agente. El segundo parametro es el grupo que deseamos crear. El tercero (String desc) es una descripción del grupo. Y el cuarto es un objeto que implementa un medio de seguridad para el ingreso al grupo (mas de esto luego).
Por ejemplo para nuestro agente PingPong vamos a usar
createGroup(true, “ping-pong”, null, null)
Esto creara un grupo distribuido llamado “ping-pong”, sin descripción y sin control de acceso.
El segundo método que podemos usar es:
int createGroup(boolean d, String c, String g, String desc, GroupIdentifier gi) |
En este método agregamos un parametro (String c) que indica la comunidad en la que queremos crear el grupo. Una comunidad es un “grupo de grupos”. De hecho, cuando usamos createGroup(true, “ping-pong”, null, null), nuestro grupo ping-pong es creado en la comunidad “public” de Madkit. Las comunidades son utilizadas principalmente para crear espacio separados para diferentes aplicaciones, evitando así que dos aplicaciones usen el mismo nombre de grupo para cosas diferentes. No voy a entrar mas en detalle sobre las comunidades ahora. Por ahora considerenlas como espacios separados.
Volvamos a nuestro ejemplo de PingPong y preparemos nuestro agente para comenzar a jugar.
import madkit.kernel.*;
public class PingPong extends Agent{
private boolean creator=false;
public void activate(){
println("PingPong agent test");
println("Buscando el grupo ping-pong");
if (isGroup("ping-pong"))
{
println ("Existe, me uno");
creator=false;
}
else
{
println ("No existe, voy a crearlo");
createGroup(true,"ping-pong",null,null);
creator=true;
}
requestRole("ping-pong","player",null);
}
public void live(){}
public void end(){}
} |
Veamos que hay de nuevo en nuestro método activate(). El primer método que encontramos es println(). Este método envía un String a el Output Writer de Madkit. Este writer no es siempre el System.out. Madkit nos permite definir el writer que deseemos. Es preferible de utilizar println() que System.out.println().
Veamos ahora los metodos relacionados con los grupos. El primero es isGroup(), este método nos indica si existe un grupo “ping-pong”. Nuestro agente verifica si el grupo existe (linea 10) y si no crea uno. Para crear un grupo usamos el método createGroup().
Una vez que el agente creo el grupo, vamos a solicitar el rol “player” (linea 21). El método requestRole tiene el formato siguiente.
int requestRole(String g, String r, Object id) |
Los dos primeros parametros son el grupo y el role que deseamos. El tercer parametro es un identificador que sera pasado al GroupIdentifier definido para el grupo en la creación (cuarto parametro del createGroup de la linea 18). Como en la creación del grupo pasamos null como GroupIdentifier, podemos pasar null en requestRole.
Bueno, ya tenemos nuestro agente listo para comunicar con otro. Ahora podemos comenzar el desarrollo del comportamiento de nuestro agente.
Este comportamiento es programado en la clase live() del agente. Lo primero que tenemos que hacer es esperar que otro agente tome el role “player” en grupo “ping-pong”.
Veamos como modificamos nuestro método live para agregar este comportamiento
import madkit.kernel.*;
public class PingPong extends Agent{
private boolean creator=false;
private AgentAddress other=null;
public void activate(){
println("PingPong agent test");
println("Buscando el grupo ping-pong");
if (isGroup("ping-pong"))
{
println ("Existe, me uno");
creator=false;
}
else
{
println ("No existe, voy a crearlo");
createGroup(true,"ping-pong",null,null);
creator=true;
}
requestRole("ping-pong","player",null);
}
public void live(){
println("Buscando a otro jugador...");
do
{
exitImmediatlyOnKill();
pause(100);
AgentAddress[] v = getAgentsWithRole("ping-pong","player");
for (int i=0; i < v.length; i++)
{
AgentAddress agent = v[i];
if (! agent.equals(getAddress()))
other = agent;
}
}
while (other == null);
println("Other is :"+other);
//A terminar (mas adelante)
}
public void end(){}
} |
Esta, por ahora primera, parte del live va esperar que otro agente tome el rol “player”. Detallemos un poco mas.
Primero, en la linea 6, agregamos una variable de tipo AgentAddress que va a contener la dirección del otro agente. La AgentAddress es un identificador único que el kernel asigna a cada agente sobre la plataforma. Con esta dirección podemos enviar un mesaje a un agente particular. Luego en el método live() agregamos un bucle con el que vamos a esperar al otro agente. Mientras no haya otro agente con el rol “player” (other==null) esperamos.
El método exitImmediatlyOnKill(); es utilizado para asegurar que el thread del agente sea terminado correctamente (ligado a un problema en java y no a madkit). Es importante que cada vez que haya un bucle se agregue este método.
El método pause() va a detener la ejecución del agente por el tiempo indicado por el parametro (tiempo en milisegundos).
Luego vamos a recuperar las direcciones de todos los agentes con el rol “player” en grupo “ping-pong” (linea 31) usando el método getAgentsWithRole(). Este método devuelve las direcciones de todos los agentes con el rol indicado. Existe una variación de este método que va devolver uno solo de los agentes con el rol buscado. El método es el siguiente
getAgentWithRole(String g, String r)
La dirección del agente devuelto por el método es seleccionado al azar por la plataforma.
Volviendo a nuestro código, una vez que las direcciones son recuperadas iteramos para compararlas con la dirección de nuestro agente (obtenida con getAddress() en la linea 35)
Una vez que encontremos un segundo jugador, podemos comenzar a comunicar con el.
Primitivas de comunicación
La comunicación en madkit se hace atreves de mensajes. La clase de base para todos los mensajes en Madkit es la clase Message. Esta clase contiene únicamente el emisor, el receptor y el momento de creación del mensaje. Estos valores son accesible con los metodos getSender(), getReceiver() y getCreationDate(), respectivamente.
Los diferentes tipos de mensajes existentes en Madkit heritan de esta clase.
Las clases de mesajes más utilizadas en madkit son:
- StringMessage: permite enviar como contenido del mesaje una cadena de caracteres (String de java).
- ObjectMessage: permite encapsular un objeto serializable.
- XMLMessage: el contenido es un documento XML
Para implementar un nuevo tipo de mensaje, solo debe heritar de la clase Message.
Envio de mensajes
En este punto ya hemos preparado el código para encontrar un segundo agente. Una vez que lo hayamos encontrado debemos comunicar con el. Veamos como modificamos el código para realizar esto.
import madkit.kernel.*;public class PingPong extends Agent{
private boolean creator=false;
private AgentAddress other=null;
public void activate(){
println("PingPong agent test");
println("Buscando el grupo ping-pong");
if (isGroup("ping-pong"))
{
println ("Existe, me uno");
creator=false;
}
else
{
println ("No existe, voy a crearlo");
createGroup(true,"ping-pong",null,null);
creator=true;
}
requestRole("ping-pong","player",null);
}
public void live(){
println("Buscando a otro jugador...");
do
{
exitImmediatlyOnKill();
pause(100);
AgentAddress[] v = getAgentsWithRole("ping-pong","player");
for (int i=0; i < v.length; i++)
{
AgentAddress agent = v[i];
if (! agent.equals(getAddress()))
other = agent;
}
}
while (other == null);
println("Other is :"+other);
// Si no soy el creador del grupo, envio la pelota primero.
if (! creator)
sendMessage(other, new StringMessage("Ball"));
for (int i = 5; i > 0; i--)
{
Message m = waitNextMessage();
if(! m.getSender().equals(other))
{
while(true)
{
exitImmediatlyOnKill();
Message m2 = waitNextMessage(1000);
if(m2==null)
{
other = m.getSender();
i=5;
println("El otro jugador se fue !!");
println("Encontre un nuevo jugador "+other);
break;
}
else
if(m2.getSender().equals(other))
break;
}
}
StringMessage ans = (StringMessage) m;
println("GEE ! Mi Turno..."+ m.getCreationDate());
pause(1000);
sendMessage(other, new StringMessage(ans.getString()));
}
}
public void end(){
println("Bye Bye !!");
println ("Agente PingPong Terminado");
}
} |
Si nuestro agente no es el creador del grupo, le enviamos un mensaje (que contiene un String de java) para iniciar la comunicacion (linea 44). Para ello usamos el método sendMessage()
El primer parametro es la dirección de el otro agente (contenida en other en nuestro código). El segundo es el mensaje (StringMessage en nuestro caso).
Existen varias variaciones de este método, permitiendo definir el role y el grupo sin necesidad de buscar antes la AgentAddress, etc. Existe también el método broadcastMessage() que nos permite enviar un mensaje a todos los agentes con un rol en particular.
Recepción de Mensajes
Una vez que enviamos el primer mensaje (si no es el creador del grupo), vamos a esperar una respuesta. Para ello usamos el método waitNextMessage() (linea 48). Este método va a detener la ejecución del agente hasta que reciba un mensaje. Es un método muy útil, pero también peligroso. Si el agente no recibe un mesaje, quedara bloqueado indefinidamente. Si no estamos seguros que vamos a recibir un mensaje, es mejor usar la variación de este método que nos permite definir un timeout (linea 54). El timeout es definido en milisegundos. En caso que el timeout expire, el método devuelve null. Por ellos siempre debemos controlar que el mensaje no sea null.
Este método esta disponible solo en los agentes que heritan de Agent. Además de waitNextMessage() existe una versión no bloqueante llamada nextMessage()- El método devuelve el primer mensaje disponible o null si no hay ninguno.
Cada Agente enviara el mismo mensaje 5 veces y luego terminará su comportamiento (en términos de java, saldrá del metodo live(). Para indicar el fin del agente, agregamos en el método end() dos mensajes.
Con esto hemos terminado nuestro Agente!!!!
Como ven desarrollar un SMA en Madkit no es nada complicado. Ahora solo nos queda ver como vamos a lanzar y ejecutar nuestro agente.
Lanzar agentes
Como ya mencionamos antes, los agentes en madkit se ejecutan sobre el Kernel. En esta sección vamos a preparar una clase simple para lanzar nuestro agente PingPong.
Esta clase, que llamaremos SMA, no tiene interfaz gráfica. Sin embargo es posible de crear nuestra propia interface y adaptarla a las necesidades de nuestra aplicación. Como pueden ver con el Desktop (el mejor ejemplo de una interfaz para el kernel) practicamente nada es imposible como interfaz.
Pasemos al código:
import madkit.kernel.*;
public class SMA{
private Kernel miKernel=null;
public SMA(){
miKernel=new Kernel("Mi PingPong Kernel");
PingPong p1=new PingPong();
System.out.println("Lanzando el primer agente");
miKernel.launchAgent(p1, "PingPong1", miKernel, false);
PingPong p2=new PingPong();
System.out.println("Lanzando el Segundo agente");
miKernel.launchAgent(p2, "PingPong2", miKernel, false);
}
public static void main (String args[]){
new SMA();
}
} |
Primero, declaramos una variable miKernel de tipo Kernel (madkit.kernel.Kernel). El constructor de nuestra clase creamos el objeto (linea 8). El parametro es un String que define el nombre que deseamos para nuestro kernel. Existen otros constructores disponible descriptos en la javadoc de madkit.
Luego, vamos a lanzar un agente de tipo PingPong (linea 12) con el método launchAgent():
launchAgent(AbstractAgent agente, String nombre , Object creador, boolean tieneGUI) |
El primer parametro es el agente que deseamos lanzar. El segundo es el nombre con el que deseamos identificar al agente (útil solo para los humanos - no tiene influencia en el comportamiento del agente). El tercero es el objeto creador del agente. Este objeto es usados para implementar una cierta “seguridad”. Solo el creador del agente puede demandar a “matarlo”. El cuarto es un boolean que indica al kernel si el agente tiene o no una interfaz gráfica.
Los agentes también pueden lanzar otros agentes. Para ellos usamos el método:
launchAgent(AbstractAgent agente, String nombre , boolean tieneGUI)
Solo el parametro creador no es utiliza ya que ya sabemos que es el agente que llama el método quien es el creador del nuevo agente.
Finalmente, como dijimos, también pordemos matar agentes. Para ellos usamos el método:
killAgent(AbstractAgent agente)
El método requiere para una referencia al agente. Se deseamos matar un agente que creamos, debemos guardar una referencia al agente para hacerlo.
Muy bien, listo!!!!… Ahora solo falta compilar y lanzar la clase SMA…
javac -cp $MADKIT/lib/madkitkernel.java:. SMA.java
java -cp $MADKIT/lib/madkitkernel.java:. SMA
Si todo salio bien deberiamos ver algo como los siguiente:
-----------------------------------------------------
MadKit/Aalaadin
by MadKit Team (c) 1997-2005
version: 4.1.1 - Arpeggios
-----------------------------------------------------
Please file bug reports on the madkit forum @ http://www.madkit.org
<Mi PingPong Kernel> : MadKit Agent microKernel desktop:K1129949651639 is up and running
Lanzando el primer agente
Lanzando el Segundo agente
<Mi PingPong Kernel> : [PingPong1] PingPong agent test
<Mi PingPong Kernel> : [PingPong1] Buscando el grupo ping-pong
<Mi PingPong Kernel> : [PingPong1] No existe, voy a crearlo
<Mi PingPong Kernel> : [PingPong1] Buscando a otro jugador...
<Mi PingPong Kernel> : [PingPong2] PingPong agent test
<Mi PingPong Kernel> : [PingPong2] Buscando el grupo ping-pong
<Mi PingPong Kernel> : [PingPong2] Existe, me uno
<Mi PingPong Kernel> : [PingPong2] Buscando a otro jugador...
<Mi PingPong Kernel> : [PingPong1] Other is :PingPong2,3@desktop:1639
<Mi PingPong Kernel> : [PingPong2] Other is :PingPong1,2@desktop:1639
<Mi PingPong Kernel> : [PingPong1] GEE ! Mi Turno...Sat Oct 22 04:54:11 CEST 2005
<Mi PingPong Kernel> : [PingPong2] GEE ! Mi Turno...Sat Oct 22 04:54:12 CEST 2005
<Mi PingPong Kernel> : [PingPong1] GEE ! Mi Turno...Sat Oct 22 04:54:13 CEST 2005
<Mi PingPong Kernel> : [PingPong2] GEE ! Mi Turno...Sat Oct 22 04:54:14 CEST 2005
<Mi PingPong Kernel> : [PingPong1] GEE ! Mi Turno...Sat Oct 22 04:54:15 CEST 2005
<Mi PingPong Kernel> : [PingPong2] GEE ! Mi Turno...Sat Oct 22 04:54:16 CEST 2005
<Mi PingPong Kernel> : [PingPong1] GEE ! Mi Turno...Sat Oct 22 04:54:17 CEST 2005
<Mi PingPong Kernel> : [PingPong2] GEE ! Mi Turno...Sat Oct 22 04:54:18 CEST 2005
<Mi PingPong Kernel> : [PingPong1] GEE ! Mi Turno...Sat Oct 22 04:54:19 CEST 2005
<Mi PingPong Kernel> : [PingPong1] Bye Bye !!
<Mi PingPong Kernel> : [PingPong1] Agente PingPong Terminado
<Mi PingPong Kernel> : [PingPong2] GEE ! Mi Turno...Sat Oct 22 04:54:20 CEST 2005
<Mi PingPong Kernel> : [PingPong2] Bye Bye !!
<Mi PingPong Kernel> : [PingPong2] Agente PingPong Terminado
Últimas Remarcas
Este documento describe un agente simple, la idea principal es de dar una introducción progresiva a el ciclo de vida, funcionamiento y los método disponibles para el desarrollo de agentes.
En el futuro espero poder preparar introducciones un poco mas avanzadas a madkit. Si tenes alguna sugerencia sobre el tema para otros tutorial, no dudes en hacermelas llegar.
Tagged Java, MadKit, MultiAgent Systems, tutorial