@devilsdev/rag-pipeline-utils 2.3.0 → 2.4.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.
Files changed (160) hide show
  1. package/README.md +293 -361
  2. package/contracts/loader-contract.json +11 -6
  3. package/contracts/reranker-contract.json +18 -26
  4. package/dist/index.cjs +21190 -47
  5. package/dist/index.d.ts +298 -68
  6. package/dist/index.mjs +9 -76
  7. package/package.json +20 -8
  8. package/src/agentic/agentic-pipeline.js +235 -0
  9. package/src/agentic/iterative-retriever.js +213 -0
  10. package/src/agentic/query-planner.js +208 -0
  11. package/src/agentic/self-critique.js +102 -0
  12. package/src/ai/adaptive-retrieval.js +0 -3
  13. package/src/ai/federated-learning.js +0 -3
  14. package/src/ai/federation.js +1 -6
  15. package/src/ai/index.js +9 -20
  16. package/src/ai/model-training.js +0 -3
  17. package/src/ai/multimodal-processing.js +0 -3
  18. package/src/ai/multimodal.js +1 -6
  19. package/src/ai/orchestrator.js +1 -6
  20. package/src/ai/retrieval-engine.js +1 -6
  21. package/src/chunking/chunking-engine.js +142 -0
  22. package/src/chunking/strategies/fixed-size.js +54 -0
  23. package/src/chunking/strategies/recursive.js +127 -0
  24. package/src/chunking/strategies/semantic.js +128 -0
  25. package/src/chunking/strategies/sentence.js +32 -0
  26. package/src/chunking/strategies/structure-aware.js +138 -0
  27. package/src/citation/citation-tracker.js +161 -0
  28. package/src/citation/hallucination-detector.js +74 -0
  29. package/src/citation/source-mapper.js +95 -0
  30. package/src/cli/commands/ai-ml/adaptive-commands.js +178 -0
  31. package/src/cli/commands/ai-ml/federated-commands.js +207 -0
  32. package/src/cli/commands/ai-ml/index.js +17 -0
  33. package/src/cli/commands/ai-ml/multimodal-commands.js +187 -0
  34. package/src/cli/commands/ai-ml/shared.js +51 -0
  35. package/src/cli/commands/ai-ml/training-commands.js +228 -0
  36. package/src/cli/commands/ai-ml.js +176 -757
  37. package/src/cli/commands/plugin-hub/certification-commands.js +221 -0
  38. package/src/cli/commands/plugin-hub/event-handlers.js +64 -0
  39. package/src/cli/commands/plugin-hub/hub-commands.js +290 -0
  40. package/src/cli/commands/plugin-hub/review-commands.js +78 -0
  41. package/src/cli/commands/plugin-hub.js +106 -782
  42. package/src/cli/diagnostics/config-checker.js +81 -0
  43. package/src/cli/diagnostics/dependency-checker.js +69 -0
  44. package/src/cli/diagnostics/environment-checker.js +45 -0
  45. package/src/cli/diagnostics/performance-checker.js +51 -0
  46. package/src/cli/diagnostics/plugin-checker.js +106 -0
  47. package/src/cli/diagnostics/security-checker.js +61 -0
  48. package/src/cli/doctor-command.js +70 -369
  49. package/src/cli/enhanced-cli-commands.js +162 -706
  50. package/src/cli/handlers/config-handler.js +126 -0
  51. package/src/cli/handlers/diagnostics-handler.js +113 -0
  52. package/src/cli/handlers/ingest-handler.js +42 -0
  53. package/src/cli/handlers/init-handler.js +51 -0
  54. package/src/cli/handlers/query-handler.js +45 -0
  55. package/src/cli/handlers/run-handler.js +181 -0
  56. package/src/cli/interactive-wizard.js +99 -723
  57. package/src/cli/shared/cli-utils.js +89 -0
  58. package/src/cli/wizard/configuration.js +148 -0
  59. package/src/cli/wizard/observability.js +79 -0
  60. package/src/cli/wizard/performance.js +76 -0
  61. package/src/cli/wizard/plugin-selection.js +297 -0
  62. package/src/cli/wizard/preview-save.js +172 -0
  63. package/src/cli/wizard/project-setup.js +66 -0
  64. package/src/config/memory-optimization.js +3 -3
  65. package/src/connectors/anthropic-connector.js +179 -0
  66. package/src/connectors/base-connector.js +94 -0
  67. package/src/connectors/cohere-connector.js +143 -0
  68. package/src/connectors/local-embedder.js +171 -0
  69. package/src/connectors/memory-retriever.js +150 -0
  70. package/src/connectors/ollama-connector.js +205 -0
  71. package/src/connectors/openai-connector.js +173 -0
  72. package/src/core/create-pipeline.js +21 -1
  73. package/src/core/observability/tracing.js +2 -1
  74. package/src/core/pipeline-factory.js +0 -1
  75. package/src/core/plugin-registry.js +14 -14
  76. package/src/cost/cost-calculator.js +146 -0
  77. package/src/cost/cost-tracker.js +260 -0
  78. package/src/cost/token-budget.js +245 -0
  79. package/src/dag/core/execution-scheduler.js +2 -1
  80. package/src/dag/core/topology-validation.js +0 -3
  81. package/src/dag/dag-engine.js +3 -5
  82. package/src/dag/dag-node.js +0 -1
  83. package/src/dag/enhanced-dag-engine.js +171 -99
  84. package/src/dashboard/dashboard-generator.js +642 -0
  85. package/src/dashboard/metrics-aggregator.js +409 -0
  86. package/src/debug/execution-tracer.js +210 -0
  87. package/src/debug/trace-inspector.js +281 -0
  88. package/src/dev/dev-server.js +5 -5
  89. package/src/dev/hot-reload.js +3 -2
  90. package/src/dx/integration-templates.js +0 -3
  91. package/src/ecosystem/plugin-analytics-dashboard.js +2 -2
  92. package/src/ecosystem/plugin-hub.js +2 -2
  93. package/src/enterprise/audit-logging.js +2 -2
  94. package/src/enterprise/sso-integration.js +52 -32
  95. package/src/evaluate/context-metrics.js +127 -0
  96. package/src/evaluate/evaluator.js +4 -2
  97. package/src/evaluate/faithfulness.js +117 -0
  98. package/src/evaluate/groundedness.js +60 -0
  99. package/src/evaluate/pipeline-evaluator.js +156 -0
  100. package/src/evaluate/relevance.js +129 -0
  101. package/src/evaluate/scoring.js +27 -0
  102. package/src/graph/entity-extractor.js +183 -0
  103. package/src/graph/graph-index.js +113 -0
  104. package/src/graph/graph-retriever.js +244 -0
  105. package/src/graph/knowledge-graph.js +256 -0
  106. package/src/guardrails/guardrails-pipeline.js +136 -0
  107. package/src/guardrails/post-generation-guard.js +170 -0
  108. package/src/guardrails/pre-retrieval-guard.js +161 -0
  109. package/src/guardrails/retrieval-guard.js +126 -0
  110. package/src/index.js +255 -9
  111. package/src/loader/html-loader.js +4 -15
  112. package/src/loader/markdown-loader.js +4 -15
  113. package/src/mcp/mcp-server.js +199 -0
  114. package/src/mcp/mcp-tool-builder.js +121 -0
  115. package/src/observability/audit-logger.js +0 -2
  116. package/src/observability/event-logger.js +2 -1
  117. package/src/observability/security-monitor.js +0 -2
  118. package/src/observability/telemetry.js +0 -2
  119. package/src/reranker/cascade-reranker.js +79 -0
  120. package/src/reranker/embedding-reranker.js +124 -0
  121. package/src/reranker/llm-reranker.js +6 -3
  122. package/src/reranker/scoring-reranker.js +123 -0
  123. package/src/retrieval/bm25-search.js +157 -0
  124. package/src/retrieval/hybrid-retriever.js +111 -0
  125. package/src/retrieval/rank-fusion.js +62 -0
  126. package/src/security/jwt-config.js +4 -3
  127. package/src/security/jwt-validator.js +2 -1
  128. package/src/security/plugin-sandbox.js +0 -3
  129. package/src/security/plugin-signature-verifier.js +4 -6
  130. package/src/security/validator.js +0 -2
  131. package/src/streaming/embedding-stream.js +197 -0
  132. package/src/streaming/sse-adapter.js +168 -0
  133. package/src/streaming/stream-router.js +210 -0
  134. package/src/streaming/streaming-embedder.js +231 -0
  135. package/src/streaming/websocket-adapter.js +171 -0
  136. package/src/templates/scaffold.js +147 -0
  137. package/src/templates/template-registry.js +272 -0
  138. package/src/utils/contract-validator.js +2 -1
  139. package/src/utils/input-sanitizer.js +80 -99
  140. package/src/utils/performance-monitor.js +0 -3
  141. package/src/utils/plugin-scaffolder.js +9 -16
  142. package/src/utils/rate-limiter.js +0 -1
  143. package/src/utils/retry-policy.js +0 -3
  144. package/src/utils/safe-expression-evaluator.js +0 -3
  145. package/src/utils/secure-logger-config.js +2 -4
  146. package/src/utils/secure-logging.js +0 -3
  147. package/src/utils/structured-logger.js +0 -3
  148. package/src/ai/index-fixed.js +0 -509
  149. package/src/ai/module-template.js +0 -37
  150. package/src/ci/workflow-analyzer.js +0 -71
  151. package/src/cli/plugin-marketplace-commands.js +0 -869
  152. package/src/dev/README.md +0 -126
  153. package/src/dev/examples/hot-reload-usage.js +0 -355
  154. package/src/mocks/openai-embedder.js +0 -119
  155. package/src/mocks/openai-llm.js +0 -77
  156. package/src/mocks/pdf-loader.js +0 -21
  157. package/src/mocks/pinecone-retriever.js +0 -106
  158. package/src/scripts/script-utilities.js +0 -36
  159. package/src/utils/ci/diagnostic-reporter.js +0 -57
  160. package/src/utils/examples/error-formatter-usage.js +0 -351
@@ -0,0 +1,281 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Trace Inspector
5
+ *
6
+ * Analyzes execution traces for bottlenecks and optimization opportunities.
7
+ * Provides summary reports, comparisons, and anomaly detection across traces.
8
+ */
9
+
10
+ const { EventEmitter } = require('events');
11
+
12
+ /**
13
+ * Trace inspector that analyzes execution traces produced by ExecutionTracer
14
+ * @extends EventEmitter
15
+ */
16
+ class TraceInspector extends EventEmitter {
17
+ /**
18
+ * Create a new TraceInspector
19
+ * @param {object} [options] - Inspector configuration
20
+ */
21
+ constructor(options = {}) {
22
+ super();
23
+ this.config = options;
24
+ }
25
+
26
+ /**
27
+ * Analyze a single trace for performance and quality insights
28
+ * @param {object} trace - Trace record from ExecutionTracer
29
+ * @returns {object} Analysis result with summary, breakdown, bottleneck, quality, and recommendations
30
+ */
31
+ analyze(trace) {
32
+ const totalDuration = trace.totalDurationMs || 0;
33
+ const steps = trace.steps || [];
34
+
35
+ // Time breakdown per step
36
+ const timeBreakdown = {};
37
+ for (const step of steps) {
38
+ timeBreakdown[step.step] = step.durationMs || 0;
39
+ }
40
+
41
+ const stepDurationSum = steps.reduce(
42
+ (sum, s) => sum + (s.durationMs || 0),
43
+ 0,
44
+ );
45
+ if (steps.length > 0) {
46
+ timeBreakdown['overhead'] = Math.max(0, totalDuration - stepDurationSum);
47
+ }
48
+
49
+ // Identify bottleneck
50
+ let bottleneck = null;
51
+ if (steps.length > 0) {
52
+ const slowest = steps.reduce((a, b) =>
53
+ (a.durationMs || 0) >= (b.durationMs || 0) ? a : b,
54
+ );
55
+ bottleneck = {
56
+ step: slowest.step,
57
+ durationMs: slowest.durationMs || 0,
58
+ };
59
+ }
60
+
61
+ // Result count
62
+ const resultCount = (trace.metadata && trace.metadata.resultCount) || 0;
63
+
64
+ // Quality metrics
65
+ const qualityMetrics = {};
66
+ const metadata = trace.metadata || {};
67
+
68
+ if (metadata.citations) {
69
+ const citations = metadata.citations;
70
+ if (citations.groundedness !== undefined) {
71
+ qualityMetrics.groundedness = citations.groundedness;
72
+ }
73
+ if (
74
+ Array.isArray(citations) &&
75
+ citations.length > 0 &&
76
+ citations[0].groundedness !== undefined
77
+ ) {
78
+ qualityMetrics.groundedness = citations[0].groundedness;
79
+ }
80
+ }
81
+
82
+ if (metadata.evaluation) {
83
+ const evaluation = metadata.evaluation;
84
+ if (evaluation.groundedness !== undefined) {
85
+ qualityMetrics.groundedness = evaluation.groundedness;
86
+ }
87
+ if (evaluation.faithfulness !== undefined) {
88
+ qualityMetrics.faithfulness = evaluation.faithfulness;
89
+ }
90
+ if (evaluation.relevance !== undefined) {
91
+ qualityMetrics.relevance = evaluation.relevance;
92
+ }
93
+ }
94
+
95
+ // Recommendations
96
+ const recommendations = [];
97
+
98
+ if (totalDuration > 5000) {
99
+ recommendations.push('Consider caching frequent queries');
100
+ }
101
+ if (resultCount === 0) {
102
+ recommendations.push(
103
+ 'No results retrieved - check retriever configuration',
104
+ );
105
+ }
106
+ if (
107
+ qualityMetrics.groundedness !== undefined &&
108
+ qualityMetrics.groundedness < 0.5
109
+ ) {
110
+ recommendations.push(
111
+ 'Low groundedness - consider increasing retrieval topK',
112
+ );
113
+ }
114
+ if (!metadata.hasCitations) {
115
+ recommendations.push('Enable citations for source tracking');
116
+ }
117
+ if (!metadata.hasEvaluation) {
118
+ recommendations.push('Enable evaluation for quality metrics');
119
+ }
120
+
121
+ const analysis = {
122
+ summary: {
123
+ totalDuration,
124
+ stepCount: steps.length,
125
+ resultCount,
126
+ },
127
+ timeBreakdown,
128
+ bottleneck,
129
+ qualityMetrics,
130
+ recommendations,
131
+ };
132
+
133
+ this.emit('analyzed', analysis);
134
+ return analysis;
135
+ }
136
+
137
+ /**
138
+ * Compare multiple traces for consistency and trends
139
+ * @param {Array<object>} traces - Array of trace records
140
+ * @returns {object} Comparison result with averages, ranges, and quality trend
141
+ */
142
+ compare(traces) {
143
+ if (!traces || traces.length === 0) {
144
+ return {
145
+ avgDuration: 0,
146
+ minDuration: 0,
147
+ maxDuration: 0,
148
+ avgResultCount: 0,
149
+ qualityTrend: {},
150
+ };
151
+ }
152
+
153
+ const durations = traces.map((t) => t.totalDurationMs || 0);
154
+ const resultCounts = traces.map(
155
+ (t) => (t.metadata && t.metadata.resultCount) || 0,
156
+ );
157
+
158
+ const sum = (arr) => arr.reduce((a, b) => a + b, 0);
159
+
160
+ const avgDuration = sum(durations) / durations.length;
161
+ const minDuration = Math.min(...durations);
162
+ const maxDuration = Math.max(...durations);
163
+ const avgResultCount = sum(resultCounts) / resultCounts.length;
164
+
165
+ // Quality trend: collect quality metrics across traces
166
+ const qualityTrend = {};
167
+ const metricAccumulators = {};
168
+
169
+ for (const trace of traces) {
170
+ const metadata = trace.metadata || {};
171
+ const evaluation = metadata.evaluation || {};
172
+ for (const key of ['groundedness', 'faithfulness', 'relevance']) {
173
+ if (evaluation[key] !== undefined) {
174
+ if (!metricAccumulators[key]) {
175
+ metricAccumulators[key] = [];
176
+ }
177
+ metricAccumulators[key].push(evaluation[key]);
178
+ }
179
+ }
180
+ }
181
+
182
+ for (const [key, values] of Object.entries(metricAccumulators)) {
183
+ qualityTrend[key] = sum(values) / values.length;
184
+ }
185
+
186
+ const comparison = {
187
+ avgDuration,
188
+ minDuration,
189
+ maxDuration,
190
+ avgResultCount,
191
+ qualityTrend,
192
+ };
193
+
194
+ this.emit('compared', comparison);
195
+ return comparison;
196
+ }
197
+
198
+ /**
199
+ * Find anomalous traces that deviate significantly from the norm
200
+ * @param {Array<object>} traces - Array of trace records
201
+ * @returns {Array<object>} Array of anomaly descriptors with traceId, anomalyType, details
202
+ */
203
+ findAnomalies(traces) {
204
+ if (!traces || traces.length === 0) {
205
+ return [];
206
+ }
207
+
208
+ const anomalies = [];
209
+
210
+ const durations = traces.map((t) => t.totalDurationMs || 0);
211
+ const avgDuration = durations.reduce((a, b) => a + b, 0) / durations.length;
212
+
213
+ const resultCounts = traces.map(
214
+ (t) => (t.metadata && t.metadata.resultCount) || 0,
215
+ );
216
+ const hasResults = resultCounts.some((c) => c > 0);
217
+
218
+ // Collect quality scores for average calculation
219
+ const qualityScores = {};
220
+ for (const trace of traces) {
221
+ const evaluation = (trace.metadata && trace.metadata.evaluation) || {};
222
+ for (const key of ['groundedness', 'faithfulness', 'relevance']) {
223
+ if (evaluation[key] !== undefined) {
224
+ if (!qualityScores[key]) {
225
+ qualityScores[key] = [];
226
+ }
227
+ qualityScores[key].push({
228
+ traceId: trace.id,
229
+ score: evaluation[key],
230
+ });
231
+ }
232
+ }
233
+ }
234
+
235
+ const qualityAverages = {};
236
+ for (const [key, entries] of Object.entries(qualityScores)) {
237
+ qualityAverages[key] =
238
+ entries.reduce((sum, e) => sum + e.score, 0) / entries.length;
239
+ }
240
+
241
+ for (const trace of traces) {
242
+ const duration = trace.totalDurationMs || 0;
243
+ const resultCount = (trace.metadata && trace.metadata.resultCount) || 0;
244
+
245
+ // Duration > 2x average
246
+ if (avgDuration > 0 && duration > 2 * avgDuration) {
247
+ anomalies.push({
248
+ traceId: trace.id,
249
+ anomalyType: 'slow_execution',
250
+ details: `Duration ${duration}ms is more than 2x the average ${Math.round(avgDuration)}ms`,
251
+ });
252
+ }
253
+
254
+ // Zero results when others have results
255
+ if (hasResults && resultCount === 0) {
256
+ anomalies.push({
257
+ traceId: trace.id,
258
+ anomalyType: 'no_results',
259
+ details: 'No results retrieved while other traces returned results',
260
+ });
261
+ }
262
+
263
+ // Quality score significantly lower than average (> 0.3 below)
264
+ const evaluation = (trace.metadata && trace.metadata.evaluation) || {};
265
+ for (const [key, avg] of Object.entries(qualityAverages)) {
266
+ if (evaluation[key] !== undefined && evaluation[key] < avg - 0.3) {
267
+ anomalies.push({
268
+ traceId: trace.id,
269
+ anomalyType: 'low_quality',
270
+ details: `${key} score ${evaluation[key].toFixed(2)} is significantly below average ${avg.toFixed(2)}`,
271
+ });
272
+ }
273
+ }
274
+ }
275
+
276
+ this.emit('anomaliesFound', anomalies);
277
+ return anomalies;
278
+ }
279
+ }
280
+
281
+ module.exports = { TraceInspector };
@@ -19,7 +19,7 @@
19
19
 
20
20
  const { EventEmitter } = require('events');
21
21
  const { createHotReloadManager } = require('./hot-reload');
22
- const logger = require('../utils/logger');
22
+ const { logger } = require('../utils/logger');
23
23
 
24
24
  /**
25
25
  * Development Server
@@ -290,17 +290,17 @@ ${this.options.watchPaths.map((p) => `║ - ${p.padEnd(56)}║`).join('\n')}
290
290
 
291
291
  switch (level) {
292
292
  case 'success':
293
- console.log(`${prefix} ✓ ${message}`);
293
+ logger.info(`${prefix} ${message}`);
294
294
  break;
295
295
  case 'error':
296
- console.error(`${prefix} ✗ ${message}`);
296
+ logger.error(`${prefix} ${message}`);
297
297
  break;
298
298
  case 'warn':
299
- console.warn(`${prefix} ⚠ ${message}`);
299
+ logger.warn(`${prefix} ${message}`);
300
300
  break;
301
301
  case 'info':
302
302
  default:
303
- console.log(`${prefix} ${message}`);
303
+ logger.info(`${prefix} ${message}`);
304
304
  break;
305
305
  }
306
306
  }
@@ -26,6 +26,7 @@ const {
26
26
  wrapError,
27
27
  ERROR_CODES,
28
28
  } = require('../utils/error-formatter');
29
+ const { logger } = require('../utils/logger');
29
30
 
30
31
  /**
31
32
  * Hot Reload Manager
@@ -701,9 +702,9 @@ class HotReloadManager extends EventEmitter {
701
702
  if (this.options.verbose || isError) {
702
703
  const prefix = '[HotReload]';
703
704
  if (isError) {
704
- console.error(`${prefix} ${message}`);
705
+ logger.error(`${prefix} ${message}`);
705
706
  } else {
706
- console.log(`${prefix} ${message}`);
707
+ logger.info(`${prefix} ${message}`);
707
708
  }
708
709
  }
709
710
  }
@@ -660,6 +660,3 @@ module.exports._config = _config;
660
660
  module.exports.generateIntegration = generateIntegration;
661
661
  module.exports.getTemplateStats = getTemplateStats;
662
662
  module.exports.addTemplate = addTemplate;
663
-
664
- // CJS+ESM interop pattern
665
- module.exports.default = module.exports;
@@ -9,6 +9,7 @@ const path = require('path');
9
9
  // eslint-disable-line global-require
10
10
  const { EventEmitter } = require('events');
11
11
  // eslint-disable-line global-require
12
+ const { logger } = require('../utils/logger');
12
13
 
13
14
  class PluginAnalyticsDashboard extends EventEmitter {
14
15
  constructor(_options = {}) {
@@ -749,8 +750,7 @@ class PluginAnalyticsDashboard extends EventEmitter {
749
750
  try {
750
751
  callback(data);
751
752
  } catch (error) {
752
- console.error('Error notifying subscriber:', error);
753
- // eslint-disable-line no-console
753
+ logger.error('Error notifying subscriber:', error);
754
754
  }
755
755
  });
756
756
  }
@@ -7,6 +7,7 @@ const fs = require('fs').promises; // eslint-disable-line global-require
7
7
  const path = require('path'); // eslint-disable-line global-require
8
8
  const crypto = require('crypto'); // eslint-disable-line global-require
9
9
  const { EventEmitter } = require('events'); // eslint-disable-line global-require
10
+ const { logger } = require('../utils/logger');
10
11
 
11
12
  function pickFetch(pref) {
12
13
  // Prefer a provided fetch if it is a function; otherwise fall back to globalThis.fetch if available.
@@ -709,8 +710,7 @@ class PluginAnalytics {
709
710
  } catch (error) {
710
711
  // Silence warnings during tests to avoid "Cannot log after tests are done"
711
712
  if (process.env.NODE_ENV !== 'test') {
712
- // eslint-disable-next-line no-console
713
- console.warn('Analytics flush failed:', error.message);
713
+ logger.warn('Analytics flush failed:', error.message);
714
714
  }
715
715
  }
716
716
  }
@@ -11,6 +11,7 @@ const crypto = require('crypto');
11
11
  // eslint-disable-line global-require
12
12
  const { EventEmitter } = require('events');
13
13
  // eslint-disable-line global-require
14
+ const { logger } = require('../utils/logger');
14
15
 
15
16
  class AuditLogger extends EventEmitter {
16
17
  constructor(_options = {}) {
@@ -916,8 +917,7 @@ class AuditLogger extends EventEmitter {
916
917
 
917
918
  try {
918
919
  // Mock webhook sending - would use actual HTTP client
919
- console.log(`Sending ${logs.length} audit logs to webhook`);
920
- // eslint-disable-line no-console
920
+ logger.info(`Sending ${logs.length} audit logs to webhook`);
921
921
  } catch (error) {
922
922
  this.emit('webhook_failed', { error: error.message, count: logs.length });
923
923
  }
@@ -15,7 +15,26 @@ class SSOManager extends EventEmitter {
15
15
  constructor(_options = {}) {
16
16
  super();
17
17
 
18
- this._config = {
18
+ this._config = this._buildConfig(_options);
19
+
20
+ this.sessions = new Map();
21
+ this.providers = new Map();
22
+ this.userCache = new Map();
23
+
24
+ this._initializeRateLimiter();
25
+ this._initializeJWTValidator(_options);
26
+ this._wireJWTEvents();
27
+ this._initializeProviders();
28
+ }
29
+
30
+ /**
31
+ * Build the full SSO configuration from user-supplied options.
32
+ * @param {Object} _options - User-supplied configuration options
33
+ * @returns {Object} Complete SSO configuration
34
+ * @private
35
+ */
36
+ _buildConfig(_options) {
37
+ return {
19
38
  providers: {
20
39
  saml: {
21
40
  enabled: false,
@@ -69,12 +88,13 @@ class SSOManager extends EventEmitter {
69
88
  },
70
89
  ..._options,
71
90
  };
91
+ }
72
92
 
73
- this.sessions = new Map();
74
- this.providers = new Map();
75
- this.userCache = new Map();
76
-
77
- // Initialize rate limiter for login attempts
93
+ /**
94
+ * Create and configure the rate limiter for login attempt throttling.
95
+ * @private
96
+ */
97
+ _initializeRateLimiter() {
78
98
  if (this._config.rateLimit.enabled) {
79
99
  this.rateLimiter = new RateLimiter({
80
100
  maxAttempts: this._config.rateLimit.maxLoginAttempts,
@@ -82,8 +102,14 @@ class SSOManager extends EventEmitter {
82
102
  blockDurationMs: this._config.rateLimit.blockDurationMs,
83
103
  });
84
104
  }
105
+ }
85
106
 
86
- // Initialize hardened JWT validator
107
+ /**
108
+ * Create and configure the hardened JWT validator for token operations.
109
+ * @param {Object} _options - Original user-supplied options for JWT settings
110
+ * @private
111
+ */
112
+ _initializeJWTValidator(_options) {
87
113
  this.jwtValidator = new JWTValidator(
88
114
  createJWTConfig({
89
115
  secret: this._config.security.jwtSecret,
@@ -99,29 +125,26 @@ class SSOManager extends EventEmitter {
99
125
  enableAuditLog: _options.jwtEnableAuditLog !== false,
100
126
  }),
101
127
  );
128
+ }
102
129
 
103
- // Subscribe to JWT validator events for audit logging
104
- this.jwtValidator.on('token_generated', (event) => {
105
- this.emit('jwt_token_generated', event);
106
- });
107
-
108
- this.jwtValidator.on('token_validated', (event) => {
109
- this.emit('jwt_token_validated', event);
110
- });
111
-
112
- this.jwtValidator.on('validation_failed', (event) => {
113
- this.emit('jwt_validation_failed', event);
114
- });
115
-
116
- this.jwtValidator.on('algorithm_mismatch', (event) => {
117
- this.emit('jwt_algorithm_mismatch', event);
118
- });
119
-
120
- this.jwtValidator.on('replay_detected', (event) => {
121
- this.emit('jwt_replay_detected', event);
122
- });
123
-
124
- this._initializeProviders();
130
+ /**
131
+ * Forward JWT validator events to the SSOManager for audit logging.
132
+ * @private
133
+ */
134
+ _wireJWTEvents() {
135
+ const eventMappings = [
136
+ ['token_generated', 'jwt_token_generated'],
137
+ ['token_validated', 'jwt_token_validated'],
138
+ ['validation_failed', 'jwt_validation_failed'],
139
+ ['algorithm_mismatch', 'jwt_algorithm_mismatch'],
140
+ ['replay_detected', 'jwt_replay_detected'],
141
+ ];
142
+
143
+ for (const [sourceEvent, targetEvent] of eventMappings) {
144
+ this.jwtValidator.on(sourceEvent, (event) => {
145
+ this.emit(targetEvent, event);
146
+ });
147
+ }
125
148
  }
126
149
 
127
150
  /**
@@ -1067,6 +1090,3 @@ module.exports = {
1067
1090
  ActiveDirectoryProvider,
1068
1091
  OIDCProvider,
1069
1092
  };
1070
-
1071
- // Ensure module.exports is properly defined
1072
- module.exports.default = module.exports;
@@ -0,0 +1,127 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ tokenize,
5
+ splitSentences,
6
+ computeJaccardSimilarity,
7
+ } = require('./scoring');
8
+
9
+ /**
10
+ * Compute context precision: the fraction of retrieved documents that are relevant to the query.
11
+ *
12
+ * @param {string} query - The user query
13
+ * @param {Array<{content: string}>} retrievedDocs - Array of retrieved documents
14
+ * @param {object} [options={}] - Configuration options
15
+ * @param {number} [options.threshold=0.2] - Minimum Jaccard similarity for a doc to be considered relevant
16
+ * @returns {{ score: number, relevantDocs: number, totalDocs: number, perDocScores: Array<{ index: number, score: number, isRelevant: boolean }> }}
17
+ */
18
+ function computeContextPrecision(query, retrievedDocs, options = {}) {
19
+ const { threshold = 0.2 } = options;
20
+
21
+ if (!query || typeof query !== 'string' || query.trim().length === 0) {
22
+ return { score: 0, relevantDocs: 0, totalDocs: 0, perDocScores: [] };
23
+ }
24
+
25
+ if (!Array.isArray(retrievedDocs) || retrievedDocs.length === 0) {
26
+ return { score: 0, relevantDocs: 0, totalDocs: 0, perDocScores: [] };
27
+ }
28
+
29
+ const queryTokens = tokenize(query);
30
+ const perDocScores = [];
31
+
32
+ for (let i = 0; i < retrievedDocs.length; i++) {
33
+ const doc = retrievedDocs[i];
34
+ const docContent = doc && (doc.content || doc.text || '');
35
+
36
+ if (!docContent) {
37
+ perDocScores.push({ index: i, score: 0, isRelevant: false });
38
+ continue;
39
+ }
40
+
41
+ const docTokens = tokenize(docContent);
42
+ const score = computeJaccardSimilarity(queryTokens, docTokens);
43
+ perDocScores.push({ index: i, score, isRelevant: score >= threshold });
44
+ }
45
+
46
+ const relevantDocs = perDocScores.filter((d) => d.isRelevant).length;
47
+ const totalDocs = perDocScores.length;
48
+ const score = totalDocs > 0 ? relevantDocs / totalDocs : 0;
49
+
50
+ return { score, relevantDocs, totalDocs, perDocScores };
51
+ }
52
+
53
+ /**
54
+ * Compute context recall: the fraction of answer claims that are supported by retrieved documents.
55
+ *
56
+ * @param {string} answer - The generated answer text
57
+ * @param {Array<{content: string}>} retrievedDocs - Array of retrieved documents
58
+ * @param {object} [options={}] - Configuration options
59
+ * @param {number} [options.threshold=0.3] - Minimum Jaccard similarity for a claim to be considered supported
60
+ * @returns {{ score: number, supportedClaims: number, totalClaims: number, details: Array<{ claim: string, score: number, isSupported: boolean, bestDocIndex: number }> }}
61
+ */
62
+ function computeContextRecall(answer, retrievedDocs, options = {}) {
63
+ const { threshold = 0.3 } = options;
64
+
65
+ if (!answer || typeof answer !== 'string' || answer.trim().length === 0) {
66
+ return { score: 0, supportedClaims: 0, totalClaims: 0, details: [] };
67
+ }
68
+
69
+ const claims = splitSentences(answer);
70
+
71
+ if (claims.length === 0) {
72
+ return { score: 0, supportedClaims: 0, totalClaims: 0, details: [] };
73
+ }
74
+
75
+ if (!Array.isArray(retrievedDocs) || retrievedDocs.length === 0) {
76
+ const details = claims.map((claim) => ({
77
+ claim,
78
+ score: 0,
79
+ isSupported: false,
80
+ bestDocIndex: -1,
81
+ }));
82
+ return {
83
+ score: 0,
84
+ supportedClaims: 0,
85
+ totalClaims: claims.length,
86
+ details,
87
+ };
88
+ }
89
+
90
+ const details = claims.map((claim) => {
91
+ const claimTokens = tokenize(claim);
92
+ let bestScore = 0;
93
+ let bestDocIndex = -1;
94
+
95
+ for (let i = 0; i < retrievedDocs.length; i++) {
96
+ const doc = retrievedDocs[i];
97
+ const docContent = doc && (doc.content || doc.text || '');
98
+
99
+ if (!docContent) {
100
+ continue;
101
+ }
102
+
103
+ const docTokens = tokenize(docContent);
104
+ const similarity = computeJaccardSimilarity(claimTokens, docTokens);
105
+
106
+ if (similarity > bestScore) {
107
+ bestScore = similarity;
108
+ bestDocIndex = i;
109
+ }
110
+ }
111
+
112
+ return {
113
+ claim,
114
+ score: bestScore,
115
+ isSupported: bestScore >= threshold,
116
+ bestDocIndex,
117
+ };
118
+ });
119
+
120
+ const supportedClaims = details.filter((d) => d.isSupported).length;
121
+ const totalClaims = details.length;
122
+ const score = totalClaims > 0 ? supportedClaims / totalClaims : 0;
123
+
124
+ return { score, supportedClaims, totalClaims, details };
125
+ }
126
+
127
+ module.exports = { computeContextPrecision, computeContextRecall };
@@ -23,8 +23,10 @@ async function evaluateRagDataset(_datasetPath, _config) {
23
23
  for (const { prompt, expected } of cases) {
24
24
  try {
25
25
  logger.info({ prompt }, 'Evaluating case');
26
- const actual = await pipeline.query(prompt);
27
- const success = normalizeText(actual) === normalizeText(expected);
26
+ const result = await pipeline.run({ query: prompt });
27
+ const actual = result.results || '';
28
+ const success =
29
+ result.success && normalizeText(actual) === normalizeText(expected);
28
30
  const scores = scoreAnswer(actual, expected);
29
31
  results.push({ prompt, expected, actual, success, scores });
30
32
  } catch (err) {