DEV Community

Web Novis
Web Novis

Posted on

Costruire un sistema RAG (Retrieval-Augmented Generation) in Node.js con OpenAI e Pinecone published: true

L'integrazione di base delle API di OpenAI è ormai una commodity. La vera sfida ingegneristica nello sviluppo di applicazioni AI-driven per il settore enterprise è la gestione della conoscenza privata: come permettere a un LLM (Large Language Model) di rispondere in modo accurato interrogando database aziendali proprietari, senza riaddestrare il modello da zero (fine-tuning)?

La risposta architetturale standard è il RAG (Retrieval-Augmented Generation). In questo articolo scomponiamo la creazione di un microservizio Node.js capace di vettorializzare documenti, archiviarli in un Vector Database (Pinecone) e utilizzarli come contesto dinamico per le chiamate a GPT-5.2.

Fase 1: Architettura e Dipendenze

Il flusso logico si divide in tre vettori:

  1. Ingestion: Trasformazione del testo in coordinate numeriche (Embeddings).
  2. Retrieval: Ricerca semantica nel database vettoriale in base alla query dell'utente.
  3. Generation: Iniezione dei risultati trovati nel prompt dell'LLM per generare la risposta finale.

Inizializziamo il nostro ambiente isolando le dipendenze essenziali:

bash
npm install @pinecone-database/pinecone openai dotenv
Enter fullscreen mode Exit fullscreen mode

Fase 2: Generazione e Archiviazione degli Embeddings (Ingestion)
Prima che l'LLM possa "leggere" i nostri dati, dobbiamo tradurli nella sua lingua nativa: i vettori. Utilizzeremo il modello text-embedding-3-small di OpenAI per convertire un blocco di testo in un array di 1536 dimensioni e salvarlo su Pinecone.

JavaScript

require('dotenv').config();
const { OpenAI } = require('openai');
const { Pinecone } = require('@pinecone-database/pinecone');

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const pinecone = new Pinecone({ apiKey: process.env.PINECONE_API_KEY });

async function vectorizeAndStoreDocument(documentText, documentId) {
    try {
        // 1. Generazione dell'Embedding
        const embeddingResponse = await openai.embeddings.create({
            model: "text-embedding-3-small",
            input: documentText,
        });
        const vector = embeddingResponse.data[0].embedding;

        // 2. Connessione all'indice Pinecone
        const index = pinecone.Index(process.env.PINECONE_INDEX_NAME);

        // 3. Upsert del vettore nel database con i metadati testuali
        await index.upsert([{
            id: documentId,
            values: vector,
            metadata: { text: documentText }
        }]);

        console.log(`Documento ${documentId} vettorializzato e salvato con successo.`);
    } catch (error) {
        console.error("Errore critico durante la fase di ingestion:", error);
    }
}
Enter fullscreen mode Exit fullscreen mode

Fase 3: Ricerca Semantica e Generazione Aumentata (Retrieval & Generation)
Quando un utente pone una domanda, non inviamo subito la query a GPT-5.2. Prima, vettorializziamo la domanda stessa, cerchiamo in Pinecone i blocchi di testo semanticamente più vicini e li usiamo per "educare" temporaneamente il modello.

JavaScript

async function askAIWithContext(userQuestion) {
    try {
        // 1. Vettorializzazione della domanda dell'utente
        const queryEmbeddingRes = await openai.embeddings.create({
            model: "text-embedding-3-small",
            input: userQuestion,
        });
        const queryVector = queryEmbeddingRes.data[0].embedding;

        // 2. Ricerca nel Vector DB (Top 3 risultati più pertinenti)
        const index = pinecone.Index(process.env.PINECONE_INDEX_NAME);
        const searchResults = await index.query({
            vector: queryVector,
            topK: 3,
            includeMetadata: true
        });

        // 3. Estrazione del contesto dai metadati
        const contextChunks = searchResults.matches.map(match => match.metadata.text).join("\n---\n");

        // 4. Costruzione del Prompt Ibrido (System + Context + User)
        const systemPrompt = `Sei un assistente tecnico. Rispondi alla domanda dell'utente basandoti ESCLUSIVAMENTE sul contesto fornito qui sotto. Se la risposta non è nel contesto, dichiara di non saperlo. \n\nCONTESTO:\n${contextChunks}`;

        const completion = await openai.chat.completions.create({
            model: "gpt-5.2",
            messages: [
                { role: "system", content: systemPrompt },
                { role: "user", content: userQuestion }
            ],
            temperature: 0.2 // Bassa temperatura per favorire la precisione analitica
        });

        return completion.choices[0].message.content;

    } catch (error) {
        console.error("Errore nella pipeline di RAG:", error);
        throw error;
    }
}
Enter fullscreen mode Exit fullscreen mode

Considerazioni Architetturali
Implementare una pipeline RAG richiede una gestione rigorosa del chunking (la divisione dei documenti di grandi dimensioni in frammenti sovrapposti) e una profonda comprensione dei limiti fisici del database vettoriale scelto.

Quando ci occupiamo dello sviluppo di piattaforme web e soluzioni software custom, isolare queste logiche di retrieval in microservizi dedicati (ad esempio tramite container Docker) diventa essenziale per garantire la scalabilità orizzontale dell'infrastruttura.

Quale strategia di chunking utilizzate solitamente per i documenti PDF o non strutturati prima dell'embedding? Discutiamone nei commenti per confrontare gli approcci algoritmici.

Top comments (0)