@devilsdev/rag-pipeline-utils 2.3.1 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -480
- package/contracts/loader-contract.json +11 -6
- package/contracts/reranker-contract.json +18 -26
- package/dist/index.cjs +21190 -47
- package/dist/index.d.ts +298 -68
- package/dist/index.mjs +9 -76
- package/package.json +37 -11
- package/src/agentic/agentic-pipeline.js +235 -0
- package/src/agentic/iterative-retriever.js +213 -0
- package/src/agentic/query-planner.js +208 -0
- package/src/agentic/self-critique.js +102 -0
- package/src/ai/adaptive-retrieval.js +0 -3
- package/src/ai/federated-learning.js +0 -3
- package/src/ai/federation.js +1 -6
- package/src/ai/index.js +9 -20
- package/src/ai/model-training.js +0 -3
- package/src/ai/multimodal-processing.js +0 -3
- package/src/ai/multimodal.js +1 -6
- package/src/ai/orchestrator.js +1 -6
- package/src/ai/retrieval-engine.js +1 -6
- package/src/chunking/chunking-engine.js +142 -0
- package/src/chunking/strategies/fixed-size.js +54 -0
- package/src/chunking/strategies/recursive.js +127 -0
- package/src/chunking/strategies/semantic.js +128 -0
- package/src/chunking/strategies/sentence.js +32 -0
- package/src/chunking/strategies/structure-aware.js +138 -0
- package/src/citation/citation-tracker.js +161 -0
- package/src/citation/hallucination-detector.js +74 -0
- package/src/citation/source-mapper.js +95 -0
- package/src/cli/commands/ai-ml/adaptive-commands.js +178 -0
- package/src/cli/commands/ai-ml/federated-commands.js +207 -0
- package/src/cli/commands/ai-ml/index.js +17 -0
- package/src/cli/commands/ai-ml/multimodal-commands.js +187 -0
- package/src/cli/commands/ai-ml/shared.js +51 -0
- package/src/cli/commands/ai-ml/training-commands.js +228 -0
- package/src/cli/commands/ai-ml.js +176 -757
- package/src/cli/commands/plugin-hub/certification-commands.js +221 -0
- package/src/cli/commands/plugin-hub/event-handlers.js +64 -0
- package/src/cli/commands/plugin-hub/hub-commands.js +290 -0
- package/src/cli/commands/plugin-hub/review-commands.js +78 -0
- package/src/cli/commands/plugin-hub.js +106 -782
- package/src/cli/diagnostics/config-checker.js +81 -0
- package/src/cli/diagnostics/dependency-checker.js +69 -0
- package/src/cli/diagnostics/environment-checker.js +45 -0
- package/src/cli/diagnostics/performance-checker.js +51 -0
- package/src/cli/diagnostics/plugin-checker.js +106 -0
- package/src/cli/diagnostics/security-checker.js +61 -0
- package/src/cli/doctor-command.js +70 -369
- package/src/cli/enhanced-cli-commands.js +162 -706
- package/src/cli/handlers/config-handler.js +126 -0
- package/src/cli/handlers/diagnostics-handler.js +113 -0
- package/src/cli/handlers/ingest-handler.js +42 -0
- package/src/cli/handlers/init-handler.js +51 -0
- package/src/cli/handlers/query-handler.js +45 -0
- package/src/cli/handlers/run-handler.js +181 -0
- package/src/cli/interactive-wizard.js +99 -723
- package/src/cli/shared/cli-utils.js +89 -0
- package/src/cli/wizard/configuration.js +148 -0
- package/src/cli/wizard/observability.js +79 -0
- package/src/cli/wizard/performance.js +76 -0
- package/src/cli/wizard/plugin-selection.js +297 -0
- package/src/cli/wizard/preview-save.js +172 -0
- package/src/cli/wizard/project-setup.js +66 -0
- package/src/config/memory-optimization.js +3 -3
- package/src/connectors/anthropic-connector.js +179 -0
- package/src/connectors/base-connector.js +94 -0
- package/src/connectors/cohere-connector.js +143 -0
- package/src/connectors/local-embedder.js +171 -0
- package/src/connectors/memory-retriever.js +150 -0
- package/src/connectors/ollama-connector.js +205 -0
- package/src/connectors/openai-connector.js +173 -0
- package/src/core/create-pipeline.js +21 -1
- package/src/core/observability/tracing.js +2 -1
- package/src/core/pipeline-factory.js +0 -1
- package/src/core/plugin-registry.js +14 -14
- package/src/cost/cost-calculator.js +146 -0
- package/src/cost/cost-tracker.js +260 -0
- package/src/cost/token-budget.js +245 -0
- package/src/dag/core/execution-scheduler.js +2 -1
- package/src/dag/core/topology-validation.js +0 -3
- package/src/dag/dag-engine.js +3 -5
- package/src/dag/dag-node.js +0 -1
- package/src/dag/enhanced-dag-engine.js +171 -99
- package/src/dashboard/dashboard-generator.js +642 -0
- package/src/dashboard/metrics-aggregator.js +409 -0
- package/src/debug/execution-tracer.js +210 -0
- package/src/debug/trace-inspector.js +281 -0
- package/src/dev/dev-server.js +5 -5
- package/src/dev/hot-reload.js +3 -2
- package/src/dx/integration-templates.js +0 -3
- package/src/ecosystem/plugin-analytics-dashboard.js +2 -2
- package/src/ecosystem/plugin-hub.js +2 -2
- package/src/enterprise/audit-logging.js +2 -2
- package/src/enterprise/sso-integration.js +52 -32
- package/src/evaluate/context-metrics.js +127 -0
- package/src/evaluate/evaluator.js +4 -2
- package/src/evaluate/faithfulness.js +117 -0
- package/src/evaluate/groundedness.js +60 -0
- package/src/evaluate/pipeline-evaluator.js +156 -0
- package/src/evaluate/relevance.js +129 -0
- package/src/evaluate/scoring.js +27 -0
- package/src/graph/entity-extractor.js +183 -0
- package/src/graph/graph-index.js +113 -0
- package/src/graph/graph-retriever.js +244 -0
- package/src/graph/knowledge-graph.js +256 -0
- package/src/guardrails/guardrails-pipeline.js +136 -0
- package/src/guardrails/post-generation-guard.js +170 -0
- package/src/guardrails/pre-retrieval-guard.js +161 -0
- package/src/guardrails/retrieval-guard.js +126 -0
- package/src/index.js +255 -9
- package/src/loader/html-loader.js +4 -15
- package/src/loader/markdown-loader.js +4 -15
- package/src/mcp/mcp-server.js +199 -0
- package/src/mcp/mcp-tool-builder.js +121 -0
- package/src/observability/audit-logger.js +0 -2
- package/src/observability/event-logger.js +2 -1
- package/src/observability/security-monitor.js +0 -2
- package/src/observability/telemetry.js +0 -2
- package/src/reranker/cascade-reranker.js +79 -0
- package/src/reranker/embedding-reranker.js +124 -0
- package/src/reranker/llm-reranker.js +6 -3
- package/src/reranker/scoring-reranker.js +123 -0
- package/src/retrieval/bm25-search.js +157 -0
- package/src/retrieval/hybrid-retriever.js +111 -0
- package/src/retrieval/rank-fusion.js +62 -0
- package/src/security/jwt-config.js +4 -3
- package/src/security/jwt-validator.js +2 -1
- package/src/security/plugin-sandbox.js +0 -3
- package/src/security/plugin-signature-verifier.js +4 -6
- package/src/security/validator.js +0 -2
- package/src/streaming/embedding-stream.js +197 -0
- package/src/streaming/sse-adapter.js +168 -0
- package/src/streaming/stream-router.js +210 -0
- package/src/streaming/streaming-embedder.js +231 -0
- package/src/streaming/websocket-adapter.js +171 -0
- package/src/templates/scaffold.js +147 -0
- package/src/templates/template-registry.js +272 -0
- package/src/utils/contract-validator.js +2 -1
- package/src/utils/input-sanitizer.js +80 -99
- package/src/utils/performance-monitor.js +0 -3
- package/src/utils/plugin-scaffolder.js +9 -16
- package/src/utils/rate-limiter.js +0 -1
- package/src/utils/retry-policy.js +0 -3
- package/src/utils/safe-expression-evaluator.js +0 -3
- package/src/utils/secure-logger-config.js +2 -4
- package/src/utils/secure-logging.js +0 -3
- package/src/utils/structured-logger.js +0 -3
- package/src/ai/index-fixed.js +0 -509
- package/src/ai/module-template.js +0 -37
- package/src/ci/workflow-analyzer.js +0 -71
- package/src/cli/plugin-marketplace-commands.js +0 -869
- package/src/dev/README.md +0 -126
- package/src/dev/examples/hot-reload-usage.js +0 -355
- package/src/mocks/openai-embedder.js +0 -119
- package/src/mocks/openai-llm.js +0 -77
- package/src/mocks/pdf-loader.js +0 -21
- package/src/mocks/pinecone-retriever.js +0 -106
- package/src/scripts/script-utilities.js +0 -36
- package/src/utils/ci/diagnostic-reporter.js +0 -57
- package/src/utils/examples/error-formatter-usage.js +0 -351
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { EventEmitter } = require('events');
|
|
4
|
+
const { PreRetrievalGuard } = require('./pre-retrieval-guard');
|
|
5
|
+
const { RetrievalGuard } = require('./retrieval-guard');
|
|
6
|
+
const { PostGenerationGuard } = require('./post-generation-guard');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Default configuration for the GuardrailsPipeline.
|
|
10
|
+
* @type {object}
|
|
11
|
+
*/
|
|
12
|
+
const DEFAULT_CONFIG = {
|
|
13
|
+
preRetrieval: {}, // PreRetrievalGuard options, or false to disable
|
|
14
|
+
retrieval: {}, // RetrievalGuard options, or false to disable
|
|
15
|
+
postGeneration: {}, // PostGenerationGuard options, or false to disable
|
|
16
|
+
strict: false, // If true, block on any guard failure; if false, warn and continue
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Pipeline wrapper that applies all three guardrail layers (pre-retrieval,
|
|
21
|
+
* retrieval, and post-generation) around an existing RAG pipeline. In strict
|
|
22
|
+
* mode, any guard failure aborts the pipeline; otherwise issues are collected
|
|
23
|
+
* as warnings and execution continues.
|
|
24
|
+
*
|
|
25
|
+
* @extends EventEmitter
|
|
26
|
+
*/
|
|
27
|
+
class GuardrailsPipeline extends EventEmitter {
|
|
28
|
+
/**
|
|
29
|
+
* @param {object} pipeline - The underlying RAG pipeline (must have a .run() method)
|
|
30
|
+
* @param {object} [options] - Override default configuration
|
|
31
|
+
* @param {object|false} [options.preRetrieval={}] - PreRetrievalGuard options, or false to disable
|
|
32
|
+
* @param {object|false} [options.retrieval={}] - RetrievalGuard options, or false to disable
|
|
33
|
+
* @param {object|false} [options.postGeneration={}] - PostGenerationGuard options, or false to disable
|
|
34
|
+
* @param {boolean} [options.strict=false] - Whether to block on any guard failure
|
|
35
|
+
*/
|
|
36
|
+
constructor(pipeline, options = {}) {
|
|
37
|
+
super();
|
|
38
|
+
|
|
39
|
+
if (!pipeline || typeof pipeline.run !== 'function') {
|
|
40
|
+
throw new Error(
|
|
41
|
+
'GuardrailsPipeline requires a pipeline with a .run() method',
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this.pipeline = pipeline;
|
|
46
|
+
this.config = { ...DEFAULT_CONFIG, ...options };
|
|
47
|
+
|
|
48
|
+
this.preGuard =
|
|
49
|
+
this.config.preRetrieval !== false
|
|
50
|
+
? new PreRetrievalGuard(this.config.preRetrieval || {})
|
|
51
|
+
: null;
|
|
52
|
+
this.retrievalGuard =
|
|
53
|
+
this.config.retrieval !== false
|
|
54
|
+
? new RetrievalGuard(this.config.retrieval || {})
|
|
55
|
+
: null;
|
|
56
|
+
this.postGuard =
|
|
57
|
+
this.config.postGeneration !== false
|
|
58
|
+
? new PostGenerationGuard(this.config.postGeneration || {})
|
|
59
|
+
: null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Run the full guarded pipeline.
|
|
64
|
+
*
|
|
65
|
+
* @param {object} input - Pipeline input
|
|
66
|
+
* @param {string} input.query - The user query
|
|
67
|
+
* @param {Float32Array} [input.queryVector] - Optional precomputed query vector
|
|
68
|
+
* @param {object} [input.options] - Additional pipeline options
|
|
69
|
+
* @param {object} [input.context] - Request context (e.g. userPermissions for ACL)
|
|
70
|
+
* @returns {Promise<object>} Pipeline result with guardrails metadata attached
|
|
71
|
+
*/
|
|
72
|
+
async run(input) {
|
|
73
|
+
const guardrails = {
|
|
74
|
+
preRetrieval: null,
|
|
75
|
+
retrieval: null,
|
|
76
|
+
postGeneration: null,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// 1. Pre-retrieval guard
|
|
80
|
+
let query = input.query;
|
|
81
|
+
if (this.preGuard) {
|
|
82
|
+
guardrails.preRetrieval = this.preGuard.check(query);
|
|
83
|
+
this.emit('preRetrieval', guardrails.preRetrieval);
|
|
84
|
+
|
|
85
|
+
if (!guardrails.preRetrieval.safe && this.config.strict) {
|
|
86
|
+
return {
|
|
87
|
+
success: false,
|
|
88
|
+
error: 'Pre-retrieval guard blocked query',
|
|
89
|
+
guardrails,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
query = guardrails.preRetrieval.sanitizedQuery;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 2. Run the underlying pipeline with (possibly sanitized) query
|
|
96
|
+
const result = await this.pipeline.run({
|
|
97
|
+
...input,
|
|
98
|
+
query,
|
|
99
|
+
options: { ...input.options, citations: true },
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// 3. Retrieval guard on results
|
|
103
|
+
if (this.retrievalGuard && result.results) {
|
|
104
|
+
guardrails.retrieval = this.retrievalGuard.filter(
|
|
105
|
+
result.results,
|
|
106
|
+
input.context,
|
|
107
|
+
);
|
|
108
|
+
this.emit('retrieval', guardrails.retrieval);
|
|
109
|
+
result.results = guardrails.retrieval.results;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 4. Post-generation guard on answer
|
|
113
|
+
if (this.postGuard && result.answer) {
|
|
114
|
+
guardrails.postGeneration = this.postGuard.check(
|
|
115
|
+
result.answer,
|
|
116
|
+
result.results,
|
|
117
|
+
);
|
|
118
|
+
this.emit('postGeneration', guardrails.postGeneration);
|
|
119
|
+
|
|
120
|
+
if (!guardrails.postGeneration.safe && this.config.strict) {
|
|
121
|
+
return {
|
|
122
|
+
...result,
|
|
123
|
+
success: false,
|
|
124
|
+
error: 'Post-generation guard flagged output',
|
|
125
|
+
guardrails,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
result.answer = guardrails.postGeneration.sanitizedOutput;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
result.guardrails = guardrails;
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
module.exports = { GuardrailsPipeline, DEFAULT_CONFIG };
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { EventEmitter } = require('events');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Default configuration for the PostGenerationGuard.
|
|
7
|
+
* @type {object}
|
|
8
|
+
*/
|
|
9
|
+
const DEFAULT_CONFIG = {
|
|
10
|
+
enablePIIDetection: true,
|
|
11
|
+
enableGroundednessCheck: true,
|
|
12
|
+
groundednessThreshold: 0.3,
|
|
13
|
+
maxResponseLength: 50000,
|
|
14
|
+
minResponseLength: 0,
|
|
15
|
+
piiPatterns: {
|
|
16
|
+
email: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
|
|
17
|
+
phone: /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/g,
|
|
18
|
+
ssn: /\b\d{3}-\d{2}-\d{4}\b/g,
|
|
19
|
+
creditCard: /\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b/g,
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Post-generation guard that checks LLM output for PII leakage,
|
|
25
|
+
* groundedness against source documents, and length constraints.
|
|
26
|
+
* Sanitizes output by redacting detected PII.
|
|
27
|
+
*
|
|
28
|
+
* @extends EventEmitter
|
|
29
|
+
*/
|
|
30
|
+
class PostGenerationGuard extends EventEmitter {
|
|
31
|
+
/**
|
|
32
|
+
* @param {object} [options] - Override default configuration
|
|
33
|
+
* @param {boolean} [options.enablePIIDetection=true] - Whether to detect PII in output
|
|
34
|
+
* @param {boolean} [options.enableGroundednessCheck=true] - Whether to check groundedness
|
|
35
|
+
* @param {number} [options.groundednessThreshold=0.3] - Minimum groundedness score
|
|
36
|
+
* @param {number} [options.maxResponseLength=50000] - Maximum allowed response length
|
|
37
|
+
* @param {number} [options.minResponseLength=0] - Minimum response length
|
|
38
|
+
* @param {object} [options.piiPatterns] - Custom PII regex patterns (merged with defaults)
|
|
39
|
+
*/
|
|
40
|
+
constructor(options = {}) {
|
|
41
|
+
super();
|
|
42
|
+
this.config = { ...DEFAULT_CONFIG, ...options };
|
|
43
|
+
if (options.piiPatterns) {
|
|
44
|
+
this.config.piiPatterns = {
|
|
45
|
+
...DEFAULT_CONFIG.piiPatterns,
|
|
46
|
+
...options.piiPatterns,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Check generated output for safety issues, PII, and groundedness.
|
|
53
|
+
*
|
|
54
|
+
* @param {string} output - The LLM-generated output text
|
|
55
|
+
* @param {Array<object>} [retrievedDocs=[]] - Source documents for groundedness checking
|
|
56
|
+
* @returns {{
|
|
57
|
+
* safe: boolean,
|
|
58
|
+
* issues: string[],
|
|
59
|
+
* sanitizedOutput: string,
|
|
60
|
+
* groundednessScore?: number
|
|
61
|
+
* }}
|
|
62
|
+
* @fires PostGenerationGuard#checked
|
|
63
|
+
*/
|
|
64
|
+
check(output, retrievedDocs = []) {
|
|
65
|
+
const issues = [];
|
|
66
|
+
|
|
67
|
+
// 1. Validate output is a string
|
|
68
|
+
if (output == null || typeof output !== 'string') {
|
|
69
|
+
issues.push('Output must be a string');
|
|
70
|
+
const result = { safe: false, issues, sanitizedOutput: '' };
|
|
71
|
+
this.emit('checked', result);
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 2. Check length constraints
|
|
76
|
+
if (
|
|
77
|
+
this.config.maxResponseLength > 0 &&
|
|
78
|
+
output.length > this.config.maxResponseLength
|
|
79
|
+
) {
|
|
80
|
+
issues.push(
|
|
81
|
+
`Output exceeds maximum length of ${this.config.maxResponseLength} characters`,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
if (
|
|
85
|
+
this.config.minResponseLength > 0 &&
|
|
86
|
+
output.length < this.config.minResponseLength
|
|
87
|
+
) {
|
|
88
|
+
issues.push(
|
|
89
|
+
`Output is shorter than minimum length of ${this.config.minResponseLength} characters`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 3. PII detection
|
|
94
|
+
const piiMatches = {};
|
|
95
|
+
if (this.config.enablePIIDetection) {
|
|
96
|
+
for (const [name, pattern] of Object.entries(this.config.piiPatterns)) {
|
|
97
|
+
// Reset lastIndex for global regex patterns
|
|
98
|
+
pattern.lastIndex = 0;
|
|
99
|
+
const matches = output.match(pattern);
|
|
100
|
+
if (matches && matches.length > 0) {
|
|
101
|
+
piiMatches[name] = matches;
|
|
102
|
+
issues.push(
|
|
103
|
+
`PII detected (${name}): ${matches.length} occurrence(s)`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 4. Groundedness check
|
|
110
|
+
let groundednessScore;
|
|
111
|
+
if (
|
|
112
|
+
this.config.enableGroundednessCheck &&
|
|
113
|
+
Array.isArray(retrievedDocs) &&
|
|
114
|
+
retrievedDocs.length > 0
|
|
115
|
+
) {
|
|
116
|
+
try {
|
|
117
|
+
// Lazy-require CitationTracker to avoid circular dependencies
|
|
118
|
+
const { CitationTracker } = require('../citation/citation-tracker');
|
|
119
|
+
const tracker = new CitationTracker();
|
|
120
|
+
const tracking = tracker.track(output, retrievedDocs);
|
|
121
|
+
groundednessScore = tracking.groundednessScore;
|
|
122
|
+
|
|
123
|
+
if (groundednessScore < this.config.groundednessThreshold) {
|
|
124
|
+
issues.push(
|
|
125
|
+
`Groundedness score (${groundednessScore.toFixed(2)}) is below threshold (${this.config.groundednessThreshold})`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
} catch (err) {
|
|
129
|
+
// If citation tracking fails, log as issue but don't block
|
|
130
|
+
issues.push(`Groundedness check failed: ${err.message}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 5. Sanitize: replace PII matches with [REDACTED]
|
|
135
|
+
let sanitizedOutput = output;
|
|
136
|
+
if (Object.keys(piiMatches).length > 0) {
|
|
137
|
+
for (const [, matches] of Object.entries(piiMatches)) {
|
|
138
|
+
for (const match of matches) {
|
|
139
|
+
sanitizedOutput = sanitizedOutput.split(match).join('[REDACTED]');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Truncate if over max length
|
|
145
|
+
if (
|
|
146
|
+
this.config.maxResponseLength > 0 &&
|
|
147
|
+
sanitizedOutput.length > this.config.maxResponseLength
|
|
148
|
+
) {
|
|
149
|
+
sanitizedOutput = sanitizedOutput.substring(
|
|
150
|
+
0,
|
|
151
|
+
this.config.maxResponseLength,
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const result = {
|
|
156
|
+
safe: issues.length === 0,
|
|
157
|
+
issues,
|
|
158
|
+
sanitizedOutput,
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
if (groundednessScore !== undefined) {
|
|
162
|
+
result.groundednessScore = groundednessScore;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
this.emit('checked', result);
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
module.exports = { PostGenerationGuard, DEFAULT_CONFIG };
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { EventEmitter } = require('events');
|
|
4
|
+
const { validateInput } = require('../security/validator');
|
|
5
|
+
const { tokenize } = require('../evaluate/scoring');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Default configuration for the PreRetrievalGuard.
|
|
9
|
+
* @type {object}
|
|
10
|
+
*/
|
|
11
|
+
const DEFAULT_CONFIG = {
|
|
12
|
+
maxQueryLength: 10000,
|
|
13
|
+
enableInjectionDetection: true,
|
|
14
|
+
enableTopicFiltering: false,
|
|
15
|
+
blockedTopics: [],
|
|
16
|
+
allowedTopics: [],
|
|
17
|
+
injectionPatterns: [
|
|
18
|
+
/ignore\s+(all\s+)?previous\s+instructions/i,
|
|
19
|
+
/you\s+are\s+now/i,
|
|
20
|
+
/forget\s+everything/i,
|
|
21
|
+
/act\s+as\s+(a|an)?\s*/i,
|
|
22
|
+
/pretend\s+(you\s+are|to\s+be)/i,
|
|
23
|
+
/system\s*:/i,
|
|
24
|
+
/\bDAN\b/,
|
|
25
|
+
/bypass\s+(all\s+)?restrictions/i,
|
|
26
|
+
/override\s+(safety|content|system)/i,
|
|
27
|
+
/jailbreak/i,
|
|
28
|
+
],
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Pre-retrieval guard that validates and sanitizes user queries before
|
|
33
|
+
* they reach the retrieval layer. Checks for injection attacks, topic
|
|
34
|
+
* violations, and input length constraints.
|
|
35
|
+
*
|
|
36
|
+
* @extends EventEmitter
|
|
37
|
+
*/
|
|
38
|
+
class PreRetrievalGuard extends EventEmitter {
|
|
39
|
+
/**
|
|
40
|
+
* @param {object} [options] - Override default configuration
|
|
41
|
+
* @param {number} [options.maxQueryLength=10000] - Maximum allowed query length
|
|
42
|
+
* @param {boolean} [options.enableInjectionDetection=true] - Whether to check for prompt injection
|
|
43
|
+
* @param {boolean} [options.enableTopicFiltering=false] - Whether to enforce topic constraints
|
|
44
|
+
* @param {string[]} [options.blockedTopics=[]] - Topics to reject
|
|
45
|
+
* @param {string[]} [options.allowedTopics=[]] - If non-empty, only these topics are permitted
|
|
46
|
+
* @param {RegExp[]} [options.injectionPatterns] - Additional injection patterns (merged with defaults)
|
|
47
|
+
*/
|
|
48
|
+
constructor(options = {}) {
|
|
49
|
+
super();
|
|
50
|
+
this.config = { ...DEFAULT_CONFIG, ...options };
|
|
51
|
+
if (options.injectionPatterns) {
|
|
52
|
+
this.config.injectionPatterns = [
|
|
53
|
+
...DEFAULT_CONFIG.injectionPatterns,
|
|
54
|
+
...options.injectionPatterns,
|
|
55
|
+
];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Check a query for safety issues, injection attempts, and topic violations.
|
|
61
|
+
*
|
|
62
|
+
* @param {string} query - The user query to validate
|
|
63
|
+
* @returns {{
|
|
64
|
+
* safe: boolean,
|
|
65
|
+
* issues: string[],
|
|
66
|
+
* sanitizedQuery: string
|
|
67
|
+
* }}
|
|
68
|
+
* @fires PreRetrievalGuard#checked
|
|
69
|
+
*/
|
|
70
|
+
check(query) {
|
|
71
|
+
const issues = [];
|
|
72
|
+
|
|
73
|
+
// 1. Validate query is a non-empty string
|
|
74
|
+
if (
|
|
75
|
+
query == null ||
|
|
76
|
+
typeof query !== 'string' ||
|
|
77
|
+
query.trim().length === 0
|
|
78
|
+
) {
|
|
79
|
+
issues.push('Query must be a non-empty string');
|
|
80
|
+
const result = { safe: false, issues, sanitizedQuery: '' };
|
|
81
|
+
this.emit('checked', result);
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 2. Run basic input validation (length + disallowed tokens)
|
|
86
|
+
const validation = validateInput(query, {
|
|
87
|
+
maxLength: this.config.maxQueryLength,
|
|
88
|
+
});
|
|
89
|
+
if (!validation.valid) {
|
|
90
|
+
for (const reason of validation.reasons) {
|
|
91
|
+
issues.push(reason);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let sanitizedQuery = validation.sanitized;
|
|
96
|
+
|
|
97
|
+
// 3. Injection detection
|
|
98
|
+
const matchedPatterns = [];
|
|
99
|
+
if (this.config.enableInjectionDetection) {
|
|
100
|
+
for (const pattern of this.config.injectionPatterns) {
|
|
101
|
+
if (pattern.test(sanitizedQuery)) {
|
|
102
|
+
matchedPatterns.push(pattern);
|
|
103
|
+
issues.push(`Potential prompt injection detected: ${pattern}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 4. Topic filtering
|
|
109
|
+
if (this.config.enableTopicFiltering) {
|
|
110
|
+
const tokens = tokenize(sanitizedQuery);
|
|
111
|
+
|
|
112
|
+
// Check blocked topics
|
|
113
|
+
if (this.config.blockedTopics.length > 0) {
|
|
114
|
+
for (const topic of this.config.blockedTopics) {
|
|
115
|
+
const topicLower = topic.toLowerCase();
|
|
116
|
+
if (tokens.includes(topicLower)) {
|
|
117
|
+
issues.push(`Query contains blocked topic: ${topic}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Check allowed topics (whitelist mode)
|
|
123
|
+
if (this.config.allowedTopics.length > 0) {
|
|
124
|
+
const allowedLower = this.config.allowedTopics.map((t) =>
|
|
125
|
+
t.toLowerCase(),
|
|
126
|
+
);
|
|
127
|
+
const hasAllowedTopic = tokens.some((token) =>
|
|
128
|
+
allowedLower.includes(token),
|
|
129
|
+
);
|
|
130
|
+
if (!hasAllowedTopic) {
|
|
131
|
+
issues.push('Query does not match any allowed topics');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 5. Sanitize: strip matched injection patterns from query
|
|
137
|
+
if (matchedPatterns.length > 0) {
|
|
138
|
+
for (const pattern of matchedPatterns) {
|
|
139
|
+
// Create a global version of the pattern for replacement
|
|
140
|
+
const flags = pattern.flags.includes('g')
|
|
141
|
+
? pattern.flags
|
|
142
|
+
: pattern.flags + 'g';
|
|
143
|
+
const globalPattern = new RegExp(pattern.source, flags);
|
|
144
|
+
sanitizedQuery = sanitizedQuery.replace(globalPattern, '').trim();
|
|
145
|
+
}
|
|
146
|
+
// Collapse multiple spaces left after stripping
|
|
147
|
+
sanitizedQuery = sanitizedQuery.replace(/\s{2,}/g, ' ').trim();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const result = {
|
|
151
|
+
safe: issues.length === 0,
|
|
152
|
+
issues,
|
|
153
|
+
sanitizedQuery,
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
this.emit('checked', result);
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = { PreRetrievalGuard, DEFAULT_CONFIG };
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { EventEmitter } = require('events');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Default configuration for the RetrievalGuard.
|
|
7
|
+
* @type {object}
|
|
8
|
+
*/
|
|
9
|
+
const DEFAULT_CONFIG = {
|
|
10
|
+
minRelevanceScore: 0,
|
|
11
|
+
maxAgeDays: 0, // 0 = disabled
|
|
12
|
+
enforceACL: false,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Retrieval guard that filters search results based on relevance score,
|
|
17
|
+
* document freshness, and access control lists. Applied between the
|
|
18
|
+
* retrieval and generation stages of the pipeline.
|
|
19
|
+
*
|
|
20
|
+
* @extends EventEmitter
|
|
21
|
+
*/
|
|
22
|
+
class RetrievalGuard extends EventEmitter {
|
|
23
|
+
/**
|
|
24
|
+
* @param {object} [options] - Override default configuration
|
|
25
|
+
* @param {number} [options.minRelevanceScore=0] - Minimum relevance score threshold
|
|
26
|
+
* @param {number} [options.maxAgeDays=0] - Maximum document age in days (0 = disabled)
|
|
27
|
+
* @param {boolean} [options.enforceACL=false] - Whether to enforce access control
|
|
28
|
+
*/
|
|
29
|
+
constructor(options = {}) {
|
|
30
|
+
super();
|
|
31
|
+
this.config = { ...DEFAULT_CONFIG, ...options };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Filter retrieval results based on configured guards.
|
|
36
|
+
*
|
|
37
|
+
* @param {Array<object>} results - Array of retrieval results, each with .score and optional .metadata
|
|
38
|
+
* @param {object} [context={}] - Request context
|
|
39
|
+
* @param {string[]} [context.userPermissions] - User's permission roles for ACL filtering
|
|
40
|
+
* @returns {{
|
|
41
|
+
* results: Array<object>,
|
|
42
|
+
* removed: Array<object>,
|
|
43
|
+
* metadata: { reasons: Object<string, number> }
|
|
44
|
+
* }}
|
|
45
|
+
* @fires RetrievalGuard#filtered
|
|
46
|
+
*/
|
|
47
|
+
filter(results, context = {}) {
|
|
48
|
+
if (!Array.isArray(results)) {
|
|
49
|
+
const output = { results: [], removed: [], metadata: { reasons: {} } };
|
|
50
|
+
this.emit('filtered', output);
|
|
51
|
+
return output;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const kept = [];
|
|
55
|
+
const removed = [];
|
|
56
|
+
const reasons = {};
|
|
57
|
+
|
|
58
|
+
const now = Date.now();
|
|
59
|
+
const maxAgeMs =
|
|
60
|
+
this.config.maxAgeDays > 0
|
|
61
|
+
? this.config.maxAgeDays * 24 * 60 * 60 * 1000
|
|
62
|
+
: 0;
|
|
63
|
+
|
|
64
|
+
for (const result of results) {
|
|
65
|
+
let dominated = false;
|
|
66
|
+
const doc = result.metadata || result.doc || result;
|
|
67
|
+
|
|
68
|
+
// 1. Relevance score filter
|
|
69
|
+
if (this.config.minRelevanceScore > 0) {
|
|
70
|
+
const score = result.score != null ? result.score : 0;
|
|
71
|
+
if (score < this.config.minRelevanceScore) {
|
|
72
|
+
dominated = true;
|
|
73
|
+
reasons.relevance = (reasons.relevance || 0) + 1;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 2. Freshness filter
|
|
78
|
+
if (!dominated && maxAgeMs > 0) {
|
|
79
|
+
const timestamp = doc.updatedAt || doc.timestamp;
|
|
80
|
+
if (timestamp) {
|
|
81
|
+
const docTime = new Date(timestamp).getTime();
|
|
82
|
+
if (!isNaN(docTime) && now - docTime > maxAgeMs) {
|
|
83
|
+
dominated = true;
|
|
84
|
+
reasons.freshness = (reasons.freshness || 0) + 1;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 3. ACL filter
|
|
90
|
+
if (!dominated && this.config.enforceACL && context.userPermissions) {
|
|
91
|
+
const requiredRoles = doc.requiredRole
|
|
92
|
+
? [].concat(doc.requiredRole)
|
|
93
|
+
: doc.acl
|
|
94
|
+
? [].concat(doc.acl)
|
|
95
|
+
: null;
|
|
96
|
+
|
|
97
|
+
if (requiredRoles && requiredRoles.length > 0) {
|
|
98
|
+
const hasAccess = requiredRoles.some((role) =>
|
|
99
|
+
context.userPermissions.includes(role),
|
|
100
|
+
);
|
|
101
|
+
if (!hasAccess) {
|
|
102
|
+
dominated = true;
|
|
103
|
+
reasons.acl = (reasons.acl || 0) + 1;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (dominated) {
|
|
109
|
+
removed.push(result);
|
|
110
|
+
} else {
|
|
111
|
+
kept.push(result);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const output = {
|
|
116
|
+
results: kept,
|
|
117
|
+
removed,
|
|
118
|
+
metadata: { reasons },
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
this.emit('filtered', { original: results, filtered: kept, removed });
|
|
122
|
+
return output;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
module.exports = { RetrievalGuard, DEFAULT_CONFIG };
|