@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.
Files changed (160) hide show
  1. package/README.md +94 -480
  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 +37 -11
  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,409 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Metrics Aggregator
5
+ *
6
+ * Collects and aggregates metrics from various RAG pipeline sources
7
+ * into a dashboard-ready format. Supports rolling windows, percentile
8
+ * calculations, and time-series data for charting.
9
+ */
10
+
11
+ const { EventEmitter } = require('events');
12
+
13
+ /**
14
+ * @typedef {Object} MetricsAggregatorConfig
15
+ * @property {number} windowSize - Rolling window in seconds (default: 60)
16
+ * @property {number} sampleInterval - Milliseconds between samples (default: 1000)
17
+ * @property {number} maxHistory - Maximum data points to keep (default: 3600)
18
+ */
19
+ const DEFAULT_CONFIG = {
20
+ windowSize: 60,
21
+ sampleInterval: 1000,
22
+ maxHistory: 3600,
23
+ };
24
+
25
+ /**
26
+ * @typedef {Object} QueryData
27
+ * @property {number} duration - Duration in milliseconds
28
+ * @property {number} [tokens] - Token count
29
+ * @property {number} [cost] - Cost in USD
30
+ * @property {boolean} [success] - Whether the operation succeeded
31
+ * @property {number} [timestamp] - Unix timestamp (defaults to Date.now())
32
+ */
33
+
34
+ /**
35
+ * @typedef {Object} ErrorData
36
+ * @property {string} message - Error message
37
+ * @property {string} [code] - Error code
38
+ * @property {string} [source] - Error source component
39
+ * @property {number} [timestamp] - Unix timestamp
40
+ */
41
+
42
+ /**
43
+ * Collects and aggregates pipeline metrics into dashboard-ready snapshots.
44
+ * @extends EventEmitter
45
+ */
46
+ class MetricsAggregator extends EventEmitter {
47
+ /**
48
+ * @param {Partial<MetricsAggregatorConfig>} options
49
+ */
50
+ constructor(options = {}) {
51
+ super();
52
+ this.config = { ...DEFAULT_CONFIG, ...options };
53
+ this.startTime = Date.now();
54
+
55
+ /** @type {{ queries: number, embeddings: number, retrievals: number, generations: number, errors: number }} */
56
+ this.counters = {
57
+ queries: 0,
58
+ embeddings: 0,
59
+ retrievals: 0,
60
+ generations: 0,
61
+ errors: 0,
62
+ };
63
+
64
+ /** @type {Array<{ value: number, timestamp: number }>} */
65
+ this.latencies = [];
66
+
67
+ /** @type {Array<{ value: number, timestamp: number }>} */
68
+ this.costs = [];
69
+
70
+ /** @type {Array<{ heapUsed: number, heapTotal: number, rss: number, timestamp: number }>} */
71
+ this.memory = [];
72
+
73
+ /** @type {Array<{ message: string, code: string, source: string, timestamp: number }>} */
74
+ this.errors = [];
75
+
76
+ /** @type {Array<{ type: string, timestamp: number, duration: number }>} */
77
+ this.history = [];
78
+ }
79
+
80
+ /**
81
+ * Record a query operation metric.
82
+ * @param {QueryData} data
83
+ */
84
+ recordQuery(data) {
85
+ this._recordOperation('queries', data);
86
+ }
87
+
88
+ /**
89
+ * Record an embedding operation metric.
90
+ * @param {QueryData} data
91
+ */
92
+ recordEmbedding(data) {
93
+ this._recordOperation('embeddings', data);
94
+ }
95
+
96
+ /**
97
+ * Record a retrieval operation metric.
98
+ * @param {QueryData} data
99
+ */
100
+ recordRetrieval(data) {
101
+ this._recordOperation('retrievals', data);
102
+ }
103
+
104
+ /**
105
+ * Record a generation operation metric.
106
+ * @param {QueryData} data
107
+ */
108
+ recordGeneration(data) {
109
+ this._recordOperation('generations', data);
110
+ }
111
+
112
+ /**
113
+ * Record an error event.
114
+ * @param {ErrorData} data
115
+ */
116
+ recordError(data) {
117
+ const timestamp = data.timestamp || Date.now();
118
+ this.counters.errors += 1;
119
+
120
+ const entry = {
121
+ message: data.message || 'Unknown error',
122
+ code: data.code || 'UNKNOWN',
123
+ source: data.source || 'unknown',
124
+ timestamp,
125
+ };
126
+ this.errors.push(entry);
127
+ this._trimArray(this.errors);
128
+
129
+ this.history.push({ type: 'error', timestamp, duration: 0 });
130
+ this._trimArray(this.history);
131
+
132
+ this.emit('metric', { type: 'error', data: entry });
133
+ }
134
+
135
+ /**
136
+ * Record a memory sample.
137
+ * @param {{ heapUsed?: number, heapTotal?: number, rss?: number }} [data]
138
+ */
139
+ recordMemory(data) {
140
+ const mem = data || process.memoryUsage();
141
+ const entry = {
142
+ heapUsed: mem.heapUsed || 0,
143
+ heapTotal: mem.heapTotal || 0,
144
+ rss: mem.rss || 0,
145
+ timestamp: Date.now(),
146
+ };
147
+ this.memory.push(entry);
148
+ this._trimArray(this.memory);
149
+
150
+ this.emit('metric', { type: 'memory', data: entry });
151
+ }
152
+
153
+ /**
154
+ * Return a complete dashboard snapshot of current metrics.
155
+ * @returns {Object} Dashboard-ready metrics snapshot
156
+ */
157
+ getSnapshot() {
158
+ const now = Date.now();
159
+ const windowMs = this.config.windowSize * 1000;
160
+
161
+ const recentLatencies = this.latencies
162
+ .filter((l) => now - l.timestamp <= windowMs)
163
+ .map((l) => l.value);
164
+
165
+ const latencyStats = this._computePercentiles(recentLatencies);
166
+
167
+ const recentHistory = this.history.filter(
168
+ (h) => now - h.timestamp <= windowMs,
169
+ );
170
+ const windowSec = this.config.windowSize || 1;
171
+
172
+ const queriesInWindow = recentHistory.filter(
173
+ (h) => h.type === 'queries',
174
+ ).length;
175
+ const embeddingsInWindow = recentHistory.filter(
176
+ (h) => h.type === 'embeddings',
177
+ ).length;
178
+
179
+ const totalCost = this.costs.reduce((sum, c) => sum + c.value, 0);
180
+ const last24hMs = 24 * 60 * 60 * 1000;
181
+ const cost24h = this.costs
182
+ .filter((c) => now - c.timestamp <= last24hMs)
183
+ .reduce((sum, c) => sum + c.value, 0);
184
+ const avgCostPerQuery =
185
+ this.counters.queries > 0 ? totalCost / this.counters.queries : 0;
186
+
187
+ const currentMem =
188
+ this.memory.length > 0
189
+ ? this.memory[this.memory.length - 1]
190
+ : { heapUsed: 0, heapTotal: 0, rss: 0 };
191
+ const peakMem = this.memory.reduce(
192
+ (peak, m) => Math.max(peak, m.heapUsed),
193
+ 0,
194
+ );
195
+ const memTrend = this._computeMemoryTrend();
196
+
197
+ const totalOps =
198
+ this.counters.queries +
199
+ this.counters.embeddings +
200
+ this.counters.retrievals +
201
+ this.counters.generations;
202
+
203
+ return {
204
+ counters: { ...this.counters },
205
+ latency: latencyStats,
206
+ throughput: {
207
+ queriesPerSec: queriesInWindow / windowSec,
208
+ embeddingsPerSec: embeddingsInWindow / windowSec,
209
+ },
210
+ cost: {
211
+ total: totalCost,
212
+ last24h: cost24h,
213
+ avgPerQuery: avgCostPerQuery,
214
+ },
215
+ memory: {
216
+ current: currentMem.heapUsed,
217
+ peak: peakMem,
218
+ trend: memTrend,
219
+ },
220
+ errorRate: this.counters.errors / (totalOps || 1),
221
+ uptime: now - this.startTime,
222
+ timestamp: now,
223
+ };
224
+ }
225
+
226
+ /**
227
+ * Return time-bucketed data for charting a given metric.
228
+ * @param {'latency'|'throughput'|'cost'|'memory'|'errors'} metric
229
+ * @param {number} [windowSeconds] - Time window in seconds (defaults to config.windowSize)
230
+ * @returns {Array<{ bucket: number, value: number, label: string }>}
231
+ */
232
+ getTimeSeries(metric, windowSeconds) {
233
+ const window = (windowSeconds || this.config.windowSize) * 1000;
234
+ const now = Date.now();
235
+ const bucketCount = 10;
236
+ const bucketSize = window / bucketCount;
237
+ const buckets = [];
238
+
239
+ for (let i = 0; i < bucketCount; i++) {
240
+ const bucketStart = now - window + i * bucketSize;
241
+ const bucketEnd = bucketStart + bucketSize;
242
+ const label = `-${Math.round((now - bucketEnd) / 1000)}s`;
243
+
244
+ let value = 0;
245
+
246
+ switch (metric) {
247
+ case 'latency': {
248
+ const items = this.latencies.filter(
249
+ (l) => l.timestamp >= bucketStart && l.timestamp < bucketEnd,
250
+ );
251
+ value =
252
+ items.length > 0
253
+ ? items.reduce((s, l) => s + l.value, 0) / items.length
254
+ : 0;
255
+ break;
256
+ }
257
+ case 'throughput': {
258
+ value =
259
+ this.history.filter(
260
+ (h) =>
261
+ h.type === 'queries' &&
262
+ h.timestamp >= bucketStart &&
263
+ h.timestamp < bucketEnd,
264
+ ).length /
265
+ (bucketSize / 1000);
266
+ break;
267
+ }
268
+ case 'cost': {
269
+ value = this.costs
270
+ .filter(
271
+ (c) => c.timestamp >= bucketStart && c.timestamp < bucketEnd,
272
+ )
273
+ .reduce((s, c) => s + c.value, 0);
274
+ break;
275
+ }
276
+ case 'memory': {
277
+ const items = this.memory.filter(
278
+ (m) => m.timestamp >= bucketStart && m.timestamp < bucketEnd,
279
+ );
280
+ value = items.length > 0 ? items[items.length - 1].heapUsed : 0;
281
+ break;
282
+ }
283
+ case 'errors': {
284
+ value = this.errors.filter(
285
+ (e) => e.timestamp >= bucketStart && e.timestamp < bucketEnd,
286
+ ).length;
287
+ break;
288
+ }
289
+ default:
290
+ break;
291
+ }
292
+
293
+ buckets.push({ bucket: bucketStart, value, label });
294
+ }
295
+
296
+ return buckets;
297
+ }
298
+
299
+ /**
300
+ * Reset all counters, history, and collected metrics.
301
+ */
302
+ reset() {
303
+ this.counters = {
304
+ queries: 0,
305
+ embeddings: 0,
306
+ retrievals: 0,
307
+ generations: 0,
308
+ errors: 0,
309
+ };
310
+ this.latencies = [];
311
+ this.costs = [];
312
+ this.memory = [];
313
+ this.errors = [];
314
+ this.history = [];
315
+ this.startTime = Date.now();
316
+
317
+ this.emit('reset');
318
+ }
319
+
320
+ /**
321
+ * Record a generic pipeline operation.
322
+ * @param {string} type - Counter key (queries, embeddings, etc.)
323
+ * @param {QueryData} data
324
+ * @private
325
+ */
326
+ _recordOperation(type, data) {
327
+ const timestamp = data.timestamp || Date.now();
328
+
329
+ this.counters[type] = (this.counters[type] || 0) + 1;
330
+
331
+ if (typeof data.duration === 'number') {
332
+ this.latencies.push({ value: data.duration, timestamp });
333
+ this._trimArray(this.latencies);
334
+ }
335
+
336
+ if (typeof data.cost === 'number') {
337
+ this.costs.push({ value: data.cost, timestamp });
338
+ this._trimArray(this.costs);
339
+ }
340
+
341
+ this.history.push({ type, timestamp, duration: data.duration || 0 });
342
+ this._trimArray(this.history);
343
+
344
+ this.emit('metric', { type, data: { ...data, timestamp } });
345
+ }
346
+
347
+ /**
348
+ * Trim an array to maxHistory length, removing oldest entries.
349
+ * @param {Array} arr
350
+ * @private
351
+ */
352
+ _trimArray(arr) {
353
+ while (arr.length > this.config.maxHistory) {
354
+ arr.shift();
355
+ }
356
+ }
357
+
358
+ /**
359
+ * Compute percentile statistics from a sorted array of values.
360
+ * @param {number[]} values
361
+ * @returns {{ avg: number, p50: number, p95: number, p99: number, min: number, max: number }}
362
+ * @private
363
+ */
364
+ _computePercentiles(values) {
365
+ if (values.length === 0) {
366
+ return { avg: 0, p50: 0, p95: 0, p99: 0, min: 0, max: 0 };
367
+ }
368
+
369
+ const sorted = [...values].sort((a, b) => a - b);
370
+ const sum = sorted.reduce((s, v) => s + v, 0);
371
+
372
+ return {
373
+ avg: sum / sorted.length,
374
+ p50: sorted[Math.floor(sorted.length * 0.5)],
375
+ p95:
376
+ sorted[Math.floor(sorted.length * 0.95)] || sorted[sorted.length - 1],
377
+ p99:
378
+ sorted[Math.floor(sorted.length * 0.99)] || sorted[sorted.length - 1],
379
+ min: sorted[0],
380
+ max: sorted[sorted.length - 1],
381
+ };
382
+ }
383
+
384
+ /**
385
+ * Compute memory usage trend (positive = increasing, negative = decreasing).
386
+ * @returns {'increasing'|'decreasing'|'stable'}
387
+ * @private
388
+ */
389
+ _computeMemoryTrend() {
390
+ if (this.memory.length < 2) {
391
+ return 'stable';
392
+ }
393
+
394
+ const recent = this.memory.slice(-10);
395
+ if (recent.length < 2) {
396
+ return 'stable';
397
+ }
398
+
399
+ const first = recent[0].heapUsed;
400
+ const last = recent[recent.length - 1].heapUsed;
401
+ const changePct = ((last - first) / (first || 1)) * 100;
402
+
403
+ if (changePct > 5) return 'increasing';
404
+ if (changePct < -5) return 'decreasing';
405
+ return 'stable';
406
+ }
407
+ }
408
+
409
+ module.exports = { MetricsAggregator, DEFAULT_CONFIG };
@@ -0,0 +1,210 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Execution Tracer
5
+ *
6
+ * Hooks into pipeline steps to capture full execution traces.
7
+ * Records timing, inputs, outputs, and metadata for each pipeline run.
8
+ */
9
+
10
+ const { EventEmitter } = require('events');
11
+ const { randomBytes } = require('crypto');
12
+
13
+ /**
14
+ * Default tracer configuration
15
+ */
16
+ const DEFAULT_CONFIG = {
17
+ captureInputs: true,
18
+ captureOutputs: true,
19
+ maxPayloadSize: 10000,
20
+ };
21
+
22
+ /**
23
+ * Generate a unique trace ID
24
+ * @returns {string} 16-character hex trace ID
25
+ */
26
+ function generateTraceId() {
27
+ return randomBytes(8).toString('hex');
28
+ }
29
+
30
+ /**
31
+ * Truncate a value if its JSON representation exceeds maxSize bytes
32
+ * @param {*} value - Value to truncate
33
+ * @param {number} maxSize - Maximum serialized size in characters
34
+ * @returns {*} Original value or truncated string
35
+ */
36
+ function truncatePayload(value, maxSize) {
37
+ if (value === undefined || value === null) {
38
+ return value;
39
+ }
40
+ let serialized;
41
+ try {
42
+ serialized = JSON.stringify(value);
43
+ } catch {
44
+ return '[unserializable]';
45
+ }
46
+ if (serialized.length <= maxSize) {
47
+ return value;
48
+ }
49
+ return serialized.slice(0, maxSize) + '...[truncated]';
50
+ }
51
+
52
+ /**
53
+ * Execution tracer that wraps pipelines to capture run traces
54
+ * @extends EventEmitter
55
+ */
56
+ class ExecutionTracer extends EventEmitter {
57
+ /**
58
+ * Create a new ExecutionTracer
59
+ * @param {object} [options] - Tracer configuration
60
+ * @param {boolean} [options.captureInputs=true] - Whether to capture step inputs
61
+ * @param {boolean} [options.captureOutputs=true] - Whether to capture step outputs
62
+ * @param {number} [options.maxPayloadSize=10000] - Max characters before payload truncation
63
+ */
64
+ constructor(options = {}) {
65
+ super();
66
+ this.config = { ...DEFAULT_CONFIG, ...options };
67
+ this.traces = [];
68
+ }
69
+
70
+ /**
71
+ * Wrap a pipeline to record execution traces on each run
72
+ * @param {object} pipeline - Pipeline instance with a .run() method
73
+ * @returns {object} Wrapped pipeline with instrumented .run()
74
+ */
75
+ trace(pipeline) {
76
+ const tracer = this;
77
+ const originalRun = pipeline.run.bind(pipeline);
78
+
79
+ const wrapped = Object.create(pipeline);
80
+
81
+ /**
82
+ * Instrumented run that records a full execution trace
83
+ * @param {object} params - Pipeline run parameters
84
+ * @returns {Promise<object>} Pipeline result with attached trace
85
+ */
86
+ wrapped.run = async function tracedRun(params) {
87
+ const traceRecord = {
88
+ id: generateTraceId(),
89
+ startTime: Date.now(),
90
+ steps: [],
91
+ metadata: {},
92
+ };
93
+
94
+ // Capture input
95
+ if (tracer.config.captureInputs) {
96
+ traceRecord.input = truncatePayload(
97
+ params,
98
+ tracer.config.maxPayloadSize,
99
+ );
100
+ }
101
+
102
+ // Record the retrieval step (wraps the full pipeline.run call)
103
+ const stepRecord = {
104
+ step: 'pipeline.run',
105
+ startTime: Date.now(),
106
+ };
107
+
108
+ let result;
109
+ let error;
110
+ try {
111
+ result = await originalRun(params);
112
+ stepRecord.success = true;
113
+ } catch (err) {
114
+ error = err;
115
+ stepRecord.success = false;
116
+ stepRecord.error = err.message;
117
+ }
118
+
119
+ stepRecord.endTime = Date.now();
120
+ stepRecord.durationMs = stepRecord.endTime - stepRecord.startTime;
121
+
122
+ if (tracer.config.captureOutputs && result) {
123
+ stepRecord.output = truncatePayload(
124
+ result,
125
+ tracer.config.maxPayloadSize,
126
+ );
127
+ }
128
+
129
+ traceRecord.steps.push(stepRecord);
130
+
131
+ // Extract quality sub-steps from the result when available
132
+ if (result) {
133
+ if (result.results) {
134
+ traceRecord.metadata.resultCount = result.results.length;
135
+ }
136
+ if (result.citations) {
137
+ traceRecord.metadata.hasCitations = true;
138
+ traceRecord.metadata.citations = result.citations;
139
+ }
140
+ if (result.evaluation) {
141
+ traceRecord.metadata.hasEvaluation = true;
142
+ traceRecord.metadata.evaluation = result.evaluation;
143
+ }
144
+ }
145
+
146
+ // Capture output
147
+ if (tracer.config.captureOutputs && result) {
148
+ traceRecord.output = truncatePayload(
149
+ result,
150
+ tracer.config.maxPayloadSize,
151
+ );
152
+ }
153
+
154
+ // Finalize
155
+ traceRecord.endTime = Date.now();
156
+ traceRecord.totalDurationMs = traceRecord.endTime - traceRecord.startTime;
157
+
158
+ tracer.traces.push(traceRecord);
159
+ tracer.emit('traced', traceRecord);
160
+
161
+ if (error) {
162
+ throw error;
163
+ }
164
+
165
+ // Attach trace to result
166
+ result.trace = traceRecord;
167
+ return result;
168
+ };
169
+
170
+ return wrapped;
171
+ }
172
+
173
+ /**
174
+ * Return all recorded traces
175
+ * @returns {Array<object>} Array of trace records
176
+ */
177
+ getTraces() {
178
+ return this.traces;
179
+ }
180
+
181
+ /**
182
+ * Return a specific trace by ID
183
+ * @param {string} traceId - The trace ID to look up
184
+ * @returns {object|undefined} The trace record, or undefined if not found
185
+ */
186
+ getTrace(traceId) {
187
+ return this.traces.find((t) => t.id === traceId);
188
+ }
189
+
190
+ /**
191
+ * Clear all recorded traces
192
+ */
193
+ clear() {
194
+ this.traces = [];
195
+ }
196
+
197
+ /**
198
+ * Export traces as a serialized string
199
+ * @param {string} [format='json'] - Export format (currently only 'json')
200
+ * @returns {string} Serialized traces
201
+ */
202
+ exportTraces(format = 'json') {
203
+ if (format === 'json') {
204
+ return JSON.stringify(this.traces, null, 2);
205
+ }
206
+ throw new Error(`Unsupported export format: ${format}`);
207
+ }
208
+ }
209
+
210
+ module.exports = { ExecutionTracer, DEFAULT_CONFIG };