Handbücher · Wissensgraphen

Build: Den Graphen abfragen

Lektion 7/98 Min. LesezeitProfi

Lernziele

  • Du lädst edges.csv und typed_edges.csv in einen gemeinsamen Graphen und erklärst, warum typisierte Kanten die einfachen überschreiben.
  • Du beantwortest mit vier Befehlen Fragen nach Nachbarn, Wegen, Themen-Clustern und Verbindungen.
  • Du liest eine Kette aus connects richtig, auch wenn eine Kante gegen die Laufrichtung zeigt.

Das ist Teil zwei des Builds. Den Graphen extrahieren hat zwei Dateien im obersten Ordner des Vaults hinterlassen: edges.csv mit den einfachen Links und typed_edges.csv mit dem typisierten Durchgang. Diese Lektion macht daraus Antworten, mit einem Python-Skript von rund 80 Zeilen.

Der Store ist NetworkX im Arbeitsspeicher. Das Skript lädt den Graphen bei jedem Aufruf frisch aus den CSV-Dateien. Das ist die zweite Stufe auf der Leiter aus Graph-Stores. Du brauchst weder Server noch Schema-Migration, und bei einem persönlichen Vault dauert das Laden Millisekunden. Du brauchst ein einziges Paket:

python3 -m pip install networkx

Stand Nov. 2026 ist NetworkX 3.7 aktuell. Es verlangt Python 3.12 oder neuer, mit älterem Python installiert pip eine Version 3.6 oder 3.5. Jede Version der Reihe 3.x reicht. Wir haben das Skript mit NetworkX 3.7 unter Python 3.13 und mit 3.5 unter Python 3.11 laufen lassen, die Ausgaben waren gleich. Meldet pip externally-managed-environment, wie bei Python aus Homebrew, legst du ein virtuelles Environment an (python3 -m venv .venv) und startest die Skripte mit .venv/bin/python.

Das Skript

Speichere es im Vault als scripts/graph_query.py:

#!/usr/bin/env python3
"""Fragt den Vault-Graphen ab. Liest edges.csv und typed_edges.csv
aus dem aktuellen Ordner.

Aufruf:
    python3 graph_query.py neighbors "Seite"
    python3 graph_query.py path "Seite A" "Seite B"
    python3 graph_query.py communities
    python3 graph_query.py connects "Seite A" "Seite B"
"""
import csv
import os
import sys

import networkx as nx

FILES = ["edges.csv", "typed_edges.csv"]
SKIP = {"index", "log"}


def load():
    g = nx.DiGraph()
    for name in FILES:
        if not os.path.exists(name):
            continue
        with open(name, encoding="utf-8") as fh:
            for row in csv.DictReader(fh):
                if row["source"] in SKIP or row["target"] in SKIP:
                    continue
                g.add_edge(row["source"], row["target"],
                           type=row.get("type", "links-to"))
    return g


def neighbors(g, node):
    out = [f"-> {t} ({g.edges[node, t]['type']})" for t in sorted(g.successors(node))]
    out += [f"<- {s} ({g.edges[s, node]['type']})" for s in sorted(g.predecessors(node))]
    return "\n".join(out) or "Keine Links."


def path(g, a, b):
    try:
        return nx.shortest_path(g.to_undirected(as_view=True), a, b)
    except (nx.NetworkXNoPath, nx.NodeNotFound):
        return None


def communities(g):
    found = nx.community.label_propagation_communities(g.to_undirected())
    groups = sorted((sorted(c) for c in found), key=len, reverse=True)
    return "\n".join(f"[{len(c)}] " + ", ".join(c) for c in groups)


def connects(g, a, b):
    p = path(g, a, b)
    if p is None:
        return f"Nichts verbindet {a} und {b}."
    hops = []
    for s, t in zip(p, p[1:]):
        d = g.get_edge_data(s, t)
        hop = f"{s} -[{d['type']}]-> {t}" if d else f"{s} <-[{g.edges[t, s]['type']}]- {t}"
        hops.append(hop)
    return "\n".join(hops)


if __name__ == "__main__":
    g = load()
    cmd, args = (sys.argv[1:] or ["hilfe"])[0], sys.argv[2:]
    missing = [a for a in args if a not in g]
    if missing:
        print("Keine Kante kennt diese Seite: " + ", ".join(missing))
    elif cmd == "neighbors":
        print(neighbors(g, args[0]))
    elif cmd == "path":
        print(" -> ".join(path(g, args[0], args[1]) or ["Kein Weg."]))
    elif cmd == "communities":
        print(communities(g))
    elif cmd == "connects":
        print(connects(g, args[0], args[1]))
    else:
        print(__doc__)

Zeilen 1-10

Vier Befehle, jeder mit Seitennamen in Anführungszeichen. Der Hilfetext ist zugleich die Ausgabe, wenn du keinen oder einen unbekannten Befehl angibst.

Zur Bauweise, kurz. Die typisierten Kanten kommen nach den einfachen, also gewinnt die bessere Beschreibung desselben Links. In unserem Test-Vault aus der letzten Lektion wurde so aus Jerry Liu → LlamaIndex (links-to) die Kante built. Wege laufen über eine ungerichtete Sicht, weil die Frage „Wie hängen die zusammen?“ keine Richtung kennt. Und Label Propagation steht für den Leiden-Schritt aus GraphRAG. Das Verfahren ist gröber, braucht aber nur NetworkX und ist bei dieser Größe sofort fertig. In unserem Test lieferte es bei drei Läufen dreimal dieselben Cluster.

Drei Stellen haben wir nach dem Test ergänzt: das Überspringen von index und log, die Meldung für unbekannte Seiten und den Hilfetext ohne Argument. Ohne den ersten Punkt meldete das Skript eine einzige Community mit 15 Seiten, und der Weg von „Nelson Liu“ zu „Neo4j“ lief über den Index.

Was jeder Befehl beantwortet

Alle Ausgaben stammen aus dem Test-Vault der letzten Lektion:

$ python3 scripts/graph_query.py neighbors "RAG"
-> GraphRAG (links-to)
-> LlamaIndex (uses)
<- GraphRAG (extends)
<- Jerry Liu (links-to)
<- Lange Kontexte ersetzen RAG (links-to)
<- LlamaIndex (links-to)
<- Lost in the Middle (links-to)

$ python3 scripts/graph_query.py communities
[7] Darren Edge, From Local to Global, GraphRAG, Graphen helfen bei Multi-Hop-Fragen, Neo4j, NetworkX, Wissensgraph
[6] Jerry Liu, Lange Kontexte ersetzen RAG, LlamaIndex, Lost in the Middle, Nelson Liu, RAG

neighbors zeigt eine Seite mit allen direkten Verbindungen in beide Richtungen und benennt jede Beziehung. communities ist die Themenebene: eine Zeile pro Community · engl. communityEine Gruppe von Knoten, die untereinander dicht verbunden sind und zum Rest des Graphen wenige Kanten haben. Algorithmen wie Leiden finden sie automatisch.Im Glossar, die größte zuerst. Die Seite „Notiz Zettel“ fehlt in beiden Clustern, weil sie keinen Link hat. Fragst du nach ihr, antwortet das Skript „Keine Kante kennt diese Seite“.

Welcher Befehl?

Ordne jede Frage dem Befehl zu, der sie beantwortet.

Noch offen (4)

  • Welche Seiten verlinken auf „RAG“, und mit welchem Typ?
  • Über welche Seiten komme ich von „Nelson Liu“ zu „Neo4j“? Die Typen sind mir egal.
  • Welche Themen hat mein Vault, und welches ist das größte?
  • Warum hängen „Darren Edge“ und die These „Lange Kontexte ersetzen RAG“ zusammen?

neighbors (0)

    path (0)

      communities (0)

        connects (0)

          Was X und Y verbindet

          Für diese Frage gibt es den Build. connects liefert die kürzeste Kette zwischen zwei Seiten, jede Etappe mit Typ:

          $ python3 scripts/graph_query.py connects "Nelson Liu" "Neo4j"
          Nelson Liu -[authored]-> Lost in the Middle
          Lost in the Middle -[links-to]-> RAG
          RAG -[links-to]-> GraphRAG
          GraphRAG -[links-to]-> Wissensgraph
          Wissensgraph -[uses]-> Neo4j
          
          $ python3 scripts/graph_query.py connects "Jerry Liu" "Lost in the Middle"
          Jerry Liu -[links-to]-> RAG
          RAG <-[links-to]- Lost in the Middle

          Das ist ein berechneter Multi-Hop-Frage · engl. multi-hop questionEine Frage, deren Antwort Fakten aus mehreren Dokumenten verknüpft. Kein einzelner Textabschnitt enthält sie.Im Glossar-Join. Genau an dieser Frage scheitert reine Vektorsuche, wie Warum Wissensgraphen zeigt. Lies die zweite Zeile der zweiten Kette genau: RAG <-[links-to]- Lost in the Middle heißt, dass „Lost in the Middle“ auf „RAG“ verlinkt.

          Der kürzeste Weg zählt Etappen und achtet nicht auf Typen. Gibt es eine typisierte Kette über drei Etappen und einen einfachen Link über zwei, gewinnt der Link. Im Test lief die Kette von „Jerry Liu“ deshalb über den Link auf „RAG“ und ließ die Kante built zu LlamaIndex liegen. Die Kette ist trotzdem richtig, sie beschreibt nur weniger. Je mehr typisierte Kanten der Extraktions-Durchgang findet, desto öfter enthalten die kürzesten Wege Typen.

          connects ist eine einfache Funktion: zwei Zeichenketten hinein, eine Zeichenkette heraus. Genau diese Form braucht ein Tool · engl. toolEin Werkzeug, das das Modell aufrufen kann: eine API, eine Datenbankabfrage, ein Shell-Befehl, eine Suche. Über Tools wirkt das Modell auf die Welt.Im Glossar für einen Agenten. Wie du es anschließt, zeigt Den Graphen in den Loop holen.

          Merksätze

          • NetworkX im Arbeitsspeicher reicht für einen persönlichen Vault. Das Skript lädt beide CSV-Dateien bei jedem Aufruf neu.
          • Typisierte Kanten überschreiben die einfachen Links zwischen demselben Paar, weil sie die Beziehung genauer beschreiben.
          • index und log gehören vor jeder Abfrage aus dem Graphen, sonst laufen Wege über sie, und alle Themen verschmelzen.
          • connects liefert eine berechnete Kette mit Typen. <- bedeutet, dass die Kante gegen die Leserichtung zeigt.
          • Der kürzeste Weg bevorzugt keine typisierten Kanten. Mehr Typen in der Extraktion machen die Ketten aussagekräftiger.

          Prüf dich selbst

          Denkfrage

          Warum sucht das Skript Wege über eine ungerichtete Sicht des Graphen?

          Denkfrage

          Was passiert, wenn du index und log nicht überspringst?

          Prüf dich selbst

          1. 1Zwischen „RAG“ und „LlamaIndex“ steht in edges.csv ein Link und in typed_edges.csv die Kante uses in derselben Richtung. Welcher Typ steht im Graphen?
          2. 2Was bedeutet die Zeile „RAG <-[links-to]- Lost in the Middle“?
          3. 3connects liefert eine Kette nur aus links-to, obwohl du typisierte Kanten extrahiert hast. Warum ist das möglich?
          4. 4pip meldet externally-managed-environment. Was tust du?

          Selbst nachweisen

          0 von 3 erledigt

          Quellen

          1. NetworkX auf PyPI · NetworkX, Stand: Nov. 2026

            Version 3.7 vom September 2026, verlangt Python 3.12 oder neuer.

          2. NetworkX: shortest_path · NetworkX, Stand: Nov. 2026
          3. NetworkX: label_propagation_communities · NetworkX, Stand: Nov. 2026

            Semi-synchrone Label Propagation.

          4. PEP 668: Python-Umgebungen als extern verwaltet markieren · Python Software Foundation, Stand: Nov. 2026

            Hintergrund zur Meldung externally-managed-environment.

          Dein Fortschritt bleibt nur in diesem Browser gespeichert.