Ricerca semantica con Ollama

banner

Ti è mai capitato di cercare su un e-commerce la parola “piede” e trovare delle scarpe? Noi, esseri umani, conosciamo la relazione tra questi due termini, ma come possiamo rendere disponibile questa conoscenza al nostro software?

Spesso questa ricerca viene chiamata “Ricerca Vettoriale” o “Ricerca Semantica”. Questo tipo di ricerca è molto più potente rispetto alla ricerca testuale (Full-Text), perché non si limita a cercare corrispondenze esatte tra le parole, ma cerca di capire il significato dietro le parole stesse.

TOC

Cos’è la ricerca semantica

La ricerca semantica (o a vettori) utilizza modelli di machine learning e rappresentazioni numeriche (vettori) per catturare il significato di testi, immagini o altri contenuti, anziché limitarsi alle parole chiave testuali. Invece di cercare corrispondenze letterali, confronta le “vicinanze” tra vettori che rappresentano concetti: in questo modo trova risultati più pertinenti sul piano semantico, anche se i termini non combaciano esattamente.

Esempio di Vector Embedding


Come si fa?

Di seguito troverai un breve tutorial su come iniziare a sviluppare un’applicazione che esegua vector embedding e vector search locale utilizzando Ollama per gestire i modelli, Orama per la ricerca vettoriale e Node.js come base applicativa. L’obiettivo è mostrarti un percorso semplice per effettuare l’embedding di testi con un modello locale (ad esempio nomic-embed-text) e salvare/ricercare questi embedding su un indice vettoriale. Il tutto in pochissimi minuti!

Al momento della stesura di questo articolo, il team di Orama sta lavorando alla versione 1.0.0 di Orama Core, un nuovo progetto che include al suo interno sia la ricerca vettoriale - obiettivo di questo articolo - sia i modelli LLM che si occupano dell’embedding. Mettere in piedi una ricerca vettoriale e un sistema di embedding è un bellissimo esperimento, ma strumenti come Orama Core sono fatti proprio per evitare di ricostruire la ruota quotidianamente, e suggeriamo di farlo solo a scopi didattici, o se veramente si ha la necessità di scendere a un livello inferiore di astrazione.


1. Introduzione e prerequisiti

  1. Node.js: assicurati di aver installato Node.js (consigliata una versione LTS).
    Per installarlo è sufficiente andare sul sito https://nodejs.org/ e scaricare l’ultima versione per il proprio sistema operativo.

  2. Ollama: è un prodotto che semplifica l’esecuzione di modelli LLM in locale. Fornisce un’interfaccia a riga di comando (CLI) e permette di integrare i modelli in un’app Node.js tramite un semplice SDK.
    Anche in questo caso, per installarlo è sufficiente andare su https://ollama.com/ e scaricare l’ultima versione per il proprio sistema operativo.


2. Ollama: installazione e comandi principali

Una volta installato Ollama, puoi utilizzare alcuni comandi chiave:

  • ollama list: mostra i modelli installati localmente.

  • ollama pull <nome-modello>: scarica un modello, ad esempio: ollama pull nomic-embed-text

  • ollama run <nome-modello>: esegue il modello con un prompt testuale.

  • ollama serve: avvia il server di Ollama, utile se vuoi servire i modelli tramite API (in alcuni casi potresti preferire la modalità integrata direttamente con l’SDK Node).

Nel nostro esempio useremo il modello nomic-embed-text per generare embedding delle stringhe.

Come mai nomic-embed-text?

Al momento della stesura di questo articolo, risulta il più popolare nella libreria di Ollama, ha una finestra di contesto molto ampia ed è molto veloce nella fase di embedding. Se preferisci, puoi usare qualsiasi altro modello di embedding.


3. Orama: indice vettoriale e schema dei dati

Per utilizzare la ricerca vettoriale di Orama, dobbiamo:

  1. Creare un database definendo lo schema e il tipo “vector” sui campi embedding.

  2. Inserire i documenti (con l’embedding calcolato).

  3. Effettuare la ricerca vettoriale su tali documenti.

Nell’esempio qui sotto, useremo Orama per indicizzare una lista di film (contenuti in un file locale chiamato movies.json, che andremo a scaricare dopo).


4. Esempio pratico con Node.js

Prepara un nuovo progetto Node.js e installa i pacchetti necessari:

npm init -y
npm install ollama @orama/orama

Assicurati di aver già lanciato il comando:

ollama pull nomic-embed-text

Così da scaricare il modello richiesto.

a) Funzione per creare embedding (ollama.js)

Crea un file ollama.js con il contenuto seguente. Questa funzione riceve un array di stringhe (documents) e restituisce un array di vettori di embedding (uno per ogni stringa):

import ollama from 'content/posts/2025/03/2025-03-04-ollama'

// documents come array di stringhe
export async function getEmbeddings(documents) {
    const results = [];
    let counter = 0;
    for (let document of documents) {
        counter++;
        console.log(`Embedding ${counter} di ${documents.length}`);
        const embeddings = await ollama.embed({
            model: 'nomic-embed-text',
            input: document,
            keep_alive: "60s"
        })
        results.push(embeddings.embeddings[0]);
    }

    return results;
}

P.S. Abbiamo aggiunto un console.log per non lasciarti in attesa del risultato!

b) Creazione dell’indice e ricerca (index.js)

Nel file index.js, utilizziamo Orama per:

  1. Creare un indice con proprietà title, description e embedding (di tipo vector).

  2. Leggere il file movies.json e preparare i documenti.
    Per questo step abbiamo utilizzato un semplice file JSON trovato su Github: https://github.com/toedter/movies-demo/blob/master/backend/src/main/resources/static/movie-data/movies-250.json.
    Con i dovuti aggiustamenti, potrai sostituire questo file con una query verso un DB, una chiamata a Redis o qualsiasi altra fonte di dato ti possa essere utile!

  3. Calcolare gli embedding con la funzione getEmbeddings.

  4. Inserire i dati in Orama.

  5. Eseguire una ricerca vettoriale passandogli l’embedding di una query.

Ecco un esempio completo (anche in questo caso, abbiamo aggiunto qualche console.log per dare un senso di progressione):

import {create, insertMultiple, search} from "@orama/orama";
import {getEmbeddings} from "./2025-03-04-ollama.md";
import fs from "fs";

console.log('Starting...');
const movies = JSON.parse(fs.readFileSync("./movies.json", "utf-8"));
console.log('Movies file read');

// Creazione del database Orama
const db = create({
    schema: {
        title: "string",
        description: "string",
        embedding: "vector[768]", // Attenzione, questo valore deve essere allineato con
    },
});

// Mappiamo i documenti
const documents = movies.map(m => ({
    title: m.Title,
    embeddable: `${m.Title} ${m.Plot}`,
    description: `${m.Plot} ${m.Genre} ${m.Director} ${m.Writer} ${m.Actors}`
}));

console.log('Mapped documents');

// Creiamo gli embedding
console.time("embedding");
const embeddings = await getEmbeddings(documents.map(d => d.embeddable));
console.timeEnd("embedding");

// Inseriamo dati con embedding nel database
console.time("insert");
await insertMultiple(db, documents.map((d, i) => ({
    title: d.title,
    description: d.description,
    embedding: embeddings[i],
})));
console.timeEnd("insert");

// Recuperiamo la query da riga di comando
const query = process.argv.slice(2).join(" ");
const embeddingQuery = (await getEmbeddings([query]))[0];

// Eseguiamo la ricerca vettoriale
console.time("search");
const results = await search(db, {
    mode: "vector",
    vector: {
        value: embeddingQuery,
        property: "embedding",
    },
    similarity: 0.5, // Punteggio minimo di similarità da ricercare
    limit: 3, // Limite dei risultati mostrati
});
console.timeEnd("search");

// Mostriamo i risultati (titolo e punteggio di similarità)
console.log(
    results.hits.map(h => ({document: h.document, score: h.score}))
);

Come eseguirlo?

node index.js "testo della tua query"

Nel nostro caso, usando come chiave di ricerca “planet”, i risultati che ci vengono restituiti sono, in ordine: Interstellar e Avatar. In un altro test, un altro modello ci ha restituito nell’ordine Avatar, Interstellar e The Avengers.

La cosa incredibile è che nessuno di questi film contiene, nelle informazioni di embedding passate, ossia titolo e plot, la parola “planet”, ed è questo il bello della ricerca semantica!


5. Conclusioni & Prossimi Step

In pochi passi hai configurato:

  • Ollama per scaricare ed eseguire in locale un modello LLM (in questo caso, per embedding).

  • Orama per creare un indice con campi testuali e vettoriali, dove salvare i dati e cercarli tramite similarità.

  • Node.js per unire il tutto in un’unica applicazione: lettura dati, generazione embedding, indicizzazione e ricerca.

E ora? Beh, la fantasia è il tuo unico limite (oltre che la dimensione del modello LLM scelto 😂).

Potresti implementare la Data Persistence (vedi qui per maggiori dettagli) per evitare di dover addestrare il tuo modello a ogni avvio, oppure potresti usare modelli differenti e confrontare i risultati!

Questa base può essere estesa per creare chatbot, motori di ricerca intelligenti, o integrazioni con dati testuali molto più ampi.

Buon sviluppo con la tua AI locale!

Post correlati

TheRedCode.it - Il mondo #tech a piccoli #bit

Partners

Community, aziende e persone che supportano attivamente il blog

Logo di Codemotion
Logo di GrUSP
Logo di Python Milano
Logo di Schrodinger Hat
Logo di Python Biella Group
Logo di Fuzzy Brains
Logo di Django Girls
Logo di Improove
Logo del libro open source
Logo di NgRome
Logo de La Locanda del Tech
Logo di Tomorrow Devs
Logo di Coderful
Logo di VueSchool

Non perderti gli ultimi aggiornamenti, iscriviti a TheRedCode Digest!

La tecnologia corre, e tu devi correre più veloce per rimanere sempre sul pezzo! 🚀

Riceverai una volta al mese (o anche meno) con codici sconto per partecipare agli eventi del settore, quiz per vincere dei gadget e i recap degli articoli più interessanti pubblicati sul blog

Ci sto!

#TheRedComics

Edizione di Gennaio - Buon Anno nuovo!

A cura di Sophie Aiello, copy di Chiara Romano

Fumetto di dicembre di Sophie Aiello, Copy di Chiara Romano

Vuoi diventare #tech content creator? 🖊️

Se vuoi raccontare la tua sul mondo #tech con dei post a tema o vuoi condividere la tua esperienza con la community, sei nel posto giusto! 😉

Manda una mail a collaborazioni[at]theredcode.it con la tua proposta e diventa la prossima penna del blog!

Ma sì, facciamolo!