GYOM15
Deploy the RAG comparison app
45d0949
Raw
History Blame Contribute Delete
1.79 kB
"""Named entity extraction via spaCy (local NER, en_core_web_sm).
Replaces the old capitalization heuristic with real statistical NER:
we keep only the entity types useful to a knowledge graph
(people, places, organizations, events...), excluding dates and numbers.
The model is loaded lazily and cached.
Model installation: python -m spacy download en_core_web_sm
"""
import re
from functools import lru_cache
# spaCy types kept (we discard DATE, CARDINAL, ORDINAL... = noise for the graph).
_KEEP = {
"PERSON", "NORP", "FAC", "ORG", "GPE", "LOC",
"PRODUCT", "EVENT", "WORK_OF_ART", "LAW", "LANGUAGE",
}
# Leading article sometimes included by spaCy ("The RMS Titanic") — removed for consistent nodes.
_LEADING_ARTICLE = re.compile(r"^(?:the|a|an)\s+", re.IGNORECASE)
@lru_cache(maxsize=1)
def _nlp():
"""Loads en_core_web_sm only once (NER only: tagger/parser disabled)."""
import spacy
try:
return spacy.load(
"en_core_web_sm",
disable=["tagger", "parser", "attribute_ruler", "lemmatizer"],
)
except OSError as exc:
raise OSError(
"spaCy model 'en_core_web_sm' not found. Install it with:\n"
" python -m spacy download en_core_web_sm"
) from exc
def extract_entities(text: str, min_length: int = 2) -> list[str]:
"""Named entities of `text` (graph-useful types), deduplicated (case-insensitive)."""
seen: set[str] = set()
found: list[str] = []
for ent in _nlp()(text).ents:
if ent.label_ not in _KEEP:
continue
name = _LEADING_ARTICLE.sub("", ent.text.strip())
key = name.lower()
if len(name) < min_length or key in seen:
continue
seen.add(key)
found.append(name)
return found