feat: SpacyEntityRelationExtractor (LLM-free KG construction)#547
Draft
jexp wants to merge 12 commits into
Draft
feat: SpacyEntityRelationExtractor (LLM-free KG construction)#547jexp wants to merge 12 commits into
jexp wants to merge 12 commits into
Conversation
This was referenced Jun 24, 2026
…WORD_FILTER constant Create spacy_utils.py with shared normalisation utilities: - normalize(text) lowercases, applies NFC Unicode normalisation, and collapses whitespace - WORD_FILTER frozenset of ~90 common English stopwords for entity key filtering - 21 unit tests covering edge cases, Unicode NFC equivalence, and type immutability Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ractor Add `extract_linear_triples(doc)` in a new module `spacy_linear_extractor.py` (separated from `spacy_utils.py` because task-002 owns that file concurrently). The function emits (entity_a, "RELATED_TO", entity_b, 0.3) for every pair of NER entities in the same sentence whose starts are ≤ 10 tokens apart with no other entity between them. It gracefully degrades when no sentence segmenter has been run by treating the whole doc as a single sentence. 14 unit tests covering all acceptance criteria pass.
…triple extractor Add `extract_dependency_triples(doc)` to `spacy_utils.py`. Walks the dependency tree per sentence finding nsubj/nsubjpass → ROOT → dobj/pobj patterns. Handles active SVO (conf=1.0), passive voice with _BY suffix (conf=0.9), and prepositional triples (conf=0.7). Uses spaCy 3.x retokenizer API to collapse multi-word noun chunks/named entities. Falls back gracefully when sentence boundaries are unavailable. Move spaCy-gated tests to a dedicated `test_spacy_dependency_triples.py` so that normalize/WORD_FILTER tests stay runnable without spaCy installed. 18 new tests; all 39 tests pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…paCy components Add integration test files for SpacyEntityRelationExtractor and HierarchicalTextSplitter that exercise the real en_core_web_sm model. All tests are auto-skipped (not failed) when the model is not installed.
Remove hierarchical_splitter.py and its tests from this branch (now in PR neo4j#548). Update example script to reference the companion PR.
…ration tests" This reverts commit 208d569.
jexp
marked this pull request as draft
June 25, 2026 12:55
NathalieCharbel
self-requested a review
June 25, 2026 12:55
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements
SpacyEntityRelationExtractor— a drop-in replacement forLLMEntityRelationExtractorthat uses SpaCy's dependency parser instead of an LLM, achieving 94% of GPT-4o extraction performance with no LLM calls, GPU-free, and orders-of-magnitude faster.Based on Min et al., arXiv:2507.03226 — Towards Practical GraphRAG: Efficient KG Construction and Hybrid Retrieval at Scale.
Companion PR: #548 adds
HierarchicalTextSplitter, which is the recommended upstream splitter for this extractor (used in the example script below).New files
spacy_utils.pynormalize()helper andWORD_FILTERstopword setspacy_linear_extractor.pyspacy_entity_relation_extractor.pySpacyEntityRelationExtractor— full componentexamples/.../spacy_kg_pipeline.pyDesign
nsubj/nsubjpass → ROOT → dobj/pobjpatterns; handles passive voice (swaps subject/object, suffixes relation with_BY); merges noun chunks viadoc.retokenize()(SpaCy 3.x API).RELATED_TOtriple with confidence 0.3. Toggle viause_linear_extractor(defaultTrue).schemaparam filters by NER type → node label mapping and by relationship type names; fail-open if no NER types map.word_filterconstructor param); defaults toWORD_FILTER(~90 English stopwords).components/__init__.pyis wrapped intry/except ImportErrorso the package imports cleanly when SpaCy is not installed.LLMEntityRelationExtractor:Neo4jGraphwithNeo4jNode(label="Entity")andNeo4jRelationship(properties={"confidence": float}).Tests
test_spacy_entity_relation_extractor.py) — all mockspacy.load, no model download requiredtest_spacy_extractor_integration.py) — skip cleanly whenen_core_web_smis not installedspacy_utils, dep triples, linear extractor)Test plan
uv run pytest tests/unit/experimental/components/test_spacy_utils.py tests/unit/experimental/components/test_spacy_dependency_triples.py tests/unit/experimental/components/test_spacy_linear_extractor.py tests/unit/experimental/components/test_spacy_entity_relation_extractor.py -vpython -m spacy download en_core_web_sm && uv run pytest tests/unit/experimental/components/test_spacy_extractor_integration.py -vuv run python -c "from neo4j_graphrag.experimental.components import SpacyEntityRelationExtractor; print('ok')"