Oracle AI Database 26ai
Proviamo le funzionalita' AI

Oracle e' il piu' noto e diffuso database relazionale commerciale. L'Oracle AI Vector Search, introdotta nelle ultime versioni, e' progettata per l'Artificial Intelligence (AI) e consente di interrogare i dati con ricerche basate sulla semantica oltre che con chiavi e valori come avviene su una base dati relazionale.

In questa paginetta proveremo, con esempi pratici e sfruttando solo componenti gratuiti e/o Open Source, alcune delle funzionalita' avanzate AI di Oracle AI Database 26ai che utilizzano gli LLMs (Large Language Models).

Per presentare in modo interessante le funzioni presenti proveremo ad eseguire un percorso che ci portera' dalla configurazione inziale alla creazione di tutti i componenti che servono ad un semplice RAG (Retrieval Augmented Generation). Per ragioni di spazio gli argomenti sono presentati in modo sintetico e per un pubblico adulto ovvero sufficientemente preparato dal punto di vista tecnico sul database Oracle.
Gli argomenti sono raccolti nei seguenti capitoli: Prerequisiti, Interrogare un LLM, Ottenere gli embedding, Creare la base dati, Ricerche semantiche, RAG, ... Varie ed Eventuali,

Prerequisiti

Quanta memoria ho? Poca, dimentico sempre tutto... ma per queste prove consiglio almeno 16GB: Oracle 26ai Free non puo' utilizzare piu' di 2GB di RAM, ma gli servono tutti ed un paio di GB in piu' al OS vanno lasciati; i modelli che che utilizzeremo sono 7b o 8b... insomma 16MB sono fortemente consigliati. Poi se di dispone di computer potente e sopratutto di una GPU con molta memoria i tempi di risposta saranno migliori. Ma per provare basta questo.

Sceglieremo strumenti ed ambienti che possiamo installare gratuitamente e localmente sul nostro PC Windows/Linux/Mac [NdA in effetti i Mac, grazie all'UMA (Unified Memory Architecture), presentano qualche vantaggio ma con l'hardware adeguato o con molta pazienza si possono ottenere comunque risultati utili]. Abbiamo bisogno di un'installazione Oracle con la versione piu' recente che possa accedere ad un moderno LLM (Large Language Model). Vediamo i dettagli e seguite i link se non conoscete gli oggetti.

Per ospitare gli LLM utilizzeremo Ollama che installeremo localmente con HomeBrew [NdA poiche' credo nell'evoluzione e quindi utilizzo Darwin]. Per le prove si possono scaricare innumerevoli modelli differenti, nel seguito useremo llama3.1:8b, mistral:7b e qllama/bge-large-en-v1.5.
Per Oracle utilizziamo la piu' recente Oracle AI Database 26ai disponibile Free; tra le diverse modalita' di installazione utilizzeremo un container: Docker o Podman praticamente non hanno differenze perche' utilizzano gli stessi comandi. Non possiamo utilizzare la versione lite di Oracle e quindi il dowload richiedera' un po' di tempo [NdA quasi 10GB], inoltre per utilizzare gli indici vector i memoria dobbiamo modificare la configurazione.

Parole, parole, parole, ... passiamo ai fatti!

# Installazione di Ollama e dei modelli LLM brew install ollama brew services start ollama ollama pull llama3.1:8b ollama pull mistral:7b ollama pull qllama/bge-large-en-v1.5:latest # Download, installazione di Oracle e configurazione docker pull container-registry.oracle.com/database/free:latest docker run -d --name=ora26ai container-registry.oracle.com/database/free:latest docker exec ora26ai ./setPassword.sh xyz docker exec -it ora26ai bash sqlplus sys/xyz@FREE as sysdba alter system set vector_memory_size = 512M scope=spfile; shutdown immediate ^D sqlplus / as sysdba startup ^D ^D

Abbiamo installato un LLM locale ed un database Oracle. Riusciremo a farli comunicare?

Interrogare un LLM da Oracle

Oracle 26ai puo' richiamare gli LLM locali o di terze parti utilizzando gli opportuni package.

Per caso di prova che abbiamo scelto useremo Mistral un LLM francese perche' e' noto che sono appassionati di vela oceanica. Dovendo raggiungere un servizio installato sul nostro PC da un container faremo riferimento ad un hostname riservato: host.docker.internal. L'accesso all'LLM verra' effettuato con una query SQL lanciata da linea di comando che richiamera' la funzione utl_to_generate_text del package dbms_vector.

docker exec -it ora26ai sqlplus system/xyz@FREE SQL> set long 1024 SQL> select dbms_vector.utl_to_generate_text('Conosci la Mini Transat?', json('{ "provider": "ollama", "host": "local", "url": "http://host.docker.internal:11434/api/generate", "model": "mistral:7b" }')) as reply from dual; REPLY -------------------------------------------------------------------------------- Si, la Mini Transat e una regata a vela che si tiene ogni due anni tra Francia e Brasile, con partenza da Douarnenez in Bretagna e arrivo ad Salvador nella Bai a di Todos-os-Santos. La competizione e nota per essere difficile e impegnativa, poiche richiede una grande resistenza fisica e mentale dalle navi e dai loro eq uipaggi. Le barche utilizzate sono le Mini 6.50, un tipo di trimarano a vela da regata sviluppato specificamente per la Mini Transat.

Naturalmente e' possibile chiedere qualsiasi cosa... Dall'esempio e' chiaro che riusciamo ad interrogare un LLM con una SELECT, che l'LLM capisce la nostra domanda, ci risponde in italiano in modo coerente anche se, come un appassionato di vela potrebbe notare, con qualche allucinazione. In effetti se provassimo a chiedere chi ha vinto la regata nel 2019 o un pronostico su chi potrebbe vincere nel 2025 l'LLM darebbe una risposta ancora piu' imprecisa: risponderebbe in modo verosimile ma inventando gran parte del testo! Stiamo interrogando in locale un modello di dimensioni ridotte: non puo' sapere tutto. Ed anche i modelli di dimensioni maggiori vengono aggiornati solo periodicamente e possono ottenere informazioni solo da quanto pubblicato su internet o comunque utilizzato nel training.

Sicuramente potremmo abbassare la temperatura per ridurre le allucinazioni [NdA e' il modo tecnicamente corretto] ma non otterremmo comunque una risposta precisa ed aggiornata. Per farlo dobbiamo fornire piu' informazioni al modello... i database sono adatti proprio a gestire le informazioni ma ci servono gli emebedding.

Ottenere gli embedding

Per fornire all'LLM la conoscenza aggiuntiva sugli argomenti che ci interessano servono due cose: le informazioni e gli embedding. Sulle base dati e' normale avere le informazioni, sono fatte per gestire grandi quantita' di dati! La novita' sono gli embeddings...
Ci sono molti modi di porre una domanda: chi ha vinto la regata nel 2019? come e' andata sei anni fa? mi interessano i risultati del ... come facciamo ad associare queste domande alle informazioni? Potremmo farlo comprendendo il significato della domanda e confrontandolo con il significato dei documenti, ma e' possibile? E' possibile con gli embedding che catturano il significato di una frase, di un testo o di un documento in una rappresentazione numerica multidimensionale. Confrontando gli embedding tra loro i piu' vicini avranno significati simili.

Parole, parole, parole, ... passiamo ai fatti!
Usiamo la funzione utl_to_embedding del package dbms_vector richiamando un modello adatto; abbiamo scelto bge-large-en-v1.5 che e' un embedding model che trasforma il testo in una rappresentazione semantica densa e ne cattura il significato.

select dbms_vector.utl_to_embedding('Chi ha vinto Mini Transat nel 2019?', json('{ "provider": "ollama", "host": "local", "url": "http://host.docker.internal:11434/api/embeddings", "model": "qllama/bge-large-en-v1.5:latest" }')) as embedding from dual; [-1.00934899E+000,-4.76052016E-001,7.7933085E-001,1.7142272E-001,-2.96123415E-001,-8.21509957E-001,-4.37104739E-002,2.55817235E-001,-2.72401333E-001,8.87830496E-001,-1.65897503E-001,8.93067181E-001,-6.81049347E-001,2.77141243E-001,-1.76580697E-001,5.96257091E-001,-2.91293999E-003,-1.04463547E-001,-2.75491655E-001,3.31555218E-001,-9.33872223E-001,4.71813381E-001,-1.01017249E+000,-8.31672788E-001,-4.7436142E-001,4.75117952E-001,-2.25154892E-001,3.90652567E-001, ...

Anche la generazione degli embedding, cosi' come interrogare un LLM, e' molto semplice da SQL o PL/SQL con Oracle 26ai.

Ora che possiamo estrarre il significato della domanda dobbiamo catalogare la nostra conoscenza nel database Oracle ed utilizzeremo i VECTOR.

Creare la base dati

Oracle 26ai dispone di un nuovo datatype VECTOR che e' stato creato appositamente per memorizzare gli embedding e per eseguire ricerche efficienti su di essi. Utilizzando i VECTOR possiamo memorizzare gli embedding sulla base dati: in questo capitolo creiamo le tabelle e gli indici necessari alle ricerche RAG.

-- 1. Creazione della tabella con la knowledge base -- La dimensione del vettore deve corrispondere a quella dell' `embedding_model` -- (`qllama/bge-large-en-v1.5` ha dimensione 1024). CREATE TABLE sail_kb ( id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, title VARCHAR2(1024), content CLOB, doc_type VARCHAR2(255) DEFAULT 'generic', priority NUMBER(5) DEFAULT 3, tags JSON, embedding VECTOR(1024, FLOAT32), -- Dimension for `qllama/bge-large-en-v1.5` CONSTRAINT tags_is_json CHECK (tags IS JSON) ); -- 2. Creazione di indici prestazionali -- In questo caso rendiamo efficienti le ricerche per TAG CREATE SEARCH INDEX idx_rag_kb_tags ON sail_kb (tags) FOR JSON; -- 3. Creazione di vector index per le ricerche semantiche -- Sono disponibili parametri specifici per ottimizzare le ricerche per distanza CREATE VECTOR INDEX idx_embedding ON sail_kb (embedding) ORGANIZATION INMEMORY NEIGHBOR GRAPH DISTANCE COSINE;

I contenuti della knowledge base sono molto importanti e le origini dei dati possono essere differenti. Nel nostro esempio partiamo da file in formato markdown di modeste dimensioni. E' una scelta tattica usata molto spesso perche' il formato markdown e' facilmente leggibile dagli umani e trattabile direttamente come testo. Una volta caricati i dati possimo utilizzare la funzione utl_to_embedding per aggiornare la colonna che contiene l'embedding:

update sail_kb set embedding=dbms_vector.utl_to_embedding(content, json('{ "provider": "ollama", "host": "local", "url": "http://host.docker.internal:11434/api/embeddings", "model": "qllama/bge-large-en-v1.5:latest" }'));

Abbiamo creato una tabella e l'abbiamo popolata ora possiamo interrogarla... continuate a leggere!

Ricerche semantiche

L'aspetto importante e' che le ricerche semantiche non sono svolte confrontando il testo o parole chiave ma comprendendo il significato della richiesta utente, confrontandolo con quello dei documenti presenti nella base dati e scegliendo quelli piu' vicini [NdA KNN: algoritmo k-nearest neighbors che e' un classificatore di apprendimento supervisionato non parametrico utilizzato sia per la classificazione che per la regressione... in parole povere: i risultati piu' vicini].

Oracle consente ricerche semantiche utilizzando la funzione VECTOR_DISTANCE applicata su dati di tipo VECTOR.
Per estrarre i 5 documenti piu' simili alla richiesta dell'utente:

SELECT id, title, content, priority FROM sail_kb ORDER BY VECTOR_DISTANCE(embedding, :query_embedding, COSINE) ASC FETCH FIRST 5 ROWS ONLY;

Per estrarre i 10 documenti con argomento simile e con i tag previsti ordinati per priorita' e per similarita':

SELECT id, title, content, priority FROM sail_kb WHERE JSON_EXISTS(tags, :json_path) AND VECTOR_DISTANCE(embedding, :query_embedding, COSINE) < 0.5 ORDER BY priority ASC, VECTOR_DISTANCE(embedding, :query_embedding, COSINE) ASC FETCH FIRST 10 ROWS ONLY;

Dovrebbe essere chiaro che le possibilita' di interrogazione sono molteplici e dipendono dalle esigenze progettuali...

RAG

In una tipica implementazione di un RAG (Retrieval Augmented Generation) la base dati viene utilizzata solo per effettuare le ricerche semantiche per estrarre i testi da aggiungere al prompt inviato all'LLM e ottenere cosi' risposte che tengono conto di una conoscenza arricchita dal contesto.
Il linguaggio di programmazione piu' utilizzato e' il Python, le basi dati utilizzate sono quelle che supportano i vettori e le ricerche per prossimita'. RAG Sequence Diagram

Oracle 26ai supporta il datatype VECTOR e puo' eseguire ricerche semantiche, quindi puo' essere utilizzato in questo tipo di architetture. Ma nei capitoli precedenti abbiamo visto che Oracle puo' fare molto di piu' perche' da SQL e' possibile calcolare l'embedding della richiesta utente e richiamare un LLM esterno.
L'intera catena di elaborazione puo' essere realizzata in PL/SQL: Oracle RAG Sequence Diagram

Con un RAG in pratica anziche' interrogare un LLM in modo generico, prima ricerchiamo in una base dati interna quali ulteriori informazioni possono essere fornite e quindi le utilizziamo per aumentare il contesto della richiesta. La ricerca sulla base dati si puo' eseguire con normali condizioni ma anche, e questa e' la parte innovativa, per significato. Per fare questo ogni documento viene valutato ottenendo un vettore che ne descrive il significato: l'embedding. Quindi la ricerca avviene con una normale query SQL che confronta la richiesta iniziale con gli embedding dei documenti disponibili.

Il primo passo e' stato quello di definire la tabella sail_kb che contiene i documenti utili ad arricchire la conoscenza di un LLM generico. Oracle 26ai supporta il datatype vector che puo' essere utilizzato per memorizzare gli embedding.
La base dati va quindi caricata con le informazioni della RAG e per ciascun documento deve essere catturato il significato, che praticamente si traduce nella valorizzazione del vettore di embedding. La scelta dei documenti, la loro trasformazione in testo, la separazione in chunk, la generazione degli embedding e la memorizzazione sulla base dati sono ovviamente molto importanti... ma li consideriamo tecnicismi!
Il risultato ottenuto dalle query viene poi inserito nel contesto ed inviato all'LLM per ottenere la risposta finale. Tipicamente l'LLM e' esterno e vieni richiamato su OCI o su Cloud, nel nostro esempio abbiamo utilizzato Ollama che viene richiamato come se fosse un servizio esterno.

Perche' non l'hai detto subito?

Ho cercato di rendere pratica e sintetica questa paginetta, in realta' ci sono parecchi ulteriori dettagli da presentare.
Perche' non l'hai detto subito? Perche' era troppo noioso!

Ma se avete avuto la pazienza di leggere fino a qui ora potete sorbirvi tutti i dettagli che non sono stati descritti in precedenza.

Teoria

Gli embedding consentono di rappresentare il significato di un testo o di un immagine. La teoria su cui si appoggiano gli LLM, i concetti di distanza, ... tutti argomenti interessanti ed importanti che non abbiamo presentato.

Sono fondamentali i concetti di token, chunk, context, ... negli esempi abbiamo utilizzato richieste che non raggiungevano i limiti di contesto e non richiedevano di dividere in chunk i testi ma e' importante conoscerli nei progetti reali. Altrettanto importanti dal punto di vista pratico sono altri dettagli tecnici come la scelta tra le diversi dimensioni di un modello, la temperatura, la quantizzazione, ...
Non li abbiamo descritti in dettaglio ma tutti questi elementi sono presenti anche nell'implementazione di Oracle.

E' possibile caricare nella base dati Oracle i modelli LLM, se disponibili in formato ONNX, e far eseguire dalla base dati stessa i modelli. Ma nella maggior parte dei casi si preferisce accedere a servizi esterni che e' possibile richiamare direttamente dal PL/SQL. Sono supportati i principali fornitori di servizi: OpenAI, Anthropic, Cohere, Mistral AI, Google AI e, naturalmente, Oracle Cloud Infrastructure Generative AI. Il richiamo avviene attraverso una chiamata REST sulla terza parte remota (eg. Google AI, Hugging Face, OpenAI, Vertex AI) o locale (Ollama).

Il datatype VECTOR consente di memorizzare gli embedding in Oracle. Oracle, dalla versione 26ai, fornisce funzioni ed indici specifici per ottimizzare le ricerche sui VECTOR e costruire catene dal dato grezzo fino all'informazione finale. L'indicazione della versione e' volutamente imprecisa perche' non e' semplice individuare il momento in cui e' stata raggiunta la maturita' necessaria per un uso concreto delle funzionalita' di AI.

I package DBMS_VECTOR, DBMS_VECTOR_CHAIN, DBMS_HYBRID_VECTOR forniscono le funzionalita' per trattare i dati semantici in Oracle.
DBMS_VECTOR (cfr. documentazione ufficiale) fornisce le funzioni di base
DBMS_VECTOR_CHAIN (cfr.
documentazione ufficiale) fornisce le funzioni avanzate
DBMS_HYBRID_VECTOR (cfr.
documentazione ufficiale) fornisce le funzioni per gestire

ONNX

ONNX! Chi era costui?

ONNX (Open Neural Network Exchange) e' un formato con cui vengono memorizzati i pesi delle reti neurali degli LLM. Non e' l'unico formato disponibile e quindi puo' essere utile questa tabella che riporta i piu' diffusi:

Modello Utilizzo Note
Safetensors Training, PyTorch, MLX Sicuro, veloce da caricare, contiene pesi "puri" da caricare in RAM senza modifiche (Zero-Copy). Creato da Hugging Face per sostituire i vecchi file .bin (Pickle) che erano potenzialmente pericolosi...
GGUF
GPT-Generated Unified Format
Ollama, llama.cpp Lossy, orientato alla quantizzazione che e' molto flessibile (eg. Q4_K_M). Tutto-in-uno, perfetto per CPU/GPU consumer.
ONNX
Open Neural Network Exchange
Produzione, Mobile, Edge Rivolto all'interoperabilita'. Sviluppato inizialmente da Microsoft. Contiene anche la logica (grafo), non solo i pesi. Puo' essere Loseness ma generalmente usa INT8 o FP16.

Esistono molti altri modelli (eg. NVIDIA TensorRT), formati (eg. AWQ: Activation-aware Weight Quantization) e la quantizzazione puo' essere molto spinta:

Formato Bit Note
FP32 32 Massimo dettaglio. Usato principalmente per il training.
FP16 / BF16 16 Standard per l'addestramento moderno e l'inferenza ad alta fedelta'.
INT8 8 Primo step di compressione massiccia. Perdita di precisione generalmente impercettibile.
Q4_K_M ~4.5 Ottimo equilibrio tra memoria e intelligenza. E' il default su GGUF/Ollama.
NF4 (Normal Float) 4 Formato speciale che usa una distribuzione normale per mappare i pesi. Molto efficace nei modelli Llama.
IQ2 / EXL2 2 Estremo. Il modello inizia a "farneticare" o perdere coerenza logica complessa (Perplexity).
Ternario 1.58 Utilizza i valori: -1, 0, 1. Il calcolo non richiede moltiplicazioni, ma solo addizioni. Adatto solo a casi particolari ma velocissimo.

Ma direi che e' sufficiente!

Pratica

Per accedere ad Oracle ho impostato una password banale ed ho utilizzato un utenza amministrativa. Ovviamente non va fatto cosi' ma e' un dettaglio.
Non ho utilizzato un volume per ospitare il database, in questo modo i dati non persistono.
Le installazioni con Oracle 26ai sono sempre multitenant. Negli esempi abbiamo utilizzato il CDB ma e' buona norma utilizzare un PDB.

Il linguaggio piu' utilizzato nell'AI e' il Python in questa pagina abbiamo utilizzato solo SQL e PL/SQL. E' ovviamente possibile utilizzare Oracle ed il suo SQL da Python.

Sono i limiti sul numero di token, la separazione del testo in chunck, la dimensione massima del contesto negli esempi abbiamo usati sono stati aggirati con alcune scelte tattiche. Le funzioni Oracle consentono il controllo e la gestione di questi aspetti che sono fondamentali per la robustezza del sistema.

Varie ed eventuali

Sicuramente e' interessante il confronto delle funzionalita' vettoriali di Oracle 26ai rispetto all'extension pgvector di PostgreSQL che era disponibile gia' dal 2021.
Le differenze sono molte perche' PostgreSQL oltre alle funzioni fornisce operatori per i confronti di distanza, quindi le query SQL risultano diverse anche se i concetti di base sono gli stessi. Inoltre PostgreSQL non prevede richiami diretti o integrazioni con gli LLM.


Titolo: Oracle Database 26ai - Utilizzo funzionalita' AI
Livello: Avanzato (3/5)
Data: 31 Ottobre 2024 🎃
Versione: 1.1.0 - 31 Ottobre 2025 🎃
Autore: mail [AT] meo.bogliolo.name