RagLeap Packages
Looking for the RagLeap platform? This page documents the open-source pip install ragleap-graph library. For the hosted business platform, see docs.ragleap.com instead.
77 tests passing, up to 2 skipped without live credentials

ragleap-graph

Knowledge-graph retrieval for ragleap-rag — LLM entity extraction, deduplication, typed relations, and hybrid vector+graph search over Neo4j.

0.5.4
PyPI version
77
Tests passing
5
Real features shipped this session
v0.1→v0.5
Version arc, single session

Install

Requires a running Neo4j instance. Works standalone with regex extraction (zero LLM dependency), or with method="llm" for higher-quality extraction.

pip install ragleap-graph
# or, with uv
uv add ragleap-graph

Quick start

from ragleap_graph import GraphConfig, GraphIndex, ExtractionConfig

graph = GraphIndex(
    config=GraphConfig(uri="bolt://localhost:7687", user="neo4j", password="..."),
    extraction=ExtractionConfig(method="llm", provider=my_provider_config),
)
graph.upsert_document(document_id="doc-1", title="Q3 report", chunks=chunks)
related = graph.search_related_entities(["Acme Corp"], max_depth=2)

Version history

Every release this session was published to PyPI and live-tested before moving to the next — real Gemini calls, real Neo4j reads/writes, never mocked at the boundary that matters.

VersionWhat shippedReal live test performed
v0.1.0GraphConfig, GraphIndex, regex entity extraction, co-occurrence graphsReal Neo4j roundtrip
v0.2.0ExtractionConfig, LLMEntityExtractor, EntityDeduplicatorReal Gemini call + real isolated Neo4j write
v0.3.0GraphRetriever, GraphRetrievalConfig — hybrid vector+graph retrievalReal Postgres + real Neo4j
v0.3.1PyPI metadata fix onlyN/A — no code change
v0.4.0LLMRelationExtractor, ExtractedRelation, find_relations(), RELATES_AS edgesReal Gemini call correctly identified REPORTED / PARTNERED_WITH, real Neo4j write + read-back
v0.5.0entity_types=, entity_type on graph nodes, find_entities_by_type()Real Gemini call with entity-type guidance, real Neo4j write + read-back
v0.5.1PyPI metadata fix — added missing Documentation URLN/A — no code change
v0.5.2Fixed sentence-initial stopword bug in regex entity extractionReproduced live, regression test written first (confirmed failing), fix applied, same test confirmed passing, full suite re-run
v0.5.3Added direction= to find_relations() — closes reverse-lookup limitationReal Neo4j writes, regression test confirmed failing first, fix applied, same test confirmed passing, full suite re-run
v0.5.4Fixed CONTAINS weight-doubling and stale-entity bugs in upsert_document()Both bugs reproduced live, regression test written, fix applied, full suite re-run

Full public API

from ragleap_graph import (
    GraphConfig, GraphIndex,
    ExtractionConfig, ExtractedEntity, EntityDeduplicator, LLMEntityExtractor,
    ExtractedRelation, LLMRelationExtractor,
    GraphRetriever, GraphRetrievalConfig,
)

graph = GraphIndex(
    config=GraphConfig(uri="bolt://localhost:7687", user="neo4j", password="..."),
    extraction=ExtractionConfig(
        method="regex" | "llm",              # default "regex", zero deps
        provider=ProviderConfig(...),          # required if method="llm"
        dedup_enabled=False,                   # v0.2.0
        dedup_threshold=0.92,                  # empirically tuned
        extract_relations=False,               # v0.4.0, requires method="llm"
        entity_types=None,                     # v0.5.0, e.g. ["Customer","Product"]
    ),
)

Core (v0.1.0)

graph.upsert_document(document_id, title, chunks, namespace=None, domain_terms=None)
Extract entities, build/update the graph for a document.
graph.find_documents_by_entities(entity_names, namespace=None, limit=25)
Find documents mentioning given entities.
graph.search_related_entities(entity_names, namespace=None, max_depth=2, limit=10)
Graph-walk from entities to related entities.
graph.document_entities(document_id, namespace=None)
List entities extracted from a document.
graph.extract_query_entities(query, max_entities=10, domain_terms=None)
Extract entities from a query string.
graph.health_check()
Verify Neo4j connectivity.

Relations (v0.4.0)

graph.find_relations(entity_name, relation_type=None, namespace=None, limit=25, direction="outgoing")
direction="outgoing" (default), "incoming", or "both" — reverse lookup added in v0.5.3.

Entity typing (v0.5.0)

graph.find_entities_by_type(entity_type, namespace=None, limit=25)
Exact-string-match type lookup.

GraphRetriever (v0.3.0)

Separate from GraphIndex — composes a GraphIndex with an existing RagLeap instance via ragleap-rag's retrieve() method.

retriever = GraphRetriever(graph=graph, rag=rag, config=GraphRetrievalConfig(mode="hybrid"|"graph_only"))
result = retriever.retrieve(query, top_k=5, namespace=None, metadata_filter=None)
# -> {"chunks":[...], "query_entities":[...], "graph_context":{...}, "citations":[...], "retrieval_method":...}

Real bugs & design decisions this session

Not fabricated wins — each of these was caught by a real test failure or a real live-verification step.

  • ✓ fixed
    Dedup threshold false-positive (v0.2.0). Default 0.85 incorrectly merged "Neo4j" and "Neo 4j." Tested against 0.85/0.90/0.92/0.95 — 0.92 clears the false positive without breaking true positives ("T.C. Antony" / "TC Antony").
  • ✓ fixed
    Test-harness bug (v0.2.0). Mock cache-busting was keyed to the wrong module path — five LLM-extraction tests were silently making real network calls instead of using the mock. Fixed, with a canary assertion added so this can't recur silently.
  • documented, not hidden
    Regex extraction fragments "ACME Corp." into disconnected candidates ("ACME" + "Corp") alongside the correct "Acme Corp" elsewhere. Dedup correctly declines to merge these (similarity ~0.67, below 0.92) — a real side-by-side test confirms method="llm" avoids this at the source.
  • ✓ shipped
    GraphRetriever design decision. Built on RagLeap.retrieve() (new public method added to ragleap-rag specifically for this) rather than reaching into RagLeap's private internals.
  • ✓ shipped
    Relation hallucination filtering (v0.4.0). LLMRelationExtractor defensively drops any relation referencing an entity outside the caller-supplied known_entities list, and drops self-relations, even though the prompt already constrains this.
  • ✓ fixed
    Entity typing gap closed (v0.5.0). LLMEntityExtractor computed a per-entity type field since v0.2.0, but GraphIndex discarded it before it reached Neo4j. Now actually used.
  • ✓ shipped
    Re-upsert type-downgrade guard (v0.5.0). A later regex-only upsert of the same document won't overwrite an already-recorded LLM-derived entity_type with "UNKNOWN," via a coalesce(NULLIF(...)) pattern in the write query.
  • ✓ fixed
    Sentence-initial stopword bug (v0.5.2). Regex extraction picked up capitalized sentence-initial words ("What," "Who," "The") as spurious entities, since English capitalizes a sentence's first word regardless of whether it's a proper noun. "What did Acme Corp launch?" previously returned ['What', 'Acme Corp'] instead of just ['Acme Corp']. Fixed with a narrow stopword filter applied only to exact single-word matches — multi-word entities unaffected. Reproduced live before fixing, regression test written and confirmed failing first.
  • ✓ fixed
    find_relations() reverse-lookup gap (v0.5.3). Only ever searched outgoing relations — searching from the object side silently returned [] even when the entity was clearly involved in a real relation. Added direction= parameter ("outgoing" default, "incoming", "both"). Zero prior test coverage existed for this method at all; backfilled alongside the fix. Reproduced live, regression test confirmed failing first.
  • ✓ fixed
    upsert_document() CONTAINS idempotency bugs (v0.5.4). Docstring claimed writes were "idempotent — safe to re-run," which was false: re-upserting identical content doubled CONTAINS weight every call, and re-upserting changed content left stale entity links in place forever. Fixed by deleting a document's CONTAINS edges before rewriting them per upsert. Both bugs reproduced live before any code changed.

Known limitations

From the package's own CHANGELOG.md, current as of v0.5.4.

  • CO_OCCURS_WITH and RELATES_AS still have the CONTAINS-style weight-doubling bug — deliberately not fixed alongside CONTAINS in v0.5.4, since those edges aggregate weight across multiple different documents by design and there's no per-document contribution tracking yet to fix it safely. Needs a real schema addition, tracked as upcoming work.
  • find_entities_by_type() — exact-string-match only, no fuzzy/case-insensitive matching.
  • No relationship-type ontology constraints — relation types and entity types aren't cross-validated against each other.
  • entity_types= is guidance only, not enforced or validated.
  • GraphRetriever doesn't dedupe or rank overlap between vector-derived and graph-derived results — both returned as-is, left to the caller.
  • Relation extraction runs once per chunk — relations spanning multiple chunks of the same document aren't identified.