Protocoles d’interaction FIPA

Les protocoles d’interaction FIPA définissent des séquences standardisées d’échanges ACL entre agents. Ils sont implémentés dans include/gagent/protocols/ sous forme de Behaviour réutilisables.

Contract Net (FIPA SC00029H)

Le Contract Net Protocol permet à un agent (initiateur) de déléguer une tâche à l’agent le plus qualifié parmi plusieurs candidats (participants), via un mécanisme d’enchère.

Initiateur          Participants
    │                    │
    │── CFP ────────────►│  "qui peut faire X ?"
    │                    │
    │◄── PROPOSE ────────│  offre avec contenu libre (ex: coût, délai)
    │◄── REFUSE ─────────│  refus (participant surchargé, hors périmètre…)
    │
    │  [selectProposals() — choisit le(s) gagnant(s)]
    │
    │── ACCEPT_PROPOSAL ►│  au(x) gagnant(s)
    │── REJECT_PROPOSAL ►│  aux autres
    │
    │◄── INFORM ─────────│  résultat de l'exécution
    │◄── FAILURE ─────────│  (si la tâche a échoué)

Inclusion

#include <gagent/protocols/ContractNet.hpp>
using namespace gagent::protocols;

ContractNetInitiator

Crée une sous-classe et surcharge les méthodes virtuelles :

class MonInitiateur : public ContractNetInitiator {
public:
    MonInitiateur(Agent* ag,
                  const std::string& my_name,
                  const std::vector<AgentIdentifier>& participants)
        : ContractNetInitiator(
            ag,
            my_name,
            [](){
                ACLMessage cfp(ACLMessage::Performative::CFP);
                cfp.setContent("ma tâche");
                cfp.setOntology("mon-ontologie");
                return cfp;
            }(),
            participants,
            5000,   // délai offres (ms)
            10000)  // délai résultat (ms)
    {}

    // Sélectionne les agents à accepter parmi les propositions reçues
    std::vector<std::string> selectProposals(
            const std::vector<ACLMessage>& proposals) override
    {
        // Exemple : prendre la proposition avec le plus petit coût
        std::string best;
        int best_cost = INT_MAX;
        for (auto& p : proposals) {
            int cost = std::stoi(p.getContent());
            if (cost < best_cost) { best_cost = cost; best = p.getSender().name; }
        }
        return { best };
    }

    void handleInform(const ACLMessage& result) override {
        std::cout << "Résultat : " << result.getContent() << "\n";
        this_agent->doDelete();
    }

    void handleRefuse(const ACLMessage& msg) override {
        std::cout << msg.getSender().name << " a refusé\n";
    }
};

Méthode à surcharger

Description

selectProposals(proposals)

Obligatoire. Retourne les noms des agents à accepter.

handleInform(msg)

Résultat reçu du participant accepté.

handleFailure(msg)

Échec signalé par le participant.

handleRefuse(msg)

Refus d’un participant.

Paramètres du constructeur :

Paramètre

Description

my_name

Nom de l’initiateur — détermine la queue /acl_<my_name>

cfp

Message CFP à envoyer (contenu déjà défini)

participants

Liste des AgentIdentifier destinataires

proposal_timeout_ms

Délai pour recevoir toutes les offres (défaut : 5000 ms)

result_timeout_ms

Délai pour recevoir le résultat d’exécution (défaut : 10000 ms)

ContractNetParticipant

class MonParticipant : public ContractNetParticipant {
public:
    MonParticipant(Agent* ag, const std::string& my_name)
        : ContractNetParticipant(ag, my_name) {}

    // Évaluer le CFP et répondre PROPOSE ou REFUSE
    ACLMessage prepareProposal(const ACLMessage& cfp) override {
        if (surcharge_) {
            ACLMessage r(ACLMessage::Performative::REFUSE);
            r.setContent("surchargé");
            return r;
        }
        ACLMessage prop(ACLMessage::Performative::PROPOSE);
        prop.setContent(std::to_string(mon_tarif_));
        return prop;
    }

    // Exécuter la tâche acceptée et rapporter INFORM ou FAILURE
    ACLMessage executeTask(const ACLMessage& accept) override {
        // ... exécution ...
        ACLMessage result(ACLMessage::Performative::INFORM);
        result.setContent("terminé");
        this_agent->doDelete();
        return result;
    }
};

Méthode à surcharger

Description

prepareProposal(cfp)

Obligatoire. Retourne PROPOSE (offre) ou REFUSE (raison).

executeTask(accept)

Obligatoire. Exécute la tâche. Retourne INFORM ou FAILURE.

Le comportement se termine automatiquement (done() = true) après :

  • avoir envoyé un REFUSE

  • avoir envoyé INFORM ou FAILURE suite à un ACCEPT

  • avoir reçu un REJECT_PROPOSAL

Subscribe-Notify (FIPA SC00035H)

Le Subscribe-Notify Protocol permet à un agent (subscriber) de s’abonner aux notifications d’un agent publisher. Le publisher envoie des INFORM à chaque changement d’état à tous ses subscribers actifs.

Subscriber          Publisher
    │                   │
    │── SUBSCRIBE ──────►│   "notifie-moi quand X change"
    │                   │
    │◄── AGREE ──────────│   accepté
    │   (ou REFUSE)      │   refusé → done
    │                   │
    │             [événement]
    │◄── INFORM ─────────│   notification (répétée)
    │◄── INFORM ─────────│
    │                   │
    │── CANCEL ──────────►│   se désabonner (optionnel)
    │◄── INFORM ─────────│   confirmation finale (optionnel)

Inclusion

#include <gagent/protocols/SubscribeNotify.hpp>
using namespace gagent::protocols;

SubscribeInitiator

class MonSubscriber : public SubscribeInitiator {
public:
    MonSubscriber(Agent* ag, const std::string& my_name,
                 const std::string& publisher)
        : SubscribeInitiator(
            ag,
            my_name,
            publisher,
            "temperature",  // topic / contenu du SUBSCRIBE
            "sensors",      // ontologie (optionnel)
            5000)           // timeout AGREE ms (défaut : 5000)
    {}

    void handleNotify(const ACLMessage& msg) override {
        std::cout << "Notification : " << msg.getContent() << "\n";
    }

    void handleRefuse(const ACLMessage& msg) override {
        std::cout << "Refusé : " << msg.getContent() << "\n";
    }

    // Retourner true pour envoyer CANCEL et se désabonner
    bool shouldCancel() override {
        return notifications_reçues_ >= 10;
    }
};

Méthode à surcharger

Description

handleNotify(msg)

Appelé à chaque INFORM reçu du publisher (y compris le dernier après CANCEL).

handleRefuse(msg)

Publisher a refusé l’abonnement.

shouldCancel()

Appelé à chaque tick. Retourner true pour envoyer CANCEL et terminer.

done() retourne true après réception de REFUSE ou après traitement du CANCEL.

SubscribeParticipant

class MonPublisher : public SubscribeParticipant {
    int tick_ = 0;
public:
    MonPublisher(Agent* ag, const std::string& my_name)
        : SubscribeParticipant(ag, my_name, 200) {}  // poll_ms

    bool handleSubscribe(const ACLMessage& sub) override {
        // Accepter ou refuser l'abonnement
        return true;
    }

    std::string handleCancel(const std::string& subscriber) override {
        // Retourner le contenu du dernier INFORM (vide = aucun)
        return "fin-abonnement";
    }

    void action() override {
        SubscribeParticipant::action();  // gère SUBSCRIBE / CANCEL entrants

        // Publier périodiquement
        if (++tick_ % 10 == 0) {
            notify(std::to_string(temperature_));
        }
    }
};

Méthode à surcharger

Description

handleSubscribe(msg)

Retourner true pour accepter (AGREE), false pour refuser (REFUSE). Par défaut accepte tout.

handleCancel(subscriber)

Appelé quand un subscriber se désabonne. Retourner le contenu du dernier INFORM à envoyer (vide = aucun).

Méthode publique

Description

notify(content, ontology="")

Envoie un INFORM à tous les subscribers actifs.

notifyOne(subscriber, content, ontology="")

Envoie un INFORM à un seul subscriber.

subscriberCount()

Retourne le nombre de subscribers actifs.

done() retourne toujours false — le publisher tourne indéfiniment.

Exemple complet (Subscribe-Notify)

Voir tests/test_subscribe_notify.cpp — capteur de température : le publisher émet toutes les 200 ms, le subscriber annule après 3 notifications.

cd build/tests
./test_subscribe_notify

Request (FIPA SC00026H)

Le Request Protocol est le protocole FIPA le plus simple : un initiateur envoie une demande à un participant qui l’exécute et retourne le résultat.

Initiateur          Participant
    │                   │
    │── REQUEST ────────►│
    │                   │
    │◄── AGREE ──────────│  (optionnel — si traitement long)
    │                   │
    │◄── INFORM ─────────│  succès
    │◄── REFUSE ─────────│  refus immédiat
    │◄── FAILURE ─────────│  échec après AGREE
    │◄── NOT_UNDERSTOOD ──│

Inclusion

#include <gagent/protocols/Request.hpp>
using namespace gagent::protocols;

RequestInitiator

class MonRequester : public RequestInitiator {
public:
    MonRequester(Agent* ag, const std::string& my_name,
                const std::string& target)
        : RequestInitiator(
            ag,
            my_name,
            target,
            "add(3,4)",   // contenu de la requête
            "math",       // ontologie (optionnel)
            10000)        // timeout ms (défaut : 10000)
    {}

    void handleInform(const ACLMessage& msg) override {
        std::cout << "Résultat : " << msg.getContent() << "\n";
    }

    void handleRefuse(const ACLMessage& msg) override {
        std::cout << "Refusé : " << msg.getContent() << "\n";
    }

    void handleTimeout() override {
        std::cerr << "Pas de réponse !\n";
    }
};

Méthode à surcharger

Description

handleInform(msg)

Requête traitée avec succès.

handleRefuse(msg)

Participant a refusé.

handleFailure(msg)

Participant a échoué après avoir accepté.

handleAgree(msg)

Participant a accepté (traitement long en cours).

handleNotUnderstood(msg)

Requête non comprise.

handleTimeout()

Pas de réponse dans le délai imparti.

done() retourne true dès réception d’une réponse ou timeout.

RequestParticipant

class MonServeur : public RequestParticipant {
public:
    MonServeur(Agent* ag, const std::string& my_name)
        : RequestParticipant(ag, my_name, 500) {}  // tick_ms

    ACLMessage handleRequest(const ACLMessage& req) override {
        // Traiter la requête et retourner INFORM, REFUSE ou FAILURE
        ACLMessage reply = req.createReply(ACLMessage::Performative::INFORM);
        reply.setSender(AgentIdentifier{my_name_});
        reply.setContent("7");
        return reply;
    }
};

Méthode à surcharger

Description

handleRequest(req)

Obligatoire. Traite la requête et retourne la réponse (INFORM, REFUSE ou FAILURE). Par défaut retourne INFORM vide.

done() retourne toujours false — le participant tourne indéfiniment et peut traiter plusieurs requêtes successives.

Exemple complet

Voir tests/test_request.cpp — un agent calcule 3+4 à distance.

cd build/tests
./test_request

Messagerie ACL — AclMQ

Les fonctions bas niveau sont disponibles séparément pour un usage direct :

#include <gagent/messaging/AclMQ.hpp>
using namespace gagent::messaging;

// Envoyer
acl_send("bob", msg);

// Recevoir avec timeout
auto opt = acl_receive("alice", 5000);  // ms
if (opt) { /* traiter *opt */ }

// Libérer la queue en fin d'agent (dans takeDown())
acl_unlink("alice");

Exemple complet (Contract Net)

Voir tests/test_contract_net.cpp — enchère de livraison avec 3 transporteurs et sélection du moins cher.

cd build/tests
./test_contract_net