490 likes | 597 Views
Realizzare Sistemi Multi-Agente 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)
E N D
Realizzare Sistemi Multi-Agente 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: 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-bin-3.2\jade\lib\jade.jar;c:\jade\jade-bin-3.2\jade\lib\jadeTools.jar;c:\jade\jade-bin-3.2\jade\ib\iiop.jar;c:\jade\jade-bin-3.2\jade\lib\base64.jar;c:\jade; • Quindi usare: javac AgenteCompratore.java
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.”);} • }
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à)
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 Thread di un agente
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 • 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 • 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()); • }
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-agent-management-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()); • } • } );