@devilsdev/rag-guardrails 3.8.1 → 3.8.2
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/dist/index.cjs +227 -16
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -184,7 +184,7 @@ var require_injection_detector = __commonJS({
|
|
|
184
184
|
// Chat-template role markers: "system:" as a line prefix, [system],
|
|
185
185
|
// <|im_start|>system, [INST], <<SYS>>, "### system".
|
|
186
186
|
id: "role-marker",
|
|
187
|
-
pattern: /(?:^|\n)\s*(?:system|assistant|developer)\s*:|\[\s*(?:system|inst)\s*\]|<\|?\s*(?:im_start|system)\s*\|?>|<<\s*sys\s
|
|
187
|
+
pattern: /(?:^|\n)\s*(?:system|assistant|developer)\s*:|\[\s*(?:system|inst)\s*\]|<\|?\s*(?:im_start|system)\s*\|?>|<<\s*sys\s*>>|(?<!#)#{2,}\s*(?:system|instruction)s?\b/
|
|
188
188
|
},
|
|
189
189
|
{
|
|
190
190
|
id: "dan",
|
|
@@ -256,7 +256,7 @@ var require_detectors = __commonJS({
|
|
|
256
256
|
var { detectInjection } = require_injection_detector();
|
|
257
257
|
var KINDS = ["injection", "pii"];
|
|
258
258
|
var DEFAULT_PII_PATTERNS = {
|
|
259
|
-
email: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
|
|
259
|
+
email: /(?<![a-zA-Z0-9._%+-])[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
|
|
260
260
|
phone: /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/g,
|
|
261
261
|
ssn: /\b\d{3}-\d{2}-\d{4}\b/g,
|
|
262
262
|
creditCard: /\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b/g
|
|
@@ -1040,12 +1040,20 @@ code{background:var(--chip);padding:1px 5px;border-radius:4px;font-size:13px}`;
|
|
|
1040
1040
|
}
|
|
1041
1041
|
});
|
|
1042
1042
|
|
|
1043
|
+
// scoped:@devilsdev/rag-core|retrieval/access-fields.js
|
|
1044
|
+
var require_access_fields = __commonJS({
|
|
1045
|
+
"scoped:@devilsdev/rag-core|retrieval/access-fields.js"(exports2, module2) {
|
|
1046
|
+
module2.exports = require("@devilsdev/rag-core")[Symbol.for("@devilsdev/rag-core/internal")]("retrieval/access-fields.js");
|
|
1047
|
+
}
|
|
1048
|
+
});
|
|
1049
|
+
|
|
1043
1050
|
// src/guardrails/retrieval-guard.js
|
|
1044
1051
|
var require_retrieval_guard = __commonJS({
|
|
1045
1052
|
"src/guardrails/retrieval-guard.js"(exports2, module2) {
|
|
1046
1053
|
"use strict";
|
|
1047
1054
|
var { EventEmitter } = require("events");
|
|
1048
1055
|
var { detectInjection } = require_injection_detector();
|
|
1056
|
+
var { NO_ACCESS } = require_access_fields();
|
|
1049
1057
|
var {
|
|
1050
1058
|
resolveDetectors,
|
|
1051
1059
|
runDetectorsSync,
|
|
@@ -1173,7 +1181,9 @@ var require_retrieval_guard = __commonJS({
|
|
|
1173
1181
|
}
|
|
1174
1182
|
}
|
|
1175
1183
|
if (!dominated && this.config.enforceACL) {
|
|
1176
|
-
const userPermissions = permissionsOf(context.userPermissions)
|
|
1184
|
+
const userPermissions = permissionsOf(context.userPermissions).filter(
|
|
1185
|
+
(role) => role !== NO_ACCESS
|
|
1186
|
+
);
|
|
1177
1187
|
const hasAccess = restrictionsOf(result).every(
|
|
1178
1188
|
(roles) => roles.some((role) => userPermissions.includes(role))
|
|
1179
1189
|
);
|
|
@@ -1507,6 +1517,13 @@ var require_post_generation_guard = __commonJS({
|
|
|
1507
1517
|
}
|
|
1508
1518
|
});
|
|
1509
1519
|
|
|
1520
|
+
// scoped:@devilsdev/rag-core|utils/run-abort.js
|
|
1521
|
+
var require_run_abort = __commonJS({
|
|
1522
|
+
"scoped:@devilsdev/rag-core|utils/run-abort.js"(exports2, module2) {
|
|
1523
|
+
module2.exports = require("@devilsdev/rag-core")[Symbol.for("@devilsdev/rag-core/internal")]("utils/run-abort.js");
|
|
1524
|
+
}
|
|
1525
|
+
});
|
|
1526
|
+
|
|
1510
1527
|
// src/guardrails/guardrails-pipeline.js
|
|
1511
1528
|
var require_guardrails_pipeline = __commonJS({
|
|
1512
1529
|
"src/guardrails/guardrails-pipeline.js"(exports2, module2) {
|
|
@@ -1516,6 +1533,7 @@ var require_guardrails_pipeline = __commonJS({
|
|
|
1516
1533
|
var { RetrievalGuard } = require_retrieval_guard();
|
|
1517
1534
|
var { PostGenerationGuard } = require_post_generation_guard();
|
|
1518
1535
|
var { detectedText } = require_detectors();
|
|
1536
|
+
var { createRunAbort } = require_run_abort();
|
|
1519
1537
|
var DEFAULT_CONFIG = {
|
|
1520
1538
|
preRetrieval: {},
|
|
1521
1539
|
// PreRetrievalGuard options, or false to disable
|
|
@@ -1526,12 +1544,79 @@ var require_guardrails_pipeline = __commonJS({
|
|
|
1526
1544
|
strict: false
|
|
1527
1545
|
// If true, block on any guard failure; if false, warn and continue
|
|
1528
1546
|
};
|
|
1547
|
+
var DEFAULT_MAX_STREAM_CHARS = 1e6;
|
|
1548
|
+
var RETRIEVAL_SKIPPED_REASON = "retrieval guard could not run: the wrapped pipeline neither called filterResults nor returned results; answer withheld";
|
|
1529
1549
|
function summarizeFilter(filterOutput) {
|
|
1530
1550
|
return {
|
|
1531
1551
|
...filterOutput,
|
|
1532
1552
|
removed: filterOutput.removed.map((doc) => ({ id: doc.id }))
|
|
1533
1553
|
};
|
|
1534
1554
|
}
|
|
1555
|
+
function isAsyncIterable(value) {
|
|
1556
|
+
return value != null && typeof value === "object" && typeof value[Symbol.asyncIterator] === "function";
|
|
1557
|
+
}
|
|
1558
|
+
function withheldSummary(guardrails) {
|
|
1559
|
+
const out = { ...guardrails };
|
|
1560
|
+
if (out.preRetrieval) {
|
|
1561
|
+
const { sanitizedQuery, injection, ...rest } = out.preRetrieval;
|
|
1562
|
+
out.preRetrieval = {
|
|
1563
|
+
...rest,
|
|
1564
|
+
...injection && {
|
|
1565
|
+
injection: {
|
|
1566
|
+
detected: injection.detected,
|
|
1567
|
+
matches: (injection.matches || []).map((m) => ({ id: m && m.id }))
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
};
|
|
1571
|
+
}
|
|
1572
|
+
if (out.retrieval && Array.isArray(out.retrieval.results)) {
|
|
1573
|
+
const { results, ...rest } = out.retrieval;
|
|
1574
|
+
out.retrieval = {
|
|
1575
|
+
...rest,
|
|
1576
|
+
removed: (rest.removed || []).map((doc) => ({ id: doc && doc.id }))
|
|
1577
|
+
};
|
|
1578
|
+
}
|
|
1579
|
+
if (out.postGeneration) {
|
|
1580
|
+
out.postGeneration = { ...out.postGeneration, sanitizedOutput: "" };
|
|
1581
|
+
}
|
|
1582
|
+
return out;
|
|
1583
|
+
}
|
|
1584
|
+
function withheld(error, guardrails) {
|
|
1585
|
+
return { success: false, error, guardrails: withheldSummary(guardrails) };
|
|
1586
|
+
}
|
|
1587
|
+
async function collectStream(source, { signal, timeout, maxChars }) {
|
|
1588
|
+
const iterator = source[Symbol.asyncIterator]();
|
|
1589
|
+
const run = createRunAbort({ timeout, signal });
|
|
1590
|
+
let text = "";
|
|
1591
|
+
let done = null;
|
|
1592
|
+
let overflow = false;
|
|
1593
|
+
try {
|
|
1594
|
+
for (; ; ) {
|
|
1595
|
+
const next = await run.race(iterator.next());
|
|
1596
|
+
if (next.done) break;
|
|
1597
|
+
const chunk = next.value;
|
|
1598
|
+
text += typeof chunk === "string" ? chunk : String((chunk && chunk.token) ?? "");
|
|
1599
|
+
if (chunk && typeof chunk === "object" && chunk.done) {
|
|
1600
|
+
const { token, ...rest } = chunk;
|
|
1601
|
+
done = rest;
|
|
1602
|
+
break;
|
|
1603
|
+
}
|
|
1604
|
+
if (text.length > maxChars) {
|
|
1605
|
+
overflow = true;
|
|
1606
|
+
break;
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
} catch (error) {
|
|
1610
|
+
if (iterator.return) Promise.resolve(iterator.return()).catch(() => {
|
|
1611
|
+
});
|
|
1612
|
+
throw error;
|
|
1613
|
+
} finally {
|
|
1614
|
+
run.cleanup();
|
|
1615
|
+
}
|
|
1616
|
+
if (iterator.return) await Promise.resolve(iterator.return()).catch(() => {
|
|
1617
|
+
});
|
|
1618
|
+
return { text, done, overflow };
|
|
1619
|
+
}
|
|
1535
1620
|
function withReasons(message, check) {
|
|
1536
1621
|
const issues = check && check.issues || [];
|
|
1537
1622
|
if (issues.length === 0) return message;
|
|
@@ -1563,6 +1648,12 @@ var require_guardrails_pipeline = __commonJS({
|
|
|
1563
1648
|
/**
|
|
1564
1649
|
* Run the full guarded pipeline.
|
|
1565
1650
|
*
|
|
1651
|
+
* With `options.stream` and a post-generation guard, the token stream is
|
|
1652
|
+
* buffered, checked and redacted as a whole, then released as an async
|
|
1653
|
+
* iterable: one token chunk with the sanitized answer, then a done chunk
|
|
1654
|
+
* carrying the redacted citations/evaluation and the `guardrails` summary.
|
|
1655
|
+
* A strict-mode block yields only a done chunk with `success: false`.
|
|
1656
|
+
*
|
|
1566
1657
|
* @param {object} input - Pipeline input
|
|
1567
1658
|
* @param {string} input.query - The user query
|
|
1568
1659
|
* @param {Float32Array} [input.queryVector] - Optional precomputed query vector
|
|
@@ -1571,6 +1662,7 @@ var require_guardrails_pipeline = __commonJS({
|
|
|
1571
1662
|
* @returns {Promise<object>} Pipeline result with guardrails metadata attached
|
|
1572
1663
|
*/
|
|
1573
1664
|
async run(input) {
|
|
1665
|
+
const startedAt = Date.now();
|
|
1574
1666
|
const guardrails = {
|
|
1575
1667
|
preRetrieval: null,
|
|
1576
1668
|
retrieval: null,
|
|
@@ -1586,14 +1678,13 @@ var require_guardrails_pipeline = __commonJS({
|
|
|
1586
1678
|
);
|
|
1587
1679
|
this.emit("preRetrieval", guardrails.preRetrieval);
|
|
1588
1680
|
if (!guardrails.preRetrieval.safe && this.config.strict) {
|
|
1589
|
-
return
|
|
1590
|
-
|
|
1591
|
-
error: withReasons(
|
|
1681
|
+
return withheld(
|
|
1682
|
+
withReasons(
|
|
1592
1683
|
"Pre-retrieval guard blocked query",
|
|
1593
1684
|
guardrails.preRetrieval
|
|
1594
1685
|
),
|
|
1595
1686
|
guardrails
|
|
1596
|
-
|
|
1687
|
+
);
|
|
1597
1688
|
}
|
|
1598
1689
|
query = guardrails.preRetrieval.sanitizedQuery;
|
|
1599
1690
|
}
|
|
@@ -1613,10 +1704,11 @@ var require_guardrails_pipeline = __commonJS({
|
|
|
1613
1704
|
query,
|
|
1614
1705
|
options: { ...input.options, citations: true, ...filterOptions }
|
|
1615
1706
|
});
|
|
1707
|
+
const streaming = isAsyncIterable(result);
|
|
1616
1708
|
if (this.retrievalGuard && preGenerationFilter) {
|
|
1617
1709
|
guardrails.retrieval = summarizeFilter(preGenerationFilter);
|
|
1618
1710
|
this.emit("retrieval", guardrails.retrieval);
|
|
1619
|
-
} else if (this.retrievalGuard && result.results) {
|
|
1711
|
+
} else if (this.retrievalGuard && !streaming && result && Array.isArray(result.results)) {
|
|
1620
1712
|
guardrails.retrieval = summarizeFilter(
|
|
1621
1713
|
await this.retrievalGuard.filterAsync(
|
|
1622
1714
|
result.results,
|
|
@@ -1627,12 +1719,29 @@ var require_guardrails_pipeline = __commonJS({
|
|
|
1627
1719
|
this.emit("retrieval", guardrails.retrieval);
|
|
1628
1720
|
result.results = guardrails.retrieval.results;
|
|
1629
1721
|
if (guardrails.retrieval.metadata.reasons.acl) {
|
|
1630
|
-
return
|
|
1631
|
-
|
|
1632
|
-
error: "Retrieval guard removed access-controlled documents after generation; answer withheld",
|
|
1722
|
+
return withheld(
|
|
1723
|
+
"Retrieval guard removed access-controlled documents after generation; answer withheld",
|
|
1633
1724
|
guardrails
|
|
1634
|
-
|
|
1725
|
+
);
|
|
1726
|
+
}
|
|
1727
|
+
} else if (this.retrievalGuard && this._retrievalChecksActive() && result && (streaming || result.answer || this.retrievalGuard.config.enforceACL && result.success !== false)) {
|
|
1728
|
+
guardrails.retrieval = {
|
|
1729
|
+
skipped: true,
|
|
1730
|
+
reason: RETRIEVAL_SKIPPED_REASON
|
|
1731
|
+
};
|
|
1732
|
+
if (this.retrievalGuard.config.enforceACL || this.config.strict) {
|
|
1733
|
+
return withheld(RETRIEVAL_SKIPPED_REASON, guardrails);
|
|
1635
1734
|
}
|
|
1735
|
+
this._warnRetrievalSkipped();
|
|
1736
|
+
}
|
|
1737
|
+
if (streaming && this.postGuard) {
|
|
1738
|
+
return this._releaseStream(result, {
|
|
1739
|
+
guardrails,
|
|
1740
|
+
callOptions,
|
|
1741
|
+
signal,
|
|
1742
|
+
timeout: this._remainingTimeout(input, startedAt),
|
|
1743
|
+
docs: preGenerationFilter ? preGenerationFilter.results : void 0
|
|
1744
|
+
});
|
|
1636
1745
|
}
|
|
1637
1746
|
if (this.postGuard && result.answer) {
|
|
1638
1747
|
guardrails.postGeneration = await this.postGuard.checkAsync(
|
|
@@ -1642,14 +1751,13 @@ var require_guardrails_pipeline = __commonJS({
|
|
|
1642
1751
|
);
|
|
1643
1752
|
this.emit("postGeneration", guardrails.postGeneration);
|
|
1644
1753
|
if (!guardrails.postGeneration.safe && this.config.strict) {
|
|
1645
|
-
return
|
|
1646
|
-
|
|
1647
|
-
error: withReasons(
|
|
1754
|
+
return withheld(
|
|
1755
|
+
withReasons(
|
|
1648
1756
|
"Post-generation guard flagged output",
|
|
1649
1757
|
guardrails.postGeneration
|
|
1650
1758
|
),
|
|
1651
1759
|
guardrails
|
|
1652
|
-
|
|
1760
|
+
);
|
|
1653
1761
|
}
|
|
1654
1762
|
result.answer = guardrails.postGeneration.sanitizedOutput;
|
|
1655
1763
|
const found = detectedText(guardrails.postGeneration);
|
|
@@ -1659,6 +1767,109 @@ var require_guardrails_pipeline = __commonJS({
|
|
|
1659
1767
|
result.guardrails = guardrails;
|
|
1660
1768
|
return result;
|
|
1661
1769
|
}
|
|
1770
|
+
/**
|
|
1771
|
+
* Whether the retrieval guard has a check that needs documents.
|
|
1772
|
+
* @private
|
|
1773
|
+
*/
|
|
1774
|
+
_retrievalChecksActive() {
|
|
1775
|
+
const c = this.retrievalGuard.config;
|
|
1776
|
+
return Boolean(
|
|
1777
|
+
c.enforceACL || c.detectInjection || c.minRelevanceScore > 0 || c.maxAgeDays > 0
|
|
1778
|
+
);
|
|
1779
|
+
}
|
|
1780
|
+
/**
|
|
1781
|
+
* Milliseconds left of the caller's options.timeout, or undefined.
|
|
1782
|
+
* @private
|
|
1783
|
+
*/
|
|
1784
|
+
_remainingTimeout(input, startedAt) {
|
|
1785
|
+
const t = input.options && input.options.timeout;
|
|
1786
|
+
if (typeof t !== "number" || !(t > 0)) return void 0;
|
|
1787
|
+
return Math.max(1, t - (Date.now() - startedAt));
|
|
1788
|
+
}
|
|
1789
|
+
/** @private One warning per pipeline instance. */
|
|
1790
|
+
_warnRetrievalSkipped() {
|
|
1791
|
+
if (this._warnedRetrievalSkipped) return;
|
|
1792
|
+
this._warnedRetrievalSkipped = true;
|
|
1793
|
+
process.emitWarning(
|
|
1794
|
+
"GuardrailsPipeline: the retrieval guard was skipped because the wrapped pipeline neither called options.filterResults nor returned results. Call options.filterResults before generating, or return results.",
|
|
1795
|
+
{ code: "RAG_GUARDRAILS_RETRIEVAL_SKIPPED" }
|
|
1796
|
+
);
|
|
1797
|
+
}
|
|
1798
|
+
/**
|
|
1799
|
+
* Collect a token stream, run the post-generation guard on the whole
|
|
1800
|
+
* answer and return an async iterable that yields the sanitized answer as
|
|
1801
|
+
* one token chunk and then the done chunk. A blocked answer yields no
|
|
1802
|
+
* tokens, only a done chunk with the error.
|
|
1803
|
+
*
|
|
1804
|
+
* @private
|
|
1805
|
+
*/
|
|
1806
|
+
async _releaseStream(source, { guardrails, callOptions, signal, timeout, docs }) {
|
|
1807
|
+
const limit = this.postGuard.config.maxResponseLength;
|
|
1808
|
+
const maxChars = limit > 0 ? limit : DEFAULT_MAX_STREAM_CHARS;
|
|
1809
|
+
const { text, done, overflow } = await collectStream(source, {
|
|
1810
|
+
signal,
|
|
1811
|
+
timeout,
|
|
1812
|
+
maxChars
|
|
1813
|
+
});
|
|
1814
|
+
let tokenChunk = null;
|
|
1815
|
+
let doneChunk;
|
|
1816
|
+
if (!text) {
|
|
1817
|
+
doneChunk = { ...done, token: "", done: true, guardrails };
|
|
1818
|
+
} else {
|
|
1819
|
+
const check = await this.postGuard.checkAsync(
|
|
1820
|
+
text,
|
|
1821
|
+
done && done.results || docs,
|
|
1822
|
+
callOptions
|
|
1823
|
+
);
|
|
1824
|
+
if (overflow && check.safe) {
|
|
1825
|
+
check.safe = false;
|
|
1826
|
+
check.sanitizedOutput = check.sanitizedOutput.slice(0, maxChars);
|
|
1827
|
+
check.issues.push(
|
|
1828
|
+
`Output exceeds maximum length of ${maxChars} characters`
|
|
1829
|
+
);
|
|
1830
|
+
}
|
|
1831
|
+
guardrails.postGeneration = check;
|
|
1832
|
+
this.emit("postGeneration", check);
|
|
1833
|
+
if (!check.safe && this.config.strict) {
|
|
1834
|
+
doneChunk = {
|
|
1835
|
+
token: "",
|
|
1836
|
+
done: true,
|
|
1837
|
+
...withheld(
|
|
1838
|
+
withReasons("Post-generation guard flagged output", check),
|
|
1839
|
+
guardrails
|
|
1840
|
+
),
|
|
1841
|
+
...done && done.timing && { timing: done.timing }
|
|
1842
|
+
};
|
|
1843
|
+
} else {
|
|
1844
|
+
const found = detectedText(check);
|
|
1845
|
+
const { citations, evaluation, ...rest } = done || {};
|
|
1846
|
+
doneChunk = {
|
|
1847
|
+
...rest,
|
|
1848
|
+
token: "",
|
|
1849
|
+
done: true,
|
|
1850
|
+
...citations !== void 0 && {
|
|
1851
|
+
citations: this._redactDeep(citations, found)
|
|
1852
|
+
},
|
|
1853
|
+
...evaluation !== void 0 && {
|
|
1854
|
+
evaluation: this._redactDeep(evaluation, found)
|
|
1855
|
+
},
|
|
1856
|
+
guardrails
|
|
1857
|
+
};
|
|
1858
|
+
if (check.sanitizedOutput) {
|
|
1859
|
+
tokenChunk = { token: check.sanitizedOutput, done: false };
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
const stream = (async function* released() {
|
|
1864
|
+
if (tokenChunk) yield tokenChunk;
|
|
1865
|
+
yield doneChunk;
|
|
1866
|
+
})();
|
|
1867
|
+
Object.defineProperty(stream, "guardrails", {
|
|
1868
|
+
value: doneChunk.guardrails,
|
|
1869
|
+
enumerable: false
|
|
1870
|
+
});
|
|
1871
|
+
return stream;
|
|
1872
|
+
}
|
|
1662
1873
|
/**
|
|
1663
1874
|
* Citations and evaluation details copy sentences from the raw answer, so
|
|
1664
1875
|
* scrub every string in them with the same PII patterns as the answer.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devilsdev/rag-guardrails",
|
|
3
|
-
"version": "3.8.
|
|
3
|
+
"version": "3.8.2",
|
|
4
4
|
"description": "Guardrails for RAG: prompt-injection detection and pre-retrieval, retrieval and post-generation guards.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"rag",
|
|
@@ -42,6 +42,6 @@
|
|
|
42
42
|
"access": "public"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@devilsdev/rag-core": "3.8.
|
|
45
|
+
"@devilsdev/rag-core": "3.8.2"
|
|
46
46
|
}
|
|
47
47
|
}
|