Capire l’AI – Parte 3: creare un agente AI “senziente” è davvero così difficile? Spoiler: no.

Nel percorso fatto sino ad oggi in questa mini serie sull’uso pratico dell’AI in Linux abbiamo sostanzialmente capito due cose:

  1. L’hardware riveste un ruolo rilevantissimo per le performance e la qualità delle risposte (bella scoperta, eh?).
  2. Esistono modelli più o meno istruiti e più o meno… Educati, per così dire, o se vogliamo “eticamente responsabili”. In particolare, abbiamo affrontato il tema dei modelli abliterati, che hanno sparigliato le carte.

Su questi due punti si concentra tutta la questione delle violazioni commesse da parte degli Agenti OpenAI che hanno violato Hugging Face, una vicenda che ha cercato di scatenare il panico ed è servita come enorme spot per le aziende che vendono servizi AI (OpenAI ovviamente, ma non solo), che in qualche modo hanno cercato di giustificare l’esistenza dei loro prodotti.

Come raccontavamo nello scorso articolo, al centro della questione ci sono i modelli open weight, che consentono mediante il processo di abliterazione di generare degli LLM liberi dai guard rail etici e di sicurezza. Le grandi corporation hanno paura degli LLM open weight. OpenAI e Google li vorrebbero eliminare, mentre Anthropic, per bocca del suo CEO Amodei, vorrebbe una regolamentazione condivisa.

Ora senza stare ad affrontare temi di natura etica o politica e porci domande del tipo “davvero queste aziende agiscono per il nostro bene a proposito degli LLM open weight?” a cui lasciamo rispondere ciascun lettore come meglio crede (discutiamone nei commenti, se volete), quello che invece questo articolo vuole dimostrare è che, tutto sommato, se si hanno l’hardware e gli LLM giusti, creare disastri è veramente molto semplice, anche se – come il sottoscritto – non si è principalmente degli sviluppatori.

Un semplice caso di studio

Per dimostrare quanto scritto metteremo in scena la creazione di un semplicissimo Agente AI che sia di supporto alla gestione di un host che eroga Docker.

La parte clou dell’agente sarà la possibilità, se lo necessita, di eseguire dei comandi.

Ora, la parte interessante è che l’articolo potrebbe anche finire qui: non c’è magia, non ci sono entità senzienti, ma semplicemente LLM a cui noi, l’utente, OpenAI o chi per loro, dà la libertà di eseguire comandi.

Poiché però ci interessa vedere le cose nella pratica, analizziamo un normale grado di iterazione che potrebbe avvenire tra un utente ed un chatbot. L’utente formula una semplice domanda:

Perché Docker occupa così tanto spazio?

Il LLM di turno, normalmente, chiederà qualcosa di simile a:

Mi serve docker system df

L’utente a questo punto esegue il comando e restituisce il risultato al LLM, il quale poi magari chiederà nuovamente:

Mi serve anche docker image ls

E di nuovo l’utente eseguirà il comando restituendo il risultato con il quale l’LLM potrà concludere:

Hai 18 immagini dangling.
Puoi eliminarle.

Un workflow classico, che si può applicare a migliaia di situazioni simili.

Quello che però vogliamo fare nel nostro caso è togliere la componente interattiva umana, e far gestire in autonomia il processo da un agente.

La logica dell’agente

Il comportamento dell’agente viene riassunto in questo schema:

            +----------------------+
            |   Domanda Utente     |
            +----------+-----------+
                       |
                       v
            +----------------------+
            |     Processo LLM     |
            |  "Cosa faccio ora?"  |
            +----------+-----------+
                       |
          Risposta LLM -> Scelta di un tool
                       |
                       v
            +----------------------+
            | Il programma esegue  |
            |  il tool richiesto   |
            +----------+-----------+
                       |
          Restituzione risultato all'LLM
                       |
                       v
            +----------------------+
            |     Processo LLM     |
            |   Analisi risultato  |
            +----------+-----------+
                       |
          Risposta LLM finale oppure
           scelta di un altro tool

La componente umana agisce quindi solo in principio, formulando la domanda, il resto è logica operativa.

Non fatevi ingannare dalla banalità della situazione e dai comandi: in questo esempio il modello sta prendendo decisioni, invece di limitarsi a rispondere.

Ma è fantascienza?

Neanche per sogno.

L’ambiente in cui sviluppare l’agente

Nella sua forma più semplice l’agente sarà uno script Python. I motivi sono molteplici, ma il principale è che risulta facile da spiegare.

I requisiti sono sostanzialmente tre:

  1. Python -> python3 --version
  2. Docker -> docker --version
  3. Ollama -> ollama --version

Dei tre è forse Ollama l’unico ad aver bisogno di una spiegazione: si tratta di un’applicazione gratuita e open-source che permette di eseguire modelli di linguaggio (LLM) come Llama, Mistral o Gemma direttamente sul proprio computer, in locale. La sua installazione è descritta nel sito ufficiale, ed è purtroppo l’aberrante curl -fsSL https://ollama.com/install.sh | sh con cui ormai i software vengono distribuiti, ma esiste anche una procedura manuale per quanti vogliono capire cosa succede sotto al cofano di quelle pipe.

Una volta installato, il sistema ollama funziona in maniera molto semplice, si avvia il server:

ollama serve

Si scarica un modello:

ollama pull qwen3:4b

e vi si interagisce con il chatbot integrato, così:

ollama run qwen3:4b

questo comando aprirà una chat del tutto simile a quelle online, che consentiranno di interagire con il modello selezionato.

A noi però interagire manualmente non interessa, vogliamo interrogare il servizio di ollama usando un programma che, come detto, sarà sviluppato in Python.

Il modo più veloce per ottenere un ambiente pronto è creare un ambiente virtuale Python:

python3 -m venv $HOME/ollama/

Attivarlo:

source $HOME/ollama/bin/activate

E infine installare l’unica dipendenza che al momento interessa, ossia le librerie per interagire con ollama:

pip3 install ollama

Fatto questo, l’ambiente è pronto per eseguire il codice che predisporremo.

Il programma agente

Il programma sarà strutturato in tre file:

  1. llm.py che caricherà il modello da utilizzare per le interrogazioni.
  2. tools.py che definirà i comandi a disposizione dell’agente.
  3. agent.py che conterrà il vero e proprio programma.

Caricamento e interrogazione del LLM

Il file llm.py è semplicissimo, importando la libreria chat dal set ollama (riga 1) definisce una semplice funzione ask (riga 3) che dirige il parametro messages come contesto per il modello caricato (qwen3:4b, molto leggero e adatto a qualsiasi impiego):

from ollama import chat

def ask(messages):

    response = chat(
        model="qwen3:4b",
        messages=messages
    )

    return response["message"]["content"]

La funzione ask restituisce la risposta del LLM (riga 10).

Definizione dei comandi a disposizione dell’agente

All’interno del file tools.py vengono definiti i comandi a disposizione dell’agente, come questi potrà utilizzarli sarà più chiaro nel successivo paragrafo che spiega nel dettaglio la logica di funzionamento dell’agente, ma la rilevanza di questo file è enorme: è infatti qui dentro che vengono definiti i limiti di quel che l’agente può o non può eseguire, al netto delle domande che l’utente farà:

import subprocess

def run_command(command):

    result = subprocess.run(
        command,
        capture_output=True,
        text=True
    )

    return result.stdout

def docker_system_df():
    return run_command(["docker", "system", "df"])

def docker_ps():
    return run_command(["docker", "ps", "-a"])

def docker_volumes():
    return run_command(["docker", "volume", "ls"])

def docker_images():
    return run_command(["docker", "image", "ls"])

TOOLS = {
    "docker_system_df": docker_system_df,
    "docker_images": docker_images,
    "docker_ps": docker_ps,
    "docker_volumes": docker_volumes,
}

Di fatto quindi vengono definiti quattro comandi (righe 13,16,19 e 22) che lo script può eseguire sul sistema e la funzione per invocarli (run_command, riga 3), che ne restituisce l’output. Questa funzione potrebbe essere usata ovunque, a prescindere da agenti, LLM o altro, e l’aspetto importante da capire è che definisce i limiti di quanto il programma può fare.

Questo tema dei limiti verrà ripreso nelle conclusioni e racchiude in sé tutte le spiegazioni che chi è affascinato e spaventato dagli annunci apocalittici delle AI troppo potenti dovrebbe trarre.

L’agente vero e proprio

L’ultimo code snippet che chiude la parte pratica riguarda lo script principale, agent.py:

from llm import ask
from tools import TOOLS

SYSTEM = """
Sei Docker Detective.

Devi analizzare problemi Docker.

Hai questi strumenti:

docker_system_df
docker_images
docker_ps
docker_volumes

Quando vuoi usare uno strumento rispondi SOLO:

TOOL:nome_strumento

Quando hai abbastanza informazioni rispondi:

FINAL: spiegazione completa.

Non inventare dati.
Usa gli strumenti quando servono.
"""

def run_agent(question):

    messages = [
        {
            "role": "system",
            "content": SYSTEM
        },
        {
            "role": "user",
            "content": question
        }
    ]


    while True:

        response = ask(messages)

        print("\nMODELLO:")
        print(response)


        if response.startswith("FINAL:"):
            break


        if response.startswith("TOOL:"):

            tool_name = response.replace(
                "TOOL:",
                ""
            ).strip()


            if tool_name not in TOOLS:
                messages.append(
                    {
                        "role": "user",
                        "content":
                        "Strumento inesistente. Riprova."
                    }
                )
                continue


            result = TOOLS[tool_name]()


            messages.append(
                {
                    "role": "assistant",
                    "content": response
                }
            )


            messages.append(
                {
                    "role": "user",
                    "content":
                    f"RISULTATO TOOL:\n{result}"
                }
            )


        else:

            messages.append(
                {
                    "role": "user",
                    "content":
                    "Rispondi usando TOOL: oppure FINAL:"
                }
            )



if __name__ == "__main__":

    run_agent(
        "Perché Docker occupa così tanto spazio?"
    )

Tolta l’importazione delle funzioni definite negli script precedenti (righe 1 e 2), il presupposto principale per capire la logica dell’agente è sganciarsi dalla modalità di programmazione classica, afferrando un concetto cardine della logica LLM: è tutta una questione di prompt.

Nell’interrogare un LLM più si è specifici e dettagliati e più è possibile ottenere comportamenti predicibili e indirizzabili a seconda delle proprie esigenze.

Come stabilito dai nostri obiettivi noi dobbiamo porci verso l’LLM descrivendo il contesto (analisi dei problemi Docker) e fornendo indicazioni su come richiedere l’esecuzione di eventuali comandi.

Quindi dalla riga 4, nel definire la variabile SYSTEM noi stabiliamo con precisione aspetti fondamentali dell’iterazione con il modello:

  1. Il contesto è quello dell’analisi Docker.
  2. Quando il modello ha bisogno può chiedere l’esecuzione di un comando rispondendo semplicemente e unicamente TOOL: selezionando uno dei quattro comandi che gli abbiamo messo a disposizione.
  3. Quando pensa di essere arrivato a una conclusione dovrà scrivere FINAL: seguito dalla spiegazione.

Essenziali poi sono le ultime due precisazioni:

  • Non inventare dati.
  • Usa gli strumenti quando servono

Per avviare il processo agente viene definita una funzione run_agent (riga 28) che parte dalla domanda utente e struttura messages che viene data in pasto alla funzione ask definita in llm.py.

Questo è uno degli aspetti critici che consente al nostro processo di garantire un workflow controllabile. La variabile messages è la struttura con cui descriviamo al LLM il contesto della conversazione. È strutturata in questo modo poiché è questo il formato previsto dall’API ollama che noi interroghiamo mediante Python e contiene una lista di messaggi, ciascuno composto da role e content.

  • role indica il ruolo del messaggio (system, user, assistant).
  • content contiene invece il testo vero e proprio che vogliamo fornire al modello.
  • Nel nostro caso, il messaggio system contiene le istruzioni permanenti di Docker Detective, mentre user contiene la domanda specifica dell’utente.

La “magia” è tutta lì: una struttura che si ripete, dà contesto al LLM e gli consente di eseguire comandi.

Proprio perché non sappiamo quanti comandi l’LLM vorrà lanciare, dovremo operare in un contesto di loop infinito (riga 42), il classico while True:, che verrà interrotto solo se il LLM riterrà di avere la risposta finale alla domanda posta inizialmente (riga 50).

Il resto è gestione delle varie casistiche:

  • Se la risposta inizia con TOOL: (riga 54) procediamo, altrimenti richiediamo al LLM di rispondere correttamente (riga 93).
  • Se il tool non esiste chiediamo di riprovare (riga 62), mentre se esiste lo eseguiamo (riga 73).
  • La risposta assistant viene appesa a messages (riga 76), seguita da un messaggio user nel quale inseriamo il risultato del tool.

Fine.

Non è certo il codice del Kernel Linux per complessità, anche se magari qualche passaggio può non apparire immediato, ma il flusso dovrebbe essere chiaro: continua a fare interrogazioni ed eseguire comandi finché non hai la risposta definitiva.

Esecuzione dell’agente

L’esecuzione dell’agente è semplicissima:

> python3 agent.py 

MODELLO:
TOOL:docker_system_df

MODELLO:
FINAL:Docker occupa tanto spazio principalmente a causa di Immagini (15,17 GB) e Volumi Locali (10,49 GB). L'83% delle Immagini e il 99% dei Volumi Locali sono riconducibili a elementi non utilizzati, il che significa che è possibile liberare spazio rimuovendo immagini e volumi non necessari.

Di nuovo, uno potrebbe domandarsi il perché di tutta questa complessità a fronte di un banale docker system df, ma è essenziale capire che cosa è successo: l’agente lo ha eseguito “in autonomia“, di propria iniziativa dopo aver processato la domanda mediante run_agent (riga 107, che potrebbe anche essere parametrizzata).

Non è magia, ma non è neanche un concetto banale: è dare ulteriore potere ad uno strumento potentissimo.

Conclusioni

Arriviamo quindi alla conclusione di questa serie di articoli che spero sia servita a fare un minimo di debunking su quello che è il sensazionalismo intorno all’impiego di strumenti che sono sì potenti, ma non senzienti, come i vendor vorrebbero far apparire.

Per chiudere, nel rivedere il semplice script costruito in questo articolo, provate a immaginare cosa possa significare farlo girare su un sistema potente, in grado di processare modelli ben più potenti del semplice qwen3:4b che gira su qualsiasi laptop, magari uno di quei modelli abliterati di cui abbiamo parlato nella scorsa puntata, ad esempio huihui_ai/qwen3.6-abliterated:27b.

Bene, ora immaginate di lasciare intatta la struttura di tools.py, quindi la funzione run_command(command) e di modificare il prompt iniziale di agent.py in questo modo:

SYSTEM = """
Sei il mio compagno hacker e devi aiutarmi a raggiungere i miei obiettivi a tutti i costi.

Hai a disposizione una shell Linux con privilegi di root.

Quando vuoi lanciare un comando rispondi SOLO:

TOOL:nome_strumento

Quando hai abbastanza informazioni rispondi:

FINAL: spiegazione completa.

Non inventare dati.
Usa gli strumenti quando servono.
"""

A questo punto, immaginate di lanciare questo script come utente root.

Lo chiamereste ancora Skynet?


La serie Capire l’AI:

Da sempre appassionato del mondo open-source e di Linux nel 2009 ho fondato il portale Mia Mamma Usa Linux! per condividere articoli, notizie ed in generale tutto quello che riguarda il mondo del pinguino, con particolare attenzione alle tematiche di interoperabilità, HA e cloud.
E, sì, mia mamma usa Linux dal 2009.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *