@intflows/genkit-guard 0.0.8 → 0.0.9-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,7 @@
1
- # **@intflows/genkit-guard**
1
+ # **@intflows/genkit-guard**
2
+
3
+ #### _This version uses OpenAI/privacy-filter instead of bert-base-NER_
4
+
2
5
  ### **Lightweight Intent, PII, and Safety Guardrails for Genkit**
3
6
 
4
7
  `@intflows/genkit-guard` provides a modular guardrail layer for Genkit flows.
@@ -24,7 +27,7 @@ This library is designed for developers who want **practical, production‑ready
24
27
  Blocks jailbreak attempts using pattern‑based heuristics.
25
28
 
26
29
  - **Model‑Light Architecture**
27
- The package uses local `all-MiniLM-L6-v2` and `bert-base-NER` Models, these Models are downloaded once and cached locally.
30
+ The package uses local `all-MiniLM-L6-v2` and `openai/privacy-filter` Models, these Models are downloaded once and cached locally.
28
31
 
29
32
  - **Drop‑in Genkit Middleware**
30
33
  Works with `ai.generate`, `ai.generateStream`, and Genkit flows.
@@ -38,12 +41,12 @@ This library is designed for developers who want **practical, production‑ready
38
41
  npm install @intflows/genkit-guard
39
42
  ```
40
43
 
41
- This library uses lightweight transformer models (MiniLM + BERT‑NER).
44
+ This library uses lightweight transformer models (MiniLM + Openai/privacy-filter).
42
45
 
43
46
  Download them once.
44
47
 
45
48
  ```bash
46
- ## Download the transformer models (MiniLM + BERT‑NER)
49
+ ## Download the transformer models (MiniLM + OpenAI/privacy-filter)
47
50
  node node_modules/@intflows/genkit-guard/scripts/download-model.js
48
51
  ```
49
52
 
@@ -62,6 +65,7 @@ npm install @intflows/genkit-guard
62
65
  # Download Local Models (Only needed once)
63
66
  node node_modules/@intflows/genkit-guard/scripts/download-model.js
64
67
  ```
68
+ _ This downloads the models to ./models folder, the total size is ~1.5 GB ( 1GB for Openai/privacy-filter + .5 GB for MiniLM-L6-v2)
65
69
 
66
70
  ### 2. Update genkit
67
71
 
@@ -133,9 +137,9 @@ Before the LLM sees the prompt:
133
137
  Detected PII includes:
134
138
 
135
139
  - Emails
136
- - Phone numbers
137
- - Names (NER)
140
+ - Phone numbers
138
141
  - AU identifiers (Medicare, TFN, ABN, etc.)
142
+ - PII detected by local Model (OpenAI/privacy-filter)
139
143
 
140
144
  ### **3. LLM Call**
141
145
  The masked prompt is sent to the model.
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- export { guard } from './middleware/middleware.js';
1
+ export { guard, guardAction, guardMiddleware, guardPlugin } from './middleware/middleware.js';
2
2
  export * from './core/types.js';
3
3
  /**
4
4
  * Pre-load the model to avoid cold-start delay on first user request.
5
5
  */
6
- export declare function initGuard(): Promise<void>;
6
+ export declare function initGuard(config?: any): Promise<void>;
package/dist/index.js CHANGED
@@ -1,15 +1,42 @@
1
1
  import { ModelSingleton } from './util/singleton.js';
2
2
  // export { intentGuard, piiGuard } from './middleware/middleware.js';
3
- export { guard } from './middleware/middleware.js';
3
+ export { guard, guardAction, guardMiddleware, guardPlugin } from './middleware/middleware.js';
4
4
  export * from './core/types.js';
5
+ function logGuardEvent(eventName, body, attributes = {}) {
6
+ console.log(JSON.stringify({
7
+ timestamp: new Date().toISOString(),
8
+ severityText: 'INFO',
9
+ severityNumber: 9,
10
+ body,
11
+ resource: {
12
+ attributes: {
13
+ 'service.name': '@intflows/genkit-guard',
14
+ },
15
+ },
16
+ attributes: {
17
+ 'event.name': eventName,
18
+ 'code.namespace': 'genkit-guard',
19
+ ...attributes,
20
+ },
21
+ }));
22
+ }
5
23
  /**
6
24
  * Pre-load the model to avoid cold-start delay on first user request.
7
25
  */
8
- export async function initGuard() {
9
- console.log('[Guard] Loading local models...');
10
- await Promise.all([
11
- ModelSingleton.getExtractor(),
12
- ModelSingleton.getNER()
13
- ]);
14
- console.log('[Guard] Models loaded');
26
+ export async function initGuard(config) {
27
+ logGuardEvent('guard.models.loading', 'Loading local guard models');
28
+ const extractorModel = config?.models?.extractor ?? 'Xenova/all-MiniLM-L6-v2';
29
+ const piiModel = config?.pii?.model;
30
+ const piiMode = config?.pii?.mode ?? 'ner';
31
+ const tasks = [ModelSingleton.getExtractor(extractorModel)];
32
+ if (piiMode === 'ner') {
33
+ tasks.push(ModelSingleton.getNER(piiModel ?? 'Xenova/bert-base-NER'));
34
+ }
35
+ else {
36
+ tasks.push(ModelSingleton.getPIIClassifier(piiModel ?? 'openai/privacy-filter'));
37
+ }
38
+ await Promise.all(tasks);
39
+ logGuardEvent('guard.models.loaded', 'Local guard models loaded', {
40
+ piiMode,
41
+ });
15
42
  }
@@ -1 +1,380 @@
1
- export declare function guard(config: any): (req: any, next: any) => Promise<any>;
1
+ import { z } from 'genkit';
2
+ declare const guardConfigSchema: z.ZodObject<{
3
+ intent: z.ZodOptional<z.ZodObject<{
4
+ mode: z.ZodOptional<z.ZodString>;
5
+ allowedIntent: z.ZodOptional<z.ZodString>;
6
+ semantic: z.ZodObject<{
7
+ threshold: z.ZodOptional<z.ZodNumber>;
8
+ intents: z.ZodRecord<z.ZodString, z.ZodString>;
9
+ }, "strip", z.ZodTypeAny, {
10
+ intents: Record<string, string>;
11
+ threshold?: number | undefined;
12
+ }, {
13
+ intents: Record<string, string>;
14
+ threshold?: number | undefined;
15
+ }>;
16
+ }, "strip", z.ZodTypeAny, {
17
+ semantic: {
18
+ intents: Record<string, string>;
19
+ threshold?: number | undefined;
20
+ };
21
+ mode?: string | undefined;
22
+ allowedIntent?: string | undefined;
23
+ }, {
24
+ semantic: {
25
+ intents: Record<string, string>;
26
+ threshold?: number | undefined;
27
+ };
28
+ mode?: string | undefined;
29
+ allowedIntent?: string | undefined;
30
+ }>>;
31
+ pii: z.ZodOptional<z.ZodObject<{
32
+ reversible: z.ZodOptional<z.ZodBoolean>;
33
+ model: z.ZodOptional<z.ZodString>;
34
+ mode: z.ZodOptional<z.ZodEnum<["ner", "classifier"]>>;
35
+ }, "strip", z.ZodTypeAny, {
36
+ mode?: "ner" | "classifier" | undefined;
37
+ reversible?: boolean | undefined;
38
+ model?: string | undefined;
39
+ }, {
40
+ mode?: "ner" | "classifier" | undefined;
41
+ reversible?: boolean | undefined;
42
+ model?: string | undefined;
43
+ }>>;
44
+ logging: z.ZodOptional<z.ZodObject<{
45
+ enabled: z.ZodOptional<z.ZodBoolean>;
46
+ level: z.ZodOptional<z.ZodEnum<["debug", "info", "warn", "error"]>>;
47
+ serviceName: z.ZodOptional<z.ZodString>;
48
+ }, "strip", z.ZodTypeAny, {
49
+ enabled?: boolean | undefined;
50
+ level?: "debug" | "info" | "warn" | "error" | undefined;
51
+ serviceName?: string | undefined;
52
+ }, {
53
+ enabled?: boolean | undefined;
54
+ level?: "debug" | "info" | "warn" | "error" | undefined;
55
+ serviceName?: string | undefined;
56
+ }>>;
57
+ models: z.ZodOptional<z.ZodObject<{
58
+ extractor: z.ZodOptional<z.ZodString>;
59
+ }, "strip", z.ZodTypeAny, {
60
+ extractor?: string | undefined;
61
+ }, {
62
+ extractor?: string | undefined;
63
+ }>>;
64
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
65
+ intent: z.ZodOptional<z.ZodObject<{
66
+ mode: z.ZodOptional<z.ZodString>;
67
+ allowedIntent: z.ZodOptional<z.ZodString>;
68
+ semantic: z.ZodObject<{
69
+ threshold: z.ZodOptional<z.ZodNumber>;
70
+ intents: z.ZodRecord<z.ZodString, z.ZodString>;
71
+ }, "strip", z.ZodTypeAny, {
72
+ intents: Record<string, string>;
73
+ threshold?: number | undefined;
74
+ }, {
75
+ intents: Record<string, string>;
76
+ threshold?: number | undefined;
77
+ }>;
78
+ }, "strip", z.ZodTypeAny, {
79
+ semantic: {
80
+ intents: Record<string, string>;
81
+ threshold?: number | undefined;
82
+ };
83
+ mode?: string | undefined;
84
+ allowedIntent?: string | undefined;
85
+ }, {
86
+ semantic: {
87
+ intents: Record<string, string>;
88
+ threshold?: number | undefined;
89
+ };
90
+ mode?: string | undefined;
91
+ allowedIntent?: string | undefined;
92
+ }>>;
93
+ pii: z.ZodOptional<z.ZodObject<{
94
+ reversible: z.ZodOptional<z.ZodBoolean>;
95
+ model: z.ZodOptional<z.ZodString>;
96
+ mode: z.ZodOptional<z.ZodEnum<["ner", "classifier"]>>;
97
+ }, "strip", z.ZodTypeAny, {
98
+ mode?: "ner" | "classifier" | undefined;
99
+ reversible?: boolean | undefined;
100
+ model?: string | undefined;
101
+ }, {
102
+ mode?: "ner" | "classifier" | undefined;
103
+ reversible?: boolean | undefined;
104
+ model?: string | undefined;
105
+ }>>;
106
+ logging: z.ZodOptional<z.ZodObject<{
107
+ enabled: z.ZodOptional<z.ZodBoolean>;
108
+ level: z.ZodOptional<z.ZodEnum<["debug", "info", "warn", "error"]>>;
109
+ serviceName: z.ZodOptional<z.ZodString>;
110
+ }, "strip", z.ZodTypeAny, {
111
+ enabled?: boolean | undefined;
112
+ level?: "debug" | "info" | "warn" | "error" | undefined;
113
+ serviceName?: string | undefined;
114
+ }, {
115
+ enabled?: boolean | undefined;
116
+ level?: "debug" | "info" | "warn" | "error" | undefined;
117
+ serviceName?: string | undefined;
118
+ }>>;
119
+ models: z.ZodOptional<z.ZodObject<{
120
+ extractor: z.ZodOptional<z.ZodString>;
121
+ }, "strip", z.ZodTypeAny, {
122
+ extractor?: string | undefined;
123
+ }, {
124
+ extractor?: string | undefined;
125
+ }>>;
126
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
127
+ intent: z.ZodOptional<z.ZodObject<{
128
+ mode: z.ZodOptional<z.ZodString>;
129
+ allowedIntent: z.ZodOptional<z.ZodString>;
130
+ semantic: z.ZodObject<{
131
+ threshold: z.ZodOptional<z.ZodNumber>;
132
+ intents: z.ZodRecord<z.ZodString, z.ZodString>;
133
+ }, "strip", z.ZodTypeAny, {
134
+ intents: Record<string, string>;
135
+ threshold?: number | undefined;
136
+ }, {
137
+ intents: Record<string, string>;
138
+ threshold?: number | undefined;
139
+ }>;
140
+ }, "strip", z.ZodTypeAny, {
141
+ semantic: {
142
+ intents: Record<string, string>;
143
+ threshold?: number | undefined;
144
+ };
145
+ mode?: string | undefined;
146
+ allowedIntent?: string | undefined;
147
+ }, {
148
+ semantic: {
149
+ intents: Record<string, string>;
150
+ threshold?: number | undefined;
151
+ };
152
+ mode?: string | undefined;
153
+ allowedIntent?: string | undefined;
154
+ }>>;
155
+ pii: z.ZodOptional<z.ZodObject<{
156
+ reversible: z.ZodOptional<z.ZodBoolean>;
157
+ model: z.ZodOptional<z.ZodString>;
158
+ mode: z.ZodOptional<z.ZodEnum<["ner", "classifier"]>>;
159
+ }, "strip", z.ZodTypeAny, {
160
+ mode?: "ner" | "classifier" | undefined;
161
+ reversible?: boolean | undefined;
162
+ model?: string | undefined;
163
+ }, {
164
+ mode?: "ner" | "classifier" | undefined;
165
+ reversible?: boolean | undefined;
166
+ model?: string | undefined;
167
+ }>>;
168
+ logging: z.ZodOptional<z.ZodObject<{
169
+ enabled: z.ZodOptional<z.ZodBoolean>;
170
+ level: z.ZodOptional<z.ZodEnum<["debug", "info", "warn", "error"]>>;
171
+ serviceName: z.ZodOptional<z.ZodString>;
172
+ }, "strip", z.ZodTypeAny, {
173
+ enabled?: boolean | undefined;
174
+ level?: "debug" | "info" | "warn" | "error" | undefined;
175
+ serviceName?: string | undefined;
176
+ }, {
177
+ enabled?: boolean | undefined;
178
+ level?: "debug" | "info" | "warn" | "error" | undefined;
179
+ serviceName?: string | undefined;
180
+ }>>;
181
+ models: z.ZodOptional<z.ZodObject<{
182
+ extractor: z.ZodOptional<z.ZodString>;
183
+ }, "strip", z.ZodTypeAny, {
184
+ extractor?: string | undefined;
185
+ }, {
186
+ extractor?: string | undefined;
187
+ }>>;
188
+ }, z.ZodTypeAny, "passthrough">>;
189
+ type GuardConfig = z.infer<typeof guardConfigSchema>;
190
+ export declare const guardMiddleware: import("genkit").GenerateMiddleware<z.ZodObject<{
191
+ intent: z.ZodOptional<z.ZodObject<{
192
+ mode: z.ZodOptional<z.ZodString>;
193
+ allowedIntent: z.ZodOptional<z.ZodString>;
194
+ semantic: z.ZodObject<{
195
+ threshold: z.ZodOptional<z.ZodNumber>;
196
+ intents: z.ZodRecord<z.ZodString, z.ZodString>;
197
+ }, "strip", z.ZodTypeAny, {
198
+ intents: Record<string, string>;
199
+ threshold?: number | undefined;
200
+ }, {
201
+ intents: Record<string, string>;
202
+ threshold?: number | undefined;
203
+ }>;
204
+ }, "strip", z.ZodTypeAny, {
205
+ semantic: {
206
+ intents: Record<string, string>;
207
+ threshold?: number | undefined;
208
+ };
209
+ mode?: string | undefined;
210
+ allowedIntent?: string | undefined;
211
+ }, {
212
+ semantic: {
213
+ intents: Record<string, string>;
214
+ threshold?: number | undefined;
215
+ };
216
+ mode?: string | undefined;
217
+ allowedIntent?: string | undefined;
218
+ }>>;
219
+ pii: z.ZodOptional<z.ZodObject<{
220
+ reversible: z.ZodOptional<z.ZodBoolean>;
221
+ model: z.ZodOptional<z.ZodString>;
222
+ mode: z.ZodOptional<z.ZodEnum<["ner", "classifier"]>>;
223
+ }, "strip", z.ZodTypeAny, {
224
+ mode?: "ner" | "classifier" | undefined;
225
+ reversible?: boolean | undefined;
226
+ model?: string | undefined;
227
+ }, {
228
+ mode?: "ner" | "classifier" | undefined;
229
+ reversible?: boolean | undefined;
230
+ model?: string | undefined;
231
+ }>>;
232
+ logging: z.ZodOptional<z.ZodObject<{
233
+ enabled: z.ZodOptional<z.ZodBoolean>;
234
+ level: z.ZodOptional<z.ZodEnum<["debug", "info", "warn", "error"]>>;
235
+ serviceName: z.ZodOptional<z.ZodString>;
236
+ }, "strip", z.ZodTypeAny, {
237
+ enabled?: boolean | undefined;
238
+ level?: "debug" | "info" | "warn" | "error" | undefined;
239
+ serviceName?: string | undefined;
240
+ }, {
241
+ enabled?: boolean | undefined;
242
+ level?: "debug" | "info" | "warn" | "error" | undefined;
243
+ serviceName?: string | undefined;
244
+ }>>;
245
+ models: z.ZodOptional<z.ZodObject<{
246
+ extractor: z.ZodOptional<z.ZodString>;
247
+ }, "strip", z.ZodTypeAny, {
248
+ extractor?: string | undefined;
249
+ }, {
250
+ extractor?: string | undefined;
251
+ }>>;
252
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
253
+ intent: z.ZodOptional<z.ZodObject<{
254
+ mode: z.ZodOptional<z.ZodString>;
255
+ allowedIntent: z.ZodOptional<z.ZodString>;
256
+ semantic: z.ZodObject<{
257
+ threshold: z.ZodOptional<z.ZodNumber>;
258
+ intents: z.ZodRecord<z.ZodString, z.ZodString>;
259
+ }, "strip", z.ZodTypeAny, {
260
+ intents: Record<string, string>;
261
+ threshold?: number | undefined;
262
+ }, {
263
+ intents: Record<string, string>;
264
+ threshold?: number | undefined;
265
+ }>;
266
+ }, "strip", z.ZodTypeAny, {
267
+ semantic: {
268
+ intents: Record<string, string>;
269
+ threshold?: number | undefined;
270
+ };
271
+ mode?: string | undefined;
272
+ allowedIntent?: string | undefined;
273
+ }, {
274
+ semantic: {
275
+ intents: Record<string, string>;
276
+ threshold?: number | undefined;
277
+ };
278
+ mode?: string | undefined;
279
+ allowedIntent?: string | undefined;
280
+ }>>;
281
+ pii: z.ZodOptional<z.ZodObject<{
282
+ reversible: z.ZodOptional<z.ZodBoolean>;
283
+ model: z.ZodOptional<z.ZodString>;
284
+ mode: z.ZodOptional<z.ZodEnum<["ner", "classifier"]>>;
285
+ }, "strip", z.ZodTypeAny, {
286
+ mode?: "ner" | "classifier" | undefined;
287
+ reversible?: boolean | undefined;
288
+ model?: string | undefined;
289
+ }, {
290
+ mode?: "ner" | "classifier" | undefined;
291
+ reversible?: boolean | undefined;
292
+ model?: string | undefined;
293
+ }>>;
294
+ logging: z.ZodOptional<z.ZodObject<{
295
+ enabled: z.ZodOptional<z.ZodBoolean>;
296
+ level: z.ZodOptional<z.ZodEnum<["debug", "info", "warn", "error"]>>;
297
+ serviceName: z.ZodOptional<z.ZodString>;
298
+ }, "strip", z.ZodTypeAny, {
299
+ enabled?: boolean | undefined;
300
+ level?: "debug" | "info" | "warn" | "error" | undefined;
301
+ serviceName?: string | undefined;
302
+ }, {
303
+ enabled?: boolean | undefined;
304
+ level?: "debug" | "info" | "warn" | "error" | undefined;
305
+ serviceName?: string | undefined;
306
+ }>>;
307
+ models: z.ZodOptional<z.ZodObject<{
308
+ extractor: z.ZodOptional<z.ZodString>;
309
+ }, "strip", z.ZodTypeAny, {
310
+ extractor?: string | undefined;
311
+ }, {
312
+ extractor?: string | undefined;
313
+ }>>;
314
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
315
+ intent: z.ZodOptional<z.ZodObject<{
316
+ mode: z.ZodOptional<z.ZodString>;
317
+ allowedIntent: z.ZodOptional<z.ZodString>;
318
+ semantic: z.ZodObject<{
319
+ threshold: z.ZodOptional<z.ZodNumber>;
320
+ intents: z.ZodRecord<z.ZodString, z.ZodString>;
321
+ }, "strip", z.ZodTypeAny, {
322
+ intents: Record<string, string>;
323
+ threshold?: number | undefined;
324
+ }, {
325
+ intents: Record<string, string>;
326
+ threshold?: number | undefined;
327
+ }>;
328
+ }, "strip", z.ZodTypeAny, {
329
+ semantic: {
330
+ intents: Record<string, string>;
331
+ threshold?: number | undefined;
332
+ };
333
+ mode?: string | undefined;
334
+ allowedIntent?: string | undefined;
335
+ }, {
336
+ semantic: {
337
+ intents: Record<string, string>;
338
+ threshold?: number | undefined;
339
+ };
340
+ mode?: string | undefined;
341
+ allowedIntent?: string | undefined;
342
+ }>>;
343
+ pii: z.ZodOptional<z.ZodObject<{
344
+ reversible: z.ZodOptional<z.ZodBoolean>;
345
+ model: z.ZodOptional<z.ZodString>;
346
+ mode: z.ZodOptional<z.ZodEnum<["ner", "classifier"]>>;
347
+ }, "strip", z.ZodTypeAny, {
348
+ mode?: "ner" | "classifier" | undefined;
349
+ reversible?: boolean | undefined;
350
+ model?: string | undefined;
351
+ }, {
352
+ mode?: "ner" | "classifier" | undefined;
353
+ reversible?: boolean | undefined;
354
+ model?: string | undefined;
355
+ }>>;
356
+ logging: z.ZodOptional<z.ZodObject<{
357
+ enabled: z.ZodOptional<z.ZodBoolean>;
358
+ level: z.ZodOptional<z.ZodEnum<["debug", "info", "warn", "error"]>>;
359
+ serviceName: z.ZodOptional<z.ZodString>;
360
+ }, "strip", z.ZodTypeAny, {
361
+ enabled?: boolean | undefined;
362
+ level?: "debug" | "info" | "warn" | "error" | undefined;
363
+ serviceName?: string | undefined;
364
+ }, {
365
+ enabled?: boolean | undefined;
366
+ level?: "debug" | "info" | "warn" | "error" | undefined;
367
+ serviceName?: string | undefined;
368
+ }>>;
369
+ models: z.ZodOptional<z.ZodObject<{
370
+ extractor: z.ZodOptional<z.ZodString>;
371
+ }, "strip", z.ZodTypeAny, {
372
+ extractor?: string | undefined;
373
+ }, {
374
+ extractor?: string | undefined;
375
+ }>>;
376
+ }, z.ZodTypeAny, "passthrough">>, void>;
377
+ export declare const guardPlugin: (pluginOptions: void) => import("@genkit-ai/ai").GenkitPluginV2;
378
+ export declare function guard(config?: GuardConfig): (req: any, ctxOrNext: any, maybeNext?: any) => Promise<any>;
379
+ export declare const guardAction: typeof guard;
380
+ export {};
@@ -1,113 +1,316 @@
1
+ import { generateMiddleware, z } from 'genkit';
1
2
  import { analyzeIntentStructured, detectInjection } from '../intent/intentAnalyzer.js';
2
3
  import { detectPII } from '../pii/detector.js';
3
4
  import { PiiTokenizer } from '../pii/tokenizer.js';
5
+ const GUARD_CONTEXT_KEY = '__genkitGuard';
6
+ const guardConfigSchema = z.object({
7
+ intent: z.object({
8
+ mode: z.string().optional(),
9
+ allowedIntent: z.string().optional(),
10
+ semantic: z.object({
11
+ threshold: z.number().optional(),
12
+ intents: z.record(z.string(), z.string()),
13
+ }),
14
+ }).optional(),
15
+ pii: z.object({
16
+ reversible: z.boolean().optional(),
17
+ model: z.string().optional(),
18
+ mode: z.enum(['ner', 'classifier']).optional(),
19
+ }).optional(),
20
+ logging: z.object({
21
+ enabled: z.boolean().optional(),
22
+ level: z.enum(['debug', 'info', 'warn', 'error']).optional(),
23
+ serviceName: z.string().optional(),
24
+ }).optional(),
25
+ models: z.object({
26
+ extractor: z.string().optional(),
27
+ }).optional(),
28
+ }).passthrough();
29
+ export const guardMiddleware = generateMiddleware({
30
+ name: 'genkitGuard',
31
+ description: 'Blocks prompt injection and disallowed intent, masks PII before model calls, restores PII for tool calls, and audits tool PII access.',
32
+ configSchema: guardConfigSchema,
33
+ }, ({ config }) => createGuardHooks(config));
34
+ export const guardPlugin = guardMiddleware.plugin;
4
35
  export function guard(config) {
5
- return async (req, next) => {
6
- const input = req.prompt ||
7
- req.messages?.[req.messages.length - 1]?.content?.[0]?.text ||
8
- "";
9
- // -------------------------
10
- // 1. INTENT ANALYSIS
11
- // -------------------------
12
- const isInjection = await detectInjection(input);
13
- if (isInjection) {
14
- console.warn(`[Intent Guard] Prompt injection pattern detected in input`);
15
- return block("Prompt injection detected", {
16
- reason: "pattern_match"
17
- });
36
+ const hooks = createGuardHooks(config);
37
+ const baseMiddleware = guardMiddleware(config);
38
+ const fnRunner = async (req, ctxOrNext, maybeNext) => {
39
+ if (typeof maybeNext === 'function') {
40
+ return hooks.model(req, ctxOrNext, maybeNext);
18
41
  }
19
- console.log(`[Intent Guard] Analyzing intent for user's input`);
20
- const intentResult = await analyzeIntentStructured(input, config.intent.semantic.intents, config.intent.semantic.threshold);
21
- console.log(`[Intent Guard] Detected intent: ${intentResult.intent} (score: ${intentResult.score.toFixed(2)})`);
22
- if (!intentResult.allowed) {
23
- console.warn(`[Intent Guard] Intent "${intentResult.intent}" not allowed ${intentResult.allowed}`);
24
- return block("Intent not allowed", {
42
+ return hooks.model(req, {}, async (modifiedReq) => ctxOrNext(modifiedReq || req));
43
+ };
44
+ const source = Object.assign({}, baseMiddleware, hooks);
45
+ for (const key of Object.keys(source)) {
46
+ if (key === 'name')
47
+ continue;
48
+ Object.defineProperty(fnRunner, key, {
49
+ value: source[key],
50
+ writable: true,
51
+ configurable: true,
52
+ enumerable: true,
53
+ });
54
+ }
55
+ return fnRunner;
56
+ }
57
+ export const guardAction = guard;
58
+ function createGuardHooks(config) {
59
+ const logger = createLogger(config);
60
+ return {
61
+ model: async (req, ctx, next) => {
62
+ const input = getInputText(req);
63
+ logger('info', 'guard.model.start', 'Starting guard checks for model request');
64
+ const isInjection = await detectInjection(input);
65
+ if (isInjection) {
66
+ logger('warn', 'guard.intent.blocked', 'Prompt injection pattern detected', {
67
+ reason: 'pattern_match',
68
+ });
69
+ return block('Prompt injection detected', {
70
+ reason: 'pattern_match',
71
+ });
72
+ }
73
+ logger('info', 'guard.intent.analysis.start', 'Analyzing request intent');
74
+ const intentResult = await analyzeIntentStructured(input, config?.intent?.semantic?.intents ?? {}, config?.intent?.semantic?.threshold ?? 0.7);
75
+ logger('info', 'guard.intent.analysis.complete', 'Intent analysis completed', {
25
76
  intent: intentResult.intent,
26
- score: intentResult.score
77
+ score: roundScore(intentResult.score),
78
+ allowed: intentResult.allowed,
79
+ });
80
+ if (!intentResult.allowed) {
81
+ logger('warn', 'guard.intent.blocked', 'Intent not allowed', {
82
+ intent: intentResult.intent,
83
+ score: roundScore(intentResult.score),
84
+ });
85
+ return block('Intent not allowed', {
86
+ intent: intentResult.intent,
87
+ score: intentResult.score,
88
+ });
89
+ }
90
+ const textForPii = collectModelRequestText(req);
91
+ const piiResponse = await scanPII(textForPii, config);
92
+ const piiMatches = piiResponse?.matches || [];
93
+ const tokenizer = new PiiTokenizer();
94
+ const piiTypes = uniqueTypes(piiMatches);
95
+ maskModelRequest(req, tokenizer, piiMatches);
96
+ pushTokenizer(ctx, tokenizer);
97
+ logger(piiMatches.length > 0 ? 'warn' : 'info', 'guard.model.pii.masked', 'PII scan completed for model request', {
98
+ piiDetected: piiMatches.length > 0,
99
+ piiMatchCount: piiMatches.length,
100
+ piiTypes,
101
+ piiMode: config?.pii?.mode ?? 'ner',
102
+ classifierOutputPresent: Boolean(piiResponse.classifier),
103
+ });
104
+ req.metadata = {
105
+ ...req.metadata,
106
+ intent: intentResult.intent,
107
+ score: intentResult.score,
108
+ piiDetected: piiMatches.length > 0,
109
+ piiTypes,
110
+ maskedInput: getInputText(req),
111
+ piiModel: config?.pii?.model,
112
+ piiMode: config?.pii?.mode,
113
+ piiClassifierOutput: piiResponse.classifier,
114
+ };
115
+ const res = await next(req, ctx);
116
+ const unmaskedResponse = unmaskObject(res, [tokenizer]);
117
+ logger('info', 'guard.model.response.unmasked', 'Model response unmasked for downstream execution', {
118
+ piiTypes,
119
+ });
120
+ return unmaskedResponse;
121
+ },
122
+ tool: async (req, ctx, next) => {
123
+ const state = getGuardState(ctx);
124
+ const toolName = req?.toolRequest?.name;
125
+ if (req?.toolRequest && 'input' in req.toolRequest) {
126
+ req.toolRequest.input = unmaskObject(req.toolRequest.input, state.tokenizers);
127
+ }
128
+ const toolInputText = collectStrings(req?.toolRequest?.input).join('\n');
129
+ const piiResponse = await scanPII(toolInputText, config);
130
+ const piiMatches = piiResponse?.matches || [];
131
+ const piiTypes = uniqueTypes(piiMatches);
132
+ req.metadata = {
133
+ ...req.metadata,
134
+ piiDetected: piiMatches.length > 0,
135
+ piiTypes,
136
+ piiMatchCount: piiMatches.length,
137
+ };
138
+ logger(piiMatches.length > 0 ? 'warn' : 'info', 'guard.tool.pii.checked', 'Tool request PII scan completed', {
139
+ toolName,
140
+ piiDetected: piiMatches.length > 0,
141
+ piiMatchCount: piiMatches.length,
142
+ piiTypes,
27
143
  });
144
+ const res = await next(req, ctx);
145
+ const toolResponseText = collectStrings(res).join('\n');
146
+ if (toolResponseText) {
147
+ const responsePii = await scanPII(toolResponseText, config);
148
+ const responseMatches = responsePii?.matches || [];
149
+ logger(responseMatches.length > 0 ? 'warn' : 'info', 'guard.tool.response.pii.checked', 'Tool response PII scan completed', {
150
+ toolName,
151
+ piiDetected: responseMatches.length > 0,
152
+ piiMatchCount: responseMatches.length,
153
+ piiTypes: uniqueTypes(responseMatches),
154
+ });
155
+ }
156
+ return res;
157
+ },
158
+ };
159
+ }
160
+ function getInputText(req) {
161
+ if (typeof req.prompt === 'string') {
162
+ return req.prompt;
163
+ }
164
+ const lastMessage = req.messages?.[req.messages.length - 1];
165
+ const firstContent = lastMessage?.content?.[0];
166
+ if (typeof firstContent?.text === 'string') {
167
+ return firstContent.text;
168
+ }
169
+ if (typeof firstContent === 'string') {
170
+ return firstContent;
171
+ }
172
+ return collectStrings(lastMessage).join('\n');
173
+ }
174
+ function collectModelRequestText(req) {
175
+ return [
176
+ ...collectStrings(req?.prompt),
177
+ ...collectStrings(req?.messages),
178
+ ...collectStrings(req?.docs),
179
+ ].join('\n');
180
+ }
181
+ function maskModelRequest(req, tokenizer, matches) {
182
+ if (typeof req.prompt === 'string') {
183
+ req.prompt = tokenizer.mask(req.prompt, matches).maskedText;
184
+ }
185
+ if (req.messages) {
186
+ req.messages = transformStrings(req.messages, (value) => tokenizer.mask(value, matches).maskedText);
187
+ }
188
+ if (req.docs) {
189
+ req.docs = transformStrings(req.docs, (value) => tokenizer.mask(value, matches).maskedText);
190
+ }
191
+ }
192
+ function unmaskObject(obj, tokenizers) {
193
+ return transformStrings(obj, (value) => {
194
+ let result = value;
195
+ for (const tokenizer of tokenizers) {
196
+ result = tokenizer.unmask(result);
28
197
  }
29
- // -------------------------
30
- // 2. PII DETECTION + MASKING
31
- // -------------------------
32
- const piiMatches = await detectPII(input);
33
- console.log(`[PII Guard] Detected PII: ${piiMatches.length} matches found`);
34
- const tokenizer = new PiiTokenizer(); // <-- SINGLE INSTANCE
35
- const piiResult = tokenizer.mask(input, piiMatches);
36
- console.log(`[PII Guard] Masked PII: ${piiResult.piiTypes.length} types found`);
37
- // Attach tokenizer so response can unmask
38
- req.metadata = {
39
- ...req.metadata,
40
- piiTokenizer: tokenizer,
41
- intent: intentResult.intent,
42
- score: intentResult.score,
43
- piiDetected: piiMatches.length > 0,
44
- piiTypes: piiResult.piiTypes,
45
- maskedInput: piiResult.maskedText
198
+ return result;
199
+ });
200
+ }
201
+ function transformStrings(obj, transform) {
202
+ if (typeof obj === 'string') {
203
+ return transform(obj);
204
+ }
205
+ if (Array.isArray(obj)) {
206
+ for (let i = 0; i < obj.length; i++) {
207
+ obj[i] = transformStrings(obj[i], transform);
208
+ }
209
+ return obj;
210
+ }
211
+ if (obj !== null && typeof obj === 'object') {
212
+ for (const key of Object.keys(obj)) {
213
+ obj[key] = transformStrings(obj[key], transform);
214
+ }
215
+ return obj;
216
+ }
217
+ return obj;
218
+ }
219
+ function collectStrings(obj) {
220
+ if (typeof obj === 'string') {
221
+ return [obj];
222
+ }
223
+ if (Array.isArray(obj)) {
224
+ return obj.flatMap(collectStrings);
225
+ }
226
+ if (obj !== null && typeof obj === 'object') {
227
+ return Object.values(obj).flatMap(collectStrings);
228
+ }
229
+ return [];
230
+ }
231
+ async function scanPII(text, config) {
232
+ if (!text.trim()) {
233
+ return {
234
+ matches: [],
235
+ classifier: undefined,
46
236
  };
47
- // Replace input
48
- req.prompt = piiResult.maskedText;
49
- req.messages = [
50
- {
51
- role: 'user',
52
- content: [{ text: piiResult.maskedText }],
237
+ }
238
+ return detectPII(text, {
239
+ model: config?.pii?.model,
240
+ mode: config?.pii?.mode,
241
+ });
242
+ }
243
+ function getGuardState(ctx = {}) {
244
+ ctx.context = ctx.context || {};
245
+ ctx.context[GUARD_CONTEXT_KEY] = ctx.context[GUARD_CONTEXT_KEY] || { tokenizers: [] };
246
+ return ctx.context[GUARD_CONTEXT_KEY];
247
+ }
248
+ function pushTokenizer(ctx, tokenizer) {
249
+ const state = getGuardState(ctx);
250
+ state.tokenizers.push(tokenizer);
251
+ }
252
+ function uniqueTypes(matches) {
253
+ return Array.from(new Set(matches.map((match) => match.type.toLowerCase())));
254
+ }
255
+ function roundScore(score) {
256
+ return Math.round(score * 10000) / 10000;
257
+ }
258
+ function createLogger(config) {
259
+ const enabled = config?.logging?.enabled ?? true;
260
+ const minimumLevel = config?.logging?.level ?? 'info';
261
+ const serviceName = config?.logging?.serviceName ?? '@intflows/genkit-guard';
262
+ const levelRank = {
263
+ debug: 10,
264
+ info: 20,
265
+ warn: 30,
266
+ error: 40,
267
+ };
268
+ const severityNumber = {
269
+ debug: 5,
270
+ info: 9,
271
+ warn: 13,
272
+ error: 17,
273
+ };
274
+ return (severity, eventName, body, attributes = {}) => {
275
+ if (!enabled || levelRank[severity] < levelRank[minimumLevel]) {
276
+ return;
277
+ }
278
+ const record = {
279
+ timestamp: new Date().toISOString(),
280
+ severityText: severity.toUpperCase(),
281
+ severityNumber: severityNumber[severity],
282
+ body,
283
+ resource: {
284
+ attributes: {
285
+ 'service.name': serviceName,
286
+ },
53
287
  },
54
- ];
55
- // -------------------------
56
- // 3. LLM CALL
57
- // -------------------------
58
- const res = await next(req);
59
- // -------------------------
60
- // 4. RESPONSE UNMASK
61
- // -------------------------
62
- console.log(`[PII Guard] Unmasking response if needed`);
63
- if (tokenizer) {
64
- /**
65
- * RECURSIVE TRANSFORMER
66
- * This will find every string in the Genkit response (no matter if it's in
67
- * candidates, message, custom, or output) and unmask it.
68
- */
69
- const transform = (obj) => {
70
- // 1. If it's a string, unmask it
71
- if (typeof obj === 'string') {
72
- return tokenizer.unmask(obj);
73
- }
74
- // 2. If it's an array, transform each element
75
- if (Array.isArray(obj)) {
76
- return obj.map(transform);
77
- }
78
- // 3. If it's an object, transform each value
79
- if (obj !== null && typeof obj === 'object') {
80
- // Note: We iterate keys and mutate the object directly
81
- // to ensure Genkit's internal references are updated.
82
- for (const key of Object.keys(obj)) {
83
- obj[key] = transform(obj[key]);
84
- }
85
- return obj;
86
- }
87
- // 4. Return as-is for numbers/booleans/null
88
- return obj;
89
- };
90
- // ----------------------------------------------------------------------
91
- // 5. Transform the entire response object in-place to unmask all strings
92
- // ----------------------------------------------------------------------
93
- transform(res);
94
- console.log("[PII Guard] Deep unmasking complete across all candidates and custom fields.");
288
+ attributes: {
289
+ 'event.name': eventName,
290
+ 'code.namespace': 'genkit-guard',
291
+ ...attributes,
292
+ },
293
+ };
294
+ const line = JSON.stringify(record);
295
+ if (severity === 'error') {
296
+ console.error(line);
297
+ }
298
+ else if (severity === 'warn') {
299
+ console.warn(line);
300
+ }
301
+ else {
302
+ console.log(line);
95
303
  }
96
- // ---------------------------------------------------------
97
- // 6. Return the modified response with unmasked content
98
- // ---------------------------------------------------------
99
- return res;
100
304
  };
101
305
  }
102
- // Helper to create a blocked response
103
306
  function block(message, metadata) {
104
307
  return {
105
308
  finishReason: 'blocked',
106
309
  output: {
107
- type: "error",
108
- status: "BLOCKED",
109
- message
310
+ type: 'error',
311
+ status: 'BLOCKED',
312
+ message,
110
313
  },
111
- metadata
314
+ metadata,
112
315
  };
113
316
  }
@@ -1,4 +1,10 @@
1
- export declare function detectPII(text: string): Promise<{
2
- type: string;
3
- value: string;
4
- }[]>;
1
+ export declare function detectPII(text: string, opts?: {
2
+ model?: string;
3
+ mode?: 'ner' | 'classifier';
4
+ }): Promise<{
5
+ matches: {
6
+ type: string;
7
+ value: string;
8
+ }[];
9
+ classifier: any;
10
+ }>;
@@ -15,20 +15,33 @@ const REGEX_RULES = [
15
15
  // CREDIT CARD (keep your existing one if needed)
16
16
  { type: 'CREDIT_CARD', pattern: /\b(?:\d[ -]*?){13,16}\b/g }
17
17
  ];
18
- export async function detectPII(text) {
19
- const ner = await ModelSingleton.getNER();
18
+ export async function detectPII(text, opts) {
19
+ const mode = opts?.mode ?? 'ner';
20
+ const model = opts?.model;
20
21
  const results = [];
21
- // ---- NER ----
22
- const entities = await ner(text);
23
- for (const e of entities) {
24
- if (e.entity.includes('PER')) {
25
- results.push({ type: 'NAME', value: e.word.replace('##', '') });
26
- }
27
- }
28
- // ---- REGEX ----
22
+ // ---- REGEX (always run) ----
29
23
  for (const rule of REGEX_RULES) {
30
24
  const matches = text.match(rule.pattern) || [];
31
25
  matches.forEach(m => results.push({ type: rule.type, value: m }));
32
26
  }
33
- return results;
27
+ // ---- NER ----
28
+ let classifierOutput = undefined;
29
+ if (mode === 'ner') {
30
+ const ner = await ModelSingleton.getNER(model);
31
+ const entities = await ner(text);
32
+ for (const e of entities) {
33
+ if (e.entity && e.entity.includes('PER')) {
34
+ results.push({ type: 'NAME', value: (e.word || '').replace(/##/g, '') });
35
+ }
36
+ }
37
+ }
38
+ else {
39
+ // classifier mode: we call the classifier and return its output alongside regex matches.
40
+ const cls = await ModelSingleton.getPIIClassifier(model);
41
+ classifierOutput = await cls(text);
42
+ }
43
+ return {
44
+ matches: results,
45
+ classifier: classifierOutput
46
+ };
34
47
  }
@@ -5,6 +5,7 @@ export type PiiResult = {
5
5
  };
6
6
  export declare class PiiTokenizer {
7
7
  private vault;
8
+ private valueToToken;
8
9
  private counter;
9
10
  private piiTypes;
10
11
  private createToken;
@@ -1,5 +1,6 @@
1
1
  export class PiiTokenizer {
2
2
  vault = new Map();
3
+ valueToToken = new Map();
3
4
  counter = 0;
4
5
  piiTypes = new Set();
5
6
  createToken(type) {
@@ -8,8 +9,16 @@ export class PiiTokenizer {
8
9
  mask(text, matches) {
9
10
  let masked = text;
10
11
  for (const match of matches) {
11
- const token = this.createToken(match.type);
12
- this.vault.set(token, match.value);
12
+ if (!masked.includes(match.value)) {
13
+ continue;
14
+ }
15
+ const key = `${match.type}:${match.value}`;
16
+ let token = this.valueToToken.get(key);
17
+ if (!token) {
18
+ token = this.createToken(match.type);
19
+ this.valueToToken.set(key, token);
20
+ this.vault.set(token, match.value);
21
+ }
13
22
  this.piiTypes.add(match.type.toLowerCase());
14
23
  masked = masked.split(match.value).join(token);
15
24
  }
@@ -1,7 +1,14 @@
1
1
  export declare class ModelSingleton {
2
- private static extractor;
3
- private static classifier;
2
+ private static extractors;
3
+ private static nerClassifiers;
4
+ private static textClassifiers;
4
5
  static init(): void;
5
- static getExtractor(): Promise<any>;
6
- static getNER(): Promise<any>;
6
+ static getExtractor(modelName?: string): Promise<any>;
7
+ static getNER(modelName?: string): Promise<any>;
8
+ static getPIIClassifier(modelName?: string): Promise<any>;
9
+ static preload(models?: {
10
+ extractor?: string;
11
+ ner?: string;
12
+ pii?: string;
13
+ }): Promise<void>;
7
14
  }
@@ -6,8 +6,9 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
6
  env.allowRemoteModels = false;
7
7
  env.localModelPath = path.join(__dirname, '../../models');
8
8
  export class ModelSingleton {
9
- static extractor = null;
10
- static classifier = null;
9
+ static extractors = new Map();
10
+ static nerClassifiers = new Map();
11
+ static textClassifiers = new Map();
11
12
  static init() {
12
13
  // Always resolve model path relative to the client app, not the library
13
14
  const projectRoot = process.cwd();
@@ -20,21 +21,55 @@ export class ModelSingleton {
20
21
  env.localModelPath = modelPath;
21
22
  // Allow remote download if missing
22
23
  env.allowRemoteModels = true;
23
- console.log("[Guard] Using model directory:", modelPath);
24
+ console.log(JSON.stringify({
25
+ timestamp: new Date().toISOString(),
26
+ severityText: 'INFO',
27
+ severityNumber: 9,
28
+ body: 'Using guard model directory',
29
+ resource: {
30
+ attributes: {
31
+ 'service.name': '@intflows/genkit-guard',
32
+ },
33
+ },
34
+ attributes: {
35
+ 'event.name': 'guard.models.directory',
36
+ 'code.namespace': 'genkit-guard',
37
+ modelPath,
38
+ },
39
+ }));
24
40
  }
25
- static async getExtractor() {
26
- if (!this.extractor) {
41
+ static async getExtractor(modelName = 'Xenova/all-MiniLM-L6-v2') {
42
+ if (!this.extractors.has(modelName)) {
27
43
  this.init();
28
- this.extractor = await pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2');
44
+ const inst = await pipeline('feature-extraction', modelName);
45
+ this.extractors.set(modelName, inst);
29
46
  }
30
- return this.extractor;
47
+ return this.extractors.get(modelName);
31
48
  }
32
- static async getNER() {
33
- if (!this.classifier) {
49
+ static async getNER(modelName = 'Xenova/bert-base-NER') {
50
+ if (!this.nerClassifiers.has(modelName)) {
34
51
  this.init();
35
- // Token classification model for identifying sensitive entities
36
- this.classifier = await pipeline('token-classification', 'Xenova/bert-base-NER');
52
+ const inst = await pipeline('token-classification', modelName);
53
+ this.nerClassifiers.set(modelName, inst);
37
54
  }
38
- return this.classifier;
55
+ return this.nerClassifiers.get(modelName);
56
+ }
57
+ static async getPIIClassifier(modelName = 'openai/privacy-filter') {
58
+ if (!this.textClassifiers.has(modelName)) {
59
+ this.init();
60
+ const inst = await pipeline('text-classification', modelName);
61
+ this.textClassifiers.set(modelName, inst);
62
+ }
63
+ return this.textClassifiers.get(modelName);
64
+ }
65
+ static async preload(models) {
66
+ const tasks = [];
67
+ if (models?.extractor)
68
+ tasks.push(this.getExtractor(models.extractor));
69
+ if (models?.ner)
70
+ tasks.push(this.getNER(models.ner));
71
+ if (models?.pii)
72
+ tasks.push(this.getPIIClassifier(models.pii));
73
+ await Promise.all(tasks);
39
74
  }
40
75
  }
package/package.json CHANGED
@@ -4,6 +4,10 @@
4
4
  "maintainers": [
5
5
  "Hemant Kohli"
6
6
  ],
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/IntFlows/genkit-guard"
10
+ },
7
11
  "keywords": [
8
12
  "genkit",
9
13
  "genkit-guard",
@@ -18,7 +22,7 @@
18
22
  "huggingface"
19
23
  ],
20
24
  "license": "Apache-2.0",
21
- "version": "0.0.8",
25
+ "version": "0.0.9-alpha.1",
22
26
  "type": "module",
23
27
  "exports": "./dist/index.js",
24
28
  "types": "./dist/index.d.ts",
@@ -33,15 +37,16 @@
33
37
  "build": "tsc",
34
38
  "prepublishOnly": "npm run build"
35
39
  },
36
- "peerDependencies": {
37
- "genkit": ">=0.5.0"
38
- },
39
40
  "dependencies": {
40
- "@huggingface/transformers": "^3.0.0",
41
- "zod": "^3.23.0"
41
+ "@huggingface/transformers": "^4.2.0",
42
+ "zod": "^4.4.3"
43
+ },
44
+ "peerDependencies": {
45
+ "genkit": "1.39.0"
42
46
  },
43
47
  "devDependencies": {
44
- "typescript": "^5.6.0",
45
- "@types/node": "^20.0.0"
48
+ "@types/node": "^25.8.0",
49
+ "genkit": "^1.39.0",
50
+ "typescript": "^6.0.3"
46
51
  }
47
52
  }
@@ -13,9 +13,14 @@ async function download() {
13
13
  await pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2', {
14
14
  device: 'cpu'
15
15
  });
16
- console.log('Downloading BERT-NER to ./models...');
17
- await pipeline('token-classification', 'Xenova/bert-base-NER', {
18
- device: 'cpu'
16
+ //console.log('Downloading BERT-NER to ./models...');
17
+ // await pipeline('token-classification', 'Xenova/bert-base-NER', {
18
+ // device: 'cpu'
19
+ // });
20
+
21
+ console.log('Downloading openai/privacy-filter to ./models...');
22
+ await pipeline('token-classification', 'openai/privacy-filter', {
23
+ device: 'cpu', dtype: "q4"
19
24
  });
20
25
  console.log('Model downloaded successfully.');
21
26
  }