lynkr 9.6.0 → 9.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lynkr",
3
- "version": "9.6.0",
3
+ "version": "9.7.0",
4
4
  "description": "Self-hosted LLM gateway and tier-routing proxy for Claude Code, Cursor, and Codex. Routes across Ollama, AWS Bedrock, OpenRouter, Databricks, Azure OpenAI, llama.cpp, and LM Studio with prompt caching, MCP tools, and 60-80% cost savings.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -16,7 +16,7 @@
16
16
  "dev": "nodemon index.js",
17
17
  "lint": "eslint src index.js",
18
18
  "test": "npm run test:unit && npm run test:performance",
19
- "test:unit": "DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com 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/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",
19
+ "test:unit": "DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node --test test/routing.test.js test/hybrid-routing-integration.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/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",
20
20
  "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",
21
21
  "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",
22
22
  "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",
@@ -79,6 +79,7 @@
79
79
  "express": "^5.1.0",
80
80
  "express-rate-limit": "^8.2.1",
81
81
  "fast-glob": "^3.3.2",
82
+ "graphify": "^1.0.0",
82
83
  "hnswlib-node": "^3.0.0",
83
84
  "js-tiktoken": "^1.0.20",
84
85
  "js-yaml": "^4.1.1",
@@ -89,7 +90,7 @@
89
90
  "undici": "^6.22.0"
90
91
  },
91
92
  "optionalDependencies": {
92
- "better-sqlite3": "^12.6.2",
93
+ "better-sqlite3": "^12.11.1",
93
94
  "dockerode": "^4.0.2",
94
95
  "tree-sitter": "^0.21.1",
95
96
  "tree-sitter-javascript": "^0.21.0",
@@ -51,7 +51,7 @@ async function _readTelemetry(days) {
51
51
  return db
52
52
  .prepare(
53
53
  `SELECT request_text AS query, provider, model, quality_score AS quality,
54
- cost, total_latency_ms AS latency, tier
54
+ cost_usd AS cost, latency_ms AS latency, tier
55
55
  FROM routing_telemetry
56
56
  WHERE timestamp >= ?
57
57
  AND quality_score IS NOT NULL
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Convert routellm/gpt4_dataset (train.jsonl) → Lynkr kNN bootstrap JSONL.
4
+
5
+ Stream-downloads from HuggingFace and maps mixtral_score (1-5) to your
6
+ TIER_* model config so the kNN index learns to recommend YOUR real models,
7
+ not RouteLLM's pool (mixtral / gpt-4).
8
+
9
+ Output format matches scripts/build-knn-index.js bootstrap input:
10
+ {"query": "...", "provider": "ollama", "model": "minimax-m2.5:cloud",
11
+ "quality": 95, "cost": 0.001, "latency": 200, "tier": "SIMPLE"}
12
+
13
+ Usage:
14
+ .venv/bin/python scripts/convert-routellm.py [--limit 30000] -o routellm.jsonl
15
+ """
16
+ import argparse
17
+ import json
18
+ import sys
19
+ import urllib.request
20
+ import ssl
21
+
22
+ # Map mixtral_score → user's tier config.
23
+ # Spread is intentionally aggressive: only score=5 (Mixtral truly nailed it)
24
+ # stays SIMPLE; score=4 escalates to MEDIUM so the kNN index has enough MEDIUM
25
+ # examples to actually recommend non-SIMPLE tiers (vs the previous mapping
26
+ # where 86% of records were SIMPLE and kNN never disagreed with the heuristic).
27
+ SCORE_TO_ROUTE = {
28
+ 5: {"tier": "SIMPLE", "provider": "ollama", "model": "minimax-m2.5:cloud", "quality": 95, "cost": 0.001, "latency": 200},
29
+ 4: {"tier": "MEDIUM", "provider": "azure-anthropic", "model": "claude-sonnet-4-6", "quality": 80, "cost": 0.003, "latency": 1500},
30
+ 3: {"tier": "MEDIUM", "provider": "azure-anthropic", "model": "claude-sonnet-4-6", "quality": 70, "cost": 0.003, "latency": 1500},
31
+ 2: {"tier": "COMPLEX", "provider": "azure-anthropic", "model": "claude-sonnet-4-6", "quality": 55, "cost": 0.003, "latency": 1500},
32
+ 1: {"tier": "REASONING", "provider": "azure-anthropic", "model": "claude-opus-4-8", "quality": 40, "cost": 0.015, "latency": 3500},
33
+ }
34
+
35
+ URL = "https://huggingface.co/datasets/routellm/gpt4_dataset/resolve/main/train.jsonl"
36
+
37
+
38
+ def main():
39
+ p = argparse.ArgumentParser()
40
+ p.add_argument("--output", "-o", default="data/routellm-bootstrap.jsonl")
41
+ p.add_argument("--limit", type=int, default=None,
42
+ help="Only convert first N rows")
43
+ args = p.parse_args()
44
+
45
+ print(f"Streaming {URL} …", file=sys.stderr)
46
+ # Some macOS Python builds have SSL issues with huggingface.co — fall back
47
+ # to unverified context if needed. The data is public, no auth.
48
+ ctx = ssl.create_default_context()
49
+ try:
50
+ with urllib.request.urlopen(URL, context=ctx, timeout=60) as resp:
51
+ _stream(resp, args.output, args.limit)
52
+ except (ssl.SSLError, OSError) as e:
53
+ print(f"SSL strict failed ({e}); retrying with unverified context", file=sys.stderr)
54
+ ctx = ssl._create_unverified_context()
55
+ with urllib.request.urlopen(URL, context=ctx, timeout=60) as resp:
56
+ _stream(resp, args.output, args.limit)
57
+
58
+
59
+ def _stream(resp, out_path, limit):
60
+ written = 0
61
+ by_score = {s: 0 for s in SCORE_TO_ROUTE}
62
+ skipped = 0
63
+ with open(out_path, "w") as out:
64
+ for line in resp:
65
+ if limit and written >= limit:
66
+ break
67
+ try:
68
+ row = json.loads(line)
69
+ except Exception:
70
+ skipped += 1
71
+ continue
72
+ score = row.get("mixtral_score")
73
+ if score not in SCORE_TO_ROUTE:
74
+ skipped += 1
75
+ continue
76
+ route = SCORE_TO_ROUTE[score]
77
+ rec = {
78
+ "query": row.get("prompt", ""),
79
+ "provider": route["provider"],
80
+ "model": route["model"],
81
+ "quality": route["quality"],
82
+ "cost": route["cost"],
83
+ "latency": route["latency"],
84
+ "tier": route["tier"],
85
+ }
86
+ if not rec["query"]:
87
+ skipped += 1
88
+ continue
89
+ out.write(json.dumps(rec) + "\n")
90
+ written += 1
91
+ by_score[score] += 1
92
+ if written % 5000 == 0:
93
+ print(f" {written} written…", file=sys.stderr)
94
+
95
+ print(f"\nWrote {written} records to {out_path} ({skipped} skipped)", file=sys.stderr)
96
+ print(f"By mixtral_score: {by_score}", file=sys.stderr)
97
+ print(f"\nTier distribution:", file=sys.stderr)
98
+ for score, route in SCORE_TO_ROUTE.items():
99
+ n = by_score[score]
100
+ if n:
101
+ print(f" {route['tier']:>10} {route['provider']}:{route['model']:30s} n={n}", file=sys.stderr)
102
+
103
+
104
+ if __name__ == "__main__":
105
+ main()