@barivia/barsom-mcp 0.14.0 → 0.15.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.
- package/README.md +3 -2
- package/dist/index.js +1 -1
- package/dist/inference_prepare.js +18 -0
- package/dist/job_status_format.js +75 -0
- package/dist/shared.js +45 -10
- package/dist/tools/datasets.js +87 -14
- package/dist/tools/inference.js +5 -0
- package/dist/tools/jobs.js +17 -18
- package/dist/tools/results.js +2 -2
- package/dist/tools/train.js +37 -10
- package/dist/train_finalize.js +25 -0
- package/dist/train_submit_message.js +52 -0
- package/dist/views/src/views/training-monitor/index.html +18 -12
- package/package.json +1 -1
package/dist/tools/train.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { registerAuditedTool } from "../audit.js";
|
|
3
3
|
import { apiCall, textResult } from "../shared.js";
|
|
4
|
+
import { buildTrainSubmitExtras } from "../train_submit_message.js";
|
|
4
5
|
import { TRAINING_INPUT_SCHEMA, buildTrainMapParams, fetchTrainingPresets, } from "./training_core.js";
|
|
5
6
|
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.
|
|
6
7
|
|
|
@@ -124,29 +125,41 @@ export async function runTrain(action, args) {
|
|
|
124
125
|
: action === "impute" ? "variant=train_impute"
|
|
125
126
|
: "variant=som";
|
|
126
127
|
data.effective_params = `${variantPrefix}, ${paramSummary}`;
|
|
128
|
+
const hasTransforms = action !== "impute" &&
|
|
129
|
+
transforms != null &&
|
|
130
|
+
typeof transforms === "object" &&
|
|
131
|
+
Object.keys(transforms).length > 0;
|
|
132
|
+
const extras = buildTrainSubmitExtras({
|
|
133
|
+
newJobId,
|
|
134
|
+
totalRows,
|
|
135
|
+
hasTransforms,
|
|
136
|
+
prepareJobId: data.prepare_job_id ?? null,
|
|
137
|
+
variantPrefix,
|
|
138
|
+
paramSummary,
|
|
139
|
+
impute: action === "impute",
|
|
140
|
+
});
|
|
127
141
|
try {
|
|
128
142
|
const sys = (await apiCall("GET", "/v1/system/info"));
|
|
129
143
|
const pending = Number(sys.status?.pending_jobs ?? sys.pending_jobs ?? 0);
|
|
130
144
|
const totalEta = Number(sys.training_time_estimates_seconds?.total ??
|
|
131
145
|
(sys.gpu_available ? 45 : 120));
|
|
132
146
|
const waitMinutes = Math.round((pending * totalEta) / 60);
|
|
133
|
-
let msg =
|
|
147
|
+
let msg = extras.message;
|
|
134
148
|
if (waitMinutes > 1)
|
|
135
|
-
msg
|
|
136
|
-
msg += `Poll with jobs(action=status, job_id="${newJobId}"). When status is completed, use results(action=get, job_id="${newJobId}") to view the map${action === "impute" ? " and imputed.csv" : ""} and metrics.`;
|
|
137
|
-
msg += ` Optional: training_monitor(job_id="${newJobId}") for charts—not required.`;
|
|
149
|
+
msg = `You are #${pending + 1} in queue. Estimated wait: ~${waitMinutes} min. ${msg}`;
|
|
138
150
|
if (effectiveGrid && totalRows > 0 && effectiveGrid[0] * effectiveGrid[1] > totalRows * 0.75) {
|
|
139
151
|
msg += ` Note: Grid may be large for ${totalRows} rows (consider grid=auto for fewer dead nodes).`;
|
|
140
152
|
}
|
|
141
153
|
data.message = msg;
|
|
154
|
+
data.suggested_next_step = extras.suggested_next_step;
|
|
142
155
|
}
|
|
143
156
|
catch {
|
|
144
|
-
let msg =
|
|
145
|
-
msg += ` Optional: training_monitor(job_id="${newJobId}") for charts—not required.`;
|
|
157
|
+
let msg = extras.message;
|
|
146
158
|
if (effectiveGrid && totalRows > 0 && effectiveGrid[0] * effectiveGrid[1] > totalRows * 0.75) {
|
|
147
159
|
msg += ` Note: Grid may be large for ${totalRows} rows (consider grid=auto for fewer dead nodes).`;
|
|
148
160
|
}
|
|
149
161
|
data.message = msg;
|
|
162
|
+
data.suggested_next_step = extras.suggested_next_step;
|
|
150
163
|
}
|
|
151
164
|
return textResult(data);
|
|
152
165
|
}
|
|
@@ -229,6 +242,13 @@ export async function runTrain(action, args) {
|
|
|
229
242
|
submitBody.label = label;
|
|
230
243
|
const data = (await apiCall("POST", "/v1/jobs", submitBody));
|
|
231
244
|
const newJobId = data.id;
|
|
245
|
+
let totalRows = 0;
|
|
246
|
+
try {
|
|
247
|
+
const preview = (await apiCall("GET", `/v1/datasets/${dataset_id}/preview?n_rows=1`));
|
|
248
|
+
totalRows = Number(preview?.total_rows ?? 0);
|
|
249
|
+
}
|
|
250
|
+
catch { /* ignore */ }
|
|
251
|
+
const hasTransforms = transforms != null && typeof transforms === "object" && Object.keys(transforms).length > 0;
|
|
232
252
|
const maxNodeSummary = max_nodes === undefined ? "max_nodes=auto(~2*sqrt(n_samples))" : `max_nodes=${max_nodes}`;
|
|
233
253
|
const paramSummary = [
|
|
234
254
|
"variant=floop",
|
|
@@ -238,10 +258,17 @@ export async function runTrain(action, args) {
|
|
|
238
258
|
gamma !== undefined ? `gamma=${gamma}` : "",
|
|
239
259
|
].filter(Boolean).join(", ");
|
|
240
260
|
data.effective_params = paramSummary;
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
261
|
+
const extras = buildTrainSubmitExtras({
|
|
262
|
+
newJobId,
|
|
263
|
+
totalRows,
|
|
264
|
+
hasTransforms,
|
|
265
|
+
prepareJobId: data.prepare_job_id ?? null,
|
|
266
|
+
variantPrefix: "variant=floop",
|
|
267
|
+
paramSummary,
|
|
268
|
+
resultsHint: "view FLooP-SIOM figures and metrics",
|
|
269
|
+
});
|
|
270
|
+
data.message = extras.message;
|
|
271
|
+
data.suggested_next_step = extras.suggested_next_step;
|
|
245
272
|
return textResult(data);
|
|
246
273
|
}
|
|
247
274
|
throw new Error("Invalid train action");
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { apiCall, pollUntilComplete } from "./shared.js";
|
|
2
|
+
/**
|
|
3
|
+
* When train compute finishes, finalize_training may still be rendering figures on worker-io.
|
|
4
|
+
* Poll it before fetching results.
|
|
5
|
+
*/
|
|
6
|
+
export async function pollFinalizeIfPresent(jobId, data, timeoutMs = 600_000) {
|
|
7
|
+
const finalizeJobId = data.finalize_job_id;
|
|
8
|
+
if (!finalizeJobId)
|
|
9
|
+
return { finalizeJobId: null, note: null };
|
|
10
|
+
const poll = await pollUntilComplete(finalizeJobId, timeoutMs);
|
|
11
|
+
if (poll.status === "failed") {
|
|
12
|
+
throw new Error(`Training job ${jobId}: finalize_training ${finalizeJobId} failed: ${poll.error ?? "unknown error"}`);
|
|
13
|
+
}
|
|
14
|
+
if (poll.status !== "completed") {
|
|
15
|
+
throw new Error(`Training job ${jobId}: finalize_training ${finalizeJobId} did not complete (status=${poll.status})`);
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
finalizeJobId,
|
|
19
|
+
note: `Finalize job ${finalizeJobId} completed (figures and summary uploaded).`,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** Re-fetch GET /v1/jobs/:id after finalize so callers see cleared finalize_job_id. */
|
|
23
|
+
export async function refreshJobAfterFinalize(jobId) {
|
|
24
|
+
return (await apiCall("GET", `/v1/jobs/${jobId}`));
|
|
25
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { getVizPort } from "./shared.js";
|
|
2
|
+
export function buildTrainSubmitExtras(opts) {
|
|
3
|
+
const { newJobId, totalRows, hasTransforms, prepareJobId, variantPrefix, paramSummary, impute, resultsHint } = opts;
|
|
4
|
+
const monitorUrl = getVizPort() > 0
|
|
5
|
+
? `http://localhost:${getVizPort()}/viz/training-monitor?mode=standalone&job_id=${encodeURIComponent(newJobId)}`
|
|
6
|
+
: null;
|
|
7
|
+
let msg = `Job submitted (${variantPrefix}, ${paramSummary}). `;
|
|
8
|
+
if (prepareJobId) {
|
|
9
|
+
msg += `Preprocessing job prepare_training_matrix (${prepareJobId}) runs on worker-io first; train job ${newJobId} starts after it completes. Poll jobs(action=status, job_id="${prepareJobId}") until completed, then poll the train job. `;
|
|
10
|
+
}
|
|
11
|
+
msg += `Recommended: training_monitor(job_id="${newJobId}")`;
|
|
12
|
+
if (monitorUrl)
|
|
13
|
+
msg += ` or open ${monitorUrl}`;
|
|
14
|
+
msg += ` while polling jobs(action=status, job_id="${newJobId}") every 60–120s for large jobs. `;
|
|
15
|
+
msg += `When status is completed, check jobs(action=status) for finalize_job_id — if present, poll that finalize_training job before results(action=get). `;
|
|
16
|
+
msg += `Then use results(action=get, job_id="${newJobId}")`;
|
|
17
|
+
if (resultsHint) {
|
|
18
|
+
msg += ` to ${resultsHint}`;
|
|
19
|
+
}
|
|
20
|
+
else if (impute) {
|
|
21
|
+
msg += ` for the map and imputed.csv`;
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
msg += ` to view the map and metrics`;
|
|
25
|
+
}
|
|
26
|
+
msg += `.`;
|
|
27
|
+
if (totalRows >= 1_000_000 || hasTransforms) {
|
|
28
|
+
msg += ` Large-table note: progress may stay low during staging preprocess before epochs start.`;
|
|
29
|
+
if (hasTransforms && totalRows >= 1_000_000) {
|
|
30
|
+
if (prepareJobId) {
|
|
31
|
+
msg += ` Transforms run in prepare_training_matrix on worker-io (full dataset, no CSV re-download on GPU).`;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
msg += ` Transforms require prepare_training_matrix on worker-io for staged datasets; the API enqueues it automatically when needed.`;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const pollSteps = prepareJobId
|
|
39
|
+
? [
|
|
40
|
+
`1) jobs(action=status, job_id="${prepareJobId}") until prepare completes`,
|
|
41
|
+
`2) jobs(action=status, job_id="${newJobId}") every 60–120s until completed`,
|
|
42
|
+
`3) if finalize_job_id is set, poll it until completed`,
|
|
43
|
+
`4) results(action=download, job_id="${newJobId}", folder=".", include_json=true)`,
|
|
44
|
+
]
|
|
45
|
+
: [
|
|
46
|
+
`1) training_monitor(job_id="${newJobId}")`,
|
|
47
|
+
`2) jobs(action=status, job_id="${newJobId}") every 60–120s until completed`,
|
|
48
|
+
`3) if finalize_job_id is set, poll it until completed`,
|
|
49
|
+
`4) results(action=download, job_id="${newJobId}", folder=".", include_json=true)`,
|
|
50
|
+
];
|
|
51
|
+
return { message: msg, suggested_next_step: pollSteps.join(" → ") };
|
|
52
|
+
}
|