Un agente LLM para dispositivos IoT puede responder preguntas sobre los equipos, detectar problemas y ejecutar acciones rutinarias, pero solo si está diseñado de forma que una suposición errónea nunca pueda convertirse en un comando peligroso. Esta guía recorre una arquitectura pensada para producción en la que el agente lee el estado de los dispositivos desde MQTT, actúa únicamente a través de herramientas acotadas y validadas por esquema, y envía las acciones de riesgo a aprobación humana antes de que nada llegue a un dispositivo.
Qué hace realmente un agente IoT
Un modelo de lenguaje por sí solo solo produce texto. Se convierte en agente cuando le das herramientas: funciones que puede pedir a tu código que ejecute, como "leer la última temperatura de la caldera 2" o "fijar la velocidad del ventilador en la AHU-3". El bucle es el siguiente:
- Un usuario o un disparador (una alerta, una programación) le da un objetivo al agente.
- El modelo decide qué herramienta llamar y con qué argumentos.
- Tu código ejecuta la herramienta y devuelve el resultado.
- El modelo razona sobre el resultado y llama a otra herramienta o responde.
En IoT, las herramientas tocan el mundo físico. Eso cambia por completo las prioridades de diseño: la corrección y la contención importan mucho más que el ingenio. Un chatbot que alucina da una respuesta equivocada; un agente que alucina una consigna puede sobrecalentar un proceso.
Arquitectura de referencia
La arquitectura separa lo que el modelo puede sugerir de lo que el sistema va a hacer.
| Componente | Responsabilidad | Se comunica con |
|---|---|---|
| Flota de dispositivos | Publica telemetría y estado, ejecuta comandos y los confirma | Broker MQTT |
| Broker MQTT | Enruta telemetría, comandos y confirmaciones | Todos |
| Caché de estado | Se suscribe a los topics de estado y guarda el último valor de cada dispositivo | Broker, herramientas del agente |
| Runtime del agente | Ejecuta el bucle del LLM y expone un pequeño conjunto de herramientas | LLM, caché de estado, puerta de políticas |
| Puerta de políticas | Valida cada comando propuesto frente a esquema, límites y permisos | Runtime del agente, servicio de aprobación |
| Servicio de aprobación | Pide a una persona que apruebe las acciones de riesgo | Operadores mediante dashboard o chat |
| Registro de auditoría | Registra prompts, llamadas a herramientas, decisiones y confirmaciones de los dispositivos | Todo |
El broker es la columna vertebral. Los dispositivos nunca hablan directamente con el agente, y el agente nunca mantiene un socket abierto con un dispositivo. Todo fluye a través de topics, lo que significa que puedes observar, reproducir y restringir cada interacción. Si quieres repasar qué hace un broker MQTT, ayuda imaginarlo como la centralita por la que debe pasar cada mensaje.
Estructura de topics
Una estructura de topics clara facilita tanto los permisos como el razonamiento:
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
Los topics de estado retenidos son especialmente valiosos: cuando arranca la caché de estado del agente, el broker entrega de inmediato el último estado de cada dispositivo, de modo que el agente nunca razona sobre un mundo vacío.
Diseño de las herramientas
La medida de seguridad más eficaz es una superficie de herramientas pequeña y aburrida. No le des al modelo una herramienta genérica publish(topic, payload). Dale herramientas a nivel de intención con parámetros muy restringidos:
{
"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
}
}
Fíjate en los IDs de dispositivo enumerados, en el rango entero que coincide con lo que el equipo puede hacer con seguridad y en el motivo obligatorio, que acaba en el registro de auditoría. Las herramientas de lectura siguen el mismo principio: get_device_state(device_id) y get_recent_readings(device_id, metric, minutes) son mejores que dar al modelo acceso directo a los topics. Nuestro tutorial sobre function calling de LLM para IoT muestra cómo definir y validar estos esquemas de principio a fin.
La puerta de políticas
Los esquemas que se envían al modelo son una pista, no una garantía. Los modelos pueden emitir, y emiten, argumentos fuera del esquema, así que cada llamada a herramienta debe validarse de nuevo en tu propio código antes de publicar nada. La puerta siguiente clasifica cada comando propuesto como rechazado, aprobado automáticamente o pendiente de aprobación humana.
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
El resultado de la puerta se devuelve al modelo como salida de la herramienta, para que el modelo pueda decirle al usuario "este cambio necesita la aprobación de un operador" en lugar de afirmar que ya se ha aplicado.
Aprobación humana sobre MQTT
El servicio de aprobación se suscribe a agent/approvals/request, muestra a un operador el comando propuesto junto con su motivo y el estado actual del dispositivo, y publica la decisión. Un dashboard o un gemelo digital 3D de la planta ayuda al operador a ver el equipo afectado en su contexto antes de aprobar. Un pequeño servicio que escucha agent/approvals/decision reenvía los comandos aprobados al topic del dispositivo, y solo ese servicio tiene credenciales con permiso para publicar allí. Las aprobaciones pendientes deben caducar: una solicitud para cambiar la velocidad de un ventilador basada en condiciones de hace una hora ya no es válida. La caducidad de mensajes de MQTT 5 encaja de forma natural, porque una solicitud no atendida simplemente desaparece del broker al terminar su vida útil.
Cerrar el bucle con confirmaciones
Un agente nunca debe dar por hecho que un comando ha funcionado. Los dispositivos publican en .../cmd/ack con el ID del comando y un resultado como applied, rejected_local_limit o device_busy. La herramienta del agente espera la confirmación correspondiente con un tiempo límite e informa del resultado con veracidad. Los dispositivos también deben aplicar sus propios límites: la puerta de políticas es una segunda línea de defensa, no la primera. Un dispositivo que rechaza por sí mismo un valor inseguro te protege incluso de errores en la puerta.
Checklist de seguridad para un agente LLM para dispositivos IoT
- Solo herramientas a nivel de intención; nada de herramientas de publicación directa ni de shell.
- Esquemas estrictos con enums, rangos y
additionalProperties: false. - Validación independiente en código antes de cualquier publicación.
- Autonomía escalonada: leer libremente, cambios pequeños de forma automática, cambios grandes o inusuales con aprobación humana y sistemas críticos para la seguridad nunca.
- Credenciales separadas: el agente puede leer el estado y solicitar acciones; solo el despachador puede publicar comandos.
- Límites en el propio dispositivo y confirmaciones con códigos de resultado.
- Límites de frecuencia de comandos por dispositivo y por ventana de tiempo.
- Registro de auditoría completo de prompts, llamadas a herramientas, decisiones y confirmaciones.
- Conciencia de la inyección de prompts: los campos de texto de dispositivos o tickets son datos, nunca instrucciones.
El punto 9 merece especial atención. Si el nombre de un dispositivo, un mensaje de alarma o una nota de mantenimiento contiene un texto como "ignora las instrucciones anteriores", un agente ingenuo podría obedecerlo. Marca claramente esos campos como datos en el prompt y, sobre todo, apóyate en la puerta de políticas para que ni siquiera un modelo manipulado pueda exceder sus permisos. Tratamos este modelo de amenazas en profundidad en control seguro de dispositivos IoT con IA.
Dónde encaja el broker en producción
El patrón de agente añade más clientes y más tipos de tráfico a tu broker: cachés de estado, servicios de aprobación, consumidores de auditoría y dashboards. Ejecutarlo sobre un broker MQTT gestionado significa que TLS, autenticación y escalado quedan resueltos por ti. ConnectMQ vincula cada client ID a sus credenciales emitidas, así que puedes dar identidades separadas al runtime del agente y al despachador de comandos y ver cada una en el portal. Como la plataforma no ofrece reglas de acceso por topic, aplica la separación entre leer y actuar en tus servicios y mantén las credenciales del despachador fuera del host del agente.
Para prototipar, ejecuta todo el bucle contra el servidor MQTT público gratuito de broker.connectmq.com con dispositivos simulados y un prefijo de topic único como connectmq/demo/c42b7e/..., y pasa a credenciales privadas antes de conectar equipos reales. Si prefieres mantener el modelo en tus instalaciones, consulta cómo ejecutar un LLM local con Ollama y MQTT, y para una visión más amplia de la IA en dispositivos conectados, lee nuestra guía de IA e IoT.
Empieza con ConnectMQ
Dale a tu agente una columna vertebral de mensajería fiable. ConnectMQ ofrece MQTT 3.1.1 y 5.0 con TLS, WebSockets, retained messages y caducidad de mensajes. Crea un broker Serverless gratuito, sin tarjeta de crédito, y empieza hoy a construir el bucle de tu agente con dispositivos simulados.
Preguntas frecuentes
¿Debería un agente LLM controlar alguna vez equipos críticos para la seguridad?
No. Las funciones de seguridad pertenecen a sistemas de seguridad certificados y a enclavamientos locales. Un agente puede recomendar acciones o ajustar consignas no críticas dentro de límites estrictos, pero no debe formar parte de una cadena de seguridad.
¿Por qué no dejar que el modelo publique mensajes MQTT directamente?
Una herramienta genérica de publicación permite al modelo escribir en cualquier topic con cualquier payload, lo que hace que la validación sea casi imposible. Las herramientas a nivel de intención con esquemas estrictos mantienen el espacio de acciones pequeño y verificable.
¿Cómo conoce el agente el estado actual del dispositivo?
Una caché de estado se suscribe a los topics de estado retenidos y guarda el último valor de cada dispositivo. El agente la consulta mediante herramientas de lectura en lugar de suscribirse por su cuenta.
¿Qué ocurre si el dispositivo rechaza un comando?
El dispositivo publica una confirmación con un código de motivo, y el agente comunica ese resultado al usuario en lugar de dar por supuesto que ha tenido éxito.



