lynkr 9.9.0 → 9.9.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/README.md +42 -14
- package/bin/cli.js +11 -7
- package/bin/lynkr-init.js +30 -0
- package/package.json +3 -3
- package/scripts/build-eval-set.js +256 -0
- package/scripts/mine-difficulty-anchors.js +288 -0
- package/scripts/validate-difficulty-classifier.js +123 -0
- package/scripts/validate-intent-anchors.js +186 -0
- package/src/api/router.js +17 -0
- package/src/context/tool-result-compressor.js +883 -40
- package/src/routing/classifier-setup.js +207 -0
- package/src/routing/complexity-analyzer.js +40 -4
- package/src/routing/difficulty-classifier.js +219 -0
- package/src/routing/index.js +35 -4
- package/src/routing/intent-score.js +114 -12
- package/src/server.js +20 -0
package/README.md
CHANGED
|
@@ -246,9 +246,9 @@ POLICY_MAX_STEPS=50
|
|
|
246
246
|
POLICY_MAX_TOOL_CALLS=100
|
|
247
247
|
```
|
|
248
248
|
|
|
249
|
-
Lynkr analyzes each request and routes it to the appropriate tier. Simple questions use fast models. Complex refactoring uses powerful models.
|
|
249
|
+
Lynkr analyzes each request and routes it to the appropriate tier. Simple questions use fast models. Complex refactoring uses powerful models. The scorer combines anchor-embedding classification (WS7, payload-invariant) with an LLM difficulty classifier (Phase 6, added 2026-07-19) that catches topic-vs-difficulty confounding — `list the exports from this file` correctly routes to MEDIUM instead of getting boosted to REASONING by "technical vocabulary" alone.
|
|
250
250
|
|
|
251
|
-
**Result:** 70-90% of requests use cheaper/faster models. Only hard problems hit expensive models.
|
|
251
|
+
**Result:** 70-90% of requests use cheaper/faster models. Only hard problems hit expensive models. **15× reduction in expensive-tier over-routing** vs the anchor-only baseline on the 381-prompt eval set (0.6% vs ~15%).
|
|
252
252
|
|
|
253
253
|
Tier configuration is strictly authoritative — bandit exploration is constrained to the models you've listed in `TIER_*`, and multi-turn conversations score with a recency-weighted sliding window so context isn't lost on short follow-ups. Conversations get a content-fingerprint session id (clients like Claude Code send none), the decision pins for the session, and a guarded escape ladder (risk keywords, force phrases, score drift, context overflow) re-escalates the moment a task outgrows its model. Full pipeline: [`docs/routing-intelligence.md`](docs/routing-intelligence.md) · intent scorer: [`docs/intent-window-routing.md`](docs/intent-window-routing.md) · verify any change: [`docs/benchmarking.md`](docs/benchmarking.md).
|
|
254
254
|
|
|
@@ -413,7 +413,7 @@ npm start
|
|
|
413
413
|
|
|
414
414
|
Head-to-head against **LiteLLM** on the **same backends** (Ollama `minimax-m2.5`, Moonshot, Azure OpenAI), 9 scenarios across 4 feature categories. Apples-to-apples comparison is Lynkr vs LiteLLM **billed tokens on the same scenario**. Run with `node benchmark-tier-routing.js`.
|
|
415
415
|
|
|
416
|
-
>
|
|
416
|
+
> _Runs: token benchmarks — June 5, 2026 (Lynkr v9.3.2 · LiteLLM v1.87.1). Tier routing head-to-head — **re-run July 19, 2026** (Lynkr working tree w/ Phase-6 classifier + config B · LiteLLM v1.94.0.dev1 Auto Router v2). macOS, Apple Silicon._
|
|
417
417
|
|
|
418
418
|
### Token reduction (vs LiteLLM, same model & prompt)
|
|
419
419
|
|
|
@@ -433,28 +433,56 @@ Lynkr strips irrelevant tool schemas (smart tool selection) and binary-compresse
|
|
|
433
433
|
|
|
434
434
|
Near-identical prompts return cached responses in 171ms. Zero model tokens billed on a cache hit.
|
|
435
435
|
|
|
436
|
-
### Tier routing — vs LiteLLM Auto Router v2 (July
|
|
436
|
+
### Tier routing — vs LiteLLM Auto Router v2 (re-run July 19, 2026)
|
|
437
437
|
|
|
438
|
-
LiteLLM v1.94 shipped a native complexity router (`auto_router/complexity_router`) with the same four tier names Lynkr uses. Head-to-head on the **same backends** with identical prompts, both proxies judged against the same acceptable-tier sets (11 routing scenarios, `MODE=routing node benchmark-tier-routing.js`, LiteLLM config in `litellm-autorouter-v2.yaml`):
|
|
438
|
+
LiteLLM v1.94 shipped a native complexity router (`auto_router/complexity_router`) with the same four tier names Lynkr uses. Head-to-head on the **same backends** with identical prompts, both proxies live, both judged against the same acceptable-tier sets (11 routing scenarios, `MODE=routing node benchmark-tier-routing.js`, LiteLLM config in `litellm-autorouter-v2.yaml`):
|
|
439
439
|
|
|
440
|
-
| Router | Routing-correct |
|
|
440
|
+
| Router | Routing-correct | Notes |
|
|
441
441
|
|---|---|---|
|
|
442
|
-
| **Lynkr** | **11/11
|
|
443
|
-
| LiteLLM v2 — heuristic (default) | 4/11 |
|
|
444
|
-
| LiteLLM v2 — LLM classifier | 6–8/11 (non-deterministic
|
|
442
|
+
| **Lynkr (Phase-6 classifier + config B)** | **11/11 ✅** | anchor embedding + local LLM classifier reconcile + FORCE_REASONING patterns + risk-remap; ~500ms warm classifier call, cached |
|
|
443
|
+
| LiteLLM v2 — heuristic (default) | **4/11** | every miss *under-routed* — banking security analysis, whole-pipeline refactor, prod auth-file fix, autonomous agentic loop all sent to a 7B local model |
|
|
444
|
+
| LiteLLM v2 — LLM classifier | 6–8/11 (non-deterministic) | paid GPT-5.2 call + ~2–3s on **every** request; fails outright with local classifier models |
|
|
445
445
|
|
|
446
|
-
|
|
446
|
+
**The 7 requests LiteLLM v2 misroutes to a 7B ollama model (should be COMPLEX or REASONING):**
|
|
447
447
|
|
|
448
|
-
|
|
448
|
+
| What you typed | Lynkr sends to | LiteLLM sends to |
|
|
449
|
+
|---|---|---|
|
|
450
|
+
| "Analyse security trade-offs of JWT vs httpOnly cookies for a banking app" | Claude Opus (top) | **ollama 7B** ❌ |
|
|
451
|
+
| "Refactor the entire ingestion pipeline and give me the plan" | z.ai GLM-5.2 (mid) | **ollama 7B** ❌ |
|
|
452
|
+
| "Fix the null-check bug in `src/auth/middleware.ts`" | Claude Opus (risk-remap) | **ollama 7B** ❌ |
|
|
453
|
+
| "Figure out why this test is flaky. You have full autonomy — iterate until 10 runs pass" | Claude Opus (autonomous) | **ollama 7B** ❌ |
|
|
454
|
+
| "please summarize the exports of this file quickly" | ollama minimax (correct — MEDIUM) | ollama minimax — but for the wrong reason (LiteLLM under-routed to SIMPLE) |
|
|
455
|
+
|
|
456
|
+
Lynkr layers the following, in decreasing priority, to catch every miss LiteLLM leaks:
|
|
457
|
+
|
|
458
|
+
- FORCE_REASONING regex (`ultrathink`, `prove`, `security audit`, `from first principles`) → deterministic top-tier
|
|
459
|
+
- Risk classifier (auth/middleware/credentials paths) → REASONING under config B
|
|
460
|
+
- Agentic detector (`AUTONOMOUS` workflows) → REASONING minimum
|
|
461
|
+
- Anchor-embedding classifier over 4 difficulty classes + FRONTIER_MIN_SIM floor
|
|
462
|
+
- Local LLM classifier (qwen2.5:3b) reconciles borderline anchor calls
|
|
463
|
+
- Envelope invariance — scores cleaned user text only, ignoring tool schemas / history / system-reminders
|
|
464
|
+
|
|
465
|
+
LiteLLM's router reads the raw last message with no envelope-stripping, no verify-then-escalate cascade, no risk classifier, no agentic detection — its fallbacks trigger only on HTTP errors, never on a bad answer.
|
|
466
|
+
|
|
467
|
+
_Fairness notes: the 11 scenarios derive from Lynkr's own regression suite, so Lynkr has home-field advantage — the transferable finding is the **direction** of LiteLLM's failures (systematic under-routing to cheap on hard prompts), not the exact scores. Lynkr's top tiers used Claude Opus 4.8 / z.ai GLM-5.2; LiteLLM was given the identical tier targets._
|
|
449
468
|
|
|
450
469
|
### Cost projection (100,000 requests/month, same backend)
|
|
451
470
|
|
|
471
|
+
**Direct cost (raw meter):**
|
|
472
|
+
|
|
452
473
|
| | Monthly cost | vs LiteLLM |
|
|
453
474
|
|---|---|---|
|
|
454
|
-
| LiteLLM | ~$818 | baseline |
|
|
455
|
-
| **Lynkr** | **~$409** | **~50% cheaper** |
|
|
475
|
+
| LiteLLM (TOON tool-heavy scenario) | ~$818 | baseline |
|
|
476
|
+
| **Lynkr (TOON tool-heavy)** | **~$409** | **~50% cheaper** via token optimization |
|
|
477
|
+
|
|
478
|
+
**Effective cost per correct-tier answer** (July 19 head-to-head, all 11 routing scenarios):
|
|
479
|
+
|
|
480
|
+
| | Direct $/mo | Effective $/mo if you re-issue misroutes at correct tier |
|
|
481
|
+
|---|---|---|
|
|
482
|
+
| **Lynkr** | ~$5.50 (spends where it should, doesn't where it shouldn't) | ~$5.50 |
|
|
483
|
+
| LiteLLM v2 heuristic | $0 (under-routes everything to ollama) | ~$89 (7 misroutes need re-issue on Moonshot/COMPLEX) |
|
|
456
484
|
|
|
457
|
-
|
|
485
|
+
The story isn't "Lynkr always spends less." It's **Lynkr spends where it should and doesn't where it shouldn't**. LiteLLM's zero-dollar cost on complex prompts is bought with wrong-tier answers to hard work — a 7B model answering "prove this rate limiter is fair under concurrent refill" is silent quality collapse, not a savings.
|
|
458
486
|
|
|
459
487
|
→ [Full benchmark report with methodology](BENCHMARK_REPORT.md)
|
|
460
488
|
|
package/bin/cli.js
CHANGED
|
@@ -14,21 +14,23 @@ const SUBCOMMANDS = {
|
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
const sub = process.argv[2];
|
|
17
|
-
|
|
17
|
+
// `lynkr start` is an alias for `lynkr` (start the proxy). Drop the token
|
|
18
|
+
// and fall through so users can use whichever spelling they prefer.
|
|
19
|
+
if (sub === 'start') {
|
|
20
|
+
process.argv.splice(2, 1);
|
|
21
|
+
} else if (sub && Object.prototype.hasOwnProperty.call(SUBCOMMANDS, sub)) {
|
|
18
22
|
process.argv.splice(2, 1); // drop the subcommand token so the script's own arg parser is happy
|
|
19
23
|
// Subcommand scripts check this to decide whether to invoke their main()
|
|
20
24
|
// when they're require()'d (vs being loaded by a test for unit-checking).
|
|
21
25
|
process.env._LYNKR_SUBCMD = sub;
|
|
22
26
|
require(SUBCOMMANDS[sub]);
|
|
23
27
|
return;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
//
|
|
27
|
-
// server (`lynkr codex` used to start a bare gateway and never say why).
|
|
28
|
-
if (sub && !sub.startsWith('-')) {
|
|
28
|
+
} else if (sub && !sub.startsWith('-')) {
|
|
29
|
+
// Unknown positional commands must error loudly, not silently boot the
|
|
30
|
+
// server (`lynkr codex` used to start a bare gateway and never say why).
|
|
29
31
|
console.error(`Error: unknown command '${sub}'.`);
|
|
30
32
|
console.error(`Did you mean: lynkr wrap ${sub}`);
|
|
31
|
-
console.error(`Known commands: ${Object.keys(SUBCOMMANDS).join(', ')} (or no command to start the server)`);
|
|
33
|
+
console.error(`Known commands: ${Object.keys(SUBCOMMANDS).join(', ')}, start (or no command to start the server)`);
|
|
32
34
|
process.exit(1);
|
|
33
35
|
}
|
|
34
36
|
|
|
@@ -45,6 +47,8 @@ ${pkg.description}
|
|
|
45
47
|
|
|
46
48
|
Usage:
|
|
47
49
|
lynkr [options] Start the proxy server (default)
|
|
50
|
+
lynkr start [options] Alias for the above
|
|
51
|
+
lynkr init [options] Interactive setup wizard (writes .env, pulls classifier model)
|
|
48
52
|
lynkr wrap <target> [options] Wrap CLI tools through Lynkr proxy
|
|
49
53
|
lynkr usage [options] Show AI spend report and tier-routing savings
|
|
50
54
|
lynkr stats [options] Shareable savings-receipt card (also: lynkr usage --card)
|
package/bin/lynkr-init.js
CHANGED
|
@@ -550,6 +550,36 @@ async function runInteractive(opts) {
|
|
|
550
550
|
close();
|
|
551
551
|
console.log('');
|
|
552
552
|
writeEnvFile(buildEnvContent(env, isWrap, tierConfig), opts);
|
|
553
|
+
|
|
554
|
+
// ── Classifier bootstrap: detect ollama, pull model, warm-up ──
|
|
555
|
+
// Runs AFTER .env is written so the classifier can read tier config
|
|
556
|
+
// (currently uses hardcoded qwen2.5:3b, but leaves room for the future
|
|
557
|
+
// fine-tuned model to plug in via SIMPLE tier or an env override).
|
|
558
|
+
if (!opts.dryRun) {
|
|
559
|
+
console.log('');
|
|
560
|
+
console.log('Setting up the difficulty classifier…');
|
|
561
|
+
try {
|
|
562
|
+
const { ensureClassifierReady } = require('../src/routing/classifier-setup');
|
|
563
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
564
|
+
const prompt = (q) => new Promise((res) => rl.question(q, (a) => res(a)));
|
|
565
|
+
const result = await ensureClassifierReady({
|
|
566
|
+
mode: 'interactive',
|
|
567
|
+
log: (m) => console.log(m),
|
|
568
|
+
warn: (m) => console.warn(m),
|
|
569
|
+
prompt,
|
|
570
|
+
});
|
|
571
|
+
rl.close();
|
|
572
|
+
if (result.ready) {
|
|
573
|
+
console.log('✓ Classifier ready.');
|
|
574
|
+
} else if (result.reason === 'ollama_missing') {
|
|
575
|
+
console.log('(Install Ollama and re-run `lynkr init` to enable the classifier — Lynkr will still start without it, using anchor-only scoring.)');
|
|
576
|
+
} else {
|
|
577
|
+
console.log(`(Classifier disabled: ${result.reason}. Lynkr will fall back to anchor-only scoring.)`);
|
|
578
|
+
}
|
|
579
|
+
} catch (err) {
|
|
580
|
+
console.warn(`⚠ Classifier setup skipped: ${err.message}`);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
553
583
|
} catch (err) {
|
|
554
584
|
close();
|
|
555
585
|
throw err;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lynkr",
|
|
3
|
-
"version": "9.9.
|
|
3
|
+
"version": "9.9.1",
|
|
4
4
|
"files": [
|
|
5
5
|
"index.js",
|
|
6
6
|
"install.sh",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"dev": "nodemon index.js",
|
|
37
37
|
"lint": "eslint src index.js",
|
|
38
38
|
"test": "npm run test:unit && npm run test:performance",
|
|
39
|
-
"test:unit": "DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com LOG_FILE_ENABLED=false node --test test/routing.test.js test/hybrid-routing-integration.test.js test/web-tools.test.js test/passthrough-mode.test.js test/openrouter-error-resilience.test.js test/format-conversion.test.js test/azure-openai-config.test.js test/azure-openai-format-conversion.test.js test/azure-openai-routing.test.js test/azure-openai-streaming.test.js test/azure-openai-error-resilience.test.js test/azure-openai-integration.test.js test/openai-integration.test.js test/toon-compression.test.js test/gcf-compression.test.js test/llamacpp-integration.test.js test/resilience.test.js test/telemetry-routing.test.js test/memory/store.test.js test/memory/surprise.test.js test/memory/extractor.test.js test/memory/search.test.js test/memory/retriever.test.js test/distill.test.js test/large-payload.test.js test/code-mode.test.js test/prompt-cache-injection.test.js test/risk-analyzer.test.js test/interaction-block.test.js test/preflight.test.js test/token-reduction.test.js test/session-affinity.test.js test/model-registry-cost.test.js test/task-decomposition.test.js test/output-format-guard.test.js test/tier-fallback.test.js test/wrap.test.js test/init.test.js test/agent-learning.test.js test/tool-call-response-metadata.test.js test/degradation.test.js test/routing-telemetry-columns.test.js test/sticky-routing.test.js test/knn-ambiguous-escalate.test.js test/deescalator.test.js test/client-profiles.test.js test/strip-internal-fields.test.js test/complexity-tool-subtraction.test.js test/bandit.test.js test/routing-propensity.test.js test/reward-pipeline.test.js test/knn-cold-start.test.js test/calibration.test.js test/feedback-loop.test.js test/session-fingerprint.test.js test/side-request-guards.test.js test/verifier.test.js test/intent-score.test.js test/usage-stats.test.js test/loop-guard.test.js",
|
|
39
|
+
"test:unit": "DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com LOG_FILE_ENABLED=false node --test test/routing.test.js test/hybrid-routing-integration.test.js test/web-tools.test.js test/passthrough-mode.test.js test/openrouter-error-resilience.test.js test/format-conversion.test.js test/azure-openai-config.test.js test/azure-openai-format-conversion.test.js test/azure-openai-routing.test.js test/azure-openai-streaming.test.js test/azure-openai-error-resilience.test.js test/azure-openai-integration.test.js test/openai-integration.test.js test/toon-compression.test.js test/gcf-compression.test.js test/llamacpp-integration.test.js test/resilience.test.js test/telemetry-routing.test.js test/memory/store.test.js test/memory/surprise.test.js test/memory/extractor.test.js test/memory/search.test.js test/memory/retriever.test.js test/distill.test.js test/large-payload.test.js test/code-mode.test.js test/prompt-cache-injection.test.js test/risk-analyzer.test.js test/interaction-block.test.js test/preflight.test.js test/token-reduction.test.js test/session-affinity.test.js test/model-registry-cost.test.js test/task-decomposition.test.js test/output-format-guard.test.js test/tier-fallback.test.js test/wrap.test.js test/init.test.js test/agent-learning.test.js test/tool-call-response-metadata.test.js test/degradation.test.js test/routing-telemetry-columns.test.js test/sticky-routing.test.js test/knn-ambiguous-escalate.test.js test/deescalator.test.js test/client-profiles.test.js test/strip-internal-fields.test.js test/complexity-tool-subtraction.test.js test/bandit.test.js test/routing-propensity.test.js test/reward-pipeline.test.js test/knn-cold-start.test.js test/calibration.test.js test/feedback-loop.test.js test/session-fingerprint.test.js test/side-request-guards.test.js test/verifier.test.js test/intent-score.test.js test/difficulty-classifier.test.js test/classifier-setup.test.js test/usage-stats.test.js test/loop-guard.test.js",
|
|
40
40
|
"test:memory": "DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node --test test/memory/store.test.js test/memory/surprise.test.js test/memory/extractor.test.js test/memory/search.test.js test/memory/retriever.test.js",
|
|
41
41
|
"test:new-features": "DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node --test test/passthrough-mode.test.js test/openrouter-error-resilience.test.js test/format-conversion.test.js",
|
|
42
42
|
"test:performance": "DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node test/hybrid-routing-performance.test.js && DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node test/performance-tests.js",
|
|
@@ -124,4 +124,4 @@
|
|
|
124
124
|
"nodemon": "^3.1.0",
|
|
125
125
|
"pino-pretty": "^10.2.0"
|
|
126
126
|
}
|
|
127
|
-
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Build the difficulty-classifier evaluation set.
|
|
4
|
+
*
|
|
5
|
+
* Sources (all zero-cost, no LLM judge required):
|
|
6
|
+
* 1. RouterArena unused rows — native empirical difficulty labels
|
|
7
|
+
* (easy/medium/hard from 42-model pass rate). No LLM involved.
|
|
8
|
+
* 2. routellm/gpt4_dataset unused rows — native mixtral_score 1–5
|
|
9
|
+
* (GPT-4-judged when the dataset was built). No LLM involved.
|
|
10
|
+
* 3. Hand-crafted coding-agent-flavored prompts — representative of
|
|
11
|
+
* Lynkr's real traffic (real logs proved too thin, ~7 unique prompts).
|
|
12
|
+
*
|
|
13
|
+
* Leak avoidance: skip any text already in data/difficulty-anchors.provenance.json
|
|
14
|
+
* (the mined anchor set) so eval and anchors don't overlap.
|
|
15
|
+
*
|
|
16
|
+
* Label → tier mapping:
|
|
17
|
+
* RouterArena easy → MEDIUM (any competent model handles)
|
|
18
|
+
* RouterArena medium → COMPLEX (needs strong general model)
|
|
19
|
+
* RouterArena hard → REASONING (frontier model territory)
|
|
20
|
+
* gpt4_dataset score 5 → MEDIUM (Mixtral got it right — trivially easy)
|
|
21
|
+
* gpt4_dataset score 3–4 → COMPLEX (Mixtral partial — real difficulty)
|
|
22
|
+
* gpt4_dataset score 1–2 → REASONING (Mixtral failed — top-tier work)
|
|
23
|
+
*
|
|
24
|
+
* Output: data/difficulty-eval.jsonl (gitignored)
|
|
25
|
+
* Usage: node scripts/build-eval-set.js
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const fs = require("fs");
|
|
29
|
+
const path = require("path");
|
|
30
|
+
|
|
31
|
+
const DS_SERVER = "https://datasets-server.huggingface.co/rows";
|
|
32
|
+
const OUT_FILE = path.join(__dirname, "../data/difficulty-eval.jsonl");
|
|
33
|
+
const PROVENANCE = path.join(__dirname, "../data/difficulty-anchors.provenance.json");
|
|
34
|
+
|
|
35
|
+
const TARGETS = { routerarena: 200, gpt4_dataset: 200 };
|
|
36
|
+
|
|
37
|
+
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
|
|
38
|
+
|
|
39
|
+
async function fetchRows(dataset, config, split, offset, length) {
|
|
40
|
+
const url = `${DS_SERVER}?dataset=${encodeURIComponent(dataset)}&config=${config}&split=${split}&offset=${offset}&length=${length}`;
|
|
41
|
+
for (let a = 0; a < 5; a++) {
|
|
42
|
+
try {
|
|
43
|
+
const res = await fetch(url);
|
|
44
|
+
if (res.status === 429) { await sleep(3000 * (a + 1)); continue; }
|
|
45
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
46
|
+
return (await res.json()).rows?.map(r => r.row) || [];
|
|
47
|
+
} catch (err) {
|
|
48
|
+
if (a === 4) return [];
|
|
49
|
+
await sleep(1500 * (a + 1));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function clean(text) {
|
|
56
|
+
if (typeof text !== "string") return null;
|
|
57
|
+
const t = text.replace(/\s+/g, " ").trim();
|
|
58
|
+
if (t.length < 15 || t.length > 400) return null;
|
|
59
|
+
if (/\(\s*\)/.test(t) || /_{3,}/.test(t)) return null;
|
|
60
|
+
if (/\b[A-D]\)\s/.test(t) || /which of the following/i.test(t)) return null;
|
|
61
|
+
if (/\b(premise|hypothesis)\b/i.test(t) && /\b(entail|inference|contradict)\b/i.test(t)) return null;
|
|
62
|
+
if (/NAME_\d|\{\{.*\}\}/.test(t)) return null;
|
|
63
|
+
const nonAscii = (t.match(/[^\x20-\x7E]/g) || []).length;
|
|
64
|
+
if (nonAscii > t.length * 0.1) return null;
|
|
65
|
+
return t;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function isQuizbowlStyle(t) {
|
|
69
|
+
return /^(this |one |a \d{4} (book|work|paper)|fictional |in one (work|section))/i.test(t) ||
|
|
70
|
+
/\([A-Za-z]+-[A-Z][A-Za-z-]+\)/.test(t);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Hand-crafted coding-agent-flavored prompts by tier. Reflects real Lynkr
|
|
74
|
+
// traffic shape (imperatives, technical vocabulary, mix of simple/mechanical/
|
|
75
|
+
// systemic/rigorous). Labels are mine — the honest reviewer for this session.
|
|
76
|
+
const HAND_EVAL = [
|
|
77
|
+
// SIMPLE — casual/acks
|
|
78
|
+
{ text: "hi", tier: "SIMPLE" },
|
|
79
|
+
{ text: "hello", tier: "SIMPLE" },
|
|
80
|
+
{ text: "ok thanks", tier: "SIMPLE" },
|
|
81
|
+
{ text: "yes continue", tier: "SIMPLE" },
|
|
82
|
+
{ text: "no skip that", tier: "SIMPLE" },
|
|
83
|
+
{ text: "got it", tier: "SIMPLE" },
|
|
84
|
+
{ text: "sure go ahead", tier: "SIMPLE" },
|
|
85
|
+
{ text: "what time is it", tier: "SIMPLE" },
|
|
86
|
+
{ text: "cool", tier: "SIMPLE" },
|
|
87
|
+
{ text: "makes sense", tier: "SIMPLE" },
|
|
88
|
+
{ text: "thanks a lot", tier: "SIMPLE" },
|
|
89
|
+
{ text: "hey there", tier: "SIMPLE" },
|
|
90
|
+
{ text: "yep proceed", tier: "SIMPLE" },
|
|
91
|
+
{ text: "great work", tier: "SIMPLE" },
|
|
92
|
+
{ text: "no worries", tier: "SIMPLE" },
|
|
93
|
+
// MEDIUM — one specific mechanical task
|
|
94
|
+
{ text: "list the exports from src/router.js", tier: "MEDIUM" },
|
|
95
|
+
{ text: "run the unit tests and summarize failures", tier: "MEDIUM" },
|
|
96
|
+
{ text: "show me the git diff for this file", tier: "MEDIUM" },
|
|
97
|
+
{ text: "what does this function do", tier: "MEDIUM" },
|
|
98
|
+
{ text: "explain this regex pattern to me", tier: "MEDIUM" },
|
|
99
|
+
{ text: "add error handling to this try block", tier: "MEDIUM" },
|
|
100
|
+
{ text: "fix the linter warnings in this file", tier: "MEDIUM" },
|
|
101
|
+
{ text: "write a unit test for the login helper", tier: "MEDIUM" },
|
|
102
|
+
{ text: "convert this callback to async/await", tier: "MEDIUM" },
|
|
103
|
+
{ text: "add a null check before this dereference", tier: "MEDIUM" },
|
|
104
|
+
{ text: "rename this variable to something more descriptive", tier: "MEDIUM" },
|
|
105
|
+
{ text: "extract this into a helper function", tier: "MEDIUM" },
|
|
106
|
+
{ text: "add a docstring to this method", tier: "MEDIUM" },
|
|
107
|
+
{ text: "check if this file has any TODO comments", tier: "MEDIUM" },
|
|
108
|
+
{ text: "search the codebase for uses of deprecated API", tier: "MEDIUM" },
|
|
109
|
+
{ text: "install the axios package as a dev dependency", tier: "MEDIUM" },
|
|
110
|
+
{ text: "revert the last commit but keep the working tree", tier: "MEDIUM" },
|
|
111
|
+
{ text: "delete unused imports from this file", tier: "MEDIUM" },
|
|
112
|
+
{ text: "format this JSON blob nicely", tier: "MEDIUM" },
|
|
113
|
+
{ text: "which files were changed in the last commit", tier: "MEDIUM" },
|
|
114
|
+
{ text: "how do I use the fetch API with timeouts", tier: "MEDIUM" },
|
|
115
|
+
{ text: "what's the difference between let and const in JavaScript", tier: "MEDIUM" },
|
|
116
|
+
{ text: "add a rate limit to this endpoint", tier: "MEDIUM" },
|
|
117
|
+
{ text: "grep for TODO in the routing folder", tier: "MEDIUM" },
|
|
118
|
+
{ text: "make this loop use Promise.all instead of sequential awaits", tier: "MEDIUM" },
|
|
119
|
+
// COMPLEX — systemic / design / multi-file
|
|
120
|
+
{ text: "do an architecture review of the orchestrator", tier: "COMPLEX" },
|
|
121
|
+
{ text: "review this retry helper for bugs and race conditions", tier: "COMPLEX" },
|
|
122
|
+
{ text: "refactor the entire ingestion pipeline and give me a plan", tier: "COMPLEX" },
|
|
123
|
+
{ text: "design a horizontally scalable architecture for the router with failure-mode analysis", tier: "COMPLEX" },
|
|
124
|
+
{ text: "code review the PR #84 routing hardening changes", tier: "COMPLEX" },
|
|
125
|
+
{ text: "analyze every module in src/ for circular dependencies", tier: "COMPLEX" },
|
|
126
|
+
{ text: "debug this complex race condition in the connection pool", tier: "COMPLEX" },
|
|
127
|
+
{ text: "plan a zero-downtime migration to the new schema", tier: "COMPLEX" },
|
|
128
|
+
{ text: "implement a distributed rate limiter with Redis backing", tier: "COMPLEX" },
|
|
129
|
+
{ text: "design the caching strategy for this API gateway including invalidation rules", tier: "COMPLEX" },
|
|
130
|
+
{ text: "trace through the request lifecycle end to end and identify bottlenecks", tier: "COMPLEX" },
|
|
131
|
+
{ text: "restructure the module boundaries to reduce coupling between core and plugins", tier: "COMPLEX" },
|
|
132
|
+
{ text: "propose a testing strategy for the new streaming pipeline covering edge cases", tier: "COMPLEX" },
|
|
133
|
+
{ text: "identify all the places in the codebase that depend on the legacy auth flow and plan the deprecation", tier: "COMPLEX" },
|
|
134
|
+
{ text: "walk me through how the tier routing decision cascade works and highlight the weak points", tier: "COMPLEX" },
|
|
135
|
+
{ text: "compare our current queueing implementation against BullMQ and recommend a migration path", tier: "COMPLEX" },
|
|
136
|
+
{ text: "extract the shared session logic into a separate package and update all consumers", tier: "COMPLEX" },
|
|
137
|
+
{ text: "design the observability layer for the multi-tenant deployment with per-tenant metrics", tier: "COMPLEX" },
|
|
138
|
+
{ text: "diagnose why the latency P99 spiked yesterday afternoon across the fleet", tier: "COMPLEX" },
|
|
139
|
+
{ text: "plan the sharding strategy for the telemetry database as we scale to 10x traffic", tier: "COMPLEX" },
|
|
140
|
+
{ text: "audit the codebase for uses of unsafe eval and propose safe replacements", tier: "COMPLEX" },
|
|
141
|
+
{ text: "propose a graceful degradation plan for when the primary embedding model is unavailable", tier: "COMPLEX" },
|
|
142
|
+
{ text: "review the error handling across the client SDK and standardize on a single pattern", tier: "COMPLEX" },
|
|
143
|
+
{ text: "outline the migration from the monolith to a services split with backwards compatibility", tier: "COMPLEX" },
|
|
144
|
+
{ text: "reason about whether this cache invalidation scheme is correct under concurrent writes", tier: "COMPLEX" },
|
|
145
|
+
// REASONING — proof / audit / formal / deep
|
|
146
|
+
{ text: "prove the correctness of this lock-free queue implementation and identify any ABA hazards", tier: "REASONING" },
|
|
147
|
+
{ text: "security audit the authentication middleware for token reuse vulnerabilities", tier: "REASONING" },
|
|
148
|
+
{ text: "derive the optimal cache eviction policy for this access pattern and prove its competitive ratio", tier: "REASONING" },
|
|
149
|
+
{ text: "formally verify that this state machine can never deadlock or produce a counterexample trace", tier: "REASONING" },
|
|
150
|
+
{ text: "from first principles design a Byzantine-fault-tolerant consensus variant tolerating f faults with 2f+1 replicas", tier: "REASONING" },
|
|
151
|
+
{ text: "think deeply about why this floating-point summation loses precision at scale and derive the compensated algorithm", tier: "REASONING" },
|
|
152
|
+
{ text: "prove this rate limiter is fair under concurrent refill or construct the starvation schedule that breaks it", tier: "REASONING" },
|
|
153
|
+
{ text: "reason through the exact memory ordering constraints this concurrent hashmap needs on ARM and justify each barrier", tier: "REASONING" },
|
|
154
|
+
{ text: "given these heap dumps reason to the retention path causing the leak and rule out the two most plausible alternatives", tier: "REASONING" },
|
|
155
|
+
{ text: "ultrathink: analyze whether this online schema migration can preserve every invariant the application relies on", tier: "REASONING" },
|
|
156
|
+
{ text: "verify no path in this state machine violates the safety property that read never observes a partial write", tier: "REASONING" },
|
|
157
|
+
{ text: "derive the amortized complexity bound for this splay tree under this adversarial access pattern and prove it tight", tier: "REASONING" },
|
|
158
|
+
{ text: "prove that this distributed algorithm makes progress under fair scheduling regardless of message reordering", tier: "REASONING" },
|
|
159
|
+
{ text: "penetration test the session token handling and enumerate every replay attack vector", tier: "REASONING" },
|
|
160
|
+
{ text: "formal proof that this compensation transaction preserves ACID under partial-failure scenarios", tier: "REASONING" },
|
|
161
|
+
{ text: "reason from first principles about whether exactly-once semantics are achievable on top of this at-least-once queue", tier: "REASONING" },
|
|
162
|
+
{ text: "vulnerability scan the recently-added crypto path for downgrade attacks and misuse of primitives", tier: "REASONING" },
|
|
163
|
+
{ text: "prove that this garbage collector will always terminate on any input given the described root set", tier: "REASONING" },
|
|
164
|
+
{ text: "verify the linearizability of this concurrent skip list using the linearization-point argument", tier: "REASONING" },
|
|
165
|
+
{ text: "think hard about which of these three algorithms actually preserves invariant I under crash recovery and which only appears to", tier: "REASONING" },
|
|
166
|
+
];
|
|
167
|
+
|
|
168
|
+
async function main() {
|
|
169
|
+
const seenTexts = new Set();
|
|
170
|
+
try {
|
|
171
|
+
const prov = JSON.parse(fs.readFileSync(PROVENANCE, "utf8"));
|
|
172
|
+
for (const text of Object.keys(prov)) seenTexts.add(text);
|
|
173
|
+
console.log(`Loaded ${seenTexts.size} anchor texts from provenance (excluded from eval)`);
|
|
174
|
+
} catch { /* first run may have no provenance */ }
|
|
175
|
+
|
|
176
|
+
const eval_ = [];
|
|
177
|
+
const seen = new Set([...seenTexts].map(t => t.slice(0, 80)));
|
|
178
|
+
|
|
179
|
+
const add = (text, tier, source, label) => {
|
|
180
|
+
const key = text.slice(0, 80);
|
|
181
|
+
if (seen.has(key)) return false;
|
|
182
|
+
seen.add(key);
|
|
183
|
+
eval_.push({ text, tier, source, label });
|
|
184
|
+
return true;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
// 1. Hand-crafted prompts (canary + real-traffic representative)
|
|
188
|
+
for (const item of HAND_EVAL) {
|
|
189
|
+
add(item.text, item.tier, "hand", "author-labeled");
|
|
190
|
+
}
|
|
191
|
+
console.log(`Hand-crafted: ${eval_.length} added`);
|
|
192
|
+
|
|
193
|
+
// 2. RouterArena — technical domains only, empirical difficulty labels
|
|
194
|
+
console.log("Fetching RouterArena rows...");
|
|
195
|
+
const TECH_DOMAIN_RE = /computer science|technology|engineering|mathematic|science/i;
|
|
196
|
+
const DATASET_EXCLUDE_RE = /superglue|cloze|entailment/i;
|
|
197
|
+
let raAdded = 0;
|
|
198
|
+
outer: for (let offset = 4000; offset < 8400; offset += 100) {
|
|
199
|
+
const rows = await fetchRows("RouteWorks/RouterArena", "default", "full", offset, 100);
|
|
200
|
+
for (const row of rows) {
|
|
201
|
+
if (raAdded >= TARGETS.routerarena) break outer;
|
|
202
|
+
if (row.Context && String(row.Context).trim()) continue;
|
|
203
|
+
if (Array.isArray(row.Options) && row.Options.length > 0) continue;
|
|
204
|
+
if (DATASET_EXCLUDE_RE.test(String(row["Dataset name"] || ""))) continue;
|
|
205
|
+
const text = clean(row.Question);
|
|
206
|
+
if (!text || isQuizbowlStyle(text)) continue;
|
|
207
|
+
const technical = TECH_DOMAIN_RE.test(String(row.Domain || ""));
|
|
208
|
+
const diff = String(row.Difficulty || "").toLowerCase();
|
|
209
|
+
let tier = null;
|
|
210
|
+
if (diff === "easy") tier = "MEDIUM";
|
|
211
|
+
else if (diff === "medium" && technical) tier = "COMPLEX";
|
|
212
|
+
else if (diff === "hard" && technical) tier = "REASONING";
|
|
213
|
+
if (!tier) continue;
|
|
214
|
+
if (add(text, tier, "RouterArena", `${diff}/${row["Dataset name"]}`)) raAdded++;
|
|
215
|
+
}
|
|
216
|
+
await sleep(200);
|
|
217
|
+
}
|
|
218
|
+
console.log(`RouterArena: +${raAdded}`);
|
|
219
|
+
|
|
220
|
+
// 3. gpt4_dataset — skip pages we mined for anchors (offset 0, 1817, 3634...)
|
|
221
|
+
console.log("Fetching gpt4_dataset rows...");
|
|
222
|
+
let gpAdded = 0;
|
|
223
|
+
outer2: for (let offset = 60000; offset < 109000; offset += 500) {
|
|
224
|
+
const rows = await fetchRows("routellm/gpt4_dataset", "default", "train", offset, 100);
|
|
225
|
+
for (const row of rows) {
|
|
226
|
+
if (gpAdded >= TARGETS.gpt4_dataset) break outer2;
|
|
227
|
+
const text = clean(row.prompt);
|
|
228
|
+
if (!text) continue;
|
|
229
|
+
const score = Number(row.mixtral_score);
|
|
230
|
+
let tier = null;
|
|
231
|
+
if (score === 5) tier = "MEDIUM";
|
|
232
|
+
else if (score === 3 || score === 4) tier = "COMPLEX";
|
|
233
|
+
else if (score === 1 || score === 2) tier = text.length < 60 ? null : "REASONING";
|
|
234
|
+
if (!tier) continue;
|
|
235
|
+
if (add(text, tier, "gpt4_dataset", `mixtral_score=${score}`)) gpAdded++;
|
|
236
|
+
}
|
|
237
|
+
await sleep(200);
|
|
238
|
+
}
|
|
239
|
+
console.log(`gpt4_dataset: +${gpAdded}`);
|
|
240
|
+
|
|
241
|
+
// Shuffle deterministically (session-repro via fixed permutation)
|
|
242
|
+
eval_.sort((a, b) => a.text.length - b.text.length);
|
|
243
|
+
const shuffled = [];
|
|
244
|
+
for (let i = 0; i < eval_.length; i++) {
|
|
245
|
+
const j = (i * 37 + 11) % eval_.length;
|
|
246
|
+
shuffled.push(eval_[j]);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
fs.writeFileSync(OUT_FILE, shuffled.map(r => JSON.stringify(r)).join("\n") + "\n");
|
|
250
|
+
console.log(`\nWrote ${OUT_FILE}: ${shuffled.length} rows`);
|
|
251
|
+
const perTier = {};
|
|
252
|
+
for (const r of shuffled) perTier[r.tier] = (perTier[r.tier] || 0) + 1;
|
|
253
|
+
console.log("Per-tier:", JSON.stringify(perTier));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
main().catch(err => { console.error(err); process.exit(1); });
|