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