Bộ sưu tập

Lập trình agent với JADE cho người mới bắt đầu (phần 3)


https://codersontrang.com/2013/01/10/lap-trinh-agent-voi-jade-cho-nguoi-moi-bat-dau-phan-3/

Sự giao tiếp giữa các agent – lớp ACLMessage

Một trong những đặc điểm quan trọng của JADE agent là chúng có khả năng để giao tiếp với nhau. Mô hình giao tiếp ở đây là mô hình asynchronous messgage passing (tạm dịch là truyền lời nhắn không đồng bộ). Mỗi agent sẽ có một hòm thư (hàng đợi để lưu các lời nhắn của agent), các lời nhắn từ các agent khác đến sẽ được lưu ở đó. Bất cứ khi nào có một lời nhắn đến thì agent nhận sẽ được thông báo. Khi một agent lấy một lời nhắn ra để xử lý, việc xử lý lời nhắn đó như thế nào sẽ hoàn toàn phụ thuộc vào agent đã được lập trình như thế nào.

Hình dưới đây mô tả về mô hình “asynchronous message passing”

jtable_beginner_p3_1

Ngôn ngữ ACL

Các lời nhắn trao đổi giữa các agent với nhau có định dạng được viết bằng ngôn ngữ ACL. Ngôn ngữ này được định nghĩa bởi FIPA (http://www.fipa.org). Định dạng này bao gồm một số trường và cụ thể là:

  • Agent gửi lời nhắn
  • Một danh sách các agent nhận
  • Mục tiêu giao tiếp cái chỉ ra rằng agent gửi muốn làm gì khi mà nó gửi lời nhắn đến agent nhận. Nó có thể là một REQUEST (yêu cầu) trong trường hợp agent gửi muốn agent nhận thực hiện một hành động nào đó, là một INFORM (thông báo) trong trường hợp agent gửi muốn agent nhận nhận biết một điều gì đó, là một QUERY_IF trong trường hợp agent gửi muốn biết xem một điều kiện là đúng hay không, là CFP (call for proposal), PROPOSE, ACCEP_PROPOSAL, REJECT_PROPOSAL, nếu như agent gửi và agent nhận đang kết hợp với nhau trong một thỏa thuận nào đó…
  • Phần nội dung: nội dung của lời nhắn
  • Ngôn ngữ cho nội dung lời nhắn: là cú pháp được sử dụng để biểu diễn nội dung (cả agent nhận lẫn agent gửi đều phải có khả năng mã hóa và giải mã các biểu diễn theo cú pháp này để việc giao tiếp trở nên hiệu quả.)
  • Phần ontology: một tập hợp các từ vựng được sử dụng trong phần nội dung và nghĩa của chúng (cả agent nhận lẫn agent gửi phải gắn các nghĩa giống nhau vào các từ trong nội dung để việc giao tiếp trở nên có hiệu quả).
  • Một vài trường khác như là conversation-id, reply-with, in-reply-to, reply-by được sử dụng để điều khiển các cuộc hội thoại diễn ra đồng thời và để chỉ định thời gian timeout cho việc nhận một câu trả lời.

Một lời nhắc trong JADE được cài đặt như một đối tượng của lớp jade.lang.acl.ACLMessage. Lớp này cung cấp các phương thức get()set() để xử lý các trường trong một lời nhắn.

Gửi lời nhắn

Gửi một lời nhắn đến một agent khác chỉ đơn giản là điền thông tin vào các trường của một đối tượng của lớp ACLMessage và sau đó gọi phương thức send của lớp Agent. Đoạn mã dưới đây gửi một thông báo đến một agent có nickname là Peter rằng “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 raning");
send(msg);

Sử dụng lời nhắn trong ví dụ book trading

Trở lại với ví dụ book trading, các agent mua sẽ gửi các lời nhắn CFP (call for proposal) cho các agent bán để yêu cầu về thông tin cho một quyển sách. Các agent bán sẽ trả lời lại các agent mua bằng một lời nhắn PROPOSE chứa thông tin về quyển sách mà agent này đang muốn mua. Sau đó, agent mua sẽ gửi một lời nhắn ACCEPT_PROPOSAL đến agent bán để cho agent bán biết agent mua đã đồng ý mua quyển sách từ agent bán và đang yêu cầu một hóa đơn thanh toán. Trong trường hợp agent bán không có quyển sách mà agent mua muốn mua trong thư mục của nó, thì agent bán sẽ gửi một lời nhắn REFUSE tới agent mua. Trong các loại lời nhắn gửi từ agent mua tới agent bán, ta giả sử rằng nội dung của lời nhắn là tiêu đề của cuốn sách mà nó muốn mua. Nội dung của lời nhắn PROPOSE từ phía agent bán sẽ là giá của quyển sách mà agent mua cần. Đoạn mã dưới đây chỉ ra cách làm sao một lời nhắn CFP có thể được tạo và gửi đi


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

Nhận lời nhắn

JADE runtime sẽ tự động lưu lời nhắn được gửi đến đến vào trong hàng đợi của agent nhận. Agent nhận sẽ lấy các lời nhắn từ hàng đợi của nó bằng phương thức receive(). Phương thức này trả về lời nhắn đầu tiên bên trong hàng đợi (và xóa nó khỏi hàng đợi) hoặc trả về giá trị là null trong trường hợp hàng đợi trống.


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

Tạo hành vi để đợi lời nhắn đến

Chúng ta cần phải cài đặt các hành vi cho agent nhận để xử lý các lời nhắn từ các agent khác gửi tới. Trong ví dụ book trading chúng ta sẽ tạo hai hành vi là OfferRequestsServerPurchaseOrdersServer để lần lượt xử lý các lời nhắn từ các agent mua gửi tới cho việc lấy thông tin sách và lấy thông tin hóa đơn. Các hành vi này sẽ chạy liên tục (hành vi loại CYCLIC). Mỗi lần thực hiện phương thức action, nó sẽ kiểm tra xem có lời nhắn nào được nhận từ các agent mua hay không và sau đó xử lý chúng. Hai hành vi này thì khá là giống nhau. Dưới đây là đoạn mã cho hành vi OfferRequestsServer. Lớp này là lớp nằm ở bên trong lớp BookSellerAgent trong file BookSellerAgent.java


private class OfferRequestsServer extends CyclicBehaviour{
	public void action(){
		ACLMessage msg = myAgent.receive();
		if(msg != null){
			// 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 availabble 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);
		}
	}
}//End the inner class OfferRequestsServer

Chúng ta cài đặt hành vi OfferRequestsServer là một lớp nằm bên trong lớp BookSellerAgent, điều này giúp chúng ta có thể dễ dàng truy cập trực tiếp vào thư mục sách của agent bán.

Phương thức createReply() của lớp ACLMessage sẽ tự động tạo một đối tượng của lớp ACLMessage sau đó sẽ điền thông tin về agent nhận và vào các trường khác có liên quan đến việc điều khiển cuộc hội thoại (conversation-id, reply-with, in-reply-to).

Chú ý rằng khi chúng ta thêm hành vi trên vào agent bán, thì thread của agent sẽ khởi động một vòng lặp liên tục và sẽ làm tiêu tốn rất nhiều tài nguyên bộ nhớ. Để có thể tránh điều này, chúng ta mong muốn rằng sẽ chỉ thực hiện phương thức action() của hành vi OfferRequestsServer khi nào mà có một lời nhắn mới được nhận. Để có thể làm điều này, ta gọi phương thức block() của lớp Behaviour. Phương thức này sẽ đánh dấu hành vi đó bị “blocked” và agent sẽ không lập lịch cho hành vi này. Khi một lời nhắn mới được thêm vào hàng đợi của agent thì lúc đó các hành vi đang bị block sẽ hoạt động trở lại và chúng sẽ lại được agent lập lịch để xử lý lời nhắn. Vì vậy phương thức action() sẽ chuyển đổi như sau:


public void action(){
	ACLMessage msg = myAgent.receive();
	if(msg != null){
		[...]
	}else{
		block();
	}
}

Đoạn mã trên cũng có thể coi làm một mẫu đặc trưng khi mà chúng ta muốn thực hiện việc nhận lời nhắn bên trong một hành vi.

Chọn lời nhắn theo đặc tính của nó từ hàng đợi

Chúng ta thấy rằng các hành vi OfferRequestsServer và PurchaseOrdersServer là các hành vi kiểu CYCLIC và phương thức action() của chúng đều bắt đầu bằng việc thực hiện dòng lệnh myAgent.receive(). Chúng ta có thể chú ý đến một vấn đề đó là làm sao chúng ta có thể chắc chắn được rằng hành vi OfferRequestsServer sẽ chỉ lấy ra từ hàng đợi những lời nhắn cho việc yêu cầu cung cấp thông tin sách, và hành vi PurchaseOrdersServer sẽ chỉ lấy ra những lời nhắn cho việc yêu cầu hóa đơn. Để có thể giải quyết vấn đề này chúng ta phải chỉ ra “mẫu” cho loại lời nhắn cụ thể khi mà chúng ta gọi phương thức receive(). Khi mà một mẫu đã được chỉ ra cho phương thức receive thì phương thức này sẽ trả về lời nhắn đầu tiên trong hàng đợi khớp với mẫu. Còn những lời nhắn không khớp với mẫu sẽ bị bỏ qua. Mỗi một mẫu sẽ được cài đặt như là một đối tượng của lớp jade.lang.acl.MessageTemplate. Lớp này cung cấp các phương thức để tạo các mẫu một cách đơn giản và linh động.

Giống như đã nói ở phía trên, chúng ta sử dụng lời nhắn CFP để chứa yêu cầu cho thông tin về cuốn sách và lời nhắn ACCEPT_PROPOSAL để yêu cầu cho việc lấy hóa đơn. Vì vậy chúng ta thay đổi phương thức action() trong lớp OfferRequestsServer để sao cho khi chúng ta gọi myAGent.receive() thì nó bỏ qua tất cả các lời nhắn chỉ trừ các lời nhắn CFP.


public void action(){
	MessageTemplate mt = MessageTemplate.MatchPerformative(ACLMessage.CFP);
	ACLMessage msg = myAgent.receive(mt);
	if(msg != null){
		// CFP Message received. Process it
		[...]
	}else{
		block();
	}
}

Đoạn mã cho lớp PurchaseOrdersServer (cũng là lớp nằm bên trong lớp BookBuyerAgent) sẽ như sau.


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

Hội thoại phức tạp
Hành vi RequestPerformer là một ví dụ của một hành vi mà ở đó thực hiện một cuộc hội thoại phức tạp. Một cuộc hội thoại là một chuỗi các lời nhắn được trao đổi qua lại bởi hai hay nhiều agent với nhau. Hành vi RequestPerformer phải gửi lời nhắn CFP đến các agent bán, sau nó nó nhận câu trả lời và trong trường hợp có ít nhất một phản hồi PROPOSE, nó sẽ gửi lời nhắn ACCEPT_PROPOSAL và sau đó lại lấy lại câu trả lời từ phía agent bán. Bất cứ khi nào cuộc hội thoại được thực hiện, chúng ta sẽ điền thông tin vào các trường quản lý hội thoại nằm ở bên trong lời nhắn được trao đổi bên trong cuộc hội thoại đó.

Đoạn mã cho lớp RequestPerformer là lớp nằm bên trong lớp BookBuyerAgent sẽ như sau:


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

Các cuộc hội thoại phức tạp hay được thực hiện theo một giao thức tương tác đã được định nghĩa chuẩn. JADE cung cấp một sự hỗ trợ cho việc cài đặt các cuộc hội thoại theo các giao thức tương tác trong gói jade.proto. Cụ thể hơn các cuộc hội thoại trong ví dụ trên được cài đặt theo giao thức “Contract-net” bằng việc cài đặt lớp jade.proto.ContractNetInitiator

Nhận lời nhắn trong chế độ blocking
Bên cạnh phương thức receive(), lớp Agent cũng cung cấp một phương thức là blockingReceive(). Nó sẽ không trả về giá trị cho đến khi có một lời nhắn trong hàng đợi của agent. Phương thức này có một phiên bản overload là nhận một tham số có kiểu là MessageTemplate (nó sẽ không trả về giá trị cho đến khi có một lời nhắn trong hàng đợi khớp với mẫu).

Một điều quan trọng mà chúng ta phải nhấn mạnh đó là phương thức blockingReceive() sẽ thực sự block cái agent thread. Vì vậy, nếu chúng ta gọi phương thức blockingReceive() từ bên trong một hành vi, điều này sẽ làm cản trở các hành vi khác được thực hiện cho đến khi phương thức này trả về giá trị. Trong thực tế, chúng ta sẽ sử dụng phương thức blockingReceive() trong các phương thức setup() và takeDown() của agent, và thay vào đó sẽ sử dụng phương thức receive() kết hợp với phương thức block() của lớp Behaviour bên trong một hành vi.

Như vậy kết thúc phần này, chúng ta cơ bản hiểu được cơ chế nào để các agent có thể tương tác được với nhau. Trong phần sau chúng ta sẽ tìm hiểu một chút về dịch vụ trang vàng (Yellow Page)


Nguồn: Giovanni Caire – Jade tutorial, Jade programming for beginners

Advertisements

Trả lời

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Đăng xuất /  Thay đổi )

Google photo

Bạn đang bình luận bằng tài khoản Google Đăng xuất /  Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Đăng xuất /  Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Đăng xuất /  Thay đổi )

Connecting to %s