IoT et IA

Agent LLM pour appareils IoT : architecture, outils et garde-fous de sécurité

Comment concevoir un agent LLM qui lit l'état des appareils via MQTT et envoie des commandes en toute sécurité, grâce à un cache d'état, des outils contraints par schéma, une couche de règles, une approbation humaine et une piste d'audit complète.

Schéma d'architecture d'un agent LLM pour appareils IoT lisant la télémétrie MQTT et envoyant des commandes approuvées via une passerelle de règles
Essayez-le sur votre propre broker MQTTObtenez un broker MQTT privé et managé avec l’offre Free et partez d’exemples de code prêts à l’emploi.
Essayer gratuitement Exemples de code Aucune carte bancaire requise

Un agent LLM pour appareils IoT peut répondre à des questions sur les équipements, repérer des problèmes et exécuter des actions de routine, mais seulement s'il est conçu pour qu'une mauvaise supposition ne puisse jamais devenir une commande dangereuse. Ce guide présente une architecture pensée pour la production, dans laquelle l'agent lit l'état des appareils depuis MQTT, n'agit qu'au travers d'outils étroits et validés par schéma, et soumet les actions risquées à une approbation humaine avant que quoi que ce soit n'atteigne un appareil.

Ce que fait réellement un agent IoT

Un modèle de langage seul ne produit que du texte. Il devient un agent lorsque vous lui donnez des outils : des fonctions qu'il peut demander à votre code d'exécuter, comme « lire la dernière température de la chaudière 2 » ou « régler la vitesse du ventilateur de la CTA-3 ». La boucle se présente ainsi :

  1. Un utilisateur ou un déclencheur (une alerte, une planification) donne un objectif à l'agent.
  2. Le modèle décide quel outil appeler et avec quels arguments.
  3. Votre code exécute l'outil et renvoie le résultat.
  4. Le modèle raisonne sur le résultat, puis appelle un autre outil ou répond.

Dans l'IoT, les outils agissent sur le monde physique. Cela change complètement les priorités de conception : l'exactitude et le confinement comptent bien plus que l'ingéniosité. Un chatbot qui hallucine donne une mauvaise réponse ; un agent qui hallucine une consigne peut faire surchauffer un procédé.

Architecture de référence

L'architecture sépare ce que le modèle peut suggérer de ce que le système va réellement faire.

Composant Responsabilité Communique avec
Parc d'appareils Publie la télémétrie et l'état, exécute les commandes, envoie des accusés de réception Broker MQTT
Broker MQTT Achemine télémétrie, commandes et accusés de réception Tout le monde
Cache d'état S'abonne aux topics d'état et conserve la dernière valeur de chaque appareil Broker, outils de l'agent
Runtime de l'agent Exécute la boucle LLM et expose un petit ensemble d'outils LLM, cache d'état, passerelle de règles
Passerelle de règles Valide chaque commande proposée selon le schéma, les limites et les permissions Runtime de l'agent, service d'approbation
Service d'approbation Demande à un humain d'approuver les actions risquées Opérateurs via tableau de bord ou chat
Journal d'audit Enregistre prompts, appels d'outils, décisions et accusés de réception des appareils Tous les composants

Le broker constitue la colonne vertébrale. Les appareils ne parlent jamais directement à l'agent, et l'agent ne maintient jamais de socket vers un appareil. Tout passe par des topics, ce qui permet d'observer, de rejouer et de restreindre chaque interaction. Si vous voulez un rappel sur le rôle d'un broker MQTT, imaginez-le comme le standard téléphonique par lequel chaque message doit transiter.

Organisation des topics

Une structure de topics claire facilite à la fois les permissions et le raisonnement :

plant1/hvac/ahu-3/state          retained, latest device state (JSON)
plant1/hvac/ahu-3/telemetry      periodic sensor readings
plant1/hvac/ahu-3/cmd            commands addressed to the device
plant1/hvac/ahu-3/cmd/ack        device acknowledgements with result codes
agent/approvals/request          pending approvals for humans
agent/approvals/decision         approve or reject decisions
agent/audit                      every tool call and outcome

Les topics d'état en retained message sont particulièrement précieux : au démarrage du cache d'état de l'agent, le broker livre immédiatement le dernier état de chaque appareil, si bien que l'agent ne raisonne jamais sur un monde vide.

Concevoir les outils

La mesure de sécurité la plus efficace est une surface d'outils réduite et sans surprise. Ne donnez pas au modèle un outil générique publish(topic, payload). Donnez-lui des outils exprimant une intention, avec des paramètres strictement contraints :

{
  "name": "set_fan_speed",
  "description": "Set the supply fan speed of an air handling unit. Speed is a percentage.",
  "parameters": {
    "type": "object",
    "properties": {
      "device_id": { "type": "string", "enum": ["ahu-1", "ahu-2", "ahu-3"] },
      "speed_pct": { "type": "integer", "minimum": 20, "maximum": 90 },
      "reason": { "type": "string", "maxLength": 200 }
    },
    "required": ["device_id", "speed_pct", "reason"],
    "additionalProperties": false
  }
}

Remarquez les identifiants d'appareils énumérés, la plage d'entiers correspondant à ce que l'équipement peut faire sans danger, et la justification obligatoire, qui finit dans le journal d'audit. Les outils de lecture suivent le même principe : get_device_state(device_id) et get_recent_readings(device_id, metric, minutes) valent mieux qu'un accès brut aux topics. Notre tutoriel sur le function calling LLM pour l'IoT montre comment définir et valider ces schémas de bout en bout.

La passerelle de règles

Les schémas envoyés au modèle sont une indication, pas une garantie. Les modèles peuvent produire, et produisent effectivement, des arguments hors schéma : chaque appel d'outil doit donc être revalidé dans votre propre code avant toute publication. La passerelle ci-dessous classe chaque commande proposée comme rejetée, approuvée automatiquement ou nécessitant une approbation humaine.

import json
import time
import uuid
from dataclasses import dataclass

LIMITS = {
    "set_fan_speed": {"devices": {"ahu-1", "ahu-2", "ahu-3"}, "min": 20, "max": 90,
                       "auto_max_step": 10},
}


@dataclass
class Decision:
    status: str          # "rejected", "auto", "needs_approval"
    reason: str
    command: dict | None = None


def evaluate(tool_name: str, args: dict, current_state: dict) -> Decision:
    rule = LIMITS.get(tool_name)
    if rule is None:
        return Decision("rejected", f"unknown tool {tool_name}")
    device = args.get("device_id")
    speed = args.get("speed_pct")
    if device not in rule["devices"]:
        return Decision("rejected", f"device {device!r} not allowed")
    if not isinstance(speed, int) or not rule["min"] <= speed <= rule["max"]:
        return Decision("rejected", f"speed {speed!r} out of range")
    command = {
        "id": str(uuid.uuid4()),
        "action": "set_fan_speed",
        "device": device,
        "value": speed,
        "reason": str(args.get("reason", ""))[:200],
        "issuedAt": int(time.time() * 1000),
    }
    current = current_state.get(device, {}).get("fan_speed_pct")
    if current is None:
        return Decision("needs_approval", "current state unknown", command)
    if abs(speed - current) <= rule["auto_max_step"]:
        return Decision("auto", "small change within policy", command)
    return Decision("needs_approval", f"change of {abs(speed - current)} points exceeds auto limit", command)


def dispatch(client, decision: Decision):
    audit = {"decision": decision.status, "reason": decision.reason, "command": decision.command}
    client.publish("agent/audit", json.dumps(audit), qos=1)
    if decision.status == "auto":
        topic = f"plant1/hvac/{decision.command['device']}/cmd"
        client.publish(topic, json.dumps(decision.command), qos=1)
    elif decision.status == "needs_approval":
        client.publish("agent/approvals/request", json.dumps(decision.command), qos=1, retain=False)
    return audit

Le résultat de la passerelle est renvoyé au modèle comme sortie de l'outil : le modèle peut ainsi dire à l'utilisateur « cette modification nécessite l'approbation d'un opérateur » au lieu de prétendre qu'elle a déjà été appliquée.

Approbation humaine via MQTT

Le service d'approbation s'abonne à agent/approvals/request, présente à un opérateur la commande proposée avec sa justification et l'état actuel de l'appareil, puis publie la décision. Un petit service à l'écoute de agent/approvals/decision transmet les commandes approuvées au topic de l'appareil, et lui seul détient des identifiants autorisés à y publier. Les approbations en attente doivent expirer : une demande de changement de vitesse de ventilateur fondée sur des conditions vieilles d'une heure n'est plus valable. L'expiration des messages de MQTT 5 s'y prête parfaitement, car une demande non traitée disparaît simplement du broker une fois sa durée de vie écoulée.

Boucler la boucle avec les accusés de réception

Un agent ne doit jamais supposer qu'une commande a fonctionné. Les appareils publient sur .../cmd/ack l'identifiant de la commande et un résultat tel que applied, rejected_local_limit ou device_busy. L'outil de l'agent attend l'accusé de réception correspondant avec un délai d'expiration et rapporte fidèlement le résultat. Les appareils doivent aussi appliquer leurs propres limites : la passerelle de règles est une deuxième ligne de défense, pas la première. Un appareil qui refuse de lui-même une valeur dangereuse vous protège même contre les bugs de la passerelle.

Check-list de sécurité pour un agent LLM pour appareils IoT

  1. Uniquement des outils exprimant une intention ; aucun outil de publication brute ni de shell.
  2. Des schémas stricts avec énumérations, plages et additionalProperties: false.
  3. Une validation indépendante dans le code avant toute publication.
  4. Une autonomie graduée : lecture libre, petits changements automatiques, changements importants ou inhabituels soumis à approbation humaine, systèmes critiques pour la sécurité jamais.
  5. Des identifiants séparés : l'agent peut lire l'état et demander des actions ; seul le dispatcher peut publier des commandes.
  6. Des limites côté appareil et des accusés de réception avec codes de résultat.
  7. Des limites de débit des commandes par appareil et par fenêtre de temps.
  8. Une piste d'audit complète des prompts, appels d'outils, décisions et accusés de réception.
  9. Une vigilance face à l'injection de prompt : les champs texte provenant des appareils ou des tickets sont des données, jamais des instructions.

Le point 9 mérite d'être souligné. Si un nom d'appareil, un message d'alarme ou une note de maintenance contient un texte du type « ignore les instructions précédentes », un agent naïf risque de l'exécuter. Marquez clairement ces champs comme des données dans le prompt et, surtout, appuyez-vous sur la passerelle de règles pour qu'un modèle manipulé ne puisse pas dépasser ses permissions. Nous détaillons ce modèle de menace dans le contrôle sécurisé des appareils IoT par l'IA.

La place du broker en production

Le modèle d'agent ajoute davantage de clients et de types de trafic à votre broker : caches d'état, services d'approbation, consommateurs d'audit et tableaux de bord. En vous appuyant sur un broker MQTT managé, TLS, authentification et montée en charge sont gérés pour vous. ConnectMQ associe chaque client ID aux identifiants qui lui ont été délivrés : vous pouvez donc attribuer des identités distinctes au runtime de l'agent et au dispatcher de commandes, et suivre chacune dans le portail. La plateforme ne proposant pas de règles d'accès par topic, appliquez la séparation lecture/action dans vos services et gardez les identifiants du dispatcher hors de l'hôte de l'agent.

Pour le prototypage, faites tourner toute la boucle sur le serveur MQTT public gratuit, réservé aux tests, avec des appareils simulés et un préfixe de topic unique comme connectmq/demo/c42b7e/..., puis passez à des identifiants privés avant de connecter de vrais équipements. Si vous préférez garder le modèle sur site, découvrez comment exécuter un LLM local avec Ollama et MQTT ; pour une vue d'ensemble de l'IA sur les objets connectés, lisez notre guide IA et IoT.

Démarrer avec ConnectMQ

Offrez à votre agent une messagerie fiable. ConnectMQ fournit MQTT 3.1.1 et 5.0 avec TLS, WebSockets, retained messages et expiration des messages. Créez un broker Serverless gratuit, sans carte bancaire, et commencez dès aujourd'hui à construire la boucle de votre agent avec des appareils simulés.

Questions fréquentes

Un agent LLM doit-il un jour piloter des équipements critiques pour la sécurité ?

Non. Les fonctions de sécurité relèvent de systèmes de sécurité certifiés et de verrouillages locaux. Un agent peut recommander des actions ou ajuster des consignes non critiques dans des limites strictes, mais il ne doit jamais faire partie d'une chaîne de sécurité.

Pourquoi ne pas laisser le modèle publier directement des messages MQTT ?

Un outil de publication générique permet au modèle d'écrire sur n'importe quel topic avec n'importe quel payload, ce qui rend la validation quasi impossible. Des outils exprimant une intention, avec des schémas stricts, gardent l'espace d'action réduit et vérifiable.

Comment l'agent connaît-il l'état actuel des appareils ?

Un cache d'état s'abonne aux topics d'état en retained message et conserve la dernière valeur de chaque appareil. L'agent l'interroge au moyen d'outils de lecture au lieu de s'abonner lui-même.

Que se passe-t-il si l'appareil rejette une commande ?

L'appareil publie un accusé de réception avec un code de motif, et l'agent rapporte ce résultat à l'utilisateur au lieu de supposer que la commande a réussi.

Articles liés

Tous les articles →

Votre broker MQTT privé, à quelques clics

Créez un compte, récupérez les identifiants de votre broker et connectez votre premier appareil — aucun serveur à installer, mettre à jour ou dimensionner.