@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.
- package/README.md +293 -361
- package/contracts/loader-contract.json +11 -6
- package/contracts/reranker-contract.json +18 -26
- package/dist/index.cjs +21190 -47
- package/dist/index.d.ts +298 -68
- package/dist/index.mjs +9 -76
- package/package.json +20 -8
- package/src/agentic/agentic-pipeline.js +235 -0
- package/src/agentic/iterative-retriever.js +213 -0
- package/src/agentic/query-planner.js +208 -0
- package/src/agentic/self-critique.js +102 -0
- package/src/ai/adaptive-retrieval.js +0 -3
- package/src/ai/federated-learning.js +0 -3
- package/src/ai/federation.js +1 -6
- package/src/ai/index.js +9 -20
- package/src/ai/model-training.js +0 -3
- package/src/ai/multimodal-processing.js +0 -3
- package/src/ai/multimodal.js +1 -6
- package/src/ai/orchestrator.js +1 -6
- package/src/ai/retrieval-engine.js +1 -6
- package/src/chunking/chunking-engine.js +142 -0
- package/src/chunking/strategies/fixed-size.js +54 -0
- package/src/chunking/strategies/recursive.js +127 -0
- package/src/chunking/strategies/semantic.js +128 -0
- package/src/chunking/strategies/sentence.js +32 -0
- package/src/chunking/strategies/structure-aware.js +138 -0
- package/src/citation/citation-tracker.js +161 -0
- package/src/citation/hallucination-detector.js +74 -0
- package/src/citation/source-mapper.js +95 -0
- package/src/cli/commands/ai-ml/adaptive-commands.js +178 -0
- package/src/cli/commands/ai-ml/federated-commands.js +207 -0
- package/src/cli/commands/ai-ml/index.js +17 -0
- package/src/cli/commands/ai-ml/multimodal-commands.js +187 -0
- package/src/cli/commands/ai-ml/shared.js +51 -0
- package/src/cli/commands/ai-ml/training-commands.js +228 -0
- package/src/cli/commands/ai-ml.js +176 -757
- package/src/cli/commands/plugin-hub/certification-commands.js +221 -0
- package/src/cli/commands/plugin-hub/event-handlers.js +64 -0
- package/src/cli/commands/plugin-hub/hub-commands.js +290 -0
- package/src/cli/commands/plugin-hub/review-commands.js +78 -0
- package/src/cli/commands/plugin-hub.js +106 -782
- package/src/cli/diagnostics/config-checker.js +81 -0
- package/src/cli/diagnostics/dependency-checker.js +69 -0
- package/src/cli/diagnostics/environment-checker.js +45 -0
- package/src/cli/diagnostics/performance-checker.js +51 -0
- package/src/cli/diagnostics/plugin-checker.js +106 -0
- package/src/cli/diagnostics/security-checker.js +61 -0
- package/src/cli/doctor-command.js +70 -369
- package/src/cli/enhanced-cli-commands.js +162 -706
- package/src/cli/handlers/config-handler.js +126 -0
- package/src/cli/handlers/diagnostics-handler.js +113 -0
- package/src/cli/handlers/ingest-handler.js +42 -0
- package/src/cli/handlers/init-handler.js +51 -0
- package/src/cli/handlers/query-handler.js +45 -0
- package/src/cli/handlers/run-handler.js +181 -0
- package/src/cli/interactive-wizard.js +99 -723
- package/src/cli/shared/cli-utils.js +89 -0
- package/src/cli/wizard/configuration.js +148 -0
- package/src/cli/wizard/observability.js +79 -0
- package/src/cli/wizard/performance.js +76 -0
- package/src/cli/wizard/plugin-selection.js +297 -0
- package/src/cli/wizard/preview-save.js +172 -0
- package/src/cli/wizard/project-setup.js +66 -0
- package/src/config/memory-optimization.js +3 -3
- package/src/connectors/anthropic-connector.js +179 -0
- package/src/connectors/base-connector.js +94 -0
- package/src/connectors/cohere-connector.js +143 -0
- package/src/connectors/local-embedder.js +171 -0
- package/src/connectors/memory-retriever.js +150 -0
- package/src/connectors/ollama-connector.js +205 -0
- package/src/connectors/openai-connector.js +173 -0
- package/src/core/create-pipeline.js +21 -1
- package/src/core/observability/tracing.js +2 -1
- package/src/core/pipeline-factory.js +0 -1
- package/src/core/plugin-registry.js +14 -14
- package/src/cost/cost-calculator.js +146 -0
- package/src/cost/cost-tracker.js +260 -0
- package/src/cost/token-budget.js +245 -0
- package/src/dag/core/execution-scheduler.js +2 -1
- package/src/dag/core/topology-validation.js +0 -3
- package/src/dag/dag-engine.js +3 -5
- package/src/dag/dag-node.js +0 -1
- package/src/dag/enhanced-dag-engine.js +171 -99
- package/src/dashboard/dashboard-generator.js +642 -0
- package/src/dashboard/metrics-aggregator.js +409 -0
- package/src/debug/execution-tracer.js +210 -0
- package/src/debug/trace-inspector.js +281 -0
- package/src/dev/dev-server.js +5 -5
- package/src/dev/hot-reload.js +3 -2
- package/src/dx/integration-templates.js +0 -3
- package/src/ecosystem/plugin-analytics-dashboard.js +2 -2
- package/src/ecosystem/plugin-hub.js +2 -2
- package/src/enterprise/audit-logging.js +2 -2
- package/src/enterprise/sso-integration.js +52 -32
- package/src/evaluate/context-metrics.js +127 -0
- package/src/evaluate/evaluator.js +4 -2
- package/src/evaluate/faithfulness.js +117 -0
- package/src/evaluate/groundedness.js +60 -0
- package/src/evaluate/pipeline-evaluator.js +156 -0
- package/src/evaluate/relevance.js +129 -0
- package/src/evaluate/scoring.js +27 -0
- package/src/graph/entity-extractor.js +183 -0
- package/src/graph/graph-index.js +113 -0
- package/src/graph/graph-retriever.js +244 -0
- package/src/graph/knowledge-graph.js +256 -0
- package/src/guardrails/guardrails-pipeline.js +136 -0
- package/src/guardrails/post-generation-guard.js +170 -0
- package/src/guardrails/pre-retrieval-guard.js +161 -0
- package/src/guardrails/retrieval-guard.js +126 -0
- package/src/index.js +255 -9
- package/src/loader/html-loader.js +4 -15
- package/src/loader/markdown-loader.js +4 -15
- package/src/mcp/mcp-server.js +199 -0
- package/src/mcp/mcp-tool-builder.js +121 -0
- package/src/observability/audit-logger.js +0 -2
- package/src/observability/event-logger.js +2 -1
- package/src/observability/security-monitor.js +0 -2
- package/src/observability/telemetry.js +0 -2
- package/src/reranker/cascade-reranker.js +79 -0
- package/src/reranker/embedding-reranker.js +124 -0
- package/src/reranker/llm-reranker.js +6 -3
- package/src/reranker/scoring-reranker.js +123 -0
- package/src/retrieval/bm25-search.js +157 -0
- package/src/retrieval/hybrid-retriever.js +111 -0
- package/src/retrieval/rank-fusion.js +62 -0
- package/src/security/jwt-config.js +4 -3
- package/src/security/jwt-validator.js +2 -1
- package/src/security/plugin-sandbox.js +0 -3
- package/src/security/plugin-signature-verifier.js +4 -6
- package/src/security/validator.js +0 -2
- package/src/streaming/embedding-stream.js +197 -0
- package/src/streaming/sse-adapter.js +168 -0
- package/src/streaming/stream-router.js +210 -0
- package/src/streaming/streaming-embedder.js +231 -0
- package/src/streaming/websocket-adapter.js +171 -0
- package/src/templates/scaffold.js +147 -0
- package/src/templates/template-registry.js +272 -0
- package/src/utils/contract-validator.js +2 -1
- package/src/utils/input-sanitizer.js +80 -99
- package/src/utils/performance-monitor.js +0 -3
- package/src/utils/plugin-scaffolder.js +9 -16
- package/src/utils/rate-limiter.js +0 -1
- package/src/utils/retry-policy.js +0 -3
- package/src/utils/safe-expression-evaluator.js +0 -3
- package/src/utils/secure-logger-config.js +2 -4
- package/src/utils/secure-logging.js +0 -3
- package/src/utils/structured-logger.js +0 -3
- package/src/ai/index-fixed.js +0 -509
- package/src/ai/module-template.js +0 -37
- package/src/ci/workflow-analyzer.js +0 -71
- package/src/cli/plugin-marketplace-commands.js +0 -869
- package/src/dev/README.md +0 -126
- package/src/dev/examples/hot-reload-usage.js +0 -355
- package/src/mocks/openai-embedder.js +0 -119
- package/src/mocks/openai-llm.js +0 -77
- package/src/mocks/pdf-loader.js +0 -21
- package/src/mocks/pinecone-retriever.js +0 -106
- package/src/scripts/script-utilities.js +0 -36
- package/src/utils/ci/diagnostic-reporter.js +0 -57
- package/src/utils/examples/error-formatter-usage.js +0 -351
|
@@ -0,0 +1,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 };
|