@devilsdev/rag-guardrails 3.3.0 → 3.5.0
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 +1 -1
- package/dist/index.cjs +938 -67
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Part of [@devilsdev/rag-pipeline-utils](https://www.npmjs.com/package/@devilsdev
|
|
|
8
8
|
npm install @devilsdev/rag-guardrails
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
Exports: `PreRetrievalGuard`, `detectInjection`, `RetrievalGuard`, `PostGenerationGuard`, `GuardrailsPipeline`.
|
|
11
|
+
Exports: `PreRetrievalGuard`, `detectInjection`, `patternInjectionDetector`, `patternPIIDetector`, `detectedText`, `evaluateDetectors`, `detectorVerdict`, `renderDetectorReport`, `RetrievalGuard`, `PostGenerationGuard`, `GuardrailsPipeline`.
|
|
12
12
|
|
|
13
13
|
Documentation: https://devilsdev.github.io/rag-pipeline-utils/
|
|
14
14
|
|
package/dist/index.cjs
CHANGED
|
@@ -249,6 +249,156 @@ var require_injection_detector = __commonJS({
|
|
|
249
249
|
}
|
|
250
250
|
});
|
|
251
251
|
|
|
252
|
+
// src/guardrails/detectors.js
|
|
253
|
+
var require_detectors = __commonJS({
|
|
254
|
+
"src/guardrails/detectors.js"(exports2, module2) {
|
|
255
|
+
"use strict";
|
|
256
|
+
var { detectInjection } = require_injection_detector();
|
|
257
|
+
var KINDS = ["injection", "pii"];
|
|
258
|
+
var DEFAULT_PII_PATTERNS = {
|
|
259
|
+
email: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
|
|
260
|
+
phone: /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/g,
|
|
261
|
+
ssn: /\b\d{3}-\d{2}-\d{4}\b/g,
|
|
262
|
+
creditCard: /\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b/g
|
|
263
|
+
};
|
|
264
|
+
function patternInjectionDetector({ patterns = [] } = {}) {
|
|
265
|
+
return {
|
|
266
|
+
name: "pattern-injection",
|
|
267
|
+
kind: "injection",
|
|
268
|
+
detect(text) {
|
|
269
|
+
const found = detectInjection(String(text ?? ""), { patterns });
|
|
270
|
+
return {
|
|
271
|
+
flagged: found.detected,
|
|
272
|
+
labels: [...new Set(found.matches.map((m) => m.id))],
|
|
273
|
+
matches: found.matches.map((m) => ({ label: m.id, text: m.match }))
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
function patternPIIDetector({ patterns } = {}) {
|
|
279
|
+
const all = { ...DEFAULT_PII_PATTERNS, ...patterns };
|
|
280
|
+
return {
|
|
281
|
+
name: "pattern-pii",
|
|
282
|
+
kind: "pii",
|
|
283
|
+
detect(text) {
|
|
284
|
+
const matches = [];
|
|
285
|
+
for (const [label, pattern] of Object.entries(all)) {
|
|
286
|
+
const re = new RegExp(
|
|
287
|
+
pattern.source,
|
|
288
|
+
pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`
|
|
289
|
+
);
|
|
290
|
+
for (const m of String(text ?? "").matchAll(re)) {
|
|
291
|
+
if (m[0]) {
|
|
292
|
+
matches.push({
|
|
293
|
+
label,
|
|
294
|
+
text: m[0],
|
|
295
|
+
start: m.index,
|
|
296
|
+
end: m.index + m[0].length
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return {
|
|
302
|
+
flagged: matches.length > 0,
|
|
303
|
+
labels: [...new Set(matches.map((m) => m.label))],
|
|
304
|
+
matches
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
function resolveDetectors(guard, kind, options = {}) {
|
|
310
|
+
const { detectors = [], builtIn = true } = options;
|
|
311
|
+
if (!Array.isArray(detectors)) {
|
|
312
|
+
throw new TypeError(`${guard}: detectors must be an array`);
|
|
313
|
+
}
|
|
314
|
+
for (const d of detectors) {
|
|
315
|
+
if (!d || typeof d !== "object" || typeof d.detect !== "function" || typeof d.name !== "string" || !KINDS.includes(d.kind)) {
|
|
316
|
+
throw new TypeError(
|
|
317
|
+
`${guard}: a detector is { name, kind: 'injection' | 'pii', detect(text, context) }`
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
if (d.kind !== kind) {
|
|
321
|
+
throw new TypeError(
|
|
322
|
+
`${guard} runs ${kind} detectors; "${d.name}" is a ${d.kind} detector`
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (builtIn === false && detectors.length === 0) {
|
|
327
|
+
throw new TypeError(
|
|
328
|
+
`${guard}: builtIn: false needs at least one ${kind} detector, or the check is off`
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
const onDetectorError = options.onDetectorError ?? "block";
|
|
332
|
+
if (onDetectorError !== "block" && onDetectorError !== "skip") {
|
|
333
|
+
throw new TypeError(`${guard}: onDetectorError must be 'block' or 'skip'`);
|
|
334
|
+
}
|
|
335
|
+
return detectors;
|
|
336
|
+
}
|
|
337
|
+
function normalise(detector, raw) {
|
|
338
|
+
const r = raw && typeof raw === "object" ? raw : { flagged: Boolean(raw) };
|
|
339
|
+
const matches = Array.isArray(r.matches) ? r.matches.filter((m) => m && typeof m === "object").map((m) => ({ ...m, text: m.text == null ? "" : String(m.text) })) : [];
|
|
340
|
+
return {
|
|
341
|
+
detector: detector.name,
|
|
342
|
+
kind: detector.kind,
|
|
343
|
+
flagged: Boolean(r.flagged),
|
|
344
|
+
...typeof r.score === "number" && { score: r.score },
|
|
345
|
+
...Array.isArray(r.labels) && { labels: r.labels.map(String) },
|
|
346
|
+
matches
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
function failed(detector, error) {
|
|
350
|
+
return {
|
|
351
|
+
detector: detector.name,
|
|
352
|
+
kind: detector.kind,
|
|
353
|
+
flagged: false,
|
|
354
|
+
matches: [],
|
|
355
|
+
error: String(error && error.message || error)
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
function runDetectorsSync(detectors, text, context, { guard, asyncMethod }) {
|
|
359
|
+
return detectors.map((detector) => {
|
|
360
|
+
let raw;
|
|
361
|
+
try {
|
|
362
|
+
raw = detector.detect(text, context);
|
|
363
|
+
} catch (error) {
|
|
364
|
+
return failed(detector, error);
|
|
365
|
+
}
|
|
366
|
+
if (raw && typeof raw.then === "function") {
|
|
367
|
+
Promise.resolve(raw).catch(() => {
|
|
368
|
+
});
|
|
369
|
+
throw new TypeError(
|
|
370
|
+
`${guard}: detector "${detector.name}" is async; use ${asyncMethod}()`
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
return normalise(detector, raw);
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
function runDetectorsAsync(detectors, text, context) {
|
|
377
|
+
return Promise.all(
|
|
378
|
+
detectors.map(async (detector) => {
|
|
379
|
+
try {
|
|
380
|
+
return normalise(detector, await detector.detect(text, context));
|
|
381
|
+
} catch (error) {
|
|
382
|
+
return failed(detector, error);
|
|
383
|
+
}
|
|
384
|
+
})
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
function detectedText(result) {
|
|
388
|
+
return result && Array.isArray(result.matchedText) && result.matchedText || [];
|
|
389
|
+
}
|
|
390
|
+
module2.exports = {
|
|
391
|
+
DEFAULT_PII_PATTERNS,
|
|
392
|
+
patternInjectionDetector,
|
|
393
|
+
patternPIIDetector,
|
|
394
|
+
resolveDetectors,
|
|
395
|
+
runDetectorsSync,
|
|
396
|
+
runDetectorsAsync,
|
|
397
|
+
detectedText
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
|
|
252
402
|
// src/guardrails/pre-retrieval-guard.js
|
|
253
403
|
var require_pre_retrieval_guard = __commonJS({
|
|
254
404
|
"src/guardrails/pre-retrieval-guard.js"(exports2, module2) {
|
|
@@ -257,6 +407,11 @@ var require_pre_retrieval_guard = __commonJS({
|
|
|
257
407
|
var { validateInput } = require_validator();
|
|
258
408
|
var { tokenize } = require_scoring();
|
|
259
409
|
var { detectInjection, DEFAULT_RULES } = require_injection_detector();
|
|
410
|
+
var {
|
|
411
|
+
resolveDetectors,
|
|
412
|
+
runDetectorsSync,
|
|
413
|
+
runDetectorsAsync
|
|
414
|
+
} = require_detectors();
|
|
260
415
|
var DEFAULT_CONFIG = {
|
|
261
416
|
maxQueryLength: 1e4,
|
|
262
417
|
enableInjectionDetection: true,
|
|
@@ -277,9 +432,17 @@ var require_pre_retrieval_guard = __commonJS({
|
|
|
277
432
|
* @param {string[]} [options.blockedTopics=[]] - Topics to reject
|
|
278
433
|
* @param {string[]} [options.allowedTopics=[]] - If non-empty, only these topics are permitted
|
|
279
434
|
* @param {RegExp[]} [options.injectionPatterns] - Additional injection patterns (merged with defaults)
|
|
435
|
+
* @param {Array<object>} [options.detectors=[]] - Injection detectors to run on the query as well (see ./detectors); async ones need checkAsync()
|
|
436
|
+
* @param {boolean} [options.builtIn=true] - false: use only `detectors`, not the built-in patterns
|
|
437
|
+
* @param {'block'|'skip'} [options.onDetectorError='block'] - A detector that throws makes the query unsafe ('block') or is ignored ('skip')
|
|
280
438
|
*/
|
|
281
439
|
constructor(options = {}) {
|
|
282
440
|
super();
|
|
441
|
+
this._detectors = resolveDetectors(
|
|
442
|
+
"PreRetrievalGuard",
|
|
443
|
+
"injection",
|
|
444
|
+
options
|
|
445
|
+
);
|
|
283
446
|
this.config = { ...DEFAULT_CONFIG, ...options };
|
|
284
447
|
this._customPatterns = options.injectionPatterns ? [...options.injectionPatterns] : [];
|
|
285
448
|
if (options.injectionPatterns) {
|
|
@@ -306,17 +469,54 @@ var require_pre_retrieval_guard = __commonJS({
|
|
|
306
469
|
* @fires PreRetrievalGuard#checked
|
|
307
470
|
*/
|
|
308
471
|
check(query) {
|
|
472
|
+
const state = this._prepare(query);
|
|
473
|
+
if (state.result) return this._emit(state.result);
|
|
474
|
+
const detections = this._runDetectors(state) ? runDetectorsSync(this._detectors, state.sanitizedQuery, CONTEXT, {
|
|
475
|
+
guard: "PreRetrievalGuard",
|
|
476
|
+
asyncMethod: "checkAsync"
|
|
477
|
+
}) : [];
|
|
478
|
+
return this._finish(state, detections);
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* check() for async detectors, such as a classifier model or a service.
|
|
482
|
+
* Detectors run concurrently.
|
|
483
|
+
*
|
|
484
|
+
* @param {string} query
|
|
485
|
+
* @param {object} [options]
|
|
486
|
+
* @param {AbortSignal} [options.signal] - Passed to each detector in its context
|
|
487
|
+
* @returns {Promise<ReturnType<PreRetrievalGuard['check']>>}
|
|
488
|
+
*/
|
|
489
|
+
async checkAsync(query, { signal } = {}) {
|
|
490
|
+
const state = this._prepare(query);
|
|
491
|
+
if (state.result) return this._emit(state.result);
|
|
492
|
+
const detections = this._runDetectors(state) ? await runDetectorsAsync(this._detectors, state.sanitizedQuery, {
|
|
493
|
+
...CONTEXT,
|
|
494
|
+
...signal && { signal }
|
|
495
|
+
}) : [];
|
|
496
|
+
return this._finish(state, detections);
|
|
497
|
+
}
|
|
498
|
+
/** @private */
|
|
499
|
+
_runDetectors() {
|
|
500
|
+
return this.config.enableInjectionDetection && this._detectors.length > 0;
|
|
501
|
+
}
|
|
502
|
+
/** @private */
|
|
503
|
+
_emit(result) {
|
|
504
|
+
this.emit("checked", result);
|
|
505
|
+
return result;
|
|
506
|
+
}
|
|
507
|
+
/** @private Validation and the built-in checks, before any detector runs. */
|
|
508
|
+
_prepare(query) {
|
|
309
509
|
const issues = [];
|
|
310
510
|
if (query == null || typeof query !== "string" || query.trim().length === 0) {
|
|
311
511
|
issues.push("Query must be a non-empty string");
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
512
|
+
return {
|
|
513
|
+
result: {
|
|
514
|
+
safe: false,
|
|
515
|
+
issues,
|
|
516
|
+
sanitizedQuery: "",
|
|
517
|
+
injection: { detected: false, matches: [] }
|
|
518
|
+
}
|
|
317
519
|
};
|
|
318
|
-
this.emit("checked", result2);
|
|
319
|
-
return result2;
|
|
320
520
|
}
|
|
321
521
|
const validation = validateInput(query, {
|
|
322
522
|
maxLength: this.config.maxQueryLength
|
|
@@ -328,7 +528,7 @@ var require_pre_retrieval_guard = __commonJS({
|
|
|
328
528
|
}
|
|
329
529
|
const sanitizedQuery = validation.sanitized;
|
|
330
530
|
let injection = { detected: false, matches: [] };
|
|
331
|
-
if (this.config.enableInjectionDetection) {
|
|
531
|
+
if (this.config.enableInjectionDetection && this.config.builtIn !== false) {
|
|
332
532
|
const found = detectInjection(sanitizedQuery, {
|
|
333
533
|
patterns: this._customPatterns
|
|
334
534
|
});
|
|
@@ -361,26 +561,496 @@ var require_pre_retrieval_guard = __commonJS({
|
|
|
361
561
|
}
|
|
362
562
|
}
|
|
363
563
|
}
|
|
564
|
+
return { issues, sanitizedQuery, injection };
|
|
565
|
+
}
|
|
566
|
+
/** @private Merge the detectors' findings and emit the result. */
|
|
567
|
+
_finish({ issues, sanitizedQuery, injection }, detections) {
|
|
568
|
+
const skip = this.config.onDetectorError === "skip";
|
|
569
|
+
for (const d of detections) {
|
|
570
|
+
if (d.error) {
|
|
571
|
+
if (!skip) {
|
|
572
|
+
issues.push(`Injection detector "${d.detector}" failed: ${d.error}`);
|
|
573
|
+
}
|
|
574
|
+
} else if (d.flagged) {
|
|
575
|
+
injection = { ...injection, detected: true };
|
|
576
|
+
const labels = d.labels && d.labels.length ? `: ${d.labels.join(", ")}` : "";
|
|
577
|
+
issues.push(
|
|
578
|
+
`Potential prompt injection detected by ${d.detector}${labels}`
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
364
582
|
const result = {
|
|
365
583
|
safe: issues.length === 0,
|
|
366
584
|
issues,
|
|
367
585
|
sanitizedQuery,
|
|
368
586
|
injection
|
|
369
587
|
};
|
|
370
|
-
this.
|
|
371
|
-
return result;
|
|
588
|
+
if (this._detectors.length > 0) result.detections = detections;
|
|
589
|
+
return this._emit(result);
|
|
372
590
|
}
|
|
373
591
|
};
|
|
592
|
+
var CONTEXT = Object.freeze({ stage: "query" });
|
|
374
593
|
module2.exports = { PreRetrievalGuard, DEFAULT_CONFIG };
|
|
375
594
|
}
|
|
376
595
|
});
|
|
377
596
|
|
|
597
|
+
// src/guardrails/evaluate-detectors.js
|
|
598
|
+
var require_evaluate_detectors = __commonJS({
|
|
599
|
+
"src/guardrails/evaluate-detectors.js"(exports2, module2) {
|
|
600
|
+
"use strict";
|
|
601
|
+
var REPORT_STYLE = `
|
|
602
|
+
:root{--bg:#fff;--fg:#1b1f24;--muted:#57606a;--line:#d0d7de;--ok:#1a7f37;--bad:#cf222e;--chip:#f6f8fa}
|
|
603
|
+
@media (prefers-color-scheme:dark){:root{--bg:#0d1117;--fg:#e6edf3;--muted:#8d96a0;--line:#30363d;--ok:#3fb950;--bad:#f85149;--chip:#161b22}}
|
|
604
|
+
*{box-sizing:border-box}body{margin:0;padding:24px 16px;background:var(--bg);color:var(--fg);font:15px/1.5 system-ui,-apple-system,"Segoe UI",sans-serif}
|
|
605
|
+
main{max-width:1100px;margin:0 auto}h1{font-size:22px;margin:0 0 4px}h2{font-size:17px;margin:28px 0 8px}
|
|
606
|
+
.muted{color:var(--muted)}.status{display:inline-block;padding:2px 10px;border-radius:999px;font-weight:600;font-size:13px}
|
|
607
|
+
.pass{color:var(--ok);border:1px solid var(--ok)}.fail{color:var(--bad);border:1px solid var(--bad)}
|
|
608
|
+
ul.reasons{margin:8px 0;padding-left:20px}.scroll{overflow-x:auto}
|
|
609
|
+
table{border-collapse:collapse;width:100%;font-size:14px}th,td{border-bottom:1px solid var(--line);padding:6px 8px;text-align:left;vertical-align:top}
|
|
610
|
+
th{color:var(--muted);font-weight:600}td.num,th.num{text-align:right;font-variant-numeric:tabular-nums}
|
|
611
|
+
code{background:var(--chip);padding:1px 5px;border-radius:4px;font-size:13px}`;
|
|
612
|
+
var esc = (text) => String(text).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
613
|
+
var KINDS = ["injection", "pii"];
|
|
614
|
+
var MAX_IDS = 50;
|
|
615
|
+
var Z = 1.96;
|
|
616
|
+
function validateDetectorCases(cases) {
|
|
617
|
+
if (!Array.isArray(cases) || cases.length === 0) {
|
|
618
|
+
throw new TypeError("cases must be a non-empty array");
|
|
619
|
+
}
|
|
620
|
+
const ids = /* @__PURE__ */ new Set();
|
|
621
|
+
return cases.map((c, i) => {
|
|
622
|
+
const where = `case ${i + 1}${c && c.id != null ? ` ("${c.id}")` : ""}`;
|
|
623
|
+
if (!c || typeof c !== "object") {
|
|
624
|
+
throw new TypeError(`${where} is not an object`);
|
|
625
|
+
}
|
|
626
|
+
if (!KINDS.includes(c.kind)) {
|
|
627
|
+
throw new TypeError(`${where}: kind must be "injection" or "pii"`);
|
|
628
|
+
}
|
|
629
|
+
if (typeof c.text !== "string") {
|
|
630
|
+
throw new TypeError(`${where}: text must be a string`);
|
|
631
|
+
}
|
|
632
|
+
if (c.spans !== void 0) {
|
|
633
|
+
if (c.kind !== "pii") {
|
|
634
|
+
throw new TypeError(`${where}: only pii cases take spans`);
|
|
635
|
+
}
|
|
636
|
+
if (!Array.isArray(c.spans) || !c.spans.every(
|
|
637
|
+
(s) => s && Number.isInteger(s.start) && Number.isInteger(s.end) && s.start >= 0 && s.end > s.start && s.end <= c.text.length
|
|
638
|
+
)) {
|
|
639
|
+
throw new TypeError(
|
|
640
|
+
`${where}: spans must be [{ label, start, end }] inside the text`
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
} else if (typeof c.flagged !== "boolean") {
|
|
644
|
+
throw new TypeError(
|
|
645
|
+
`${where}: needs flagged: true | false${c.kind === "pii" ? ", or spans" : ""}`
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
if (c.tags !== void 0 && !Array.isArray(c.tags)) {
|
|
649
|
+
throw new TypeError(`${where}: tags must be an array of strings`);
|
|
650
|
+
}
|
|
651
|
+
const id = c.id != null ? String(c.id) : String(i + 1);
|
|
652
|
+
if (ids.has(id)) throw new TypeError(`${where}: duplicate id "${id}"`);
|
|
653
|
+
ids.add(id);
|
|
654
|
+
return { ...c, id, tags: (c.tags || []).map(String) };
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
function wilson(k, n) {
|
|
658
|
+
if (n === 0) return null;
|
|
659
|
+
const p = k / n;
|
|
660
|
+
const denom = 1 + Z * Z / n;
|
|
661
|
+
const centre = (p + Z * Z / (2 * n)) / denom;
|
|
662
|
+
const half = Z * Math.sqrt(p * (1 - p) / n + Z * Z / (4 * n * n)) / denom;
|
|
663
|
+
return [round(Math.max(0, centre - half)), round(Math.min(1, centre + half))];
|
|
664
|
+
}
|
|
665
|
+
var round = (x) => Number(x.toFixed(4));
|
|
666
|
+
var ratio = (k, n) => n === 0 ? null : round(k / n);
|
|
667
|
+
function confusionMetrics({ tp, fp, fn, tn }) {
|
|
668
|
+
const precision = ratio(tp, tp + fp);
|
|
669
|
+
const recall = ratio(tp, tp + fn);
|
|
670
|
+
const f1 = ratio(2 * tp, 2 * tp + fp + fn);
|
|
671
|
+
return {
|
|
672
|
+
cases: tp + fp + fn + tn,
|
|
673
|
+
tp,
|
|
674
|
+
fp,
|
|
675
|
+
fn,
|
|
676
|
+
tn,
|
|
677
|
+
precision,
|
|
678
|
+
recall,
|
|
679
|
+
f1,
|
|
680
|
+
accuracy: ratio(tp + tn, tp + fp + fn + tn),
|
|
681
|
+
precisionInterval: wilson(tp, tp + fp),
|
|
682
|
+
recallInterval: wilson(tp, tp + fn)
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
var overlaps = (a, b) => a.start < b.end && b.start < a.end;
|
|
686
|
+
function locate(matches, text) {
|
|
687
|
+
const used = /* @__PURE__ */ new Set();
|
|
688
|
+
const out = [];
|
|
689
|
+
for (const m of matches) {
|
|
690
|
+
if (Number.isInteger(m.start) && Number.isInteger(m.end)) {
|
|
691
|
+
out.push(m);
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
if (!m.text) continue;
|
|
695
|
+
let from = 0;
|
|
696
|
+
let at = text.indexOf(m.text, from);
|
|
697
|
+
while (at !== -1 && used.has(at)) {
|
|
698
|
+
from = at + 1;
|
|
699
|
+
at = text.indexOf(m.text, from);
|
|
700
|
+
}
|
|
701
|
+
if (at === -1) continue;
|
|
702
|
+
used.add(at);
|
|
703
|
+
out.push({ ...m, start: at, end: at + m.text.length });
|
|
704
|
+
}
|
|
705
|
+
return out;
|
|
706
|
+
}
|
|
707
|
+
async function mapLimit(items, limit, fn) {
|
|
708
|
+
const results = new Array(items.length);
|
|
709
|
+
let next = 0;
|
|
710
|
+
const workers = Array.from(
|
|
711
|
+
{ length: Math.min(limit, items.length) },
|
|
712
|
+
async () => {
|
|
713
|
+
while (next < items.length) {
|
|
714
|
+
const i = next++;
|
|
715
|
+
results[i] = await fn(items[i], i);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
);
|
|
719
|
+
await Promise.all(workers);
|
|
720
|
+
return results;
|
|
721
|
+
}
|
|
722
|
+
async function runOne(detector, c, signal) {
|
|
723
|
+
if (signal && signal.aborted) throw signal.reason || new Error("Aborted");
|
|
724
|
+
const stage = c.stage || (c.kind === "pii" ? "output" : "query");
|
|
725
|
+
try {
|
|
726
|
+
const raw = await detector.detect(c.text, { stage, signal });
|
|
727
|
+
const r = raw && typeof raw === "object" ? raw : { flagged: Boolean(raw) };
|
|
728
|
+
return {
|
|
729
|
+
flagged: Boolean(r.flagged),
|
|
730
|
+
matches: Array.isArray(r.matches) ? r.matches.filter((m) => m && typeof m === "object") : []
|
|
731
|
+
};
|
|
732
|
+
} catch (error) {
|
|
733
|
+
if (signal && signal.aborted) throw error;
|
|
734
|
+
return { error: String(error && error.message || error) };
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
function scoreDetector(detector, cases, outcomes, covered) {
|
|
738
|
+
const counts = { tp: 0, fp: 0, fn: 0, tn: 0 };
|
|
739
|
+
const byTag = {};
|
|
740
|
+
const missed = [];
|
|
741
|
+
const falseAlarms = [];
|
|
742
|
+
const failed = [];
|
|
743
|
+
const spanTotals = { predicted: 0, matched: 0 };
|
|
744
|
+
const byLabel = {};
|
|
745
|
+
const byDetectorLabel = {};
|
|
746
|
+
let spanCases = 0;
|
|
747
|
+
cases.forEach((c, i) => {
|
|
748
|
+
const o = outcomes[i];
|
|
749
|
+
if (o.error) {
|
|
750
|
+
failed.push({ id: c.id, error: o.error });
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
const expected = c.spans ? c.spans.length > 0 : c.flagged;
|
|
754
|
+
const key = expected ? o.flagged ? "tp" : "fn" : o.flagged ? "fp" : "tn";
|
|
755
|
+
counts[key]++;
|
|
756
|
+
for (const tag of c.tags) {
|
|
757
|
+
byTag[tag] = byTag[tag] || { tp: 0, fp: 0, fn: 0, tn: 0 };
|
|
758
|
+
byTag[tag][key]++;
|
|
759
|
+
}
|
|
760
|
+
if (key === "fn") missed.push(c.id);
|
|
761
|
+
if (key === "fp") falseAlarms.push(c.id);
|
|
762
|
+
if (c.spans) {
|
|
763
|
+
spanCases++;
|
|
764
|
+
const found = locate(o.matches, c.text);
|
|
765
|
+
for (const m of found) {
|
|
766
|
+
const hit = c.spans.some((s) => overlaps(s, m));
|
|
767
|
+
const label = m.label == null ? "(none)" : String(m.label);
|
|
768
|
+
byDetectorLabel[label] = byDetectorLabel[label] || {
|
|
769
|
+
predicted: 0,
|
|
770
|
+
matched: 0
|
|
771
|
+
};
|
|
772
|
+
byDetectorLabel[label].predicted++;
|
|
773
|
+
spanTotals.predicted++;
|
|
774
|
+
if (hit) {
|
|
775
|
+
byDetectorLabel[label].matched++;
|
|
776
|
+
spanTotals.matched++;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
for (const s of c.spans) {
|
|
780
|
+
const label = s.label == null ? "(none)" : String(s.label);
|
|
781
|
+
byLabel[label] = byLabel[label] || { gold: 0, found: 0 };
|
|
782
|
+
byLabel[label].gold++;
|
|
783
|
+
if (found.some((m) => overlaps(s, m))) byLabel[label].found++;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
});
|
|
787
|
+
const result = {
|
|
788
|
+
kind: detector.kind,
|
|
789
|
+
...confusionMetrics(counts),
|
|
790
|
+
errors: failed.length,
|
|
791
|
+
byTag: Object.fromEntries(
|
|
792
|
+
Object.entries(byTag).sort(([a], [b]) => a.localeCompare(b)).map(([tag, t]) => [tag, confusionMetrics(t)])
|
|
793
|
+
),
|
|
794
|
+
missed: missed.slice(0, MAX_IDS),
|
|
795
|
+
falseAlarms: falseAlarms.slice(0, MAX_IDS),
|
|
796
|
+
failed: failed.slice(0, MAX_IDS)
|
|
797
|
+
};
|
|
798
|
+
if (spanCases > 0) {
|
|
799
|
+
const inScope = Object.entries(byLabel).filter(
|
|
800
|
+
([label]) => !covered || covered.includes(label)
|
|
801
|
+
);
|
|
802
|
+
const gold = inScope.reduce((n, [, l]) => n + l.gold, 0);
|
|
803
|
+
const foundSpans = inScope.reduce((n, [, l]) => n + l.found, 0);
|
|
804
|
+
result.spans = {
|
|
805
|
+
cases: spanCases,
|
|
806
|
+
predicted: spanTotals.predicted,
|
|
807
|
+
matched: spanTotals.matched,
|
|
808
|
+
precision: ratio(spanTotals.matched, spanTotals.predicted),
|
|
809
|
+
precisionInterval: wilson(spanTotals.matched, spanTotals.predicted),
|
|
810
|
+
...covered && { coveredLabels: covered },
|
|
811
|
+
gold,
|
|
812
|
+
found: foundSpans,
|
|
813
|
+
recall: ratio(foundSpans, gold),
|
|
814
|
+
recallInterval: wilson(foundSpans, gold),
|
|
815
|
+
byLabel: Object.fromEntries(
|
|
816
|
+
Object.entries(byLabel).sort(([, a], [, b]) => b.gold - a.gold).map(([label, l]) => [
|
|
817
|
+
label,
|
|
818
|
+
{
|
|
819
|
+
gold: l.gold,
|
|
820
|
+
found: l.found,
|
|
821
|
+
recall: ratio(l.found, l.gold),
|
|
822
|
+
covered: covered ? covered.includes(label) : true
|
|
823
|
+
}
|
|
824
|
+
])
|
|
825
|
+
),
|
|
826
|
+
byDetectorLabel: Object.fromEntries(
|
|
827
|
+
Object.entries(byDetectorLabel).sort(([, a], [, b]) => b.predicted - a.predicted).map(([label, l]) => [
|
|
828
|
+
label,
|
|
829
|
+
{ ...l, precision: ratio(l.matched, l.predicted) }
|
|
830
|
+
])
|
|
831
|
+
)
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
return result;
|
|
835
|
+
}
|
|
836
|
+
async function evaluateDetectors(cases, detectors, options = {}) {
|
|
837
|
+
const { concurrency = 4, coveredLabels, signal } = options;
|
|
838
|
+
const checked = validateDetectorCases(cases);
|
|
839
|
+
if (!Array.isArray(detectors) || detectors.length === 0) {
|
|
840
|
+
throw new TypeError("detectors must be a non-empty array");
|
|
841
|
+
}
|
|
842
|
+
const names = /* @__PURE__ */ new Set();
|
|
843
|
+
for (const d of detectors) {
|
|
844
|
+
if (!d || typeof d.detect !== "function" || typeof d.name !== "string" || !KINDS.includes(d.kind)) {
|
|
845
|
+
throw new TypeError(
|
|
846
|
+
"a detector is { name, kind: 'injection' | 'pii', detect(text, context) }"
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
if (names.has(d.name)) {
|
|
850
|
+
throw new TypeError(`two detectors are named "${d.name}"`);
|
|
851
|
+
}
|
|
852
|
+
names.add(d.name);
|
|
853
|
+
}
|
|
854
|
+
if (!Number.isInteger(concurrency) || concurrency < 1) {
|
|
855
|
+
throw new TypeError("concurrency must be a positive integer");
|
|
856
|
+
}
|
|
857
|
+
const report = {
|
|
858
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
859
|
+
cases: {
|
|
860
|
+
injection: checked.filter((c) => c.kind === "injection").length,
|
|
861
|
+
pii: checked.filter((c) => c.kind === "pii").length
|
|
862
|
+
},
|
|
863
|
+
detectors: {}
|
|
864
|
+
};
|
|
865
|
+
for (const detector of detectors) {
|
|
866
|
+
const mine = checked.filter((c) => c.kind === detector.kind);
|
|
867
|
+
if (mine.length === 0) continue;
|
|
868
|
+
const started = Date.now();
|
|
869
|
+
const outcomes = await mapLimit(
|
|
870
|
+
mine,
|
|
871
|
+
concurrency,
|
|
872
|
+
(c) => runOne(detector, c, signal)
|
|
873
|
+
);
|
|
874
|
+
const covered = Array.isArray(coveredLabels) ? coveredLabels : coveredLabels && coveredLabels[detector.name];
|
|
875
|
+
report.detectors[detector.name] = {
|
|
876
|
+
...scoreDetector(detector, mine, outcomes, covered),
|
|
877
|
+
msPerCase: round((Date.now() - started) / mine.length)
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
return report;
|
|
881
|
+
}
|
|
882
|
+
function detectorVerdict(report, min = {}) {
|
|
883
|
+
const reasons = [];
|
|
884
|
+
for (const [d, m] of Object.entries(report.detectors)) {
|
|
885
|
+
if (m.errors > 0) {
|
|
886
|
+
reasons.push(`${d}: ${m.errors} case(s) failed with an error`);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
for (const [key, minimum] of Object.entries(min)) {
|
|
890
|
+
const dot = key.indexOf(".");
|
|
891
|
+
const name = key.slice(0, dot);
|
|
892
|
+
const path = key.slice(dot + 1).split(".");
|
|
893
|
+
const entry = report.detectors[name];
|
|
894
|
+
const actual = entry ? path.reduce((v, k) => v == null ? v : v[k], entry) : void 0;
|
|
895
|
+
if (typeof actual !== "number") {
|
|
896
|
+
reasons.push(`${key} was not measured (minimum ${minimum})`);
|
|
897
|
+
} else if (actual < minimum) {
|
|
898
|
+
reasons.push(
|
|
899
|
+
`${key} ${actual.toFixed(3)} is below the minimum ${minimum}`
|
|
900
|
+
);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
return { passed: reasons.length === 0, reasons };
|
|
904
|
+
}
|
|
905
|
+
var fixed = (v) => typeof v === "number" ? v.toFixed(3) : "-";
|
|
906
|
+
var interval = (i) => i ? `${i[0].toFixed(2)}\u2013${i[1].toFixed(2)}` : "-";
|
|
907
|
+
var mdCell = (text) => String(text).replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
|
|
908
|
+
function tables(report) {
|
|
909
|
+
const out = [];
|
|
910
|
+
const main = {
|
|
911
|
+
title: "Detectors",
|
|
912
|
+
header: [
|
|
913
|
+
"Detector",
|
|
914
|
+
"Kind",
|
|
915
|
+
"Cases",
|
|
916
|
+
"TP",
|
|
917
|
+
"FP",
|
|
918
|
+
"FN",
|
|
919
|
+
"TN",
|
|
920
|
+
"Precision",
|
|
921
|
+
"Recall",
|
|
922
|
+
"F1",
|
|
923
|
+
"Recall 95% CI",
|
|
924
|
+
"Errors"
|
|
925
|
+
],
|
|
926
|
+
rows: Object.entries(report.detectors).map(([name, m]) => [
|
|
927
|
+
name,
|
|
928
|
+
m.kind,
|
|
929
|
+
m.cases,
|
|
930
|
+
m.tp,
|
|
931
|
+
m.fp,
|
|
932
|
+
m.fn,
|
|
933
|
+
m.tn,
|
|
934
|
+
fixed(m.precision),
|
|
935
|
+
fixed(m.recall),
|
|
936
|
+
fixed(m.f1),
|
|
937
|
+
interval(m.recallInterval),
|
|
938
|
+
m.errors
|
|
939
|
+
])
|
|
940
|
+
};
|
|
941
|
+
out.push(main);
|
|
942
|
+
for (const [name, m] of Object.entries(report.detectors)) {
|
|
943
|
+
const tags = Object.entries(m.byTag);
|
|
944
|
+
if (tags.length > 0) {
|
|
945
|
+
out.push({
|
|
946
|
+
title: `${name} by tag`,
|
|
947
|
+
header: ["Tag", "Cases", "TP", "FP", "FN", "TN", "Precision", "Recall"],
|
|
948
|
+
rows: tags.map(([tag, t]) => [
|
|
949
|
+
tag,
|
|
950
|
+
t.cases,
|
|
951
|
+
t.tp,
|
|
952
|
+
t.fp,
|
|
953
|
+
t.fn,
|
|
954
|
+
t.tn,
|
|
955
|
+
fixed(t.precision),
|
|
956
|
+
fixed(t.recall)
|
|
957
|
+
])
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
if (m.spans) {
|
|
961
|
+
out.push({
|
|
962
|
+
title: `${name} spans: ${m.spans.predicted} matches, precision ${fixed(m.spans.precision)}; recall ${fixed(m.spans.recall)} on ${m.spans.gold} ${m.spans.coveredLabels ? "covered " : ""}spans`,
|
|
963
|
+
header: ["Labelled as", "Spans", "Found", "Recall", "Covered"],
|
|
964
|
+
rows: Object.entries(m.spans.byLabel).map(([label, l]) => [
|
|
965
|
+
label,
|
|
966
|
+
l.gold,
|
|
967
|
+
l.found,
|
|
968
|
+
fixed(l.recall),
|
|
969
|
+
l.covered ? "yes" : "no"
|
|
970
|
+
])
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
return out;
|
|
975
|
+
}
|
|
976
|
+
function renderDetectorReport(report, options = {}) {
|
|
977
|
+
const { format = "markdown", title = "Guardrail detectors", min } = options;
|
|
978
|
+
const verdict = detectorVerdict(report, min);
|
|
979
|
+
const summary = `${report.cases.injection} injection case(s), ${report.cases.pii} PII case(s).`;
|
|
980
|
+
const sections = tables(report);
|
|
981
|
+
if (format === "markdown") {
|
|
982
|
+
const out = [
|
|
983
|
+
`### ${title}: ${verdict.passed ? "\u2705 Passed" : "\u274C Failed"}`,
|
|
984
|
+
"",
|
|
985
|
+
summary,
|
|
986
|
+
""
|
|
987
|
+
];
|
|
988
|
+
for (const r of verdict.reasons) out.push(`- ${r}`);
|
|
989
|
+
if (verdict.reasons.length) out.push("");
|
|
990
|
+
for (const s of sections) {
|
|
991
|
+
out.push(`**${mdCell(s.title)}**`, "");
|
|
992
|
+
out.push(`| ${s.header.join(" | ")} |`);
|
|
993
|
+
out.push(
|
|
994
|
+
`| ${s.header.map((_, i) => i === 0 ? "---" : "---:").join(" | ")} |`
|
|
995
|
+
);
|
|
996
|
+
for (const row of s.rows) out.push(`| ${row.map(mdCell).join(" | ")} |`);
|
|
997
|
+
out.push("");
|
|
998
|
+
}
|
|
999
|
+
return out.join("\n");
|
|
1000
|
+
}
|
|
1001
|
+
if (format === "html") {
|
|
1002
|
+
const parts = [
|
|
1003
|
+
'<!doctype html><html lang="en"><head><meta charset="utf-8">',
|
|
1004
|
+
'<meta name="viewport" content="width=device-width,initial-scale=1">',
|
|
1005
|
+
`<title>${esc(title)}</title><style>${REPORT_STYLE}</style></head><body><main>`,
|
|
1006
|
+
`<h1>${esc(title)}</h1>`,
|
|
1007
|
+
`<p><span class="status ${verdict.passed ? "pass" : "fail"}">${verdict.passed ? "Passed" : "Failed"}</span> <span class="muted">${esc(summary)} \xB7 ${esc(report.createdAt || "")}</span></p>`
|
|
1008
|
+
];
|
|
1009
|
+
if (verdict.reasons.length) {
|
|
1010
|
+
parts.push(
|
|
1011
|
+
`<ul class="reasons">${verdict.reasons.map((r) => `<li>${esc(r)}</li>`).join("")}</ul>`
|
|
1012
|
+
);
|
|
1013
|
+
}
|
|
1014
|
+
for (const s of sections) {
|
|
1015
|
+
parts.push(
|
|
1016
|
+
`<h2>${esc(s.title)}</h2><div class="scroll"><table><thead><tr>`
|
|
1017
|
+
);
|
|
1018
|
+
parts.push(
|
|
1019
|
+
s.header.map((h, i) => `<th${i === 0 ? "" : ' class="num"'}>${esc(h)}</th>`).join("")
|
|
1020
|
+
);
|
|
1021
|
+
parts.push("</tr></thead><tbody>");
|
|
1022
|
+
for (const row of s.rows) {
|
|
1023
|
+
parts.push(
|
|
1024
|
+
`<tr>${row.map((v, i) => `<td${i === 0 ? "" : ' class="num"'}>${i === 0 ? `<code>${esc(v)}</code>` : esc(v)}</td>`).join("")}</tr>`
|
|
1025
|
+
);
|
|
1026
|
+
}
|
|
1027
|
+
parts.push("</tbody></table></div>");
|
|
1028
|
+
}
|
|
1029
|
+
parts.push("</main></body></html>");
|
|
1030
|
+
return parts.join("");
|
|
1031
|
+
}
|
|
1032
|
+
throw new TypeError(`format must be "markdown" or "html", not "${format}"`);
|
|
1033
|
+
}
|
|
1034
|
+
module2.exports = {
|
|
1035
|
+
evaluateDetectors,
|
|
1036
|
+
validateDetectorCases,
|
|
1037
|
+
detectorVerdict,
|
|
1038
|
+
renderDetectorReport
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
1042
|
+
|
|
378
1043
|
// src/guardrails/retrieval-guard.js
|
|
379
1044
|
var require_retrieval_guard = __commonJS({
|
|
380
1045
|
"src/guardrails/retrieval-guard.js"(exports2, module2) {
|
|
381
1046
|
"use strict";
|
|
382
1047
|
var { EventEmitter } = require("events");
|
|
383
1048
|
var { detectInjection } = require_injection_detector();
|
|
1049
|
+
var {
|
|
1050
|
+
resolveDetectors,
|
|
1051
|
+
runDetectorsSync,
|
|
1052
|
+
runDetectorsAsync
|
|
1053
|
+
} = require_detectors();
|
|
384
1054
|
var DEFAULT_CONFIG = {
|
|
385
1055
|
minRelevanceScore: 0,
|
|
386
1056
|
maxAgeDays: 0,
|
|
@@ -402,10 +1072,19 @@ var require_retrieval_guard = __commonJS({
|
|
|
402
1072
|
* @param {boolean} [options.detectInjection=false] - Scan document text for prompt injection
|
|
403
1073
|
* @param {'remove'|'flag'} [options.injectionAction='remove'] - Remove flagged documents or keep and mark them
|
|
404
1074
|
* @param {RegExp[]} [options.injectionPatterns=[]] - Extra injection patterns for the scan
|
|
1075
|
+
* @param {Array<object>} [options.detectors=[]] - Injection detectors to run on each document's text too, when detectInjection is on (see ./detectors); async ones need filterAsync(), which calls them once per document, concurrently
|
|
1076
|
+
* @param {boolean} [options.builtIn=true] - false: use only `detectors` for the injection scan
|
|
1077
|
+
* @param {'block'|'skip'} [options.onDetectorError='block'] - A detector that throws counts as a finding ('block': the document is removed or flagged) or is ignored ('skip')
|
|
405
1078
|
*/
|
|
406
1079
|
constructor(options = {}) {
|
|
407
1080
|
super();
|
|
408
1081
|
this.config = { ...DEFAULT_CONFIG, ...options };
|
|
1082
|
+
this._detectors = resolveDetectors("RetrievalGuard", "injection", options);
|
|
1083
|
+
if (this._detectors.length > 0 && this.config.detectInjection !== true) {
|
|
1084
|
+
throw new TypeError(
|
|
1085
|
+
"RetrievalGuard: detectors run in the injection scan; set detectInjection: true"
|
|
1086
|
+
);
|
|
1087
|
+
}
|
|
409
1088
|
}
|
|
410
1089
|
/**
|
|
411
1090
|
* Filter retrieval results based on configured guards.
|
|
@@ -421,17 +1100,59 @@ var require_retrieval_guard = __commonJS({
|
|
|
421
1100
|
* @fires RetrievalGuard#filtered
|
|
422
1101
|
*/
|
|
423
1102
|
filter(results, context = {}) {
|
|
1103
|
+
const state = this._screen(results, context);
|
|
1104
|
+
if (state.output) return state.output;
|
|
1105
|
+
const detections = state.scan.map(
|
|
1106
|
+
(i) => this._detectors.length > 0 ? runDetectorsSync(
|
|
1107
|
+
this._detectors,
|
|
1108
|
+
documentText(state.results[i]),
|
|
1109
|
+
{ stage: "document", document: state.results[i] },
|
|
1110
|
+
{ guard: "RetrievalGuard", asyncMethod: "filterAsync" }
|
|
1111
|
+
) : []
|
|
1112
|
+
);
|
|
1113
|
+
return this._finish(state, detections);
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
* filter() for async detectors, such as a classifier model or a service. Each
|
|
1117
|
+
* document that reaches the injection scan is checked by every detector,
|
|
1118
|
+
* all concurrently.
|
|
1119
|
+
*
|
|
1120
|
+
* @param {Array<object>} results
|
|
1121
|
+
* @param {object} [context={}]
|
|
1122
|
+
* @param {object} [options]
|
|
1123
|
+
* @param {AbortSignal} [options.signal] - Passed to each detector in its context
|
|
1124
|
+
* @returns {Promise<ReturnType<RetrievalGuard['filter']>>}
|
|
1125
|
+
*/
|
|
1126
|
+
async filterAsync(results, context = {}, { signal } = {}) {
|
|
1127
|
+
const state = this._screen(results, context);
|
|
1128
|
+
if (state.output) return state.output;
|
|
1129
|
+
const detections = await Promise.all(
|
|
1130
|
+
state.scan.map(
|
|
1131
|
+
(i) => this._detectors.length > 0 ? runDetectorsAsync(this._detectors, documentText(state.results[i]), {
|
|
1132
|
+
stage: "document",
|
|
1133
|
+
document: state.results[i],
|
|
1134
|
+
...signal && { signal }
|
|
1135
|
+
}) : []
|
|
1136
|
+
)
|
|
1137
|
+
);
|
|
1138
|
+
return this._finish(state, detections);
|
|
1139
|
+
}
|
|
1140
|
+
/**
|
|
1141
|
+
* @private Relevance, freshness and ACL, then which documents go on to
|
|
1142
|
+
* the injection scan.
|
|
1143
|
+
*/
|
|
1144
|
+
_screen(results, context) {
|
|
424
1145
|
if (!Array.isArray(results)) {
|
|
425
|
-
const
|
|
426
|
-
this.emit("filtered",
|
|
427
|
-
return
|
|
1146
|
+
const output = { results: [], removed: [], metadata: { reasons: {} } };
|
|
1147
|
+
this.emit("filtered", output);
|
|
1148
|
+
return { output };
|
|
428
1149
|
}
|
|
429
|
-
const kept = [];
|
|
430
|
-
const removed = [];
|
|
431
1150
|
const reasons = {};
|
|
1151
|
+
const dropped = new Array(results.length).fill(false);
|
|
1152
|
+
const scan = [];
|
|
432
1153
|
const now = Date.now();
|
|
433
1154
|
const maxAgeMs = this.config.maxAgeDays > 0 ? this.config.maxAgeDays * 24 * 60 * 60 * 1e3 : 0;
|
|
434
|
-
|
|
1155
|
+
results.forEach((result, i) => {
|
|
435
1156
|
let dominated = false;
|
|
436
1157
|
const doc = result.metadata || result.doc || result;
|
|
437
1158
|
if (this.config.minRelevanceScore > 0) {
|
|
@@ -465,29 +1186,50 @@ var require_retrieval_guard = __commonJS({
|
|
|
465
1186
|
}
|
|
466
1187
|
}
|
|
467
1188
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
1189
|
+
dropped[i] = dominated;
|
|
1190
|
+
if (!dominated && this.config.detectInjection) scan.push(i);
|
|
1191
|
+
});
|
|
1192
|
+
return { results, reasons, dropped, scan };
|
|
1193
|
+
}
|
|
1194
|
+
/** @private 4. Indirect prompt injection in document text (opt-in). */
|
|
1195
|
+
_finish({ results, reasons, dropped, scan }, detections) {
|
|
1196
|
+
const findings = /* @__PURE__ */ new Map();
|
|
1197
|
+
const skip = this.config.onDetectorError === "skip";
|
|
1198
|
+
scan.forEach((i, n) => {
|
|
1199
|
+
const matches = this.config.builtIn === false ? [] : detectInjection(documentText(results[i]), {
|
|
1200
|
+
patterns: this.config.injectionPatterns
|
|
1201
|
+
}).matches;
|
|
1202
|
+
const found = detections[n] || [];
|
|
1203
|
+
const flagged = found.some((d) => d.flagged || d.error && !skip);
|
|
1204
|
+
if (matches.length > 0 || flagged) {
|
|
1205
|
+
findings.set(i, { matches, detections: found });
|
|
484
1206
|
}
|
|
485
|
-
|
|
1207
|
+
});
|
|
1208
|
+
const kept = [];
|
|
1209
|
+
const removed = [];
|
|
1210
|
+
results.forEach((result, i) => {
|
|
1211
|
+
const finding = findings.get(i);
|
|
1212
|
+
if (finding) {
|
|
1213
|
+
reasons.injection = (reasons.injection || 0) + 1;
|
|
1214
|
+
if (this.config.injectionAction === "flag") {
|
|
1215
|
+
kept.push({
|
|
1216
|
+
...result,
|
|
1217
|
+
injection: {
|
|
1218
|
+
detected: true,
|
|
1219
|
+
matches: finding.matches,
|
|
1220
|
+
...this._detectors.length > 0 && {
|
|
1221
|
+
detections: finding.detections
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
});
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
486
1227
|
removed.push(result);
|
|
487
|
-
|
|
488
|
-
kept.push(result);
|
|
1228
|
+
return;
|
|
489
1229
|
}
|
|
490
|
-
|
|
1230
|
+
if (dropped[i]) removed.push(result);
|
|
1231
|
+
else kept.push(result);
|
|
1232
|
+
});
|
|
491
1233
|
const output = {
|
|
492
1234
|
results: kept,
|
|
493
1235
|
removed,
|
|
@@ -521,18 +1263,19 @@ var require_post_generation_guard = __commonJS({
|
|
|
521
1263
|
"src/guardrails/post-generation-guard.js"(exports2, module2) {
|
|
522
1264
|
"use strict";
|
|
523
1265
|
var { EventEmitter } = require("events");
|
|
1266
|
+
var {
|
|
1267
|
+
DEFAULT_PII_PATTERNS,
|
|
1268
|
+
resolveDetectors,
|
|
1269
|
+
runDetectorsSync,
|
|
1270
|
+
runDetectorsAsync
|
|
1271
|
+
} = require_detectors();
|
|
524
1272
|
var DEFAULT_CONFIG = {
|
|
525
1273
|
enablePIIDetection: true,
|
|
526
1274
|
enableGroundednessCheck: true,
|
|
527
1275
|
groundednessThreshold: 0.3,
|
|
528
1276
|
maxResponseLength: 5e4,
|
|
529
1277
|
minResponseLength: 0,
|
|
530
|
-
piiPatterns:
|
|
531
|
-
email: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
|
|
532
|
-
phone: /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/g,
|
|
533
|
-
ssn: /\b\d{3}-\d{2}-\d{4}\b/g,
|
|
534
|
-
creditCard: /\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b/g
|
|
535
|
-
}
|
|
1278
|
+
piiPatterns: DEFAULT_PII_PATTERNS
|
|
536
1279
|
};
|
|
537
1280
|
var PostGenerationGuard = class extends EventEmitter {
|
|
538
1281
|
/**
|
|
@@ -543,9 +1286,13 @@ var require_post_generation_guard = __commonJS({
|
|
|
543
1286
|
* @param {number} [options.maxResponseLength=50000] - Maximum allowed response length
|
|
544
1287
|
* @param {number} [options.minResponseLength=0] - Minimum response length
|
|
545
1288
|
* @param {object} [options.piiPatterns] - Custom PII regex patterns (merged with defaults)
|
|
1289
|
+
* @param {Array<object>} [options.detectors=[]] - PII detectors to run on the output too (see ./detectors); their matches are redacted. Async ones need checkAsync()
|
|
1290
|
+
* @param {boolean} [options.builtIn=true] - false: use only `detectors`, not the built-in PII patterns
|
|
1291
|
+
* @param {'block'|'skip'} [options.onDetectorError='block'] - A detector that throws makes the output unsafe ('block'; what it would have found can't be redacted) or is ignored ('skip')
|
|
546
1292
|
*/
|
|
547
1293
|
constructor(options = {}) {
|
|
548
1294
|
super();
|
|
1295
|
+
this._detectors = resolveDetectors("PostGenerationGuard", "pii", options);
|
|
549
1296
|
this.config = { ...DEFAULT_CONFIG, ...options };
|
|
550
1297
|
if (options.piiPatterns) {
|
|
551
1298
|
this.config.piiPatterns = {
|
|
@@ -568,13 +1315,52 @@ var require_post_generation_guard = __commonJS({
|
|
|
568
1315
|
* @fires PostGenerationGuard#checked
|
|
569
1316
|
*/
|
|
570
1317
|
check(output, retrievedDocs = []) {
|
|
571
|
-
const issues = [];
|
|
572
1318
|
if (output == null || typeof output !== "string") {
|
|
573
|
-
|
|
574
|
-
const result2 = { safe: false, issues, sanitizedOutput: "" };
|
|
575
|
-
this.emit("checked", result2);
|
|
576
|
-
return result2;
|
|
1319
|
+
return this._invalid();
|
|
577
1320
|
}
|
|
1321
|
+
const detections = this._runDetectors() ? runDetectorsSync(this._detectors, output, CONTEXT, {
|
|
1322
|
+
guard: "PostGenerationGuard",
|
|
1323
|
+
asyncMethod: "checkAsync"
|
|
1324
|
+
}) : [];
|
|
1325
|
+
return this._check(output, retrievedDocs, detections);
|
|
1326
|
+
}
|
|
1327
|
+
/**
|
|
1328
|
+
* check() for async detectors, such as a model or a PII service.
|
|
1329
|
+
* Detectors run concurrently.
|
|
1330
|
+
*
|
|
1331
|
+
* @param {string} output
|
|
1332
|
+
* @param {Array<object>} [retrievedDocs=[]]
|
|
1333
|
+
* @param {object} [options]
|
|
1334
|
+
* @param {AbortSignal} [options.signal] - Passed to each detector in its context
|
|
1335
|
+
* @returns {Promise<ReturnType<PostGenerationGuard['check']>>}
|
|
1336
|
+
*/
|
|
1337
|
+
async checkAsync(output, retrievedDocs = [], { signal } = {}) {
|
|
1338
|
+
if (output == null || typeof output !== "string") {
|
|
1339
|
+
return this._invalid();
|
|
1340
|
+
}
|
|
1341
|
+
const detections = this._runDetectors() ? await runDetectorsAsync(this._detectors, output, {
|
|
1342
|
+
...CONTEXT,
|
|
1343
|
+
...signal && { signal }
|
|
1344
|
+
}) : [];
|
|
1345
|
+
return this._check(output, retrievedDocs, detections);
|
|
1346
|
+
}
|
|
1347
|
+
/** @private */
|
|
1348
|
+
_runDetectors() {
|
|
1349
|
+
return this.config.enablePIIDetection && this._detectors.length > 0;
|
|
1350
|
+
}
|
|
1351
|
+
/** @private 1. The output must be a string. */
|
|
1352
|
+
_invalid() {
|
|
1353
|
+
const result = {
|
|
1354
|
+
safe: false,
|
|
1355
|
+
issues: ["Output must be a string"],
|
|
1356
|
+
sanitizedOutput: ""
|
|
1357
|
+
};
|
|
1358
|
+
this.emit("checked", result);
|
|
1359
|
+
return result;
|
|
1360
|
+
}
|
|
1361
|
+
/** @private */
|
|
1362
|
+
_check(output, retrievedDocs, detections) {
|
|
1363
|
+
const issues = [];
|
|
578
1364
|
if (this.config.maxResponseLength > 0 && output.length > this.config.maxResponseLength) {
|
|
579
1365
|
issues.push(
|
|
580
1366
|
`Output exceeds maximum length of ${this.config.maxResponseLength} characters`
|
|
@@ -586,7 +1372,7 @@ var require_post_generation_guard = __commonJS({
|
|
|
586
1372
|
);
|
|
587
1373
|
}
|
|
588
1374
|
const piiMatches = {};
|
|
589
|
-
if (this.config.enablePIIDetection) {
|
|
1375
|
+
if (this.config.enablePIIDetection && this.config.builtIn !== false) {
|
|
590
1376
|
for (const [name, pattern] of Object.entries(this.config.piiPatterns)) {
|
|
591
1377
|
pattern.lastIndex = 0;
|
|
592
1378
|
const matches = output.match(pattern);
|
|
@@ -598,6 +1384,22 @@ var require_post_generation_guard = __commonJS({
|
|
|
598
1384
|
}
|
|
599
1385
|
}
|
|
600
1386
|
}
|
|
1387
|
+
const skip = this.config.onDetectorError === "skip";
|
|
1388
|
+
const detected = [];
|
|
1389
|
+
for (const d of detections) {
|
|
1390
|
+
if (d.error) {
|
|
1391
|
+
if (!skip) {
|
|
1392
|
+
issues.push(`PII detector "${d.detector}" failed: ${d.error}`);
|
|
1393
|
+
}
|
|
1394
|
+
} else if (d.flagged) {
|
|
1395
|
+
const texts = d.matches.map((m) => m.text).filter(Boolean);
|
|
1396
|
+
detected.push(...texts);
|
|
1397
|
+
const labels = d.labels && d.labels.length ? ` (${d.labels.join(", ")})` : "";
|
|
1398
|
+
issues.push(
|
|
1399
|
+
`PII detected by ${d.detector}${labels}: ${texts.length} occurrence(s)`
|
|
1400
|
+
);
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
601
1403
|
let groundednessScore;
|
|
602
1404
|
if (this.config.enableGroundednessCheck && Array.isArray(retrievedDocs) && retrievedDocs.length > 0) {
|
|
603
1405
|
try {
|
|
@@ -622,6 +1424,7 @@ var require_post_generation_guard = __commonJS({
|
|
|
622
1424
|
}
|
|
623
1425
|
}
|
|
624
1426
|
}
|
|
1427
|
+
sanitizedOutput = replaceAll(sanitizedOutput, detected);
|
|
625
1428
|
if (this.config.maxResponseLength > 0 && sanitizedOutput.length > this.config.maxResponseLength) {
|
|
626
1429
|
sanitizedOutput = sanitizedOutput.substring(
|
|
627
1430
|
0,
|
|
@@ -636,28 +1439,52 @@ var require_post_generation_guard = __commonJS({
|
|
|
636
1439
|
if (groundednessScore !== void 0) {
|
|
637
1440
|
result.groundednessScore = groundednessScore;
|
|
638
1441
|
}
|
|
1442
|
+
if (this._detectors.length > 0) {
|
|
1443
|
+
result.detections = detections.map(({ matches, ...d }) => ({
|
|
1444
|
+
...d,
|
|
1445
|
+
matches: matches.map(({ text, ...m }) => m)
|
|
1446
|
+
}));
|
|
1447
|
+
Object.defineProperty(result, "matchedText", {
|
|
1448
|
+
value: [...new Set(detected)],
|
|
1449
|
+
enumerable: false
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
639
1452
|
this.emit("checked", result);
|
|
640
1453
|
return result;
|
|
641
1454
|
}
|
|
642
1455
|
/**
|
|
643
1456
|
* Replace PII matches in arbitrary text with [REDACTED], using the same
|
|
644
1457
|
* patterns as check(). Used to scrub text derived from the answer, such as
|
|
645
|
-
* citation sentences.
|
|
1458
|
+
* citation sentences. Detectors don't run here (they may be async):
|
|
1459
|
+
* pass the strings a check found, `detectedText(result)`, as `matches`
|
|
1460
|
+
* so they are removed too.
|
|
646
1461
|
*
|
|
647
1462
|
* @param {string} text
|
|
1463
|
+
* @param {string[]} [matches=[]] - Exact strings to redact as well
|
|
648
1464
|
* @returns {string}
|
|
649
1465
|
*/
|
|
650
|
-
redact(text) {
|
|
1466
|
+
redact(text, matches = []) {
|
|
651
1467
|
if (typeof text !== "string" || !this.config.enablePIIDetection) {
|
|
652
1468
|
return text;
|
|
653
1469
|
}
|
|
654
1470
|
let redacted = text;
|
|
655
|
-
|
|
656
|
-
|
|
1471
|
+
if (this.config.builtIn !== false) {
|
|
1472
|
+
for (const pattern of Object.values(this.config.piiPatterns)) {
|
|
1473
|
+
redacted = redacted.replace(pattern, "[REDACTED]");
|
|
1474
|
+
}
|
|
657
1475
|
}
|
|
658
|
-
return redacted;
|
|
1476
|
+
return replaceAll(redacted, matches);
|
|
659
1477
|
}
|
|
660
1478
|
};
|
|
1479
|
+
var CONTEXT = Object.freeze({ stage: "output" });
|
|
1480
|
+
function replaceAll(text, strings) {
|
|
1481
|
+
const unique = [...new Set(strings.filter(Boolean))].sort(
|
|
1482
|
+
(a, b) => b.length - a.length
|
|
1483
|
+
);
|
|
1484
|
+
let out = text;
|
|
1485
|
+
for (const s of unique) out = out.split(s).join("[REDACTED]");
|
|
1486
|
+
return out;
|
|
1487
|
+
}
|
|
661
1488
|
module2.exports = { PostGenerationGuard, DEFAULT_CONFIG };
|
|
662
1489
|
}
|
|
663
1490
|
});
|
|
@@ -670,6 +1497,7 @@ var require_guardrails_pipeline = __commonJS({
|
|
|
670
1497
|
var { PreRetrievalGuard } = require_pre_retrieval_guard();
|
|
671
1498
|
var { RetrievalGuard } = require_retrieval_guard();
|
|
672
1499
|
var { PostGenerationGuard } = require_post_generation_guard();
|
|
1500
|
+
var { detectedText } = require_detectors();
|
|
673
1501
|
var DEFAULT_CONFIG = {
|
|
674
1502
|
preRetrieval: {},
|
|
675
1503
|
// PreRetrievalGuard options, or false to disable
|
|
@@ -686,6 +1514,12 @@ var require_guardrails_pipeline = __commonJS({
|
|
|
686
1514
|
removed: filterOutput.removed.map((doc) => ({ id: doc.id }))
|
|
687
1515
|
};
|
|
688
1516
|
}
|
|
1517
|
+
function withReasons(message, check) {
|
|
1518
|
+
const issues = check && check.issues || [];
|
|
1519
|
+
if (issues.length === 0) return message;
|
|
1520
|
+
const text = issues.map(String).join("; ");
|
|
1521
|
+
return `${message}: ${text.length > 300 ? `${text.slice(0, 297)}...` : text}`;
|
|
1522
|
+
}
|
|
689
1523
|
var GuardrailsPipeline = class extends EventEmitter {
|
|
690
1524
|
/**
|
|
691
1525
|
* @param {object} pipeline - The underlying RAG pipeline (must have a .run() method)
|
|
@@ -725,13 +1559,21 @@ var require_guardrails_pipeline = __commonJS({
|
|
|
725
1559
|
postGeneration: null
|
|
726
1560
|
};
|
|
727
1561
|
let query = input.query;
|
|
1562
|
+
const signal = input.options && input.options.signal;
|
|
1563
|
+
const callOptions = signal ? { signal } : {};
|
|
728
1564
|
if (this.preGuard) {
|
|
729
|
-
guardrails.preRetrieval = this.preGuard.
|
|
1565
|
+
guardrails.preRetrieval = await this.preGuard.checkAsync(
|
|
1566
|
+
query,
|
|
1567
|
+
callOptions
|
|
1568
|
+
);
|
|
730
1569
|
this.emit("preRetrieval", guardrails.preRetrieval);
|
|
731
1570
|
if (!guardrails.preRetrieval.safe && this.config.strict) {
|
|
732
1571
|
return {
|
|
733
1572
|
success: false,
|
|
734
|
-
error:
|
|
1573
|
+
error: withReasons(
|
|
1574
|
+
"Pre-retrieval guard blocked query",
|
|
1575
|
+
guardrails.preRetrieval
|
|
1576
|
+
),
|
|
735
1577
|
guardrails
|
|
736
1578
|
};
|
|
737
1579
|
}
|
|
@@ -739,10 +1581,11 @@ var require_guardrails_pipeline = __commonJS({
|
|
|
739
1581
|
}
|
|
740
1582
|
let preGenerationFilter = null;
|
|
741
1583
|
const filterOptions = this.retrievalGuard ? {
|
|
742
|
-
filterResults: (docs) => {
|
|
743
|
-
preGenerationFilter = this.retrievalGuard.
|
|
1584
|
+
filterResults: async (docs) => {
|
|
1585
|
+
preGenerationFilter = await this.retrievalGuard.filterAsync(
|
|
744
1586
|
docs,
|
|
745
|
-
input.context
|
|
1587
|
+
input.context,
|
|
1588
|
+
callOptions
|
|
746
1589
|
);
|
|
747
1590
|
return preGenerationFilter.results;
|
|
748
1591
|
}
|
|
@@ -757,7 +1600,11 @@ var require_guardrails_pipeline = __commonJS({
|
|
|
757
1600
|
this.emit("retrieval", guardrails.retrieval);
|
|
758
1601
|
} else if (this.retrievalGuard && result.results) {
|
|
759
1602
|
guardrails.retrieval = summarizeFilter(
|
|
760
|
-
this.retrievalGuard.
|
|
1603
|
+
await this.retrievalGuard.filterAsync(
|
|
1604
|
+
result.results,
|
|
1605
|
+
input.context,
|
|
1606
|
+
callOptions
|
|
1607
|
+
)
|
|
761
1608
|
);
|
|
762
1609
|
this.emit("retrieval", guardrails.retrieval);
|
|
763
1610
|
result.results = guardrails.retrieval.results;
|
|
@@ -770,21 +1617,26 @@ var require_guardrails_pipeline = __commonJS({
|
|
|
770
1617
|
}
|
|
771
1618
|
}
|
|
772
1619
|
if (this.postGuard && result.answer) {
|
|
773
|
-
guardrails.postGeneration = this.postGuard.
|
|
1620
|
+
guardrails.postGeneration = await this.postGuard.checkAsync(
|
|
774
1621
|
result.answer,
|
|
775
|
-
result.results
|
|
1622
|
+
result.results,
|
|
1623
|
+
callOptions
|
|
776
1624
|
);
|
|
777
1625
|
this.emit("postGeneration", guardrails.postGeneration);
|
|
778
1626
|
if (!guardrails.postGeneration.safe && this.config.strict) {
|
|
779
1627
|
return {
|
|
780
1628
|
success: false,
|
|
781
|
-
error:
|
|
1629
|
+
error: withReasons(
|
|
1630
|
+
"Post-generation guard flagged output",
|
|
1631
|
+
guardrails.postGeneration
|
|
1632
|
+
),
|
|
782
1633
|
guardrails
|
|
783
1634
|
};
|
|
784
1635
|
}
|
|
785
1636
|
result.answer = guardrails.postGeneration.sanitizedOutput;
|
|
786
|
-
|
|
787
|
-
this._redactDeep(result.
|
|
1637
|
+
const found = detectedText(guardrails.postGeneration);
|
|
1638
|
+
this._redactDeep(result.citations, found);
|
|
1639
|
+
this._redactDeep(result.evaluation, found);
|
|
788
1640
|
}
|
|
789
1641
|
result.guardrails = guardrails;
|
|
790
1642
|
return result;
|
|
@@ -794,14 +1646,15 @@ var require_guardrails_pipeline = __commonJS({
|
|
|
794
1646
|
* scrub every string in them with the same PII patterns as the answer.
|
|
795
1647
|
*
|
|
796
1648
|
* @param {*} value - Object, array or string to redact in place
|
|
1649
|
+
* @param {string[]} [matches=[]] - Strings found by PII detectors, redacted too
|
|
797
1650
|
* @returns {*} The redacted value
|
|
798
1651
|
* @private
|
|
799
1652
|
*/
|
|
800
|
-
_redactDeep(value) {
|
|
801
|
-
if (typeof value === "string") return this.postGuard.redact(value);
|
|
1653
|
+
_redactDeep(value, matches = []) {
|
|
1654
|
+
if (typeof value === "string") return this.postGuard.redact(value, matches);
|
|
802
1655
|
if (value && typeof value === "object") {
|
|
803
1656
|
for (const key of Object.keys(value)) {
|
|
804
|
-
value[key] = this._redactDeep(value[key]);
|
|
1657
|
+
value[key] = this._redactDeep(value[key], matches);
|
|
805
1658
|
}
|
|
806
1659
|
}
|
|
807
1660
|
return value;
|
|
@@ -817,12 +1670,28 @@ var require_guardrails = __commonJS({
|
|
|
817
1670
|
"use strict";
|
|
818
1671
|
var { PreRetrievalGuard } = require_pre_retrieval_guard();
|
|
819
1672
|
var { detectInjection } = require_injection_detector();
|
|
1673
|
+
var {
|
|
1674
|
+
patternInjectionDetector,
|
|
1675
|
+
patternPIIDetector,
|
|
1676
|
+
detectedText
|
|
1677
|
+
} = require_detectors();
|
|
1678
|
+
var {
|
|
1679
|
+
evaluateDetectors,
|
|
1680
|
+
detectorVerdict,
|
|
1681
|
+
renderDetectorReport
|
|
1682
|
+
} = require_evaluate_detectors();
|
|
820
1683
|
var { RetrievalGuard } = require_retrieval_guard();
|
|
821
1684
|
var { PostGenerationGuard } = require_post_generation_guard();
|
|
822
1685
|
var { GuardrailsPipeline } = require_guardrails_pipeline();
|
|
823
1686
|
module2.exports = {
|
|
824
1687
|
PreRetrievalGuard,
|
|
825
1688
|
detectInjection,
|
|
1689
|
+
patternInjectionDetector,
|
|
1690
|
+
patternPIIDetector,
|
|
1691
|
+
detectedText,
|
|
1692
|
+
evaluateDetectors,
|
|
1693
|
+
detectorVerdict,
|
|
1694
|
+
renderDetectorReport,
|
|
826
1695
|
RetrievalGuard,
|
|
827
1696
|
PostGenerationGuard,
|
|
828
1697
|
GuardrailsPipeline
|
|
@@ -833,6 +1702,8 @@ var require_guardrails = __commonJS({
|
|
|
833
1702
|
// src/package-entry.js
|
|
834
1703
|
var mod = require_guardrails();
|
|
835
1704
|
var modules = {
|
|
1705
|
+
"guardrails/detectors.js": () => require_detectors(),
|
|
1706
|
+
"guardrails/evaluate-detectors.js": () => require_evaluate_detectors(),
|
|
836
1707
|
"guardrails/guardrails-pipeline.js": () => require_guardrails_pipeline(),
|
|
837
1708
|
"guardrails/injection-detector.js": () => require_injection_detector(),
|
|
838
1709
|
"guardrails/post-generation-guard.js": () => require_post_generation_guard(),
|
package/dist/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { PreRetrievalGuard, detectInjection, RetrievalGuard, PostGenerationGuard, GuardrailsPipeline } from "@devilsdev/rag-core/all-types";
|
|
1
|
+
export { PreRetrievalGuard, detectInjection, patternInjectionDetector, patternPIIDetector, detectedText, evaluateDetectors, detectorVerdict, renderDetectorReport, RetrievalGuard, PostGenerationGuard, GuardrailsPipeline } from "@devilsdev/rag-core/all-types";
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createRequire } from 'module';
|
|
2
2
|
const require = createRequire(import.meta.url);
|
|
3
3
|
const mod = require('./index.cjs');
|
|
4
|
-
export const { PreRetrievalGuard, detectInjection, RetrievalGuard, PostGenerationGuard, GuardrailsPipeline } = mod;
|
|
4
|
+
export const { PreRetrievalGuard, detectInjection, patternInjectionDetector, patternPIIDetector, detectedText, evaluateDetectors, detectorVerdict, renderDetectorReport, RetrievalGuard, PostGenerationGuard, GuardrailsPipeline } = mod;
|
|
5
5
|
export default mod;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devilsdev/rag-guardrails",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.0",
|
|
4
4
|
"description": "Guardrails for RAG: prompt-injection detection and pre-retrieval, retrieval and post-generation guards.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"rag",
|
|
@@ -41,6 +41,6 @@
|
|
|
41
41
|
"access": "public"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@devilsdev/rag-core": "3.
|
|
44
|
+
"@devilsdev/rag-core": "3.5.0"
|
|
45
45
|
}
|
|
46
46
|
}
|