A Vercel AI SDK middleware that automatically redacts personally identifiable information (PII) from chat messages using OpenAI's privacy-filter model — running entirely locally via Transformers.js.
No API calls. No cloud. The 1.5B param model runs in your browser or on your server.
npm install ai-sdk-privacy-filter-middlewareWith your preferred AI SDK provider (OpenAI, OpenRouter, etc):
npm install @ai-sdk/openai
# or
npm install @openrouter/ai-sdk-providerimport { wrapLanguageModel } from 'ai';
import { openai } from '@ai-sdk/openai';
import { privacyFilterMiddleware } from 'ai-sdk-privacy-filter-middleware';
const model = wrapLanguageModel({
model: openai('gpt-4o'),
middleware: privacyFilterMiddleware(),
});import { wrapLanguageModel } from 'ai';
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
import { privacyFilterMiddleware } from 'ai-sdk-privacy-filter-middleware';
const openrouter = createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY });
const model = wrapLanguageModel({
model: openrouter('anthropic/claude-3.5-sonnet'),
middleware: privacyFilterMiddleware(),
});That's it. Any generateText or streamText call using model will now automatically:
- Detect PII in user/system messages (names, emails, phones, addresses, etc.)
- Replace them with typed placeholders (
[PERSON_1],[EMAIL_1]) - Restore the original values in the LLM's response
// User sends: "I'm Alice, my email is alice@corp.com"
// LLM sees: "I'm [PERSON_1], my email is [EMAIL_1]"
// LLM replies: "Hello [PERSON_1], I'll contact [EMAIL_1]"
// User sees: "Hello Alice, I'll contact alice@corp.com"const model = wrapLanguageModel({
model: openai('gpt-4o'),
middleware: privacyFilterMiddleware({
// Only redact specific entity types (default: auto-detect all)
entityTypes: ['private_person', 'private_email', 'private_phone'],
// Minimum confidence score (default: 0.8)
minScore: 0.9,
// Whether to unredact LLM responses (default: true)
redactResponses: true,
// Custom placeholder format
placeholderFormat: (type, n) => `<<${type}_${n}>>`,
// Device override ('wasm' | 'webgpu')
device: 'webgpu',
// Model loading progress callback
onProgress: ({ status, progress }) => {
console.log(status, progress);
},
}),
});The model loads lazily on first use (first request will be slower). To pre-load:
import { createPrivacyFilter } from 'ai-sdk-privacy-filter-middleware';
const middleware = await createPrivacyFilter({ device: 'webgpu' });
const model = wrapLanguageModel({
model: openai('gpt-4o'), // or openrouter('anthropic/claude-3.5-sonnet')
middleware,
});The model auto-detects 8 categories of PII:
| Type | Placeholder | Example |
|---|---|---|
private_person |
[PERSON_N] |
Alice Smith |
private_email |
[EMAIL_N] |
alice@example.com |
private_phone |
[PHONE_N] |
+1-555-0123 |
private_address |
[ADDRESS_N] |
123 Main St |
private_date |
[DATE_N] |
1990-01-15 |
private_url |
[URL_N] |
https://example.com |
account_number |
[ACCOUNT_N] |
1234-5678-9012 |
secret |
[SECRET_N] |
API keys, passwords |
User message ──► transformParams (detect + redact PII)
│
▼
LLM sees redacted text
│
▼
LLM response (with placeholders)
│
▼
wrapGenerate/wrapStream (unredact placeholders)
│
▼
User sees original PII restored
The detection model runs locally via Transformers.js (WASM or WebGPU). Only user and system messages are redacted — assistant and tool messages pass through untouched. Response unredaction uses regex replacement on the known placeholders — no ML inference needed on the response side.
| Runtime | Device | Status |
|---|---|---|
| Node.js | WASM | Supported |
| Browser | WebGPU | Supported |
| Browser | WASM | Supported (fallback) |
The middleware auto-detects the best available device. WebGPU is used when available, falling back to WASM.
ai>= 4.0.0 (peer dependency)@huggingface/transformers>= 4.2.0 (bundled)
MIT