OGuardAI
Architecture

Detector Capabilities

Entity detection capabilities by runtime mode, language support matrix, performance characteristics, and deployment modes

Entity Detection by Runtime Mode

Entity TypeBuiltin (Regex)+ GLiNER+ spaCy
EmailHighHighHigh
PhoneHigh (intl + DE)HighHigh
SSNHighHighHigh
IBANHighHighHigh
VAT ID (EU)HighHighHigh
Credit CardHigh (Luhn)HighHigh
IP AddressHigh (v4 + v6)HighHigh
URLHighHighHigh
Customer IDHigh (pattern)HighHigh
Order NumberHigh (pattern)HighHigh
PassportMedium (pattern)HighMedium
Health IDMedium (pattern)HighMedium
Date of BirthHigh (with context)HighHigh
German Tax IDHighHighHigh
German Social SecurityHighHighHigh
Money (ISO 4217)HighHighHigh
Quote IDHigh (pattern)HighHigh
Case ReferenceHigh (pattern)HighHigh
Person NameNoneGoodGood
Company NameNoneGoodModerate
LocationNoneGoodGood
Address (street)Medium (DE/US)BetterBetter

German Tax ID, German Social Security, Money, Quote ID, and Case Reference are the five always-on builtin_custom detectors: they run in every mode (including builtin) and emit typed custom:<name> entities. Together with the named builtin formats, these are the builtin entity types available before any NER, config, or policy additions.

Entity Source Provenance

Every entity type in GET /v1/capabilities carries a source field so a client can tell the fixed built-in floor apart from the policy and config extensible surface. It answers "is this type baked into the runtime, or did this deployment add it?" without reading the policy files back.

sourceMeaningExtensible by
builtinA universal PII format detector (email, phone, IBAN, SSN, IP, URL, credit card, ...). The safe floor.Not removable; always on
builtin_customA shipped custom regex detector (money, German tax ID, German social security, ...).Not removable; always on
nerDetected by the NER model (person, company, location, or a policy mapped zero-shot label). Present only when detector.mode is both or advanced.detection.ner_labels + ner_label_map
config_customAdded by this deployment's detector config (custom_detectors or vocab.extra_patterns).Server config, no recompile
policy_customDefined by a loaded policy's custom_patterns.Policy YAML, no recompile

The builtin and builtin_custom types are The Line: they always run and cannot be switched off by config. Everything tagged config_custom or policy_custom is what this deployment added on top, so a developer can confirm a new policy or vocab entry is live by querying capabilities. The field is additive: an older server that omits it is read as builtin by the SDKs.

VAT ID in the capabilities descriptor: the EU VAT ID detector runs on the builtin regex floor (see the detection table above), but it currently surfaces in GET /v1/capabilities as the custom:vat_id type with source: builtin_custom, not as a curated vat_id named type with source: builtin. Detection is unaffected; only the descriptor entry differs. Query the live entity_types list rather than assuming a vat_id named entry.

Language Support

Language support is not a fixed list; it is derived from the running deployment. There is no hardcoded set of supported languages. Three layers combine:

  • Structured PII (builtin) is language-independent: email, IBAN, IP, URL, SSN, credit card, customer ID, and order numbers are matched by pattern in any language. Phone and address have locale-specific format coverage (international plus German, US, French, Italian, Spanish, Dutch, UK formats today), extendable per locale via detector.vocab.extra_patterns with no recompile.
  • NER detection (person, company, location) covers whatever the Python sidecar reports. GLiNER is broadly multilingual; the exact set is the enrichment registry plus any loaded GUARDAI_LANGUAGE_PACKS. Every language is data (a built-in JSON lexicon, no per-language code); operators add, extend, or override (_mode: replace) any language from data, with extend guaranteed to only widen the built-in floor. The server fetches this from the sidecar at startup.
  • Restoration (honorifics, gendered titles, abstract labels) ships built-in templates for ~30 languages and is extended or overridden for any locale via restore_templates.

Do not rely on a static table: query GET /v1/capabilities for the live list this deployment advertises. Each language reports detection_support (present when the NER sidecar covers it) and rehydration_support (present when a restore template covers it). GET /v1/diagnostics (admin) shows the deployment's vocab additions, custom detectors, suppressions, and restore-template overrides.

NER Noise Suppression (Precision Floor)

Zero-shot NER (GLiNER) tends to over-tag common greeting and function words as person, company, or location entities. To keep precision high, OGuardAI applies a built-in noise floor after NER, additive over the raw model output:

  • A universal acronym stoplist (~50 entries such as PII, API, CEO, IBAN, SWIFT, HR, SSN) drops NER spans that are just a known acronym.
  • A per-language noise floor, shipped as data (crates/detector-builtins/data/ner_noise_stoplist.json, covering German, English, and Arabic today), suppresses NER spans whose tokens are all noise words, stopwords, or proclitics. This also catches glued phrases where the model tags a whole run of function words as one entity.
  • A minimum-confidence floor for NER-only types (built-in 0.65) drops low-confidence names.

This is a precision floor, not a ceiling. A deployment may add acronyms to the stoplist, un-suppress a specific built-in acronym so an acronym-shaped real organization name is detected, or raise the confidence floor (stricter only). It cannot lower the floor or weaken detection below the built-in baseline, and invalid overrides are rejected at startup. The active suppressions are visible via GET /v1/diagnostics.

Performance Characteristics

No latency, throughput, cold-start, or memory figures are published here. None have been produced by a reproducible, named run against a dataset this project can point to, and a number without a run behind it is a guess. See Benchmarks for the harness that produces real, hardware-specific numbers on your own machine.

The structural differences between the modes are these:

PropertyBuiltin Only+ GLiNER+ spaCy
Where detection runsIn process (Rust regex)Sidecar round trip plus model inferenceSidecar round trip plus model inference
Startup costNone beyond process startLoads a model before servingLoads a model before serving
Resident modelNoneYesYes
DeterministicYesModel output, not guaranteed identical run to runModel output, not guaranteed identical run to run
GPU requiredNoNo (optional)No

Because an NER-enabled request adds a network hop and a forward pass that the builtin path does not have, it is the dominant cost in any mode that uses it. That ordering is architectural. The size of the gap is not, and belongs to a measurement.

Deployment Modes

ModeConfigWhat RunsBest For
builtindetector.mode: builtinRust regex onlyLow-latency, structured data
advanceddetector.mode: advanced + detector.advanced_urlRust regex + required Python NER (fail-closed)Maximum accuracy
bothdetector.mode: both + URLRust regex + Python NER mergedBest coverage

Degraded Mode Behavior

Behavior on NER sidecar unavailability depends on the configured mode.

both mode (graceful fallback):

  1. Server logs: ner_service_unavailable_falling_back_to_builtin
  2. Detection continues with builtin regex only
  3. No crash, no hang, no data loss
  4. Entity types requiring NER (Person, Company, Location) will not be detected

advanced mode, or any policy required_for type (fail closed):

  1. NER is mandatory and is not downgraded to builtin
  2. A sidecar OUTAGE (unreachable, transport error, timeout, or a degraded response that ran no real NER) fails closed with GUARDAI_DETECTION_UNAVAILABLE (503, retriable): the content was never judged, so a retry may succeed once the sidecar recovers
  3. GUARDAI_DETECTION_FAILED (500) is reserved for a genuine capability rejection: the sidecar RESPONDED but its declared labels cannot serve the required types, which reproduces on retry
  4. A policy's required_for gates on whether NER actually ran, not on the configured mode, so a required_for type that NER did not produce rejects the request rather than passing the input through undetected

Health endpoint: GET /v1/health reports the CONFIGURED mode, not live NER reachability. It never flips to a detector degraded status on a live NER outage (this keeps Kubernetes liveness/readiness probes stable). The detector component message reflects configuration only: builtin_only (...) in builtin mode, builtin_and_ner (full entity detection) in both mode, and ner_required (...) in advanced mode. To check whether NER is configured, read the ner_active field from GET /v1/capabilities; to see whether NER actually ran for a given request, read the detector_mode and warnings fields on the transform response.