Unstructured2Graph

Unstructured2Graph turns documents, such as reports, PDFs, research papers or meeting notes, into a knowledge graph in Memgraph that an LLM can query. It’s part of the Memgraph AI Toolkit.

It combines two parts:

  • Unstructured IO extracts, cleans and chunks documents in formats such as PDF, DOCX and TXT.
  • An extraction backend finds entities and relationships in the chunks and writes them to Memgraph.

You choose the extraction backend:

  • GLiNER2 (default): A local model, with no LLM calls and no API key. It extracts the types listed in its ontology, a file of entity and relationship types. The default ontology lists entity types such as people, organizations and locations, and no relationship types, so GLiNER2 extracts relationships only with an ontology of your own.
  • LightRAG: An LLM extracts entities and any relationships it finds, and merges mentions of the same entity across chunks. Each chunk costs at least one LLM call.

The result is a graph of entities, relationships and embeddings, ready for GraphRAG.

Getting started

Follow these steps to create your first knowledge graph from documents.

Start Memgraph

Start by preparing your workspace and running Memgraph locally using Docker:

docker run -p 7687:7687 -p 7444:7444 --name memgraph memgraph/memgraph-mage

Open your terminal, VS Code, Cursor, or any other development environment you prefer. This is where you’ll run Python scripts connected to your Memgraph instance.

You are now ready to start building your graph.

Install Unstructured2Graph

Install Unstructured2Graph from PyPI. It needs Python 3.10 or later, and it installs PyTorch for the GLiNER2 model, which downloads on first use:

pip install unstructured2graph

To parse PDF, DOCX and other document formats, install it with the all-docs extra instead. The example below reads a PDF, so it needs the extra:

pip install "unstructured2graph[all-docs]"

Configure environment variables

GLiNER2 needs no API key. The LightRAG backend and the GraphRAG query below call OpenAI, so for those, set your OpenAI API key:

export OPENAI_API_KEY=YOUR_OPENAI_API_KEY

Replace YOUR_OPENAI_API_KEY with your key from the OpenAI platform.

Ingest documents

Unstructured2Graph reads local files and URLs in formats such as PDF, DOCX, TXT and HTML. It extracts the readable text, removes elements such as headers and page numbers, and splits the text into chunks by document layout. The extraction backend then reads each chunk.

This example ingests a local file and a web page with GLiNER2 and creates embeddings for search:

import asyncio
import logging
from memgraph_toolbox.api.memgraph import Memgraph
from unstructured2graph import from_unstructured, compute_embeddings, create_vector_search_index
from unstructured2graph.gliner2_backend import GLiNER2Backend
 
async def ingest_documents():
    # Connect to Memgraph and clear existing data
    memgraph = Memgraph()
    memgraph.query("MATCH (n) DETACH DELETE n;")
 
    # Extract entities with a local GLiNER2 model, downloaded on first use
    backend = GLiNER2Backend()
 
    # Define your document sources
    sources = [
        "docs/paper.pdf",                # local file
        "https://example.com/page.html"  # remote URL
    ]
 
    # Chunk the documents, extract entities and write them to Memgraph
    await from_unstructured(
        sources=sources,
        memgraph=memgraph,
        extraction_backend=backend,
        link_chunks=True,       # link chunks in order with NEXT relationships
        enforce_ontology=True,  # add each entity's type as a label, such as :Person
    )
 
    # Create embeddings and a vector index for semantic search
    compute_embeddings(memgraph, "Chunk")
    create_vector_search_index(memgraph, "Chunk", "embedding")
 
if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)
    asyncio.run(ingest_documents())

To extract with LightRAG instead, wrap a LightRAG instance in LightRAGBackend and pass it as the backend. Replace the backend and from_unstructured() lines in the example with:

from lightrag_memgraph import MemgraphLightRAGWrapper
from unstructured2graph import LightRAGBackend
 
lightrag = MemgraphLightRAGWrapper()
await lightrag.initialize(working_dir="./lightrag_storage")
 
await from_unstructured(
    sources=sources,
    memgraph=memgraph,
    extraction_backend=LightRAGBackend(lightrag),
    link_chunks=True,
)
await lightrag.afinalize()

Here’s what happens step by step:

  1. Unstructured IO extracts, cleans and chunks the text.
  2. Each chunk becomes a Chunk node with properties such as hash and text. Unstructured2Graph creates a uniqueness constraint and an index on Chunk.hash.
  3. The backend extracts entities. GLiNER2 writes them as gliner2 nodes with an entity_type property, and with enforce_ontology=True adds the type as a label, such as Person. LightRAG writes base nodes.
  4. Entities are linked to the chunks that mention them with MENTIONED_IN relationships.
  5. Chunks are linked in order with NEXT relationships.
  6. compute_embeddings() stores an embedding on each chunk, and create_vector_search_index() creates the vs_name vector index on it.

After processing, your Memgraph instance will hold a complete, queryable knowledge graph ready for GraphRAG.

Query with GraphRAG

Once your data is ingested, you can perform GraphRAG retrieval directly inside Memgraph with a single query. This combines semantic search with graph traversal to retrieve the most relevant context for your questions.

import os
from memgraph_toolbox.api.memgraph import Memgraph
from openai import OpenAI
 
def graphrag_query(prompt: str):
    memgraph = Memgraph()
    
    # Retrieve relevant chunks using vector search + graph traversal
    retrieved_chunks = []
    for row in memgraph.query(
        """
        CALL embeddings.text([$prompt]) YIELD embeddings, success
        CALL vector_search.search('vs_name', 5, embeddings[0]) YIELD distance, node, similarity
        OPTIONAL MATCH (node)-[r*bfs]-(dst:Chunk)
        WITH collect(DISTINCT node) + collect(DISTINCT dst) AS chunks
        UNWIND chunks AS chunk
        WITH DISTINCT chunk, degree(chunk) AS degree ORDER BY degree DESC
        RETURN chunk AS dst LIMIT 5;
        """,
        params={"prompt": prompt},
    ):
        if "text" in row["dst"]:
            retrieved_chunks.append(row["dst"]["text"])
 
    if not retrieved_chunks:
        print("No chunks retrieved.")
        return
 
    # Send retrieved context to LLM for summarization
    context = "\n\n".join(retrieved_chunks)
    client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
    
    completion = client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {"role": "system", "content": "Answer the question based on the provided context."},
            {"role": "user", "content": f"Context:\n{context}\n\nQuestion: {prompt}"},
        ],
        temperature=0.1,
    )
    
    answer = completion.choices[0].message.content
    print(f"Question: {prompt}")
    print(f"Answer: {answer}")
 
if __name__ == "__main__":
    graphrag_query("What are the key findings in the document?")

Here’s what the GraphRAG query does:

  1. Converts the input prompt, passed as the $prompt parameter, into an embedding.
  2. Searches for the most semantically relevant chunks using vector search.
  3. Adds the chunks connected to them in the graph, found with BFS traversal, and keeps the five with the most relationships.
  4. Sends the retrieved text to an LLM for summarization or question answering.

Visualize the graph in Memgraph Lab

Open Memgraph Lab and connect to your local instance. Then, in the Query Execution tab, run:

MATCH (n)-[r]->(m) RETURN n, r, m;

You’ll see:

  • Chunk nodes for text sections
  • gliner2 nodes for extracted entities, labeled with their type, such as Person (base nodes with LightRAG)
  • MENTIONED_IN relationships linking entities to their source chunks
  • NEXT relationships connecting sequential chunks

Explore this graph visually to understand how your content has been transformed into a connected network of knowledge. From here, you can keep querying and navigating the graph to uncover additional context, run advanced analyses, or expand your knowledge base as needed.

Try it in Memgraph Cloud

Want to skip local setup? You can also use Unstructured2Graph directly with Memgraph Cloud. Sign up, create a new project, and start building your knowledge graph in minutes.