@barivia/barsom-mcp 0.22.4 → 0.23.2
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 +9 -7
- package/dist/audit.js +3 -1
- package/dist/deferred_feedback.js +88 -0
- package/dist/index.js +1 -1
- package/dist/inventory_format.js +69 -0
- package/dist/prepare_training_prompt.js +3 -2
- package/dist/shared.js +240 -43
- package/dist/siom_occupation_format.js +57 -0
- package/dist/tools/account.js +112 -8
- package/dist/tools/datasets.js +74 -23
- package/dist/tools/feedback.js +72 -6
- package/dist/tools/guide_barsom.js +9 -4
- package/dist/tools/jobs.js +78 -17
- package/dist/tools/results.js +12 -12
- package/dist/tools/train.js +43 -6
- package/dist/tools/training_core.js +6 -6
- package/dist/training_scale_guidance.js +73 -0
- package/package.json +1 -1
package/dist/tools/train.js
CHANGED
|
@@ -4,6 +4,7 @@ import { apiCall } from "../shared.js";
|
|
|
4
4
|
import { buildTrainSubmitExtras } from "../train_submit_message.js";
|
|
5
5
|
import { TRAINING_MONITOR_URI, buildTrainingMonitorResult, } from "./training_monitor.js";
|
|
6
6
|
import { TRAINING_INPUT_SCHEMA, buildTrainMapParams, buildFloopParams, fetchTrainingPresets, assertValidNormalizationArgs, } from "./training_core.js";
|
|
7
|
+
import { SMALL_N_SOFT, buildSmallNGuardrailLines, suggestedAutoGridSide, } from "../training_scale_guidance.js";
|
|
7
8
|
export const TRAIN_DESCRIPTION = `Submit a self-organizing map training job. Returns a job_id; poll with jobs(action=status) and then results(action=get). All actions are async.
|
|
8
9
|
|
|
9
10
|
| Action | Use when |
|
|
@@ -13,11 +14,11 @@ export const TRAIN_DESCRIPTION = `Submit a self-organizing map training job. Ret
|
|
|
13
14
|
| impute | Sparse training data: trains a missing-tolerant map (accelerated missSOM; SIOM missSOM when model=auto on som_siom+ or model=SIOM) AND returns dense imputed.csv in one job. Plain numeric columns only. Optional SIOM knobs: gamma, siom_decay, siom_penalty, penalty_alpha, reset_per_epoch. |
|
|
14
15
|
| floop_siom | FLooP-SIOM — a growing node-budget manifold instead of a fixed grid; default topology=free (CHL). Requires a plan with all_algorithms (Premium/Enterprise). |
|
|
15
16
|
|
|
16
|
-
First quick map (no separate baseline action): use \`train(action=map, preset=quick)\` (or
|
|
17
|
+
First quick map (no separate baseline action): use \`train(action=map, preset=quick)\` when n is large enough (hundreds+ rows); for n<1000 prefer omitting grid_x/grid_y for auto (~sqrt(5·√n)) or an explicit small grid — see **training_guidance** / analyze next_steps. On CPU, preset=quick 15×15 is typically very fast up to ~100k rows — do not subsample first.
|
|
17
18
|
|
|
18
19
|
Parameter details (grid, epochs, batch, model, presets, normalize, categorical_features, backend, FLooP max_nodes/effort, SIOM geometry) live in the **training_guidance** tool / **prepare_training** prompt — call them instead of guessing. Backend strings are "cpu" | "gpu" | "gpu_graphs" (no colon); on CPU-only hosts pass backend=cpu.
|
|
19
20
|
|
|
20
|
-
ASYNC: every action returns a job_id immediately. Poll jobs(action=status, job_id=...) every 10–15s (do NOT poll faster). Map training typical times:
|
|
21
|
+
ASYNC: every action returns a job_id immediately. Poll jobs(action=status, job_id=...) every 10–15s (do NOT poll faster). Map training typical times: preset=quick 15×15 often tens of seconds even at ~30k–100k rows on CPU | 20×20 ~3–5 min | 40×40 ~15–30 min — do not assume failure before 3 minutes on CPU. When status is completed, call results(action=get, job_id=...).
|
|
21
22
|
|
|
22
23
|
action=impute: use when many cells are missing across training columns; use inference(impute_column) instead when you already have a complete-case map and need to fill one held-out column. Supports transforms (log/sqrt/etc., not rank), temporal_features, cyclic_features, optional cyclic_pairs=[[cos,sin],...] (circular prototype refresh + atomic chord BMU; those columns get circular quality metrics only — never quote Euclidean R² on cos/sin), and normalization_methods (zscore/mad/sigmoidal/none — not sepd). Does not support auto_log_transforms, rank transforms, or normalize=sepd. Defaults model=auto (SIOM missSOM on som_siom+ plans, else plain missSOM), cv_folds=5 → quality.csv (linear: kfold_holdout_pool + resubstitution_optimistic; cyclic pairs: kfold_holdout_circular). Aggregate MAE/RMSE excludes cyclic columns. Do not treat k-fold alone as true missing-cell ground truth. Optional SIOM knobs: gamma, gamma_f, siom_decay, siom_penalty, penalty_alpha, reset_per_epoch. Artifacts: imputed.csv, imputation_mask.csv, optional imputation_uncertainty.csv (pool_std = neighbourhood dispersion). A completed impute job_id is a valid parent for inference(impute_column).
|
|
23
24
|
action=floop_siom: max_nodes is a TOTAL node budget (not a grid width/area); if coverage collapses, reduce max_nodes before increasing effort.
|
|
@@ -55,6 +56,10 @@ async function submitTrainJob(opts) {
|
|
|
55
56
|
const submitBody = { dataset_id: opts.dataset_id, params: opts.params };
|
|
56
57
|
if (opts.label && opts.label.trim() !== "")
|
|
57
58
|
submitBody.label = opts.label;
|
|
59
|
+
if (opts.description !== undefined)
|
|
60
|
+
submitBody.description = opts.description;
|
|
61
|
+
if (opts.tags !== undefined)
|
|
62
|
+
submitBody.tags = opts.tags;
|
|
58
63
|
const data = (await apiCall("POST", "/v1/jobs", submitBody));
|
|
59
64
|
const newJobId = data.id;
|
|
60
65
|
data.effective_params = `${opts.variantPrefix}, ${opts.paramSummary}`;
|
|
@@ -62,6 +67,11 @@ async function submitTrainJob(opts) {
|
|
|
62
67
|
if (opts.gridLargeForRows) {
|
|
63
68
|
data.grid_auto_warning = true;
|
|
64
69
|
}
|
|
70
|
+
const guardrails = opts.guardrailLines ?? [];
|
|
71
|
+
if (guardrails.length > 0) {
|
|
72
|
+
data.training_guardrails = guardrails;
|
|
73
|
+
data.small_n_warning = opts.totalRows > 0 && opts.totalRows < SMALL_N_SOFT;
|
|
74
|
+
}
|
|
65
75
|
const extras = buildTrainSubmitExtras({
|
|
66
76
|
newJobId,
|
|
67
77
|
totalRows: opts.totalRows,
|
|
@@ -85,8 +95,12 @@ async function submitTrainJob(opts) {
|
|
|
85
95
|
catch {
|
|
86
96
|
/* ignore queue enrichment */
|
|
87
97
|
}
|
|
88
|
-
if (
|
|
89
|
-
msg += `
|
|
98
|
+
if (guardrails.length > 0) {
|
|
99
|
+
msg += ` ${guardrails.join(" ")}`;
|
|
100
|
+
}
|
|
101
|
+
else if (opts.gridLargeForRows) {
|
|
102
|
+
const autoSide = suggestedAutoGridSide(opts.totalRows);
|
|
103
|
+
msg += ` Note: Grid may be large for ${opts.totalRows} rows — prefer omitting grid_x/grid_y for auto (~${autoSide}×${autoSide}) to reduce dead nodes.`;
|
|
90
104
|
}
|
|
91
105
|
data.message = msg;
|
|
92
106
|
data.suggested_next_step = extras.suggested_next_step;
|
|
@@ -104,7 +118,7 @@ async function submitTrainJob(opts) {
|
|
|
104
118
|
export async function runTrain(action, args) {
|
|
105
119
|
const dataset_id = args.dataset_id;
|
|
106
120
|
if (action === "map" || action === "siom_map" || action === "impute") {
|
|
107
|
-
const { preset, grid_x, grid_y, epochs, model, periodic, columns, cyclic_features, cyclic_pairs, temporal_features, feature_weights, transforms, auto_log_transforms, time_delay_embeddings, categorical_features, normalize, normalization_methods, sigma_f, learning_rate, batch_size, quality_metrics, backend, output_format, output_dpi, colormap, row_range, gamma, gamma_f, siom_decay, siom_penalty, penalty_alpha, reset_per_epoch, siom_feature_geometry, siom_qe_backend, siom_qe_batch_size, label, cv_folds, viz_mode, viz_top_components, viz_upload_cyclic_components, emit_cell_uncertainty, } = args;
|
|
121
|
+
const { preset, grid_x, grid_y, epochs, model, periodic, columns, cyclic_features, cyclic_pairs, temporal_features, feature_weights, transforms, auto_log_transforms, time_delay_embeddings, categorical_features, normalize, normalization_methods, sigma_f, learning_rate, batch_size, quality_metrics, backend, output_format, output_dpi, colormap, row_range, gamma, gamma_f, siom_decay, siom_penalty, penalty_alpha, reset_per_epoch, siom_feature_geometry, siom_qe_backend, siom_qe_batch_size, label, description, tags, cv_folds, viz_mode, viz_top_components, viz_upload_cyclic_components, emit_cell_uncertainty, } = args;
|
|
108
122
|
let PRESETS = {};
|
|
109
123
|
try {
|
|
110
124
|
PRESETS = await fetchTrainingPresets();
|
|
@@ -191,6 +205,16 @@ export async function runTrain(action, args) {
|
|
|
191
205
|
typeof transforms === "object" &&
|
|
192
206
|
Object.keys(transforms).length > 0;
|
|
193
207
|
const gridLargeForRows = effectiveGrid && totalRows > 0 && effectiveGrid[0] * effectiveGrid[1] > totalRows * 0.75;
|
|
208
|
+
const nFeatures = Array.isArray(columns) ? columns.length
|
|
209
|
+
: Array.isArray(params.columns) ? params.columns.length
|
|
210
|
+
: undefined;
|
|
211
|
+
const guardrailLines = buildSmallNGuardrailLines({
|
|
212
|
+
n: totalRows,
|
|
213
|
+
nFeatures,
|
|
214
|
+
preset: typeof preset === "string" ? preset : undefined,
|
|
215
|
+
gridX: effectiveGrid?.[0],
|
|
216
|
+
gridY: effectiveGrid?.[1],
|
|
217
|
+
});
|
|
194
218
|
const resultsHint = action === "impute" ? "view the map and imputed.csv"
|
|
195
219
|
: "view the map and metrics";
|
|
196
220
|
const variantPrefix = action === "siom_map" ? "variant=siom"
|
|
@@ -200,6 +224,8 @@ export async function runTrain(action, args) {
|
|
|
200
224
|
dataset_id,
|
|
201
225
|
params,
|
|
202
226
|
label,
|
|
227
|
+
description,
|
|
228
|
+
tags,
|
|
203
229
|
totalRows,
|
|
204
230
|
hasTransforms,
|
|
205
231
|
variantPrefix,
|
|
@@ -207,13 +233,14 @@ export async function runTrain(action, args) {
|
|
|
207
233
|
resultsHint,
|
|
208
234
|
impute: action === "impute",
|
|
209
235
|
gridLargeForRows: !!gridLargeForRows,
|
|
236
|
+
guardrailLines,
|
|
210
237
|
});
|
|
211
238
|
}
|
|
212
239
|
if (action === "floop_siom") {
|
|
213
240
|
if (!dataset_id)
|
|
214
241
|
throw new Error("train(floop_siom) requires dataset_id");
|
|
215
242
|
assertValidNormalizationArgs(args);
|
|
216
|
-
const { transforms, label } = args;
|
|
243
|
+
const { transforms, label, description, tags } = args;
|
|
217
244
|
const { params, paramSummary } = buildFloopParams(args);
|
|
218
245
|
let totalRows = 0;
|
|
219
246
|
try {
|
|
@@ -226,6 +253,8 @@ export async function runTrain(action, args) {
|
|
|
226
253
|
dataset_id,
|
|
227
254
|
params,
|
|
228
255
|
label,
|
|
256
|
+
description,
|
|
257
|
+
tags,
|
|
229
258
|
totalRows,
|
|
230
259
|
hasTransforms,
|
|
231
260
|
variantPrefix: "variant=floop",
|
|
@@ -246,6 +275,14 @@ export function registerTrainTool(server) {
|
|
|
246
275
|
.max(120)
|
|
247
276
|
.optional()
|
|
248
277
|
.describe("Optional run label (≤120 chars); appears in jobs(list) and the jobs(compare) table; sanitized server-side. Useful for sweeps (e.g. label=\"sweep_periodic_true\")."),
|
|
278
|
+
description: z
|
|
279
|
+
.string()
|
|
280
|
+
.optional()
|
|
281
|
+
.describe("Optional free-text description for inventory/navigation (shown in jobs(list) and account(inventory))."),
|
|
282
|
+
tags: z
|
|
283
|
+
.array(z.string())
|
|
284
|
+
.optional()
|
|
285
|
+
.describe("Optional tags for inventory/navigation (e.g. [\"sweep\",\"baseline\"])."),
|
|
249
286
|
...TRAINING_INPUT_SCHEMA,
|
|
250
287
|
}, async (args) => runTrain(args.action, args));
|
|
251
288
|
}
|
|
@@ -46,7 +46,7 @@ export async function fetchTrainingConfig() {
|
|
|
46
46
|
export const TRAINING_INPUT_SCHEMA = {
|
|
47
47
|
preset: z.enum(["quick", "standard", "refined", "high_res"]).optional(),
|
|
48
48
|
grid_x: z.number().int().optional()
|
|
49
|
-
.describe("Grid width. Omit grid_x AND grid_y (and preset) to auto-size
|
|
49
|
+
.describe("Grid width. Omit grid_x AND grid_y (and preset) to auto-size via sqrt(5·√n) per side (clamped 5–50; ~6×6 at n≈45). Pass grid_x/grid_y — not a bare `grid` field. Results report hit_stats.active_node_fraction and grid_suggestion when many nodes are dead."),
|
|
50
50
|
grid_y: z.number().int().optional().describe("Grid height. See grid_x for auto-sizing."),
|
|
51
51
|
epochs: z.preprocess((v) => {
|
|
52
52
|
if (v === undefined || v === null)
|
|
@@ -118,17 +118,17 @@ export const TRAINING_INPUT_SCHEMA = {
|
|
|
118
118
|
emit_cell_uncertainty: z.boolean().optional()
|
|
119
119
|
.describe("impute: write imputation_uncertainty.csv (pool_std = prototype-neighbourhood dispersion, not calibrated SD; pool_n = contributing nodes)"),
|
|
120
120
|
gamma: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.number().optional())
|
|
121
|
-
.describe("siom_map / impute (SIOM or auto on som_siom+): self-interaction strength γ (default 0.5 on
|
|
121
|
+
.describe("siom_map / impute (SIOM or auto on som_siom+): self-interaction strength γ (default 0.5). Prefer γ∈[0.5,1.5] on large n; γ>1.5 often diminishing returns."),
|
|
122
122
|
gamma_f: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.number().optional())
|
|
123
|
-
.describe("siom_map / impute SIOM: final γ for exponential decay
|
|
123
|
+
.describe("siom_map / impute SIOM: final γ for exponential decay (0 = constant γ). Pattern: start strict, relax late (e.g. 1.5→0.2)."),
|
|
124
124
|
siom_decay: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.number().optional())
|
|
125
|
-
.describe("siom_map / impute SIOM: occupation EMA decay λ (default 0.01; 0 = cumulative
|
|
125
|
+
.describe("siom_map / impute SIOM: occupation EMA decay λ (default 0.01; 0 = cumulative). Avoid pairing high γ with aggressive decay (e.g. 0.05)."),
|
|
126
126
|
siom_penalty: z.enum(["linear", "log", "exp"]).optional()
|
|
127
|
-
.describe("siom_map / impute SIOM: occupation penalty shape"),
|
|
127
|
+
.describe("siom_map / impute SIOM: occupation penalty shape (linear default; prefer exp over log — log often worsens TE)"),
|
|
128
128
|
penalty_alpha: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.number().optional())
|
|
129
129
|
.describe("siom_map / impute SIOM: scaling α inside log/exp penalty"),
|
|
130
130
|
reset_per_epoch: z.boolean().optional()
|
|
131
|
-
.describe("siom_map / impute SIOM: reset occupation μ
|
|
131
|
+
.describe("siom_map / impute SIOM: reset occupation μ each epoch (default false; avoid with high γ — can collapse EV)"),
|
|
132
132
|
siom_feature_geometry: z.enum(["l2", "mixed", "auto"]).optional().describe("siom_map: l2 (default) = legacy L2 on cos/sin columns; mixed = cyclic torus distance when cyclic pairs exist; auto = mixed only when cyclic encodings are present"),
|
|
133
133
|
siom_qe_backend: z.enum(["cpu", "cuda", "auto"]).optional().describe("siom_map: backend for metric-aligned siom_qe when using mixed geometry; auto uses GPU when available"),
|
|
134
134
|
siom_qe_batch_size: z.number().int().min(1).max(1_000_000).optional().describe("siom_map: batch size for GPU SIOM QE (default 256 on worker)"),
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scale / small-n guidance shared by datasets(preview|analyze) and train submit.
|
|
3
|
+
* Auto-grid formula matches API/worker: clamp(round(sqrt(5*sqrt(n))), 5, 50).
|
|
4
|
+
*/
|
|
5
|
+
export const SMALL_N_SOFT = 1000;
|
|
6
|
+
export const SMALL_N_HARD = 100;
|
|
7
|
+
/** Agents should not suggest sample_n below this unless the user asks. */
|
|
8
|
+
export const SAMPLE_N_SUGGEST_THRESHOLD = 100_000;
|
|
9
|
+
/** Worker/API auto grid side length. */
|
|
10
|
+
export function suggestedAutoGridSide(n) {
|
|
11
|
+
if (!Number.isFinite(n) || n < 1)
|
|
12
|
+
return 5;
|
|
13
|
+
return Math.max(5, Math.min(50, Math.round(Math.sqrt(5 * Math.sqrt(n)))));
|
|
14
|
+
}
|
|
15
|
+
export function presetGridCells(preset) {
|
|
16
|
+
switch (preset) {
|
|
17
|
+
case "quick":
|
|
18
|
+
return 15 * 15;
|
|
19
|
+
case "standard":
|
|
20
|
+
return 25 * 25;
|
|
21
|
+
case "refined":
|
|
22
|
+
return 40 * 40;
|
|
23
|
+
case "high_res":
|
|
24
|
+
return 60 * 60;
|
|
25
|
+
default:
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Build prominent small-n / oversize-grid warnings for agents.
|
|
31
|
+
* Empty when n is healthy and the chosen grid is not oversized.
|
|
32
|
+
*/
|
|
33
|
+
export function buildSmallNGuardrailLines(opts) {
|
|
34
|
+
const { n, nFeatures, preset, gridX, gridY } = opts;
|
|
35
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
36
|
+
return [];
|
|
37
|
+
const lines = [];
|
|
38
|
+
const autoSide = suggestedAutoGridSide(n);
|
|
39
|
+
const cells = gridX !== undefined && gridY !== undefined
|
|
40
|
+
? gridX * gridY
|
|
41
|
+
: presetGridCells(preset);
|
|
42
|
+
if (n < SMALL_N_HARD) {
|
|
43
|
+
lines.push(`JUMPBACK — small-n (${n} rows): maps are fragile below ~100 rows. Prefer grid_x/grid_y ≈ ${autoSide}×${autoSide} (omit both for auto ≈ sqrt(5·√n)), not preset=quick/standard (15×15/25×25 → mostly dead nodes). Drop redundant geo features (keep distance OR lat/lon, not all three when |r|>0.9). Check hit_stats.active_node_fraction / TE before narrating insights.`);
|
|
44
|
+
}
|
|
45
|
+
else if (n < SMALL_N_SOFT) {
|
|
46
|
+
lines.push(`Warning — small-n (${n} rows < 1000): oversized grids leave dead nodes. Auto grid ≈ ${autoSide}×${autoSide}; omit grid_x/grid_y for auto, or size so |features|×grid_cells is not ≫ n. preset=quick (15×15) is often too large below a few hundred rows — verify active_node_fraction after train.`);
|
|
47
|
+
}
|
|
48
|
+
if (cells !== null && cells > n * 0.75) {
|
|
49
|
+
lines.push(`Grid risk: ${cells} cells vs ${n} rows (cells ≫ n) → expect dead nodes / TE≈1. Use auto (~${autoSide}×${autoSide}) or a smaller explicit grid_x/grid_y.`);
|
|
50
|
+
}
|
|
51
|
+
if (nFeatures !== undefined &&
|
|
52
|
+
nFeatures > 0 &&
|
|
53
|
+
cells !== null &&
|
|
54
|
+
nFeatures * cells > n * 2) {
|
|
55
|
+
lines.push(`Capacity risk: ${nFeatures} features × ${cells} cells ≫ ${n} rows. Drop highly correlated columns (see analyze recommendations) and/or shrink the grid before train.`);
|
|
56
|
+
}
|
|
57
|
+
return lines;
|
|
58
|
+
}
|
|
59
|
+
/** CPU scale guidance: do not subsample by default below 100k. */
|
|
60
|
+
export function buildCpuScaleGuidanceLines(n) {
|
|
61
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
62
|
+
return [];
|
|
63
|
+
if (n <= SAMPLE_N_SUGGEST_THRESHOLD) {
|
|
64
|
+
return [
|
|
65
|
+
`Scale: on CPU, preset=quick (15×15) is typically very fast up to ~100k rows (tens of seconds). Do NOT call datasets(subset)/sample_n for n=${n.toLocaleString()} — train the full dataset_id unless the user asks to downsample. Only suggest sample_n when n>100k or the user requests it.`,
|
|
66
|
+
];
|
|
67
|
+
}
|
|
68
|
+
return [
|
|
69
|
+
`Scale: n=${n.toLocaleString()} > 100k — full-table train is still supported; suggest sample_n only if the user wants a faster exploratory pass or ops runbook says so. Never auto-subset silently.`,
|
|
70
|
+
];
|
|
71
|
+
}
|
|
72
|
+
/** Periodicity caveat for analyze next_steps (row-order dependent). */
|
|
73
|
+
export const PERIODICITY_ROW_ORDER_NOTE = "Periodicity assumes meaningful row order (time/sequence). If the CSV is unordered or shuffled, ignore lag-based suggestions; only use cyclic_features when the column is genuinely cyclic (e.g. hour, month, angle).";
|