PRODPiattaforma BaaS europea sovranaApri Dashboard →

IA nativa · 10 lettura minima

Tutorial: pipeline RAG con Postgres e pgvector

Affane Daylami · Fondateur · 13 aprile 2026

Torniamo al blog

Costruire una pipeline RAG su Postgres generalmente richiede l'assemblaggio di diversi pezzi da soli: un'affettatrice di testo, una chiamata di incorporamento, una tabella pgvettoriale, una query di somiglianza. Questa è la classica LangChain + PGVectorchain. Su Aurabase, questa pipeline esiste già lato server: due chiamate, ragIngest() e rag(), la sostituiscono, basandosi su PostgreSQL e pgvettori standard, senza una base vettoriale proprietaria da aggiungere.

Questo testo inglese è stato generato automaticamente dall'originale francese e non è stato ancora rivisto.
Questa pagina è stata tradotta automaticamente. Fa fede la versione inglese.

Il RAG nativo fa parte dell'intelligenza artificiale nativaintegrata nel backend Aurabase, insieme a NL2SQL: non un servizio di terze parti da assemblare su una base generale. Prerequisiti per seguire questo tutorial: un progetto Aurabase esistente, un provider LLM configurato (OpenAI o Google Gemini per gli incorporamenti, uno dei tre provider nativi per la generazione) e una chiave API del progetto.

L'essenziale
  • Una pipeline Aurabase RAG è composta da due chiamate: ragIngest() per indicizzare un documento, rag() per interrogare e generare una risposta. Chunking, incorporamenti e ricerca vettoriale sono gestiti lato server.
  • Sotto il cofano, ci sono PostgreSQL e pgvector standard: una tabella embeddings con una colonna vettoriale per classe di dimensione (768, 1536, 3072) e un indice HNSW parziale per classe.
  • Il Chunking utilizza un vero e proprio tokenizer (tiktoken o200k), con sovrapposizione configurabile e protezione anti-esplosione su documenti di grandi dimensioni.
  • Il contenuto recuperato viene neutralizzato prima di essere inserito nel prompt: un documento che tenta di sfuggire al proprio tag per falsificare le istruzioni del sistema viene esplicitamente disinnescato.
  • Solo OpenAI e Gemini generano incorporamenti sul lato Aurabase. Anthropic/Claude non dispone di un'API di incorporamento pubblica, rimane riservata alla generazione della risposta finale.
#
Concetto

Come funziona questa pipeline RAG

La pipeline si svolge in cinque fasi. Dopo l'acquisizione, il testo viene suddiviso, ogni blocco viene vettorizzato in batch e quindi archiviato. Dopo l'interrogazione, la domanda viene vettorizzata a sua volta, rispetto ai blocchi memorizzati dalla similarità del coseno, e gli snippet più vicini vengono inseriti nel prompt inviato al modello di generazione.

Chunking (tiktoken)→Embedding (batch)→archiviazione pgvettoriale (HNSW)→Ricerca di similarità→Generazione aumentata

Un dettaglio architettonico importante nella produzione: durante le chiamate di rete al fornitore di incorporamento o generazione non viene mantenuta alcuna connessione Postgres. La transazione DB si chiude prima della chiamata esterna e si riapre dopo, per non bloccare mai un backend PgBouncer condiviso per la latenza di rete di terze parti.

#
Passaggio 1

Creare il progetto

A differenza di un tipico pgvector self-hosted, non è necessario eseguire CREATE EXTENSION vector o creare tu stesso una tabella per questa pipeline. Il provisioning dello schema embeddings del progetto, con le relative colonne vettoriali e gli indici HNSW, viene eseguito automaticamente al momento della creazione del progetto.

terminalbash
# Conto Aurabase + CLI
npm i -g @aurabase/cli
aura login

# Crea il progetto e collegalo a questa cartella
aura projects create mon-assistant --engine postgres
aura link --project-id <uuid-du-projet>

# Recupera le chiavi API del progetto collegato
aura projects api-keys

Il fornitore di incorporamento viene configurato una volta, lato progetto (Studio → IA → Fornitori). È questo provider che determina la dimensione effettiva dei tuoi vettori, quindi la colonna utilizzata nella tabella embeddings.

#
Passaggio 2

Indicizza i tuoi documenti con ragIngest()

Per indicizzare un documento è sufficiente una chiamata: il testo viene diviso in pezzi, ogni pezzo viene vettorizzato, quindi archiviato nel namespace richiesto. La suddivisione utilizza un vero tokenizzatore (tiktoken, codifica o200k_base), non una semplice suddivisione per spazi, che rimane corretta sul testo senza spazi come alcune lingue asiatiche.

app/api/ingest/route.tstypescript
import { aura } from '@/lib/aurabase'

export async function POST(req: Request) {
  const { content, metadata } = await req.json()

  const { data, error } = await aura.ai.ragIngest({
    namespace: 'docs-produit',
    content,
    metadata,
  })

  if (error) return Response.json({ error }, { status: 400 })
  return Response.json(data) // { id, pezzi }
}

In HTTP non elaborato, il percorso equivalente è POST /v1/ai/{project_id}/rag/ingest, autenticato dalla chiave API del progetto.

terminalbash
curl -X POST https://<votre-gateway>/v1/ai/<project_id>/rag/ingest \
  -H "apikey: <votre-cle-api>" \
  -H "Content-Type: application/json" \
  -d '{
    "namespace": "docs-produit",
    "content": "Le texte complet de votre document ici...",
    "metadata": { "source": "guide-utilisateur.pdf" }
  }'

L'acquisizione è idempotente per impostazione predefinita: un identificatore del documento viene derivato automaticamente (hash SHA-256 del contenuto o metadata.document_id se lo fornisci). La reintegrazione dello stesso contenuto sostituisce i blocchi esistenti invece di duplicarli, rendendo sicuro il processo di sincronizzazione periodica da riprodurre.

#
Passaggio 3

Ciò che effettivamente arriva in Postgres

Nessuna magia proprietaria qui: la tabella che riceve i tuoi vettori è una normale tabella Postgres, con una colonna vector per classe di dimensione e un indice HNSW parziale per colonna (attivo solo sulle righe che la popolano). Ecco la sua vera definizione, semplificata:

diagramma della piattaforma del progetto (semplificato)sql
create table embeddings (
  id               uuid primary key default gen_random_uuid(),
  project_id       text not null,
  namespace        text not null,
  content          text not null,
  embedding_768    vector(768),
  embedding_1536   vector(1536),
  embedding_3072   vector(3072),
  embedding_model  text,
  dims             int,
  metadata         jsonb default '{}'::jsonb,
  created_at       timestamptz not null default now()
);

-- Un indice HNSW parziale per classe di dimensione
create index idx_embeddings_vec_1536 on embeddings
  using hnsw (embedding_1536 vector_cosine_ops)
  where embedding_1536 is not null;

-- Oltre le 2000 dimensioni, HNSW non supporta il tipo vettoriale:
-- fuso in halfvec per la classe 3072
create index idx_embeddings_vec_3072 on embeddings
  using hnsw ((embedding_3072::halfvec(3072)) halfvec_cosine_ops)
  where embedding_3072 is not null;

Ogni ricerca confronta i vettori con l'operatore di distanza coseno (<=>), quello preso di mira dalle classi di operatori vector_cosine_ops e halfvec_cosine_ops dell'indice. Per i dettagli sui compromessi richiamo/latenza di HNSW rispetto a IVFFlat, vedere l'articolo dedicato all'indicizzazione HNSW. Per la scelta della dimensione di incorporamento stessa, vedere il confronto 768 vs 1536 vs 3072.

#
Passaggio 4

Interroga e genera risposta con rag()

Lato query, rag() concatena la vettorizzazione della domanda, la ricerca per similarità nel namespace, la costruzione del prompt aumentato e la chiamata al modello di generazione, in un unico andata e ritorno di rete lato client.

app/api/ask/route.tstypescript
import { aura } from '@/lib/aurabase'

export async function POST(req: Request) {
  const { question } = await req.json()

  const { data, error } = await aura.ai.rag({
    question,
    namespace: 'docs-produit',
  })

  if (error) return Response.json({ error }, { status: 400 })
  return Response.json(data)
}

La risposta riporta la risposta generata e le sue fonti, con il fornitore effettivamente utilizzato:

risposta (estratto)json
{
  "data": {
    "answer": "D'après la documentation, ...",
    "sources": [
      { "id": "...", "content": "...", "similarity": 0.87 }
    ],
    "model": "claude-3-5-sonnet",
    "tokens": 412,
    "provider": "anthropic",
    "fallback_used": false
  }
}

Il contenuto recuperato non viene mai inserito in modo grezzo nel prompt del sistema. Si tratta di dati inaffidabili (caricamento utente, pagina indicizzata): un documento che contiene, ad esempio, un tag di sezione di chiusura seguito da false istruzioni viene neutralizzato prima dell'assemblaggio, le sue parentesi angolari sostituite da parentesi quadre, il testo preservato ma la struttura disinnescata.

Corpus indicizzato secondo un altro modello

Se rag() non trova nulla anche se il tuo spazio dei nomi non è vuoto, la risposta contiene un avvertimento esplicito anziché un silenzio fuorviante: il tuo corpus è probabilmente indicizzato sotto un altro modello o un'altra dimensione di incorporamento. Reindicizzarlo tramite POST /v1/ai/{project_id}/rag/{namespace}/reindex.

#
Confronto

Da LangChain e un pgvettoriale fatto a mano: cosa cambia

Se hai già creato un chatbot RAG su PostgreSQL con LangChain, qui ogni mattone manuale ha un equivalente gestito lato server, senza modificare il database sottostante.

Suddividere il testoRecursiveCharacterTextSplitter da impostareragIngest(): suddivisione integrata dei tiktoken, 512 token/64 sovrapposti per impostazione predefinita
IncorporamentiChiamata manuale a OpenAIEmbeddings, gestione dei limiti batchNuovo tentativo temporaneo incorporato in batch automatico
Memorizzazione dei vettoritabella pgvettoriale + indice HNSW per creare e migrare autonomamenteSchema e indici forniti per progetto
Cerca + richiestaPGVector.similarity_search() quindi assemblando manualmente il promptrag(): ricerca e generazione aumentata in una chiamata
Contenuto recuperatoIniettato così come indicato nel promptNeutralizzazione automatica dei tag della struttura prima del montaggio

Stai migrando un progetto esistente basato su Supabase con questo tipo di assemblaggio manuale? La logica di migrazione per il resto del backend (schema, policy RLS, SDK) è trattata nella nostra guida alla migrazione da Supabase ad Aurabase.

#
Produzione

Impostazioni e limiti di cui essere consapevoli prima di entrare in produzione

Tre impostazioni influiscono direttamente su costo e latenza, tutte verificate nel codice del servizio aura-ai. Il numero di tentativi su una chiamata di incorporamento temporanea (errore di rete, errore del provider 429) è 3 per impostazione predefinita, con un backoff di base di 100 ms. I documenti di grandi dimensioni sono incorporati in sottolotti limitati a 2048 blocchi per impostazione predefinita, per rispettare i limiti API dei fornitori senza fallire su un singolo documento di grandi dimensioni. Un guardrail (10.000 blocchi per impostazione predefinita, configurabile) rifiuta esplicitamente l'acquisizione di un documento che produrrebbe un numero aberrante di blocchi.

Dal lato della ricerca, la dimensione dell'elenco dei candidati HNSW si adatta automaticamente a max(64, top_k × 4): più risultati richiedi, più candidati l'indice esplora per preservarne il ricordo. Un valore fisso rimane possibile tramite una variabile d'ambiente se il tuo corpus ha un profilo particolare.

Gli spazi vettoriali di modelli diversi non vengono mai confrontati tra loro: ogni ricerca rimane limitata al modello di incorporamento corrente, e il cambiamento dei modelli richiede una reindicizzazione esplicita piuttosto che un cambiamento silenzioso che spezzerebbe la coerenza dei risultati.

#
Onestà

Limiti attuali di cui essere consapevoli

La dimensione di incorporamento deve rientrare in una delle tre classi supportate: 768, 1536 o 3072. Un provider che restituisce un'altra dimensione viene rifiutato con un errore esplicito, mai troncato o trasmesso automaticamente.

La generazione di incorporamenti è disponibile solo tramite OpenAI o Google Gemini tra i tre fornitori nativi: Anthropic/Claude non espone un'API pubblica di incorporamenti, quindi viene utilizzata solo per generare la risposta finale in questa pipeline, mai per la vettorizzazione.

L'SDK JavaScript non espone ancora gli override top_k e threshold sulla chiamata rag(): rimangono accessibili in HTTP diretto, limitati rispettivamente a [1, 50] e [0, 1], ma non da aura.ai.rag() come avviene oggi. La soglia di somiglianza predefinita (0,3) è deliberatamente permissiva; restringere il campo a un corpus denso per evitare fonti irrilevanti nel prompt.

#
Vai oltre

Per andare oltre

I RAG coprono domande su contenuti non strutturati (documenti, note, ticket). Per domande sui dati relazionali, NL2SQL nativo di Aurabase traduce direttamente una domanda in SQL convalidato. Per i dettagli sui parametri HNSW e sulle classi di dimensione menzionati sopra, vedere l'articolo sull'indicizzazione HNSW e il confronto delle dimensioni di incorporamento. Il riferimento API completo rimane la documentazione RAG e pgvector e la documentazione AI Gateway.

#
Domande frequenti

Domande frequenti

Devi gestire tu stesso l'estensione pgvettoriale e l'indice HNSW?+
No. Lo schema di incorporamento, l'estensione pgvettoriale e gli indici HNSW parziali (uno per classe di dimensione: 768, 1536, 3072) vengono forniti automaticamente al momento della creazione del progetto. Chiami direttamente ragIngest() quindi rag(); la messa a punto dell'indice (ef_search, iterative_scan mode) rimane accessibile sul lato server se il tuo corpus ha un profilo particolare.
Quale dimensione di incorporamento dovrei scegliere per il mio caso d'uso?+
Aurabase convalida la dimensione restituita dal tuo fornitore rispetto a tre classi supportate: 768, 1536 e 3072. La scelta dipende dal modello di incorporamento configurato (ad esempio text-embedding-3-small su OpenAI produce 1536 dimensioni) e comporta un compromesso tra richiamo, costo e dimensioni di archiviazione dettagliate nel nostro confronto dedicato alle dimensioni di incorporamento.

PRONTO PER L'IMPLEMENTAZIONE?

Il tuo backend in cinque minuti.

Nessuna carta di credito richiesta · 500 MB gratuiti · 50.000 MAU