Agenti e RAG
su misura

Agenti e RAG
su misura
I modelli linguistici (LLM) hanno una conoscenza ampia ma non sempre specializzata. Come possiamo adattarli per renderli esperti su un dominio di conoscenza privato e circoscritto, come quello contenuto in un documento?
La risposta risiede nella sinergia di due tecnologie chiave: gli Agenti AI e il pattern RAG (Retrieval-Augmented Generation).
In questo articolo affronteremo sia la fase di ingestion sia quella di utilizzo di un Agente AI, utilizzando Mastra AI (TypeScript). Sfrutteremo questo framewor, che sta sempre più prendendo piede, per leggere un file PDF e, tramite un database, istruire un Agente specifico per le ricerche.
Il panorama offre diverse soluzioni, non più solo in Python, ma anche in altri linguaggi. Fra questi JavaScript e TypeScript stanno emergendo come solide alternative. Il framework Mastra AI ha diversi punti a favore:
Come sempre adottare un framework che copre così tante aree necessita di una valutazione attenta, progetto per progetto. Ma è indubbio che questa sia una soluzione vincente in diverse situazioni.
Prima di addentrarci nell'implementazione, nonostante siano ormai di largo utilizzo, è necessario comprendere bene i due pilastri di questo articolo. Per entrambi questi punti useremo solo ed esclusivamente Mastra AI.
È un approccio architetturale che potenzia gli LLM ancorandoli a fonti di dati esterne. Invece di basarsi unicamente sulla sua conoscenza pre-addestrata, il modello "recupera" (retrieve) le informazioni più pertinenti da una sorgente dati (ad esempio, il nostro PDF) e le utilizza come contesto per "generare" (generate) una risposta accurata e contestualizzata. Questo processo minimizza il rischio di "allucinazioni" (risposte inventate) e garantisce che le risultanze siano fedeli alla fonte.
Un agente AI non è semplicemente un modello che risponde a un input. È un sistema più complesso, progettato per essere autonomo ed efficace. Un agente riceve un obiettivo, ragiona su come raggiungerlo, e ha a disposizione una serie di "strumenti" (tools) che può decidere di utilizzare per compiere azioni. Nel nostro caso, lo strumento principale sarà la capacità di ricercare informazioni in una base di conoscenza (un database ).
La combinazione di un agente AI con un meccanismo RAG crea un sistema potente, capace di dialogare in modo intelligente e informato su argomenti specifici.
Per l'esempio useremo un solo libro, ma ovviamente il sistema è facilmente modificabile e usabile su diverse cartelle, database o altri data sources opportunamente collegati.
Di seguito, la classe (contenuta) che implementa l'agente:
import { google } from '@ai-sdk/google'
import { Agent } from '@mastra/core/agent'
import { Memory } from '@mastra/memory'
import { LibSQLStore } from '@mastra/libsql'
import { createVectorQueryTool } from '@mastra/rag'
const model = google.textEmbeddingModel('gemini-embedding-001')
// Create a tool for semantic search over embeddings
const vectorQueryTool = createVectorQueryTool({
vectorStoreName: 'libSqlVector',
indexName: 'books',
model: model
})
export const researchAgent = new Agent({
name: 'Research Assistant',
instructions: `You are a helpful research assistant ...`,
model: google('gemini-2.5-flash'),
tools: {
vectorQueryTool
},
memory: new Memory({
storage: new LibSQLStore({
url: 'file:./database/mastra.db'
})
})
})
Come si nota, la sua semplicità è anche nel passare e configurare diversi provider AI, come con gemini (in questo caso) ma è possibile accedere anche ad altri passando addirittura da modelli federati e locali grazie a Ollama.
Di seguito un esempio di risposte date dall'agente sul libro de "Il fu Mattia Pascal" di Pirandello. Da notare che, all'ultima domanda, l'agente non ha saputo rispondere, nonostante sia un libro famoso di Pirandello, perché diverso da quello "acquisito" durante la fase di ingestion.
Output src/demo.ts
Le varie risposte che si leggono dalla console precedente sono possibili solo perché il VectorQueryTool ha la capacità di accedere al database ed eseguire delle ricerche per similarità ottenedo così i vari chunk pertinenti e da qui costruire una risposta sensata.
Per rendere "comprensibile" il libro e quindi trasformarlo in una conoscenza interrogabile è necessario prima di tutto eseguirne l'estrazione del testo (tramite librerie apposite o anche sfruttando servizi AI, ad esempio come Mistral OCR). Una volta fatto ciò, per ogni chunk (frammento di testo) si crea la sua rappresentazione tramite embeddings (ossia in vettori multidimensionali) che permettono così di indicizzare il contenuto e soprattutto permetterne la ricerca per vicinanza.
Sebbene sembri un compito difficile, ormai molti framework e librerie offrono ottime implementazioni. Si veda questo come esempio:
import { MDocument } from '@mastra/rag'
import { google } from '@ai-sdk/google'
import { embedMany } from 'ai'
import { mastra } from './mastra/index.ts'
import { extractTextFromPath } from './mastra/utils.ts'
let { extractedText: paperText } = await extractTextFromPath('./inputs/libro.pdf')
paperText = paperText.slice(0, 10000) // Limit to first 10k characters
// Create document and chunk it
const doc = MDocument.fromText(paperText)
const chunks = await doc.chunk({
strategy: 'recursive',
maxSize: 512,
overlap: 50,
separators: ['\n\n', '\n', ' ']
})
const model = google.textEmbeddingModel('gemini-embedding-001')
// Generate embeddings
const { embeddings } = await embedMany({
model: model,
values: chunks.map((chunk) => chunk.text),
providerOptions: {
google: {
taskType: 'QUESTION_ANSWERING'
}
}
})
// Get the vector store instance from Mastra
const vectorStore = mastra.getVector('libSqlVector')
// Create an index for paper chunks
// 1536 for OpenAI text-embedding-3-small, 768 for google text-embedding-001, 3072 for gemini-embedding-001
await vectorStore.createIndex({
indexName: 'books',
dimension: 3072
})
// Store embeddings
await vectorStore.upsert({
indexName: 'books',
vectors: embeddings,
metadata: chunks.map((chunk) => ({
text: chunk.text,
source: 'transformer-book'
}))
})
Sembra strano ma a parte un file di utilities e l'istanza di Mastra, non è necessario altro codice.
La scelta del database LibSQL è stata fatta per semplicità, ossia localmente viene creato un file db contenente tutti i dati. In casi reali e pronti alla produzione, molto probabilmente avrete bisogno di altri tipi di database, nel caso vi consigliamo di guardare PostgreSQL con la sua estensione pgVector.
Nonostante abbia diverse qualità, un ecosistema molto variegato e la sua forte operabilità con l'AI SDK di Vercel, come sempre, il consiglio è quello di dargli sì un'occasione (non ve ne pentirete) ma non di adottarlo al 100% in tutte le situazioni senza aver fatto le dovute considerazioni.
Se vi serve aiuto in questa fase, siamo qui a disposizione.
Per ulteriori informazioni si possono consultare le seguenti risorse:
Data di pubblicazione: 26 agosto 2025
Ultima revisione: 26 agosto 2025