Realizzare Sistemi MultiAgente con JADE - Prima Parte Corso di Reti di Calcolatori A.A. 2004-2005 Ing. Domenico Rosaci JADE Middleware che facilita la creazione di sistemi multi-agente (MAS) Ambiente run-time dove gli agenti possono “vivere”. Deve assere attivo su un host se si vuole che degli agenti vengano eseguiti su quell’host Libreria di classi JAVA Tool grafici per l’amministrazione del MAS Piattaforme Container: istanza di JADE Runtime Environment: al suo interno vengono esguiti degli agenti Piattaforma: insieme di container attivi In una piattaforma deve esistere un Main Container, presso cui tutti gli altri container devono registrarsi Ogni agente in una piattaforma ha un nome unico Esempio di Piattaforme Esempio: Agenti che comprano e vendono Ogni agente compratore accetta il nome di un libro come input Periodicamente, chiede a tutti gli agenti venditori che conosce di fare un’offerta di vendita Tra tutte le offerte pervenute, sceglie quella a più basso prezzo ed emette un ordine di acquisto Creiamo l’agente compratore import jade.core.Agent; public class AgenteCompratore extends Agent { protected void setup() { // Stampa un messaggio di benvenuto System.out.println(“Ciao! L’agente compratore “+getAID().getName()+” e’ pronto.”); } } Il metodo setup() Serve a specificare quali azioni l’agente compie in fase iniziale Il vero comportamento dell’agente si otterrà inserendo nell’agente un “behaviour” Agent Identifier (AID) Ogni agente è identificato da un AID che è un’istanza della classe jade.core.AID Il metodo getAID() restituisce l’AID dell’agente che lo invoca L’AID si compone di un nome locale alla piattaforma (nickname) e dell’indirizzo della piattaforma Per ottenere l’AID a partire dal nickname: String nickname = “Peter”; AID id = new AID(nickname, AID.ISLOCALNAME); ISLOCALNAME indica che nickname è un nome locale Compilare un agente Includere nella variabile d’ambiente CLASSPATH i percorsi: c:\jade\jade-bin3.2\jade\lib\jade.jar;c:\jade\jade-bin3.2\jade\lib\jadeTools.jar;c:\jade\jade-bin3.2\jade\ib\iiop.jar;c:\jade\jade-bin3.2\jade\lib\base64.jar;c:\jade; Quindi usare: javac AgenteCompratore.java Creiamo una piattaforma Creiamo un agente compratore Argomenti, doDelete(), takeDown() import jade.core.Agent; import jade.core.AID; public class AgenteCompratore extends Agent { // Titolo del libro da acquistare private String targetBookTitle; protected void setup() { System.out.println(“Ciao! L’agente compratore “+getAID().getName()+” e’ pronto.”); Object[] args = getArguments(); if (args != null && args.length > 0) { targetBookTitle = (String) args[0]; System.out.println(“Sto cercando di acquistare “+targetBookTitle); } else { // Fa terminare immediatamente l’agente System.out.println(“Nessun libro specificato“); doDelete();} } // Operazioni di chiusura protected void takeDown() { System.out.println(“agente compratore “+getAID().getName()+” terminato.”);} } Agente Compratore con argomenti Il behaviour di un agente Specifica i task che l’agente esegue E’ implementato come un oggetto di una classe che estende jade.core.behaviours.Behaviour Per aggiungere tale oggetto si usa il metodo addBehaviour() della classe Agent. Lo si può fare in qualunque momento, nel metodo start() della classe Agent o all’interno di un qualunque altro behaviour Ogni classe che estende Behaviour deve implementare il metodo action() che specifica il task da eseguire e il metodo done() che specifica se il task è stato eseguito Ancora sui behaviours Un agente può eseguire più behaviour concorrentemente Lo scheduling è collaborativo, non pre-emptive: quando un behaviour è eseguito, esso non viene interrotto finchè non termina E’ il programmatore che decide quando saltare da un behaviour a un altro Sforzo aggiuntivo per il programmatore, ma vari vantaggi: Un singolo thread per agente (importante quando si usano device con limitate risorse, es. cellulari) Migliori performance rispetto agli switch dei thread java Nessun problema di sincronizzazione: tutti i behaviour sono eseguiti nello stesso thread Quando si verifica uno switch, lo stato di un agente non contiene informazioni sullo stack e può quindi essere salvato per una successiva “riesumazione” (persistenza) o trasferito in un altro container (mobilità) Thread di un agente Inizializzazione Inserimento behaviour iniziali L’agente è “ucciso”? (doDelete() eseguito?) In rosso i metodi che il programmatore deve implementare Ottieni il prossimo behaviour da pool dei behaviour attivi Vita dell’agente: Esecuzione di behaviour Rimuovi il behaviour corrente dal pool dei behaviour attivi Operazioni di clean-up Importante: Nessun altro behaviour può essere eseguito finchè il metodo action() del behaviour corrente non si completa Quando non ci sono behaviour disponibili per l’esecuzione, l’agente si “addormenta” per non consumare tempo di CPU, rsivegliandosi quando si verifica la disponibilità di un behaviour Behaviour one-shot public class MyOneShotBehaviour extends OneShotBehaviour { public void action() { // esegue l’operazione X } } Il metodo action(), e quindi anche l’operazione X, sono eseguiti una sola volta. Behaviour cyclic public class MyCyclicBehaviour extends CyclicBehaviour { public void action() { // perform operation Y } } Il metodo action() è eseguito indefinitamente, fin quando l’agente che ha lanciato il behaviour termina Behaviour generic 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; } } A seconda dello stato in cui l’agente si trova, si esegue un’operazione. Il behaviour viene ripetuto fino a quando si raggiunge una certa condizione (nell’es., step==3) Schedulare le operazioni: la classe WakerBehaviour action() and done() sono implementati in modo tale da eseguire il metodo astratto handleElapsedTimeout() dopo che un dato timeout (specificato nel costruttore) viene raggiunto. Dopo l’esecuzione di handleElapsedTimeout() il behaviour si completa. public class MyAgent extends Agent { protected void setup() { System.out.println(“Aggiunto un waker behaviour”); addBehaviour(new WakerBehaviour(this, 10000) { protected void handleElapsedTimeout() { // perform operation X } } ); } } L’operazione X è eseguita 10 secondi dopo che appare il messaggio “Aggiunto un waker behaviour” . Schedulare le operazioni: la classe TickerBehaviour • action() and done() sono implementati in modo tale da eseguire il metodo astratto onTick() ripetitivamente aspettando un certo periodo (specificato nel costruttore) dopo ogni esecuzione. Un TickerBehaviour non si completa mai public class MyAgent extends Agent { protected void setup() { addBehaviour(new TickerBehaviour(this, 10000) { protected void onTick() { // perform operation Y } } ); } } L’operazione Y è eseguita periodicamente ogni 10 secondi. Il behaviour dell’AgenteCompratore protected void setup() { System.out.println(“Ciao! L’agente compratore “+getAID().getName()+” è pronto.”); Object[] args = getArguments(); if (args != null && args.length > 0) { targetBookTitle = (String) args[0]; System.out.println(“sto cercando di acquistare “+targetBookTitle); // Aggiunge un TickerBehaviour che schedula una richiesta all’agente venditore ogni minuto addBehaviour(new TickerBehaviour(this, 60000) { protected void onTick() { myAgent.addBehaviour(new RequestPerformer()); } } ); } else { // Fa terminare l’agente System.out.println(“Nessun titolo di libro specificato“); doDelete(); } } RequestPerformer() è una richiesta di comunicazione: ne parleremo più avanti myAgent (variabile protected della classe Agent) è un puntatore all’agente che sta eseguendo il behaviour Il behaviour dell’Agente Venditore import jade.core.Agent; jade.core.behaviours.*; import java.util.*; public class BookSellerAgent extends Agent { // Catalogo dei libri (titolo e prezzo) private Hashtable catalogue; // La GUI per mezzo della quale l’utente può aggiungere libri al catalogo private BookSellerGui myGui; protected void setup() { // Crea il catalogo catalogue = new Hashtable(); // Crea e mostra la GUI myGui = new BookSellerGui(this); myGui.show(); // Aggiunge il behaviour serving requests per le offerte dagli agenti compratori addBehaviour(new OfferRequestsServer()); // Aggiunge il behaviour serving purchase orders per gli ordini dagli agenti compratori addBehaviour(new PurchaseOrdersServer()); } protected void takeDown() { // Chiude la GUI myGui.dispose(); System.out.println(“Agente Venditore “+getAID().getName()+” terminato.”); } /** Questo è invocato dalla GUI quando l’utente aggiunge un nuovo libro per la vendita */ public void updateCatalogue(final String title, final int price) { addBehaviour(new OneShotBehaviour() { public void action() { catalogue.put(title, new Integer(price)); } } ); } } La classe BookSellerGui è una semplice GUI Swing, disponibile fra gli esempi di JADE Comunicazione tra agenti Paradigma di comunicazione: asyncronous message passing Ogni agente ha una message queue in cui JADE inserisce i messaggi inviati dagli altri agenti L’agente viene avvertito quando riceve un nuovo messaggio Il linguaggio ACL Specifiche definite da FIPA: Sender: mittente del messaggio Lista dei destinatari Performative: indica ciò che il messaggio vuole ottenere dal destinatario. Può essere: REQUEST INFORM QUERY_IF CFP (Call for Proposals) PROPOSE, ACCEPT_PROPOSAL, REFUSE (performative di negoziazione) Il Linguaggio ACL Content (Contenuto del messaggio) Content Language: Sintassi usata per esprimere il content Ontology: vocabolario dei simboli usati nel content e loro significato Altri campi per il controllo delle conversazioni concorrenti Messaggi JADE Un messaggio JADE è un oggetto della classe jade.lang.acl.ACLMessage 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); Esempio del commercio libri Si può usare la performativa CFP per le richieste di ricevere un’offerta che l’agente compratore fa all’agente venditore Si possono usare la performativa PROPOSE per le offerte del venditore e le performative ACCEPT_PROPOSAL e REFUSE per le risposte del compratore ad un’offerta Messaggio di richiesta di offerta ACLMessage cfp = new ACLMessage(ACLMessage.CFP); for (int i = 0; i < sellerAgents.lenght; ++i) { cfp.addReceiver(sellerAgents[i]); cfp.setContent(targetBookTitle); myAgent.send(cfp); Essendo sellerAgents un vettore di agenti venditori Il metodo receive() ACLMessage msg = receive(); if (msg != null) { } Estrae il primo messaggio dalla coda dei messaggi Classe interna OfferRequestsServer private class OfferRequestsServer extends CyclicBehaviour { public void action() { ACLMessage msg = myAgent.receive(); if (msg != null) { String title = msg.getContent(); ACLMessage reply = msg.createReply(); Integer price = (Integer) catalogue.get(title); if (price != null) { // Il libro richiesto è disponibile per la vendita. Rispondi con il prezzo reply.setPerformative(ACLMessage.PROPOSE); reply.setContent(String.valueOf(price.intValue())); } else { // Il libro richiesto non è disponibile per la vendita. reply.setPerformative(ACLMessage.REFUSE); reply.setContent(“not-available”); } myAgent.send(reply); } } } Bloccare il behaviour OfferRequestServer è implementata come classe interna della classe AgenteVenditore Quando noi inseriamo questo behaviour nell’agente, il thread dell’agente inizia un oneroso loop. Per evitarlo, usiamo dentro il metodo action() il metodo block() che blocca l’agente fino a quando non è ricevuto un messaggio Bloccare il behaviour public void action() { ACLMessage msg = myAgent.receive(); if (msg != null) { // Messaggio ricevuto. Processalo ... } else { block(); } } Quando un nuovo messaggio è inserito nella coda dell’agente, tutti i behaviour bloccati diventano disponibili. Selezionare messaggi con caratteristiche specifiche public void action() { MessageTemplate mt = MessageTemplate.MatchPerformative(ACLMessage.CFP); ACLMessage msg = myAgent.receive(mt); if (msg != null) { // CFP Message received. Process it ... } else { block(); } } Questo agente processa solo messaggi che aderiscono al template mt, che “richiede” la performativa CFP La classe RequestPerformer -1 private class RequestPerformer extends Behaviour { private AID bestSeller; // Agente che fornisce l’offerta migliore private int bestPrice; // Miglior prezzo offerto private int repliesCnt = 0; // Contatore delle risposte da agenti venditori private MessageTemplate mt; // Template per ricevere risposte private int step = 0; public void action() { switch (step) { case 0: // Manda una Call for Proposals a tutti gli agenti 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()); // valore unico myAgent.send(cfp); La classe RequestPerformer - 2 // Prepara il template per ricevere le proposte Mt = MessageTemplate.and(MessageTemplate.MatchConversationId(“book-trade”), MessageTemplate.MatchInReplyTo(cfp.getReplyWith())); step = 1; break; case 1: // riceve tutte le proposte/rifiuti dagli agenti venditori ACLMessage reply = myAgent.receive(mt); if (reply != null) { // Replica alle proposte ricevute if (reply.getPerformative() == ACLMessage.PROPOSE) { // Questa è un’offerta int price = Integer.parseInt(reply.getContent()); if (bestSeller == null || price < bestPrice) { // Questo è al momento il miglior offerente bestPrice = price; bestSeller = reply.getSender(); } } repliesCnt++; La classe RequestPerformer -3 if (repliesCnt >= sellerAgents.length) { // ricevute tutte le offerte step = 2; } } else { block(); } break; case 2: // manda ordine di acquisto al seller che ha fornito l’offerta migliore 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); // Prepara il template per l’ordine di offerta mt = MessageTemplate.and(MessageTemplate.MatchConversationId(“book-trade”), MessageTemplate.MatchInReplyTo(order.getReplyWith())); step = 3; break; La classe RequestPerformer -4 case 3: // Riceve la risposta all’ordine di acquisto reply = myAgent.receive(mt); if (reply != null) { if (reply.getPerformative() == ACLMessage.INFORM) { // risposta positiva. Possiamo terminare System.out.println(targetBookTitle+“ acquisto concluso.”); System.out.println(“Prezzo = ”+bestPrice); myAgent.doDelete(); } step = 4; } else { block(); } break; } } public boolean done() { return ((step == 2 && bestSeller == null) || step == 4); } } // Fine della classe interna RequestPerformer Protocolli di negoziazione Il protocollo da noi implementato è un protocollo ben noto il jade; si chiama “Contract-net”: avrebbe potuto essere molto più facilmente implementato tramite la classe jade.proto.ContractNetInitiator Il package proto contiene diversi protocolli di interazione Pagine Gialle: la classe DFService Come fare in modo che un agente compratore conosca quali siano gli agenti venditori? Il servizio di pagine gialle consente a un agente di rendere noti i servizi che eroga Il servizio di pagine gialle in JADE è fornito dagli agenti DF (Directory Facilitator) Gli agenti DF Ogni piattaforma conforme a FIPA contiene un agente DF, chiamato df Altri agenti DF possono essere attivati per formare con l’agente df di default un sistema di pagine gialle distribuito Interazione con gli agenti DF Si realizza tramite messaggi ACL Content language: SL0 Ontology Language: FIPA-agentmanagement-ontology Package: jade.domain.DFService Servizio di Pubblicazione Ogni agente interessato al servizio deve fornire al DF: Il suo AID La lista dei linguaggi e delle ontologie che gli altri agenti devono conoscere per interagire con lui La lista dei servizi offerti Servizi offerti da un agente Per ogni servizio offerto occorre specificare: Tipo Nome Linguaggi e ontologie richieste Proprietà del servizio Classi coinvolte: DFAgentDescription, ServiceDescription, Property incluse nel package jade.domain.FIPAAgentManagement Pubblicare un servizio Creare un’istanza della classe DFAgentDescription Chiamare il metodo register() della classe DFService protected void setup() { ... // Registra il servizio di book-selling nelle pagine gialle 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(); } ... } Deregistrazione dei servizi protected void takeDown() { // Deregistra dalle pagine gialle try { DFService.deregister(this); } catch (FIPAException fe) { fe.printStackTrace(); } // Close the GUI myGui.dispose(); System.out.println(“Seller-agent “+getAID().getName()+” terminating.”); } Ricerca di servizi Per cercare un servizio si deve fornire al DF un template descrittivo Il risultato della ricerca è una lista di descrizioni che soddisfano il template fornito Si usa il metodo search() della classe DFService L’agente compratore public class BookBuyerAgent extends Agent { private AID[] sellerAgents; protected void setup() { System.out.println(“Hallo! Buyer-agent “+getAID().getName()+” is ready.”); Object[] args = getArguments(); if (args != null && args.length > 0) { targetBookTitle = (String) args[0]; System.out.println(“Trying to buy “+targetBookTitle); addBehaviour(new TickerBehaviour(this, 60000) { protected void onTick() { // aggiorna la lista degli agenti venditori DFAgentDescription template = new DFAgentDescription(); ServiceDescription sd = new ServiceDescription(); sd.setType(“book-selling”); template.addServices(sd); try { DFAgentDescription[] result = DFService.search(myAgent, template); sellerAgents = new AID[result.length]; for (int i = 0; i < result.length; ++i) { sellerAgents[i] = result.getName(); } } catch (FIPAException fe) { fe.printStackTrace(); } myAgent.addBehaviour(new RequestPerformer()); } } );