Оригинал 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-спецификаций и инструментов, которые поддерживают фазы отладки и развертывания системы.
Перечень ссылок
Приложение А Листинг программы
//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(); } }