Custom components let you package business logic as part of the pipeline so every Doc comes out fully annotated.
A component is any callable that takes a Doc and returns a Doc. Register it so spaCy can add it by name and save it with the pipeline:
@Language.component("flag_urgent")
def flag_urgent(doc):
doc._.urgent = any(t.lower_ in {"asap", "urgent"} for t in doc)
return doc
nlp.add_pipe("flag_urgent", last=True)For components with settings or state (a lookup table, a model), use `@Language.factory("name", default_config={...})` on a function that receives nlp, name and the config values and returns the component object. Config values can then be set in add_pipe(..., config={...}) or a training config file.
Extension attributes add your own fields to Doc, Token and Span under the ._ namespace, keeping them apart from built-in attributes:
- attribute:
Doc.set_extension("urgent", default=False), writable per object - getter:
Token.set_extension("is_color", getter=lambda t: t.lower_ in COLORS), computed on access - method:
Span.set_extension("count", method=lambda span, w: ...)
Register extensions once at import time; force=True overwrites an existing one (useful in notebooks).
Components should only add information, be deterministic and avoid heavy work at import. Place them relative to others with before=/after= and remember dependencies: a component reading ent_type_ must run after ner. Extension values are stored in doc.user_data and serialised with the Doc when using DocBin(store_user_data=True).
Example
import spacy
from spacy.language import Language
from spacy.tokens import Doc, Span
Doc.set_extension("urgent", default=False, force=True)
Span.set_extension("is_money_big", force=True,
getter=lambda s: s.label_ == "MONEY" and "million" in s.text)
@Language.component("flag_urgent")
def flag_urgent(doc):
doc._.urgent = any(t.lower_ in {"asap", "urgent"} for t in doc)
return doc
@Language.factory("keyword_tagger", default_config={"keywords": ["refund"]})
def make_keyword_tagger(nlp, name, keywords):
kw = set(keywords)
def tagger(doc):
doc.user_data["keywords"] = [t.text for t in doc if t.lower_ in kw]
return doc
return tagger
nlp = spacy.load("en_core_web_sm")
nlp.add_pipe("flag_urgent", after="ner")
nlp.add_pipe("keyword_tagger", config={"keywords": ["refund", "invoice"]})
doc = nlp("URGENT: refund the $3 million invoice ASAP.")
print(doc._.urgent, doc.user_data["keywords"]) # True ['refund', 'invoice']
print([(e.text, e._.is_money_big) for e in doc.ents]) # [('$3 million', True), ('ASAP', False)]Remember
- A component is a Doc -> Doc callable registered with @Language.component
- Use @Language.factory for configurable or stateful components
- Extensions on Doc/Token/Span live under ._ (attribute, getter, method)
- Mind component order and dependencies with before=/after=
Watch out for
- Registering extensions inside the component function, which runs on every Doc; register once at import.