Programma
Quante volte vorresti che la tua IA potesse fare più che ripetere ciò che già sa? Da persona che ha passato innumerevoli ore a perfezionare e mettere a punto sistemi di IA, posso dirti che i modelli tradizionali, per quanto impressionanti, spesso sembrano bloccati in una bolla limitata a ciò su cui sono stati addestrati. È qui che entra in gioco Agentic RAG.
Nell’Agentic RAG, le capacità decisionali dell’agentic AI incontrano l’adattabilità della retrieval-augmented generation (RAG). Insieme, formano un sistema che può accedere autonomamente alle informazioni, ragionarci sopra e generare contenuti pertinenti.
In precedenza ho scritto un articolo su Agentic RAG che ti aiuterà a capire tutto dal punto di vista teorico. Questo articolo sarà più pratico, quindi concentriamoci su quello.
Panoramica del progetto Agentic RAG
Ti guiderò passo dopo passo in quello che stiamo costruendo. L’obiettivo è creare una pipeline RAG basata sull’architettura mostrata qui:

Passo 1: Richiesta dell’utente
Che si tratti di una domanda semplice o di un problema complesso, tutto parte da una richiesta dell’utente. Questa è la scintilla che mette in moto la nostra pipeline.
Passo 2: Instradamento della richiesta
Poi il sistema verifica: Posso rispondere a questo?
Sì? Attrae dalla conoscenza esistente e consegna subito la risposta.
No? È ora di scavare più a fondo! La richiesta viene inoltrata al passo successivo.
Passo 3: Recupero dei dati
Se la risposta non è immediatamente disponibile, la pipeline esplora due possibili fonti:
- Documenti locali: useremo un PDF pre-elaborato come base di conoscenza, in cui il sistema cerca parti di informazione pertinenti.
- Ricerca su Internet: se serve più contesto, la pipeline può attingere a fonti esterne per recuperare informazioni aggiornate.
Passo 4: Costruzione del contesto
I dati recuperati, che provengano dal PDF o dal web, vengono poi assemblati in un contesto coerente. È come raccogliere tutti i pezzi del puzzle prima di metterli insieme.
Passo 5: Generazione della risposta finale
Infine, questo contesto viene passato a un large language model (LLM) per comporre una risposta chiara e accurata. Non si tratta solo di recuperare dati, ma di capirli e presentarli nel modo migliore.
Alla fine, avremo una pipeline RAG intelligente ed efficiente che può rispondere dinamicamente alle richieste con contesto reale.
Fonte dati locale
Per iniziare, userò un documento reale come fonte dati locale. Il documento con cui lavoreremo è nientemeno che Generative AI Principles. È pieno di spunti utili, quindi sarà il caso di test perfetto per la nostra pipeline. Ti servirà anche un riassunto del file che uso a scopo di instradamento. Puoi scaricare il file di riepilogo da qui.
Ecco tutto ciò che ti serve per iniziare, passo dopo passo.
Prerequisiti
Prima di partire, ci sono un paio di cose che devi avere pronte:
- Groq API key: ti serve una chiave API di Groq, che puoi ottenere qui: Groq API Console
- Gemini API key: Gemini ti aiuterà nell’orchestrazione con agenti, Groq dà risposte super rapide ma potrebbe bloccarsi con i limiti di rate attuali: Gemini API Console
- Serper.dev API key: per qualsiasi funzionalità di ricerca su Internet useremo Serper.dev. Ottieni qui la tua chiave API: Serper.dev API Key
Installazione dei pacchetti
Assicuriamoci di avere gli strumenti giusti installati. Apri il terminale ed esegui i seguenti comandi per installare i pacchetti Python necessari:
pip install langchain-groq faiss-cpu crewai serper pypdf2 python-dotenv setuptools sentence-transformers huggingface distutils`
Configurazione dell’ambiente
Una volta che hai chiavi e pacchetti pronti, sei a posto! Ti consiglio di salvare le chiavi API in un file .env o in modo sicuro nel codice. Ecco un esempio di come potrebbe apparire il file .env:
import os from dotenv
import load_dotenv from langchain.vectorstores
import FAISS from langchain.document_loaders
import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_huggingface.embeddings import HuggingFaceEmbeddings
from langchain_groq import ChatGroq
from crewai_tools import SerperDevTool
from crewai import Agent, Task, Crew, LLM
load_dotenv()
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
SERPER_API_KEY = os.getenv("SERPER_API_KEY")
GEMINI=os.getenv("GEMINI")
Ho caricato le variabili d’ambiente per gestire in sicurezza chiavi API e dati sensibili senza inserirli hardcoded. Questo mantiene lo script sicuro e mi permette di cambiare le chiavi in un unico posto (file .env) se necessario.
In poche parole, ecco cosa fanno questi pacchetti e funzioni importati:
- os: fornisce utilità per interfacciarsi con il sistema operativo.
- dotenv: carica variabili d’ambiente dai file
.env. - FAISS: archivio vettoriale per ricerca per similarità e retrieval.
- PyPDFLoader: estrae contenuti testuali da documenti PDF.
- RecursiveCharacterTextSplitter: divide il testo in parti gestibili in modo ricorsivo.
- HuggingFaceEmbeddings: genera embedding di testo usando modelli HuggingFace.
- ChatGroq: interfaccia chat con accelerazione hardware Groq.
- SerperDevTool: strumento per effettuare ricerche sul web.
- ScrapeWebsiteTool: estrae dati dalle pagine web.
- Agent: definisce un’entità IA con ruoli e obiettivi.
- Task: rappresenta un obiettivo o un’azione specifica per un agente.
- Crew: orchestra agenti e task per workflow coordinati.
- LLM: interfaccia con large language model per generare risposte testuali.
Inizializzazione degli LLM
Iniziamo configurando due modelli linguistici:
llm: per compiti generali come instradamento e generazione di risposte. Useremo il modellollama-3.3-70b-specdec.crew_llm: specifico per l’agente di web scraping, dato che richiede una configurazione leggermente diversa (come la temperatura per output più creativi). Useremo il modellogemini/gemini-1.5-flash.
# Initialize LLM
llm = ChatGroq(
model="llama-3.3-70b-specdec",
temperature=0,
max_tokens=500,
timeout=None,
max_retries=2,
)
crew_llm = LLM(
model="gemini/gemini-1.5-flash",
api_key=GEMINI,
max_tokens=500,
temperature=0.7
)
Aggiungere il decisore
La funzione check_local_knowledge() qui sotto funge da decisore. Ho creato un prompt in cui ho passato la richiesta dell’utente e un po’ di contesto locale (dal PDF). Il modello risponde con "Yes" o "No", indicando se ha abbastanza informazioni locali per rispondere alla richiesta. In caso contrario, ricorreremo al web scraping.
def check_local_knowledge(query, context):
"""Router function to determine if we can answer from local knowledge"""
prompt = '''Role: Question-Answering Assistant
Task: Determine whether the system can answer the user's question based on the provided text.
Instructions:
- Analyze the text and identify if it contains the necessary information to answer the user's question.
- Provide a clear and concise response indicating whether the system can answer the question or not.
- Your response should include only a single word. Nothing else, no other text, information, header/footer.
Output Format:
- Answer: Yes/No
Study the below examples and based on that, respond to the last question.
Examples:
Input:
Text: The capital of France is Paris.
User Question: What is the capital of France?
Expected Output:
Answer: Yes
Input:
Text: The population of the United States is over 330 million.
User Question: What is the population of China?
Expected Output:
Answer: No
Input:
User Question: {query}
Text: {text}
'''
formatted_prompt = prompt.format(text=context, query=query)
response = llm.invoke(formatted_prompt)
return response.content.strip().lower() == "yes"
Agente di ricerca e scraping web
Successivamente, configuriamo un agente di ricerca e scraping web usando la crewai library. Questo agente usa uno strumento di ricerca (SerperDevTool) per trovare articoli correlati alla richiesta dell’utente. La descrizione del task assicura che l’agente sappia cosa recuperare e ne riassuma i contenuti rilevanti. È come inviare un lavoratore specializzato a prelevare dati da Internet.
Poi, la funzione get_web_content() esegue l’agente di web scraping. Invia la query come input e recupera un riassunto conciso dell’articolo più pertinente. Restituisce il risultato grezzo, che diventa il nostro contesto se il router decide che non abbiamo abbastanza informazioni locali.
def setup_web_scraping_agent():
"""Setup the web scraping agent and related components"""
search_tool = SerperDevTool() # Tool for performing web searches
scrape_website = ScrapeWebsiteTool() # Tool for extracting data from websites
# Define the web search agent
web_search_agent = Agent(
role="Expert Web Search Agent",
goal="Identify and retrieve relevant web data for user queries",
backstory="An expert in identifying valuable web sources for the user's needs",
allow_delegation=False,
verbose=True,
llm=crew_llm
)
# Define the web scraping agent
web_scraper_agent = Agent(
role="Expert Web Scraper Agent",
goal="Extract and analyze content from specific web pages identified by the search agent",
backstory="A highly skilled web scraper, capable of analyzing and summarizing website content accurately",
allow_delegation=False,
verbose=True,
llm=crew_llm
)
# Define the web search task
search_task = Task(
description=(
"Identify the most relevant web page or article for the topic: '{topic}'. "
"Use all available tools to search for and provide a link to a web page "
"that contains valuable information about the topic. Keep your response concise."
),
expected_output=(
"A concise summary of the most relevant web page or article for '{topic}', "
"including the link to the source and key points from the content."
),
tools=[search_tool],
agent=web_search_agent,
)
# Define the web scraping task
scraping_task = Task(
description=(
"Extract and analyze data from the given web page or website. Focus on the key sections "
"that provide insights into the topic: '{topic}'. Use all available tools to retrieve the content, "
"and summarize the key findings in a concise manner."
),
expected_output=(
"A detailed summary of the content from the given web page or website, highlighting the key insights "
"and explaining their relevance to the topic: '{topic}'. Ensure clarity and conciseness."
),
tools=[scrape_website],
agent=web_scraper_agent,
)
# Define the crew to manage agents and tasks
crew = Crew(
agents=[web_search_agent, web_scraper_agent],
tasks=[search_task, scraping_task],
verbose=1,
memory=False,
)
return crew
def get_web_content(query):
"""Get content from web scraping"""
crew = setup_web_scraping_agent()
result = crew.kickoff(inputs={"topic": query})
return result.raw
Creazione del database vettoriale
La funzione setup_vector_db() configura il database vettoriale a partire da un file PDF. Ecco come l’ho fatto, passo dopo passo:
- Carica il PDF: ho usato
PyPDFLoaderper estrarre il contenuto. - Suddividi il testo: ho diviso il contenuto del PDF in chunk più piccoli (1000 caratteri con sovrapposizione di 50) usando
RecursiveCharacterTextSplitter. Questo rende i dati gestibili e ricercabili. - Crea il DB vettoriale: ho creato embedding dei chunk usando un modello sentence-transformer e li ho archiviati in un database vettoriale FAISS. Questo mi consente di cercare rapidamente il testo rilevante.
Dopodiché, la funzione get_local_content() interroga il database vettoriale per i 5 chunk più pertinenti alla richiesta dell’utente. Combina questi chunk in una singola stringa di contesto.
def setup_vector_db(pdf_path):
"""Setup vector database from PDF"""
# Load and chunk PDF
loader = PyPDFLoader(pdf_path)
documents = loader.load()
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=50
)
chunks = text_splitter.split_documents(documents)
# Create vector database
embeddings = HuggingFaceEmbeddings(
model_name="sentence-transformers/all-mpnet-base-v2"
)
vector_db = FAISS.from_documents(chunks, embeddings)
return vector_db
def get_local_content(vector_db, query):
"""Get content from vector database"""
docs = vector_db.similarity_search(query, k=5)
return " ".join([doc.page_content for doc in docs])
Generazione della risposta finale
Una volta ottenuto il contesto (da documenti locali o da web scraping), lo passo al modello linguistico insieme alla richiesta dell’utente. L’LLM genera la risposta finale combinando contesto e query in un formato conversazionale.
Ecco il flusso dell’elaborazione principale della richiesta:
- Instradamento: uso
check_local_knowledge()per decidere se il contenuto del PDF locale ha abbastanza dati per rispondere. - Se "Yes", prelevo i chunk rilevanti dal database vettoriale.
- Se "No", cerco sul web articoli pertinenti.
- Risposta finale: una volta ottenuto il contesto, lo passo all’LLM per generare la risposta definitiva.
def generate_final_answer(context, query):
"""Generate final answer using LLM"""
messages = [
(
"system",
"You are a helpful assistant. Use the provided context to answer the query accurately.",
),
("system", f"Context: {context}"),
("human", query),
]
response = llm.invoke(messages)
return response.content
def process_query(query, vector_db, local_context):
"""Main function to process user query"""
print(f"Processing query: {query}")
# Step 1: Check if we can answer from local knowledge
can_answer_locally = check_local_knowledge(query, local_context)
print(f"Can answer locally: {can_answer_locally}")
# Step 2: Get context either from local DB or web
if can_answer_locally:
context = get_local_content(vector_db, query)
print("Retrieved context from local documents")
else:
context = get_web_content(query)
print("Retrieved context from web scraping")
# Step 3: Generate final answer
answer = generate_final_answer(context, query)
return answer
Infine, mettiamo tutto insieme con la funzione main():
def main():
# Setup
pdf_path = "genai-principles.pdf"
# Initialize vector database
print("Setting up vector database...")
vector_db = setup_vector_db(pdf_path)
# Get initial context from PDF for routing
local_context = get_local_content(vector_db, "")
# Example usage
query = "What is Agentic RAG?"
result = process_query(query, vector_db, local_context)
print("\nFinal Answer:")
print(result)
if __name__ == "__main__":
main()
Sopra, abbiamo:
- Configurato il database vettoriale: caricato ed elaborato il PDF.
- Inizializzato il contesto locale: abbiamo recuperato contenuto generale dal PDF da passare come contesto al router.
- Eseguito una query di esempio: abbiamo lanciato una richiesta di prova (
"What is Agentic RAG?") e stampato la risposta finale.
Questo è l’output del programma:
Agentic RAG is a technique for building Large Language Model (LLM) powered applications that incorporates AI agents. It is an extension of the traditional Retrieval-Augmented Generation (RAG) approach, which uses an external knowledge source to provide the LLM with relevant context and reduce hallucinations.
In traditional RAG pipelines, the retrieval component is typically composed of an embedding model and a vector database, and the generative component is an LLM. At inference time, the user query is used to run a similarity search over the indexed documents to retrieve the most similar documents to the query and provide the LLM with additional context.
Agentic RAG, on the other hand, introduces AI agents that are designed to interact with the user query and provide additional context to the LLM. These agents can be thought of as "virtual assistants" that help the LLM to better understand the user's intent and retrieve relevant documents.
The key components of Agentic RAG include:
1. AI agents: These are the virtual assistants that interact with the user query and provide additional context to the LLM.
2. Retrieval component: This is the component that retrieves the most similar documents to the user query.
3. Generative component: This is the component that uses the retrieved documents to generate the final output.
Agentic RAG has several benefits, including:
1. Improved accuracy: By providing additional context to the LLM, Agentic RAG can improve the accuracy of the generated output.
2. Enhanced user experience: Agentic RAG can help to reduce the complexity of the user interface and provide a more natural and intuitive experience.
3. Increased flexibility: Agentic RAG can be easily extended to support new use cases and applications.
However, Agentic RAG also has some limitations, including:
1. Increased complexity: Agentic RAG requires additional components and infrastructure, which can increase the complexity of the system.
2. Higher computational requirements: Agentic RAG requires more computational resources to handle the additional complexity of the AI agents and the retrieval component.
3. Training requirements: Agentic RAG requires more data and training to learn the behaviour of the AI agents and the retrieval component.
Spiegazione dell’output
Quando ho chiesto "What is Agentic RAG?", che non era esplicitamente presente nel documento fornito, il sistema ha dimostrato flessibilità e intelligenza. Gli agenti di crewAI hanno instradato correttamente la richiesta, riconoscendo tramite il router che serviva contesto esterno. Gli agenti hanno collaborato per individuare l’articolo più pertinente sul web, analizzarne il contenuto e generare una risposta ben informata.
Il sistema ha fornito una spiegazione chiara dell’Agentic RAG, dei suoi componenti (agenti IA, retrieval e parte generativa), dei benefici (accuratezza, esperienza utente e flessibilità) e dei limiti (complessità, esigenze computazionali e requisiti di training).
Questo mette in evidenza la capacità della pipeline di recuperare dinamicamente il contesto e offrire insight precisi, concisi e utili, anche di fronte a richieste che vanno oltre il dataset iniziale.
Conclusione
La pipeline che abbiamo costruito è un processo dinamico e multi-step che gestisce in modo efficiente le richieste degli utenti combinando conoscenza esistente e fonti esterne.
Il sistema può adattarsi dinamicamente a richieste diverse e fornire risposte accurate e facili da comprendere. Con l’integrazione di dati locali, ricerche live su Internet e un’interfaccia fluida, la pipeline RAG si dimostra una soluzione solida e pratica per casi d’uso reali. Come prossimo passo, prova a costruire qualcosa di tuo seguendo il workflow che abbiamo visto.
Senior GenAI Engineer e Content Creator che ha totalizzato 20 milioni di visualizzazioni condividendo conoscenze su GenAI e data science.


