@juspay/neurolink 9.24.0 → 9.25.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.
- package/CHANGELOG.md +12 -0
- package/dist/adapters/tts/googleTTSHandler.js +26 -1
- package/dist/adapters/video/vertexVideoHandler.js +23 -17
- package/dist/cli/commands/config.d.ts +3 -3
- package/dist/cli/commands/observability.d.ts +53 -0
- package/dist/cli/commands/observability.js +453 -0
- package/dist/cli/commands/telemetry.d.ts +63 -0
- package/dist/cli/commands/telemetry.js +689 -0
- package/dist/cli/factories/commandFactory.js +29 -15
- package/dist/cli/parser.js +6 -9
- package/dist/cli/utils/formatters.d.ts +13 -0
- package/dist/cli/utils/formatters.js +23 -0
- package/dist/constants/contextWindows.js +6 -0
- package/dist/constants/enums.d.ts +6 -0
- package/dist/constants/enums.js +8 -2
- package/dist/context/budgetChecker.js +75 -48
- package/dist/context/contextCompactor.js +135 -127
- package/dist/core/baseProvider.d.ts +5 -0
- package/dist/core/baseProvider.js +117 -110
- package/dist/core/conversationMemoryInitializer.js +7 -4
- package/dist/core/conversationMemoryManager.d.ts +2 -0
- package/dist/core/conversationMemoryManager.js +6 -2
- package/dist/core/modules/GenerationHandler.d.ts +2 -2
- package/dist/core/modules/GenerationHandler.js +12 -12
- package/dist/evaluation/ragasEvaluator.js +39 -19
- package/dist/evaluation/scoring.js +46 -20
- package/dist/features/ppt/presentationOrchestrator.js +23 -0
- package/dist/features/ppt/slideGenerator.js +13 -0
- package/dist/features/ppt/slideRenderers.d.ts +1 -1
- package/dist/features/ppt/slideRenderers.js +6 -4
- package/dist/features/ppt/slideTypeInference.d.ts +1 -1
- package/dist/features/ppt/slideTypeInference.js +75 -73
- package/dist/files/fileTools.d.ts +6 -6
- package/dist/index.d.ts +46 -12
- package/dist/index.js +79 -17
- package/dist/lib/adapters/tts/googleTTSHandler.js +26 -1
- package/dist/lib/adapters/video/vertexVideoHandler.js +23 -17
- package/dist/lib/constants/contextWindows.js +6 -0
- package/dist/lib/constants/enums.d.ts +6 -0
- package/dist/lib/constants/enums.js +8 -2
- package/dist/lib/context/budgetChecker.js +75 -48
- package/dist/lib/context/contextCompactor.js +135 -127
- package/dist/lib/core/baseProvider.d.ts +5 -0
- package/dist/lib/core/baseProvider.js +117 -110
- package/dist/lib/core/conversationMemoryInitializer.js +7 -4
- package/dist/lib/core/conversationMemoryManager.d.ts +2 -0
- package/dist/lib/core/conversationMemoryManager.js +6 -2
- package/dist/lib/core/modules/GenerationHandler.d.ts +2 -2
- package/dist/lib/core/modules/GenerationHandler.js +12 -12
- package/dist/lib/evaluation/ragasEvaluator.js +39 -19
- package/dist/lib/evaluation/scoring.js +46 -20
- package/dist/lib/features/ppt/presentationOrchestrator.js +23 -0
- package/dist/lib/features/ppt/slideGenerator.js +13 -0
- package/dist/lib/features/ppt/slideRenderers.d.ts +1 -1
- package/dist/lib/features/ppt/slideRenderers.js +6 -4
- package/dist/lib/features/ppt/slideTypeInference.d.ts +1 -1
- package/dist/lib/features/ppt/slideTypeInference.js +75 -73
- package/dist/lib/files/fileTools.d.ts +6 -6
- package/dist/lib/index.d.ts +46 -12
- package/dist/lib/index.js +79 -17
- package/dist/lib/mcp/httpRateLimiter.js +39 -12
- package/dist/lib/mcp/httpRetryHandler.js +22 -1
- package/dist/lib/mcp/mcpClientFactory.js +13 -15
- package/dist/lib/memory/memoryRetrievalTools.js +22 -0
- package/dist/lib/neurolink.d.ts +64 -72
- package/dist/lib/neurolink.js +1007 -564
- package/dist/lib/observability/exporterRegistry.d.ts +152 -0
- package/dist/lib/observability/exporterRegistry.js +414 -0
- package/dist/lib/observability/exporters/arizeExporter.d.ts +32 -0
- package/dist/lib/observability/exporters/arizeExporter.js +139 -0
- package/dist/lib/observability/exporters/baseExporter.d.ts +117 -0
- package/dist/lib/observability/exporters/baseExporter.js +191 -0
- package/dist/lib/observability/exporters/braintrustExporter.d.ts +30 -0
- package/dist/lib/observability/exporters/braintrustExporter.js +155 -0
- package/dist/lib/observability/exporters/datadogExporter.d.ts +37 -0
- package/dist/lib/observability/exporters/datadogExporter.js +197 -0
- package/dist/lib/observability/exporters/index.d.ts +13 -0
- package/dist/lib/observability/exporters/index.js +14 -0
- package/dist/lib/observability/exporters/laminarExporter.d.ts +48 -0
- package/dist/lib/observability/exporters/laminarExporter.js +303 -0
- package/dist/lib/observability/exporters/langfuseExporter.d.ts +47 -0
- package/dist/lib/observability/exporters/langfuseExporter.js +204 -0
- package/dist/lib/observability/exporters/langsmithExporter.d.ts +26 -0
- package/dist/lib/observability/exporters/langsmithExporter.js +124 -0
- package/dist/lib/observability/exporters/otelExporter.d.ts +39 -0
- package/dist/lib/observability/exporters/otelExporter.js +165 -0
- package/dist/lib/observability/exporters/posthogExporter.d.ts +48 -0
- package/dist/lib/observability/exporters/posthogExporter.js +288 -0
- package/dist/lib/observability/exporters/sentryExporter.d.ts +32 -0
- package/dist/lib/observability/exporters/sentryExporter.js +166 -0
- package/dist/lib/observability/index.d.ts +25 -0
- package/dist/lib/observability/index.js +32 -0
- package/dist/lib/observability/metricsAggregator.d.ts +260 -0
- package/dist/lib/observability/metricsAggregator.js +557 -0
- package/dist/lib/observability/otelBridge.d.ts +49 -0
- package/dist/lib/observability/otelBridge.js +132 -0
- package/dist/lib/observability/retryPolicy.d.ts +192 -0
- package/dist/lib/observability/retryPolicy.js +384 -0
- package/dist/lib/observability/sampling/index.d.ts +4 -0
- package/dist/lib/observability/sampling/index.js +5 -0
- package/dist/lib/observability/sampling/samplers.d.ts +116 -0
- package/dist/lib/observability/sampling/samplers.js +217 -0
- package/dist/lib/observability/spanProcessor.d.ts +129 -0
- package/dist/lib/observability/spanProcessor.js +304 -0
- package/dist/lib/observability/tokenTracker.d.ts +156 -0
- package/dist/lib/observability/tokenTracker.js +414 -0
- package/dist/lib/observability/types/exporterTypes.d.ts +250 -0
- package/dist/lib/observability/types/exporterTypes.js +6 -0
- package/dist/lib/observability/types/index.d.ts +6 -0
- package/dist/lib/observability/types/index.js +5 -0
- package/dist/lib/observability/types/spanTypes.d.ts +244 -0
- package/dist/lib/observability/types/spanTypes.js +93 -0
- package/dist/lib/observability/utils/index.d.ts +4 -0
- package/dist/lib/observability/utils/index.js +5 -0
- package/dist/lib/observability/utils/safeMetadata.d.ts +10 -0
- package/dist/lib/observability/utils/safeMetadata.js +26 -0
- package/dist/lib/observability/utils/spanSerializer.d.ts +115 -0
- package/dist/lib/observability/utils/spanSerializer.js +291 -0
- package/dist/lib/providers/amazonSagemaker.d.ts +5 -4
- package/dist/lib/providers/amazonSagemaker.js +3 -4
- package/dist/lib/providers/googleVertex.d.ts +7 -0
- package/dist/lib/providers/googleVertex.js +76 -2
- package/dist/lib/rag/pipeline/RAGPipeline.d.ts +0 -5
- package/dist/lib/rag/pipeline/RAGPipeline.js +122 -87
- package/dist/lib/rag/ragIntegration.js +30 -0
- package/dist/lib/rag/retrieval/hybridSearch.js +22 -0
- package/dist/lib/server/abstract/baseServerAdapter.js +51 -19
- package/dist/lib/server/middleware/common.js +44 -12
- package/dist/lib/services/server/ai/observability/instrumentation.d.ts +2 -2
- package/dist/lib/services/server/ai/observability/instrumentation.js +10 -5
- package/dist/lib/types/conversationMemoryInterface.d.ts +2 -0
- package/dist/lib/types/modelTypes.d.ts +18 -18
- package/dist/lib/types/providers.d.ts +5 -0
- package/dist/lib/utils/pricing.js +25 -1
- package/dist/lib/utils/ttsProcessor.js +74 -59
- package/dist/lib/workflow/config.d.ts +36 -36
- package/dist/lib/workflow/core/ensembleExecutor.js +10 -0
- package/dist/lib/workflow/core/judgeScorer.js +20 -2
- package/dist/lib/workflow/core/workflowRunner.js +34 -1
- package/dist/mcp/httpRateLimiter.js +39 -12
- package/dist/mcp/httpRetryHandler.js +22 -1
- package/dist/mcp/mcpClientFactory.js +13 -15
- package/dist/memory/memoryRetrievalTools.js +22 -0
- package/dist/neurolink.d.ts +64 -72
- package/dist/neurolink.js +1007 -564
- package/dist/observability/FEATURE-STATUS.md +269 -0
- package/dist/observability/exporterRegistry.d.ts +152 -0
- package/dist/observability/exporterRegistry.js +413 -0
- package/dist/observability/exporters/arizeExporter.d.ts +32 -0
- package/dist/observability/exporters/arizeExporter.js +138 -0
- package/dist/observability/exporters/baseExporter.d.ts +117 -0
- package/dist/observability/exporters/baseExporter.js +190 -0
- package/dist/observability/exporters/braintrustExporter.d.ts +30 -0
- package/dist/observability/exporters/braintrustExporter.js +154 -0
- package/dist/observability/exporters/datadogExporter.d.ts +37 -0
- package/dist/observability/exporters/datadogExporter.js +196 -0
- package/dist/observability/exporters/index.d.ts +13 -0
- package/dist/observability/exporters/index.js +13 -0
- package/dist/observability/exporters/laminarExporter.d.ts +48 -0
- package/dist/observability/exporters/laminarExporter.js +302 -0
- package/dist/observability/exporters/langfuseExporter.d.ts +47 -0
- package/dist/observability/exporters/langfuseExporter.js +203 -0
- package/dist/observability/exporters/langsmithExporter.d.ts +26 -0
- package/dist/observability/exporters/langsmithExporter.js +123 -0
- package/dist/observability/exporters/otelExporter.d.ts +39 -0
- package/dist/observability/exporters/otelExporter.js +164 -0
- package/dist/observability/exporters/posthogExporter.d.ts +48 -0
- package/dist/observability/exporters/posthogExporter.js +287 -0
- package/dist/observability/exporters/sentryExporter.d.ts +32 -0
- package/dist/observability/exporters/sentryExporter.js +165 -0
- package/dist/observability/index.d.ts +25 -0
- package/dist/observability/index.js +31 -0
- package/dist/observability/metricsAggregator.d.ts +260 -0
- package/dist/observability/metricsAggregator.js +556 -0
- package/dist/observability/otelBridge.d.ts +49 -0
- package/dist/observability/otelBridge.js +131 -0
- package/dist/observability/retryPolicy.d.ts +192 -0
- package/dist/observability/retryPolicy.js +383 -0
- package/dist/observability/sampling/index.d.ts +4 -0
- package/dist/observability/sampling/index.js +4 -0
- package/dist/observability/sampling/samplers.d.ts +116 -0
- package/dist/observability/sampling/samplers.js +216 -0
- package/dist/observability/spanProcessor.d.ts +129 -0
- package/dist/observability/spanProcessor.js +303 -0
- package/dist/observability/tokenTracker.d.ts +156 -0
- package/dist/observability/tokenTracker.js +413 -0
- package/dist/observability/types/exporterTypes.d.ts +250 -0
- package/dist/observability/types/exporterTypes.js +5 -0
- package/dist/observability/types/index.d.ts +6 -0
- package/dist/observability/types/index.js +4 -0
- package/dist/observability/types/spanTypes.d.ts +244 -0
- package/dist/observability/types/spanTypes.js +92 -0
- package/dist/observability/utils/index.d.ts +4 -0
- package/dist/observability/utils/index.js +4 -0
- package/dist/observability/utils/safeMetadata.d.ts +10 -0
- package/dist/observability/utils/safeMetadata.js +25 -0
- package/dist/observability/utils/spanSerializer.d.ts +115 -0
- package/dist/observability/utils/spanSerializer.js +290 -0
- package/dist/providers/amazonSagemaker.d.ts +5 -4
- package/dist/providers/amazonSagemaker.js +3 -4
- package/dist/providers/googleVertex.d.ts +7 -0
- package/dist/providers/googleVertex.js +76 -2
- package/dist/rag/pipeline/RAGPipeline.d.ts +0 -5
- package/dist/rag/pipeline/RAGPipeline.js +122 -87
- package/dist/rag/ragIntegration.js +30 -0
- package/dist/rag/retrieval/hybridSearch.js +22 -0
- package/dist/server/abstract/baseServerAdapter.js +51 -19
- package/dist/server/middleware/common.js +44 -12
- package/dist/services/server/ai/observability/instrumentation.d.ts +2 -2
- package/dist/services/server/ai/observability/instrumentation.js +10 -5
- package/dist/types/conversationMemoryInterface.d.ts +2 -0
- package/dist/types/providers.d.ts +5 -0
- package/dist/utils/pricing.js +25 -1
- package/dist/utils/ttsProcessor.js +74 -59
- package/dist/workflow/config.d.ts +52 -52
- package/dist/workflow/core/ensembleExecutor.js +10 -0
- package/dist/workflow/core/judgeScorer.js +20 -2
- package/dist/workflow/core/workflowRunner.js +34 -1
- package/package.json +1 -1
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metrics Aggregator
|
|
3
|
+
* Comprehensive metrics aggregation with latency percentiles, token usage, and cost tracking
|
|
4
|
+
*/
|
|
5
|
+
import { TokenTracker } from "./tokenTracker.js";
|
|
6
|
+
/**
|
|
7
|
+
* Metrics Aggregator for comprehensive telemetry analysis
|
|
8
|
+
* Provides latency percentiles, token aggregation, and cost tracking
|
|
9
|
+
*/
|
|
10
|
+
export class MetricsAggregator {
|
|
11
|
+
spans = [];
|
|
12
|
+
latencyValues = [];
|
|
13
|
+
tokenTracker;
|
|
14
|
+
timeWindows = new Map();
|
|
15
|
+
config;
|
|
16
|
+
spansByType = new Map();
|
|
17
|
+
costByProvider = new Map();
|
|
18
|
+
costByModel = new Map();
|
|
19
|
+
successCount = 0;
|
|
20
|
+
failureCount = 0;
|
|
21
|
+
firstSpanTime;
|
|
22
|
+
lastSpanTime;
|
|
23
|
+
constructor(config = {}) {
|
|
24
|
+
this.config = {
|
|
25
|
+
maxSpansRetained: config.maxSpansRetained ?? 10000,
|
|
26
|
+
enableTimeWindows: config.enableTimeWindows ?? true,
|
|
27
|
+
timeWindowMs: config.timeWindowMs ?? 60000, // 1 minute default
|
|
28
|
+
maxTimeWindows: config.maxTimeWindows ?? 60, // 1 hour of 1-minute windows
|
|
29
|
+
};
|
|
30
|
+
this.tokenTracker = new TokenTracker();
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Record a span for metrics aggregation
|
|
34
|
+
*/
|
|
35
|
+
recordSpan(span) {
|
|
36
|
+
// Enforce maximum spans limit
|
|
37
|
+
if (this.spans.length >= this.config.maxSpansRetained) {
|
|
38
|
+
const evicted = this.spans.shift(); // Remove oldest span
|
|
39
|
+
// Only trim latencyValues when the evicted span had a duration recorded
|
|
40
|
+
if (evicted?.durationMs !== undefined) {
|
|
41
|
+
this.latencyValues.shift();
|
|
42
|
+
}
|
|
43
|
+
// Note: We keep aggregated metrics, only raw spans and latency values are trimmed
|
|
44
|
+
}
|
|
45
|
+
this.spans.push(span);
|
|
46
|
+
// Update timestamps
|
|
47
|
+
const spanTime = new Date(span.startTime);
|
|
48
|
+
if (!this.firstSpanTime || spanTime < this.firstSpanTime) {
|
|
49
|
+
this.firstSpanTime = spanTime;
|
|
50
|
+
}
|
|
51
|
+
if (!this.lastSpanTime || spanTime > this.lastSpanTime) {
|
|
52
|
+
this.lastSpanTime = spanTime;
|
|
53
|
+
}
|
|
54
|
+
// Track latency if duration is available
|
|
55
|
+
if (span.durationMs !== undefined) {
|
|
56
|
+
this.latencyValues.push(span.durationMs);
|
|
57
|
+
}
|
|
58
|
+
// Track success/failure
|
|
59
|
+
if (span.status === 2) {
|
|
60
|
+
// SpanStatus.ERROR = 2
|
|
61
|
+
this.failureCount++;
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
this.successCount++;
|
|
65
|
+
}
|
|
66
|
+
// Track by span type
|
|
67
|
+
const currentCount = this.spansByType.get(span.type) ?? 0;
|
|
68
|
+
this.spansByType.set(span.type, currentCount + 1);
|
|
69
|
+
// Track tokens via TokenTracker
|
|
70
|
+
this.tokenTracker.trackSpan(span);
|
|
71
|
+
// Track cost by provider and model
|
|
72
|
+
this.trackCosts(span);
|
|
73
|
+
// Update time window if enabled
|
|
74
|
+
if (this.config.enableTimeWindows) {
|
|
75
|
+
this.updateTimeWindow(span);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Track cost aggregations from a span
|
|
80
|
+
*/
|
|
81
|
+
trackCosts(span) {
|
|
82
|
+
const attrs = span.attributes;
|
|
83
|
+
const provider = attrs["ai.provider"];
|
|
84
|
+
const model = attrs["ai.model"];
|
|
85
|
+
const inputCost = attrs["ai.cost.input"] ?? 0;
|
|
86
|
+
const outputCost = attrs["ai.cost.output"] ?? 0;
|
|
87
|
+
const totalCost = attrs["ai.cost.total"] ?? inputCost + outputCost;
|
|
88
|
+
const inputTokens = attrs["ai.tokens.input"] ?? 0;
|
|
89
|
+
const outputTokens = attrs["ai.tokens.output"] ?? 0;
|
|
90
|
+
// Update provider costs
|
|
91
|
+
if (provider) {
|
|
92
|
+
const existing = this.costByProvider.get(provider) ?? {
|
|
93
|
+
provider,
|
|
94
|
+
totalCost: 0,
|
|
95
|
+
requestCount: 0,
|
|
96
|
+
avgCostPerRequest: 0,
|
|
97
|
+
inputCost: 0,
|
|
98
|
+
outputCost: 0,
|
|
99
|
+
};
|
|
100
|
+
existing.totalCost += totalCost;
|
|
101
|
+
existing.requestCount += 1;
|
|
102
|
+
existing.avgCostPerRequest = existing.totalCost / existing.requestCount;
|
|
103
|
+
existing.inputCost += inputCost;
|
|
104
|
+
existing.outputCost += outputCost;
|
|
105
|
+
this.costByProvider.set(provider, existing);
|
|
106
|
+
}
|
|
107
|
+
// Update model costs
|
|
108
|
+
if (model) {
|
|
109
|
+
const existing = this.costByModel.get(model) ?? {
|
|
110
|
+
model,
|
|
111
|
+
provider: provider ?? "unknown",
|
|
112
|
+
totalCost: 0,
|
|
113
|
+
requestCount: 0,
|
|
114
|
+
avgCostPerRequest: 0,
|
|
115
|
+
inputTokens: 0,
|
|
116
|
+
outputTokens: 0,
|
|
117
|
+
inputCost: 0,
|
|
118
|
+
outputCost: 0,
|
|
119
|
+
};
|
|
120
|
+
existing.totalCost += totalCost;
|
|
121
|
+
existing.requestCount += 1;
|
|
122
|
+
existing.avgCostPerRequest = existing.totalCost / existing.requestCount;
|
|
123
|
+
existing.inputTokens += inputTokens;
|
|
124
|
+
existing.outputTokens += outputTokens;
|
|
125
|
+
existing.inputCost += inputCost;
|
|
126
|
+
existing.outputCost += outputCost;
|
|
127
|
+
this.costByModel.set(model, existing);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Update time window statistics
|
|
132
|
+
*/
|
|
133
|
+
updateTimeWindow(span) {
|
|
134
|
+
const spanTime = new Date(span.startTime).getTime();
|
|
135
|
+
const windowKey = Math.floor(spanTime / this.config.timeWindowMs) *
|
|
136
|
+
this.config.timeWindowMs;
|
|
137
|
+
// Enforce maximum time windows
|
|
138
|
+
if (this.timeWindows.size >= this.config.maxTimeWindows &&
|
|
139
|
+
!this.timeWindows.has(windowKey)) {
|
|
140
|
+
// Remove oldest window
|
|
141
|
+
const oldestKey = Math.min(...Array.from(this.timeWindows.keys()));
|
|
142
|
+
this.timeWindows.delete(oldestKey);
|
|
143
|
+
}
|
|
144
|
+
let window = this.timeWindows.get(windowKey);
|
|
145
|
+
if (!window) {
|
|
146
|
+
window = this.createEmptyTimeWindow(windowKey);
|
|
147
|
+
this.timeWindows.set(windowKey, window);
|
|
148
|
+
}
|
|
149
|
+
// Update window stats
|
|
150
|
+
window.requestCount++;
|
|
151
|
+
if (span.status === 2) {
|
|
152
|
+
window.errorCount++;
|
|
153
|
+
}
|
|
154
|
+
window.successRate =
|
|
155
|
+
(window.requestCount - window.errorCount) / window.requestCount;
|
|
156
|
+
window.throughput = window.requestCount / (window.windowDurationMs / 1000);
|
|
157
|
+
// Update window end time
|
|
158
|
+
const spanEndTime = new Date(span.endTime ?? span.startTime);
|
|
159
|
+
if (spanEndTime > window.windowEnd) {
|
|
160
|
+
window.windowEnd = spanEndTime;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Create an empty time window
|
|
165
|
+
*/
|
|
166
|
+
createEmptyTimeWindow(windowKey) {
|
|
167
|
+
return {
|
|
168
|
+
windowStart: new Date(windowKey),
|
|
169
|
+
windowEnd: new Date(windowKey + this.config.timeWindowMs),
|
|
170
|
+
windowDurationMs: this.config.timeWindowMs,
|
|
171
|
+
requestCount: 0,
|
|
172
|
+
errorCount: 0,
|
|
173
|
+
successRate: 1,
|
|
174
|
+
throughput: 0,
|
|
175
|
+
latency: this.createEmptyLatencyStats(),
|
|
176
|
+
tokens: {
|
|
177
|
+
totalInputTokens: 0,
|
|
178
|
+
totalOutputTokens: 0,
|
|
179
|
+
totalTokens: 0,
|
|
180
|
+
cacheReadTokens: 0,
|
|
181
|
+
cacheCreationTokens: 0,
|
|
182
|
+
reasoningTokens: 0,
|
|
183
|
+
totalCost: 0,
|
|
184
|
+
byProvider: new Map(),
|
|
185
|
+
byModel: new Map(),
|
|
186
|
+
bySpanType: new Map(),
|
|
187
|
+
},
|
|
188
|
+
costByProvider: new Map(),
|
|
189
|
+
costByModel: new Map(),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Create empty latency stats
|
|
194
|
+
*/
|
|
195
|
+
createEmptyLatencyStats() {
|
|
196
|
+
return {
|
|
197
|
+
min: 0,
|
|
198
|
+
max: 0,
|
|
199
|
+
mean: 0,
|
|
200
|
+
median: 0,
|
|
201
|
+
p50: 0,
|
|
202
|
+
p75: 0,
|
|
203
|
+
p90: 0,
|
|
204
|
+
p95: 0,
|
|
205
|
+
p99: 0,
|
|
206
|
+
stdDev: 0,
|
|
207
|
+
count: 0,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Calculate latency percentile from sorted array
|
|
212
|
+
*/
|
|
213
|
+
calculatePercentile(sortedValues, percentile) {
|
|
214
|
+
if (sortedValues.length === 0) {
|
|
215
|
+
return 0;
|
|
216
|
+
}
|
|
217
|
+
const index = Math.ceil((percentile / 100) * sortedValues.length) - 1;
|
|
218
|
+
return sortedValues[Math.max(0, index)] ?? 0;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Calculate standard deviation
|
|
222
|
+
*/
|
|
223
|
+
calculateStdDev(values, mean) {
|
|
224
|
+
if (values.length < 2) {
|
|
225
|
+
return 0;
|
|
226
|
+
}
|
|
227
|
+
const squaredDiffs = values.map((v) => (v - mean) ** 2);
|
|
228
|
+
const avgSquaredDiff = squaredDiffs.reduce((a, b) => a + b, 0) / values.length;
|
|
229
|
+
return Math.sqrt(avgSquaredDiff);
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Get comprehensive latency statistics
|
|
233
|
+
*/
|
|
234
|
+
getLatencyStats() {
|
|
235
|
+
if (this.latencyValues.length === 0) {
|
|
236
|
+
return this.createEmptyLatencyStats();
|
|
237
|
+
}
|
|
238
|
+
const sorted = [...this.latencyValues].sort((a, b) => a - b);
|
|
239
|
+
const sum = sorted.reduce((a, b) => a + b, 0);
|
|
240
|
+
const mean = sum / sorted.length;
|
|
241
|
+
return {
|
|
242
|
+
min: sorted[0] ?? 0,
|
|
243
|
+
max: sorted[sorted.length - 1] ?? 0,
|
|
244
|
+
mean,
|
|
245
|
+
median: this.calculatePercentile(sorted, 50),
|
|
246
|
+
p50: this.calculatePercentile(sorted, 50),
|
|
247
|
+
p75: this.calculatePercentile(sorted, 75),
|
|
248
|
+
p90: this.calculatePercentile(sorted, 90),
|
|
249
|
+
p95: this.calculatePercentile(sorted, 95),
|
|
250
|
+
p99: this.calculatePercentile(sorted, 99),
|
|
251
|
+
stdDev: this.calculateStdDev(sorted, mean),
|
|
252
|
+
count: sorted.length,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Get token usage statistics
|
|
257
|
+
*/
|
|
258
|
+
getTokenStats() {
|
|
259
|
+
return this.tokenTracker.getStats();
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Get cost breakdown by provider
|
|
263
|
+
*/
|
|
264
|
+
getCostByProvider() {
|
|
265
|
+
return Array.from(this.costByProvider.values());
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Get cost breakdown by model
|
|
269
|
+
*/
|
|
270
|
+
getCostByModel() {
|
|
271
|
+
return Array.from(this.costByModel.values());
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Get total cost across all providers
|
|
275
|
+
*/
|
|
276
|
+
getTotalCost() {
|
|
277
|
+
let total = 0;
|
|
278
|
+
const providerStatsArray = Array.from(this.costByProvider.values());
|
|
279
|
+
for (const stats of providerStatsArray) {
|
|
280
|
+
total += stats.totalCost;
|
|
281
|
+
}
|
|
282
|
+
return total;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Get time window statistics
|
|
286
|
+
*/
|
|
287
|
+
getTimeWindows() {
|
|
288
|
+
return Array.from(this.timeWindows.values()).sort((a, b) => a.windowStart.getTime() - b.windowStart.getTime());
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Get statistics for a specific time range
|
|
292
|
+
*/
|
|
293
|
+
getStatsForTimeRange(startTime, endTime) {
|
|
294
|
+
const relevantSpans = this.spans.filter((span) => {
|
|
295
|
+
const spanTime = new Date(span.startTime);
|
|
296
|
+
return spanTime >= startTime && spanTime <= endTime;
|
|
297
|
+
});
|
|
298
|
+
// Create a temporary aggregator for this time range
|
|
299
|
+
const tempAggregator = new MetricsAggregator({
|
|
300
|
+
enableTimeWindows: false,
|
|
301
|
+
});
|
|
302
|
+
for (const span of relevantSpans) {
|
|
303
|
+
tempAggregator.recordSpan(span);
|
|
304
|
+
}
|
|
305
|
+
const summary = tempAggregator.getSummary();
|
|
306
|
+
const durationMs = endTime.getTime() - startTime.getTime();
|
|
307
|
+
return {
|
|
308
|
+
windowStart: startTime,
|
|
309
|
+
windowEnd: endTime,
|
|
310
|
+
windowDurationMs: durationMs,
|
|
311
|
+
requestCount: summary.totalSpans,
|
|
312
|
+
errorCount: summary.failedSpans,
|
|
313
|
+
successRate: summary.successRate,
|
|
314
|
+
throughput: summary.totalSpans / (durationMs / 1000),
|
|
315
|
+
latency: summary.latency,
|
|
316
|
+
tokens: summary.tokens,
|
|
317
|
+
costByProvider: new Map(summary.costByProvider.map((p) => [p.provider, p])),
|
|
318
|
+
costByModel: new Map(summary.costByModel.map((m) => [m.model, m])),
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Record a latency measurement for an operation
|
|
323
|
+
* Use this for standalone latency tracking without a full span
|
|
324
|
+
*/
|
|
325
|
+
recordLatency(operation, latencyMs) {
|
|
326
|
+
this.latencyValues.push(latencyMs);
|
|
327
|
+
// Track by operation type (similar to span type)
|
|
328
|
+
const currentCount = this.spansByType.get(operation) ?? 0;
|
|
329
|
+
this.spansByType.set(operation, currentCount + 1);
|
|
330
|
+
// Update timestamps
|
|
331
|
+
const now = new Date();
|
|
332
|
+
if (!this.firstSpanTime) {
|
|
333
|
+
this.firstSpanTime = now;
|
|
334
|
+
}
|
|
335
|
+
this.lastSpanTime = now;
|
|
336
|
+
// Increment success count (standalone latency records are assumed successful)
|
|
337
|
+
this.successCount++;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Get comprehensive metrics summary (alias for getSummary)
|
|
341
|
+
*/
|
|
342
|
+
getMetrics() {
|
|
343
|
+
return this.getSummary();
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Get comprehensive metrics summary
|
|
347
|
+
*/
|
|
348
|
+
getSummary() {
|
|
349
|
+
const totalSpans = this.successCount + this.failureCount;
|
|
350
|
+
return {
|
|
351
|
+
totalSpans,
|
|
352
|
+
successfulSpans: this.successCount,
|
|
353
|
+
failedSpans: this.failureCount,
|
|
354
|
+
successRate: totalSpans > 0 ? this.successCount / totalSpans : 1,
|
|
355
|
+
latency: this.getLatencyStats(),
|
|
356
|
+
tokens: this.getTokenStats(),
|
|
357
|
+
costByProvider: this.getCostByProvider(),
|
|
358
|
+
costByModel: this.getCostByModel(),
|
|
359
|
+
totalCost: this.getTotalCost(),
|
|
360
|
+
spansByType: Object.fromEntries(this.spansByType),
|
|
361
|
+
firstSpanTime: this.firstSpanTime,
|
|
362
|
+
lastSpanTime: this.lastSpanTime,
|
|
363
|
+
trackingDurationMs: this.firstSpanTime && this.lastSpanTime
|
|
364
|
+
? this.lastSpanTime.getTime() - this.firstSpanTime.getTime()
|
|
365
|
+
: undefined,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Get all recorded spans (returns a copy)
|
|
370
|
+
*/
|
|
371
|
+
getSpans() {
|
|
372
|
+
return [...this.spans];
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Get spans grouped by traceId as hierarchical trace views
|
|
376
|
+
*/
|
|
377
|
+
getTraces() {
|
|
378
|
+
const traceMap = new Map();
|
|
379
|
+
for (const span of this.spans) {
|
|
380
|
+
const existing = traceMap.get(span.traceId) || [];
|
|
381
|
+
existing.push(span);
|
|
382
|
+
traceMap.set(span.traceId, existing);
|
|
383
|
+
}
|
|
384
|
+
const traces = [];
|
|
385
|
+
for (const [traceId, spans] of traceMap) {
|
|
386
|
+
// Root span is the one with no parentSpanId, or first span if all have parents
|
|
387
|
+
const rootSpan = spans.find((s) => !s.parentSpanId) || spans[0];
|
|
388
|
+
const childSpans = spans.filter((s) => s !== rootSpan);
|
|
389
|
+
// Calculate total duration
|
|
390
|
+
let totalDurationMs = 0;
|
|
391
|
+
for (const s of spans) {
|
|
392
|
+
if (s.durationMs) {
|
|
393
|
+
totalDurationMs = Math.max(totalDurationMs, s.durationMs);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
// Determine status
|
|
397
|
+
const hasError = spans.some((s) => s.status === 2); // SpanStatus.ERROR
|
|
398
|
+
const allOk = spans.every((s) => s.status === 1); // SpanStatus.OK
|
|
399
|
+
const status = hasError
|
|
400
|
+
? "error"
|
|
401
|
+
: allOk
|
|
402
|
+
? "ok"
|
|
403
|
+
: "partial";
|
|
404
|
+
traces.push({
|
|
405
|
+
traceId,
|
|
406
|
+
rootSpan,
|
|
407
|
+
childSpans,
|
|
408
|
+
totalDurationMs,
|
|
409
|
+
spanCount: spans.length,
|
|
410
|
+
status,
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
return traces;
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Get the underlying token tracker for custom pricing configuration
|
|
417
|
+
*/
|
|
418
|
+
getTokenTracker() {
|
|
419
|
+
return this.tokenTracker;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Reset all metrics
|
|
423
|
+
*/
|
|
424
|
+
reset() {
|
|
425
|
+
this.spans = [];
|
|
426
|
+
this.latencyValues = [];
|
|
427
|
+
this.tokenTracker.reset();
|
|
428
|
+
this.timeWindows.clear();
|
|
429
|
+
this.spansByType.clear();
|
|
430
|
+
this.costByProvider.clear();
|
|
431
|
+
this.costByModel.clear();
|
|
432
|
+
this.successCount = 0;
|
|
433
|
+
this.failureCount = 0;
|
|
434
|
+
this.firstSpanTime = undefined;
|
|
435
|
+
this.lastSpanTime = undefined;
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Export metrics as JSON
|
|
439
|
+
*/
|
|
440
|
+
toJSON() {
|
|
441
|
+
const summary = this.getSummary();
|
|
442
|
+
return {
|
|
443
|
+
totalSpans: summary.totalSpans,
|
|
444
|
+
successfulSpans: summary.successfulSpans,
|
|
445
|
+
failedSpans: summary.failedSpans,
|
|
446
|
+
successRate: summary.successRate,
|
|
447
|
+
latency: summary.latency,
|
|
448
|
+
tokens: this.tokenTracker.toJSON(),
|
|
449
|
+
costByProvider: summary.costByProvider,
|
|
450
|
+
costByModel: summary.costByModel,
|
|
451
|
+
totalCost: summary.totalCost,
|
|
452
|
+
spansByType: summary.spansByType,
|
|
453
|
+
firstSpanTime: summary.firstSpanTime?.toISOString(),
|
|
454
|
+
lastSpanTime: summary.lastSpanTime?.toISOString(),
|
|
455
|
+
trackingDurationMs: summary.trackingDurationMs,
|
|
456
|
+
timeWindows: this.config.enableTimeWindows
|
|
457
|
+
? this.getTimeWindows().map((w) => ({
|
|
458
|
+
windowStart: w.windowStart.toISOString(),
|
|
459
|
+
windowEnd: w.windowEnd.toISOString(),
|
|
460
|
+
requestCount: w.requestCount,
|
|
461
|
+
errorCount: w.errorCount,
|
|
462
|
+
successRate: w.successRate,
|
|
463
|
+
throughput: w.throughput,
|
|
464
|
+
}))
|
|
465
|
+
: undefined,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Format cost as currency string
|
|
470
|
+
*/
|
|
471
|
+
formatCost(cost, currency = "USD") {
|
|
472
|
+
return new Intl.NumberFormat("en-US", {
|
|
473
|
+
style: "currency",
|
|
474
|
+
currency,
|
|
475
|
+
minimumFractionDigits: 4,
|
|
476
|
+
}).format(cost);
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Get a formatted summary string
|
|
480
|
+
*/
|
|
481
|
+
getFormattedSummary() {
|
|
482
|
+
const summary = this.getSummary();
|
|
483
|
+
const latency = summary.latency;
|
|
484
|
+
const lines = [
|
|
485
|
+
"=== Metrics Summary ===",
|
|
486
|
+
"",
|
|
487
|
+
"Request Statistics:",
|
|
488
|
+
` Total requests: ${summary.totalSpans.toLocaleString()}`,
|
|
489
|
+
` Successful: ${summary.successfulSpans.toLocaleString()}`,
|
|
490
|
+
` Failed: ${summary.failedSpans.toLocaleString()}`,
|
|
491
|
+
` Success rate: ${(summary.successRate * 100).toFixed(2)}%`,
|
|
492
|
+
"",
|
|
493
|
+
"Latency (ms):",
|
|
494
|
+
` Min: ${latency.min.toFixed(2)}`,
|
|
495
|
+
` Max: ${latency.max.toFixed(2)}`,
|
|
496
|
+
` Mean: ${latency.mean.toFixed(2)}`,
|
|
497
|
+
` P50: ${latency.p50.toFixed(2)}`,
|
|
498
|
+
` P95: ${latency.p95.toFixed(2)}`,
|
|
499
|
+
` P99: ${latency.p99.toFixed(2)}`,
|
|
500
|
+
"",
|
|
501
|
+
"Token Usage:",
|
|
502
|
+
` Input tokens: ${summary.tokens.totalInputTokens.toLocaleString()}`,
|
|
503
|
+
` Output tokens: ${summary.tokens.totalOutputTokens.toLocaleString()}`,
|
|
504
|
+
` Total tokens: ${summary.tokens.totalTokens.toLocaleString()}`,
|
|
505
|
+
"",
|
|
506
|
+
"Cost:",
|
|
507
|
+
` Total: ${this.formatCost(summary.totalCost)}`,
|
|
508
|
+
];
|
|
509
|
+
// Add cost by provider
|
|
510
|
+
if (summary.costByProvider.length > 0) {
|
|
511
|
+
lines.push("");
|
|
512
|
+
lines.push("Cost by Provider:");
|
|
513
|
+
for (const providerCost of summary.costByProvider) {
|
|
514
|
+
lines.push(` ${providerCost.provider}: ${this.formatCost(providerCost.totalCost)} (${providerCost.requestCount} requests)`);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
// Add cost by model
|
|
518
|
+
if (summary.costByModel.length > 0) {
|
|
519
|
+
lines.push("");
|
|
520
|
+
lines.push("Cost by Model:");
|
|
521
|
+
for (const modelCost of summary.costByModel) {
|
|
522
|
+
lines.push(` ${modelCost.model}: ${this.formatCost(modelCost.totalCost)} (${modelCost.requestCount} requests)`);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
// Add tracking duration
|
|
526
|
+
if (summary.trackingDurationMs) {
|
|
527
|
+
const durationSec = summary.trackingDurationMs / 1000;
|
|
528
|
+
const throughput = summary.totalSpans / durationSec;
|
|
529
|
+
lines.push("");
|
|
530
|
+
lines.push(`Tracking duration: ${durationSec.toFixed(1)}s (${throughput.toFixed(2)} req/s)`);
|
|
531
|
+
}
|
|
532
|
+
return lines.join("\n");
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Global metrics aggregator instance (singleton pattern from main)
|
|
537
|
+
*/
|
|
538
|
+
let globalMetricsAggregator = null;
|
|
539
|
+
/**
|
|
540
|
+
* Get the global metrics aggregator instance
|
|
541
|
+
*/
|
|
542
|
+
export function getMetricsAggregator() {
|
|
543
|
+
if (!globalMetricsAggregator) {
|
|
544
|
+
globalMetricsAggregator = new MetricsAggregator();
|
|
545
|
+
}
|
|
546
|
+
return globalMetricsAggregator;
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Reset the global metrics aggregator (for testing)
|
|
550
|
+
*/
|
|
551
|
+
export function resetMetricsAggregator() {
|
|
552
|
+
if (globalMetricsAggregator) {
|
|
553
|
+
globalMetricsAggregator.reset();
|
|
554
|
+
}
|
|
555
|
+
globalMetricsAggregator = null;
|
|
556
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenTelemetry Bridge
|
|
3
|
+
* Bidirectional context propagation between NeuroLink and OpenTelemetry
|
|
4
|
+
*/
|
|
5
|
+
import { type SpanContext } from "@opentelemetry/api";
|
|
6
|
+
import type { SpanData } from "./types/spanTypes.js";
|
|
7
|
+
import { type SpanType } from "./types/spanTypes.js";
|
|
8
|
+
/**
|
|
9
|
+
* Bridge for bidirectional context propagation between
|
|
10
|
+
* NeuroLink's observability system and OpenTelemetry
|
|
11
|
+
*/
|
|
12
|
+
export declare class OtelBridge {
|
|
13
|
+
private readonly tracer;
|
|
14
|
+
/**
|
|
15
|
+
* Extract trace context from incoming request headers
|
|
16
|
+
*/
|
|
17
|
+
extractContext(headers: Record<string, string>): SpanContext | null;
|
|
18
|
+
/**
|
|
19
|
+
* Inject trace context into outgoing request headers
|
|
20
|
+
*/
|
|
21
|
+
injectContext(headers: Record<string, string>): Record<string, string>;
|
|
22
|
+
/**
|
|
23
|
+
* Create a NeuroLink span from OpenTelemetry context
|
|
24
|
+
*/
|
|
25
|
+
createSpanFromOtelContext(spanContext: SpanContext, type: SpanType, name: string): SpanData;
|
|
26
|
+
/**
|
|
27
|
+
* Wrap a function with OpenTelemetry tracing that also creates NeuroLink spans
|
|
28
|
+
*/
|
|
29
|
+
wrapWithTracing<T>(name: string, type: SpanType, fn: (span: SpanData) => Promise<T>, onSpanEnd?: (span: SpanData) => void): Promise<T>;
|
|
30
|
+
/**
|
|
31
|
+
* Convert NeuroLink span to OpenTelemetry span and export
|
|
32
|
+
*/
|
|
33
|
+
exportToOtel(span: SpanData): void;
|
|
34
|
+
/**
|
|
35
|
+
* Get current trace context for correlation
|
|
36
|
+
*/
|
|
37
|
+
getCurrentTraceContext(): {
|
|
38
|
+
traceId: string;
|
|
39
|
+
spanId: string;
|
|
40
|
+
} | null;
|
|
41
|
+
/**
|
|
42
|
+
* Filter attributes to only include OTel-compatible types
|
|
43
|
+
*/
|
|
44
|
+
private filterAttributes;
|
|
45
|
+
/**
|
|
46
|
+
* Filter event attributes
|
|
47
|
+
*/
|
|
48
|
+
private filterEventAttributes;
|
|
49
|
+
}
|