← Назад в библиотеку

Giovanni Caire, перевёл Зайцев И. М.

JADE TUTORIAL FOR BEGINNERS

Оригинал http://jade.tilab.com/doc/tutorials/JADEProgramming-Tutorial-for-beginners.pdf

ВВЕДЕНИЕ

     
JADE (Java Agent Development Framework) — программная среда разработки мультиагентных систем и приложений. Включает два основных продукта: агентную платформу и пакет разработки java-агентов. В JADE встроены инструменты, облегчающие администрирование платформы и разработку приложений.
JADE написана на языке программирования Java с использованием Java RMI, Java CORBA IDL, Java Serialization и Java Reflection API. Она упрощает разработку мультиагентных систем благодаря использованию FIPA-спецификаций и инструментов, которые поддерживают фазы отладки и развертывания системы.
Основной целью данного проекта является рассмотрение основных принципов создания многоагентных систем в среде JADE. Примером служит система «Торговля книгами». Она включает в себя агенты, продающие книги и другие агенты, покупающие книги от имени пользователей.

 

1 ОБЗОР  JADE

JADE является промежуточным слоем программного обеспечение, который предназначен для облегчения разработки мультиагентных систем. Он включает в себя:

 

1.1 Контейнеры и платформы
Каждый запущенный экземпляр среды JADE называется контейнером, так как он может содержать несколько агентов. Группа активных контейнеров называется платформой. Одиночный специальный главный контейнер (Main Container) всегда должен быть активен, и все другие контейнеры должны быть зарегистрированы им, как только они создаются. Отсюда следует, что первый контейнер, который запускается на платформе на платформе должно быть основным контейнером, а все остальные контейнеры должны быть "нормальными" (т.е. неосновными) контейнерами и должны получить указания где искать (хост и порт) их основного контейнера (т.е. того контейнера, где они должны быть зарегистрированы).

 

Если другой основной контейнер был запущен где-либо в сети, он представит собой другую платформу в которой новые нормальные контейнеры имеют возможность зарегистрироваться. Рисунок 1 иллюстрирует приведенные выше концепции описывающие сценарий с двумя JADE платформами, состоящими из трёх и одного контейнера соответственно. JADE агенты идентифицируются по уникальному имени и при условии, что они знают имя друг друга, они могут общаться напрямую, независимо от их фактического местонахождения: внутри одного контейнера, в различных контейнерах внутри одной платформы  или в различных платформах .
Не обязательно знать, как работает среда JADE, всего лишь нужно запустить её перед запуском ваших агентов. Запуск JADE в качестве главного или нормального  контейнера и выполнение агентов в ней, описаны в JADE Administrative Tutorial на сайте JADE.

1.2 AMS  и  DF

Помимо возможности приема регистраций от других контейнеров, основной контейнер отличается от нормальных контейнеров тем, что он содержит два специальных агента (автоматически запущенных, когда запустился основной контейнер).
AMS (Agent Management System) — система управления агентами, которая обеспечивает службы именования (т.е., гарантирует, что каждый агент внутри платформы имеет уникальное имя) и представляет собой «власть» в платформе (например, можно создавать / убивать агентов в удаленных контейнеров, запрашивая это через AMS).
DF (Directory facilitator) — маршрутизатор  каталогов, который обеспечивает сервис «Жёлтых страниц», с помощью которого агент может найти других агентов, которые предоставляют услуги, нужные ему для достижения своих целей.



Рисунок 1.1 — Контейнеры и платформы.

 

2  АНАЛИЗ ПРЕДМЕТНОЙ ОБЛАСТИ

Рассмотрим предметную область «Торговля книгами», на которой будут проиллюстрированы шаги, необходимые для создания агентно-ориентированных приложений с JADE. Это сценарий включает в себя агенты, продающие книги и другие агенты, покупающие книги от имени пользователей.
Каждый покупающий агент получает название книги, которую он должен приобрести в качестве аргумента командной строки и периодически запрашивает всех известных ему продавцов-агентов, чтобы сделать запрос о покупке. Как только предложение получено, агент-покупатель подтверждает его и отправляет заказ. Если больше чем один агент-продавец предоставляет данную книгу, покупатель выбирает лучшее предложение (самую лучшую цену). Купив требуемую книгу, агент-покупатель завершает работу.
Каждый агент-продавец имеет минимальный интерфейс, с помощью которого пользователь может добавлять новые названия (и их цену) в локальный каталог книг, выставленных на продажу.  Агенты-продавцы находятся в состоянии ожидания запросов от агентов-покупателей. Когда они получают запрос на книгу, они проверяют, имеется ли данная книга в их каталоге. Если да — то они отвечают предложением с ценой. Иначе — отказывают. Когда они получают заказ на покупку, они обрабатывают его и удаляют запрошенную книгу из своего каталога.
Все вопросы, связанные с электронной оплатой находятся за рамками этого примера и не рассматриваются.

 

3 РАЗРАБОТКА МНОГОАГЕНТНОЙ СИСТЕМЫ

3.1 Создание агента в Jade – класс Agent

Для создания агента в JADE необходимо просто определить класс, наследующий класс jade.core.Agent и реализовав метод setup(), как показано ниже:

import  jade.core.Agent; 
   
   public class  BookBuyerAgent extends Agent { 
   protected void setup() { 
   // Printout a welcome message 
   System.out.println(“Hello! Buyer-agent  “+getAID().getName()+” is ready.”); 
   } 
   }

 

Метод setup() должен включать инициализацию агента.

 

3.2 Идентификация агента

Каждый агент идентифицируется «идентификатором агента», представленным экземпляром класса jade.core.AID. Метод getAID() класса Agent позволяет получить идентификатор агента. AID объект включает уникальное имя и адреса. Имя в JADE имеет формат <nickname>@<platform-name>, например, агент, названный Peter, существующий в платформе P1 будет иметь глобально уникальное имя Peter@P1.
Адреса, включённые в AID —  адреса платформы, где существует агент. Эти адреса используются только тогда, когда агент запрашивает коммуникацию с другим агентом, существующим в другой платформе.
Зная имя агента, его AID может быть получен следующим образом:

String nickname = “Peter”;
AID id = new AID(nickname, AID.ISLOCALNAME);

 

Флаг ISLOCALNAME показывает, что первый параметр является именем локальным для платформы, а не глобальным уникальным именем агента.

 

3.3 Запуск и завершение работы агентов

Созданный агент может быть скомпилирован следующим образом:
javac –classpath <JADE-classes> BookBuyerAgent.java
Для того, чтобы выполнить скомпилированный агент среда JADE должна быть запущена и должно быть выбрано имя агента для запуска:
java –classpath <JADE-classes>;. jade.Boot buyer:BookBuyerAgent 
Результат выполнения последней команды приведен на рисунке 3.3. Первая часть выведенного текста —  заголовок JADE, который выводится каждый раз при запуске среды JADE. Дальше идёт инициализация сервисов ядра. Наконец, сообщение о том, что контейнер с именем «Main-Container» был успешно запущен. Когда среда JADE и наш агент запущены, выводится приветствие. Имя агента «buyer», как мы задали в командной строке. Имя платформы “NBNT2004130496:1099/JADE” автоматически присваивается, используя хост и порт, на котором запущена JADE.

 

C:\jade>java  –classpath <JADE-classes> jade.Boot buyer:BookBuyerAgent 
   5-mag-2008  11.06.45 jade.core.Runtime beginContainer
   INFO:  ---------------------------------- 
   This is JADE snapshot - revision 5995 of  2007/09/03 09:45:22 
   downloaded in Open Source, under LGPL restrictions, 
   at http://jade.tilab.com/ 
   ---------------------------------------- 
   5-mag-2008  11.06.51 jade.core.BaseService init 
   INFO: Service  jade.core.management.AgentManagement initialized 
   5-mag-2008 11.06.51  jade.core.BaseService init 
   INFO: Service  jade.core.messaging.Messaging initialized 
   5-mag-2008  11.06.52 jade.core.BaseService init 
   INFO: Service  jade.core.mobility.AgentMobility initialized 
   5-mag-2008  11.06.52 jade.core.BaseService init 
   INFO: Service  jade.core.event.Notification initialized 
   5-mag-2008  11.06.52 jade.core.messaging.MessagingService clearCachedSlice 
   INFO: Clearing  cache 
   5-mag-2008  11.06.53 jade.mtp.http.HTTPServer <init> 
   INFO: HTTP-MTP  Using XML parser com.sun.org.apache.xerces.internal.parsers.SAXParser 
   5-mag-2008  11.06.54 jade.core.messaging.MessagingService boot 
   INFO: MTP  addresses: 
   http://NBNT2004130496.telecomitalia.local:7778/acc 
   5-mag-2008  11.06.54 jade.core.AgentContainerImpl joinPlatform 
   INFO:  -------------------------------------- 
   Agent container  Main-Container@NBNT2004130496 is ready. 
   -------------------------------------------- 
   Hello!  Buyer-agent buyer@NBNT2004130496:1099/JADE is ready.
 
Даже если не  предусмотрены никакие действия после печати приветствия, наш агент всё ещё  запущен. Для того, чтобы завершить его работу нужно использовать метод doDelete(). Подобно методу setup() (который вызывается средой JADE сразу после создания агента, и в который необходимо  включить инициализацию агента), метод takeDown()  вызывается перед завершением работы агента.  Он должен содержать операции по очистке.
3.4 Передача аргументов  агенту
Агенты  могут получать аргументы при запуске из командной строки. Эти аргументы могут  быть получены как массив Object, с помощью метода getArguments() класса Agent. Нам необходимо, чтобы наш агент BookByuerAgent получал названия книги, которые  необходимо купить через командную строку. Для того, чтобы достичь этого,  модифицируем агент следующим образом:
import  jade.core.Agent; 
   import  jade.core.AID; 
   public class  BookBuyerAgent extends Agent { 
   // The title of the book to buy 
   private String targetBookTitle; 
   // The list of known seller agents 
   private AID[] sellerAgents = {new AID(“seller1”,  AID.ISLOCALNAME),new AID(“seller2”, AID.ISLOCALNAME)}; 
   // Put agent initializations here 
   protected void setup() { 
   // Printout a welcome message 
   System.out.println(“Hello! Buyer-agent  “+getAID().getName()+” is ready.”); 
   // Get the title of the book to buy as a  start-up argument 
   Object[] args = getArguments(); 
   if (args != null && args.length  > 0) { 
   targetBookTitle = (String) args[0]; 
   System.out.println(“Trying to buy  “+targetBookTitle); 
   } 
   else { 
   // Make the agent terminate immediately 
   System.out.println(“No book title  specified“); 
   doDelete(); 
   } } 
   // Put agent clean-up operations here 
   protected void takeDown() { 
   // Printout a dismissal message 
   System.out.println(“Buyer-agent  “+getAID().getName()+” terminating.”); } }
   
   Аргументы командной строки заключены  в скобки и разделены пробелами.
C:\jade>java  jade.Boot buyer:BookBuyerAgent(The-Lord-of-the-rings) 
   ... 
   ... 
   5-mag-2008  11.11.00 jade.core.AgentContainerImpl joinPlatform 
   INFO:  -------------------------------------- 
   Agent container  Main-Container@NBNT2004130496 is ready. 
   -------------------------------------------- 
   Hello!  Buyer-agent buyer@NBNT2004130496:1099/JADE is ready. 
   Trying to buy  The-Lord-of-the-Rings
 

 

 

4 ЗАДАНИЕ АГЕНТА – КЛАСС BEHAVIOUR

Как отмечалось ранее, фактическая работа, которую агент должен делать, как правило, осуществляется в рамках «поведения агента».
Поведение представляет собой задачу, которую агент может выполнять. Оно реализуется как объект класса, наследующего класс jade.core.behaviours.Behaviour. Для того чтобы агент мог исполнять задачу, описываемую объектом поведения, достаточно добавить поведение агента с помощью метода addBehaviour() класса Agent. Поведение может быть добавлено в любой момент: когда запускается агент (в методе setup()) или в рамках других поведений. Каждый класс, наследующий Behavour должен реализовывать метод action(), который фактически определяет операции, которые будут выполняться, когда агент следует данному поведению и done() метод (возвращает булево значение), который определяет, завершены ли все действия данного поведения и необходимо ли удалить его из поведенческого набора, который имеется у агента.

 

4.1 Согласование поведений

Агент может выполнять одновременно несколько моделей поведения. Однако важно заметить, что  расписание нескольких моделей поведения в агенте имеет не упреждающий характер, а кооперативный. Это означает, что когда поведение исполняется по расписанию, его метод action() вызывается и работает до тех пор, пока не завершится. Поэтому именно программист  определяет, когда агент переключается от исполнения данного поведения к выполнение следующего.
Хотя это требует небольшие дополнительных усилий от программистов, такой подход имеет несколько преимуществ:

Алгоритм работы потока агента показан на рисунке 4.1.


Рисунок 4.1 – Алгоритм работы агента

 

С учетом описанного механизма планирования важно подчеркнуть, что поведение, подобное представленному ниже, препятствует выполнению любого другого поведения, т.к его action() метод никогда не завершается.

   public class  OverbearingBehaviour extends Behaviour { 
   public void action() { 
   while (true) { 
   // do something 
   } 
   } 
   
   public boolean done() { 
   return true; 
   } 
   }
 

4.2 Одноразовый , циклический, общий типы поведения
Мы можем выделить три типа поведения.
«Одноразовое» поведение завершается сразу и его метод action() выполняется только один раз. jade.core.behaviours.OneShotBehaviour уже реализовывает метод done(), возвращая true и может быть удобным образом наследоваться, чтобы реализовывать данный тип поведения.

public class  MyOneShotBehaviour extends OneShotBehaviour { 
   public void action() { 
   // perform operation X 
   } 
 }
 

В данном  примере операция X выполнится единожды.

«Циклическое» поведение никогда не завершается и его метод action() выполняет одни и те же операции каждый раз, когда он вызывается. jade.core.behaviours.CyclicBehaviour уже реализовывает метод done()  всегда возвращая false и может наследоваться для реализации циклических моделей поведения.

   public class  MyCyclicBehaviour extends CyclicBehaviour { 
   public void action() { 
   // perform operation Y 
   } 
   }
 

Операция Y будет выполняться в цикле бесконечно (пока агент, имеющий это поведение, не будет завершён).

Общий случай поведения включает в себя статус, в зависимости от которого выполняются различные операции. Выполнение действий завершается, когда встречается данное условие.

   public  class MyThreeStepBehaviour extends Behaviour { 
   private int step = 0; 
   public void action() { 
   switch (step) { 
   case 0: 
   // perform operation X 
   step++; 
   break; 
   case 1: 
   // perform operation Y 
   step++; 
   break; 
   case 2: 
   // perform operation Z 
   step++; 
   break; 
   } 
   } 
   
   public boolean done() { 
   return step == 3; 
   } 
   }

Рисунок 4.5 –Общий случай поведения

Операции X, Y и Z выполняются друг за другом, после них поведение завершается.
Jade предоставляет возможность соединять простые формы поведения для создания более сложных. (SequentialBehaviour, ParallelBehaviour и FSMBehaviour).

4.3 Планирование операций в указанные моменты времени

Jade предоставляет два готовых класса (в пакете jade.core.behaviours), с помощью которых можно легко реализовать поведение, позволяющее выполнять определённые действия в заданные моменты времени.

 

public class  MyAgent extends Agent { 
   protected void setup() { 
   System.out.println(“Adding waker  behaviour”); 
   addBehaviour(new WakerBehaviour(this,  10000) { 
   protected void handleElapsedTimeout() { 
   // perform operation X 
   } 
   } ); 
   } 
   }

Рисунок 4.6 – WakerBehaviour

 

В данном случае операция X выполнится через 10 секунд после того, как была выведена надпись «Adding waker behaviour»

 

public class  MyAgent extends Agent { 
   protected void setup() { 
   addBehaviour(new TickerBehaviour(this,  10000) { 
   protected void onTick() { 
   // perform operation Y 
   } 
   } ); 
   } 
   }
   

Рисунок 4.7– TickerBehaviour

Операция Y выполняется с периодом в 10 секунд.

 

4.4 Поведения агентов

Описав базовые типы поведения, нужно проанализировать, какие поведения необходимы агентам Book-buyer и Book-seller в примере с покупкой и продажей книг.

4.4.1 Поведение агента-покупателя книг

Как было описано в ранее, агент-покупатель книг периодически опрашивает агентов-продавцов о книге, которую он должен купить. Мы можем легко достичь подобного поведения, используя TickerBehaviour, которое при каждом выполнении будет добавлять другое поведение, которое уже непосредственно будет выполнять запрос к агенту-продавцу. Ниже приведено, как изменится метод setup() агента класса BookBuyerAgent.

 

protected  void setup() { 
   // Printout a welcome message 
   System.out.println(“Hello! Buyer-agent  “+getAID().getName()+” is ready.”); 
   
   // Get the title of the book to buy as a  start-up argument 
   Object[] args = getArguments(); 
   if (args != null && args.length >  0) { 
   targetBookTitle = (String) args[0]; 
   System.out.println(“Trying to buy  “+targetBookTitle); 
   
   // Add a TickerBehaviour that schedules a  request to seller agents every minute      addBehaviour(new TickerBehaviour(this, 60000) { 
   protected void onTick() { 
   myAgent.addBehaviour(new  RequestPerformer()); 
   } 
   } ); 
   } 
   else { 
   // Make the agent terminate 
   System.out.println(“No target book title  specified“); 
   doDelete(); 
   } 
   }

Рисунок 4.8 – Метод setup() агента класса BookBuyerAgent

Необходимо обратить внимание на использование защищённого (protected)  поля myAgent — каждое поведение имеет указатель на агент, который вызывает его.

4.4.2 Поведение агента-продавца книг

Как было описано в разделе 2, агент-продавец книг ждёт запросов от покупателя и обслуживает его. Эти запросы могут быть запросами о наличии книги или заказом на поставку.  Подобное поведение можно достичь, если заставить агента-продавца выполнять два циклических поведения, одно, предназначенное для обслуживания запросов о наличии, другое — для обслуживания заказов на закупку. (Как именно входящие запросы определяются и обслуживаются — описано в главе 5.) Кроме того, нужно заставить агента-продавца книг выполнять одноразовые поведения для обновления каталога доступных для продажи книг, когда пользователь через графический интерфейс добавляет новую книгу. Ниже приведен код, как класс BookSellerAgent может быть реализован.

import  jade.core.Agent; 
   import jade.core.behaviours.*; import java.util.*; 
   public class BookSellerAgent extends Agent { 
   // The  catalogue of books for sale (maps the title of a book to its price) 
   private  Hashtable catalogue; 
   // The GUI by  means of which the user can add books in the catalogue 
   private  BookSellerGui myGui; 
   // Put agent  initializations here 
   protected void  setup() { 
   // Create  the catalogue 
   catalogue =  new Hashtable(); 
   // Create  and show the GUI  
   myGui = new  BookSellerGui(this); 
   myGui.show(); 
   // Add the  behaviour serving requests for offer from buyer agents 
   addBehaviour(new OfferRequestsServer()); 
   // Add the  behaviour serving purchase orders from buyer agents 
   addBehaviour(new PurchaseOrdersServer()); 
   } 
   protected void  takeDown() {     // Close the GUI 
   myGui.dispose(); 
   // Printout  a dismissal message 
   System.out.println(“Seller-agent “+getAID().getName()+” terminating.”);  } 
   /** 
   This is  invoked by the GUI when the user adds a new book for sale 
   */ 
   public void  updateCatalogue(final String title, final int price) { 
   addBehaviour(new OneShotBehaviour() { 
   public  void action() { 
   catalogue.put(title, new Integer(price)); 
   } } ); } } 


Рисунок 4.8 – Класс BookSellerAgent


5 ОБЩЕНИЕ АГЕНТОВ

Одной из наиболее важных возможностей, которые имеют JADE агенты — это возможность общаться. Избранная парадигма общения — асинхронная передача сообщений. Каждый агент имеет своего рода почтовый ящик (очередь сообщений агента), куда среда JADE записывает сообщения, посланные другими агентами. Всякий раз, когда сообщение будет размещаться в очереди сообщений, агент-владелец очереди будет уведомляться о поступлении нового сообщения.
То, когда агент заберет сообщение из очереди сообщений для обработки и заберет ли вообще, полностью определяется программистом.

 

Рисунок 5.1- Асинхронный обмен сообщениями в JADE

5.1 Язык ACL

Сообщения, которыми обмениваются JADE агенты имеют языковой формат  ACL определенный  FIPA(http://www.fipa.org) - международным стандартом взаимодействия агентов. Этот формат сообщения включает в себя несколько обязательных полей и, в частности:

С сообщением в JADE работают как с объектом  класса jade.lang.acl.ACLMessage, который имеет get и set методы обработки всех полей сообщения.

 

5.2 Отправка сообщений

Отправка сообщения другому агенту, состоит в заполнении ACLMessage объекта и вызове метода send() класса Agent.Код, приведенный ниже, информирует агента по имени Peter о том, что сегодня дождь —  «today it's raining».

ACLMessage msg  = new ACLMessage(ACLMessage.INFORM);
   msg.addReceiver(new  AID(“Peter”, AID.ISLOCALNAME));
   msg.setLanguage(“English”);  
   msg.setOntology(“Weather-forecast-ontology”);  
   msg.setContent(“Today  it’s raining”);  
   send(msg);

Рисунок 5.2 – Отправка сообщений

Решая наш пример торговли книгами, мы можем удобно использовать CFP (call for proposal) вид сообщений  для сообщений о том, что покупатель-агент направляет продавцу-агенту с просьбу выдать ему предложение на заданную книгу.
Сообщения PROPOSE могут быть использованы для сообщений, содержащих предложения агентов-продавцов, а тип ACCEPT_PROPOSAL - согласия участия в сделке, покупательские заказы. Наконец, тип REFUSE будет использован для сообщений, посланных агентами-продавцами, когда запрошенная книга не была найдена ими в своем каталоге. В обоих типах сообщений, посланных покупателями, содержанием сообщения является название книги. Содержание сообщений типа PROPOSE – цена книги. Как пример приведен код, в котором CFP сообщение создается и отправляется: 

 

// Message  carrying a request for offer 
   ACLMessage cfp  = new ACLMessage(ACLMessage.CFP); 
   for (int i = 0;  i < sellerAgents.lenght; ++i) { 
   cfp.addReceiver(sellerAgents[i]); 
   } 
   cfp.setContent(targetBookTitle); 
   myAgent.send(cfp);

Рисунок 5.3 – Отправка и создание сообщения

 

5.3 Получение сообщений
Как было упомянуто выше — среда JADE автоматически размещает сообщения в личной очереди сообщений получателя, как только они приходят. Агент может забрать сообщения из своей очереди сообщений с использованием метода receive(). Этот метод возвращает первое сообщение в очереди сообщений (удаляет его) или null, если очереди сообщений пуста.

ACLMessage msg  = receive(); 
   if (msg !=  null) { 
   // Process the message }

Рисунок 5.4 – Получение сообщений

 

5.4 Выбор сообщений из очереди сообщений

Учитывая, что оба поведения:  OfferRequestsServer и PurchaseOrdersServer являются циклическими, метод action() которых начинается с вызова метода myAgent.receive(), можно заметить проблему:  как мы можем быть уверены, что поведение OfferRequestsServer выбирает из очереди сообщений только сообщение в которых содержится запрос о наличии книги, а PurchaseOrdersServer — только сообщения, содержащие заказы на поставку? Для того, чтобы разрешить эту проблему, нужно изменить код, указав соответствующие «шаблоны», когда вызывается метод receive(). Когда шаблон указан, метод receive() возвращает первое сообщение (если оно есть), соответствующее этому шаблону, игнорирую все неподходящие сообщения. Подобные шаблоны реализованы как экземпляры класса jade.lang.acl.MessageTemplate, который предоставляет набор производящих методов, позволяющих просто и гибко создавать шаблоны.
Как было упомянуто в 5.2, использовалась перформация CFP для сообщений, в которых заключён вопрос о наличии книги, и перформация ACCEPT_PROPOSAL для сообщений передающих предложение о заказе (согласие с предложением). Поэтому необходимо изменить метод action() класса OfferRequestServer так, чтобы вызов myAgent.receive() игнорировал все сообщения, кроме тех, перформацией которых является CFP.

public void  action() { 
   MessageTemplate  mt = MessageTemplate.MatchPerformative(ACLMessage.CFP); 
   ACLMessage msg  = myAgent.receive(mt);
   if  (msg != null) {
   // Получено сообщение типа CFP. Обработка сообщения
   ... 
   }
   else  {
   block();
   }
   } 

Рисунок 5.5 – Метод action() класса OfferRequestServer

 

 

5.5 Сложные коммуникации
Поведение RequestPerformer, упомянутое ранее, представляет собой пример поведения, проводящего «сложную» «беседу». «Беседа», в данном случае — последовательность сообщений, которыми обмениваются два или более агента с чётко определёнными причинными и временными отношениями. Поведение RequestProposal должно послать CFP-сообщение нескольким агентам (известным агентам-продавцам), получить обратно все ответы и, в случае, если получен хотя бы один ответ типа PROPOSE получен, позже послать сообщение ACCEPT_PROPOSAL (агенту-продавцу, который сделал предложение) и получить обратно ответ. Всякий раз, когда происходит обмен сообщениями, хорошим тоном будет определить управляющие поля в сообщениях, участвующих в обмене. Это позволит легко и недвусмысленно создавать шаблоны, соответствующие возможным ответам.

private class  RequestPerformer extends Behaviour { 
   private  AID bestSeller; // Агент, сделавший лучшее предложение
   private  int bestPrice; // Цена лучшего предложения
   private int  repliesCnt = 0; // The counter of replies from seller agents 
   private  MessageTemplate mt; // Шаблон для получения правильных  ответов
   private int  step = 0; 
   public void  action() { 
   switch (step) { 
   case 0: 
   // Послать CFP  всем продавцам
   ACLMessage cfp  = new ACLMessage(ACLMessage.CFP); 
   for (int i = 0;  i < sellerAgents.length; ++i) { 
   cfp.addReceiver(sellerAgents[i]);
   }
   cfp.setContent(targetBookTitle); 
   cfp.setConversationId(“book-trade”);
   cfp.setReplyWith(“cfp”+System.currentTimeMillis());  // Unique value 
   myAgent.send(cfp);
   // Prepare the  template to get proposals
   mt =  MessageTemplate.and(MessageTemplate.MatchConversationId(“book-trade”),
   MessageTemplate.MatchInReplyTo(cfp.getReplyWith())); 
   step = 1;
   break; 
   case 1: 
   // Receive all  proposals/refusals from seller agents
   ACLMessage  reply = myAgent.receive(mt);
   if (reply !=  null) {
   // Reply  received
   if  (reply.getPerformative() == ACLMessage.PROPOSE) {
   // This is an  offer 
   int price =  Integer.parseInt(reply.getContent()); 
   if (bestSeller  == null || price < bestPrice) { 
   // This is the  best offer at present
   bestPrice =  price; 
   bestSeller =  reply.getSender(); 
   }
   }
   repliesCnt++; 
   if (repliesCnt  >= sellerAgents.length) { 
   // We received  all replies 
   step = 2;
   }
   }
   else { 
   block(); 
   }
   break; 
   case 2: 
   // Send the  purchase order to the seller that provided the best offer
   ACLMessage  order = new ACLMessage(ACLMessage.ACCEPT_PROPOSAL);
   order.addReceiver(bestSeller); 
   order.setContent(targetBookTitle); 
   order.setConversationId(“book-trade”);
   order.setReplyWith(“order”+System.currentTimeMillis()); 
   myAgent.send(order);
   // Prepare the  template to get the purchase order reply 
   mt = MessageTemplate.and(MessageTemplate.MatchConversationId(“book-trade”),
   MessageTemplate.MatchInReplyTo(order.getReplyWith())); 
   step = 3;
   break; 
   case 3: 
   // Receive the  purchase order reply 
   reply =  myAgent.receive(mt); 
   if (reply !=  null) {
   // Purchase order  reply received
   if  (reply.getPerformative() == ACLMessage.INFORM) { 
   // Purchase  successful. We can terminate
   System.out.println(targetBookTitle+“  successfully purchased.”);
   System.out.println(“Price  = ”+bestPrice); 
   myAgent.doDelete(); 
   }
   step = 4;
   }
   else {
   block(); 
   }break;}} 
   public boolean  done() { 
   return ((step  == 2 && bestSeller == null) || step == 4);}} 

 

Рисунок 5.6 – Класс RequestPerformer

Сложные коммуникации обычно осуществляются следующим хорошо определённым протоколом общения. JADE предоставляет основу для реализаций обмена сообщениями по указанным протоколам в пакете jade.protopackage. В частности, обмен сообщениями, который был реализован в примере, придерживается протокола «Contract-net» и может быть легко реализован с помощью класса jade.proto.ContractNetInitiator.

 

6 СЕРВИС «ЖЕЛТЫХ СТРАНИЦ»

В приведенном коде примера было сделано допущение, что существует некое фиксированное множество агентов-продавцов (seller1 и seller2) и каждый покупатель заранее знает о них. В этой главе описано как избавиться от этого допущения и использовать сервис «жёлтых страниц», предоставленный платформой JADE для того, чтобы позволить агентам-покупателям динамически узнавать об агентах-продавцах, доступных в данный момент времени.

6.1 Агент DF
Сервис «Жёлтых страниц» позволяет агентам опубликовать данные об одном или более сервисах, которые они предоставляют, так, что другие агенты могут находить и успешно использовать эти сервисы.

Рисунок 6.1 — Сервис «Жёлтых страниц»

Сервис «жёлтых страниц» в JADE (согласно спецификации FIPA) предоставлен агентом, названным DF (Directory Facilitator, Менеджер Директорий). Каждая соответствующая FIPA платформа поддерживает по умолчанию агент DF (его локальное имя — «df»). Другие DF-агенты могут быть запущены и несколько DF-агентов (включая те, что находятся в платформе по умолчанию) и объединены для обеспечения единого распределённого каталога «жёлтых страниц».

6.2 Взаимодействие с DF
Поскольку DF является агентом, имеется возможность взаимодействовать с ним привычным образом обмениваясь ACL-сообщениями, используя соответствующий язык содержания (язык SLo) и соответствующую онтологию (онтологию FIPA-agent-management), согласно спецификациям FIPA. Для упрощения этих взаимоотношений JADE предоставляет класс jade.domain.DFService с помощью которого можно опубликовывать и искать сервисы через вызовы методов.

6.3 Сервисы для публикаций
Агент, желающий опубликовать (сделать доступными публично) один или более сервисов должен предоставить DF описание, включающее, AID этого агента, список возможных языков и онтологий, которые должны знать другие агенты для взаимодействия с ним и список сервисов для публикации. Для каждого опубликовываемого сервиса предоставляется описание, включающее тип сервиса, его имя, языки и онтологии, необходимые для его использования и ещё некоторое количество свойств, специфичных для данного сервиса. Классы DFAgentDescription, ServiceDescription и Property, включённые в пакет jade.domain.FIPAAgentManagement, соответственно представляют собой три упомянутые абстракции.
Чтобы опубликовать сервис агент должен создать подходящее описание (представленное экземпляром класса DFAgentDescription) и вызвать статический метод register() класса DFService. Типично (но не обязательно) регистрация сервиса проходит в методе setup() как показано ниже в случае с агентом-продавцом книг.

protected void  setup() { 
   ... 
   // Register the  book-selling service in the yellow pages
   DFAgentDescription  dfd = new DFAgentDescription(); 
   dfd.setName(getAID()); 
   ServiceDescription  sd = new ServiceDescription(); 
   sd.setType(“book-selling”);
   sd.setName(“JADE-book-trading”);
   dfd.addServices(sd);
   try {
   DFService.register(this,  dfd); 
   }
   catch  (FIPAException fe) { 
   fe.printStackTrace(); 
   } 
   ... 
   }


Рисунок 6.2 — Методе setup()

 

 

ВЫВОДЫ

В рамках данного проекта была разработана агентно-ориентированная система в среде JADE. В процессе ее создания были разобраны основные шаги создания мультиагентных систем с данной среде, были разработаны агенты, их методы, рассмотрены средсва коммуникации.
Можно сделать вывод, что JADE является одной из перспективных сред для создания многоагентных систем, удобство которой не подлежит сомнению. Она упрощает разработку мультиагентных систем благодаря использованию FIPA-спецификаций и инструментов, которые поддерживают фазы отладки и развертывания системы.

 

Перечень ссылок

  1. Giovanni Caire (TILAB, formerly CSELT). Руководство JADE Copyright (C) 2003 TILab S.p.A.;
  2. Швецов А.Н. Агентно-ориентированные системы: от формальных моделей к промышленным приложениям / Всероссийский конкурсный отбор обзорно-аналитических статей по приоритетному направлению "Информационно-телекоммуникационные системы", 2008. - 101 с.

 

 

Приложение А Листинг программы

//BookBuyerAgent.java**************************************
 package  examples.bookTrading;
import jade.core.Agent;
   import jade.core.AID;
   import  jade.core.behaviours.*;
   import  jade.lang.acl.ACLMessage;
   import  jade.lang.acl.MessageTemplate;
   import  jade.domain.DFService;
   import  jade.domain.FIPAException;
   import  jade.domain.FIPAAgentManagement.DFAgentDescription;
   import  jade.domain.FIPAAgentManagement.ServiceDescription;
public class BookBuyerAgent  extends Agent {
   // The title of the book to buy
   private String targetBookTitle;
   // The list of known seller agents
   private AID[] sellerAgents;
   
   // Put agent initializations here
   protected void setup() {
   // Printout a welcome  message
   System.out.println("Hallo! Buyer-agent  "+getAID().getName()+" is ready.");
   
   // Get the title of  the book to buy as a start-up argument
   Object[] args =  getArguments();
   if (args != null  && args.length > 0) {
   targetBookTitle =  (String) args[0];
   System.out.println("Target book is "+targetBookTitle);
   
   // Add a  TickerBehaviour that schedules a request to seller agents every minute
   addBehaviour(new  TickerBehaviour(this, 60000) {
   protected void  onTick() {
   System.out.println("Trying to buy "+targetBookTitle);
   // Update the list of seller agents
   DFAgentDescription template = new  DFAgentDescription();
   ServiceDescription sd = new  ServiceDescription();
   sd.setType("book-selling");
   template.addServices(sd);
   try {
   DFAgentDescription[]  result = DFService.search(myAgent, template); 
   System.out.println("Found  the following seller agents:");
   sellerAgents = new  AID[result.length];
   for (int i = 0; i < result.length;  ++i) {
   sellerAgents[i] =  result[i].getName();
   System.out.println(sellerAgents[i].getName());
   }
   }
   catch (FIPAException fe) {
   fe.printStackTrace();
   }
   
   // Perform the request
   myAgent.addBehaviour(new RequestPerformer());
   }
   } );
   }
   else {
   // Make the agent  terminate
   System.out.println("No target book title specified");
   doDelete();
   }
   }
  // Put agent clean-up operations here
   protected void takeDown() {
   // Printout a dismissal message
   System.out.println("Buyer-agent  "+getAID().getName()+" terminating.");
   }
   
   /**
   Inner class  RequestPerformer.
   This is the behaviour  used by Book-buyer agents to request seller 
   agents the target  book.
   */
   private class RequestPerformer extends Behaviour {
   private AID  bestSeller; // The agent who provides the best offer 
   private int  bestPrice;  // The best offered price
   private int repliesCnt  = 0; // The counter of replies from seller agents
   private  MessageTemplate mt; // The template to receive replies
   private int step = 0;
   
   public void action() {
   switch (step) {
   case 0:
   // Send the cfp to all sellers
   ACLMessage cfp =  new ACLMessage(ACLMessage.CFP);
   for (int i = 0; i  < sellerAgents.length; ++i) {
   cfp.addReceiver(sellerAgents[i]);
   } 
   cfp.setContent(targetBookTitle);
   cfp.setConversationId("book-trade");
   cfp.setReplyWith("cfp"+System.currentTimeMillis()); // Unique  value
   myAgent.send(cfp);
   // Prepare the  template to get proposals
   mt =  MessageTemplate.and(MessageTemplate.MatchConversationId("book-trade"),
   MessageTemplate.MatchInReplyTo(cfp.getReplyWith()));
   step = 1;
   break;
   case 1:
   // Receive all  proposals/refusals from seller agents
   ACLMessage reply =  myAgent.receive(mt);
   if (reply != null)  {
   // Reply  received
   if  (reply.getPerformative() == ACLMessage.PROPOSE) {
   // This is an  offer 
   int price =  Integer.parseInt(reply.getContent());
   if (bestSeller  == null || price < bestPrice) {
   // This is  the best offer at present
   bestPrice =  price;
   bestSeller =  reply.getSender();
   }
   }
   repliesCnt++;
   if (repliesCnt  >= sellerAgents.length) {
   // We received  all replies
   step = 2; 
   }
   }
   else {
   block();
   }
   break;
   case 2:
   // Send the  purchase order to the seller that provided the best offer
   ACLMessage order =  new ACLMessage(ACLMessage.ACCEPT_PROPOSAL);
   order.addReceiver(bestSeller);
   order.setContent(targetBookTitle);
   order.setConversationId("book-trade");
   order.setReplyWith("order"+System.currentTimeMillis());
   myAgent.send(order);
   // Prepare the  template to get the purchase order reply
   mt =  MessageTemplate.and(MessageTemplate.MatchConversationId("book-trade"),
   MessageTemplate.MatchInReplyTo(order.getReplyWith()));
   step = 3;
   break;
   case 3:      
   // Receive the  purchase order reply
   reply =  myAgent.receive(mt);
   if (reply != null)  {
   // Purchase  order reply received
   if  (reply.getPerformative() == ACLMessage.INFORM) {
   // Purchase  successful. We can terminate
   System.out.println(targetBookTitle+" successfully purchased from  agent "+reply.getSender().getName());
   System.out.println("Price = "+bestPrice);
   myAgent.doDelete();
   }
   else {
   System.out.println("Attempt failed: requested book already  sold.");
   }
   
   step = 4;
   }
   else {
   block();
   }
   break;
   }        
   }
   
   public boolean done()  {
   if (step == 2 && bestSeller ==  null) {
   System.out.println("Attempt  failed: "+targetBookTitle+" not available for sale");
   }
   return ((step == 2  && bestSeller == null) || step == 4);
   }
   }  // End of inner class  RequestPerformer
   }
 
BookSellerAgent.java
   /*****************************************************************/
package  examples.bookTrading;
import jade.core.Agent;
   import  jade.core.behaviours.*;
   import  jade.lang.acl.ACLMessage;
   import  jade.lang.acl.MessageTemplate;
   import jade.domain.DFService;
   import  jade.domain.FIPAException;
   import  jade.domain.FIPAAgentManagement.DFAgentDescription;
   import  jade.domain.FIPAAgentManagement.ServiceDescription;
import java.util.*;
public class BookSellerAgent  extends Agent {
   // The catalogue of books for sale (maps the  title of a book to its price)
   private Hashtable catalogue;
   // The GUI by means of which the user can add  books in the catalogue
   private BookSellerGui myGui;
  // Put agent initializations here
   protected void setup() {
   // Create the catalogue
   catalogue = new Hashtable();
    // Create and show the GUI 
   myGui = new BookSellerGui(this);
   myGui.show();
    // Register the book-selling service in the  yellow pages
   DFAgentDescription dfd = new DFAgentDescription();
   dfd.setName(getAID());
   ServiceDescription sd = new  ServiceDescription();
   sd.setType("book-selling");
   sd.setName("JADE-book-trading");
   dfd.addServices(sd);
   try {
   DFService.register(this, dfd);
   }
   catch (FIPAException fe) {
   fe.printStackTrace();
   }
   
   // Add the behaviour serving queries from  buyer agents
   addBehaviour(new OfferRequestsServer());
    // Add the behaviour serving purchase  orders from buyer agents
   addBehaviour(new PurchaseOrdersServer());
   }
  // Put agent clean-up operations here
   protected void takeDown() {
   // Deregister from the yellow pages
   try {
   DFService.deregister(this);
   }
   catch (FIPAException fe) {
   fe.printStackTrace();
   }
   //  Close the GUI
   myGui.dispose();
   // Printout a dismissal message
   System.out.println("Seller-agent  "+getAID().getName()+" terminating.");
   }
  /**
   This is invoked by the GUI when the user  adds a new book for sale
   */
   public void updateCatalogue(final String  title, final int price) {
   addBehaviour(new OneShotBehaviour() {
   public void action() {
   catalogue.put(title, new  Integer(price));
   System.out.println(title+"  inserted into catalogue. Price = "+price);
   }
   } );
   }
   
   /**
   Inner class  OfferRequestsServer.
   This is the behaviour  used by Book-seller agents to serve incoming requests 
   for offer from buyer  agents.
   If the requested book  is in the local catalogue the seller agent replies 
   with a PROPOSE  message specifying the price. Otherwise a REFUSE message is
   sent back.
   */
   private class OfferRequestsServer extends CyclicBehaviour {
   public void action() {
   MessageTemplate  mt = MessageTemplate.MatchPerformative(ACLMessage.CFP);
   ACLMessage msg  = myAgent.receive(mt);
   if (msg != null) {
   // CFP Message  received. Process it
   String title =  msg.getContent();
   ACLMessage reply =  msg.createReply();
   
   Integer price =  (Integer) catalogue.get(title);
   if (price != null)  {
   // The requested  book is available for sale. Reply with the price
   reply.setPerformative(ACLMessage.PROPOSE);
   reply.setContent(String.valueOf(price.intValue()));
   }
   else {
   // The requested  book is NOT available for sale.
   reply.setPerformative(ACLMessage.REFUSE);
   reply.setContent("not-available");
   }
   myAgent.send(reply);
   }
   else {
   block();
   }
   }
   }  // End of inner class  OfferRequestsServer
   
   /**
   Inner class  PurchaseOrdersServer.
   This is the behaviour  used by Book-seller agents to serve incoming 
   offer acceptances  (i.e. purchase orders) from buyer agents.
   The seller agent  removes the purchased book from its catalogue 
   and replies with an  INFORM message to notify the buyer that the
   purchase has been  sucesfully completed.
   */
   private class PurchaseOrdersServer extends CyclicBehaviour {
   public void action() {
   MessageTemplate  mt = MessageTemplate.MatchPerformative(ACLMessage.ACCEPT_PROPOSAL);
   ACLMessage msg  = myAgent.receive(mt);
   if (msg != null) {
   // ACCEPT_PROPOSAL  Message received. Process it
   String title = msg.getContent();
   ACLMessage reply =  msg.createReply();
   
   Integer price =  (Integer) catalogue.remove(title);
   if (price != null)  {
   reply.setPerformative(ACLMessage.INFORM);
   System.out.println(title+" sold to agent  "+msg.getSender().getName());
   }
   else {
   // The requested  book has been sold to another buyer in the meanwhile .
   reply.setPerformative(ACLMessage.FAILURE);
   reply.setContent("not-available");
   }
   myAgent.send(reply);
   }
   else {
   block();
   }
   }
   }  // End of inner class  OfferRequestsServer
   }
 
//BookSEllerGui.java***********************************************************
   package  examples.bookTrading;
import jade.core.AID;
import java.awt.*;
   import java.awt.event.*;
   import javax.swing.*;
/**
   @author Giovanni Caire - TILAB
   */
   class BookSellerGui extends  JFrame {    
   private BookSellerAgent myAgent;
   
   private JTextField titleField, priceField;
   
   BookSellerGui(BookSellerAgent a) {
   super(a.getLocalName());
   
   myAgent = a;
   
   JPanel p = new JPanel();
   p.setLayout(new GridLayout(2, 2));
   p.add(new JLabel("Book title:"));
   titleField = new JTextField(15);
   p.add(titleField);
   p.add(new JLabel("Price:"));
   priceField = new JTextField(15);
   p.add(priceField);
   getContentPane().add(p, BorderLayout.CENTER);
   
   JButton addButton = new JButton("Add");
   addButton.addActionListener( new ActionListener() {
   public void actionPerformed(ActionEvent ev) {
   try {
   String title =  titleField.getText().trim();
   String price =  priceField.getText().trim();
   myAgent.updateCatalogue(title,  Integer.parseInt(price));
   titleField.setText("");
   priceField.setText("");
   }
   catch (Exception e) {
   JOptionPane.showMessageDialog(BookSellerGui.this,  "Invalid values. "+e.getMessage(), "Error",  JOptionPane.ERROR_MESSAGE); 
   }
   }
   } );
   p = new JPanel();
   p.add(addButton);
   getContentPane().add(p, BorderLayout.SOUTH);
   
   // Make the agent terminate when the user closes 
   // the GUI using the button on the upper right corner 
   addWindowListener(new      WindowAdapter()  {
   public void windowClosing(WindowEvent e) {
   myAgent.doDelete();
   }
   } );
   
   setResizable(false);
   }
   
   public void show() {
   pack();
   Dimension screenSize =  Toolkit.getDefaultToolkit().getScreenSize();
   int centerX = (int)screenSize.getWidth() / 2;
   int centerY = (int)screenSize.getHeight() / 2;
   setLocation(centerX - getWidth() / 2, centerY -  getHeight() / 2);
   super.show();
   }      
   }