← Alle berichten
15 jan 2025Matías Aguirre

RAG met Neo4j en LLMs - AI Verbeteren met Kennisgrafen

Leer hoe je Retrieval-Augmented Generation implementeert met Neo4j als kennisbasis om taalmodelresponsen te verbeteren

neo4jragllmaigrafenpython
RAG architectuurdiagram met Neo4j
RAG architectuurdiagram met Neo4j

Wat is RAG en Waarom Combineren met Neo4j?

RAG (Retrieval-Augmented Generation) is een techniek die de antwoorden van grote taalmodellen (LLM’s) verbetert door ze relevante context te geven die is opgehaald uit een externe kennisbasis. In plaats van alleen te vertrouwen op kennis in modelparameters, zoekt RAG naar specifieke informatie voordat het een antwoord genereert.

Neo4j, als grafendatabase, is ideaal voor RAG omdat:

  • ✅ Relationele context: Legt complexe relaties tussen entiteiten vast
  • ✅ Efficiënte navigatie: Maakt snelle traversal van relaties mogelijk
  • ✅ Gestructureerde kennis: Organiseert informatie semantisch
  • ✅ Contextueel zoeken: Vindt relevante informatie rekening houdend met verbindingen

RAG Architectuur met Neo4j

Gebruiker → Vraag
    ↓
1. Vraag embedding
    ↓
2. Zoeken in Neo4j (Vector Search)
    ↓
3. Ophalen van relationele context
    ↓
4. Prompt constructie met context
    ↓
5. LLM genereert verrijkt antwoord
    ↓
Gecontextualiseerd antwoord → Gebruiker

Vereisten

# Installeer benodigde afhankelijkheden
pip install neo4j langchain openai tiktoken

Je hebt nodig:

  • Neo4j 5.11+ (met Vector Index ondersteuning)
  • OpenAI API key (of elk compatibel LLM)
  • Python 3.9+

Stap 1: Configureer Neo4j met Vectoren

Maak eerst een vector index in Neo4j om embeddings op te slaan:

// Maak vector index voor documenten
CREATE VECTOR INDEX document_embeddings IF NOT EXISTS
FOR (d:Document)
ON d.embedding
OPTIONS {
  indexConfig: {
    `vector.dimensions`: 1536,
    `vector.similarity_function`: 'cosine'
  }
}

Stap 2: Laad Kennis in de Graaf

from neo4j import GraphDatabase
import openai

class KnowledgeGraphRAG:
    def __init__(self, uri, user, password, openai_key):
        self.driver = GraphDatabase.driver(uri, auth=(user, password))
        openai.api_key = openai_key

    def close(self):
        self.driver.close()

    def create_embedding(self, text):
        """Genereer embedding met OpenAI"""
        response = openai.embeddings.create(
            model="text-embedding-3-small",
            input=text
        )
        return response.data[0].embedding

    def add_document(self, title, content, metadata=None):
        """Voeg document toe aan graaf met embedding"""
        embedding = self.create_embedding(content)

        with self.driver.session() as session:
            session.run("""
                CREATE (d:Document {
                    title: $title,
                    content: $content,
                    embedding: $embedding,
                    metadata: $metadata,
                    created_at: datetime()
                })
            """, title=title, content=content,
                embedding=embedding, metadata=metadata)

    def add_relationship(self, doc1_title, doc2_title, rel_type):
        """Maak relatie tussen documenten"""
        with self.driver.session() as session:
            session.run("""
                MATCH (d1:Document {title: $doc1})
                MATCH (d2:Document {title: $doc2})
                CREATE (d1)-[r:%s]->(d2)
            """ % rel_type, doc1=doc1_title, doc2=doc2_title)
def retrieve_context(self, query, top_k=3):
    """Haal relevante context op met vector search"""
    query_embedding = self.create_embedding(query)

    with self.driver.session() as session:
        result = session.run("""
            CALL db.index.vector.queryNodes(
                'document_embeddings',
                $top_k,
                $query_embedding
            ) YIELD node, score

            // Haal ook gerelateerde documenten op
            OPTIONAL MATCH (node)-[r:RELATES_TO|REFERENCES]-(related:Document)

            RETURN
                node.title as title,
                node.content as content,
                score,
                collect(DISTINCT {
                    title: related.title,
                    type: type(r)
                }) as related_docs
            ORDER BY score DESC
        """, query_embedding=query_embedding, top_k=top_k)

        return [dict(record) for record in result]

Stap 4: Genereer Antwoord met LLM

def generate_response(self, query, context_docs):
    """Genereer antwoord met opgehaalde context"""
    # Bouw context van Neo4j
    context_text = "\n\n".join([
        f"Document: {doc['title']}\n{doc['content']}\n"
        f"Gerelateerde documenten: {', '.join([r['title'] for r in doc['related_docs']])}"
        for doc in context_docs
    ])

    # Maak verrijkte prompt
    prompt = f"""Gebaseerd op de volgende context, beantwoord de vraag van de gebruiker.

Context:
{context_text}

Vraag: {query}

Gedetailleerd antwoord:"""

    # Roep OpenAI aan
    response = openai.chat.completions.create(
        model="gpt-4",
        messages=[
            {"role": "system", "content": "Je bent een expert assistent die antwoordt op basis van de gegeven context."},
            {"role": "user", "content": prompt}
        ],
        temperature=0.7,
        max_tokens=500
    )

    return response.choices[0].message.content

def ask(self, query):
    """Hoofd RAG methode"""
    # 1. Haal context op
    context_docs = self.retrieve_context(query, top_k=3)

    # 2. Genereer antwoord
    answer = self.generate_response(query, context_docs)

    # 3. Retourneer met bronnen
    return {
        "answer": answer,
        "sources": [doc['title'] for doc in context_docs],
        "relevance_scores": [doc['score'] for doc in context_docs]
    }

Stap 5: Volledig Systeemgebruik

# Initialiseer
rag = KnowledgeGraphRAG(
    uri="bolt://localhost:7687",
    user="neo4j",
    password="password",
    openai_key="sk-..."
)

# Voeg kennis toe aan de graaf
rag.add_document(
    title="Introductie tot Neo4j",
    content="Neo4j is een native grafendatabase die gegevens opslaat in nodes en relaties...",
    metadata={"category": "database", "level": "beginner"}
)

rag.add_document(
    title="Cypher Query Language",
    content="Cypher is Neo4j's declaratieve querytaal, ontworpen om met grafen te werken...",
    metadata={"category": "query-language", "level": "intermediate"}
)

# Maak relaties tussen documenten
rag.add_relationship(
    "Introductie tot Neo4j",
    "Cypher Query Language",
    "PREREQUISITE"
)

# Stel vragen
result = rag.ask("Hoe kan ik gegevens opvragen in Neo4j?")

print("Antwoord:", result['answer'])
print("\nBronnen:", result['sources'])
print("Relevantie:", result['relevance_scores'])

# Sluit verbinding
rag.close()

Voorbeeld Output

Antwoord: Om gegevens in Neo4j op te vragen, gebruik je Cypher Query Language,
de declaratieve taal specifiek ontworpen voor werken met grafen.
Cypher stelt je in staat om MATCH te gebruiken voor patronen, WHERE om te filteren,
en RETURN om te specificeren welke gegevens je wilt ophalen. Bijvoorbeeld:

MATCH (n:Person)-[:KNOWS]->(friend)
WHERE n.name = 'Alice'
RETURN friend.name

Deze query vindt alle vrienden van Alice in de graaf.

Bronnen: ['Cypher Query Language', 'Introductie tot Neo4j']
Relevantie: [0.89, 0.76]
KenmerkEenvoudige VectorenNeo4j RAG
Relationele context❌ Nee✅ Ja
Kennisnavigatie❌ Beperkt✅ Compleet
Uitlegbaarheid⚠️ Laag✅ Hoog
Updates⚠️ Complex✅ Eenvoudig
Multi-hop redenering❌ Nee✅ Ja

Geavanceerde Gebruiksscenario’s

1. Multi-hop RAG (Meerstaps Redenering)

def multi_hop_retrieve(self, query, depth=2):
    """Haal context op door de graaf in diepte te navigeren"""
    query_embedding = self.create_embedding(query)

    with self.driver.session() as session:
        result = session.run("""
            CALL db.index.vector.queryNodes(
                'document_embeddings',
                3,
                $query_embedding
            ) YIELD node, score

            // Navigeer tot 2 niveaus diep
            CALL apoc.path.subgraphAll(node, {
                maxLevel: $depth,
                relationshipFilter: 'RELATES_TO|REFERENCES|PREREQUISITE'
            })
            YIELD nodes, relationships

            RETURN node.title as main_doc,
                   [n IN nodes | n.title] as related_docs,
                   [r IN relationships | type(r)] as relationship_types,
                   score
        """, query_embedding=query_embedding, depth=depth)

        return [dict(record) for record in result]

2. RAG met Temporeel Filteren

def retrieve_recent_context(self, query, days=30):
    """Haal alleen recente context op"""
    query_embedding = self.create_embedding(query)

    with self.driver.session() as session:
        result = session.run("""
            CALL db.index.vector.queryNodes(
                'document_embeddings',
                5,
                $query_embedding
            ) YIELD node, score

            WHERE node.created_at > datetime() - duration({days: $days})

            RETURN node.title, node.content, score
            ORDER BY score DESC
        """, query_embedding=query_embedding, days=days)

        return [dict(record) for record in result]

3. RAG met Gebruikerspersonalisatie

def personalized_retrieve(self, query, user_id):
    """Haal gepersonaliseerde context op op basis van gebruikersvoorkeuren"""
    query_embedding = self.create_embedding(query)

    with self.driver.session() as session:
        result = session.run("""
            // Vind relevante documenten
            CALL db.index.vector.queryNodes(
                'document_embeddings',
                10,
                $query_embedding
            ) YIELD node, score

            // Boost documenten gerelateerd aan gebruiker
            OPTIONAL MATCH (u:User {id: $user_id})-[:INTERESTED_IN]->(t:Topic)
            OPTIONAL MATCH (node)-[:ABOUT]->(t)

            WITH node, score,
                 CASE WHEN t IS NOT NULL THEN 1.5 ELSE 1.0 END as user_boost

            RETURN node.title, node.content,
                   (score * user_boost) as adjusted_score
            ORDER BY adjusted_score DESC
            LIMIT 5
        """, query_embedding=query_embedding, user_id=user_id)

        return [dict(record) for record in result]

Optimalisatie en Best Practices

1. Intelligent Chunking

def smart_chunk_document(self, long_text, chunk_size=500, overlap=50):
    """Splits lange documenten in chunks met overlap"""
    words = long_text.split()
    chunks = []

    for i in range(0, len(words), chunk_size - overlap):
        chunk = ' '.join(words[i:i + chunk_size])
        chunks.append(chunk)

    return chunks

def add_long_document(self, title, content):
    """Voeg lang document toe in gerelateerde chunks"""
    chunks = self.smart_chunk_document(content)

    for i, chunk in enumerate(chunks):
        chunk_title = f"{title} - Deel {i+1}"
        self.add_document(chunk_title, chunk)

        # Relateer opeenvolgende chunks
        if i > 0:
            prev_chunk = f"{title} - Deel {i}"
            self.add_relationship(prev_chunk, chunk_title, "NEXT_CHUNK")

2. Embedding Cache

from functools import lru_cache

@lru_cache(maxsize=1000)
def create_embedding_cached(self, text):
    """Cached versie van create_embedding"""
    return self.create_embedding(text)

3. Relevantie Monitoring

def ask_with_confidence(self, query, min_score=0.7):
    """RAG met vertrouwensdrempel"""
    context_docs = self.retrieve_context(query, top_k=3)

    # Controleer minimale relevantie
    if not context_docs or context_docs[0]['score'] < min_score:
        return {
            "answer": "Ik heb geen voldoende relevante informatie gevonden om met vertrouwen te antwoorden.",
            "confidence": "low",
            "sources": []
        }

    answer = self.generate_response(query, context_docs)

    return {
        "answer": answer,
        "confidence": "high",
        "sources": [doc['title'] for doc in context_docs],
        "avg_score": sum(d['score'] for d in context_docs) / len(context_docs)
    }

Conclusie

RAG met Neo4j combineert het beste van twee werelden: de generatiecapaciteit van LLM’s en gestructureerde kennis in grafen. Deze architectuur maakt mogelijk:

  • 🎯 Nauwkeurigere antwoorden gebaseerd op specifieke kennis
  • 🔍 Volledige traceerbaarheid van bronnen en redenering
  • 🔄 Dynamische updates van kennis zonder modellen opnieuw te trainen
  • 🌐 Relationele context die eenvoudige vectoren niet kunnen vastleggen
  • 📊 Uitlegbaarheid van waarom bepaalde antwoorden werden gegenereerd

De toekomst van conversationele AI ligt in het combineren van taalmodellen met gestructureerde kennisbases, en Neo4j is het perfecte platform voor het bouwen van deze systemen.

Aanvullende Bronnen

Delen
Volgende stap

Klaar om Uw Project te Starten?

Laten we bespreken hoe technologie uw organisatie kan transformeren. Wij bieden technische consultaties en maatwerkoplossingen afgestemd op institutionele vereisten. Plan een vrijblijvend strategisch gesprek.

Neem contact op
Gratis consultatieVrijblijvendSnelle reactie