def is_permitted_for_new_dialogue(self, message: Message, known_pbks: List[str], sender: Address) -> bool: """ Check whether an agent message is permitted for a new dialogue. That is, the message has to - be a CFP, - have the correct msg id and message target, and - be from a known public key. :param message: the agent message :param known_pbks: the list of known public keys :return: a boolean indicating whether the message is permitted for a new dialogue """ msg_id = message.get("id") target = message.get("target") performative = message.get("performative") result = performative == FIPAMessage.Performative.CFP \ and msg_id == STARTING_MESSAGE_ID\ and target == STARTING_MESSAGE_TARGET \ and (sender in known_pbks) return result
def get_dialogue( self, message: Message, sender: Address, agent_pbk: Address ) -> Dialogue: """ Retrieve dialogue. :param message: the agent message :param agent_pbk: the public key of the agent :return: the dialogue """ dialogue_id = message.get("dialogue_id") opponent = sender target = message.get("target") performative = message.get("performative") self_initiated_dialogue_label = DialogueLabel(dialogue_id, opponent, agent_pbk) other_initiated_dialogue_label = DialogueLabel(dialogue_id, opponent, opponent) if ( performative == FIPAMessage.Performative.PROPOSE and target == PROPOSE_TARGET and self_initiated_dialogue_label in self.dialogues ): dialogue = self.dialogues[self_initiated_dialogue_label] elif performative == FIPAMessage.Performative.ACCEPT: if ( target == ACCEPT_TARGET and other_initiated_dialogue_label in self.dialogues ): dialogue = self.dialogues[other_initiated_dialogue_label] else: raise ValueError("Should have found dialogue.") elif performative == FIPAMessage.Performative.MATCH_ACCEPT: if ( target == MATCH_ACCEPT_TARGET and self_initiated_dialogue_label in self.dialogues ): dialogue = self.dialogues[self_initiated_dialogue_label] else: raise ValueError("Should have found dialogue.") elif performative == FIPAMessage.Performative.DECLINE: if ( target == DECLINED_CFP_TARGET and self_initiated_dialogue_label in self.dialogues ): dialogue = self.dialogues[self_initiated_dialogue_label] elif ( target == DECLINED_PROPOSE_TARGET and other_initiated_dialogue_label in self.dialogues ): dialogue = self.dialogues[other_initiated_dialogue_label] elif ( target == DECLINED_ACCEPT_TARGET and self_initiated_dialogue_label in self.dialogues ): dialogue = self.dialogues[self_initiated_dialogue_label] else: raise ValueError("Should have found dialogue.") else: raise ValueError("Should have found dialogue.") return dialogue
def on_oef_error(self, oef_error: Message) -> None: """ Handle an OEF error message. :param oef_error: the oef error :return: None """ logger.error( "[{}]: Received OEF error: answer_id={}, operation={}".format( self.agent_name, oef_error.get("id"), oef_error.get("operation")))
def on_dialogue_error(self, dialogue_error: Message) -> None: """ Handle a dialogue error message. :param dialogue_error: the dialogue error message :return: None """ logger.error( "[{}]: Received Dialogue error: answer_id={}, dialogue_id={}, origin={}" .format(self.agent_name, dialogue_error.get("id"), dialogue_error.get("dialogue_id"), dialogue_error.get("origin")))
def is_belonging_to_registered_dialogue(self, message: Message, agent_pbk: Address, sender: Address) -> bool: """ Check whether an agent message is part of a registered dialogue. :param message: the agent message :param agent_pbk: the public key of the agent :return: boolean indicating whether the message belongs to a registered dialogue """ dialogue_id = message.get("dialogue_id") opponent = sender target = message.get("target") performative = message.get("performative") self_initiated_dialogue_label = DialogueLabel(dialogue_id, opponent, agent_pbk) other_initiated_dialogue_label = DialogueLabel(dialogue_id, opponent, opponent) result = False if performative == FIPAMessage.Performative.PROPOSE and target == PROPOSE_TARGET and self_initiated_dialogue_label in self.dialogues: self_initiated_dialogue = self.dialogues[ self_initiated_dialogue_label] result = self_initiated_dialogue.is_expecting_propose() elif performative == FIPAMessage.Performative.ACCEPT: if target == ACCEPT_TARGET and other_initiated_dialogue_label in self.dialogues: other_initiated_dialogue = self.dialogues[ other_initiated_dialogue_label] result = other_initiated_dialogue.is_expecting_initial_accept() elif performative == FIPAMessage.Performative.MATCH_ACCEPT: if target == MATCH_ACCEPT_TARGET and self_initiated_dialogue_label in self.dialogues: self_initiated_dialogue = self.dialogues[ self_initiated_dialogue_label] result = self_initiated_dialogue.is_expecting_matching_accept() elif performative == FIPAMessage.Performative.DECLINE: if target == DECLINED_CFP_TARGET and self_initiated_dialogue_label in self.dialogues: self_initiated_dialogue = self.dialogues[ self_initiated_dialogue_label] result = self_initiated_dialogue.is_expecting_cfp_decline() elif target == DECLINED_PROPOSE_TARGET and other_initiated_dialogue_label in self.dialogues: other_initiated_dialogue = self.dialogues[ other_initiated_dialogue_label] result = other_initiated_dialogue.is_expecting_propose_decline( ) elif target == DECLINED_ACCEPT_TARGET and self_initiated_dialogue_label in self.dialogues: self_initiated_dialogue = self.dialogues[ self_initiated_dialogue_label] result = self_initiated_dialogue.is_expecting_accept_decline() return result
def _handle(self, message: Message, dialogue: Dialogue) -> List[Envelope]: """ Handle a message according to the defined behaviour. :param message: the agent message :param dialogue: the dialogue :return: a list of agent messages """ dialogue.incoming_extend([message]) results = [] # type: List[Envelope] performative = message.get("performative") if performative == FIPAMessage.Performative.CFP: result = self.negotiation_behaviour.on_cfp(message, dialogue) results = [result] elif performative == FIPAMessage.Performative.PROPOSE: result = self.negotiation_behaviour.on_propose(message, dialogue) results = [result] elif performative == FIPAMessage.Performative.ACCEPT: results = self.negotiation_behaviour.on_accept(message, dialogue) elif performative == FIPAMessage.Performative.MATCH_ACCEPT: results = self.negotiation_behaviour.on_match_accept( message, dialogue) elif performative == FIPAMessage.Performative.DECLINE: self.negotiation_behaviour.on_decline(message, dialogue) results = [] else: raise ValueError("Performative not supported: {}".format( str(performative))) return results
def on_propose(self, propose: Message, dialogue: Dialogue) -> Envelope: """ Handle a Propose. :param propose: the message containing the Propose :param dialogue: the dialogue :return: an Accept or a Decline """ logger.debug("[{}]: on propose as {}.".format(self.agent_name, dialogue.role)) assert propose.get("performative") == FIPAMessage.Performative.PROPOSE proposal = propose.get("proposal")[0] transaction_id = generate_transaction_id( self.crypto.public_key, dialogue.dialogue_label.dialogue_opponent_pbk, dialogue.dialogue_label, dialogue.is_seller) transaction = Transaction.from_proposal( proposal=proposal, transaction_id=transaction_id, is_sender_buyer=not dialogue.is_seller, counterparty=dialogue.dialogue_label.dialogue_opponent_pbk, sender=self.crypto.public_key) new_msg_id = propose.get("id") + 1 is_profitable_transaction, propose_log_msg = self.game_instance.is_profitable_transaction( transaction, dialogue) logger.debug(propose_log_msg) if is_profitable_transaction: logger.debug("[{}]: Accepting propose (as {}).".format( self.agent_name, dialogue.role)) self.game_instance.transaction_manager.add_locked_tx( transaction, as_seller=dialogue.is_seller) self.game_instance.transaction_manager.add_pending_initial_acceptance( dialogue.dialogue_label, new_msg_id, transaction) msg = FIPAMessage(message_id=new_msg_id, dialogue_id=propose.get("dialogue_id"), target=propose.get("id"), performative=FIPAMessage.Performative.ACCEPT) dialogue.outgoing_extend([msg]) msg_bytes = FIPASerializer().encode(msg) result = Envelope(to=dialogue.dialogue_label.dialogue_opponent_pbk, sender=self.crypto.public_key, protocol_id=FIPAMessage.protocol_id, message=msg_bytes) else: logger.debug("[{}]: Declining propose (as {})".format( self.agent_name, dialogue.role)) msg = FIPAMessage(message_id=new_msg_id, dialogue_id=propose.get("dialogue_id"), target=propose.get("id"), performative=FIPAMessage.Performative.DECLINE) dialogue.outgoing_extend([msg]) msg_bytes = FIPASerializer().encode(msg) result = Envelope(to=dialogue.dialogue_label.dialogue_opponent_pbk, sender=self.crypto.public_key, protocol_id=FIPAMessage.protocol_id, message=msg_bytes) self.game_instance.stats_manager.add_dialogue_endstate( EndState.DECLINED_PROPOSE, dialogue.is_self_initiated) return result
def on_new_dialogue(self, message: Message, sender: Address) -> None: """ React to a new dialogue. :param message: the agent message :return: None """ assert message.get("performative") == FIPAMessage.Performative.CFP services = json.loads(message.get("query").decode("utf-8")) is_seller = services["description"] == TAC_DEMAND_DATAMODEL_NAME dialogue = self.dialogues.create_opponent_initiated( sender, message.get("dialogue_id"), is_seller) logger.debug("[{}]: saving dialogue (as {}): dialogue_id={}".format( self.agent_name, dialogue.role, dialogue.dialogue_label.dialogue_id)) envelopes = self._handle(message, dialogue) for envelope in envelopes: self.mailbox.outbox.put(envelope)
def on_decline(self, decline: Message, dialogue: Dialogue) -> None: """ Handle a Decline. :param decline: the message containing the Decline :param dialogue: the dialogue :return: None """ assert decline.get("performative") == FIPAMessage.Performative.DECLINE logger.debug( "[{}]: on_decline: msg_id={}, dialogue_id={}, origin={}, target={}" .format( self.agent_name, decline.get("id"), decline.get("dialogue_id"), dialogue.dialogue_label.dialogue_opponent_pbk, decline.get("target"), )) target = decline.get("target") if target == 1: self.game_instance.stats_manager.add_dialogue_endstate( EndState.DECLINED_CFP, dialogue.is_self_initiated) elif target == 2: self.game_instance.stats_manager.add_dialogue_endstate( EndState.DECLINED_PROPOSE, dialogue.is_self_initiated) transaction = self.game_instance.transaction_manager.pop_pending_proposal( dialogue.dialogue_label, target) if self.game_instance.strategy.is_world_modeling: self.game_instance.world_state.update_on_declined_propose( transaction) elif target == 3: self.game_instance.stats_manager.add_dialogue_endstate( EndState.DECLINED_ACCEPT, dialogue.is_self_initiated) transaction = self.game_instance.transaction_manager.pop_pending_initial_acceptance( dialogue.dialogue_label, target) self.game_instance.transaction_manager.pop_locked_tx( transaction.transaction_id) return None
def on_search_result(self, search_result: Message) -> None: """ Split the search results from the OEF. :param search_result: the search result :return: None """ search_id = search_result.get("id") agents = search_result.get("agents") logger.debug("[{}]: on search result: {} {}".format( self.agent_name, search_id, agents)) if search_id in self.game_instance.search.ids_for_tac: self._on_controller_search_result(agents) elif search_id in self.game_instance.search.ids_for_sellers: self._on_services_search_result(agents, is_searching_for_sellers=True) elif search_id in self.game_instance.search.ids_for_buyers: self._on_services_search_result(agents, is_searching_for_sellers=False) else: logger.debug("[{}]: Unknown search id: search_id={}".format( self.agent_name, search_id))
def on_match_accept(self, match_accept: Message, dialogue: Dialogue) -> List[Envelope]: """ Handle a matching Accept. :param match_accept: the envelope containing the MatchAccept :param dialogue: the dialogue :return: a Transaction """ assert (match_accept.get("performative") == FIPAMessage.Performative.MATCH_ACCEPT and dialogue.dialogue_label in self.game_instance. transaction_manager.pending_initial_acceptances and match_accept.get("target") in self.game_instance.transaction_manager. pending_initial_acceptances[dialogue.dialogue_label]) logger.debug( "[{}]: on_match_accept: msg_id={}, dialogue_id={}, origin={}, target={}" .format( self.agent_name, match_accept.get("id"), match_accept.get("dialogue_id"), dialogue.dialogue_label.dialogue_opponent_pbk, match_accept.get("target"), )) results = [] transaction = self.game_instance.transaction_manager.pop_pending_initial_acceptance( dialogue.dialogue_label, match_accept.get("target")) tac_msg = TACMessage( tac_type=TACMessage.Type.TRANSACTION, transaction_id=transaction.transaction_id, is_sender_buyer=transaction.is_sender_buyer, counterparty=transaction.counterparty, amount=transaction.amount, quantities_by_good_pbk=transaction.quantities_by_good_pbk, ) dialogue.outgoing_extend([tac_msg]) tac_bytes = TACSerializer().encode(tac_msg) results.append( Envelope( to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=TACMessage.protocol_id, message=tac_bytes, )) return results
def on_cfp(self, cfp: Message, dialogue: Dialogue) -> Envelope: """ Handle a CFP. :param cfp: the message containing the CFP :param dialogue: the dialogue :return: a Propose or a Decline """ assert cfp.get("performative") == FIPAMessage.Performative.CFP goods_description = self.game_instance.get_service_description( is_supply=dialogue.is_seller) new_msg_id = cfp.get("id") + 1 decline = False cfp_services = json.loads(cfp.get("query").decode('utf-8')) if not self.game_instance.is_matching(cfp_services, goods_description): decline = True logger.debug( "[{}]: Current holdings do not satisfy CFP query.".format( self.agent_name)) else: proposal = self.game_instance.generate_proposal( cfp_services, dialogue.is_seller) if proposal is None: decline = True logger.debug( "[{}]: Current strategy does not generate proposal that satisfies CFP query." .format(self.agent_name)) if decline: logger.debug("[{}]: sending to {} a Decline{}".format( self.agent_name, dialogue.dialogue_label.dialogue_opponent_pbk, pprint.pformat({ "msg_id": new_msg_id, "dialogue_id": cfp.get("dialogue_id"), "origin": dialogue.dialogue_label.dialogue_opponent_pbk, "target": cfp.get("target") }))) msg = FIPAMessage(message_id=new_msg_id, dialogue_id=cfp.get("dialogue_id"), performative=FIPAMessage.Performative.DECLINE, target=cfp.get("id")) dialogue.outgoing_extend([msg]) msg_bytes = FIPASerializer().encode(msg) response = Envelope( to=dialogue.dialogue_label.dialogue_opponent_pbk, sender=self.crypto.public_key, protocol_id=FIPAMessage.protocol_id, message=msg_bytes) self.game_instance.stats_manager.add_dialogue_endstate( EndState.DECLINED_CFP, dialogue.is_self_initiated) else: proposal = cast(Description, proposal) transaction_id = generate_transaction_id( self.crypto.public_key, dialogue.dialogue_label.dialogue_opponent_pbk, dialogue.dialogue_label, dialogue.is_seller) transaction = Transaction.from_proposal( proposal=proposal, transaction_id=transaction_id, is_sender_buyer=not dialogue.is_seller, counterparty=dialogue.dialogue_label.dialogue_opponent_pbk, sender=self.crypto.public_key) self.game_instance.transaction_manager.add_pending_proposal( dialogue.dialogue_label, new_msg_id, transaction) logger.debug("[{}]: sending to {} a Propose{}".format( self.agent_name, dialogue.dialogue_label.dialogue_opponent_pbk, pprint.pformat({ "msg_id": new_msg_id, "dialogue_id": cfp.get("dialogue_id"), "origin": dialogue.dialogue_label.dialogue_opponent_pbk, "target": cfp.get("id"), "propose": proposal.values }))) msg = FIPAMessage(performative=FIPAMessage.Performative.PROPOSE, message_id=new_msg_id, dialogue_id=cfp.get("dialogue_id"), target=cfp.get("id"), proposal=[proposal]) dialogue.outgoing_extend([msg]) dialogue.outgoing_extend([msg]) msg_bytes = FIPASerializer().encode(msg) response = Envelope( to=dialogue.dialogue_label.dialogue_opponent_pbk, sender=self.crypto.public_key, protocol_id=FIPAMessage.protocol_id, message=msg_bytes) return response
def on_accept(self, accept: Message, dialogue: Dialogue) -> List[Envelope]: """ Handle an Accept. :param accept: the message containing the Accept :param dialogue: the dialogue :return: a Decline, or an Accept and a Transaction, or a Transaction (in a Message object) """ assert accept.get("performative") == FIPAMessage.Performative.ACCEPT \ and dialogue.dialogue_label in self.game_instance.transaction_manager.pending_proposals \ and accept.get("target") in self.game_instance.transaction_manager.pending_proposals[dialogue.dialogue_label] logger.debug( "[{}]: on_accept: msg_id={}, dialogue_id={}, origin={}, target={}". format(self.agent_name, accept.get("id"), accept.get("dialogue_id"), dialogue.dialogue_label.dialogue_opponent_pbk, accept.get("target"))) new_msg_id = accept.get("id") + 1 results = [] transaction = self.game_instance.transaction_manager.pop_pending_proposal( dialogue.dialogue_label, accept.get("target")) is_profitable_transaction, accept_log_msg = self.game_instance.is_profitable_transaction( transaction, dialogue) logger.debug(accept_log_msg) if is_profitable_transaction: if self.game_instance.strategy.is_world_modeling: self.game_instance.world_state.update_on_initial_accept( transaction) logger.debug("[{}]: Locking the current state (as {}).".format( self.agent_name, dialogue.role)) self.game_instance.transaction_manager.add_locked_tx( transaction, as_seller=dialogue.is_seller) tac_msg = TACMessage( tac_type=TACMessage.Type.TRANSACTION, transaction_id=transaction.transaction_id, is_sender_buyer=transaction.is_sender_buyer, counterparty=transaction.counterparty, amount=transaction.amount, quantities_by_good_pbk=transaction.quantities_by_good_pbk) dialogue.outgoing_extend([tac_msg]) tac_bytes = TACSerializer().encode(tac_msg) results.append( Envelope(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=TACMessage.protocol_id, message=tac_bytes)) msg = FIPAMessage( message_id=new_msg_id, dialogue_id=accept.get("dialogue_id"), target=accept.get("id"), performative=FIPAMessage.Performative.MATCH_ACCEPT) dialogue.outgoing_extend([msg]) msg_bytes = FIPASerializer().encode(msg) results.append( Envelope(to=dialogue.dialogue_label.dialogue_opponent_pbk, sender=self.crypto.public_key, protocol_id=FIPAMessage.protocol_id, message=msg_bytes)) else: logger.debug("[{}]: Decline the accept (as {}).".format( self.agent_name, dialogue.role)) msg = FIPAMessage(message_id=new_msg_id, dialogue_id=accept.get("dialogue_id"), target=accept.get("id"), performative=FIPAMessage.Performative.DECLINE) dialogue.outgoing_extend([msg]) msg_bytes = FIPASerializer().encode(msg) results.append( Envelope(to=dialogue.dialogue_label.dialogue_opponent_pbk, sender=self.crypto.public_key, protocol_id=FIPAMessage.protocol_id, message=msg_bytes)) self.game_instance.stats_manager.add_dialogue_endstate( EndState.DECLINED_ACCEPT, dialogue.is_self_initiated) return results
def encode(self, msg: Message) -> bytes: """ Decode the message. :param msg: the message object :return: the bytes """ tac_type = TACMessage.Type(msg.get("type")) tac_container = tac_pb2.TACMessage() if tac_type == TACMessage.Type.REGISTER: agent_name = msg.get("agent_name") tac_msg = tac_pb2.TACAgent.Register() # type: ignore tac_msg.agent_name = agent_name tac_container.register.CopyFrom(tac_msg) elif tac_type == TACMessage.Type.UNREGISTER: tac_msg = tac_pb2.TACAgent.Unregister() # type: ignore tac_container.unregister.CopyFrom(tac_msg) elif tac_type == TACMessage.Type.TRANSACTION: tac_msg = tac_pb2.TACAgent.Transaction() # type: ignore tac_msg.transaction_id = msg.get("transaction_id") tac_msg.is_sender_buyer = msg.get("is_sender_buyer") tac_msg.counterparty = msg.get("counterparty") tac_msg.amount = msg.get("amount") tac_msg.quantities.extend( _from_dict_to_pairs(msg.get("quantities_by_good_pbk"))) tac_container.transaction.CopyFrom(tac_msg) elif tac_type == TACMessage.Type.GET_STATE_UPDATE: tac_msg = tac_pb2.TACAgent.GetStateUpdate() # type: ignore tac_container.get_state_update.CopyFrom(tac_msg) elif tac_type == TACMessage.Type.CANCELLED: tac_msg = tac_pb2.TACController.Cancelled() # type: ignore tac_container.cancelled.CopyFrom(tac_msg) elif tac_type == TACMessage.Type.GAME_DATA: tac_msg = tac_pb2.TACController.GameData() # type: ignore tac_msg.money = msg.get("money") tac_msg.endowment.extend(msg.get("endowment")) tac_msg.utility_params.extend(msg.get("utility_params")) tac_msg.nb_agents = msg.get("nb_agents") tac_msg.nb_goods = msg.get("nb_goods") tac_msg.tx_fee = msg.get("tx_fee") tac_msg.agent_pbk_to_name.extend( _from_dict_to_pairs(msg.get("agent_pbk_to_name"))) tac_msg.good_pbk_to_name.extend( _from_dict_to_pairs(msg.get("good_pbk_to_name"))) tac_container.game_data.CopyFrom(tac_msg) elif tac_type == TACMessage.Type.TRANSACTION_CONFIRMATION: tac_msg = tac_pb2.TACController.TransactionConfirmation( ) # type: ignore tac_msg.transaction_id = msg.get("transaction_id") tac_container.transaction_confirmation.CopyFrom(tac_msg) elif tac_type == TACMessage.Type.STATE_UPDATE: tac_msg = tac_pb2.TACController.StateUpdate() # type: ignore game_data_json = msg.get("initial_state") game_data = tac_pb2.TACController.GameData() # type: ignore game_data.money = game_data_json["money"] # type: ignore game_data.endowment.extend( game_data_json["endowment"]) # type: ignore game_data.utility_params.extend( game_data_json["utility_params"]) # type: ignore game_data.nb_agents = game_data_json["nb_agents"] # type: ignore game_data.nb_goods = game_data_json["nb_goods"] # type: ignore game_data.tx_fee = game_data_json["tx_fee"] # type: ignore game_data.agent_pbk_to_name.extend( _from_dict_to_pairs( cast(Dict[str, str], game_data_json["agent_pbk_to_name"]))) # type: ignore game_data.good_pbk_to_name.extend( _from_dict_to_pairs( cast(Dict[str, str], game_data_json["good_pbk_to_name"]))) # type: ignore tac_msg.initial_state.CopyFrom(game_data) transactions = [] msg_transactions = cast(List[Any], msg.get("transactions")) for t in msg_transactions: tx = tac_pb2.TACAgent.Transaction() # type: ignore tx.transaction_id = t.get("transaction_id") tx.is_sender_buyer = t.get("is_sender_buyer") tx.counterparty = t.get("counterparty") tx.amount = t.get("amount") tx.quantities.extend( _from_dict_to_pairs(t.get("quantities_by_good_pbk"))) transactions.append(tx) tac_msg.txs.extend(transactions) tac_container.state_update.CopyFrom(tac_msg) elif tac_type == TACMessage.Type.TAC_ERROR: tac_msg = tac_pb2.TACController.Error() # type: ignore tac_msg.error_code = TACMessage.ErrorCode( msg.get("error_code")).value if msg.is_set("error_msg"): tac_msg.error_msg = msg.get("error_msg") if msg.is_set("details"): tac_msg.details.update(msg.get("details")) tac_container.error.CopyFrom(tac_msg) else: raise ValueError("Type not recognized: {}.".format(tac_type)) tac_message_bytes = tac_container.SerializeToString() return tac_message_bytes