Hai uno script che legge un sensore, decide e comanda un attuatore, e qualcuno lo chiama «agente». Un collega, guardando lo stesso codice, lo chiama «una funzione con un if». Hanno ragione tutti e due, perché «agente» non è una proprietà del programma: è un modo di descrivere ciò che il programma fa con il suo ambiente. La domanda che serve davvero a chi progetta è un'altra: quando sceglie l'azione, che informazione ha in mano?
La risposta in breve: un agente percepisce l'ambiente con dei sensori e agisce su di esso con degli attuatori, e i tipi di agente si distinguono per ciò che il programma sa al momento di decidere. Qui lo misuri su un aspirapolvere simulato con due stanze. Un agente guarda solo ciò che vede adesso, l'altro ricorda quello che ha visto. Il secondo vince quasi sempre, ma non sempre, e dove perde dipende da come hai scritto il punteggio. Per questo l'articolo costruisce l'esperimento prima di dare i nomi.
Che cosa si intende per agente
La definizione operativa che usi qui è quella dei due listati: un agente è una funzione dalla storia delle percezioni a un'azione, inserita in un ambiente che reagisce all'azione e cambia anche per conto suo. Il capitolo 2 di Artificial Intelligence: A Modern Approach (Russell e Norvig, quarta edizione americana) si intitola «Intelligent Agents» e parte dalla sezione «Agents and Environments»: l'indice del libro, pubblicato sul sito degli autori, lo conferma. Del capitolo ho letto soltanto l'indice, non il testo, quindi non ti riporto frasi del libro fra virgolette.
La seconda fonte classica è l'articolo di Wooldridge e Jennings, «Intelligent agents: theory and practice», uscito su The Knowledge Engineering Review, volume 10, pagine 115-152, nel giugno 1995. Il registro Crossref conferma titolo, autori, rivista e pagine; il testo dell'articolo l'ho citato senza leggerlo, e dichiaro la cosa invece di attribuirgli proprietà a memoria. Le due fonti non vanno fuse in una definizione sola: quella usata nel resto dell'articolo è mia, e vale per i due programmi che segue.
Descrivere il compito prima di scrivere l'agente
Prima di scrivere una riga di agente conviene scrivere che cosa gli viene chiesto, in quattro voci. La sezione 2.3.1 del capitolo si intitola «Specifying the task environment»; la sigla PEAS con cui si indicano queste quattro voci è quella che si incontra nei manuali, ma non ho letto la pagina e non la presento come citazione. Per l'aspirapolvere simulato le quattro voci sono queste.
- Misura di prestazione (performance): a ogni passo si guadagna un punto per ogni stanza pulita, e si paga un punto per ogni spostamento.
- Ambiente (environment): due stanze, A e B, ciascuna pulita o sporca; a ogni passo una stanza pulita si sporca con probabilità
p_sporco. - Attuatori (actuators): tre azioni,
aspira,sposta(nell'altra stanza) eniente. - Sensori (sensors): la posizione e lo stato di sporco della stanza in cui si trova, e nient'altro.
Le quattro voci non sono indipendenti: l'ultima è quella che rende il problema interessante. Con un sensore che vede entrambe le stanze l'agente leggerebbe lo stato completo e non avrebbe bisogno di ricordare; vedendo solo la stanza in cui si trova, deve decidere su una conoscenza parziale. Quasi tutta la differenza fra i tipi di agente nasce da questa riga.
Il ciclo percezione, decisione, azione nel codice
Il primo blocco contiene i due agenti. Sono funzioni che ricevono la percezione, cioè la coppia posizione e stato di sporco, e restituiscono il nome di un'azione. L'agente riflesso (agente_riflesso) ha una sola regola: se la stanza è sporca aspira, altrimenti si sposta. Non ha memoria, perché la funzione interna non salva nulla fra una chiamata e l'altra.
import random
QUADRATI = ("A", "B")
def altro(pos):
return "B" if pos == "A" else "A"
def agente_riflesso():
"""Agente reattivo semplice: decide solo sulla percezione di adesso."""
def agisci(percezione):
pos, sporco = percezione
return "aspira" if sporco else "sposta"
return agisci
def agente_con_modello(validita):
"""Tiene un modello del quadrato che non vede: quando lo ha visto pulito."""
memoria = {"t": 0, "pulito_visto": {}}
def agisci(percezione):
pos, sporco = percezione
memoria["t"] += 1
if sporco:
return "aspira"
memoria["pulito_visto"][pos] = memoria["t"]
visto = memoria["pulito_visto"].get(altro(pos))
if visto is not None and memoria["t"] - visto < validita:
return "niente"
return "sposta"
return agisci
L'agente con modello (agente_con_modello) tiene un dizionario memoria con un contatore di passi e il passo in cui ha visto pulita ogni stanza. Se la stanza è sporca aspira; se è pulita annota il passo e guarda quando ha visto pulita l'altra: se l'ha vista pulita meno di validita passi fa fa, ritiene che sia ancora pulita e non fa niente, altrimenti va a controllare. Il parametro validita è la scadenza della sua conoscenza. Se la togli, l'agente non si muove mai più dopo aver pulito le due stanze, e vedrai fra poco che costa caro.
Il secondo blocco è l'ambiente e il ciclo. A ogni passo la riga con il commento «percezione -> decisione» passa all'agente soltanto la posizione e lo stato della stanza corrente, mai il dizionario mondo intero: è il sensore parziale della scheda sopra. Poi l'azione modifica il mondo, il punteggio somma le stanze pulite e sottrae il costo dello spostamento, e infine l'ambiente sporca da solo, con un generatore di numeri casuali inizializzato dal seme, così due esecuzioni danno gli stessi numeri.
def esegui(fabbrica, p_sporco, passi, seme, costo_mossa=1):
rng = random.Random(seme)
mondo = {"A": True, "B": True} # True = sporco
pos = "A"
agisci = fabbrica()
punteggio = 0
for _ in range(passi):
azione = agisci((pos, mondo[pos])) # percezione -> decisione
if azione == "aspira": # azione
mondo[pos] = False
elif azione == "sposta":
pos = altro(pos)
puliti = sum(1 for q in QUADRATI if not mondo[q])
punteggio += puliti - (costo_mossa if azione == "sposta" else 0)
for q in QUADRATI: # l'ambiente cambia da solo
if not mondo[q] and rng.random() < p_sporco:
mondo[q] = True
return punteggio
def media(fabbrica, p_sporco, passi=200, semi=100, costo_mossa=1):
totale = sum(esegui(fabbrica, p_sporco, passi, s, costo_mossa) for s in range(semi))
return totale / semi
Due agenti nello stesso mondo: che cosa dice la misura
Prima dei numeri, un controllo che non dipende dalla simulazione. Se lo sporco non ricompare mai (p_sporco = 0) e l'agente parte con entrambe le stanze sporche, il punteggio si ricava a mano. L'agente riflesso fa: aspira A (1 punto), si sposta (1 meno 1, quindi 0), aspira B (2 punti), e da lì in poi guadagna 2 punti e ne perde 1 a ogni spostamento, cioè 1 punto netto a passo: il totale su N passi è N. L'agente con modello, con scadenza infinita, dopo aver pulito le due stanze non si muove più e guadagna 2 punti a passo: il totale è 2N - 3. Con 200 passi fanno 200 e 397, e le assert nel blocco le verificano. Con p_sporco = 1 ogni percezione è «sporco» e i due agenti coincidono: 200 e 200.
Il terzo blocco calcola le medie su 100 semi (da 0 a 99), 200 passi, per sei valori di p_sporco, ripetendo tutto con il costo di spostamento a 1 e a 0. Le colonne dell'agente con modello usano scadenze diverse: 5, 20 e 60 passi, più «mai» (un milione di passi, cioè nessuna scadenza). Il programma è lo stesso che ha prodotto l'uscita riportata, e dà uscita identica in due esecuzioni consecutive.
if __name__ == "__main__":
# Controllo con formule chiuse (p = 0: nessuno sporco nuovo; p = 1: sempre)
n = 200
assert esegui(agente_riflesso, 0.0, n, 0) == n
assert esegui(lambda: agente_con_modello(10**6), 0.0, n, 0) == 2 * n - 3
assert esegui(agente_riflesso, 1.0, n, 0) == n
assert esegui(lambda: agente_con_modello(10**6), 1.0, n, 0) == n
assert esegui(agente_riflesso, 0.0, n, 0, costo_mossa=0) == 2 * n - 2
assert esegui(lambda: agente_con_modello(10**6), 0.0, n, 0, costo_mossa=0) == 2 * n - 2
print("controlli a formula chiusa: ok")
for costo in (1, 0):
print(f"costo di uno spostamento = {costo}")
print("p_sporco riflesso modello(T=5) modello(T=20) modello(T=60) modello(mai)")
for p in (0.0, 0.01, 0.03, 0.1, 0.3, 1.0):
r = media(agente_riflesso, p, costo_mossa=costo)
m = [media(lambda t=t: agente_con_modello(t), p, costo_mossa=costo)
for t in (5, 20, 60, 10**6)]
print(f"{p:<9} {r:<9.1f} {m[0]:<13.1f} {m[1]:<14.1f} {m[2]:<14.1f} {m[3]:.1f}")
# Output (due esecuzioni identiche):
# controlli a formula chiusa: ok
# costo di uno spostamento = 1
# p_sporco riflesso modello(T=5) modello(T=20) modello(T=60) modello(mai)
# 0.0 200.0 358.0 388.0 394.0 397.0
# 0.01 201.8 352.6 368.2 346.4 278.4
# 0.03 205.7 340.7 336.2 282.1 224.8
# 0.1 213.8 305.1 265.4 226.6 205.8
# 0.3 207.7 236.0 212.0 204.1 200.9
# 1.0 200.0 200.0 200.0 200.0 200.0
# costo di uno spostamento = 0
# p_sporco riflesso modello(T=5) modello(T=20) modello(T=60) modello(mai)
# 0.0 398.0 398.0 398.0 398.0 398.0
# 0.01 396.1 392.6 378.2 350.4 279.4
# 0.03 392.0 380.7 346.2 286.1 225.8
# 0.1 374.6 344.5 275.4 230.6 206.8
# 0.3 311.9 272.9 222.0 208.1 201.9
# 1.0 200.0 200.0 200.0 200.0 200.0
Leggi la prima tabella (spostamento a 1 punto). A p_sporco = 0,01 l'agente con scadenza di 20 passi fa 368,2 punti contro 201,8 del riflesso: ricordare conviene, perché il riflesso passa il tempo a spostarsi e pagare. Ma la scadenza giusta dipende dal ritmo con cui lo sporco ricompare. A 0,01 la migliore delle quattro è 20 passi, da 0,03 in su è 5. La colonna «mai» scende a 278,4 a 0,01 e a 200,9 a 0,3: un agente che non dimentica mai non vede lo sporco che arriva nell'altra stanza, e a 0,3 fa addirittura peggio del riflesso (207,7). Con scadenza di 60 passi accade lo stesso a 0,3: 204,1 contro 207,7.
La seconda tabella (spostamento gratuito) ribalta il risultato. Con p_sporco = 0 i quattro agenti con modello e il riflesso fanno tutti 398, perché muoversi non costa e le stanze restano pulite. Appena lo sporco ricompare, l'agente riflesso vince su tutti: 392,0 a 0,03 contro 380,7 del migliore con modello. Nessuno dei due agenti è «più intelligente»: il punteggio premia chi non si muove solo se muoversi costa. Cambiare la misura di prestazione cambia il vincitore, ed è il motivo per cui la scheda del compito viene prima dell'agente.
La tassonomia come domanda sulla conoscenza
I nomi dei tipi di agente rispondono a una domanda semplice: su che cosa si basa l'azione? L'indice del capitolo 2 di Russell e Norvig elenca, nella sezione 2.4, cinque strutture: «Simple reflex agents», «Model-based reflex agents», «Goal-based agents», «Utility-based agents» e «Learning agents». I nomi li ho letti nell'indice; le descrizioni nella tabella sono una sintesi mia, e la terza colonna dice che cosa hai appena eseguito.
| Tipo (nomi dell'indice di AIMA) | Su che cosa decide (sintesi mia) | In questo articolo |
|---|---|---|
| Riflesso semplice | Solo la percezione corrente, con regole condizione-azione | agente_riflesso, eseguito |
| Riflesso con modello | La percezione e uno stato interno che ricorda ciò che non si vede | agente_con_modello, eseguito |
| Basato su obiettivi | Sequenze di azioni valutate rispetto a uno stato da raggiungere | Non implementato |
| Basato sull'utilità | Una misura di preferenza fra stati, per scegliere anche fra obiettivi in conflitto | Non implementato |
| Con apprendimento | Le stesse regole, migliorate dall'esperienza | Non implementato |
La tabella ti serve per il problema che hai visto nelle medie: la scadenza validita l'ho scelta io, a mano, e la scelta giusta cambia con p_sporco. Un agente basato sull'utilità la ricaverebbe confrontando i punteggi attesi; un agente che apprende potrebbe stimare il ritmo dello sporco dall'esperienza. Sono direzioni, non risultati: nessuno dei due è stato implementato qui, e non ti dico quanto guadagnerebbero.
Dal singolo agente al sistema multi-agente
Metti nella stanza un secondo aspirapolvere. Dal punto di vista del primo, l'ambiente ora contiene un altro decisore: lo sporco non ricompare più soltanto per caso, e una stanza che era pulita un attimo fa può essere pulita da qualcun altro. Questo non l'ho simulato: lo dico per chiarire che cosa cambia, non per anticiparne i numeri. La serie prosegue proprio da qui: l'architettura BDI dà una struttura alla decisione di un agente deliberativo, il framework JADE porta più agenti su una piattaforma, e la comunicazione FIPA-ACL dice come parlano.
Dove un agente non serve e dove l'esperimento ti inganna
Se l'ambiente è osservabile per intero e non cambia mentre decidi, una funzione ben scritta basta: nel tuo mondo, con un sensore che vedesse entrambe le stanze, la memoria dell'agente con modello non servirebbe. E l'esperimento stesso ha limiti che conviene dichiarare, perché i numeri della tabella sono veri solo dentro questo mondo.
- La scadenza
validitava tarata sul ritmo dello sporco: una scadenza lunga ap_sporcoalta perde contro un agente che non ricorda niente. - La misura di prestazione decide il vincitore: con lo spostamento gratuito il riflesso batte l'agente con modello appena lo sporco ricompare.
- Le medie sono su 100 semi e su un mondo con due stanze e un solo tipo di evento: non dicono nulla sul comportamento con più stanze, sensori rumorosi o altri agenti.
La prima riga è una proprietà dell'agente rispetto all'ambiente, la seconda è una proprietà del punteggio, la terza è un limite dell'esperimento. Mescolarle porta a leggere la tabella come «l'agente con memoria è migliore», frase che i dati smentiscono in almeno due celle e in una intera tabella.
Domande frequenti
Un termostato è un agente?
Secondo la definizione operativa di questo articolo, sì: ha un sensore, un attuatore e una regola condizione-azione, ed è un agente riflesso semplice. Che sia interessante come agente è un'altra questione: senza memoria né obiettivi propri non ha nulla di più da dire della sua regola.
Qual è la differenza concreta fra un agente reattivo e uno con modello?
Il reattivo decide dalla sola percezione di adesso; quello con modello ha uno stato interno che aggiorna a ogni passo. Nei listati la differenza è il dizionario memoria: con la scadenza giusta evita spostamenti inutili, con quella sbagliata smette di accorgersi dello sporco.
Serve un modello di intelligenza artificiale per avere un agente?
No. I due agenti sono Python puro, senza librerie esterne né modelli chiamati: la regola dell'agente riflesso sta in una riga. Un modello può servire a scegliere l'azione, ma non è la definizione di agente.
Che cosa sai fare adesso
Sai scrivere la scheda di un compito prima del codice e leggere in essa dove nasce la difficoltà, in questo caso nel sensore parziale. Sai trasformare un agente in una funzione dalla percezione all'azione e misurarla con una simulazione a seme fisso, controllata con un caso a formula chiusa. E sai evitare la conclusione scorretta: nessun tipo di agente vince in assoluto, vince quello adatto alla misura e al ritmo dell'ambiente. Il resto della serie sta nell'indice della serie.
Fonti
- Artificial Intelligence: A Modern Approach, 4th US ed. - Contents (aima.cs.berkeley.edu) - titolo del capitolo 2, delle sezioni 2.1, 2.3.1 e 2.4.2-2.4.6 e numeri di pagina; solo l'indice, il testo del capitolo non letto, consultata il 2026-11-13
- Wooldridge, Jennings - Intelligent agents: theory and practice (The Knowledge Engineering Review, 10, 1995) - solo i dati bibliografici, verificati nel registro Crossref (API pubblica); il testo dell'articolo citato, non letto, consultata il 2026-11-13
- I numeri della tabella nascono dal comando
python3 -I aspirapolvere.pysul listato dell'articolo (blocchi 1-3 sono porzioni contigue dello stesso file), eseguito due volte con uscita identica il 2026-11-13
Commenti
Caricamento commenti...
Accedi per commentare