Skip to content

Commit ca6a565

Browse files
committed
docs(pii): gallery pii_detection policy + NER-centric docs
- gallery/index.yaml: privacy-filter-multilingual ships a default pii_detection policy (validated against the GGUF's real label set — mask everything; block PASSWORD/PIN/CVV/CREDITCARD/IBAN/BIC/BANKACCOUNT/ SSN/{BITCOIN,ETHEREUM,LITECOIN}ADDRESS). - docs/advanced/model-configuration.md: new "PII filtering" section (pii_detection on detector models + pii.detectors on consumers). - docs/features/middleware.md: rewrote the PII section for the NER-only model; dropped the removed regex pattern catalogue / endpoints / MCP pattern tools / streaming filter. Assisted-by: claude-code:claude-opus-4-8 [Claude Code]
1 parent 4e82f2b commit ca6a565

3 files changed

Lines changed: 115 additions & 97 deletions

File tree

docs/content/advanced/model-configuration.md

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -788,7 +788,43 @@ known_usecases:
788788

789789
Available flags: `chat`, `completion`, `edit`, `embeddings`, `rerank`, `image`, `transcript`, `tts`, `sound_generation`, `tokenize`, `vad`, `video`, `detection`, `llm` (combination of CHAT, COMPLETION, EDIT).
790790

791-
`token_classify` marks a model as a token-classification (NER) provider for the PII redactor's encoder tier (e.g. an `openai-privacy-filter` GGUF). Declare it explicitly together with `embeddings: true` (the classifier loads via TOKEN_CLS pooling). On the `llama-cpp` backend it must not be combined with `chat`/`completion` in the same config — `TokenClassify` bypasses the slot loop and would race generation, so the loader rejects that mix; split into separate model configs.
791+
`token_classify` marks a model as a token-classification (NER) provider for the PII filter (e.g. an `openai-privacy-filter` GGUF). Declare it explicitly together with `embeddings: true` (the classifier loads via TOKEN_CLS pooling). On the `llama-cpp` backend it must not be combined with `chat`/`completion` in the same config — `TokenClassify` bypasses the slot loop and would race generation, so the loader rejects that mix; split into separate model configs.
792+
793+
## PII filtering
794+
795+
PII redaction is NER-based and runs on the **request** (input) side. It has two halves:
796+
797+
- **Detector models** are `token_classify` models that carry the detection *policy* in a top-level `pii_detection:` block. The policy is defined once, on the model itself:
798+
799+
```yaml
800+
name: privacy-filter-multilingual
801+
backend: llama-cpp
802+
embeddings: true
803+
known_usecases:
804+
- token_classify
805+
pii_detection:
806+
min_score: 0.5 # drop detections below this confidence
807+
default_action: mask # mask | block | allow — applied to any detected
808+
# group with no explicit entry (empty = mask)
809+
entity_actions: # which PII to block vs mask vs allow-log
810+
PASSWORD: block
811+
CREDITCARD: block
812+
EMAIL: mask
813+
```
814+
815+
- **Consuming models** opt in and reference one or more detectors by name — no per-consumer policy:
816+
817+
```yaml
818+
name: my-assistant
819+
pii:
820+
enabled: true # default: off for local backends, on for cloud-proxy
821+
detectors:
822+
- privacy-filter-multilingual
823+
```
824+
825+
Multiple detectors union their detections; overlapping spans resolve to the strongest action (`block` > `mask` > `allow`). A configured detector that can't be loaded fails the request closed (HTTP 503) rather than silently skipping the check. Detections are audited at `/api/pii/events` (hash-prefix only, never the raw value).
826+
827+
> The earlier regex pattern tier (`pii.patterns`, the global pattern catalogue, `--pii-config`, and the `/api/pii/patterns` admin endpoints) has been removed, along with response/streaming-side redaction. Those keys now no-op with a startup warning; migrate to `pii.detectors` + a detector's `pii_detection` block.
792828

793829
## Complete Example
794830

docs/content/features/middleware.md

Lines changed: 58 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -37,52 +37,52 @@ and block actions.
3737

3838
## PII filtering
3939

40-
PII redaction is **per-model and off by default**. The default flips to
41-
**on for any backend whose name starts with `proxy-`** because that traffic
42-
crosses the network to a third-party provider. Explicit `pii.enabled`
43-
in a model's YAML always wins over the backend default.
40+
PII redaction is **per-model, NER-based, and runs request-side (input)**.
41+
It is **off by default**, flipping to **on for any `cloud-proxy` backend**
42+
because that traffic crosses the network to a third-party provider.
43+
Explicit `pii.enabled` in a model's YAML always wins over the backend
44+
default.
4445

45-
### Pattern catalog
46+
> The earlier regex pattern tier (`pii.patterns`, the built-in pattern
47+
> catalogue, `--pii-config`, the `/api/pii/patterns|test|decide` endpoints)
48+
> and response/streaming-side redaction have been **removed**. Detection is
49+
> now driven entirely by token-classification (NER) models. Legacy keys
50+
> no-op with a startup warning.
4651
47-
The built-in regex tier ships six patterns. Each has a default action
48-
(`mask`, `block`, or `allow`) and a length cap that prevents
49-
pathological inputs from blowing up scanning time:
52+
### Detector models
5053

51-
| ID | Description | Default action | Max length |
52-
|---|---|---|---|
53-
| `email` | Email address | `mask` | 254 |
54-
| `phone` | Phone number (international or US) | `mask` | 24 |
55-
| `ssn` | US Social Security Number | `mask` | 11 |
56-
| `credit_card` | Credit card number (Luhn-verified) | `mask` | 19 |
57-
| `ipv4` | IPv4 address | `mask` | 15 |
58-
| `api_key_prefix` | `sk-`, `pk-`, `xoxb-`, `ghp_`, `github_pat_` | **`block`** | 200 |
59-
60-
`mask` rewrites the match to `[REDACTED:<id>]` in the request body before
61-
forwarding. `block` returns HTTP 400 with `error.type=pii_blocked` to the
62-
client without forwarding. `allow` detects and logs the match (a PIIEvent
63-
is still recorded) but leaves the text unchanged — use it to downgrade a
64-
pattern's default for a model while keeping it visible in the audit log.
65-
It is also the foundation for surfacing detected-PII labels to the router,
66-
a planned router-model feature.
67-
68-
### Per-model configuration
69-
70-
Add a `pii:` block to a model YAML to opt in (or out, or to override
71-
per-pattern actions):
54+
A **detector** is a `token_classify` model (e.g. an `openai-privacy-filter`
55+
GGUF) that carries the detection *policy* in a top-level `pii_detection:`
56+
block — defined once, on the model itself:
7257

7358
```yaml
74-
# Local model — explicit opt-in so chats with this model get redaction
75-
# applied request-side.
76-
name: qwen-7b-local
59+
name: privacy-filter-multilingual
7760
backend: llama-cpp
78-
pii:
79-
enabled: true
61+
embeddings: true # TOKEN_CLS pooling
62+
known_usecases:
63+
- token_classify
64+
pii_detection:
65+
min_score: 0.5 # drop detections below this confidence
66+
default_action: mask # applied to any detected group with no entry
67+
entity_actions: # which PII to block vs mask vs allow-log
68+
PASSWORD: block
69+
CREDITCARD: block
70+
EMAIL: mask
8071
```
8172
73+
`mask` rewrites the matched span to `[REDACTED:ner:<GROUP>]` in the request
74+
body before forwarding. `block` returns HTTP 400 (`error.type=pii_blocked`)
75+
without forwarding. `allow` detects and logs (a PIIEvent is still recorded)
76+
but leaves the text unchanged. The entity-group names are whatever the model
77+
emits (the privacy-filter family uses uppercase names like `EMAIL`,
78+
`PASSWORD`, `CREDITCARD`).
79+
80+
### Consuming models
81+
82+
Any model opts in by enabling PII and referencing one or more detectors —
83+
no per-consumer policy:
84+
8285
```yaml
83-
# Cloud-bound model — defaults to enabled because backend is cloud-proxy.
84-
# Tighten api_key_prefix from the global default and downgrade email to
85-
# allow so emails are logged but pass through unchanged.
8686
name: claude-strict
8787
backend: cloud-proxy
8888
proxy:
@@ -91,85 +91,47 @@ proxy:
9191
upstream_url: https://api.anthropic.com/v1/messages
9292
api_key_env: ANTHROPIC_API_KEY
9393
pii:
94-
patterns:
95-
- id: api_key_prefix
96-
action: block # already the default, made explicit for audit
97-
- id: email
98-
action: allow
94+
enabled: true # default-on for cloud-proxy; explicit for audit
95+
detectors:
96+
- privacy-filter-multilingual
9997
```
10098

101-
The regex itself stays global — only the action is settable per-model.
102-
Adding new patterns is a build-time concern (extend `patternRegexps` in
103-
`core/services/routing/pii/patterns.go`).
104-
105-
### NER tier (optional)
106-
107-
The regex matcher covers high-precision patterns. For natural-language
108-
PII (proper names, addresses, organization names) LocalAI carries an
109-
**encoder NER tier** that runs after the regex pass. It expects a
110-
transformers token-classification model wired through the `TokenClassify`
111-
gRPC primitive (e.g. `dslim/bert-base-NER`). The detector annotates
112-
spans with an entity group (`PER`, `LOC`, `ORG`, `MISC`); per-group
113-
actions are configurable through the same `pii:` block.
114-
115-
The NER tier ships as a contract (`NERDetector`, `NERConfig` in
116-
`core/services/routing/pii/ner.go`); an operator-facing knob to load and
117-
attach a detector is not plumbed yet. When no detector is configured the
118-
regex tier still runs.
119-
120-
### Streaming PII filter
121-
122-
Buffered (`/v1/chat/completions` without `"stream": true`) responses are
123-
forwarded verbatim today — only the request-side scan runs. Streaming
124-
responses run through `pii.StreamFilter` which buffers SSE chunks until
125-
either a full pattern matches or the buffer's max length is reached,
126-
then emits the safe prefix. The streaming filter is what makes the
127-
cloud-proxy backend and the MITM proxy safe to expose to clients that
128-
issue streaming requests.
129-
130-
The streaming filter is wired automatically for any model with `pii.enabled`
131-
true — there is no separate streaming toggle.
99+
Multiple detectors **union** their detections; overlapping spans resolve to
100+
the strongest action (`block` > `mask` > `allow`). A configured detector
101+
that can't be loaded **fails the request closed** (HTTP 503,
102+
`error.type=pii_ner_unavailable`) rather than silently skipping the check.
103+
The same NER path runs on the [MITM proxy]({{< relref "mitm-proxy.md" >}})
104+
request body for intercepted hosts. Response/output redaction is out of
105+
scope for now.
132106

133107
### Admin page
134108

135109
The `/app/middleware` page (admin role only) has four tabs — **Filtering**,
136110
**Routing**, **MITM Proxy** (see the [MITM doc]({{< relref "mitm-proxy.md" >}})),
137-
and **Events**. The Filtering tab shows:
138-
139-
- The pattern catalogue with live action dropdowns. Changing an action via
140-
the UI calls `PUT /api/pii/patterns/:id` and updates the live redactor
141-
in-process. Click **Persist** in the action header to write the current
142-
state into `runtime_settings.json` so the next process start re-applies it.
143-
- A per-model resolved-state table — each model row reports `enabled`,
144-
the per-pattern overrides, and which patterns are effectively active.
145-
- A live test panel that posts sample text to `/api/pii/test` and
146-
highlights matches with their resolved actions, without storing the
147-
text in the event log.
111+
and **Events**. The Filtering tab shows a per-model table: each row reports
112+
`enabled` and the detector model(s) it references, plus the recent event
113+
count. Detection policy is edited on each detector model's config (Models →
114+
edit → PII), not globally.
148115

149116
### REST surface
150117

151118
| Method | Path | Auth | Purpose |
152119
|---|---|---|---|
153-
| GET | `/api/pii/patterns` | any | Live pattern list with current actions. Used by the UI catalogue. |
154-
| POST | `/api/pii/test` | any | Dry-run the redactor on `{"text":"..."}`. Returns hits and the would-be-rewritten body. Does not write to the event log. |
155-
| GET | `/api/pii/events` | admin | Recent middleware events — PII redactions, MITM connect/traffic, admission denials. Filterable by `correlation_id`, `user_id`, `pattern_id`, `kind`. |
156-
| PUT | `/api/pii/patterns/:id` | admin | Update a pattern in-process. Body accepts `{"action":"mask"\|"block"\|"allow"}` and/or `{"disabled":true\|false}`. Transient — reverts on restart unless persisted. |
157-
| POST | `/api/pii/patterns/persist` | admin | Snapshot the live per-pattern (action, disabled) state into `runtime_settings.json`. |
158-
| GET | `/api/middleware/status` | admin | Aggregated dashboard data: patterns + per-model resolved state + router status + MITM status + admission status. One round-trip for the UI. |
120+
| GET | `/api/pii/events` | admin | Recent middleware events — PII redactions, MITM connect/traffic, admission denials. Filterable by `correlation_id`, `user_id`, `pattern_id` (e.g. `ner:EMAIL`), `kind`. |
121+
| GET | `/api/middleware/status` | admin | Aggregated dashboard data: per-model PII state + detectors + router status + MITM status + admission status. One round-trip for the UI. |
159122

160123
### MCP tools
161124

162-
The same surface is mirrored through the LocalAI Assistant MCP server so
163-
the in-process and stdio assistants can manage the filter conversationally:
125+
The same surface is mirrored through the LocalAI Assistant MCP server:
164126

165127
| Tool | Read/Write | Purpose |
166128
|---|---|---|
167-
| `list_pii_patterns` | read | Returns the live pattern list. |
168129
| `get_pii_events` | read | Recent redaction / block events with optional filters. |
169-
| `test_pii_redaction` | read | Dry-run sample text without writing to the event log. |
170130
| `get_middleware_status` | read | Aggregator — the same payload as `GET /api/middleware/status`. |
171-
| `set_pii_pattern_action` | write | Update a pattern's action. Admin-only. |
172-
| `persist_pii_patterns` | write | Snapshot live state to `runtime_settings.json`. Admin-only. |
131+
132+
Detection policy is part of a detector model's config, so it is managed
133+
through the model-config tools (`edit_model_config`), not a dedicated PII
134+
tool.
173135

174136
---
175137

gallery/index.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,26 @@
4040
- token_classify
4141
parameters:
4242
model: llama-cpp/models/privacy-filter-multilingual/privacy-filter-multilingual-f16.gguf
43+
# Detection policy used when another model references this one via
44+
# pii.detectors. Default-mask everything the model flags; block the
45+
# credential/financial-secret/crypto categories. Keys are the model's
46+
# own entity-group names (uppercase, no separators); anything not
47+
# listed falls through to default_action: mask.
48+
pii_detection:
49+
min_score: 0.5
50+
default_action: mask
51+
entity_actions:
52+
PASSWORD: block
53+
PIN: block
54+
CVV: block
55+
CREDITCARD: block
56+
IBAN: block
57+
BIC: block
58+
BANKACCOUNT: block
59+
SSN: block
60+
BITCOINADDRESS: block
61+
ETHEREUMADDRESS: block
62+
LITECOINADDRESS: block
4363
files:
4464
- filename: llama-cpp/models/privacy-filter-multilingual/privacy-filter-multilingual-f16.gguf
4565
sha256: 01b76572f80b7d2ebee80a27cb9c3699c26b04cae1c402eee7664fc17a4b5ce6

0 commit comments

Comments
 (0)