adaptive-memory-multi-model-router 2.16.2 → 2.16.3

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.
@@ -72,6 +72,23 @@ export function resolveModel(modelName: string, prompt?: string): ModelMapping |
72
72
  return lookupProviderModel(route.primary_model, available);
73
73
  }
74
74
 
75
+ // 1b. "jev-auto" → System One single-pass routing (Jev interface pattern).
76
+ // Lazy + guarded: falls back to the heuristic router if the jev module
77
+ // or its weights are unavailable.
78
+ if (modelName === "jev-auto") {
79
+ const query = prompt || "";
80
+ let route: ReturnType<typeof routeQuery>;
81
+ try {
82
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
83
+ const { jevRoute } = require("../routing/jev/jevRouter");
84
+ route = jevRoute(query);
85
+ } catch {
86
+ route = routeQuery(query);
87
+ }
88
+ if (!route.primary_model) return null;
89
+ return lookupProviderModel(route.primary_model, available);
90
+ }
91
+
75
92
  // 2. "provider/model" format → direct lookup
76
93
  if (modelName.includes("/")) {
77
94
  const mapping = lookupProviderModel(modelName, available);
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Jev router tests — engine contract, routing shape, fallback behavior.
3
+ */
4
+ import { describe, it, expect, beforeAll } from "vitest";
5
+ import { scoreOptions, scoreScalar, scoreNoul } from "../../src/routing/jev/optionAttention";
6
+ import { jevRoute, loadWeights, resetWeightsCache, MIN_CONFIDENCE } from "../../src/routing/jev/jevRouter";
7
+ import type { OptionAttentionWeights } from "../../src/routing/jev/optionAttention";
8
+
9
+ function toyWeights(): OptionAttentionWeights {
10
+ const D = 16;
11
+ const rand = (s: number) => {
12
+ // deterministic LCG
13
+ let x = s;
14
+ return () => {
15
+ x = (x * 1103515245 + 12345) % 2147483648;
16
+ return (x / 2147483648 - 0.5) * 0.2;
17
+ };
18
+ };
19
+ const r = rand(42);
20
+ const fill = (rows: number, cols: number) => Array.from({ length: rows }, () => Array.from({ length: cols }, () => r()));
21
+ return {
22
+ emb: fill(2048, D),
23
+ wq: fill(D, D),
24
+ bq: new Array(D).fill(0),
25
+ w: Array.from({ length: D }, () => r()),
26
+ b: [0],
27
+ ws: Array.from({ length: D }, () => r()),
28
+ bs: [0],
29
+ temperature: 1.0,
30
+ dim: D,
31
+ };
32
+ }
33
+
34
+ describe("optionAttention engine", () => {
35
+ const W = toyWeights();
36
+
37
+ it("choice probabilities sum to 1 and match option count", () => {
38
+ const r = scoreOptions("write a python function", ["groq fast cheap", "openai premium quality", "ollama local"], W);
39
+ expect(r.probs).toHaveLength(3);
40
+ const sum = r.probs.reduce((a, b) => a + b, 0);
41
+ expect(sum).toBeCloseTo(1.0, 5);
42
+ expect(r.probs.every((p) => p >= 0 && p <= 1)).toBe(true);
43
+ });
44
+
45
+ it("handles unseen options (dynamic option sets)", () => {
46
+ const r = scoreOptions("math proof", ["brand-new-provider strengths:math quality:0.9", "another"], W);
47
+ expect(r.probs).toHaveLength(2);
48
+ expect(r.top[0].prob).toBeGreaterThan(0);
49
+ });
50
+
51
+ it("is deterministic", () => {
52
+ const a = scoreOptions("same ctx", ["x", "y"], W);
53
+ const b = scoreOptions("same ctx", ["x", "y"], W);
54
+ expect(a.probs).toEqual(b.probs);
55
+ });
56
+
57
+ it("empty context and empty options do not throw", () => {
58
+ expect(() => scoreOptions("", ["only"], W)).not.toThrow();
59
+ const r = scoreOptions("ctx", [], W);
60
+ expect(r.probs).toHaveLength(0);
61
+ });
62
+
63
+ it("score head returns value in [0,1]", () => {
64
+ const s = scoreScalar("design a distributed system", W);
65
+ expect(s).toBeGreaterThanOrEqual(0);
66
+ expect(s).toBeLessThanOrEqual(1);
67
+ });
68
+
69
+ it("noul returns calibrated boolean", () => {
70
+ const n = scoreNoul("write sql", "requires code generation", W);
71
+ expect(typeof n.value).toBe("boolean");
72
+ expect(n.prob).toBeGreaterThanOrEqual(0.5);
73
+ expect(n.prob).toBeLessThanOrEqual(1);
74
+ });
75
+
76
+ it("100 options in < 150ms (regression guard; prod path is ~32 opts + query cache ≈ 2ms)", () => {
77
+ const opts = Array.from({ length: 100 }, (_, i) => `provider-${i} strengths:code quality:0.${i % 10}`);
78
+ const t0 = Date.now();
79
+ scoreOptions("some long context here ".repeat(10), opts, W);
80
+ expect(Date.now() - t0).toBeLessThan(150);
81
+ });
82
+ });
83
+
84
+ describe("jevRouter", () => {
85
+ beforeAll(() => resetWeightsCache());
86
+
87
+ it("falls back to System 2 when no weights file", () => {
88
+ // weights file either exists (trained) or not; both paths must return a decision
89
+ const d = jevRoute("write a haiku");
90
+ expect(d).toBeTruthy();
91
+ expect(typeof d.primary_model === "string" || d.primary_model === null).toBe(true);
92
+ expect(d.reasoning).toBeTruthy();
93
+ });
94
+
95
+ it("returns a full RouteDecision shape", () => {
96
+ const d = jevRoute("implement a rate limiter using token bucket");
97
+ expect(d).toHaveProperty("fallback_models");
98
+ expect(d).toHaveProperty("confidence");
99
+ expect(d).toHaveProperty("reasoning");
100
+ expect(d).toHaveProperty("estimated_cost");
101
+ if (d.primary_model) {
102
+ expect(d.confidence).toBeGreaterThan(0);
103
+ }
104
+ });
105
+
106
+ it("low-confidence path delegates to heuristics", () => {
107
+ const d = jevRoute("zzz qwerty obscure gibberish prompt");
108
+ expect(d.reasoning).toBeTruthy();
109
+ });
110
+ });
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ calibrate_temp.py — post-train temperature calibration + weight rounding.
4
+
5
+ Binary-searches temperature so mean(top_prob) on the distillation corpus ≈
6
+ TARGET (slightly below train accuracy → honest calibration), then rounds all
7
+ floats to 4 decimals (≈2× smaller JSON) and rewrites the weights file.
8
+
9
+ Usage: python3 tools/calibrate_temp.py [--target 0.90]
10
+ """
11
+
12
+ import argparse
13
+ import json
14
+ import math
15
+ import os
16
+ import sys
17
+
18
+ import numpy as np
19
+
20
+ sys.path.insert(0, os.path.dirname(__file__))
21
+ from train_jev import tokenize, softmax # reuse exact tokenizer
22
+
23
+ def main():
24
+ ap = argparse.ArgumentParser()
25
+ ap.add_argument("--data", default="data/jev-distill.jsonl")
26
+ ap.add_argument("--weights", default="src/routing/jev/weights/jev-router-weights.json")
27
+ ap.add_argument("--target", type=float, default=0.90)
28
+ args = ap.parse_args()
29
+
30
+ with open(args.weights) as f:
31
+ W = json.load(f)
32
+ emb = np.array(W["emb"]); wq = np.array(W["wq"]); bq = np.array(W["bq"])
33
+ w = np.array(W["w"]); b = np.array(W["b"]); D = W["dim"]
34
+ scale = 1.0 / math.sqrt(D)
35
+
36
+ rows = [json.loads(l) for l in open(args.data) if l.strip()]
37
+ ctx_tok = [np.array(tokenize(r["context"])) for r in rows]
38
+ labels = np.array([r["label"] for r in rows])
39
+
40
+ # precompute all scores once (temperature-independent)
41
+ all_scores = []
42
+ for i, r in enumerate(rows):
43
+ E = emb[ctx_tok[i]]
44
+ Ho = np.stack([emb[np.array(tokenize(o))].mean(axis=0) for o in r["options"]])
45
+ Q = np.tanh(Ho @ wq.T + bq)
46
+ A = softmax((Q @ E.T) * scale, axis=1)
47
+ C = A @ E
48
+ all_scores.append((Q * C) @ w + b[0])
49
+ acc = float(np.mean([int(np.argmax(s)) == y for s, y in zip(all_scores, labels)]))
50
+ print(f"train top-1 (T=1): {acc:.3f}")
51
+
52
+ def mean_top_prob(T):
53
+ ps = [softmax(s / T) for s in all_scores]
54
+ return float(np.mean([p.max() for p in ps]))
55
+
56
+ lo, hi = 0.1, 8.0
57
+ for _ in range(40):
58
+ mid = (lo + hi) / 2
59
+ if mean_top_prob(mid) > args.target:
60
+ lo = mid # too confident → raise T
61
+ else:
62
+ hi = mid
63
+ T = (lo + hi) / 2
64
+ print(f"calibrated temperature: {T:.4f} → mean top-prob {mean_top_prob(T):.3f} (target {args.target})")
65
+
66
+ # round to shrink file
67
+ W["emb"] = np.round(emb, 4).tolist()
68
+ W["wq"] = np.round(wq, 4).tolist()
69
+ W["bq"] = np.round(bq, 4).tolist()
70
+ W["w"] = np.round(w, 4).tolist()
71
+ W["b"] = np.round(b, 4).tolist()
72
+ W["ws"] = np.round(np.array(W["ws"]), 4).tolist()
73
+ W["bs"] = np.round(np.array(W["bs"]), 4).tolist()
74
+ W["temperature"] = round(T, 4)
75
+
76
+ with open(args.weights, "w") as f:
77
+ json.dump(W, f)
78
+ print(f"rewrote {args.weights} ({os.path.getsize(args.weights)/1024:.0f} KB)")
79
+
80
+ if __name__ == "__main__":
81
+ main()
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * distill.mjs — System 2 → System 1 distillation corpus generator.
4
+ *
5
+ * Runs advancedRouter.routeQuery (the heuristic "System 2" router) over a
6
+ * synthetic prompt corpus spanning A3M's routing domains, and emits JSONL:
7
+ *
8
+ * {"context": "<prompt>", "label": <provider idx>, "complexity": 0.42,
9
+ * "needs_code": true, "options": ["<model option text>", ...]}
10
+ *
11
+ * Train with tools/train_jev.py afterwards.
12
+ *
13
+ * Usage: npm run build && node tools/distill.mjs > data/jev-distill.jsonl
14
+ */
15
+
16
+ import { routeQuery, MODEL_PROFILES } from "../dist/routing/advancedRouter.js";
17
+ import { writeFileSync, mkdirSync, readFileSync, existsSync } from "fs";
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Synthetic prompt corpus — domain × complexity × intent coverage
21
+ // ---------------------------------------------------------------------------
22
+
23
+ const TEMPLATES = [
24
+ // trivial / short chat
25
+ ["hi", "hello there", "thanks", "ok got it", "good morning", "hey quick question"],
26
+ ["what is 2+2", "define entropy", "what does HTTP stand for", "name a primary color", "capital of France"],
27
+ // simple code
28
+ ["write a python function to reverse a linked list", "add two numbers in javascript", "css center a div",
29
+ "sql select where clause", "regex match an email", "bash list files by size", "git undo last commit",
30
+ "python read a csv file", "js fetch with async await", "html form with validation"],
31
+ // moderate code
32
+ ["implement a LRU cache in typescript with O(1) operations", "write a rate limiter using token bucket",
33
+ "refactor this express app into clean architecture layers", "design a postgres schema for multi-tenant SaaS",
34
+ "write unit tests for a react hook", "dockerize a fastapi service with healthchecks",
35
+ "explain this stack trace and fix the race condition", "optimize this slow SQL query with proper indexes"],
36
+ // hard / architecture
37
+ ["design a distributed rate limiter for 10k rps across regions", "architecture review: should we move from REST to gRPC",
38
+ "implement consensus for a 5-node kv store", "migrate a monolith to event-driven microservices with zero downtime",
39
+ "design a vector database index for billion-scale embeddings", "review this kubernetes manifest for production readiness"],
40
+ // math / reasoning
41
+ ["prove the integral of 1/x is ln|x|", "solve this differential equation numerically", "bayes theorem applied to medical testing",
42
+ "calculate the eigenvalues of this matrix", "explain the central limit theorem with a proof", "monte carlo simulation in python"],
43
+ // domains
44
+ ["diagnose chest pain differential", "explain insulin resistance pathophysiology", "clinical trial design for a new oncology drug",
45
+ "contract clause review for liability limits", "GDPR compliance checklist for a saas", "case law on fair use for AI training data",
46
+ "portfolio optimization with markowitz", "explain this 10-K cash flow statement", "hedge delta-neutral options strategy",
47
+ "fine-tune bert for sentiment classification", "compare adam vs sgd optimizer convergence", "interpret this shapley value plot",
48
+ "promote soil health in organic farming", "structural load calculations for a bridge", "chemical synthesis route planning"],
49
+ // translation / multilingual
50
+ ["translate this sentence to japanese: good morning", "traduce esta frase al español",
51
+ " übersetze diesen Satz ins Deutsche", "translate and localize this UI string to hindi"],
52
+ // creative
53
+ ["write a haiku about the sea", "short story about a robot learning to paint",
54
+ "marketing copy for an eco water bottle", "write a limerick about databases", "brainstorm names for a coffee startup"],
55
+ // security
56
+ ["penetration test plan for a web app", " OWASP top 10 mitigations", "secure secret storage in kubernetes",
57
+ "threat model an oauth flow", "analyze this suspicious payload"],
58
+ // devops / infra
59
+ ["terraform module for vpc with private subnets", "kubernetes hpa based on custom metrics",
60
+ "ci pipeline with canary deploys", "postgres replication lag troubleshooting", "nginx rate limiting config"],
61
+ // multimodal
62
+ ["describe this image and extract the text", "transcribe this audio file to text",
63
+ "analyze this chart and summarize trends", "generate alt text for this screenshot"],
64
+ // long context
65
+ ["summarize this 50 page document about climate policy: " + "policy considerations ".repeat(80),
66
+ "review this long transcript and extract action items: " + "the team discussed ".repeat(100)],
67
+ ];
68
+
69
+ const LENGTHS = ["", "be brief", "be detailed and thorough", "step by step",
70
+ "explain like I am five", "with code examples", "in a table", "as a checklist"];
71
+
72
+ const PREFIXES = ["", "please ", "I need you to ", "can you ", "urgent: ", ""];
73
+
74
+ function corpus() {
75
+ const prompts = [];
76
+ for (const group of TEMPLATES) {
77
+ for (const p of group) {
78
+ prompts.push(p);
79
+ for (let k = 0; k < 3; k++) {
80
+ const pre = PREFIXES[Math.floor(Math.random() * PREFIXES.length)];
81
+ const suf = LENGTHS[Math.floor(Math.random() * LENGTHS.length)];
82
+ prompts.push(pre + p + (suf ? " — " + suf : ""));
83
+ }
84
+ }
85
+ }
86
+ return prompts;
87
+ }
88
+
89
+ // ---------------------------------------------------------------------------
90
+ // Run
91
+ // ---------------------------------------------------------------------------
92
+
93
+ const OUT_DIR = new URL("../data/", import.meta.url).pathname;
94
+ mkdirSync(OUT_DIR, { recursive: true });
95
+
96
+ // warm profile cache
97
+ routeQuery("warmup");
98
+ const profiles = MODEL_PROFILES;
99
+ const modelNames = Object.keys(profiles);
100
+ if (modelNames.length === 0) {
101
+ console.error("no model profiles — check provider config");
102
+ process.exit(1);
103
+ }
104
+
105
+ // option text must EXACTLY match jevRouter.optionTextForModel
106
+ function optionTextForModel(model) {
107
+ const p = profiles[model];
108
+ const strengths = (p.strengths || []).slice(0, 6).join(",");
109
+ const cost = p.cost_per_1k_input != null ? ((p.cost_per_1k_input + (p.cost_per_1k_output || 0)) / 2).toFixed(4) : "?";
110
+ return `${model} provider:${p.providerName || "?"} type:${p.type || "api"} strengths:${strengths} quality:${p.quality_score ?? "?"} cost:${cost}`;
111
+ }
112
+
113
+ const options = modelNames.map(optionTextForModel);
114
+
115
+ // merge benchmark prompts if present
116
+ let bench = [];
117
+ const benchPath = new URL("../eval/benchmark_dataset.jsonl", import.meta.url).pathname;
118
+ if (existsSync(benchPath)) {
119
+ bench = readFileSync(benchPath, "utf8").trim().split("\n").map((l) => JSON.parse(l).prompt);
120
+ }
121
+
122
+ const all = [...new Set([...corpus(), ...bench])];
123
+ const rows = [];
124
+ for (const prompt of all) {
125
+ const d = routeQuery(prompt);
126
+ if (!d.primary_model) continue;
127
+ const label = modelNames.indexOf(d.primary_model);
128
+ if (label < 0) continue;
129
+ rows.push({
130
+ context: prompt,
131
+ label,
132
+ complexity: d.features?.complexity ?? 0.3,
133
+ needs_code: d.features?.has_code ?? false,
134
+ options,
135
+ });
136
+ }
137
+
138
+ const outPath = OUT_DIR + "jev-distill.jsonl";
139
+ writeFileSync(outPath, rows.map((r) => JSON.stringify(r)).join("\n"));
140
+ console.error(`wrote ${rows.length} rows → ${outPath} (${modelNames.length} candidate models)`);
141
+ const dist = {};
142
+ for (const r of rows) dist[modelNames[r.label]] = (dist[modelNames[r.label]] || 0) + 1;
143
+ console.error("label distribution:", JSON.stringify(dist, null, 0).slice(0, 400));
@@ -0,0 +1,213 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ train_jev.py — train the OptionAttention System One router (numpy only, vectorized).
4
+
5
+ Reads data/jev-distill.jsonl (from tools/distill.mjs), trains:
6
+ - choice head: provider option → calibrated probability (cross-entropy)
7
+ - score head: context → complexity in [0,1] (MSE + sigmoid)
8
+ - noul head: context+"question" → boolean prob (CE, 2-way)
9
+
10
+ Architecture (must match src/routing/jev/optionAttention.ts):
11
+ q_i = tanh(Wq · meanPool(trigrams(option)) + bq)
12
+ a_ij = softmax_j(q_i · E_j / sqrt(D))
13
+ c_i = Σ_j a_ij E_j
14
+ s_i = w · (q_i ⊙ c_i) + b → softmax(s/T) = probs
15
+
16
+ Usage:
17
+ python3 tools/train_jev.py [--epochs 200] [--dim 64] [--lr 0.5]
18
+ """
19
+
20
+ import argparse
21
+ import json
22
+ import math
23
+ import os
24
+ import sys
25
+ import time
26
+
27
+ import numpy as np
28
+
29
+ VOCAB = 2048
30
+ CTX_CAP = 160
31
+
32
+ def trigram_hash(b0, b1, b2):
33
+ h = 0x811C9DC5
34
+ for c in (b0 & 0xFF, b1 & 0xFF, b2 & 0xFF):
35
+ h = ((h ^ c) * 0x01000193) & 0xFFFFFFFF
36
+ return h % VOCAB
37
+
38
+ def tokenize(text, cap=CTX_CAP):
39
+ s = " " + text + " "
40
+ out = []
41
+ for i in range(len(s) - 2):
42
+ out.append(trigram_hash(ord(s[i]), ord(s[i+1]), ord(s[i+2])))
43
+ if len(out) >= cap:
44
+ break
45
+ return out or [trigram_hash(32, 32, 32)]
46
+
47
+ def softmax(x, axis=-1):
48
+ x = x - np.max(x, axis=axis, keepdims=True)
49
+ e = np.exp(x)
50
+ return e / np.maximum(e.sum(axis=axis, keepdims=True), 1e-9)
51
+
52
+ def main():
53
+ ap = argparse.ArgumentParser()
54
+ ap.add_argument("--data", default="data/jev-distill.jsonl")
55
+ ap.add_argument("--out", default="src/routing/jev/weights/jev-router-weights.json")
56
+ ap.add_argument("--epochs", type=int, default=200)
57
+ ap.add_argument("--dim", type=int, default=64)
58
+ ap.add_argument("--lr", type=float, default=0.5)
59
+ args = ap.parse_args()
60
+
61
+ rows = []
62
+ with open(args.data) as f:
63
+ for line in f:
64
+ line = line.strip()
65
+ if line:
66
+ rows.append(json.loads(line))
67
+ if not rows:
68
+ sys.exit("no training rows — run tools/distill.mjs first")
69
+
70
+ rng = np.random.default_rng(42)
71
+ D = args.dim
72
+ lr = args.lr
73
+ lr_c = args.lr # choice path
74
+ lr_s = 0.20 # score path
75
+
76
+ emb = rng.normal(0, 0.40, (VOCAB, D))
77
+ wq = rng.normal(0, 0.40, (D, D))
78
+ bq = np.zeros(D)
79
+ w = rng.normal(0, 1.2, D)
80
+ b = np.zeros(1)
81
+ ws = rng.normal(0, 0.30, D)
82
+ bs = np.array([0.0])
83
+ temperature = 1.0
84
+
85
+ NQ = "requires code generation"
86
+ nq_yes_tok = np.array(tokenize(NQ + " — yes"))
87
+ nq_no_tok = np.array(tokenize(NQ + " — no"))
88
+
89
+ labels = np.array([r["label"] for r in rows])
90
+ complexity = np.array([r["complexity"] for r in rows])
91
+ needs_code = np.array([1.0 if r["needs_code"] else 0.0 for r in rows])
92
+ n = len(rows)
93
+
94
+ ctx_tok = [np.array(tokenize(r["context"])) for r in rows]
95
+ opt_tok = [[np.array(tokenize(o)) for o in r["options"]] for r in rows]
96
+ opt_lens = np.array([[len(t) for t in orow] for orow in opt_tok], dtype=float)
97
+
98
+ scale = 1.0 / math.sqrt(D)
99
+ M = len(rows[0]["options"])
100
+
101
+ def forward_choice(i, opt_tokens):
102
+ E = emb[ctx_tok[i]] # (T, D)
103
+ Ho = np.stack([emb[ot].mean(axis=0) for ot in opt_tokens]) # (M, D)
104
+ Q = np.tanh(Ho @ wq.T + bq) # (M, D)
105
+ S = (Q @ E.T) * scale # (M, T)
106
+ A = softmax(S, axis=1)
107
+ C = A @ E # (M, D)
108
+ scores = (Q * C) @ w + b[0]
109
+ return E, Ho, Q, A, C, scores
110
+
111
+ print(f"rows={n} options={M} dim={D} epochs={args.epochs} lr={lr}")
112
+ t_start = time.time()
113
+ for epoch in range(args.epochs):
114
+ ce = se = ne = 0.0
115
+ correct = 0
116
+ order = rng.permutation(n)
117
+ for i in order:
118
+ y = labels[i]
119
+ E, Ho, Q, A, C, scores = forward_choice(i, opt_tok[i])
120
+ probs = softmax(scores / temperature)
121
+ ce += -math.log(max(probs[y], 1e-9))
122
+ if int(np.argmax(probs)) == y:
123
+ correct += 1
124
+
125
+ # ---- backward (vectorized over options) ----
126
+ ds = probs.copy()
127
+ ds[y] -= 1.0
128
+ ds /= temperature # (M,)
129
+
130
+ w -= lr_c * ((Q * C) * ds[:, None]).sum(0) # scoring vector
131
+ b -= lr_c * 0.10 * ds.sum()
132
+
133
+ dQ = ds[:, None] * (w * C) * (1 - Q * Q) # (M, D)
134
+ wq -= lr_c * 0.10 * dQ.T @ Ho
135
+ bq -= lr_c * 0.10 * dQ.sum(0)
136
+ dHo = dQ @ wq # (M, D)
137
+
138
+ # option-token embedding grads — one batched np.add.at per row
139
+ flat_idx = np.concatenate([t for t in opt_tok[i]])
140
+ flat_rep = np.concatenate([np.full(len(t), m) for m, t in enumerate(opt_tok[i])])
141
+ vals = (dHo[flat_rep] / opt_lens[i][flat_rep, None])
142
+ np.add.at(emb, flat_idx, -lr_c * 0.10 * vals)
143
+
144
+ # context embedding grads: dE = A.T @ dC
145
+ dC = ds[:, None] * (w * Q) # (M, D)
146
+ dE = A.T @ dC # (T, D)
147
+ np.add.at(emb, ctx_tok[i], -lr_c * 0.10 * dE)
148
+
149
+ # ---- score head ----
150
+ p = E.mean(axis=0)
151
+ z = float(ws @ p + bs[0])
152
+ sig = 1 / (1 + math.exp(-z))
153
+ se += (sig - complexity[i]) ** 2
154
+ gz = (sig - complexity[i]) * sig * (1 - sig)
155
+ ws -= lr_s * gz * p
156
+ bs -= lr_s * gz * 0.10
157
+ np.add.at(emb, ctx_tok[i], -lr_s * 0.05 * (gz * ws) / E.shape[0])
158
+
159
+ # ---- noul head (every 4th epoch) ----
160
+ if epoch % 4 == 0:
161
+ yn = 0 if needs_code[i] > 0.5 else 1
162
+ ntoks = [nq_yes_tok, nq_no_tok]
163
+ Hn = np.stack([emb[ot].mean(axis=0) for ot in ntoks])
164
+ Qn = np.tanh(Hn @ wq.T + bq)
165
+ Sn = (Qn @ E.T) * scale
166
+ An = softmax(Sn, axis=1)
167
+ Cn = An @ E
168
+ ns = (Qn * Cn) @ w + b[0]
169
+ npr = softmax(ns)
170
+ ne += -math.log(max(npr[yn], 1e-9))
171
+ gn = npr.copy()
172
+ gn[yn] -= 1.0
173
+ dQn = gn[:, None] * (w * Cn) * (1 - Qn * Qn)
174
+ wq -= lr_c * 0.05 * dQn.T @ Hn
175
+ bq -= lr_c * 0.05 * dQn.sum(0)
176
+
177
+ if epoch % 10 == 0 or epoch == args.epochs - 1:
178
+ el = time.time() - t_start
179
+ print(f"epoch {epoch:4d} CE={ce/n:.4f} (chance={math.log(M):.3f}) "
180
+ f"SE={se/n:.5f} NE={ne/n:.4f} top1={correct/n:.3f} [{el:.0f}s]")
181
+
182
+ # ---------- final eval ----------
183
+ correct = 0
184
+ for i in range(n):
185
+ _, _, _, _, _, scores = forward_choice(i, opt_tok[i])
186
+ if int(np.argmax(scores)) == labels[i]:
187
+ correct += 1
188
+ print(f"FINAL train top-1: {correct/n:.3f}")
189
+
190
+ # ---------- temperature calibration on train confidence ----------
191
+ # scale T so mean top-prob lands near the observed accuracy (calibrated)
192
+ gaps = []
193
+ for i in range(0, n, 4):
194
+ _, _, _, _, _, scores = forward_choice(i, opt_tok[i])
195
+ srt = np.sort(scores)[::-1]
196
+ gaps.append(srt[0] - srt[1])
197
+ mean_gap = float(np.mean(gaps)) if gaps else 1.0
198
+ temperature = max(0.25, min(4.0, mean_gap))
199
+ print(f"calibrated temperature: {temperature:.3f}")
200
+
201
+ os.makedirs(os.path.dirname(args.out), exist_ok=True)
202
+ weights = {
203
+ "emb": emb.tolist(), "wq": wq.tolist(), "bq": bq.tolist(),
204
+ "w": w.tolist(), "b": b.tolist(),
205
+ "ws": ws.tolist(), "bs": bs.tolist(),
206
+ "temperature": temperature, "dim": D,
207
+ }
208
+ with open(args.out, "w") as f:
209
+ json.dump(weights, f)
210
+ print(f"wrote {args.out} ({os.path.getsize(args.out)/1024:.0f} KB)")
211
+
212
+ if __name__ == "__main__":
213
+ main()
package/dist/cli/tui.d.ts DELETED
@@ -1,6 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * A3M Router Quick TUI
4
- * Zero-config experience - no API key needed for free tier
5
- */
6
- export {};
@@ -1 +0,0 @@
1
- {"version":3,"file":"tui.js","sourceRoot":"","sources":["../../src/cli/tui.ts"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"shadowSampler.d.ts","sourceRoot":"","sources":["../../src/routing/shadowSampler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAAc,oBAAoB,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAInF,MAAM,WAAW,mBAAmB;IAClC;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAE9B;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;OAEG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,cAAe,SAAQ,aAAa;IACnD,2DAA2D;IAC3D,SAAS,EAAE,OAAO,CAAC;IACnB,sDAAsD;IACtD,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,gDAAgD;IAChD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,wDAAwD;IACxD,QAAQ,EAAE,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC;IAClD,4CAA4C;IAC5C,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,KAAK,GAAG,WAAW,CAAC;IACnD,eAAe,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAgG;IAG9G,OAAO,CAAC,eAAe,CAAuB;IAG9C,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,WAAW,CAAK;IAExB,YAAY,MAAM,GAAE,mBAAwB,EAW3C;IAED;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAiC5B;;;;;;;;;;OAUG;IACH,OAAO,CAAC,kBAAkB;IAoB1B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,wBAAwB;IA4DhC;;;;;;OAMG;IACH,eAAe,CACb,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;QAAE,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;KAAE,GACpE,cAAc,CA+BhB;IAED;;;;;;;OAOG;IACG,cAAc,CAClB,aAAa,EAAE,MAAM,EACrB,YAAY,EAAE,MAAM,GAAG,IAAI,GAC1B,OAAO,CAAC,gBAAgB,CAAC,CAgD3B;IAED;;OAEG;IACH,QAAQ,IAAI;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAM1E;IAED;;OAEG;IACH,UAAU,IAAI,IAAI,CAGjB;IAED;;OAEG;IACH,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,mBAAmB,CAAC,GAAG,IAAI,CAEpD;CACF"}