Come creare un agente AI per interrogare un database MySQL con PHP e Neuron AI

Come interrogare un database in linguaggio naturale usando PHP e il framework Neuron AI. Un progetto passo-passo per costruire un data analyst virtuale capace di esplorare i dati di un CRM/ERP senza scrivere query manuali.

Luglio 11, 2025

Introduzione

Quando si vuole interrogare una base dati complessa è spesso necessario rivolgersi ad un data analyst, cioè una figura tecnica che conosca almeno in parte l’applicativo da esplorare e sappia interpretare le relazioni tra le diverse entità del DB.

Le informazioni all’interno di un database sono strutturate in tabelle tra di loro interconnesse e il DB stesso contiene al suo interno le informazioni che legano le tabelle tra di loro, o per lo meno le lascia intuire tramite i nomi dei campi e delle tabelle, sta poi alle capacità del data analyst estrarre i dati facendo le query con il linguaggio SQL e fornendo così le informazioni a chi le richiede.

Supponiamo di avere un database che racchiude le informazioni di un timesheet, lo strumento utilizzato dagli impiegati dell’ufficio per inserire i dati viene utilizzato quotidianamente e contiene le presenze dei lavoratori e le ore effettuate sui vari progetti dei clienti dell’azienda, il costo orario del personale, etc. etc.. Lo strumento, qualunque esso sia, sicuramente ha una sezione per la reportistica dei dati, ma è possibile per un data analyst collegarsi direttamente al database ed effettuare delle estrazioni di dati particolari, per esempio selezionando con filtri non previsti dal sistema o effettuando estrazioni concatenate, in sequenza, in un processo più articolato.

Ad esempio:

  • quante ore di lavoro vengono fatte nei diversi giorni della settimana?
  • chi ha lavorato di più sui clienti X e Y?
  • quali sono i progetti che sono costati di più?
  • quanti progetti hanno superato il budget?
  • chi ha lavorato su un solo progetto nel mese di aprile?

Tutte queste domande hanno le loro risposte nei dati del database e certe volte il software CRM/ERP a disposizione non ha la risposta pronta nella sua sezione di report. Per questo a volte i software hanno strumenti di estrazione dati, che poi qualcun altro apre in Excel, magari fa una tabella pivot e analizza.

Le domande qua sopra e molte altre possono essere chieste ad un Agente AI, cioè un sistema software basato sui modelli LLM di intelligenza artificiale e istruito per fare il data analyst. In quest’articolo proviamo a costruirlo.

data analayst agent in PHP
Stupida immagine generata dall’intelligenza artificiale per corredare quest’articolo. Dovrebbe essere un’agente AI che fa il data analyst, così l’ha immaginato ChatGPT.

Un agente data analyst in PHP

La maggior parte del software in ambito AI è realizzato con Python, Node.js, React… tecnologie che molti sviluppatori NPG (=Non Più Giovani) conoscono poco rispetto al più classico PHP e rappresentano quindi un ostacolo per lo sviluppo in ambito AI. Personalmente questa mancanza di strumenti mi ha costretto ad utilizzare le LLM con un approccio un po’ rozzo, cioè partendo da zero e reinventando la ruota, tecnica che sicuramente è pesante dal punto di vista delle ore di lavoro, ma che permette di comprendere la tecnologia in profondità. Per usare ChatGPT in un progetto, per esempio, si devono implementare delle chiamate curl alle API di Open AI, costruendo i prompt da PHP e mantenendo localmente una memoria per conversare con la AI dal proprio software. In questo modo posso salvare dati parziali nel mio db, o posso recuperare informazioni da passare all’AI prendendole dal db del progetto (RAG).

Recentemente, cercando di approfondire l’uso degli MCP, ho scoperto il framework Neuron AI che semplifica lo sviluppo di agenti AI: astrae dal modello (è più facile passere ad esempio da Open AI ad Anthropic o a Ollama, etc.) e offre dei tool pronti che possiamo utilizzare con l’agente per fare cose, per esempio, su un database o su un altro sistema.

Un Agente è un software che usa i modelli LLM e con una certa autonomia prende decisioni e fa cose. Può essere un bot che va in giro per il web a cercare i migliori voli, oppure un tool che automatizza l’agenda degli appuntamenti, oppure – come raccontato – un data analyst che cerca i dati nel database.

Installare le librerie e primi passi

Per prima cosa bisogna creare un progetto e installare il framework di Neuron:

> mkdir test-neuron
> cd test-neuron
> composer require inspector-apm/neuron-ai

Dopodiché ho creato un nuovo progetto su Open ai (ho un account ChatGPT Plus, forse è necessario), ho quindi prelevato la API key e ho scritto questo file hi.php nella cartella per testare il funzionamento base di Neuron:

<?php
namespace App\Neuron;

include (__DIR__ . '/vendor/autoload.php');

use NeuronAI\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\OpenAI\OpenAI;

class MyAgent extends Agent
{
    public function provider(): AIProviderInterface
    {
        return new OpenAI(
            key: 'LAMIACHIAVEDIOPENAI',
            model: 'o4-mini',
        );
    }
}

$response = MyAgent::make()->chat(new UserMessage("Ciao come va!"));
echo $response->getContent();
?>

Questo semplice script non ha alcuna interfaccia, potete metterlo su un server e chiamarlo via browser, oppure potete lanciarlo localmente da riga di comando:

> php hi.php
Ciao! Tutto bene, grazie. E tu come stai? Posso aiutarti in qualcosa oggi?

Se non funziona al primo colpo potreste avere qualche problema con il PHP, nella mia installazione infatti da linea di comando veniva utilizzato un diverso file php.ini di configurazione, in cui mancava il certificato ssl per la connessione. Il software, infatti, viene eseguito in locale ma utilizza ChatGPT, quindi invia i dati a Open AI e la connessione viene criptata con certificato SSL. Ho quindi dovuto attivare il certificato nel php.ini (ho dovuto scommentare togliendo il ; all’inizio delle due righe curl.cainfo e openssl.cafile):

[curl]
; A default value for the CURLOPT_CAINFO option. This is required to be an
; absolute path.
curl.cainfo="D:\xampp\apache\bin\curl-ca-bundle.crt"

[openssl]
; The location of a Certificate Authority (CA) file on the local filesystem
; to use when verifying the identity of SSL/TLS peers. Most users should
; not specify a value for this directive as PHP will attempt to use the
; OS-managed cert stores in its absence. If specified, this value may still
; be overridden on a per-stream basis via the "cafile" SSL stream context
; option.
openssl.cafile="D:\xampp\apache\bin\curl-ca-bundle.crt"

Se non hai il certificato lo puoi scaricare da curl.se/cacert.pem e copiarlo in una cartella che poi indichi nel php.ini.

Interfacciamento al database

La suite di Neuron ha un “MySQL toolkit” che viene utilizzato per estrarre le informazioni sulla struttura del database e usarle nei prompt dell’agente. Si chiamano “Tool” gli strumenti che forniamo al modello LLM che stiamo utilizzando per interagire con il mondo. La descrizione testuale del tool permette al modello di capire che funzione chiamare della nostra classe per utilizzare quello strumento quando gli serve per raggiungere uno scopo.

Ho creato un nuovo file mysql.php e un altro file config.php.

Lo script mysql.php implementa una classe che ho chiamato DataAnalystAgent che estende la classe Agents di Neuron. La classe Agents permette l’utilizzo di alcuni tool dedicati e specializzati nell’interazione con il db. In particolare ci sono MySQLSchemaTool che consente l’estrazione in forma testuale delle regole del database, della struttura delle tabelle, dei tipi dei campi, degli indici e delle relazioni. Poi c’è MySQLSelectTool che consente l’esecuzione di query select e poi c’è MySQLWriteTool che consente le operazioni più delicate di scrittura e cancellazione sul db.

In quest’articolo l’agente è limitato ad utilizzare MySQLSchemaTool e MySQLSelectTool.

Nel file config ho inserito sia le chiavi e gli accessi al database, che una classe chiamata LoggedPDO che logga ogni query appena viene eseguita. In questo modo possiamo vedere che operazioni ha fatto il nostro data analyst agent per darci la risposta.

Se eseguite tutto in locale dovete attivare il mysql e dovete avere un database da esplorare sulla vostra macchina. Se siete su un server dovete compilare opportunamente tutti i campi della stringa di connessione per collegarvi al database.

DEFINE ("OPENAI_KEY","LACHIAVEOPENAI");
DEFINE ("PDO_DSN","mysql:host=localhost;dbname=mydatabase;charset=utf8mb4");

class LoggedPDO extends \PDO {
    public array $log = [];

    #[\ReturnTypeWillChange]
    public function prepare($statement, $options = [])  {
        $this->log[] = "[PREPARE] " . $statement;
        return parent::prepare($statement, $options);
    }

    #[\ReturnTypeWillChange]
    public function query($statement, ...$args) {
        $this->log[] = "[QUERY] " . $statement;
        return parent::query($statement, ...$args);
    }

    public function saveLogToFile(string $filename = 'queries.log') {
        file_put_contents($filename, PHP_EOL.'-----------------------'.date('Y-m-d H:i:s').'------------------------'.PHP_EOL, FILE_APPEND);
        file_put_contents($filename, implode(PHP_EOL, $this->log) . PHP_EOL, FILE_APPEND);
    }
}

$pdo = new \LoggedPDO(PDO_DSN, 'root', '');

?>

Ed ecco infine l’implementazione del file mysql.php che è il nostro agente che possiamo interrogare da liena di comando:

<?php
namespace App\Neuron;

include (__DIR__ . '/config.php');
include (__DIR__ . '/vendor/autoload.php');

use NeuronAI\Agent;
use NeuronAI\SystemPrompt;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\OpenAI\OpenAI;
use NeuronAI\Chat\Messages\UserMessage;

use NeuronAI\Tools\Toolkits\MySQL\MySQLSchemaTool;
use NeuronAI\Tools\Toolkits\MySQL\MySQLSelectTool;

class DataAnalystAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new OpenAI(
            key: OPENAI_KEY,
            model: 'o4-mini',
        );
    }
    
    public function instructions(): string 
    {
        return new SystemPrompt(
            background: ["You are a data analyst."],
        );
    }
    
    protected function tools(): array
    {
        global $pdo;     
        return [
            MySQLSchemaTool::make($pdo),
            MySQLSelectTool::make($pdo)
        ];
    }
}


    
// Legge l'intero prompt passato da riga di comando
$prompt = implode(' ', array_slice($argv, 1));

if (empty($prompt)) {
    echo "&#x274c; Inserisci una domanda dopo il comando. Esempio:\n";
    echo "   php mysql.php \"quanti utenti ci sono?\"\n";
    exit(1);
}

$pdo->log[] = "User query: " . $prompt;

$response = DataAnalystAgent::make()->chat(
    new UserMessage($prompt)
);

echo $response->getContent() . PHP_EOL;

echo "Queries: " . count($pdo->log);

$pdo->saveLogToFile();
?>

In dettaglio, mysql.php legge la stringa che gli viene passata da linea di comando, nella classe DataAnalystAgent viene creato il SystemPrompt che dice al LLM il ruolo dell’agente ed eventuali istruzioni iniziali. All’agente vengono anche forniti i tool MySQLSchemaTool e MySQLSelectTool per estrarre lo schema del db e per estrarre i dati.

La classe LoggedPDO scrive su file ogni volta che l’agente si interfaccia con il nostro db.

Ho utilizzato per test il database di un timesheet di un’azienda, modificando opportunamente i dati per privacy. Ecco l’agente in azione con alcune domande/risposte:

> php mysql.php "fai l'elenco dei 10 clienti su cui abbiamo speso di piu' a gennaio 2025."
Ecco i 10 clienti su cui abbiamo speso di più a gennaio 2025 (spesa calcolata come ore lavorate × costo orario utente):

ID Cliente | Nome Cliente               | Totale Spesa (EUR)
-----------|----------------------------|-------------------
105        | 00 Non attribuibile        | 22 478,50
258        | Supersoup                  | 7 324,00
115        | Yardening                  | 6 676,00
113        | ITALGAS                    | 6 444,00
215        | Manetti SpA                | 3 445,00
150        | Yoda                       | 1 960,00
284        | Arembi                     | 1 583,00
142        | italiana pietre e oli      | 1 458,00
148        | ATM S.p.A.                 | 1 450,00
227        | Politecnico di Milano      | 1 188,00

Tutti i valori sono arrotondati al centesimo.
Queries: 7

(Nota a margine: il cliente 00 Non attribuibile è quello in cui sono caricate tutte le ore di amministrazione, le ferie, le malattie…)

Altro test:

> php mysql.php "quale è stato il progetto a gennaio 2025 che è costato di più? escludi i progetti del cliente '00 Non Attribuibile'."
Il progetto di gennaio 2025 che è costato di più (escludendo quelli del cliente “00 Non Attribuibile”) è:

ID Progetto: 869  
Nome Progetto: Supersoup, Progetto 1  
Costo Totale: € 4.078,00
Queries: 6

E poi:

> php mysql.php "dimmi i nomi delle persone che hanno lavorato a gennaio 2025 sul cliente Supersoup, progetto 1"    
I nomi delle persone che hanno lavorato a gennaio 2025 sul cliente “Supersoup”, progetto “Progeto 1” sono:

- Marco Belotti
- Elena Dailà
- Raffaele Chiavistelli
- Martina Tisistemo
- Marco Murando
Queries: 9

Query eseguite dall’agente

Possiamo esplorare tramite il log file compilato da LoggedPDO le query eseguite da ChatGPT utilizzando i Tool di Neuron AI. In questo modo possiamo un poco indagare quello che fa l’agente, sia per tenerlo sotto controllo, sia per poter verificare le sue azioni in caso di errori e per debug.

Ad ogni domanda che facciamo passano all’inizio queste query, è il tool che indaga sullo schema del nostro database, recuperando dal db stesso le informazioni sulle tabelle, le relazioni, i campi:

[PREPARE] 
            SELECT
                t.TABLE_NAME,
                t.ENGINE,
                t.TABLE_ROWS,
                t.TABLE_COMMENT,
                c.COLUMN_NAME,
                c.ORDINAL_POSITION,
                c.COLUMN_DEFAULT,
                c.IS_NULLABLE,
                c.DATA_TYPE,
                c.CHARACTER_MAXIMUM_LENGTH,
                c.NUMERIC_PRECISION,
                c.NUMERIC_SCALE,
                c.COLUMN_TYPE,
                c.COLUMN_KEY,
                c.EXTRA,
                c.COLUMN_COMMENT
            FROM INFORMATION_SCHEMA.TABLES t
            LEFT JOIN INFORMATION_SCHEMA.COLUMNS c ON t.TABLE_NAME = c.TABLE_NAME
                AND t.TABLE_SCHEMA = c.TABLE_SCHEMA
            WHERE t.TABLE_SCHEMA = DATABASE() AND t.TABLE_TYPE = 'BASE TABLE'
            ORDER BY t.TABLE_NAME, c.ORDINAL_POSITION
        
[PREPARE] 
            SELECT
                kcu.CONSTRAINT_NAME,
                kcu.TABLE_NAME as source_table,
                kcu.COLUMN_NAME as source_column,
                kcu.REFERENCED_TABLE_NAME as target_table,
                kcu.REFERENCED_COLUMN_NAME as target_column,
                rc.UPDATE_RULE,
                rc.DELETE_RULE
            FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
            JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc
                ON kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
                AND kcu.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA
            WHERE kcu.TABLE_SCHEMA = DATABASE() AND kcu.REFERENCED_TABLE_NAME IS NOT NULL
            ORDER BY kcu.TABLE_NAME, kcu.ORDINAL_POSITION
        
[PREPARE] 
            SELECT
                TABLE_NAME,
                INDEX_NAME,
                COLUMN_NAME,
                SEQ_IN_INDEX,
                NON_UNIQUE,
                INDEX_TYPE,
                CARDINALITY
            FROM INFORMATION_SCHEMA.STATISTICS
            WHERE TABLE_SCHEMA = DATABASE()
                AND INDEX_NAME != 'PRIMARY'
            ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX
        
[PREPARE] 
            SELECT
                CONSTRAINT_NAME,
                TABLE_NAME,
                CONSTRAINT_TYPE
            FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
            WHERE CONSTRAINT_SCHEMA = DATABASE() AND CONSTRAINT_TYPE IN ('UNIQUE')

Vediamo ora le query dell’ultima domanda: “dimmi i nomi delle persone che hanno lavorato a gennaio 2025 sul cliente Supersoup, progetto 1“. Quelle che seguono sono le quattro query utilizzate attraverso il secondo tool per estrarre i dati:

[PREPARE] SELECT DISTINCT u.nome, u.cognome
FROM ts_ore o
JOIN ts_job j ON o.cd_job = j.id_job
JOIN ts_clienti c ON j.cd_cliente = c.id_cliente
JOIN frw_utenti u ON o.cd_utente = u.id
WHERE c.de_nomecliente = 'Supersoup'
  AND j.de_nomejob = 'Progetto 1'
  AND o.dt_giorno BETWEEN '2025-01-01' AND '2025-01-31';

[PREPARE] SELECT id_cliente, de_nomecliente FROM ts_clienti WHERE de_nomecliente LIKE '%Supersoup%';

[PREPARE] SELECT id_job, de_nomejob FROM ts_job WHERE cd_cliente = 258 AND de_nomejob LIKE '%Progetto 1%';

[PREPARE] SELECT DISTINCT u.nome, u.cognome
FROM ts_ore o
JOIN frw_utenti u ON o.cd_utente = u.id
WHERE o.cd_job = 869
AND o.dt_giorno BETWEEN '2025-01-01' AND '2025-01-31';

Conclusione

L’agente utilizzando il ragionamento del modello o4-mini di ChatGPT, ha autonomamente letto la struttura del db e l’ha interpretata, ha quindi cercato l’id del cliente, poi l’id del progetto, con questi dati è andato sulla tabella delle ore e ha cercato i nomi e cognomi delle persone.

La costruzione dell’agente è stata tutto sommato abbastanza semplice e questo strumento un po’ basico e composto da poche righe di codice è in grado di fare davvero cose molto utili. Con quest’approccio offerto da Neuron AI, con il tool che preleva lo schema, ogni modifica al db viene direttamente passata all’agente che quindi è in grado (grazie a tutto l’addestramento del modello LLM) di destreggiarsi tra tutte le tabelle del sistema restando sempre aggiornato sulla struttura dati e i dati.

Il risultato è fantastico!

p.s.
Quest’articolo è stato ispirato da questo articolo di Valerio Barbera a cui va un grosso ‘grazie’!

Author

PHP expert. Wordpress plugin and theme developer. Father, Maker, Arduino and ESP8266 enthusiast.

Comments on “Come creare un agente AI per interrogare un database MySQL con PHP e Neuron AI”

Lascia un commento

Your email address will not be published. Required fields are marked with an *