@devilsdev/rag-pipeline-utils 2.1.12 → 2.2.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 (110) hide show
  1. package/.ragrc.schema.json +218 -0
  2. package/README.md +292 -32
  3. package/bin/cli.js +8 -14
  4. package/dist/index.d.ts +237 -0
  5. package/dist/index.mjs +22 -7
  6. package/package.json +17 -13
  7. package/src/ai/adaptive-retrieval.js +191 -0
  8. package/src/ai/federated-learning.js +214 -0
  9. package/src/ai/federation/federated-learning-coordinator.js +345 -0
  10. package/src/ai/federation.js +76 -0
  11. package/src/ai/index-fixed.js +509 -0
  12. package/src/ai/index.js +33 -0
  13. package/src/ai/model-trainer.js +217 -0
  14. package/src/ai/model-training.js +205 -0
  15. package/src/ai/module-template.js +37 -0
  16. package/src/ai/multimodal/multi-modal-processor.js +345 -0
  17. package/src/ai/multimodal-processing.js +268 -0
  18. package/src/ai/multimodal.js +71 -0
  19. package/src/ai/orchestrator.js +70 -0
  20. package/src/ai/retrieval/adaptive-retrieval-engine.js +398 -0
  21. package/src/ai/retrieval-engine.js +69 -0
  22. package/src/ai/training/model-training-orchestrator.js +228 -0
  23. package/src/ci/workflow-analyzer.js +71 -0
  24. package/src/cli/commands/ai-ml.js +967 -0
  25. package/src/cli/commands/docs.js +228 -0
  26. package/src/cli/commands/dx.js +538 -0
  27. package/src/cli/commands/plugin-hub.js +875 -0
  28. package/src/cli/doctor-command.js +639 -0
  29. package/src/cli/enhanced-cli-commands.js +945 -0
  30. package/src/cli/interactive-wizard.js +997 -0
  31. package/src/cli/plugin-marketplace-commands.js +869 -0
  32. package/src/cli.js +17 -0
  33. package/src/config/enhanced-ragrc-schema.js +632 -0
  34. package/src/config/load-config.js +54 -0
  35. package/src/config/load-plugin-config.js +69 -0
  36. package/src/config/normalize-config.js +105 -0
  37. package/src/config/schema-validator.js +315 -0
  38. package/src/config/validate-plugin-schema.js +35 -0
  39. package/src/config/validate-schema.js +113 -0
  40. package/src/contracts/plugin-contracts.js +33 -0
  41. package/src/core/create-pipeline.js +99 -0
  42. package/src/core/dependency-injection.js +242 -0
  43. package/src/core/observability/event-logger.js +241 -0
  44. package/src/core/observability/instrumented-pipeline.js +609 -0
  45. package/src/core/observability/metrics.js +497 -0
  46. package/src/core/observability/tracing.js +781 -0
  47. package/src/core/performance/benchmark.js +516 -0
  48. package/src/core/performance/parallel-processor.js +240 -0
  49. package/src/core/performance/streaming-safeguards.js +611 -0
  50. package/src/core/pipeline-factory.js +60 -0
  51. package/src/core/plugin-contracts.js +89 -0
  52. package/src/core/plugin-marketplace/plugin-metadata.js +485 -0
  53. package/src/core/plugin-marketplace/plugin-publisher.js +574 -0
  54. package/src/core/plugin-marketplace/plugin-registry-format.js +419 -0
  55. package/src/core/plugin-marketplace/version-resolver.js +61 -0
  56. package/src/core/plugin-registry.js +265 -0
  57. package/src/dag/dag-engine.js +954 -0
  58. package/src/dag/dag-node.js +76 -0
  59. package/src/dag/enhanced-dag-engine.js +822 -0
  60. package/src/dx/index.js +40 -0
  61. package/src/dx/integration-templates.js +665 -0
  62. package/src/dx/performance-profiler.js +968 -0
  63. package/src/dx/realtime-debugger.js +755 -0
  64. package/src/dx/visual-pipeline-builder.js +578 -0
  65. package/src/ecosystem/plugin-analytics-dashboard.js +778 -0
  66. package/src/ecosystem/plugin-certification.js +580 -0
  67. package/src/ecosystem/plugin-hub.js +821 -0
  68. package/src/enterprise/audit-logging.js +967 -0
  69. package/src/enterprise/data-governance.js +408 -0
  70. package/src/enterprise/multi-tenancy.js +496 -0
  71. package/src/enterprise/sso-integration.js +903 -0
  72. package/src/evaluate/evaluator.js +52 -0
  73. package/src/evaluate/scoring.js +91 -0
  74. package/src/index.js +67 -0
  75. package/src/ingest.js +59 -0
  76. package/src/loader/csv-loader.js +38 -0
  77. package/src/loader/directory-loader.js +48 -0
  78. package/src/loader/html-loader.js +62 -0
  79. package/src/loader/markdown-loader.js +67 -0
  80. package/src/mocks/openai-embedder.js +119 -0
  81. package/src/mocks/openai-llm.js +77 -0
  82. package/src/mocks/pdf-loader.js +21 -0
  83. package/src/mocks/pinecone-retriever.js +106 -0
  84. package/src/observability/audit-logger.js +758 -0
  85. package/src/observability/dashboard-config.json +675 -0
  86. package/src/observability/event-logger.js +310 -0
  87. package/src/observability/metrics.js +691 -0
  88. package/src/observability/security-monitor.js +830 -0
  89. package/src/observability/slo-monitor.js +319 -0
  90. package/src/observability/telemetry.js +686 -0
  91. package/src/pipeline/pipeline.js +18 -0
  92. package/src/plugins/sample-embedder.js +53 -0
  93. package/src/plugins/sample-llm.js +30 -0
  94. package/src/plugins/sample-loader.js +24 -0
  95. package/src/plugins/sample-retriever.js +84 -0
  96. package/src/query.js +117 -0
  97. package/src/reranker/llm-reranker.js +85 -0
  98. package/src/scripts/script-utilities.js +36 -0
  99. package/src/security/plugin-sandbox.js +586 -0
  100. package/src/security/plugin-signature-verifier.js +414 -0
  101. package/src/security/validator.js +36 -0
  102. package/src/utils/ci/diagnostic-reporter.js +57 -0
  103. package/src/utils/logger.js +211 -0
  104. package/src/utils/performance-monitor.js +561 -0
  105. package/src/utils/plugin-scaffolder.js +447 -0
  106. package/src/utils/retry-policy.js +443 -0
  107. package/src/utils/retry.js +107 -0
  108. package/src/utils/safe-expression-evaluator.js +579 -0
  109. package/src/utils/structured-logger.js +361 -0
  110. package/src/utils/validate-plugin-contract.js +42 -0
@@ -0,0 +1,781 @@
1
+ /**
2
+ * Distributed tracing support for RAG pipeline operations
3
+ * Provides span-style lifecycle tracking with OpenTelemetry compatibility
4
+ */
5
+
6
+ const { randomBytes } = require('crypto'); // eslint-disable-line global-require
7
+ const {
8
+ eventLogger,
9
+ EventTypes,
10
+ EventSeverity,
11
+ PipelineEventLogger,
12
+ } = require('../../observability/event-logger.js');
13
+
14
+ const _logger = global.eventLogger || eventLogger || new PipelineEventLogger();
15
+ global.eventLogger = _logger;
16
+
17
+ /**
18
+ * Span status codes (OpenTelemetry compatible)
19
+ */
20
+ const SpanStatusCode = {
21
+ UNSET: 0,
22
+ OK: 1,
23
+ ERROR: 2,
24
+ };
25
+
26
+ /**
27
+ * Span kinds (OpenTelemetry compatible)
28
+ */
29
+ const SpanKind = {
30
+ INTERNAL: 0,
31
+ SERVER: 1,
32
+ CLIENT: 2,
33
+ PRODUCER: 3,
34
+ CONSUMER: 4,
35
+ };
36
+
37
+ /**
38
+ * Generate trace ID
39
+ * @returns {string} 32-character hex trace ID
40
+ */
41
+ function generateTraceId() {
42
+ return randomBytes(16).toString('hex');
43
+ }
44
+
45
+ /**
46
+ * Generate span ID
47
+ * @returns {string} 16-character hex span ID
48
+ */
49
+ function generateSpanId() {
50
+ return randomBytes(8).toString('hex');
51
+ }
52
+
53
+ /**
54
+ * Trace span implementation
55
+ */
56
+ class Span {
57
+ constructor(name, traceIdOrOptions = {}, parentSpanId = undefined) {
58
+ this.name = name;
59
+
60
+ // Handle legacy parameter format: (name, traceId, parentSpanId)
61
+ // vs new options format: (name, options)
62
+ let _options = {};
63
+ if (typeof traceIdOrOptions === 'string') {
64
+ // Legacy format: second parameter is traceId
65
+ _options.traceId = traceIdOrOptions;
66
+ _options.parentSpanId = parentSpanId;
67
+ } else {
68
+ // New format: second parameter is options object
69
+ _options = traceIdOrOptions || {};
70
+ }
71
+
72
+ this.traceId = _options.traceId || generateTraceId();
73
+ this.spanId = generateSpanId();
74
+ this.parentSpanId = _options.parentSpanId || undefined;
75
+ this.kind = _options.kind || SpanKind.INTERNAL;
76
+ this.startTime = new Date();
77
+ this.endTime = null;
78
+ this.duration = null;
79
+ this.status = { code: 'UNSET' };
80
+ this.attributes = new Map();
81
+ this.events = [];
82
+ this.links = [];
83
+ this.resource = _options.resource || {};
84
+ this.instrumentationLibrary = _options.instrumentationLibrary || {
85
+ name: 'rag-pipeline-utils',
86
+ version: '2.1.8',
87
+ };
88
+ // Store reference to parent tracer for span lifecycle management
89
+ this._tracer = _options._tracer || null;
90
+
91
+ // Create a proxy for bracket notation access to attributes
92
+ const attributesProxy = new Proxy(this.attributes, {
93
+ get(target, prop) {
94
+ // Handle string properties as Map.get() for bracket notation
95
+ if (typeof prop === 'string' && !target[prop]) {
96
+ return target.get(prop);
97
+ }
98
+ // Delegate all other properties (including Map methods) to the target
99
+ const value = target[prop];
100
+ return typeof value === 'function' ? value.bind(target) : value;
101
+ },
102
+ set(target, prop, value) {
103
+ if (typeof prop === 'string') {
104
+ target.set(prop, value);
105
+ return true;
106
+ }
107
+ target[prop] = value;
108
+ return true;
109
+ },
110
+ });
111
+
112
+ // Override attributes with proxy for bracket notation access
113
+ this.attributes = attributesProxy;
114
+
115
+ // Set initial attributes if provided
116
+ if (_options.attributes && typeof _options.attributes === 'object') {
117
+ Object.entries(_options.attributes).forEach(([key, value]) => {
118
+ this.attributes.set(key, value);
119
+ });
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Set span attribute
125
+ * @param {string} key - Attribute key
126
+ * @param {any} value - Attribute value
127
+ */
128
+ setAttribute(key, value) {
129
+ this.attributes.set(key, value);
130
+ return this;
131
+ }
132
+
133
+ /**
134
+ * Set multiple span attributes
135
+ * @param {object} attributes - Attributes object
136
+ */
137
+ setAttributes(attributes) {
138
+ Object.entries(attributes).forEach(([key, value]) => {
139
+ this.setAttribute(key, value);
140
+ });
141
+ return this;
142
+ }
143
+
144
+ /**
145
+ * Set span status
146
+ * @param {object} status - Status object with code and optional message
147
+ */
148
+ setStatus(status) {
149
+ this.status = status;
150
+ return this;
151
+ }
152
+
153
+ /**
154
+ * Add event to span
155
+ * @param {string} name - Event name
156
+ * @param {object} attributes - Event attributes
157
+ * @param {number} timestamp - Event timestamp (optional)
158
+ */
159
+ addEvent(name, attributes = {}, timestamp = new Date()) {
160
+ this.events.push({
161
+ name,
162
+ attributes,
163
+ timestamp,
164
+ });
165
+ return this;
166
+ }
167
+
168
+ /**
169
+ * Record exception in span
170
+ * @param {Error} exception - Exception to record
171
+ */
172
+ recordException(exception) {
173
+ // Check if we already have an exception event to avoid duplicates
174
+ const hasExceptionEvent = this.events.some(
175
+ (event) => event.name === 'exception',
176
+ );
177
+ if (hasExceptionEvent) {
178
+ return this;
179
+ }
180
+
181
+ const attributes = {
182
+ 'exception.type': exception.name || 'Error',
183
+ 'exception.message': exception.message || 'Unknown error',
184
+ };
185
+
186
+ // Only include stacktrace if it exists and is not empty
187
+ if (exception.stack && exception.stack.trim()) {
188
+ attributes['exception.stacktrace'] = exception.stack;
189
+ }
190
+
191
+ // Include exception code if present
192
+ if (exception.code) {
193
+ attributes['exception.code'] = exception.code;
194
+ }
195
+
196
+ // Include additional error properties
197
+ if (exception.errno) {
198
+ attributes['exception.errno'] = exception.errno;
199
+ }
200
+
201
+ if (exception.syscall) {
202
+ attributes['exception.syscall'] = exception.syscall;
203
+ }
204
+
205
+ if (exception.path) {
206
+ attributes['exception.path'] = exception.path;
207
+ }
208
+
209
+ this.addEvent('exception', attributes);
210
+ this.setStatus({
211
+ code: 'ERROR',
212
+ message: exception.message || 'Unknown error',
213
+ });
214
+ return this;
215
+ }
216
+
217
+ /**
218
+ * Add link to another span
219
+ * @param {object} spanContext - Span context to link to
220
+ * @param {object} attributes - Link attributes
221
+ */
222
+ addLink(spanContext, attributes = {}) {
223
+ this.links.push({
224
+ spanContext,
225
+ attributes,
226
+ });
227
+ return this;
228
+ }
229
+
230
+ /**
231
+ * End the span
232
+ * @param {number} endTime - End timestamp (optional)
233
+ */
234
+ end(endTime = new Date()) {
235
+ if (this.endTime !== null) {
236
+ console.warn(`Span ${this.name} already ended`); // eslint-disable-line no-console
237
+ return;
238
+ }
239
+
240
+ this.endTime = endTime;
241
+ this.duration = Math.max(
242
+ 1,
243
+ this.endTime.getTime() - this.startTime.getTime(),
244
+ );
245
+
246
+ // Set status to OK if not already set
247
+ if (this.status.code === 'UNSET') {
248
+ this.status.code = 'OK';
249
+ }
250
+
251
+ // Notify tracer to move span from active to completed (only if not already called by tracer)
252
+ if (this._tracer && this._tracer.activeSpans.has(this.spanId)) {
253
+ this._tracer.endSpan(this);
254
+ }
255
+
256
+ // Log span completion
257
+ _logger.logEvent(
258
+ EventTypes.STAGE_END,
259
+ EventSeverity.DEBUG,
260
+ {
261
+ traceId: this.traceId,
262
+ spanId: this.spanId,
263
+ parentSpanId: this.parentSpanId,
264
+ duration: this.duration,
265
+ attributes: Object.fromEntries(this.attributes),
266
+ status: this.status,
267
+ eventCount: this.events.length,
268
+ },
269
+ `Span completed: ${this.name} (${this.duration}ms)`,
270
+ );
271
+ }
272
+
273
+ /**
274
+ * Get span context
275
+ * @returns {object} Span context
276
+ */
277
+ getSpanContext() {
278
+ return {
279
+ traceId: this.traceId,
280
+ spanId: this.spanId,
281
+ traceFlags: 1, // Sampled
282
+ traceState: undefined,
283
+ };
284
+ }
285
+
286
+ /**
287
+ * Check if span is recording
288
+ * @returns {boolean} True if span is recording
289
+ */
290
+ isRecording() {
291
+ return this.endTime === null;
292
+ }
293
+
294
+ /**
295
+ * Export span data
296
+ * @returns {object} Span export data
297
+ */
298
+ export() {
299
+ return {
300
+ name: this.name,
301
+ traceId: this.traceId,
302
+ spanId: this.spanId,
303
+ parentSpanId: this.parentSpanId,
304
+ kind: this.kind,
305
+ startTime: this.startTime,
306
+ endTime: this.endTime,
307
+ duration: this.duration,
308
+ status: this.status,
309
+ attributes: Object.fromEntries(this.attributes),
310
+ events: this.events,
311
+ links: this.links,
312
+ resource: this.resource,
313
+ instrumentationLibrary: this.instrumentationLibrary,
314
+ };
315
+ }
316
+
317
+ /**
318
+ * Check if span is finished
319
+ * @returns {boolean} True if span is finished
320
+ */
321
+ isFinished() {
322
+ return this.endTime !== null;
323
+ }
324
+
325
+ /**
326
+ * Get span duration
327
+ * @returns {number} Duration in milliseconds
328
+ */
329
+ getDuration() {
330
+ if (this.endTime !== null) {
331
+ return this.duration;
332
+ }
333
+ // Return current duration for active span
334
+ return Date.now() - this.startTime.getTime();
335
+ }
336
+
337
+ /**
338
+ * Serialize span to JSON
339
+ * @returns {object} JSON representation of span
340
+ */
341
+ toJSON() {
342
+ return {
343
+ name: this.name,
344
+ traceId: this.traceId,
345
+ spanId: this.spanId,
346
+ parentSpanId: this.parentSpanId,
347
+ kind: this.kind,
348
+ startTime: this.startTime ? this.startTime.toISOString() : null,
349
+ endTime: this.endTime ? this.endTime.toISOString() : null,
350
+ duration: this.duration,
351
+ status: this.status,
352
+ attributes: Object.fromEntries(this.attributes),
353
+ events: this.events,
354
+ links: this.links,
355
+ resource: this.resource,
356
+ instrumentationLibrary: this.instrumentationLibrary,
357
+ };
358
+ }
359
+ }
360
+
361
+ /**
362
+ * Tracer implementation
363
+ */
364
+ class Tracer {
365
+ constructor(name, version = '1.0.0', _options = {}) {
366
+ this.name = name;
367
+ this.version = version;
368
+ this.activeSpans = new Map();
369
+ this.completedSpans = [];
370
+ this.maxCompletedSpans = _options.maxCompletedSpans || 1000;
371
+ this.resource = _options.resource || {
372
+ 'service.name': 'rag-pipeline-utils',
373
+ 'service.version': '2.1.8',
374
+ };
375
+ }
376
+
377
+ /**
378
+ * Start a new span
379
+ * @param {string} name - Span name
380
+ * @param {object} _options - Span _options
381
+ * @returns {Span} New span
382
+ */
383
+ startSpan(name, _options = {}) {
384
+ // Handle parent span option
385
+ const spanOptions = { ..._options };
386
+ if (_options.parent) {
387
+ spanOptions.traceId = _options.parent.traceId;
388
+ spanOptions.parentSpanId = _options.parent.spanId;
389
+ delete spanOptions.parent; // Remove parent from options passed to Span constructor
390
+ }
391
+
392
+ const span = new Span(name, {
393
+ ...spanOptions,
394
+ resource: this.resource,
395
+ instrumentationLibrary: {
396
+ name: this.name,
397
+ version: this.version,
398
+ },
399
+ _tracer: this,
400
+ });
401
+
402
+ this.activeSpans.set(span.spanId, span);
403
+
404
+ // Log span start
405
+ _logger.logEvent(
406
+ EventTypes.STAGE_START,
407
+ EventSeverity.DEBUG,
408
+ {
409
+ traceId: span.traceId,
410
+ spanId: span.spanId,
411
+ parentSpanId: span.parentSpanId,
412
+ spanName: name,
413
+ attributes: Object.fromEntries(span.attributes),
414
+ },
415
+ `Span started: ${name}`,
416
+ );
417
+
418
+ return span;
419
+ }
420
+
421
+ /**
422
+ * Start an active span and execute callback
423
+ * @param {string} name - Span name
424
+ * @param {Function} _fn - Function to execute within span
425
+ * @param {object} _options - Span _options
426
+ * @returns {Promise<any>} Result of callback function
427
+ */
428
+ async startActiveSpan(name, _fn, _options = {}) {
429
+ const span = this.startSpan(name, _options);
430
+
431
+ try {
432
+ const result = await _fn(span);
433
+ span.setStatus({ code: 'OK' });
434
+ return result;
435
+ } catch (error) {
436
+ span.recordException(error);
437
+ throw error;
438
+ } finally {
439
+ this.endSpan(span);
440
+ }
441
+ }
442
+
443
+ /**
444
+ * End a span
445
+ * @param {Span} span - Span to end
446
+ */
447
+ endSpan(span) {
448
+ // Check if span is still in active spans (avoid double-ending)
449
+ if (!this.activeSpans.has(span.spanId)) {
450
+ return;
451
+ }
452
+
453
+ // Move span from active to completed first
454
+ this.activeSpans.delete(span.spanId);
455
+
456
+ // End the span (sets endTime and duration) - only if not already ended
457
+ if (span.endTime === null) {
458
+ span.end();
459
+ }
460
+
461
+ // Add to completed spans
462
+ this.completedSpans.push(span);
463
+ if (this.completedSpans.length > this.maxCompletedSpans) {
464
+ this.completedSpans.shift();
465
+ }
466
+ }
467
+
468
+ /**
469
+ * Get active span by ID
470
+ * @param {string} spanId - Span ID
471
+ * @returns {Span|null} Active span or null
472
+ */
473
+ getActiveSpan(spanId) {
474
+ return this.activeSpans.get(spanId) || null;
475
+ }
476
+
477
+ /**
478
+ * Get all active spans
479
+ * @returns {Span[]} Array of active spans
480
+ */
481
+ getActiveSpans() {
482
+ return Array.from(this.activeSpans.values());
483
+ }
484
+
485
+ /**
486
+ * Get completed spans
487
+ * @param {object} filters - Filters to apply
488
+ * @returns {Span[]} Array of completed spans
489
+ */
490
+ getCompletedSpans(filters = {}) {
491
+ let spans = [...this.completedSpans];
492
+
493
+ if (filters.traceId) {
494
+ spans = spans.filter((s) => s.traceId === filters.traceId);
495
+ }
496
+
497
+ if (filters.name) {
498
+ spans = spans.filter((s) => s.name.includes(filters.name));
499
+ }
500
+
501
+ if (filters.namePattern) {
502
+ spans = spans.filter((s) => filters.namePattern.test(s.name));
503
+ }
504
+
505
+ if (filters.status) {
506
+ spans = spans.filter((s) => s.status.code === filters.status);
507
+ }
508
+
509
+ if (filters.since) {
510
+ const sinceTime = new Date(filters.since).getTime();
511
+ spans = spans.filter((s) => s.startTime >= sinceTime);
512
+ }
513
+
514
+ if (filters.limit) {
515
+ spans = spans.slice(-filters.limit);
516
+ }
517
+
518
+ return spans;
519
+ }
520
+
521
+ /**
522
+ * Export spans for external tracing systems
523
+ * @param {object} filters - Filters to apply
524
+ * @returns {object[]} Array of exported span data
525
+ */
526
+ exportSpans(filters = {}) {
527
+ const spans = this.getCompletedSpans(filters);
528
+ return spans.map((span) => span.export());
529
+ }
530
+
531
+ /**
532
+ * Clear completed spans
533
+ */
534
+ clearCompletedSpans() {
535
+ this.completedSpans = [];
536
+ }
537
+
538
+ /**
539
+ * Get trace statistics
540
+ * @returns {object} Trace statistics
541
+ */
542
+ getTraceStats() {
543
+ const activeSpans = this.getActiveSpans();
544
+ const completedSpans = this.completedSpans;
545
+ const totalSpans = activeSpans.length + completedSpans.length;
546
+
547
+ const traceIds = new Set([
548
+ ...activeSpans.map((s) => s.traceId),
549
+ ...completedSpans.map((s) => s.traceId),
550
+ ]);
551
+
552
+ const statusCounts = {};
553
+ completedSpans.forEach((span) => {
554
+ const status = span.status.code;
555
+ statusCounts[status] = (statusCounts[status] || 0) + 1;
556
+ });
557
+
558
+ // Count spans by type
559
+ const spansByType = {};
560
+ const allSpans = [...activeSpans, ...completedSpans];
561
+ allSpans.forEach((span) => {
562
+ // Extract type from span name (e.g., "embedder.openai" -> "embedder")
563
+ const spanType = span.name.includes('.')
564
+ ? span.name.split('.')[0]
565
+ : 'plugin';
566
+ spansByType[spanType] = (spansByType[spanType] || 0) + 1;
567
+ });
568
+
569
+ // If we have plugin-related spans, group them under 'plugin'
570
+ if (
571
+ Object.keys(spansByType).some((_type) =>
572
+ ['embedder', 'llm', 'retriever', 'loader', 'reranker'].includes(_type),
573
+ )
574
+ ) {
575
+ const pluginCount = Object.entries(spansByType)
576
+ .filter(([_type]) =>
577
+ ['embedder', 'llm', 'retriever', 'loader', 'reranker'].includes(
578
+ _type,
579
+ ),
580
+ )
581
+ .reduce((sum, [, count]) => sum + count, 0);
582
+ if (pluginCount > 0) {
583
+ spansByType.plugin = pluginCount;
584
+ }
585
+ }
586
+
587
+ return {
588
+ totalSpans,
589
+ activeSpans: activeSpans.length,
590
+ completedSpans: completedSpans.length,
591
+ uniqueTraces: traceIds.size,
592
+ statusCounts,
593
+ spansByType,
594
+ averageDuration:
595
+ completedSpans.length > 0
596
+ ? completedSpans.reduce(
597
+ (sum, span) => sum + (span.duration || 0),
598
+ 0,
599
+ ) / completedSpans.length
600
+ : 0,
601
+ };
602
+ }
603
+ }
604
+
605
+ /**
606
+ * Pipeline tracer with plugin-specific instrumentation
607
+ */
608
+ class PipelineTracer extends Tracer {
609
+ constructor(_options = {}) {
610
+ super('rag-pipeline-tracer', '1.0.0', _options);
611
+ }
612
+
613
+ /**
614
+ * Trace plugin execution
615
+ * @param {string} pluginType - Type of plugin
616
+ * @param {string} pluginName - Name of plugin
617
+ * @param {Function} fn - Plugin function to execute
618
+ * @param {any} input - Plugin input
619
+ * @param {object} context - Plugin execution context
620
+ * @returns {Promise<any>} Plugin result
621
+ */
622
+ async tracePlugin(pluginType, pluginName, _fn, input, context = {}) {
623
+ return this.startActiveSpan(
624
+ `plugin.${pluginType}.${pluginName}`,
625
+ async (span) => {
626
+ span.setAttributes({
627
+ 'plugin.type': pluginType,
628
+ 'plugin.name': pluginName,
629
+ 'plugin.input.size': this.getInputSize(input),
630
+ 'operation.type': 'plugin_execution',
631
+ 'plugin.version': context.version || 'unknown',
632
+ 'plugin.provider': context.provider || pluginName,
633
+ });
634
+
635
+ // Add context attributes if provided
636
+ if (context && typeof context === 'object') {
637
+ Object.entries(context).forEach(([key, value]) => {
638
+ span.setAttribute(`plugin.context.${key}`, value);
639
+ });
640
+ }
641
+
642
+ const startTime = Date.now();
643
+
644
+ try {
645
+ const result = await _fn(input);
646
+ const duration = Date.now() - startTime;
647
+
648
+ span.setAttributes({
649
+ 'plugin.duration': duration,
650
+ 'plugin.result.size': this.getResultSize(result),
651
+ 'plugin.status': 'success',
652
+ 'plugin.success': true,
653
+ 'plugin.result.type': Array.isArray(result)
654
+ ? 'array'
655
+ : typeof result,
656
+ });
657
+
658
+ span.setStatus({ code: 'OK' });
659
+
660
+ return result;
661
+ } catch (error) {
662
+ const duration = Date.now() - startTime;
663
+
664
+ span.setAttributes({
665
+ 'plugin.duration': duration,
666
+ 'plugin.status': 'error',
667
+ 'plugin.success': false,
668
+ 'plugin.error.type': error.name || 'Error',
669
+ 'plugin.error.message': error.message || 'Unknown error',
670
+ });
671
+
672
+ span.recordException(error);
673
+
674
+ span.setStatus({
675
+ code: 'ERROR',
676
+ message: error.message || 'Unknown error',
677
+ });
678
+
679
+ throw error;
680
+ }
681
+ },
682
+ );
683
+ }
684
+
685
+ /**
686
+ * Trace pipeline stage
687
+ * @param {string} stage - Stage name
688
+ * @param {Function} fn - Stage function to execute
689
+ * @param {object} context - Stage execution context
690
+ * @param {object} metadata - Stage metadata
691
+ * @returns {Promise<any>} Stage result
692
+ */
693
+ async traceStage(stage, _fn, context = {}, metadata = {}) {
694
+ return this.startActiveSpan(`pipeline.${stage}`, async (span) => {
695
+ span.setAttributes({
696
+ 'pipeline.stage': stage,
697
+ 'operation.type': 'pipeline_stage',
698
+ ...metadata,
699
+ });
700
+
701
+ // Add context attributes if provided
702
+ if (context && typeof context === 'object') {
703
+ Object.entries(context).forEach(([key, value]) => {
704
+ span.setAttribute(`stage.context.${key}`, value);
705
+ });
706
+ }
707
+
708
+ try {
709
+ const result = await _fn();
710
+
711
+ // Set success attributes
712
+ span.setAttribute('stage.success', true);
713
+ span.setStatus({ code: 'OK' });
714
+
715
+ return result;
716
+ } catch (error) {
717
+ // Set failure attributes
718
+ span.setAttribute('stage.success', false);
719
+ span.setStatus({
720
+ code: 'ERROR',
721
+ message: error.message,
722
+ });
723
+
724
+ // Record the exception
725
+ span.recordException(error);
726
+
727
+ throw error;
728
+ }
729
+ });
730
+ }
731
+
732
+ /**
733
+ * Get input size for tracing
734
+ * @param {any} input - Input to measure
735
+ * @returns {number} Input size
736
+ */
737
+ getInputSize(input) {
738
+ if (Array.isArray(input)) {
739
+ return input.length;
740
+ } else if (typeof input === 'string') {
741
+ return input.length;
742
+ } else if (typeof input === 'object' && input !== null) {
743
+ return Object.keys(input).length;
744
+ }
745
+ return 1;
746
+ }
747
+
748
+ /**
749
+ * Get result size for tracing
750
+ * @param {any} result - Result to measure
751
+ * @returns {number} Result size
752
+ */
753
+ getResultSize(result) {
754
+ return this.getInputSize(result);
755
+ }
756
+ }
757
+
758
+ // Global tracer instance
759
+ const pipelineTracer = new PipelineTracer();
760
+
761
+ /**
762
+ * OpenTelemetry-compatible trace API
763
+ */
764
+ const trace = {
765
+ getTracer: (name, version, _options) => new Tracer(name, version, _options),
766
+ getActiveSpan: () => null, // Simplified for now
767
+ setSpan: () => {}, // Simplified for now
768
+ deleteSpan: () => {}, // Simplified for now
769
+ };
770
+
771
+ // Default export
772
+
773
+ module.exports = {
774
+ Span,
775
+ Tracer,
776
+ PipelineTracer,
777
+ SpanStatusCode,
778
+ SpanKind,
779
+ pipelineTracer,
780
+ trace,
781
+ };