lynkr 9.11.0 → 9.12.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 +46 -11
- package/bin/statusline.js +80 -0
- package/bin/wrap.js +10 -6
- package/package.json +8 -3
- package/public/dashboard.html +325 -1
- package/src/api/openai-router.js +120 -2
- package/src/clients/databricks.js +28 -1
- package/src/dashboard/api.js +78 -2
- package/src/dashboard/recommendations/cache-weakspots.js +88 -0
- package/src/dashboard/recommendations/deadweight.js +82 -0
- package/src/dashboard/recommendations/downsize.js +109 -0
- package/src/dashboard/recommendations/index.js +106 -0
- package/src/dashboard/recommendations/side-requests.js +68 -0
- package/src/dashboard/recommendations/wilson.js +24 -0
- package/src/dashboard/router.js +5 -0
- package/src/orchestrator/sse-transformer.js +9 -3
- package/src/routing/complexity-analyzer.js +30 -9
- package/src/routing/telemetry.js +206 -2
package/src/dashboard/api.js
CHANGED
|
@@ -208,8 +208,12 @@ function routing(req, res) {
|
|
|
208
208
|
// Cache-aware routing (Phase 6): per-decision switch/hold economics,
|
|
209
209
|
// aggregated into "cache dollars saved by routing".
|
|
210
210
|
const cacheEconomics = telemetry.getCacheEconomics({ since });
|
|
211
|
+
// Lens money-framing: the MEASURED companion — cache reads actually
|
|
212
|
+
// served this window, auditable against the bill (vs the projected
|
|
213
|
+
// figures above). null until cache counters have accrued.
|
|
214
|
+
const cacheMeasured = telemetry.getMeasuredCacheSavings({ since });
|
|
211
215
|
|
|
212
|
-
res.json({ tierDefinitions: TIER_DEFINITIONS, accuracy, stats, providerStats, circuitBreakers: cbStates, cacheEconomics, window: win.label });
|
|
216
|
+
res.json({ tierDefinitions: TIER_DEFINITIONS, accuracy, stats, providerStats, circuitBreakers: cbStates, cacheEconomics, cacheMeasured, window: win.label });
|
|
213
217
|
} catch (e) {
|
|
214
218
|
res.status(500).json({ error: 'routing_api_error', detail: e.message });
|
|
215
219
|
}
|
|
@@ -233,4 +237,76 @@ function logs(req, res) {
|
|
|
233
237
|
}
|
|
234
238
|
}
|
|
235
239
|
|
|
236
|
-
|
|
240
|
+
/* ── Lens endpoints (feature/lens-dashboard) ─────────────────────────── */
|
|
241
|
+
|
|
242
|
+
// Recommendations — analyzer findings ranked by past overspend.
|
|
243
|
+
function recommendations(req, res) {
|
|
244
|
+
try {
|
|
245
|
+
const engine = require('./recommendations');
|
|
246
|
+
const windowMs = req.query.window === '30d' ? 30 * 86400000
|
|
247
|
+
: req.query.window === '24h' ? 86400000
|
|
248
|
+
: 7 * 86400000;
|
|
249
|
+
res.json(engine.run({ windowMs, force: req.query.force === 'true' }));
|
|
250
|
+
} catch (e) {
|
|
251
|
+
res.status(500).json({ error: 'recommendations_api_error', detail: e.message });
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Session drill-down: per-model mix + context-growth series + raw rows.
|
|
256
|
+
function sessionDetail(req, res) {
|
|
257
|
+
try {
|
|
258
|
+
const detail = telemetry.getSessionDetail(req.params.id);
|
|
259
|
+
if (!detail) return res.status(404).json({ error: 'session_not_found' });
|
|
260
|
+
res.json(detail);
|
|
261
|
+
} catch (e) {
|
|
262
|
+
res.status(500).json({ error: 'session_api_error', detail: e.message });
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Pivot explorer: metric × dimension (× stack), whitelisted server-side.
|
|
267
|
+
function analytics(req, res) {
|
|
268
|
+
try {
|
|
269
|
+
const days = Math.min(90, Math.max(1, parseInt(req.query.days || '7', 10) || 7));
|
|
270
|
+
const data = telemetry.getAnalytics({
|
|
271
|
+
metric: req.query.metric,
|
|
272
|
+
by: req.query.by,
|
|
273
|
+
stack: req.query.stack || null,
|
|
274
|
+
since: Date.now() - days * 86400000,
|
|
275
|
+
});
|
|
276
|
+
if (!data) return res.status(503).json({ error: 'telemetry_unavailable' });
|
|
277
|
+
res.json(data);
|
|
278
|
+
} catch (e) {
|
|
279
|
+
res.status(500).json({ error: 'analytics_api_error', detail: e.message });
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Statusline: one cheap snapshot for the zero-token status line — last
|
|
284
|
+
// routed request, today's spend, measured cache re-read share.
|
|
285
|
+
function statusline(req, res) {
|
|
286
|
+
try {
|
|
287
|
+
const last = telemetry.query({ limit: 1 })[0] ?? null;
|
|
288
|
+
const since = Date.now() - 86400000;
|
|
289
|
+
const db = telemetry.getDb();
|
|
290
|
+
let today = null;
|
|
291
|
+
if (db) {
|
|
292
|
+
today = db.prepare(
|
|
293
|
+
`SELECT SUM(COALESCE(cost_usd,0)) spend,
|
|
294
|
+
SUM(COALESCE(cache_read_tokens,0)) rd,
|
|
295
|
+
SUM(COALESCE(cache_read_tokens,0)+COALESCE(cache_creation_tokens,0)+COALESCE(input_tokens,0)) total
|
|
296
|
+
FROM routing_telemetry WHERE timestamp > ?`
|
|
297
|
+
).get(since);
|
|
298
|
+
}
|
|
299
|
+
res.json({
|
|
300
|
+
last: last && {
|
|
301
|
+
tier: last.tier, provider: last.provider, model: last.model,
|
|
302
|
+
pinned: !!last.pinned, at: last.timestamp,
|
|
303
|
+
},
|
|
304
|
+
todaySpendUsd: today?.spend ?? null,
|
|
305
|
+
cacheReadPct: today && today.total > 0 ? Math.round((today.rd / today.total) * 100) : null,
|
|
306
|
+
});
|
|
307
|
+
} catch (e) {
|
|
308
|
+
res.status(500).json({ error: 'statusline_api_error', detail: e.message });
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
module.exports = { overview, usage, routing, logs, recommendations, sessionDetail, analytics, statusline };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cache-weakspots — prompt-cache hit ratio per (provider, model), worst first.
|
|
3
|
+
*
|
|
4
|
+
* Reads the per-request cache counters (cache_read_tokens /
|
|
5
|
+
* cache_creation_tokens). Rows recorded before capture began are NULL and
|
|
6
|
+
* excluded — when nothing has been measured yet the finding says so
|
|
7
|
+
* explicitly instead of reporting a fake all-clear.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const MIN_REQUESTS = 5;
|
|
11
|
+
const WEAK_RATIO = 0.5;
|
|
12
|
+
|
|
13
|
+
module.exports = {
|
|
14
|
+
id: 'cache-weakspots',
|
|
15
|
+
title: 'Weak prompt-cache hit ratios',
|
|
16
|
+
|
|
17
|
+
analyze({ db, since }) {
|
|
18
|
+
const rows = db
|
|
19
|
+
.prepare(
|
|
20
|
+
`SELECT provider, model, COUNT(*) n,
|
|
21
|
+
SUM(COALESCE(cache_read_tokens,0)) rd,
|
|
22
|
+
SUM(COALESCE(cache_creation_tokens,0)) cr,
|
|
23
|
+
SUM(COALESCE(input_tokens,0)) inp
|
|
24
|
+
FROM routing_telemetry
|
|
25
|
+
WHERE timestamp > ? AND cache_read_tokens IS NOT NULL
|
|
26
|
+
GROUP BY provider, model
|
|
27
|
+
HAVING COUNT(*) >= ${MIN_REQUESTS}`
|
|
28
|
+
)
|
|
29
|
+
.all(since);
|
|
30
|
+
|
|
31
|
+
if (!rows.length) {
|
|
32
|
+
return {
|
|
33
|
+
id: this.id, title: this.title, mechanism: 'info', state: 'not_measured',
|
|
34
|
+
framing: 'usd', pastOverspendUsd: null, pastOverspendTokens: null,
|
|
35
|
+
stats: [], evidence: { columns: [], rows: [] }, fix: null,
|
|
36
|
+
caveat: null,
|
|
37
|
+
estimateBasis: 'Cache counters are captured from this version onward — data accrues as requests flow. An un-run measurement is not an all-clear.',
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const { resolveCacheEconomics } = require('../../routing/cache-economics');
|
|
42
|
+
const weak = [];
|
|
43
|
+
let usd = 0;
|
|
44
|
+
for (const r of rows) {
|
|
45
|
+
const total = r.rd + r.cr + r.inp;
|
|
46
|
+
const ratio = total > 0 ? r.rd / total : 0;
|
|
47
|
+
if (ratio >= WEAK_RATIO) continue;
|
|
48
|
+
const econ = resolveCacheEconomics(r.provider, r.model);
|
|
49
|
+
// Ceiling: what the uncached input cost beyond cache-read price.
|
|
50
|
+
const missedUsd = econ.unknownPricing || econ.mechanism === 'local'
|
|
51
|
+
? null
|
|
52
|
+
: (r.inp * Math.max(0, econ.inputPerM - econ.cacheReadPerM)) / 1_000_000;
|
|
53
|
+
if (missedUsd != null) usd += missedUsd;
|
|
54
|
+
weak.push([r.provider, r.model, r.n, `${Math.round(ratio * 100)}%`, r.inp, missedUsd ?? '(local/unknown)']);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!weak.length) {
|
|
58
|
+
return {
|
|
59
|
+
id: this.id, title: this.title, mechanism: 'info', state: 'clean',
|
|
60
|
+
framing: 'usd', pastOverspendUsd: 0, pastOverspendTokens: null,
|
|
61
|
+
stats: [{ label: 'models measured', value: rows.length }],
|
|
62
|
+
evidence: { columns: [], rows: [] }, fix: null, caveat: null,
|
|
63
|
+
estimateBasis: `Hit ratio = cache reads ÷ (reads + writes + uncached input); weak = below ${WEAK_RATIO * 100}%.`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
id: this.id,
|
|
69
|
+
title: this.title,
|
|
70
|
+
mechanism: 'info',
|
|
71
|
+
state: 'actionable',
|
|
72
|
+
framing: 'usd',
|
|
73
|
+
pastOverspendUsd: usd,
|
|
74
|
+
pastOverspendTokens: null,
|
|
75
|
+
stats: [
|
|
76
|
+
{ label: 'weak (provider, model) pairs', value: weak.length },
|
|
77
|
+
{ label: 'models measured', value: rows.length },
|
|
78
|
+
],
|
|
79
|
+
evidence: {
|
|
80
|
+
columns: ['provider', 'model', 'requests', 'hit ratio', 'uncached input tokens', 'ceiling $'],
|
|
81
|
+
rows: weak.sort((a, b) => parseFloat(a[3]) - parseFloat(b[3])),
|
|
82
|
+
},
|
|
83
|
+
fix: 'Low ratios usually mean an unstable prefix: per-request bytes in the system prompt, client-side history rewrites, or conversations too short to cache. Check the silent-invalidator list and whether the distiller freeze covers these sessions.',
|
|
84
|
+
caveat: 'The dollar figure is a CEILING — it prices every uncached input token at the read discount, which assumes perfect cacheability. Real recovery is lower.',
|
|
85
|
+
estimateBasis: 'Uncached input tokens × (input price − cache-read price) for pairs below a 50% hit ratio, this window. NULL-counter rows (pre-capture) excluded.',
|
|
86
|
+
};
|
|
87
|
+
},
|
|
88
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* deadweight — tool schemas carried on every request but never called.
|
|
3
|
+
*
|
|
4
|
+
* Telemetry stores per-request tool COUNTS, not names, so the detectable
|
|
5
|
+
* unit is the session: sessions whose every request shipped a tool loadout
|
|
6
|
+
* and whose tool_calls_made never left zero paid schema rent for nothing.
|
|
7
|
+
* (Per-tool attribution needs tool names in telemetry — future work.)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const EST_TOKENS_PER_TOOL = 150; // typical JSON schema; stated in estimateBasis
|
|
11
|
+
const MIN_REQUESTS = 3;
|
|
12
|
+
|
|
13
|
+
module.exports = {
|
|
14
|
+
id: 'deadweight',
|
|
15
|
+
title: 'Tool schemas carried but never used',
|
|
16
|
+
|
|
17
|
+
analyze({ db, since }) {
|
|
18
|
+
const rows = db
|
|
19
|
+
.prepare(
|
|
20
|
+
`SELECT session_id,
|
|
21
|
+
COUNT(*) reqs,
|
|
22
|
+
AVG(COALESCE(tool_count,0)) avg_tools,
|
|
23
|
+
MAX(model) model,
|
|
24
|
+
MAX(provider) provider,
|
|
25
|
+
SUM(COALESCE(cost_usd,0)) session_cost
|
|
26
|
+
FROM routing_telemetry
|
|
27
|
+
WHERE timestamp > ? AND session_id IS NOT NULL AND COALESCE(tool_count,0) > 0
|
|
28
|
+
GROUP BY session_id
|
|
29
|
+
HAVING SUM(COALESCE(tool_calls_made,0)) = 0 AND COUNT(*) >= ${MIN_REQUESTS}
|
|
30
|
+
ORDER BY reqs * avg_tools DESC
|
|
31
|
+
LIMIT 50`
|
|
32
|
+
)
|
|
33
|
+
.all(since);
|
|
34
|
+
|
|
35
|
+
if (!rows.length) {
|
|
36
|
+
return {
|
|
37
|
+
id: this.id, title: this.title, mechanism: 'snippet', state: 'clean',
|
|
38
|
+
framing: 'usd', pastOverspendUsd: 0, pastOverspendTokens: 0,
|
|
39
|
+
stats: [], evidence: { columns: [], rows: [] },
|
|
40
|
+
fix: null, caveat: null,
|
|
41
|
+
estimateBasis: 'Sessions with >=3 requests, a tool loadout on every request, and zero tool calls.',
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const { resolveCacheEconomics } = require('../../routing/cache-economics');
|
|
46
|
+
let tokens = 0;
|
|
47
|
+
let usd = 0;
|
|
48
|
+
const evidence = [];
|
|
49
|
+
for (const r of rows) {
|
|
50
|
+
const wastedTokens = Math.round(r.avg_tools * EST_TOKENS_PER_TOOL * r.reqs);
|
|
51
|
+
tokens += wastedTokens;
|
|
52
|
+
const econ = resolveCacheEconomics(r.provider, r.model);
|
|
53
|
+
const rowUsd = econ.unknownPricing ? null : (wastedTokens * econ.inputPerM) / 1_000_000;
|
|
54
|
+
if (rowUsd != null) usd += rowUsd;
|
|
55
|
+
evidence.push([
|
|
56
|
+
r.session_id, r.reqs, Math.round(r.avg_tools), wastedTokens,
|
|
57
|
+
rowUsd != null ? rowUsd : '(price unknown)',
|
|
58
|
+
]);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
id: this.id,
|
|
63
|
+
title: this.title,
|
|
64
|
+
mechanism: 'snippet',
|
|
65
|
+
state: 'actionable',
|
|
66
|
+
framing: 'usd',
|
|
67
|
+
pastOverspendUsd: usd,
|
|
68
|
+
pastOverspendTokens: tokens,
|
|
69
|
+
stats: [
|
|
70
|
+
{ label: 'sessions affected', value: rows.length },
|
|
71
|
+
{ label: 'est. schema tokens re-sent', value: tokens },
|
|
72
|
+
],
|
|
73
|
+
evidence: {
|
|
74
|
+
columns: ['session', 'requests', 'avg tools', 'est. wasted tokens', 'est. $'],
|
|
75
|
+
rows: evidence,
|
|
76
|
+
},
|
|
77
|
+
fix: 'These sessions never called a tool — their MCP servers/tool registrations paid schema rent on every request. Trim unused MCP servers from the client config, or scope them per-project.',
|
|
78
|
+
caveat: 'Schema size is estimated at ~150 tokens per tool; telemetry stores tool counts, not names, so attribution is per-session rather than per-tool.',
|
|
79
|
+
estimateBasis: 'avg tools × ~150 est. tokens/schema × requests per session, priced at each session\'s model input rate. Sessions with any tool call are excluded entirely.',
|
|
80
|
+
};
|
|
81
|
+
},
|
|
82
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* downsize — request types where the tier below has statistically proven
|
|
3
|
+
* itself, Wilson-bounded.
|
|
4
|
+
*
|
|
5
|
+
* The de-escalator's live rule uses raw averages (>=30 rows, avg quality
|
|
6
|
+
* >=70). This analyzer applies the stricter test TokenJam-bench uses for
|
|
7
|
+
* verdicts: the WILSON LOWER BOUND of the lower tier's success rate must
|
|
8
|
+
* clear the bar, so 21-of-30 (lower bound ~0.52) stays "unproven" while
|
|
9
|
+
* 140-of-170 (~0.76) is a verdict. Success = quality >= 70 and no error.
|
|
10
|
+
*
|
|
11
|
+
* No quality-equivalence claim is made — the verdict is "the lower tier's
|
|
12
|
+
* measured floor clears the bar on this request type", nothing more.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const { wilsonLowerBound } = require('./wilson');
|
|
16
|
+
|
|
17
|
+
const TIER_ORDER = ['SIMPLE', 'MEDIUM', 'COMPLEX', 'REASONING'];
|
|
18
|
+
const MIN_SAMPLES = 20;
|
|
19
|
+
const SUCCESS_QUALITY = 70;
|
|
20
|
+
const WILSON_BAR = 0.7;
|
|
21
|
+
const EVIDENCE_WINDOW_MS = 30 * 24 * 60 * 60 * 1000; // evidence looks back further than the spend window
|
|
22
|
+
|
|
23
|
+
module.exports = {
|
|
24
|
+
id: 'downsize',
|
|
25
|
+
title: 'Tiers that could step down (Wilson-proven)',
|
|
26
|
+
|
|
27
|
+
analyze({ db, since, until }) {
|
|
28
|
+
// Lower-tier track record per request_type over 30 days.
|
|
29
|
+
const evidence = db
|
|
30
|
+
.prepare(
|
|
31
|
+
`SELECT tier, request_type,
|
|
32
|
+
COUNT(*) n,
|
|
33
|
+
SUM(CASE WHEN COALESCE(quality_score,0) >= ${SUCCESS_QUALITY} AND error_type IS NULL THEN 1 ELSE 0 END) k,
|
|
34
|
+
AVG(COALESCE(cost_usd,0)) avg_cost
|
|
35
|
+
FROM routing_telemetry
|
|
36
|
+
WHERE timestamp > ? AND tier IS NOT NULL AND request_type IS NOT NULL
|
|
37
|
+
GROUP BY tier, request_type`
|
|
38
|
+
)
|
|
39
|
+
.all(until - EVIDENCE_WINDOW_MS);
|
|
40
|
+
|
|
41
|
+
const byKey = new Map(evidence.map((r) => [`${r.tier}::${r.request_type}`, r]));
|
|
42
|
+
|
|
43
|
+
// Spend at each upper tier per request_type, this window.
|
|
44
|
+
const upperSpend = db
|
|
45
|
+
.prepare(
|
|
46
|
+
`SELECT tier, request_type, COUNT(*) n, SUM(COALESCE(cost_usd,0)) cost, AVG(COALESCE(cost_usd,0)) avg_cost
|
|
47
|
+
FROM routing_telemetry
|
|
48
|
+
WHERE timestamp > ? AND tier IS NOT NULL AND request_type IS NOT NULL
|
|
49
|
+
GROUP BY tier, request_type`
|
|
50
|
+
)
|
|
51
|
+
.all(since);
|
|
52
|
+
|
|
53
|
+
const rows = [];
|
|
54
|
+
let usd = 0;
|
|
55
|
+
let anyEvidence = false;
|
|
56
|
+
for (const u of upperSpend) {
|
|
57
|
+
const idx = TIER_ORDER.indexOf(u.tier);
|
|
58
|
+
if (idx <= 0) continue;
|
|
59
|
+
const lower = byKey.get(`${TIER_ORDER[idx - 1]}::${u.request_type}`);
|
|
60
|
+
if (!lower) continue;
|
|
61
|
+
anyEvidence = true;
|
|
62
|
+
if (lower.n < MIN_SAMPLES) continue;
|
|
63
|
+
const bound = wilsonLowerBound(lower.k, lower.n);
|
|
64
|
+
if (bound < WILSON_BAR) continue;
|
|
65
|
+
// Empirical per-request price delta between the two tiers on the SAME
|
|
66
|
+
// request type — measured, not modeled.
|
|
67
|
+
const delta = Math.max(0, (u.avg_cost ?? 0) - (lower.avg_cost ?? 0)) * u.n;
|
|
68
|
+
usd += delta;
|
|
69
|
+
rows.push([
|
|
70
|
+
u.request_type, u.tier, `${TIER_ORDER[idx - 1]}`,
|
|
71
|
+
`${lower.k}/${lower.n}`, bound.toFixed(2), u.n, delta,
|
|
72
|
+
]);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!rows.length) {
|
|
76
|
+
return {
|
|
77
|
+
id: this.id, title: this.title, mechanism: 'info',
|
|
78
|
+
state: anyEvidence ? 'clean' : 'not_measured',
|
|
79
|
+
framing: 'usd', pastOverspendUsd: anyEvidence ? 0 : null, pastOverspendTokens: null,
|
|
80
|
+
stats: [],
|
|
81
|
+
evidence: { columns: [], rows: [] }, fix: null, caveat: null,
|
|
82
|
+
estimateBasis: anyEvidence
|
|
83
|
+
? `No (tier, request_type) pair has a lower tier whose Wilson lower bound clears ${WILSON_BAR} on >=${MIN_SAMPLES} samples yet.`
|
|
84
|
+
: 'Needs quality-scored telemetry with request_type populated — accrues as traffic flows (request_type capture is recent).',
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
rows.sort((a, b) => b[6] - a[6]);
|
|
89
|
+
return {
|
|
90
|
+
id: this.id,
|
|
91
|
+
title: this.title,
|
|
92
|
+
mechanism: 'info',
|
|
93
|
+
state: 'actionable',
|
|
94
|
+
framing: 'usd',
|
|
95
|
+
pastOverspendUsd: usd,
|
|
96
|
+
pastOverspendTokens: null,
|
|
97
|
+
stats: [
|
|
98
|
+
{ label: 'proven (request_type, tier) pairs', value: rows.length },
|
|
99
|
+
],
|
|
100
|
+
evidence: {
|
|
101
|
+
columns: ['request_type', 'served tier', 'proven lower tier', 'lower success', 'Wilson lower bound', 'requests this window', 'measured delta $'],
|
|
102
|
+
rows,
|
|
103
|
+
},
|
|
104
|
+
fix: 'The live de-escalator will demote these automatically as its own thresholds clear; to act sooner, adjust the TIER_* mapping for these request types or lower the de-escalator sample bar deliberately.',
|
|
105
|
+
caveat: 'Verdict is "the lower tier\'s measured success floor clears the bar" — not quality equivalence. Success = quality >= 70 with no error.',
|
|
106
|
+
estimateBasis: 'Per-request cost delta between tiers measured on the SAME request_type (30-day evidence window), × this window\'s upper-tier request count. Wilson 95% lower bound.',
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lens recommendations engine.
|
|
3
|
+
*
|
|
4
|
+
* Registry-driven analyzers over routing telemetry that answer "what should
|
|
5
|
+
* the operator change", TokenJam-style, with three disciplines borrowed
|
|
6
|
+
* deliberately:
|
|
7
|
+
*
|
|
8
|
+
* - PAST OVERSPEND, never projected savings: every dollar figure is
|
|
9
|
+
* backward-looking so the operator can check it against a bill already
|
|
10
|
+
* paid. Ceilings are labeled as ceilings in estimateBasis.
|
|
11
|
+
* - THREE-STATE HONESTY: 'actionable' (evidence found), 'clean' (ran,
|
|
12
|
+
* found nothing), 'not_measured' (data doesn't exist yet on this
|
|
13
|
+
* install). An un-run scan is not an all-clear.
|
|
14
|
+
* - MECHANISM badges: 'auto' (Lynkr could act), 'snippet' (client-side
|
|
15
|
+
* fix the operator applies), 'info' (diagnosis only).
|
|
16
|
+
*
|
|
17
|
+
* Analyzer contract: analyze({db, since, until}) -> finding
|
|
18
|
+
* {id, title, mechanism, state, framing: 'usd'|'tokens',
|
|
19
|
+
* pastOverspendUsd|null, pastOverspendTokens|null, stats: [{label,value}],
|
|
20
|
+
* evidence: {columns:[], rows:[][]}, fix, caveat, estimateBasis}
|
|
21
|
+
*
|
|
22
|
+
* The analyzers' overspend bases are disjoint by construction (dead tool
|
|
23
|
+
* schemas / harness side traffic / uncached input / tier price deltas), so
|
|
24
|
+
* the total is a plain sum — revisit if an analyzer is added whose basis
|
|
25
|
+
* overlaps an existing one.
|
|
26
|
+
*
|
|
27
|
+
* @module dashboard/recommendations
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
const logger = require('../../logger');
|
|
31
|
+
const telemetry = require('../../routing/telemetry');
|
|
32
|
+
|
|
33
|
+
const ANALYZERS = [
|
|
34
|
+
require('./deadweight'),
|
|
35
|
+
require('./side-requests'),
|
|
36
|
+
require('./cache-weakspots'),
|
|
37
|
+
require('./downsize'),
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const DEFAULT_WINDOW_MS = 7 * 24 * 60 * 60 * 1000;
|
|
41
|
+
const RESULT_TTL_MS = 45 * 1000;
|
|
42
|
+
|
|
43
|
+
let _cached = null;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Run all analyzers. Cached for 45s — findings shift slowly and the
|
|
47
|
+
* dashboard polls every 30s.
|
|
48
|
+
*
|
|
49
|
+
* @param {Object} [opts] {windowMs, force}
|
|
50
|
+
*/
|
|
51
|
+
function run(opts = {}) {
|
|
52
|
+
const windowMs = opts.windowMs ?? DEFAULT_WINDOW_MS;
|
|
53
|
+
if (!opts.force && _cached && Date.now() - _cached.computedAt < RESULT_TTL_MS
|
|
54
|
+
&& _cached.windowMs === windowMs) {
|
|
55
|
+
return _cached;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const db = telemetry.getDb();
|
|
59
|
+
const until = Date.now();
|
|
60
|
+
const since = until - windowMs;
|
|
61
|
+
|
|
62
|
+
const findings = [];
|
|
63
|
+
for (const analyzer of ANALYZERS) {
|
|
64
|
+
try {
|
|
65
|
+
const finding = db
|
|
66
|
+
? analyzer.analyze({ db, since, until })
|
|
67
|
+
: { id: analyzer.id, title: analyzer.title, state: 'not_measured', reason: 'telemetry unavailable' };
|
|
68
|
+
if (finding) findings.push(finding);
|
|
69
|
+
} catch (err) {
|
|
70
|
+
logger.debug({ analyzer: analyzer.id, err: err.message }, '[Recommendations] analyzer failed');
|
|
71
|
+
findings.push({ id: analyzer.id, title: analyzer.title, state: 'not_measured', reason: err.message });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Rank by dollars, biggest first; token-framed findings after usd ones.
|
|
76
|
+
findings.sort((a, b) => (b.pastOverspendUsd ?? -1) - (a.pastOverspendUsd ?? -1));
|
|
77
|
+
|
|
78
|
+
let spendUsd = 0;
|
|
79
|
+
try {
|
|
80
|
+
if (db) {
|
|
81
|
+
spendUsd = db
|
|
82
|
+
.prepare('SELECT SUM(COALESCE(cost_usd,0)) s FROM routing_telemetry WHERE timestamp > ?')
|
|
83
|
+
.get(since)?.s ?? 0;
|
|
84
|
+
}
|
|
85
|
+
} catch { /* spend share stays 0 */ }
|
|
86
|
+
|
|
87
|
+
const totalRecoverableUsd = findings
|
|
88
|
+
.filter((f) => f.state === 'actionable' && typeof f.pastOverspendUsd === 'number')
|
|
89
|
+
.reduce((acc, f) => acc + f.pastOverspendUsd, 0);
|
|
90
|
+
|
|
91
|
+
_cached = {
|
|
92
|
+
computedAt: Date.now(),
|
|
93
|
+
windowMs,
|
|
94
|
+
spendUsd,
|
|
95
|
+
totalRecoverableUsd,
|
|
96
|
+
findings,
|
|
97
|
+
};
|
|
98
|
+
return _cached;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Test helper — drop the memoized result. */
|
|
102
|
+
function _clearCacheForTests() {
|
|
103
|
+
_cached = null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = { run, ANALYZERS, _clearCacheForTests };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* side-requests — harness side traffic served above the SIMPLE tier.
|
|
3
|
+
*
|
|
4
|
+
* Coding harnesses replay the conversation for their own bookkeeping
|
|
5
|
+
* (title generation, topic detection). Those calls embed the user's text,
|
|
6
|
+
* so they score like the user's request and ride the same tier — observed
|
|
7
|
+
* live: a title request served by a frontier COMPLEX model. The whole
|
|
8
|
+
* category needs a tiny model.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const SIDE_PATTERNS = [
|
|
12
|
+
"Generate a title%",
|
|
13
|
+
"%Analyze if this message indicates a new conversation topic%",
|
|
14
|
+
"Please write a %commit message%",
|
|
15
|
+
"Summarize this conversation%",
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
module.exports = {
|
|
19
|
+
id: 'side-requests',
|
|
20
|
+
title: 'Harness side traffic on expensive tiers',
|
|
21
|
+
|
|
22
|
+
analyze({ db, since }) {
|
|
23
|
+
const where = SIDE_PATTERNS.map(() => 'request_text LIKE ?').join(' OR ');
|
|
24
|
+
const rows = db
|
|
25
|
+
.prepare(
|
|
26
|
+
`SELECT tier, provider, model, COUNT(*) n, SUM(COALESCE(cost_usd,0)) cost
|
|
27
|
+
FROM routing_telemetry
|
|
28
|
+
WHERE timestamp > ? AND COALESCE(tool_count,0) = 0 AND (${where})
|
|
29
|
+
GROUP BY tier, provider, model
|
|
30
|
+
ORDER BY cost DESC`
|
|
31
|
+
)
|
|
32
|
+
.all(since, ...SIDE_PATTERNS);
|
|
33
|
+
|
|
34
|
+
const above = rows.filter((r) => r.tier && r.tier !== 'SIMPLE');
|
|
35
|
+
const total = rows.reduce((a, r) => a + r.n, 0);
|
|
36
|
+
if (!above.length) {
|
|
37
|
+
return {
|
|
38
|
+
id: this.id, title: this.title, mechanism: 'auto', state: total > 0 ? 'clean' : 'clean',
|
|
39
|
+
framing: 'usd', pastOverspendUsd: 0, pastOverspendTokens: null,
|
|
40
|
+
stats: [{ label: 'side requests seen', value: total }],
|
|
41
|
+
evidence: { columns: [], rows: [] }, fix: null, caveat: null,
|
|
42
|
+
estimateBasis: 'Tool-less requests matching known harness bookkeeping prompts (title/topic/commit/summary).',
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const usd = above.reduce((a, r) => a + r.cost, 0);
|
|
47
|
+
return {
|
|
48
|
+
id: this.id,
|
|
49
|
+
title: this.title,
|
|
50
|
+
mechanism: 'auto',
|
|
51
|
+
state: 'actionable',
|
|
52
|
+
framing: 'usd',
|
|
53
|
+
pastOverspendUsd: usd,
|
|
54
|
+
pastOverspendTokens: null,
|
|
55
|
+
stats: [
|
|
56
|
+
{ label: 'side requests above SIMPLE', value: above.reduce((a, r) => a + r.n, 0) },
|
|
57
|
+
{ label: 'total side requests', value: total },
|
|
58
|
+
],
|
|
59
|
+
evidence: {
|
|
60
|
+
columns: ['tier', 'provider', 'model', 'requests', 'spend $'],
|
|
61
|
+
rows: above.map((r) => [r.tier, r.provider, r.model, r.n, r.cost]),
|
|
62
|
+
},
|
|
63
|
+
fix: 'Route harness bookkeeping (title generation, topic detection, commit messages) to the SIMPLE tier unconditionally — a pattern guard ahead of intent scoring. Lynkr can enforce this; the patterns above are the trigger list.',
|
|
64
|
+
caveat: 'Ceiling figure: it counts the full spend of these calls, not the delta vs a SIMPLE-tier serve (which would be near zero for local SIMPLE tiers).',
|
|
65
|
+
estimateBasis: 'Sum of cost_usd for tool-less requests matching harness prompt patterns, served on any tier above SIMPLE, this window.',
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wilson score interval — lower bound on a binomial proportion.
|
|
3
|
+
*
|
|
4
|
+
* Used by the downsize analyzer: "the lower tier succeeded k of n times"
|
|
5
|
+
* is only evidence when the LOWER BOUND of the confidence interval clears
|
|
6
|
+
* the bar. A raw average (k/n >= 0.7) treats 21/30 the same as 700/1000;
|
|
7
|
+
* Wilson does not — small samples get wide intervals and stay unproven.
|
|
8
|
+
*
|
|
9
|
+
* @param {number} successes
|
|
10
|
+
* @param {number} n
|
|
11
|
+
* @param {number} [z=1.96] - 95% confidence
|
|
12
|
+
* @returns {number} lower bound in [0,1]; 0 when n === 0
|
|
13
|
+
*/
|
|
14
|
+
function wilsonLowerBound(successes, n, z = 1.96) {
|
|
15
|
+
if (!Number.isFinite(n) || n <= 0) return 0;
|
|
16
|
+
const p = Math.min(1, Math.max(0, successes / n));
|
|
17
|
+
const z2 = z * z;
|
|
18
|
+
const denom = 1 + z2 / n;
|
|
19
|
+
const centre = p + z2 / (2 * n);
|
|
20
|
+
const margin = z * Math.sqrt((p * (1 - p) + z2 / (4 * n)) / n);
|
|
21
|
+
return Math.max(0, (centre - margin) / denom);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = { wilsonLowerBound };
|
package/src/dashboard/router.js
CHANGED
|
@@ -18,5 +18,10 @@ router.get('/api/overview', api.overview);
|
|
|
18
18
|
router.get('/api/usage', api.usage);
|
|
19
19
|
router.get('/api/routing', api.routing);
|
|
20
20
|
router.get('/api/logs', api.logs);
|
|
21
|
+
// Lens (feature/lens-dashboard)
|
|
22
|
+
router.get('/api/recommendations', api.recommendations);
|
|
23
|
+
router.get('/api/sessions/:id', api.sessionDetail);
|
|
24
|
+
router.get('/api/analytics', api.analytics);
|
|
25
|
+
router.get('/api/statusline', api.statusline);
|
|
21
26
|
|
|
22
27
|
module.exports = router;
|
|
@@ -20,9 +20,14 @@
|
|
|
20
20
|
*/
|
|
21
21
|
const logger = require("../logger");
|
|
22
22
|
|
|
23
|
-
// Upstreams whose streaming wire format is OpenAI Chat Completions SSE.
|
|
24
|
-
// ollama is excluded (own format + buffering quirks)
|
|
25
|
-
//
|
|
23
|
+
// Upstreams whose streaming streaming wire format is OpenAI Chat Completions SSE.
|
|
24
|
+
// ollama is excluded (own format + buffering quirks); zai is excluded (its
|
|
25
|
+
// invoke fn converts buffered responses to Anthropic, and its Anthropic-format
|
|
26
|
+
// endpoint has the native passthrough path instead). moonshot joined the list
|
|
27
|
+
// once invokeMoonshot learned to return the raw stream (its old forced
|
|
28
|
+
// stream:false predated this transformer). Caveat: reasoning_content deltas
|
|
29
|
+
// (kimi thinking) are not reshaped — thinking text is dropped from streamed
|
|
30
|
+
// responses; the buffered path still lifts it into thinking blocks.
|
|
26
31
|
const DEFAULT_OPENAI_SSE_PROVIDERS = [
|
|
27
32
|
"openai",
|
|
28
33
|
"azure-openai",
|
|
@@ -30,6 +35,7 @@ const DEFAULT_OPENAI_SSE_PROVIDERS = [
|
|
|
30
35
|
"databricks",
|
|
31
36
|
"lmstudio",
|
|
32
37
|
"llamacpp",
|
|
38
|
+
"moonshot",
|
|
33
39
|
];
|
|
34
40
|
|
|
35
41
|
function _transformProviders() {
|