@devilsdev/rag-pipeline-utils 2.1.11 → 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.
- package/.ragrc.schema.json +218 -0
- package/README.md +651 -393
- package/bin/cli.js +8 -14
- package/dist/index.d.ts +237 -0
- package/dist/index.mjs +22 -7
- package/package.json +17 -13
- package/src/ai/adaptive-retrieval.js +191 -0
- package/src/ai/federated-learning.js +214 -0
- package/src/ai/federation/federated-learning-coordinator.js +345 -0
- package/src/ai/federation.js +76 -0
- package/src/ai/index-fixed.js +509 -0
- package/src/ai/index.js +33 -0
- package/src/ai/model-trainer.js +217 -0
- package/src/ai/model-training.js +205 -0
- package/src/ai/module-template.js +37 -0
- package/src/ai/multimodal/multi-modal-processor.js +345 -0
- package/src/ai/multimodal-processing.js +268 -0
- package/src/ai/multimodal.js +71 -0
- package/src/ai/orchestrator.js +70 -0
- package/src/ai/retrieval/adaptive-retrieval-engine.js +398 -0
- package/src/ai/retrieval-engine.js +69 -0
- package/src/ai/training/model-training-orchestrator.js +228 -0
- package/src/ci/workflow-analyzer.js +71 -0
- package/src/cli/commands/ai-ml.js +967 -0
- package/src/cli/commands/docs.js +228 -0
- package/src/cli/commands/dx.js +538 -0
- package/src/cli/commands/plugin-hub.js +875 -0
- package/src/cli/doctor-command.js +639 -0
- package/src/cli/enhanced-cli-commands.js +945 -0
- package/src/cli/interactive-wizard.js +997 -0
- package/src/cli/plugin-marketplace-commands.js +869 -0
- package/src/cli.js +17 -0
- package/src/config/enhanced-ragrc-schema.js +632 -0
- package/src/config/load-config.js +54 -0
- package/src/config/load-plugin-config.js +69 -0
- package/src/config/normalize-config.js +105 -0
- package/src/config/schema-validator.js +315 -0
- package/src/config/validate-plugin-schema.js +35 -0
- package/src/config/validate-schema.js +113 -0
- package/src/contracts/plugin-contracts.js +33 -0
- package/src/core/create-pipeline.js +99 -0
- package/src/core/dependency-injection.js +242 -0
- package/src/core/observability/event-logger.js +241 -0
- package/src/core/observability/instrumented-pipeline.js +609 -0
- package/src/core/observability/metrics.js +497 -0
- package/src/core/observability/tracing.js +781 -0
- package/src/core/performance/benchmark.js +516 -0
- package/src/core/performance/parallel-processor.js +240 -0
- package/src/core/performance/streaming-safeguards.js +611 -0
- package/src/core/pipeline-factory.js +60 -0
- package/src/core/plugin-contracts.js +89 -0
- package/src/core/plugin-marketplace/plugin-metadata.js +485 -0
- package/src/core/plugin-marketplace/plugin-publisher.js +574 -0
- package/src/core/plugin-marketplace/plugin-registry-format.js +419 -0
- package/src/core/plugin-marketplace/version-resolver.js +61 -0
- package/src/core/plugin-registry.js +265 -0
- package/src/dag/dag-engine.js +954 -0
- package/src/dag/dag-node.js +76 -0
- package/src/dag/enhanced-dag-engine.js +822 -0
- package/src/dx/index.js +40 -0
- package/src/dx/integration-templates.js +665 -0
- package/src/dx/performance-profiler.js +968 -0
- package/src/dx/realtime-debugger.js +755 -0
- package/src/dx/visual-pipeline-builder.js +578 -0
- package/src/ecosystem/plugin-analytics-dashboard.js +778 -0
- package/src/ecosystem/plugin-certification.js +580 -0
- package/src/ecosystem/plugin-hub.js +821 -0
- package/src/enterprise/audit-logging.js +967 -0
- package/src/enterprise/data-governance.js +408 -0
- package/src/enterprise/multi-tenancy.js +496 -0
- package/src/enterprise/sso-integration.js +903 -0
- package/src/evaluate/evaluator.js +52 -0
- package/src/evaluate/scoring.js +91 -0
- package/src/index.js +67 -0
- package/src/ingest.js +59 -0
- package/src/loader/csv-loader.js +38 -0
- package/src/loader/directory-loader.js +48 -0
- package/src/loader/html-loader.js +62 -0
- package/src/loader/markdown-loader.js +67 -0
- package/src/mocks/openai-embedder.js +119 -0
- package/src/mocks/openai-llm.js +77 -0
- package/src/mocks/pdf-loader.js +21 -0
- package/src/mocks/pinecone-retriever.js +106 -0
- package/src/observability/audit-logger.js +758 -0
- package/src/observability/dashboard-config.json +675 -0
- package/src/observability/event-logger.js +310 -0
- package/src/observability/metrics.js +691 -0
- package/src/observability/security-monitor.js +830 -0
- package/src/observability/slo-monitor.js +319 -0
- package/src/observability/telemetry.js +686 -0
- package/src/pipeline/pipeline.js +18 -0
- package/src/plugins/sample-embedder.js +53 -0
- package/src/plugins/sample-llm.js +30 -0
- package/src/plugins/sample-loader.js +24 -0
- package/src/plugins/sample-retriever.js +84 -0
- package/src/query.js +117 -0
- package/src/reranker/llm-reranker.js +85 -0
- package/src/scripts/script-utilities.js +36 -0
- package/src/security/plugin-sandbox.js +586 -0
- package/src/security/plugin-signature-verifier.js +414 -0
- package/src/security/validator.js +36 -0
- package/src/utils/ci/diagnostic-reporter.js +57 -0
- package/src/utils/logger.js +211 -0
- package/src/utils/performance-monitor.js +561 -0
- package/src/utils/plugin-scaffolder.js +447 -0
- package/src/utils/retry-policy.js +443 -0
- package/src/utils/retry.js +107 -0
- package/src/utils/safe-expression-evaluator.js +579 -0
- package/src/utils/structured-logger.js +361 -0
- 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
|
+
};
|