@intflows/genkit-guard 0.0.8-alpha.1 → 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.
@@ -1,120 +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,
27
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,
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 piiResponse = await detectPII(input, {
33
- model: config?.pii?.model,
34
- mode: config?.pii?.mode
35
- });
36
- const piiMatches = piiResponse?.matches || [];
37
- console.log(`[PII Guard] Detected PII: ${piiMatches.length} matches found` + (piiResponse.classifier ? ' (classifier output present)' : ''));
38
- const tokenizer = new PiiTokenizer(); // <-- SINGLE INSTANCE
39
- const piiResult = tokenizer.mask(input, piiMatches);
40
- console.log(`[PII Guard] Masked PII: ${piiResult.piiTypes.length} types found`);
41
- // Attach tokenizer so response can unmask
42
- req.metadata = {
43
- ...req.metadata,
44
- piiTokenizer: tokenizer,
45
- intent: intentResult.intent,
46
- score: intentResult.score,
47
- piiDetected: piiMatches.length > 0,
48
- piiTypes: piiResult.piiTypes,
49
- maskedInput: piiResult.maskedText,
50
- piiModel: config?.pii?.model,
51
- piiMode: config?.pii?.mode,
52
- piiClassifierOutput: piiResponse.classifier
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,
53
236
  };
54
- // Replace input
55
- req.prompt = piiResult.maskedText;
56
- req.messages = [
57
- {
58
- role: 'user',
59
- 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
+ },
60
287
  },
61
- ];
62
- // -------------------------
63
- // 3. LLM CALL
64
- // -------------------------
65
- const res = await next(req);
66
- // -------------------------
67
- // 4. RESPONSE UNMASK
68
- // -------------------------
69
- console.log(`[PII Guard] Unmasking response if needed`);
70
- if (tokenizer) {
71
- /**
72
- * RECURSIVE TRANSFORMER
73
- * This will find every string in the Genkit response (no matter if it's in
74
- * candidates, message, custom, or output) and unmask it.
75
- */
76
- const transform = (obj) => {
77
- // 1. If it's a string, unmask it
78
- if (typeof obj === 'string') {
79
- return tokenizer.unmask(obj);
80
- }
81
- // 2. If it's an array, transform each element
82
- if (Array.isArray(obj)) {
83
- return obj.map(transform);
84
- }
85
- // 3. If it's an object, transform each value
86
- if (obj !== null && typeof obj === 'object') {
87
- // Note: We iterate keys and mutate the object directly
88
- // to ensure Genkit's internal references are updated.
89
- for (const key of Object.keys(obj)) {
90
- obj[key] = transform(obj[key]);
91
- }
92
- return obj;
93
- }
94
- // 4. Return as-is for numbers/booleans/null
95
- return obj;
96
- };
97
- // ----------------------------------------------------------------------
98
- // 5. Transform the entire response object in-place to unmask all strings
99
- // ----------------------------------------------------------------------
100
- transform(res);
101
- 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);
102
303
  }
103
- // ---------------------------------------------------------
104
- // 6. Return the modified response with unmasked content
105
- // ---------------------------------------------------------
106
- return res;
107
304
  };
108
305
  }
109
- // Helper to create a blocked response
110
306
  function block(message, metadata) {
111
307
  return {
112
308
  finishReason: 'blocked',
113
309
  output: {
114
- type: "error",
115
- status: "BLOCKED",
116
- message
310
+ type: 'error',
311
+ status: 'BLOCKED',
312
+ message,
117
313
  },
118
- metadata
314
+ metadata,
119
315
  };
120
316
  }
@@ -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
  }
@@ -21,7 +21,22 @@ export class ModelSingleton {
21
21
  env.localModelPath = modelPath;
22
22
  // Allow remote download if missing
23
23
  env.allowRemoteModels = true;
24
- 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
+ }));
25
40
  }
26
41
  static async getExtractor(modelName = 'Xenova/all-MiniLM-L6-v2') {
27
42
  if (!this.extractors.has(modelName)) {
package/package.json CHANGED
@@ -22,7 +22,7 @@
22
22
  "huggingface"
23
23
  ],
24
24
  "license": "Apache-2.0",
25
- "version": "0.0.8-alpha.1",
25
+ "version": "0.0.9-alpha.1",
26
26
  "type": "module",
27
27
  "exports": "./dist/index.js",
28
28
  "types": "./dist/index.d.ts",
@@ -41,8 +41,12 @@
41
41
  "@huggingface/transformers": "^4.2.0",
42
42
  "zod": "^4.4.3"
43
43
  },
44
+ "peerDependencies": {
45
+ "genkit": "1.39.0"
46
+ },
44
47
  "devDependencies": {
45
48
  "@types/node": "^25.8.0",
49
+ "genkit": "^1.39.0",
46
50
  "typescript": "^6.0.3"
47
51
  }
48
52
  }
@@ -1,18 +0,0 @@
1
- export interface IntentGuardConfig {
2
- allowedIntent: string;
3
- intents: Record<string, string>;
4
- threshold?: number;
5
- fallbackMessage?: string;
6
- }
7
- export interface IntentResult {
8
- allowed: boolean;
9
- score: number;
10
- }
11
- export interface PiiRule {
12
- name: string;
13
- pattern: RegExp;
14
- }
15
- export interface PiiConfig {
16
- rules?: PiiRule[];
17
- maskCharacter?: string;
18
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,6 +0,0 @@
1
- export { guard } from './middleware/middleware.js';
2
- export * from './core/types.js';
3
- /**
4
- * Pre-load the model to avoid cold-start delay on first user request.
5
- */
6
- export declare function initGuard(config?: any): Promise<void>;
package/dist/src/index.js DELETED
@@ -1,22 +0,0 @@
1
- import { ModelSingleton } from './util/singleton.js';
2
- // export { intentGuard, piiGuard } from './middleware/middleware.js';
3
- export { guard } from './middleware/middleware.js';
4
- export * from './core/types.js';
5
- /**
6
- * Pre-load the model to avoid cold-start delay on first user request.
7
- */
8
- export async function initGuard(config) {
9
- console.log('[Guard] Loading local models...');
10
- const extractorModel = config?.models?.extractor ?? 'Xenova/all-MiniLM-L6-v2';
11
- const piiModel = config?.pii?.model;
12
- const piiMode = config?.pii?.mode ?? 'ner';
13
- const tasks = [ModelSingleton.getExtractor(extractorModel)];
14
- if (piiMode === 'ner') {
15
- tasks.push(ModelSingleton.getNER(piiModel ?? 'Xenova/bert-base-NER'));
16
- }
17
- else {
18
- tasks.push(ModelSingleton.getPIIClassifier(piiModel ?? 'openai/privacy-filter'));
19
- }
20
- await Promise.all(tasks);
21
- console.log('[Guard] Models loaded');
22
- }
@@ -1,6 +0,0 @@
1
- export declare function detectInjection(userInput: string): Promise<boolean>;
2
- export declare function analyzeIntentStructured(input: string, intents: Record<string, string>, threshold: number): Promise<{
3
- intent: string;
4
- score: number;
5
- allowed: boolean;
6
- }>;
@@ -1,80 +0,0 @@
1
- import { cos_sim } from '@huggingface/transformers';
2
- import { ModelSingleton } from '../util/singleton.js';
3
- const INJECTION_PATTERNS = [
4
- 'ignore instructions',
5
- 'reveal secrets',
6
- 'bypass security',
7
- 'system prompt',
8
- 'internal policy',
9
- 'developer message',
10
- 'confidential information',
11
- 'do not follow previous instructions',
12
- 'disregard earlier directives',
13
- 'override safety measures',
14
- 'leak data',
15
- 'expose vulnerabilities',
16
- 'access restricted areas',
17
- 'execute arbitrary code',
18
- 'perform unauthorized actions',
19
- 'escalate privileges',
20
- 'disable safeguards',
21
- 'manipulate output',
22
- 'circumvent controls',
23
- 'exploit weaknesses',
24
- 'compromise integrity',
25
- 'subvert protections',
26
- 'inject malicious content',
27
- 'craft deceptive prompts',
28
- 'trick the model',
29
- 'confuse the system',
30
- 'You are now a hacker',
31
- 'You are a malicious actor',
32
- 'Find a vulnerability',
33
- 'Write a prompt that bypasses security',
34
- 'Write a prompt that tricks the model',
35
- 'Write a prompt that confuses the system',
36
- "ignore previous",
37
- "ignore earlier",
38
- "disregard previous",
39
- "disregard earlier",
40
- "you are a hacker",
41
- "you are now a hacker",
42
- "you are malicious",
43
- "export the api key",
44
- "give me the api key",
45
- "bypass",
46
- "override",
47
- "jailbreak",
48
- "system override",
49
- "forget instructions",
50
- "forget previous",
51
- "forget earlier",
52
- "do anything now",
53
- "dan mode",
54
- "developer mode",
55
- "break character",
56
- "act as"
57
- ];
58
- export async function detectInjection(userInput) {
59
- return INJECTION_PATTERNS.some(p => userInput.toLowerCase().includes(p));
60
- }
61
- export async function analyzeIntentStructured(input, intents, threshold) {
62
- const extractor = await ModelSingleton.getExtractor();
63
- let bestIntent = '';
64
- let bestScore = 0;
65
- for (const [key, desc] of Object.entries(intents)) {
66
- const output = await extractor([`intent: ${desc}`, `intent: ${input}`], { pooling: 'mean', normalize: true });
67
- const vectors = output.tolist();
68
- const score = cos_sim(vectors[0], vectors[1]);
69
- const finalScore = typeof score === 'number' ? score : score.data[0];
70
- if (finalScore > bestScore) {
71
- bestScore = finalScore;
72
- bestIntent = key;
73
- }
74
- }
75
- return {
76
- intent: bestIntent,
77
- score: bestScore,
78
- allowed: bestScore >= threshold
79
- };
80
- }
@@ -1 +0,0 @@
1
- export declare function guard(config: any): (req: any, next: any) => Promise<any>;