Named-Entity Recognition
Detect named entities (people, organisations, locations, dates, money amounts, emails, phones, URLs, plus caller-supplied custom labels) in extracted text. Result populates ExtractedDocument.entities.
Backends
Section titled “Backends”| Backend | Cargo feature | When to use | Status |
|---|---|---|---|
Onnx (xberg-gliner) |
ner-onnx |
High throughput, local inference, deterministic | Available. |
Llm (liter-llm) |
ner-llm |
Domain-specific zero-shot labels, any of 165 providers | Available today. |
In the browser
Section titled “In the browser”The WASM build detects entities inside the page, with no server round-trip. This is a direct API
rather than a backend selection: load the GLiNER2 weights once and call the model.
const model = await NerModel.load({ weights, tokenizer, encoderConfig });const entities = await model.detect(text, { categories: ["person", "organization"] });model.free();Weights are not embedded in the .wasm, so the host fetches the safetensors, tokenizer, and encoder
config and passes the bytes. The model stays resident, so repeated detect calls do not re-parse it.
Inference is synchronous on a single-threaded target — run it in a Web Worker if main-thread
responsiveness matters.
free() is optional: like every class in the package it is reclaimed by the garbage collector.
Calling it is still worth it here, because GC timing is unobservable and WASM linear memory only ever
grows, so a few hundred megabytes of weights can sit allocated long after the last detect.
The ONNX backend downloads supported Xberg GLiNER aliases and catalog ids from
xberg-io/gliner-models. The runtime consumes exported ONNX artifacts and
tokenizer files; it does not load arbitrary source PyTorch model repositories.
If the artifact repository is private or not publicly readable, authenticate
with Hugging Face using credentials supported by hf-hub before warming the
cache or running inference.
When to Use
Section titled “When to Use”- You need entity tags attached to extracted text for retrieval, faceting, or compliance review.
- You need PII categories surfaced for downstream redaction (NER pairs with the redaction post-processor — see Redaction & Anonymisation).
- You need zero-shot labelling against caller-supplied categories (“Treatment”, “Vessel”, “Product”) that fall outside the GLiNER taxonomy.
When Not to Use
Section titled “When Not to Use”- You only need regex-detectable PII (emails, phones, IBANs, SSNs). The redaction pattern engine is 1000× cheaper. See Redaction & Anonymisation.
- You want sub-100ms latency on a hot path with a large LLM. Prefer the ONNX backend (
ner-onnx) for deterministic local inference.
Configuration
Section titled “Configuration”import asynciofrom xberg import ExtractInput, extract, ExtractionConfig, NerConfig, LlmConfig
async def main() -> None: config = ExtractionConfig( ner=NerConfig( backend="llm", llm=LlmConfig(model="openai/gpt-4o-mini"), ), ) result = await extract(ExtractInput(uri="contract.pdf"), config) for entity in result.results[0].entities or []: print(f"{entity.category}: {entity.text} (confidence={entity.confidence})")
asyncio.run(main())import { extract } from '@xberg-io/xberg';
const output = await extract({ kind: "uri", uri: "contract.pdf",}, { ner: { backend: "llm", llm: { model: "openai/gpt-4o-mini" }, },});
for (const entity of output.results[0].entities ?? []) { console.log(`${entity.category}: ${entity.text}`);}use xberg::{extract, ExtractionConfig, ExtractInput, NerConfig, NerBackendKind, LlmConfig};
let config = ExtractionConfig { ner: Some(NerConfig { backend: NerBackendKind::Llm, llm: Some(LlmConfig { model: "openai/gpt-4o-mini".to_string(), ..Default::default() }), ..Default::default() }), ..Default::default()};let output = extract(ExtractInput::from_uri("contract.pdf"), &config).await?;for entity in output.results[0].entities.unwrap_or_default() { println!("{:?}: {} (confidence={:?})", entity.category, entity.text, entity.confidence);}xberg extract contract.pdf \ --config xberg.toml \ --api-key "$XBERG_LLM_API_KEY"[ner]backend = "llm"custom_labels = ["Treatment", "Vessel", "Product"]
[ner.llm]model = "openai/gpt-4o-mini"Custom Labels (Zero-Shot)
Section titled “Custom Labels (Zero-Shot)”Pass arbitrary labels via NerConfig.custom_labels. The LLM backend folds each label into the structured-output schema; the ONNX backend uses GLiNER’s native zero-shot inference.
from xberg import ExtractionConfig, NerConfig, LlmConfig
config = ExtractionConfig( ner=NerConfig( backend="llm", llm=LlmConfig(model="openai/gpt-4o-mini"), custom_labels=["Treatment", "Vessel", "Product"], ),)import { extract } from "@xberg-io/xberg";
const output = await extract({ kind: "uri", uri: "contract.pdf",}, { ner: { backend: "llm", llm: { model: "openai/gpt-4o-mini" }, customLabels: ["Treatment", "Vessel", "Product"], },});use xberg::{ExtractionConfig, NerConfig, NerBackendKind, LlmConfig};
let config = ExtractionConfig { ner: Some(NerConfig { backend: NerBackendKind::Llm, llm: Some(LlmConfig { model: "openai/gpt-4o-mini".to_string(), ..Default::default() }), custom_labels: vec!["Treatment".into(), "Vessel".into(), "Product".into()], ..Default::default() }), ..Default::default()};Custom hits surface as EntityCategory::Custom(label) in the resulting Entity stream. Casing of the supplied label is preserved.
Output Shape
Section titled “Output Shape”ExtractedDocument.entities is Option<Vec<Entity>>, populated when NER ran and produced at least one detection. JSON shape:
{ "entities": [ { "category": "person", "text": "Ada Lovelace", "start": 42, "end": 54, "confidence": 0.93 }, { "category": { "custom": "Treatment" }, "text": "metformin", "start": 120, "end": 129, "confidence": 0.81 } ]}Byte offsets refer to result.content. When the redaction post-processor rewrites the document, NER offsets are recomputed against the redacted text — use the audit trail in result.redaction_report to reconstruct positions in the original.
Categories
Section titled “Categories”EntityCategory |
Description |
|---|---|
Person |
Person names. |
Organization |
Organisations, companies, institutions. |
Location |
Geographic locations. |
Date |
Date mentions. |
Time |
Time-of-day mentions. |
Money |
Monetary amounts with currency. |
Percent |
Percentages. |
Email |
Email addresses. |
Phone |
Phone numbers. |
Url |
URLs. |
Custom(label) |
Caller-supplied zero-shot label. |
LLM Backend Setup
Section titled “LLM Backend Setup”When backend = "llm", configure the model via NerConfig.llm. The API-key precedence chain matches LLM Integration:
NerConfig.llm.api_keyXBERG_LLM_API_KEY- Per-provider env var (
OPENAI_API_KEY,ANTHROPIC_API_KEY, …)
Local engines (Ollama, LM Studio, vLLM) need no key.
Known Limitations
Section titled “Known Limitations”- The LLM backend’s accuracy depends on the chosen model. Use
gpt-4o-minior larger for production NER.
Related
Section titled “Related”- Redaction & Anonymisation — uses NER for PERSON / ORGANIZATION / LOCATION categories
- LLM Integration — full LLM provider matrix, local engine setup, API-key precedence
- Configuration Reference — full field reference