Guides

Processors

Opt-in steps that wrap your chat completion — redact PII, polish output, enforce policy — via a single array on the request body.

What are processors?

Processors sit around your chat completion and do one job well. You turn them on per request via the inferada.processors array — we handle the rest.

Five processors are currently available:

  • anonymise— redact personal data before it reaches the model, then reinsert the originals in the model's response.
  • spellcheck— clean up spelling and grammar in the model's response before it reaches your application.
  • pii_guard — scan the prompt for PII and reject the request if any is found. Content policy, not redaction.
  • require_local — reject the request unless the target upstream is classified as running on-premises. Routing policy, not content.
  • prompt_shield — block suspected prompt-injection / jailbreak attempts before any upstream LLM call. Heuristics in-process, optional classifier sidecar.

How to enable one

Add an inferada.processors array to your request body. If any enabled processor needs a language (e.g. anonymise, spellcheck), pass inferada.language as well:

json
{
  "model": "qwen3.5-35b",
  "messages": [
    { "role": "user", "content": "Jan Jansen woont in Amsterdam. Vat zijn adres samen." }
  ],
  "inferada": {
    "processors": ["anonymise", "spellcheck"],
    "language": "nl"
  }
}

Scope requirements:

anonymiseprocessor
Requires the pii scope. Requires inferada.language.
spellcheckprocessor
Requires the language scope. Requires inferada.language.
pii_guardprocessor
Requires the pii scope. No language needed — scans across every language the PII upstream supports.
require_localprocessor
Requires the completions scope (everyone has it). No language needed.
prompt_shieldprocessor
Requires the completions scope (everyone has it). No language needed — heuristics are pattern-based and the optional classifier inspects flattened text.

Missing scope → 403 naming the processor. Missing or unsupported language → 422 naming the processor and the upstream's supported-language list.

Where PII scanning looks — put the haystack in user messages

  • System prompt — your rules, behaviour, few-shot templates, static definitions. Trusted. Not scanned.
  • User / assistant messages — real runtime data. This is where the scanner looks.

If you tuck a real name into a system message, the guard won't catch it and anonymisewon't redact it. Keep real identifiable data in user-role messages.

prompt_shield follows the same convention — it inspects user and tool roles by default and trusts the calling application's system prompt. If you template the system prompt, see Prompt Templating for the rendering rules.

anonymise — redact and restore

Use anonymisewhen your prompt may contain personal data that shouldn't reach the model. We replace names, addresses, and other entities with placeholders before the request leaves your organisation; if the model echoes those placeholders back, they're restored in the final response. Your application sees the original values; the model only ever sees redacted text.

Placeholders use the format <INF_TYPE_N> (e.g. <INF_PERSON_1>, <INF_EMAIL_ADDRESS_2>). The INF_ namespace prevents collisions with your own template tags like <CUSTOMER>. Numbering is consistent across every message in the conversation — the same original value always resolves to the same tag, so <INF_PERSON_1> means the same person in message 0 and message 5.

spellcheck — polish the output

Use spellcheckwhen you want us to polish the model's output before you display it. The response text is returned cleaned up and ready to present.

Because spellcheck needs the complete response text to work, requests with spellcheck enabled are returned buffered rather than streamed. The response body sets inferada.stream_forced_buffered: true so your client can detect the change.

pii_guard — reject if PII is detected

A content-scanning backstop. Unlike anonymise, which rewrites the prompt, pii_guard rejects the whole request when PII is detected so the model never sees the prompt at all. Useful as a hard gate on customer-facing paths that must never forward personal data — particularly when composed with require_local on cloud-routed upstreams.

The guard scans every language listed on the PII upstream's supported_languages. That means you don't need to pass inferada.language — false positives on a backstop are acceptable, missed detections are not.

On block (HTTP 422):

json
{
  "error": "request_blocked",
  "processor": "pii_guard",
  "reason": "pii_detected",
  "detected_types": ["PERSON", "EMAIL_ADDRESS"],
  "message": "Request blocked by pii_guard",
  "request_id": "…"
}

The detected entity types appear in the response; the actual PII values never do — they are the thing we are trying not to leak.

On pass-through:

json
{
  "inferada": {
    "processors": {
      "pii_guard": {
        "applied": true,
        "pii_detected": false,
        "languages_checked": ["en", "nl"],
        "timings": { "pre_ms": 42, "total_ms": 42 }
      }
    }
  }
}

require_local — reject if not routing on-prem

A routing backstop. Fails the request with 422 unless the target completions upstream is classified processing = "local" by an admin. Use it when a given endpoint must never reach a cloud model, regardless of the prompt contents.

This check depends on every upstream being explicitly classified. Unclassified upstreams (processing = null) are rejected — we fail closed rather than assume the route is safe.

On block (HTTP 422):

json
{
  "error": "request_blocked",
  "processor": "require_local",
  "reason": "upstream_not_local",
  "detected_types": [],
  "message": "Request blocked by require_local",
  "request_id": "…"
}

prompt_shield — block prompt-injection attempts

A pre-completion content guard. Inspects the prompt for instruction-override, role-hijack, persona-attack, delimiter-injection and encoded-payload patterns and blocks the request before the upstream LLM is called. Two layers run in order; the first hit wins:

  • Heuristics — in-process rule engine, no network call, free. Per-rule sensitivity gating across low / medium / max.
  • Classifier sidecar— opt-in HTTP service wrapping ProtectAI's deberta-v3-base prompt-injection model. Only runs when an admin has configured a prompt_shield service for the org.

Configuration knobs sit under inferada.prompt_shield on the request (or as model-side defaults — request wins on merge):

level"off" | "low" | "medium" | "max"
"medium" is recommended for chat. "max" raises sensitivity at the cost of more false positives. "off" disables the processor.
action"reject" | "respond"
"reject" returns HTTP 422 with the structured request_blocked body (default). "respond" returns HTTP 200 with a synthesized chat-shaped refusal — preferable for end-user chat surfaces.
fail_mode"auto" | "open" | "closed"
"auto" (default) is level-aware: low/medium fail open, max fails closed. "open" always lets the request through on a sidecar outage; "closed" always blocks. Heuristics still run regardless.
classifier_timeout_msnumber
Hard timeout for the classifier sidecar HTTP call. Defaults to 3000, capped at 10000.
refusal_messagestring
Per-request override for the synthesized refusal copy in "respond" mode. Only used when the request blocks and action resolves to "respond"; if omitted, the model-default refusal_messages (or the built-in fallback) are used. Useful for tuning UX copy on the calling surface without re-configuring the model.

Inspect roles default to ["user", "tool"]— the calling application's system prompt is treated as trusted.

On block with action: "reject" (HTTP 422):

json
{
  "error": "request_blocked",
  "processor": "prompt_shield",
  "reason": "prompt_injection_suspected",
  "detected_types": ["instruction_override", "role_hijack"],
  "message": "Request blocked by prompt_shield",
  "request_id": "…"
}

On block with action: "respond" (HTTP 200): a normal-looking OpenAI chat completion with finish_reason: "content_filter" and a localised refusal message in the assistant content. Useful when the calling surface is a chat UI and you want the refusal to look like a model response rather than a transport error.

Forensic detections (with the matched text) persist server-side in request_logs.usage.prompt_shield_detections for audit. They never appear in client responses — the response body would otherwise be a debugging oracle for crafted attacks.

Composing the guards

pii_guard and require_local are orthogonal — one cares about content, the other about routing. Pick whichever combination matches your intent:

["pii_guard"]content
Scan for PII, reject if found. Routes anywhere — cloud or local, I don&apos;t care.
["require_local"]routing
The request must stay on-prem. I&apos;m not worried about the prompt content.
["require_local", "pii_guard"]defence-in-depth
Reject if the route is cloud OR if the prompt has PII. Typical for customer-facing paths.

Language support

Language-dependent processors (anonymise, spellcheck) require inferada.languageon the request and verify it against the backing upstream's declared supported_languages. There are no hardcoded defaults — if the language isn't declared, the request is rejected.

Admins declare the language set per upstream in the portal. New upstreams inherit their template's default set on creation and can be trimmed or extended from there. We ship sensible defaults for the Presidio and LanguageTool templates.

pii_guard is route-agnostic on language — it scans across the full supported_languages list regardless of what the caller sends. prompt_shield is also language-agnostic: heuristic patterns are language-independent and the classifier flattens text before scoring.

Reading processor metadata

When processors run, the response includes an inferada.processors object keyed by processor name:

json
{
  "inferada": {
    "processors": {
      "anonymise": {
        "applied": true,
        "entities_redacted": 2,
        "replacements_restored": 2,
        "timings": { "pre_ms": 12, "post_ms": 8, "total_ms": 20 }
      },
      "spellcheck": {
        "applied": true,
        "corrections": 1,
        "timings": { "post_ms": 25, "total_ms": 25 }
      }
    },
    "stream_forced_buffered": true
  }
}
  • applied — whether the processor actually did something.
  • entities_redacted / replacements_restored — counts from anonymise.
  • corrections — count from spellcheck.
  • pii_detected / languages_checked — from pii_guard on the pass-through path (blocks return a 422 instead of a response body).
  • timings — latency added by the processor, in milliseconds.
  • stream_forced_buffered — set to true when streaming was turned off because a processor needed the full response.

Usage & costs

Each processor counts toward its own scope's usage:

  • anonymise and pii_guard add to your pii usage.
  • spellcheck adds to your language usage.
  • require_localhas no backend hit — it's a pure config check, no cost.
  • prompt_shield heuristics are in-process and free. The classifier sidecar (when configured) costs one HTTP call per request and counts toward the prompt_shield service.

pii_guard runs one PII-analysis call per message per configured language. On an upstream with many languages, this adds up — budget accordingly.

The completion request's history entry also records per-processor metrics inside its usage object. See Usage Accounting for the exact shape.