lynkr 9.7.2 → 9.9.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 +29 -19
- package/bin/cli.js +11 -0
- package/bin/lynkr-init.js +14 -1
- package/bin/lynkr-usage.js +78 -0
- package/bin/wrap.js +60 -35
- package/config/difficulty-anchors.json +22 -0
- package/package.json +24 -3
- package/scripts/audit-log-reader.js +399 -0
- package/scripts/calibrate-thresholds.js +38 -157
- package/scripts/compact-dictionary.js +204 -0
- package/scripts/test-deduplication.js +448 -0
- package/scripts/ws7-anchor-replay.js +108 -0
- package/skills/lynkr/SKILL.md +195 -0
- package/src/agents/context-manager.js +18 -2
- package/src/agents/definitions/loader.js +90 -0
- package/src/agents/executor.js +24 -2
- package/src/agents/index.js +9 -1
- package/src/agents/parallel-coordinator.js +2 -2
- package/src/agents/reflector.js +11 -1
- package/src/api/middleware/loop-guard.js +87 -0
- package/src/api/middleware/request-logging.js +5 -64
- package/src/api/middleware/session.js +0 -0
- package/src/api/openai-router.js +120 -101
- package/src/api/providers-handler.js +27 -2
- package/src/api/router.js +450 -125
- package/src/budget/index.js +2 -19
- package/src/cache/semantic.js +9 -0
- package/src/clients/databricks.js +459 -146
- package/src/clients/gpt-utils.js +11 -105
- package/src/clients/openai-format.js +10 -3
- package/src/clients/openrouter-utils.js +49 -24
- package/src/clients/prompt-cache-injection.js +1 -0
- package/src/clients/provider-capabilities.js +1 -1
- package/src/clients/responses-format.js +34 -3
- package/src/clients/routing.js +15 -0
- package/src/config/index.js +36 -2
- package/src/context/gcf.js +275 -0
- package/src/context/tool-result-compressor.js +51 -9
- package/src/dashboard/api.js +1 -0
- package/src/logger/index.js +14 -1
- package/src/memory/search.js +12 -40
- package/src/memory/tools.js +3 -24
- package/src/orchestrator/bypass.js +4 -2
- package/src/orchestrator/index.js +144 -88
- package/src/routing/affinity-store.js +194 -0
- package/src/routing/agentic-detector.js +36 -6
- package/src/routing/bandit.js +25 -6
- package/src/routing/calibration.js +212 -0
- package/src/routing/client-profiles.js +292 -0
- package/src/routing/complexity-analyzer.js +48 -11
- package/src/routing/deescalator.js +148 -0
- package/src/routing/degradation.js +109 -0
- package/src/routing/feedback.js +157 -0
- package/src/routing/index.js +897 -87
- package/src/routing/intent-score.js +339 -0
- package/src/routing/interaction.js +3 -0
- package/src/routing/knn-router.js +70 -21
- package/src/routing/model-registry.js +28 -7
- package/src/routing/model-tiers.js +25 -2
- package/src/routing/reward-pipeline.js +68 -2
- package/src/routing/risk-analyzer.js +30 -1
- package/src/routing/risk-classifier.js +6 -2
- package/src/routing/session-affinity.js +162 -34
- package/src/routing/telemetry.js +298 -10
- package/src/routing/verifier.js +267 -0
- package/src/server.js +66 -21
- package/src/sessions/cleanup.js +17 -0
- package/src/tools/index.js +1 -15
- package/src/tools/smart-selection.js +10 -0
- package/src/tools/web-client.js +3 -3
- package/.eslintrc.cjs +0 -12
- package/benchmark-configs/litellm_config.yaml +0 -86
- package/benchmark-configs/lynkr.env +0 -48
- package/benchmark-configs/portkey-config.json +0 -60
- package/benchmark-configs/portkey-docker.sh +0 -23
- package/benchmark-tier-routing.js +0 -449
- package/funding.json +0 -110
- package/src/api/middleware/validation.js +0 -261
- package/src/routing/drift-monitor.js +0 -113
- package/src/workers/helpers.js +0 -185
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WS5.3 — feedback loop.
|
|
3
|
+
*
|
|
4
|
+
* Every routing decision produces an outcome (quality, cost, latency,
|
|
5
|
+
* status, error). Historically the bandit's `update()` was a dead export
|
|
6
|
+
* and `reward-pipeline.js` had no importer at all — the ML pipeline was
|
|
7
|
+
* built but never trained. This module closes that loop:
|
|
8
|
+
*
|
|
9
|
+
* 1. Compute the reward from the outcome (`reward-pipeline`).
|
|
10
|
+
* 2. If the decision came from a bandit pick, feed the reward back with
|
|
11
|
+
* the same context vector the bandit saw (`bandit.update`).
|
|
12
|
+
* 3. If we captured the query embedding at decision time and the outcome
|
|
13
|
+
* is conclusive (quality ≥ 70 or ≤ 40), add the entry to the kNN
|
|
14
|
+
* index for online growth.
|
|
15
|
+
*
|
|
16
|
+
* Contracts:
|
|
17
|
+
* - `recordOutcome()` NEVER throws. Every failure is swallowed into the
|
|
18
|
+
* degradation registry so we never poison the response path.
|
|
19
|
+
* - Work runs on `setImmediate` — the caller is invoking this from the
|
|
20
|
+
* hot path right after `telemetry.record(...)`.
|
|
21
|
+
* - Missing / partial routing metadata is acceptable: the function
|
|
22
|
+
* records what it can and skips what it can't.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const logger = require('../logger');
|
|
26
|
+
const degradation = require('./degradation');
|
|
27
|
+
const { getRewardPipeline } = require('./reward-pipeline');
|
|
28
|
+
const { getBandit } = require('./bandit');
|
|
29
|
+
const { getKnnRouter } = require('./knn-router');
|
|
30
|
+
|
|
31
|
+
// Quality thresholds for kNN online growth. Above HIGH → positive exemplar;
|
|
32
|
+
// below LOW → negative exemplar. The mid-band is intentionally excluded to
|
|
33
|
+
// avoid polluting the index with ambiguous cases.
|
|
34
|
+
const KNN_POSITIVE_QUALITY = 70;
|
|
35
|
+
const KNN_NEGATIVE_QUALITY = 40;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {object} args
|
|
39
|
+
* @param {object} args.routingResult — the decision returned by
|
|
40
|
+
* `determineProviderSmart` (or `pickTierByIntent` — see call sites in
|
|
41
|
+
* src/clients/databricks.js). Only underscored internals are consumed.
|
|
42
|
+
* @param {object} args.body — the original request body (unused today but
|
|
43
|
+
* accepted so future signals — user id, workspace, task id — can be
|
|
44
|
+
* threaded without a signature change).
|
|
45
|
+
* @param {object} args.outcome — {qualityScore, costUsd, latencyMs,
|
|
46
|
+
* statusCode, errorType, wasFallback}.
|
|
47
|
+
* @returns {void}
|
|
48
|
+
*/
|
|
49
|
+
function recordOutcome(args) {
|
|
50
|
+
// Never trust the caller's shape. Any non-object gets silently dropped —
|
|
51
|
+
// the response path is not the place to surface a caller bug.
|
|
52
|
+
const safeArgs = (args && typeof args === 'object') ? args : {};
|
|
53
|
+
setImmediate(() => {
|
|
54
|
+
try {
|
|
55
|
+
_recordOutcomeSync(safeArgs);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
// Absolute last-resort guard. Every sub-call inside _recordOutcomeSync
|
|
58
|
+
// already has its own try/catch → degradation.record(...); this catch
|
|
59
|
+
// exists so a truly unexpected throw (e.g. degradation itself blowing
|
|
60
|
+
// up) can never leak.
|
|
61
|
+
try { degradation.record('feedback', err); } catch (_) { /* nope */ }
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function _recordOutcomeSync({ routingResult, outcome }) {
|
|
67
|
+
if (!routingResult || !outcome) return;
|
|
68
|
+
|
|
69
|
+
const {
|
|
70
|
+
qualityScore = null,
|
|
71
|
+
costUsd = null,
|
|
72
|
+
latencyMs = null,
|
|
73
|
+
} = outcome;
|
|
74
|
+
|
|
75
|
+
let reward = null;
|
|
76
|
+
try {
|
|
77
|
+
const pipeline = getRewardPipeline();
|
|
78
|
+
reward = pipeline.reward({
|
|
79
|
+
quality: qualityScore,
|
|
80
|
+
cost: costUsd ?? 0,
|
|
81
|
+
latency: latencyMs ?? 0,
|
|
82
|
+
});
|
|
83
|
+
} catch (err) {
|
|
84
|
+
degradation.record('feedback', err);
|
|
85
|
+
reward = null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Bandit update — only when the decision actually came from a bandit pick
|
|
89
|
+
// (i.e. we stashed the context vector on the decision). Without ctx, an
|
|
90
|
+
// update would be meaningless: LinUCB's A/b matrices require the same
|
|
91
|
+
// feature vector the arm was scored on.
|
|
92
|
+
if (routingResult._banditContext
|
|
93
|
+
&& routingResult.provider
|
|
94
|
+
&& routingResult.model
|
|
95
|
+
&& routingResult.tier
|
|
96
|
+
&& typeof reward === 'number') {
|
|
97
|
+
try {
|
|
98
|
+
const bandit = getBandit();
|
|
99
|
+
bandit.update(
|
|
100
|
+
routingResult.tier,
|
|
101
|
+
routingResult.provider,
|
|
102
|
+
routingResult.model,
|
|
103
|
+
routingResult._banditContext,
|
|
104
|
+
reward,
|
|
105
|
+
);
|
|
106
|
+
} catch (err) {
|
|
107
|
+
degradation.record('feedback', err);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// kNN online growth — conclusive quality only. We paid for the embedding
|
|
112
|
+
// at decision time (stashed on `_queryEmbedding`), so add() is essentially
|
|
113
|
+
// free. Skipping the mid-band (40 < q < 70) keeps the index from filling
|
|
114
|
+
// up with ambiguous exemplars that would only muddy future advice.
|
|
115
|
+
if (routingResult._queryEmbedding
|
|
116
|
+
&& typeof qualityScore === 'number'
|
|
117
|
+
&& (qualityScore >= KNN_POSITIVE_QUALITY || qualityScore <= KNN_NEGATIVE_QUALITY)) {
|
|
118
|
+
try {
|
|
119
|
+
const router = getKnnRouter();
|
|
120
|
+
router.add(routingResult._queryEmbedding, {
|
|
121
|
+
query: routingResult._queryText ?? null,
|
|
122
|
+
provider: routingResult.provider,
|
|
123
|
+
model: routingResult.model,
|
|
124
|
+
tier: routingResult.tier,
|
|
125
|
+
quality: qualityScore,
|
|
126
|
+
cost: costUsd ?? 0,
|
|
127
|
+
latency: latencyMs ?? 0,
|
|
128
|
+
// A negative exemplar is worth recording so future queries whose
|
|
129
|
+
// neighbours are these bad outcomes score poorly. The router's
|
|
130
|
+
// score function already blends quality naturally; no separate
|
|
131
|
+
// "polarity" flag is needed.
|
|
132
|
+
});
|
|
133
|
+
} catch (err) {
|
|
134
|
+
degradation.record('feedback', err);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (reward != null) {
|
|
139
|
+
logger.debug({
|
|
140
|
+
reward: reward.toFixed(2),
|
|
141
|
+
tier: routingResult.tier,
|
|
142
|
+
provider: routingResult.provider,
|
|
143
|
+
model: routingResult.model,
|
|
144
|
+
hasBandit: !!routingResult._banditContext,
|
|
145
|
+
hasEmbedding: !!routingResult._queryEmbedding,
|
|
146
|
+
qualityScore,
|
|
147
|
+
}, '[Feedback] Outcome recorded');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
module.exports = {
|
|
152
|
+
recordOutcome,
|
|
153
|
+
// Test-only export so unit tests can drive the sync path directly.
|
|
154
|
+
_recordOutcomeSync,
|
|
155
|
+
KNN_POSITIVE_QUALITY,
|
|
156
|
+
KNN_NEGATIVE_QUALITY,
|
|
157
|
+
};
|