Ga naar hoofdinhoud

Agentic RAG: stapsgewijze tutorial met demoproject

Leer hoe je vanaf nul een Agentic RAG-pijplijn bouwt, met lokale databronnen en webscraping om contextbewuste antwoorden op gebruikersvragen te genereren.
Bijgewerkt 1 jun 2026  · 12 min lezen

Hoe vaak wou je dat je AI meer kon dan alleen herhalen wat hij al weet? Als iemand die ontelbare uren heeft besteed aan het verfijnen en tweaken van AI-systemen, kan ik je vertellen dat traditionele modellen, hoe indrukwekkend ook, vaak aanvoelen alsof ze vastzitten in een bubbel die beperkt is tot waar ze op zijn getraind. Daar komt Agentic RAG om de hoek kijken.

Bij agentic RAG komen de beslisvaardigheden van agentic AI samen met de flexibiliteit van retrieval-augmented generation (RAG). Samen vormen ze een systeem dat zelfstandig relevante informatie kan opzoeken, ermee kan redeneren en die kan genereren.

Eerder schreef ik een artikel over agentic RAG dat je helpt om alles vanuit theoretisch oogpunt te begrijpen. Dit artikel is praktischer, dus laten we daarop focussen.

Overzicht van het Agentic RAG-project

Ik neem je stap voor stap mee door wat we gaan bouwen. We creëren een RAG-pijplijn op basis van de architectuur die je hier ziet:

overzicht demoproject agentic rag

Stap 1: Vraag van de gebruiker

Of het nu gaat om een eenvoudige vraag of een complex probleem, het begint allemaal met een vraag van de gebruiker. Dat is de vonk die onze pijplijn in werking zet.

Stap 2: De vraag routeren

Vervolgens checkt het systeem: Kan ik dit beantwoorden?

Ja? Dan wordt er geput uit bestaande kennis en volgt het antwoord meteen.

Nee? Tijd om dieper te graven! De vraag wordt doorgestuurd naar de volgende stap.

Stap 3: De data ophalen

Als het antwoord niet direct beschikbaar is, duikt de pijplijn in twee mogelijke bronnen:

  1. Lokale documenten: We gebruiken een vooraf verwerkte PDF als onze kennisbank, waarin het systeem zoekt naar relevante stukken informatie.
  2. Internetzoektocht: Als er meer context nodig is, kan de pijplijn externe bronnen raadplegen om actuele informatie te scrapen.

Stap 4: De context opbouwen

De opgehaalde data, of die nu uit de PDF of van het web komt, wordt vervolgens samengebracht tot een samenhangende, opgehaalde context. Zie het als alle puzzelstukjes verzamelen voordat je ze in elkaar zet.

Stap 5: Het definitieve antwoord genereren

Tot slot geven we deze context door aan een large language model (LLM) om een duidelijk en accuraat antwoord te formuleren. Het draait niet alleen om data ophalen, maar om begrijpen en het op de best mogelijke manier presenteren.

Aan het einde hebben we een slimme, efficiënte RAG-pijplijn die dynamisch op vragen kan reageren met context uit de echte wereld.

Lokale databron

Om te beginnen gebruik ik een document uit de praktijk als onze lokale databron. Het document waarmee we werken is niemand minder dan Generative AI Principles. Het zit boordevol waardevolle inzichten, dus het is de perfecte testcase voor onze pijplijn. Je hebt ook een samenvatting van het bestand nodig die ik gebruik voor het routeren. Je kunt het samenvattingsbestand hier vandaan halen hier.

Hier is alles wat je nodig hebt om te beginnen, stap voor stap.

Vereisten

Voordat we erin duiken, zijn er een paar dingen die je geregeld moet hebben:

  1. Groq API-sleutel: Je hebt een API-sleutel van Groq nodig, die je hier kunt ophalen: Groq API Console
  2. Gemini API-sleutel: Gemini helpt je bij het orkestreren met agents; Groq geeft supersnelle reacties maar kan vastlopen door de huidige snelheidslimieten: Gemini API Console
  3. Serper.dev API-sleutel: Voor alle internetzoekfunctionaliteit gebruiken we Serper.dev. Haal je API-sleutel hier: Serper.dev API Key

Pakketten installeren

Laten we zorgen dat we de juiste tools geïnstalleerd hebben. Open je terminal en voer de volgende commando’s uit om de benodigde Python-pakketten te installeren:

pip install langchain-groq faiss-cpu crewai serper pypdf2 python-dotenv setuptools sentence-transformers huggingface distutils`

De omgeving instellen

Zodra je de sleutels en pakketten klaar hebt, kun je aan de slag! Ik raad aan om je API-sleutels op te slaan in een.env-bestand of veilig in je codebase. Zo zou je .env-bestand eruit kunnen zien:

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")

Ik heb omgevingsvariabelen geladen zodat ik API-sleutels en gevoelige data veilig kan beheren zonder ze hard te coderen. Zo blijft het script veilig en kan ik sleutels op één plek (.env-bestand) wijzigen als dat nodig is.

Kort samengevat doen deze geïmporteerde pakketten en functies het volgende:

  • os: Biedt hulpprogramma’s voor het besturingssysteem.
  • dotenv: Laadt omgevingsvariabelen uit .env-bestanden.
  • FAISS: Vectorstore voor similariteitszoektocht en retrieval.
  • PyPDFLoader: Haalt tekstinhoud uit PDF-documenten.
  • RecursiveCharacterTextSplitter: Splitst tekst recursief in behapbare stukken.
  • HuggingFaceEmbeddings: Genereert tekstembeddings met HuggingFace-modellen.
  • ChatGroq: Chatinterface versneld door Groq-hardware.
  • SerperDevTool: Tool voor het uitvoeren van webzoekopdrachten.
  • ScrapeWebsiteTool: Haalt data van webpagina’s op.
  • Agent: Definieert een AI-entiteit met rollen en doelen.
  • Task: Stelt een specifiek doel of actie voor een agent voor.
  • Crew: Orkestreert agents en taken voor gecoördineerde workflows.
  • LLM: Interface voor large language models om tekstantwoorden te genereren.

De LLM’s initialiseren

We beginnen met het initialiseren van twee taalmodellen:

  • llm: Voor algemene taken zoals routering en antwoorden genereren. We gebruiken het model llama-3.3-70b-specdec.
  • crew_llm: Specifiek voor de webscraping-agent, omdat die een iets andere configuratie nodig heeft (zoals temperatuur voor creatievere output). We gebruiken het model gemini/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
)

De beslisser toevoegen

De onderstaande functie check_local_knowledge() fungeert als beslisser. Ik maakte een prompt waarin ik de gebruikersvraag en wat lokale context (uit de PDF) meegaf. Het model reageert met "Yes" of "No" en laat zo weten of er lokaal genoeg informatie is om de vraag te beantwoorden. Zo niet, dan schakelen we over op webscraping.

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"

Webzoeken en -scrapen met een agent

Vervolgens zetten we een webzoek- en scraping-agent op met de crewai-bibliotheek. Deze agent gebruikt een zoektool (SerperDevTool) om artikelen te vinden die met de vraag van de gebruiker te maken hebben. De taakbeschrijving zorgt ervoor dat de agent weet wat hij moet ophalen, en hij vat de relevante webinhoud samen. Het is alsof je een gespecialiseerde medewerker eropuit stuurt om data van het internet te halen.

Daarna voert de functie get_web_content() de webscraping-agent uit. Hij stuurt de query als input mee en haalt een bondige samenvatting op van het meest relevante artikel. Hij retourneert het ruwe resultaat, dat onze context wordt als de router beslist dat we niet genoeg lokale informatie hebben.

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

De vectordatabase maken

De functie setup_vector_db() zet de vectordatabase op vanuit een PDF-bestand. Zo deed ik dat stap voor stap:

  1. PDF laden: Ik gebruikte PyPDFLoader om de inhoud te extraheren.
  2. Tekst opdelen: Ik splitste de PDF-inhoud in kleinere stukken (1000 tekens met 50 tekens overlap) met RecursiveCharacterTextSplitter. Zo wordt de data beheersbaar en doorzoekbaar.
  3. Vector-DB maken: Ik embedde de stukken met een sentence-transformer-model en sloeg ze op in een FAISS-vectordatabase. Daarmee kan ik efficiënt naar relevante tekst zoeken.

Daarna haalt de functie get_local_content() de top 5 meest relevante stukken op uit de vectordatabase die met de vraag van de gebruiker te maken hebben. Deze stukken worden samengevoegd tot één string met context. 

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])

Het definitieve antwoord genereren

Zodra ik de context heb (uit lokale documenten of via webscraping), geef ik die samen met de vraag van de gebruiker door aan het taalmodel. De LLM genereert het definitieve antwoord door context en vraag in een conversatievorm te combineren.

Zo verloopt de hoofdverwerking van de query:

  1. Routering: Ik gebruik check_local_knowledge() om te bepalen of de lokale PDF-inhoud genoeg data heeft om de vraag te beantwoorden.
    • Als "Yes", haal ik relevante stukken op uit de vectordatabase.
    • Als "No", scrape ik het web voor relevante artikelen.
  2. Definitief antwoord: Zodra ik de context heb, geef ik die door aan de LLM om het uiteindelijke antwoord te genereren.
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

Tot slot koppelen we alles samen in de functie 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()

Hierboven hebben we:

  1. De vectordatabase opgezet: De PDF geladen en verwerkt.
  2. De lokale context geïnitialiseerd: We hebben algemene inhoud uit de PDF opgehaald om als context aan de router door te geven.
  3. Een voorbeeldquery uitgevoerd: We hebben een voorbeeldquery uitgevoerd ("What is Agentic RAG?") en het definitieve antwoord geprint.

Dit is de output van het 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.

Uitleg van de output

Toen ik de vraag stelde "What is Agentic RAG?", die niet expliciet in het document stond, liet het systeem zijn flexibiliteit en intelligentie zien. De crewAI-agents routeerden de vraag correct door gebruik te maken van het inzicht van de router dat externe context nodig was. De agents werkten samen om het meest relevante artikel van het web te scrapen, de inhoud te analyseren en een goed onderbouwd antwoord te genereren.

Het systeem gaf een heldere uitleg van agentic RAG, de onderdelen (AI-agents, retrieval en generatieve elementen), de voordelen (nauwkeurigheid, gebruikerservaring en flexibiliteit) en de beperkingen (complexiteit, rekenkracht en trainingsvereisten).

Dit benadrukt het vermogen van de pijplijn om context dynamisch op te halen en precieze, beknopte en waardevolle inzichten te leveren, zelfs bij vragen buiten de oorspronkelijke dataset.

Conclusie

De pijplijn die we hebben gebouwd is een dynamisch, meerstaps proces dat gebruikersvragen efficiënt afhandelt door bestaande en externe kennis te combineren.

Het systeem kan zich dynamisch aanpassen aan uiteenlopende vragen en accurate, gebruiksvriendelijke antwoorden geven. Met de integratie van lokale data, live internetzoektochten en een naadloze gebruikersinterface bewijst de RAG-pijplijn zich als een robuuste en praktische oplossing voor toepassingen in de echte wereld. Ga als volgende stap zelf iets bouwen met de workflow die we hebben behandeld.


Bhavishya Pandit's photo
Author
Bhavishya Pandit
LinkedIn
Twitter

Senior GenAI Engineer en contentmaker die 20 miljoen weergaven heeft behaald door kennis te delen over GenAI en datawetenschap.

Onderwerpen

Leer AI met deze cursussen!

Leerpad

AI-toepassingen ontwikkelen

21 Hr
Leer AI-apps maken met de nieuwste AI-ontwikkelaarstools, zoals de OpenAI API, Hugging Face en LangChain.
Bekijk detailsRight Arrow
Begin met de cursus
Meer zienRight Arrow
Gerelateerd

blog

AI vanaf nul leren in 2026: een complete gids van de experts

Ontdek alles wat je moet weten om in 2026 AI te leren, van tips om te beginnen tot handige resources en inzichten van industrie-experts.
Adel Nehme's photo

Adel Nehme

15 min

Meer zienMeer zien