cachegate 1.3.1 → 1.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/.env.example +83 -4
- package/README.md +154 -15
- package/cache.js +133 -9
- package/cascade.js +206 -0
- package/coalescing.js +65 -0
- package/embeddings.js +78 -16
- package/guardrails.js +80 -0
- package/metrics.js +89 -5
- package/package.json +12 -2
- package/pii.js +128 -0
- package/providers/deepseek.js +194 -0
- package/providers/index.js +62 -0
- package/providers/openai.js +10 -2
- package/providers/openrouter.js +233 -0
- package/public/dashboard.html +1 -0
- package/router.js +33 -4
- package/semanticCache.js +115 -3
- package/server.js +494 -118
- package/tracing.js +97 -0
package/tracing.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// model-router/tracing.js
|
|
2
|
+
//
|
|
3
|
+
// OpenTelemetry export (roadmap step 36.2). Builds on the request-scoped
|
|
4
|
+
// `trace_id` that server.js generates (step 36.1): that id is set as a span
|
|
5
|
+
// attribute so a trace in an OTel backend and a metrics.js row for the same
|
|
6
|
+
// request can be cross-referenced by the same value even though they are two
|
|
7
|
+
// different systems.
|
|
8
|
+
//
|
|
9
|
+
// Gated off by default (OTEL_ENABLED). The @opentelemetry/api no-op tracer
|
|
10
|
+
// is always present (it is the API surface and costs nothing when the SDK is
|
|
11
|
+
// not initialized), but the heavy SDK + exporter are lazily required and only
|
|
12
|
+
// started when BOTH OTEL_ENABLED=true AND OTEL_EXPORTER_OTLP_ENDPOINT is set -
|
|
13
|
+
// "never initializes at all" when unused, not "initializes and exports
|
|
14
|
+
// nowhere". Spans here are MANUAL ONLY: auto-instrumentation is explicitly out
|
|
15
|
+
// of scope (spec 220), so no registerInstrumentations() call.
|
|
16
|
+
|
|
17
|
+
const { trace, context } = require('@opentelemetry/api');
|
|
18
|
+
|
|
19
|
+
const TRACER_NAME = 'cachegate';
|
|
20
|
+
|
|
21
|
+
let initialized = false;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Initialize the OTel SDK exactly once. A no-op unless both gates are open.
|
|
25
|
+
* Any SDK failure is logged and swallowed (fail-open): tracing must never be
|
|
26
|
+
* the reason a real request fails.
|
|
27
|
+
*/
|
|
28
|
+
function initTracing() {
|
|
29
|
+
if (initialized) return;
|
|
30
|
+
initialized = true;
|
|
31
|
+
if (process.env.OTEL_ENABLED !== 'true') return;
|
|
32
|
+
const endpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
|
|
33
|
+
if (!endpoint) return;
|
|
34
|
+
try {
|
|
35
|
+
// Lazy-require the SDK + exporter only on the enabled path, so a
|
|
36
|
+
// deployment that never opts in never loads them.
|
|
37
|
+
const { NodeSDK } = require('@opentelemetry/sdk-node');
|
|
38
|
+
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
|
|
39
|
+
const sdk = new NodeSDK({
|
|
40
|
+
traceExporter: new OTLPTraceExporter({ url: endpoint })
|
|
41
|
+
// No instrumentations: manual spans only (spec 220, out of scope).
|
|
42
|
+
});
|
|
43
|
+
sdk.start();
|
|
44
|
+
process.on('SIGTERM', () => {
|
|
45
|
+
sdk.shutdown().finally(() => process.exit(0));
|
|
46
|
+
});
|
|
47
|
+
} catch (err) {
|
|
48
|
+
console.warn('⚠️ OTel SDK failed to initialize, tracing disabled:', err.message);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Start a span as a child of whatever span is currently active. With a no-op
|
|
54
|
+
* tracer (SDK off) this is a no-op object whose methods are all safe no-ops.
|
|
55
|
+
*/
|
|
56
|
+
function startSpan(name, attributes = {}) {
|
|
57
|
+
return trace.getTracer(TRACER_NAME).startSpan(name, { attributes });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Run `fn` inside a span named `name`, ending it when `fn` settles. The span
|
|
62
|
+
* is a child of the active span (the request's root span, set via
|
|
63
|
+
* withRootSpan). Does NOT make the new span itself active - none of the
|
|
64
|
+
* wrapped operations start their own nested spans, so there is nothing to
|
|
65
|
+
* nest. Re-throws on failure after recording the exception on the span.
|
|
66
|
+
*/
|
|
67
|
+
async function withSpan(name, attributes, fn) {
|
|
68
|
+
const span = startSpan(name, attributes);
|
|
69
|
+
try {
|
|
70
|
+
return await fn();
|
|
71
|
+
} catch (err) {
|
|
72
|
+
span.recordException(err);
|
|
73
|
+
throw err;
|
|
74
|
+
} finally {
|
|
75
|
+
span.end();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Run `fn` inside a NEW root span (the one span per request), making it the
|
|
81
|
+
* active context so every `withSpan`/`startSpan` inside nests under it.
|
|
82
|
+
* `trace_id` is carried as a span attribute so this trace can be joined to
|
|
83
|
+
* metrics rows for the same request.
|
|
84
|
+
*/
|
|
85
|
+
async function withRootSpan(name, attributes, fn) {
|
|
86
|
+
const span = startSpan(name, attributes);
|
|
87
|
+
try {
|
|
88
|
+
return await context.with(trace.setSpan(context.active(), span), fn);
|
|
89
|
+
} catch (err) {
|
|
90
|
+
span.recordException(err);
|
|
91
|
+
throw err;
|
|
92
|
+
} finally {
|
|
93
|
+
span.end();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = { initTracing, startSpan, withSpan, withRootSpan };
|