Agentes e Sistemas Multiagente | Mestrado em Inteligência Artificial | Universidade do Minho | 2025/26
Sistema multiagente distribuído para gestão inteligente de bibliotecas e salas de estudo universitárias, implementado com SPADE sobre XMPP, com comunicação FIPA-ACL e negociação via protocolo Contract Net.
O projeto divide-se em duas fases:
- Trabalho de Investigação — revisão sistemática (PRISMA) e proposta de arquitetura multiagente (artigo LNCS)
- Trabalho Prático — implementação do SMA em SPADE com simulação de salas, sensores virtuais e dashboard em tempo real
- Monitorizar condições ambientais (temperatura, humidade, CO₂, ruído, iluminância) com sensores virtuais
- Gerir a ocupação com granularidade ao nível da mesa e cadeira, com indicadores LED (🟢🟡🔴)
- Redistribuir estudantes automaticamente via FIPA Contract Net quando uma sala atinge lotação
- Recomendar espaços de forma personalizada com base em preferências declaradas pelo utilizador
- Integrar pesquisa de livros com disponibilidade de lugares em múltiplas bibliotecas
┌──────────────────── Coordenação Global ─────────────────────┐
│ Ag. Coordenador Global │
└──────────────────────────┬──────────────────────────────────┘
FIPA-ACL
┌──────────────── Coordenação Local (por biblioteca) ─────────┐
│ Ag. Coordenador ←→ Ag. Biblioteca ←→ Ag. Interface │
└───────┬──────────────────────────────────────────┬──────────┘
│ │
┌───────┴───── Execução (por sala/piso) ──────┐ │ REST/HTTP
│ Ag. Sala ←→ Ag. Conforto ←→ Ag. Energia │ │
└─────────────────────────────────────────────┘ │
↑ MQTT (simulado) ↓
Sensores IoT (virtuais) Dashboard Web
Ag. Utilizador
| Agente | Arquivo | Arquitetura | Função |
|---|---|---|---|
| RoomAgent | room_agent.py |
BDI (FAA) | Gere ocupação granular, responde a CFP, pede redistribuição |
| ComfortAgent | comfort_agent.py |
Reativa | Monitoriza sensores, emite alertas condição-ação |
| CoordinatorAgent | coordinator_agent.py |
Dispatcher–worker | Orquestra Contract Net para redistribuição entre salas |
| GlobalCoordinatorAgent | global_coordinator_agent.py |
Dispatcher–worker | Medeia negociação multicampus entre coordenadores locais |
| EnergyAgent | energy_agent.py |
Utilidade | Otimiza U = we·E + wc·(1−C) + wq·(1−Q) |
| UserAgent | user_agent.py |
BDI | Preferências declarativas, recomendação proativa |
| LibraryAgent | library_agent.py |
Procedimental | Pesquisa de livros, participação em negociação multicampus |
| InterfaceAgent | interface_agent.py |
Reativa | Agrega estado, REST API, dashboard web |
| Protocolo | Performativas | Cenário |
|---|---|---|
| Contract Net | CFP → PROPOSE / REFUSE → ACCEPT-PROPOSAL / REJECT-PROPOSAL |
Redistribuição de estudantes |
| Request | REQUEST → INFORM |
Consulta de estado, pesquisa de livros |
| Inform | INFORM |
Alertas de conforto, atualizações de sensores |
Todas as mensagens transportam objectos serializados em JSON com to_json() / from_json():
| Classe | Utilização |
|---|---|
ComfortAlert |
Alerta de parâmetro fora de limiar |
RedistributionRequest |
Pedido de redistribuição (sala lotada) |
RoomProposal |
Proposta de acolhimento (Contract Net) |
RoomState |
Estado completo de uma sala |
BookSearchRequest |
Pesquisa de livro |
SpaceRecommendation |
Recomendação de espaço ao utilizador |
EnergyAdjustment |
Sugestão de ajuste energético |
UserPreferences |
Perfil declarado pelo estudante |
RoomAgent (lotado) ──REQUEST──→ CoordinatorAgent
CoordinatorAgent ──CFP──────→ RoomAgent A, B, C...
RoomAgent A ──PROPOSE──→ CoordinatorAgent
RoomAgent B ──REFUSE───→ CoordinatorAgent
CoordinatorAgent ──ACCEPT───→ RoomAgent A (melhor utilidade)
CoordinatorAgent ──REJECT───→ RoomAgent C
ComfortAgent ──INFORM (ComfortAlert)──→ RoomAgent
RoomAgent ──REQUEST──→ EnergyAgent (ajuste HVAC)
RoomAgent ──INFORM──→ InterfaceAgent (alerta visual)
UserAgent ──REQUEST (BookSearchRequest)──→ LibraryAgent
LibraryAgent ──INFORM (resultados)──────────→ UserAgent
UserAgent avalia salas conhecidas → SpaceRecommendation
O dashboard web mostra em tempo real:
- Estatísticas globais — bibliotecas, salas, lugares livres/ocupados
- Cards por sala — indicador LED (verde/amarelo/vermelho), sensores, ocupação
- Detalhe de sala — clique para ver sensores ambientais e dados de ocupação
- Eventos — redistribuições (Contract Net), alertas de conforto
Atualização automática a cada 3 segundos via polling HTTP.
ASM/
├── main.py # Entry point — instancia campus e agentes
├── register_agents.py # Regista contas XMPP em massa no Prosody
├── config.py # Configuração (XMPP, campus, limiares, MQTT)
├── env.yml # Ambiente Conda
├── pyproject.toml # Config pytest, coverage e ruff
├── docker-compose.yml # Prosody + Mosquitto + Prometheus + Grafana
├── prosody.cfg.lua # Configuração Prosody (XMPP)
├── mosquitto.conf # Configuração Mosquitto (MQTT)
├── setup_xmpp.sh # Setup Prosody nativo (alternativa ao Docker)
│
├── core/ # Ontologia e protocolos
│ ├── ontology.py # SmartStudyRoom-Ontology (objectos serializáveis)
│ ├── fipa.py # Helpers FIPA-ACL para SPADE
│ ├── metrics.py # Instrumentação Prometheus
│ └── logging_config.py # Logging estruturado (structlog)
│
├── agents/ # Agentes SPADE
│ ├── room_agent.py # Agente Sala (BDI real via spade_bdi)
│ ├── comfort_agent.py # Agente Conforto (Reativo + MQTT)
│ ├── coordinator_agent.py # Agente Coordenador (BDI + Contract Net)
│ ├── global_coordinator_agent.py # Coordenador Global (escalação multicampus)
│ ├── energy_agent.py # Agente Energia (Utilidade multiobjetivo)
│ ├── user_agent.py # Agente Utilizador (BDI + preferências)
│ ├── library_agent.py # Agente Biblioteca (pesquisa livros)
│ ├── interface_agent.py # Agente Interface (REST + /metrics + Dashboard)
│ └── bdi/ # Programas AgentSpeak (.asl) para BDI real
│ ├── room_agent.asl
│ └── user_agent.asl
│
├── simulation/ # Simulação de sensores virtuais
│ ├── __init__.py # SensorSimulator + OccupancySimulator
│ └── mqtt_bus.py # Publisher/Subscriber MQTT (paho-mqtt)
│
├── scenarios/ # 13 cenários de validação reproduzíveis
│ ├── live.py # Cenários "vivos" (injeção no sistema em execução)
│ ├── exam_week.py # Semana de exames (saturação global)
│ ├── sensor_failure.py # Sensor degradado → alertas de conforto
│ ├── massive_negotiation.py # Contract Net massivo (throughput)
│ ├── multi_campus_cfp.py # Escalação multicampus via GlobalCoord.
│ └── … # + comfort_cascade, peak_hour, library_rush,
│ # user_recommendations, book_search_burst, etc.
│
├── tests/ # 223 testes pytest (unit + integração)
│ ├── test_ontology.py, test_fipa.py, test_config.py, …
│ └── test_contract_net.py implícito em test_coordinator_async.py
│
├── web/ # Dashboard (HTML/CSS/JS)
│ ├── dashboard.html
│ ├── dashboard.css
│ └── dashboard.js
│
├── prometheus/prometheus.yml # Scrape config (alvo host.docker.internal:9100)
├── grafana/
│ ├── provisioning/ # Datasource Prometheus + provider de dashboards
│ └── dashboards/smartstudyroom.json # Dashboard pré-configurado
│
├── .github/workflows/ci.yml # CI: ruff + pytest + coverage (fail < 60%)
└── docs/ # Documentação
├── paper.pdf # Artigo LNCS (Trabalho de Investigação)
└── report.pdf # Relatório (Trabalho Prático)
- Python 3.11+ (via Anaconda/Miniconda)
- Docker (recomendado — lança Prosody + Mosquitto + Prometheus + Grafana) ou Prosody + Mosquitto instalados localmente
-
Clonar o repositório:
git clone https://github.com/pedroreis2468/ASM.git cd SmartStudyRoom -
Criar e ativar o ambiente:
conda env create -f env.yml conda activate ASM
-
Arrancar os serviços (XMPP + MQTT + Prometheus + Grafana):
Opção A — Docker (recomendado):
docker-compose up -d
Traz 4 serviços:
- Prosody (XMPP)
localhost:5222 - Mosquitto (MQTT)
localhost:1883 - Prometheus http://localhost:9090
- Grafana http://localhost:3000 (admin/admin; dashboard pré-carregado)
Opção B — Prosody nativo:
sudo bash setup_xmpp.sh
- Prosody (XMPP)
-
Registar contas XMPP dos agentes (primeira execução apenas):
python register_agents.py
-
Executar o SMA:
python main.py
-
Abrir:
- Dashboard SMA: http://localhost:8080
- Métricas Prometheus: http://localhost:8080/metrics (ou
:9100) - Grafana: http://localhost:3000
Ctrl+C no terminal (para os agentes)
docker-compose down (para os serviços)Editar config.py ou definir variáveis de ambiente:
| Parâmetro | Descrição | Default |
|---|---|---|
XMPP_SERVER |
Servidor XMPP | localhost |
XMPP_PASSWORD |
Password partilhada dos agentes | password |
CAMPUS_CONFIG |
Bibliotecas, salas, mesas | 2 bibliotecas, 6 salas, 22 mesas, 104 lugares |
COMFORT_THRESHOLDS |
Limiares de conforto | temp 18–26 °C, CO₂ < 1000 ppm, ruído < 60 dB |
SENSOR_UPDATE_INTERVAL |
Intervalo de leitura de sensores | 5 s |
OCCUPANCY_CHANGE_INTERVAL |
Intervalo de simulação de ocupação | 8 s |
DASHBOARD_PORT |
Porta do dashboard + /metrics |
8080 |
HTTP_BIND |
Interface do dashboard (0.0.0.0 para expor) |
127.0.0.1 |
MQTT_ENABLED / MQTT_BROKER_HOST |
Ativar sensores via broker MQTT | true / localhost |
PROMETHEUS_PORT |
Porta do exportador Prometheus dedicado | 9100 |
SIM_SEED |
Seed determinística para reprodutibilidade | (unset) |
CONTRACT_NET_WEIGHTS |
Pesos availability / comfort / proximity do CN |
0.4 / 0.3 / 0.3 |
ENERGY_UTILITY_WEIGHTS |
Pesos we / wc / wq da função U do EnergyAgent |
0.3 / 0.4 / 0.3 |
Suite de 223 testes (unit + assíncronos) com configuração em pyproject.toml:
# Correr todos os testes
python -m pytest tests/ -v
# Coverage com relatório no terminal
python -m coverage run -m pytest tests/
python -m coverage report
# Coverage HTML interativo
python -m coverage html && open htmlcov/index.htmlCobertura atual:
| Módulo | Cobertura |
|---|---|
core/ontology.py |
99% |
core/metrics.py |
100% |
core/fipa.py |
95% |
config.py |
100% |
agents/library_agent.py |
94% |
agents/energy_agent.py |
81% |
| Total | 60% |
Cobertura dos agentes é propositadamente mais baixa porque a camada XMPP/SPADE é mockada em testes puros; os comportamentos assíncronos têm testes dedicados em test_*_async.py.
Todas as métricas relevantes do SMA são exportadas em formato Prometheus, disponíveis em duas URLs:
- Exportador dedicado: http://localhost:9100/metrics
- Mirror no InterfaceAgent: http://localhost:8080/metrics
| Métrica | Tipo | Labels | Significado |
|---|---|---|---|
asm_cfp_sent_total |
Counter | library, scope | CFPs emitidas |
asm_proposals_received_total |
Counter | library, scope | PROPOSEs recebidos |
asm_refusals_received_total |
Counter | library, scope | REFUSEs recebidos |
asm_redistributions_total |
Counter | library, scope | Redistribuições concluídas |
asm_escalations_total |
Counter | library | Escalações ao Coord. Global |
asm_comfort_alerts_total |
Counter | room, parameter | Alertas de conforto |
asm_energy_adjustments_total |
Counter | library, action | Ajustes energéticos aplicados |
asm_recommendations_total |
Counter | user | Recomendações emitidas |
asm_room_occupancy_ratio |
Gauge | room, library | Ocupação por sala ∈ [0,1] |
asm_room_led_status |
Gauge | room, library | 0=vermelho, 1=amarelo, 2=verde |
asm_active_negotiations |
Gauge | library | Negociações CN em curso |
asm_cfp_round_seconds |
Histogram | library, scope | Latência de uma ronda CN |
asm_redistribution_utility |
Histogram | library, scope | Utilidade da proposta vencedora |
asm_energy_utility |
Histogram | library | Função U do EnergyAgent |
O Grafana (localhost:3000) vem com o dashboard SmartStudyRoom pré-provisionado em grafana/dashboards/smartstudyroom.json, com painéis para negociações ativas, totais de CFPs/redistribuições, estado LED por sala e rácio de ocupação.
Cenários reproduzíveis (sem XMPP/MQTT — exercitam as funções de utilidade e regras de decisão puras):
python -m scenarios.exam_week # Semana de exames (todas as salas ≥90%)
python -m scenarios.sensor_failure # Sensores degradados + alertas de conforto
python -m scenarios.massive_negotiation # Contract Net massivo (throughput)
python -m scenarios.multi_campus_cfp # Escalação multicampus BG → BACada cenário imprime um relatório no terminal mostrando candidatas, utilidade calculada, sala vencedora e (quando aplicável) ações HVAC resolvidas.
Com o sistema a correr (python main.py), é possível disparar cenários no sistema vivo e vê-los acontecer no dashboard — útil para demonstrações. A injeção mexe nos objetos Room partilhados pelos agentes, pelo que o RoomAgent/ComfortAgent reagem de imediato (Contract Net, alertas, escalamento). Funciona com o Modo Demo desligado (estado estável, sem ruído aleatório a sobrepor a injeção).
O catálogo ao vivo (definido em scenarios/live.py) cobre todos os cenários determinísticos, agrupados por categoria no dropdown do dashboard: Redistribuição (saturate, exam_week, massive_negotiation…), Conforto/Energia (comfort_spike, sensor_failure, comfort_cascade…), Multicampus (multicampus, multi_campus_cfp, unbalanced_campuses), Recomendação (recommendation_conflict, user_recommendations, peak_hour), Livros (book_search_burst, book_no_match), Combinado (library_rush, perfect_storm) e Reset.
Três formas de disparar:
# 1) CLI
python toggle_demo.py scenarios # lista os cenários ao vivo
python toggle_demo.py scenario saturate # satura uma sala -> redistribuição
python toggle_demo.py scenario multicampus # satura uma biblioteca -> escala ao global
python toggle_demo.py scenario comfort_spike # picos de CO2/temp/ruído -> alertas
python toggle_demo.py scenario book_search_burst # pesquisa de livros multi-biblioteca
python toggle_demo.py scenario reset # repõe as salas (re-arma os cenários)
# 2) REST
curl "http://localhost:8080/api/demo/scenario?name=saturate"
# 3) Dashboard: dropdown "Cenário" + botão "Correr" (junto ao Modo Demo)Nota (
comfort_spike): os alertas de conforto disparam de forma fiável no modo fallback (sem broker MQTT, em que oComfortAgentreavalia o ambiente em cada ciclo). Com um broker MQTT ativo a avaliação é orientada por eventos, pelo que ocomfort_spikeao vivo é mais fiável sem o broker. Os cenários de ocupação (saturate,multicampus) disparam o Contract Net em qualquer modo. Dica: subir a velocidade (ex.: 5x) acelera a reação.
| Nome | Nº | |
|---|---|---|
| Luís Miguel Pereira Silva | PG60390 | pg60390@alunos.uminho.pt |
| Pedro Miguel S. A. Urbano dos Reis | PG59908 | pg59908@alunos.uminho.pt |
| Guilherme Lobo Pinto | PG60225 | pg60225@alunos.uminho.pt |
| Pedro Alexandre Silva Gomes | PG60289 | pg60289@alunos.uminho.pt |
Este trabalho é de cariz estritamente académico. Universidade do Minho, Escola de Engenharia, Departamento de Informática.