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.
| Version | What shipped | Real live test performed |
|---|---|---|
| v0.1.0 | GraphConfig, GraphIndex, regex entity extraction, co-occurrence graphs | Real Neo4j roundtrip |
| v0.2.0 | ExtractionConfig, LLMEntityExtractor, EntityDeduplicator | Real Gemini call + real isolated Neo4j write |
| v0.3.0 | GraphRetriever, GraphRetrievalConfig — hybrid vector+graph retrieval | Real Postgres + real Neo4j |
| v0.3.1 | PyPI metadata fix only | N/A — no code change |
| v0.4.0 | LLMRelationExtractor, ExtractedRelation, find_relations(), RELATES_AS edges | Real Gemini call correctly identified REPORTED / PARTNERED_WITH, real Neo4j write + read-back |
| v0.5.0 | entity_types=, entity_type on graph nodes, find_entities_by_type() | Real Gemini call with entity-type guidance, real Neo4j write + read-back |
| v0.5.1 | PyPI metadata fix — added missing Documentation URL | N/A — no code change |
| v0.5.2 | Fixed sentence-initial stopword bug in regex entity extraction | Reproduced live, regression test written first (confirmed failing), fix applied, same test confirmed passing, full suite re-run |
| v0.5.3 | Added direction= to find_relations() — closes reverse-lookup limitation | Real Neo4j writes, regression test confirmed failing first, fix applied, same test confirmed passing, full suite re-run |
| v0.5.4 | Fixed 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.
- ✓ fixedDedup 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").
- ✓ fixedTest-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 hiddenRegex 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. - ✓ shippedGraphRetriever design decision. Built on
RagLeap.retrieve()(new public method added toragleap-ragspecifically for this) rather than reaching intoRagLeap's private internals. - ✓ shippedRelation hallucination filtering (v0.4.0).
LLMRelationExtractordefensively drops any relation referencing an entity outside the caller-suppliedknown_entitieslist, and drops self-relations, even though the prompt already constrains this. - ✓ fixedEntity typing gap closed (v0.5.0).
LLMEntityExtractorcomputed a per-entitytypefield since v0.2.0, butGraphIndexdiscarded it before it reached Neo4j. Now actually used. - ✓ shippedRe-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_typewith "UNKNOWN," via acoalesce(NULLIF(...))pattern in the write query. - ✓ fixedSentence-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. - ✓ fixedfind_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. Addeddirection=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. - ✓ fixedupsert_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.