CREARE UN'APPLICAZIONE SPRING BOOT CON DATABASE VETTORIALE POSTGRESQL



In questo articolo, esploreremo come creare un'applicazione Spring Boot che utilizza un database vettoriale PostgreSQL per eseguire ricerche di similarità basate su vettori.
Un database vettoriale è un qualsiasi database che ti consente di archiviare, indicizzare ed eseguire query su incorporamenti vettoriali o rappresentazioni numeriche di dati non strutturati, come testo, immagini o audio.

Prerequisiti

  • Java 17+
  • Spring Boot 3+
  • PostgreSQL 15+ con estensione pgvector
  • Docker (opzionale per eseguire PostgreSQL in un container)

Configurazione di PostgreSQL con pgvector

Per utilizzare PostgreSQL come database vettoriale, dobbiamo installare l'estensione pgvector. Se usiamo Docker, possiamo eseguire il seguente comando:

docker run --name pgvector-db -e POSTGRES_USER=admin -e POSTGRES_PASSWORD=admin -e POSTGRES_DB=vector_db -p 5432:5432 ankane/pgvector

Questa immagine Docker include PostgreSQL con pgvector preinstallato. In alternativa, possiamo installare manualmente l'estensione su un'istanza esistente:

CREATE EXTENSION IF NOT EXISTS vector;

Creazione del Progetto Spring Boot

Creiamo un progetto Spring Boot utilizzando Spring Initializr con le seguenti dipendenze:

  • Spring Web
  • Spring Data JPA
  • PostgreSQL Driver

Il file application.yml conterrà la configurazione per collegarsi a PostgreSQL:

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/vector_db
    username: admin
    password: admin
    driver-class-name: org.postgresql.Driver
  jpa:
    database-platform: org.hibernate.dialect.PostgreSQLDialect
    hibernate:
      ddl-auto: update

Definizione dell'Entità con Colonna Vettoriale

Creiamo un'entità Articolo con un campo vettoriale:

import jakarta.persistence.*;
import java.time.LocalDateTime;
import java.util.Arrays;

@Entity
@Table(name = "articoli")
public class Articolo {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String titolo;
    
    @Column(length = 500)
    private String sommario;
    
    @Column(columnDefinition = "TEXT")
    private String contenuto;
    
    private String autore;
    
    private LocalDateTime dataCreazione;
    
    @Column(name = "categoria")
    private String categoria;
    
    @Column(columnDefinition = "vector(384)")
    private float[] embedding;

    // Getter e Setter
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    
    public String getTitolo() { return titolo; }
    public void setTitolo(String titolo) { this.titolo = titolo; }
    
    public String getSommario() { return sommario; }
    public void setSommario(String sommario) { this.sommario = sommario; }
    
    public String getContenuto() { return contenuto; }
    public void setContenuto(String contenuto) { this.contenuto = contenuto; }
    
    public String getAutore() { return autore; }
    public void setAutore(String autore) { this.autore = autore; }
    
    public LocalDateTime getDataCreazione() { return dataCreazione; }
    public void setDataCreazione(LocalDateTime dataCreazione) { this.dataCreazione = dataCreazione; }
    
    public String getCategoria() { return categoria; }
    public void setCategoria(String categoria) { this.categoria = categoria; }
    
    public float[] getEmbedding() { return embedding; }
    public void setEmbedding(float[] embedding) { this.embedding = embedding; }

    @Override
    public String toString() {
        return "Articolo{" + 
               "id=" + id + 
               ", titolo='" + titolo + '\'' + 
               ", autore='" + autore + '\'' + 
               ", categoria='" + categoria + '\'' + 
               ", dataCreazione=" + dataCreazione + 
               ", embedding=" + Arrays.toString(embedding) + 
               '}';
    }
}

Creazione del Repository

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;

public interface ArticoloRepository extends JpaRepository<Articolo, Long> {
    
    /**
     * Trova gli articoli semanticamente più simili al vettore di input
     * utilizzando la distanza coseno (operatore <->)
     */
    @Query(value = "SELECT * FROM articoli ORDER BY embedding <-> :vector LIMIT :limit", nativeQuery = true)
    List<Articolo> trovaSimilari(@Param("vector") float[] vector, @Param("limit") int limit);
    
    /**
     * Trova articoli simili per categoria
     */
    @Query(value = "SELECT * FROM articoli WHERE categoria = :categoria ORDER BY embedding <-> :vector LIMIT :limit", nativeQuery = true)
    List<Articolo> trovaSimilariPerCategoria(@Param("vector") float[] vector, @Param("categoria") String categoria, @Param("limit") int limit);
}

Creazione del Controller REST

import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.List;

@RestController
@RequestMapping("/api/articoli")
public class ArticoloController {
    
    private final ArticoloRepository articoloRepository;

    public ArticoloController(ArticoloRepository articoloRepository) {
        this.articoloRepository = articoloRepository;
    }

    @PostMapping
    public Articolo creaArticolo(@RequestBody Articolo articolo) {
        // Imposta la data di creazione se non specificata
        if (articolo.getDataCreazione() == null) {
            articolo.setDataCreazione(LocalDateTime.now());
        }
        return articoloRepository.save(articolo);
    }

    @GetMapping("/{id}")
    public Articolo getArticolo(@PathVariable Long id) {
        return articoloRepository.findById(id)
            .orElseThrow(() -> new RuntimeException("Articolo non trovato con id: " + id));
    }

    @GetMapping("/cerca/simili")
    public List<Articolo> cercaSimili(@RequestParam float[] vector, @RequestParam(defaultValue = "5") int limit) {
        return articoloRepository.trovaSimilari(vector, limit);
    }
    
    @GetMapping("/cerca/categoria/{categoria}")
    public List<Articolo> cercaSimiliPerCategoria(
            @RequestParam float[] vector, 
            @PathVariable String categoria, 
            @RequestParam(defaultValue = "5") int limit) {
        return articoloRepository.trovaSimilariPerCategoria(vector, categoria, limit);
    }
}

Test dell'Applicazione

Il caso d'uso di ricerca semantica di articoli di blog è molto intuitivo - permette di trovare contenuti simili basandosi sul significato semantico invece che su semplici corrispondenze di parole chiave.

Per testare il salvataggio e la ricerca semantica degli articoli, possiamo interrogare gli endpoint eposti:

# Creazione di un nuovo articolo
curl -X POST "http://localhost:8080/api/articoli" -H "Content-Type: application/json" -d '{
  "titolo": "Introduzione a Spring Boot",
  "sommario": "Una panoramica delle funzionalità di Spring Boot",
  "contenuto": "Spring Boot è un framework...",
  "autore": "Mario Rossi",
  "categoria": "Java",
  "embedding": [0.1, 0.2, 0.3, 0.4, 0.5, ... ]
}'

# Ricerca di articoli simili
curl -X GET "http://localhost:8080/api/articoli/cerca/simili?vector=0.1,0.2,0.3,0.4,0.5,...&limit=3"

# Ricerca di articoli simili nella categoria Java
curl -X GET "http://localhost:8080/api/articoli/cerca/categoria/Java?vector=0.1,0.2,0.3,0.4,0.5,...&limit=3"

Conclusione

Abbiamo creato un'applicazione Spring Boot con PostgreSQL e pgvector per eseguire ricerche vettoriali. Questo approccio è utile in scenari come la ricerca di immagini, il NLP e le raccomandazioni basate su embeddings.

Nei prossimi articoli cercheremo di trasformare questo piccolo esempio arricchendolo delle seguenti funzionalità:
  • creazione di un vero e proprio motore di ricerca per un blog
  • generazione automaticamente degli embedding del testo usando modelli come OpenAI, Cohere o ed altri modelli open source
  • implementazione di una funzione "articoli correlati" che mostra contenuti semanticamente simili

Comments

Popular posts from this blog

QUEI COPIONI DEGLI ETF

STRATEGIE D'INVESTIMENTO A CONFRONTO: DOLLAR-COST AVERAGING VS VALUE AVERAGING

SOFTWARE TESTER ONLINE CON TESTBIRDS

UNA PICCOLA INTRODUZIONE A DART

HO COMPRATO UNO XIOAMI 14 ULTRA

AGGIORNAMENTO INVESTIMENTI 1° TRIMESTRE 2024

AGGIORNAMENTO INVESTIMENTI 2° TRIMESTRE 2024