CREARE UN'APPLICAZIONE SPRING BOOT CON DATABASE VETTORIALE POSTGRESQL
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
- 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
Post a Comment