maintainence love:)
This commit is contained in:
parent
2f2d507baa
commit
b2ccf37de9
@ -244,7 +244,21 @@ Integration tests live under **`orchestrator/`** (see comments in `orchestrator/
|
||||
npm run test:ipc
|
||||
```
|
||||
|
||||
Requires a built **`dist/kbot.exe`** (or `kbot` on Unix).
|
||||
Classifier batch (semantic distances vs JobViewer labels):
|
||||
|
||||
```bash
|
||||
npm run test:ipc:classifier
|
||||
npm run test:ipc:classifier:openrouter
|
||||
```
|
||||
|
||||
Stress: repeat the **same** batched `kbot-ai` call **N** times on **one** worker; prints per-run wall time, token usage (when present), then **min / max / avg / p50 / p95** and Σ tokens. Default **N = 5** for the OpenRouter stress script:
|
||||
|
||||
```bash
|
||||
npm run test:ipc:classifier:openrouter:stress
|
||||
KBOT_CLASSIFIER_STRESS_RUNS=10 npm run test:ipc:classifier:openrouter:stress
|
||||
```
|
||||
|
||||
Requires a built **`dist/kbot.exe`** (or `kbot` on Unix). Set API keys via `config/postgres.toml` for OpenRouter.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@ -290,6 +290,28 @@ export function renderMarkdownReport(payload) {
|
||||
}
|
||||
}
|
||||
|
||||
if (payload.stress?.summary && typeof payload.stress.summary === 'object') {
|
||||
const s = payload.stress.summary;
|
||||
const w = s.wallMs;
|
||||
lines.push('## Classifier stress (batch repeats)');
|
||||
lines.push('');
|
||||
lines.push(`| Metric | Value |`);
|
||||
lines.push(`| --- | --- |`);
|
||||
lines.push(`| Requested runs | ${s.requestedRuns ?? '—'} |`);
|
||||
if (w && typeof w === 'object') {
|
||||
lines.push(
|
||||
`| Wall time (ms) | min ${w.min} · max ${w.max} · avg ${w.avg} · p50 ${w.p50} · p95 ${w.p95} |`
|
||||
);
|
||||
}
|
||||
lines.push(`| Batch OK / fail | ${s.successCount ?? '—'} / ${s.failCount ?? '—'} |`);
|
||||
if (s.totalTokens > 0 || s.totalPromptTokens > 0 || s.totalCompletionTokens > 0) {
|
||||
lines.push(
|
||||
`| Σ tokens (prompt / completion / total) | ${s.totalPromptTokens} / ${s.totalCompletionTokens} / ${s.totalTokens} |`
|
||||
);
|
||||
}
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
if (payload.env && typeof payload.env === 'object') {
|
||||
lines.push('## Environment (selected)');
|
||||
lines.push('');
|
||||
|
||||
@ -5,6 +5,10 @@
|
||||
* to every business label (JobViewer.tsx ~205). Output is a single JSON array (+ meta).
|
||||
*
|
||||
* Run: npm run test:ipc:classifier
|
||||
* CLI (overrides env): yargs — see parseClassifierArgv()
|
||||
* npm run test:ipc:classifier -- --help
|
||||
* npm run test:ipc:classifier -- --provider openrouter --model openai/gpt-4o-mini --backend remote -n 3
|
||||
* npm run test:ipc:classifier -- -r openrouter -m x -F stress,no-heartbeat
|
||||
*
|
||||
* Env:
|
||||
* KBOT_IPC_CLASSIFIER_LLAMA — set 0 to use OpenRouter (KBOT_ROUTER, KBOT_IPC_MODEL) instead of local llama :8888
|
||||
@ -14,6 +18,8 @@
|
||||
* KBOT_CLASSIFIER_TIMEOUT_MS — single batched kbot-ai call (default: 300000)
|
||||
*
|
||||
* OpenRouter: npm run test:ipc:classifier:openrouter (sets KBOT_IPC_CLASSIFIER_LLAMA=0)
|
||||
* Stress (batch repeats, one worker): KBOT_CLASSIFIER_STRESS_RUNS=N (default 1)
|
||||
* npm run test:ipc:classifier:openrouter:stress → OpenRouter + 5 runs (override N via env)
|
||||
*
|
||||
* Reports (reports.js): cwd/tests/test-ipc-classifier__HH-mm.{json,md}; array-only distances in
|
||||
* test-ipc-classifier-distances__HH-mm.json (same timestamp as the main JSON).
|
||||
@ -25,6 +31,8 @@ import { dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import net from 'node:net';
|
||||
import { existsSync, unlinkSync } from 'node:fs';
|
||||
import yargs from 'yargs';
|
||||
import { hideBin } from 'yargs/helpers';
|
||||
|
||||
import {
|
||||
distExePath,
|
||||
@ -57,6 +65,116 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
/** Set at run start; used by catch for error reports */
|
||||
let classifierMetricsCollector = null;
|
||||
let classifierRunStartedAt = null;
|
||||
/** Feature flags from `-F` / `--feature` (stress, no-heartbeat, no-report, quiet) */
|
||||
let classifierFeatures = /** @type {Set<string>} */ (new Set());
|
||||
/** Parsed argv (after yargs); set in parseClassifierArgv */
|
||||
let classifierArgv = /** @type {Record<string, unknown> | null} */ (null);
|
||||
|
||||
/**
|
||||
* @param {unknown} featureOpt
|
||||
* @returns {Set<string>}
|
||||
*/
|
||||
function parseFeatureList(featureOpt) {
|
||||
const out = [];
|
||||
const arr = Array.isArray(featureOpt) ? featureOpt : [];
|
||||
for (const f of arr) {
|
||||
if (typeof f === 'string') out.push(...f.split(',').map((s) => s.trim()).filter(Boolean));
|
||||
}
|
||||
return new Set(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse CLI and apply to `process.env` (CLI wins over prior env).
|
||||
* @returns {Record<string, unknown> & { featuresSet: Set<string> }}
|
||||
*/
|
||||
export function parseClassifierArgv() {
|
||||
const y = yargs(hideBin(process.argv))
|
||||
.scriptName('test-ipc-classifier')
|
||||
.usage('$0 [options]\n\nIPC classifier batch test. Flags override env vars for this process.')
|
||||
.option('provider', {
|
||||
alias: 'r',
|
||||
type: 'string',
|
||||
describe: 'Router / provider → KBOT_ROUTER (e.g. openrouter, ollama, openai)',
|
||||
})
|
||||
.option('model', {
|
||||
alias: 'm',
|
||||
type: 'string',
|
||||
describe: 'Model id → KBOT_IPC_MODEL',
|
||||
})
|
||||
.option('runs', {
|
||||
alias: 'n',
|
||||
type: 'number',
|
||||
describe: 'Batch repeats (stress) → KBOT_CLASSIFIER_STRESS_RUNS',
|
||||
})
|
||||
.option('limit', {
|
||||
alias: 'l',
|
||||
type: 'number',
|
||||
describe: 'Max labels → KBOT_CLASSIFIER_LIMIT',
|
||||
})
|
||||
.option('timeout', {
|
||||
alias: 't',
|
||||
type: 'number',
|
||||
describe: 'LLM HTTP timeout ms → KBOT_CLASSIFIER_TIMEOUT_MS',
|
||||
})
|
||||
.option('backend', {
|
||||
type: 'string',
|
||||
choices: ['local', 'remote'],
|
||||
describe: 'local = llama :8888; remote = router (sets KBOT_IPC_CLASSIFIER_LLAMA=0)',
|
||||
})
|
||||
.option('no-autostart', {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
describe: 'Do not spawn run-7b.sh → KBOT_IPC_LLAMA_AUTOSTART=0',
|
||||
})
|
||||
.option('feature', {
|
||||
alias: 'F',
|
||||
type: 'array',
|
||||
default: [],
|
||||
describe:
|
||||
'Feature flags (repeat or comma-separated): stress, no-heartbeat, no-report, quiet',
|
||||
})
|
||||
.strict()
|
||||
.help()
|
||||
.alias('h', 'help');
|
||||
|
||||
const argv = y.parseSync();
|
||||
const featuresSet = parseFeatureList(argv.feature);
|
||||
|
||||
if (argv.provider != null && String(argv.provider).trim() !== '') {
|
||||
process.env.KBOT_ROUTER = String(argv.provider).trim();
|
||||
}
|
||||
if (argv.model != null && String(argv.model).trim() !== '') {
|
||||
process.env.KBOT_IPC_MODEL = String(argv.model).trim();
|
||||
}
|
||||
if (argv.runs != null && Number.isFinite(argv.runs) && argv.runs >= 1) {
|
||||
process.env.KBOT_CLASSIFIER_STRESS_RUNS = String(Math.min(500, Math.floor(Number(argv.runs))));
|
||||
}
|
||||
if (argv.limit != null && Number.isFinite(argv.limit) && argv.limit >= 1) {
|
||||
process.env.KBOT_CLASSIFIER_LIMIT = String(Math.floor(Number(argv.limit)));
|
||||
}
|
||||
if (argv.timeout != null && Number.isFinite(argv.timeout) && argv.timeout > 0) {
|
||||
process.env.KBOT_CLASSIFIER_TIMEOUT_MS = String(Math.floor(Number(argv.timeout)));
|
||||
}
|
||||
if (argv['no-autostart'] === true) {
|
||||
process.env.KBOT_IPC_LLAMA_AUTOSTART = '0';
|
||||
}
|
||||
if (argv.backend === 'remote') {
|
||||
process.env.KBOT_IPC_CLASSIFIER_LLAMA = '0';
|
||||
} else if (argv.backend === 'local') {
|
||||
delete process.env.KBOT_IPC_CLASSIFIER_LLAMA;
|
||||
}
|
||||
|
||||
if (featuresSet.has('stress') && (argv.runs == null || !Number.isFinite(argv.runs))) {
|
||||
if (!process.env.KBOT_CLASSIFIER_STRESS_RUNS) {
|
||||
process.env.KBOT_CLASSIFIER_STRESS_RUNS = '5';
|
||||
}
|
||||
}
|
||||
|
||||
classifierFeatures = featuresSet;
|
||||
const out = { ...argv, featuresSet };
|
||||
classifierArgv = out;
|
||||
return out;
|
||||
}
|
||||
const EXE = distExePath(__dirname);
|
||||
const stats = createAssert();
|
||||
const { assert } = stats;
|
||||
@ -166,6 +284,44 @@ function batchTimeoutMs() {
|
||||
return Number.isFinite(n) && n > 0 ? n : 300_000;
|
||||
}
|
||||
|
||||
/** Sequential batch iterations on one worker (stress). Default 1 = single run. */
|
||||
function stressRunCount() {
|
||||
const raw = process.env.KBOT_CLASSIFIER_STRESS_RUNS;
|
||||
if (raw === undefined || raw === '') return 1;
|
||||
const n = Number.parseInt(String(raw).trim(), 10);
|
||||
if (!Number.isFinite(n) || n < 1) return 1;
|
||||
return Math.min(n, 500);
|
||||
}
|
||||
|
||||
/** @param {unknown} llm — job_result.llm from kbot-ai */
|
||||
function usageTokens(llm) {
|
||||
if (!llm || typeof llm !== 'object') return null;
|
||||
const u = /** @type {Record<string, unknown>} */ (llm).usage;
|
||||
if (!u || typeof u !== 'object') return null;
|
||||
const o = /** @type {Record<string, unknown>} */ (u);
|
||||
return {
|
||||
prompt: o.prompt_tokens ?? o.promptTokens ?? null,
|
||||
completion: o.completion_tokens ?? o.completionTokens ?? null,
|
||||
total: o.total_tokens ?? o.totalTokens ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
/** @param {number[]} values */
|
||||
function summarizeMs(values) {
|
||||
if (values.length === 0) return null;
|
||||
const sorted = [...values].sort((a, b) => a - b);
|
||||
const sum = values.reduce((a, b) => a + b, 0);
|
||||
const mid = (a, b) => (a + b) / 2;
|
||||
const p = (q) => sorted[Math.min(sorted.length - 1, Math.max(0, Math.floor(q * (sorted.length - 1))))];
|
||||
return {
|
||||
min: sorted[0],
|
||||
max: sorted[sorted.length - 1],
|
||||
avg: Math.round((sum / values.length) * 100) / 100,
|
||||
p50: sorted.length % 2 ? sorted[Math.floor(sorted.length / 2)] : mid(sorted[sorted.length / 2 - 1], sorted[sorted.length / 2]),
|
||||
p95: p(0.95),
|
||||
};
|
||||
}
|
||||
|
||||
/** Log progress while awaiting a long LLM call (no silent hang). */
|
||||
function withHeartbeat(promise, ipcTimeoutMs, backendLabel) {
|
||||
const every = 15_000;
|
||||
@ -192,13 +348,66 @@ function buildKbotAiPayload(labels, tmo) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse kbot-ai job_result; updates assertion stats.
|
||||
* @returns {{ distances: {label:string,distance:number|null}[], missing: string[], parseError: string|null, rawText: string|null, batchOk: boolean }}
|
||||
*/
|
||||
function processBatchResponse(p, labels) {
|
||||
let rawText = null;
|
||||
let distances = [];
|
||||
let parseError = null;
|
||||
let missing = [];
|
||||
let batchOk = false;
|
||||
|
||||
if (p?.status === 'success' && typeof p?.text === 'string') {
|
||||
rawText = p.text;
|
||||
const arr = extractJsonArray(p.text);
|
||||
if (arr) {
|
||||
const norm = normalizeBatchArray(arr, labels);
|
||||
distances = norm.distances;
|
||||
missing = norm.missing;
|
||||
if (missing.length === 0) {
|
||||
assert(true, 'batch JSON array: all labels have distance');
|
||||
batchOk = true;
|
||||
} else {
|
||||
assert(false, `batch array complete (${missing.length} missing labels)`);
|
||||
parseError = `missing: ${missing.join('; ')}`;
|
||||
}
|
||||
} else {
|
||||
assert(false, 'batch response parses as JSON array');
|
||||
parseError = 'could not parse JSON array from model text';
|
||||
}
|
||||
} else {
|
||||
assert(false, 'kbot-ai success');
|
||||
parseError = p?.error ?? 'not success';
|
||||
}
|
||||
|
||||
return { distances, missing, parseError, rawText, batchOk };
|
||||
}
|
||||
|
||||
async function runSingleBatch(ipc, labels, tmo, ipcDeadlineMs, waitLabel) {
|
||||
const payload = buildKbotAiPayload(labels, tmo);
|
||||
const t0 = performance.now();
|
||||
const pending = ipc.request({ type: 'kbot-ai', payload }, ipcDeadlineMs);
|
||||
const msg = classifierFeatures.has('no-heartbeat')
|
||||
? await pending
|
||||
: await withHeartbeat(pending, ipcDeadlineMs, waitLabel);
|
||||
const elapsedMs = Math.round(performance.now() - t0);
|
||||
const p = payloadObj(msg);
|
||||
const parsed = processBatchResponse(p, labels);
|
||||
return { elapsedMs, p, ...parsed };
|
||||
}
|
||||
|
||||
async function run() {
|
||||
const quiet = classifierFeatures.has('quiet');
|
||||
classifierMetricsCollector = createMetricsCollector();
|
||||
classifierRunStartedAt = new Date().toISOString();
|
||||
const startedAt = classifierRunStartedAt;
|
||||
const useLlama = ipcClassifierLlamaEnabled();
|
||||
const backendLabel = useLlama ? `llama @ :${llama.port}` : `router=${router.fromEnv()}`;
|
||||
console.log(`\n📐 IPC classifier (${backendLabel}) — one batch, distance vs "machine workshop"\n`);
|
||||
if (!quiet) {
|
||||
console.log(`\n📐 IPC classifier (${backendLabel}) — one batch, distance vs "machine workshop"\n`);
|
||||
}
|
||||
|
||||
if (!existsSync(EXE)) {
|
||||
console.error(`❌ Binary not found at ${EXE}`);
|
||||
@ -248,47 +457,104 @@ async function run() {
|
||||
|
||||
const tmo = batchTimeoutMs();
|
||||
const ipcDeadlineMs = tmo + 60_000;
|
||||
console.log(` Single kbot-ai batch: ${labels.length} labels`);
|
||||
console.log(` liboai HTTP timeout: ${tmo} ms (llm_timeout_ms) — rebuild kbot if this was stuck at ~30s before`);
|
||||
console.log(` IPC wait deadline: ${ipcDeadlineMs} ms (HTTP + margin)`);
|
||||
console.log(` (Large batches can take many minutes; heartbeat every 15s…)\n`);
|
||||
|
||||
const payload = buildKbotAiPayload(labels, tmo);
|
||||
const waitLabel = useLlama ? 'llama' : router.fromEnv();
|
||||
const msg = await withHeartbeat(
|
||||
ipc.request({ type: 'kbot-ai', payload }, ipcDeadlineMs),
|
||||
ipcDeadlineMs,
|
||||
waitLabel
|
||||
);
|
||||
const p = payloadObj(msg);
|
||||
const nRuns = stressRunCount();
|
||||
|
||||
let rawText = null;
|
||||
let distances = [];
|
||||
let parseError = null;
|
||||
let missing = [];
|
||||
|
||||
if (p?.status === 'success' && typeof p?.text === 'string') {
|
||||
rawText = p.text;
|
||||
const arr = extractJsonArray(p.text);
|
||||
if (arr) {
|
||||
const norm = normalizeBatchArray(arr, labels);
|
||||
distances = norm.distances;
|
||||
missing = norm.missing;
|
||||
if (missing.length === 0) {
|
||||
assert(true, 'batch JSON array: all labels have distance');
|
||||
} else {
|
||||
assert(false, `batch array complete (${missing.length} missing labels)`);
|
||||
parseError = `missing: ${missing.join('; ')}`;
|
||||
}
|
||||
} else {
|
||||
assert(false, 'batch response parses as JSON array');
|
||||
parseError = 'could not parse JSON array from model text';
|
||||
}
|
||||
} else {
|
||||
assert(false, 'kbot-ai success');
|
||||
parseError = p?.error ?? 'not success';
|
||||
if (!quiet) {
|
||||
console.log(` kbot-ai batch: ${labels.length} labels × ${nRuns} run(s)`);
|
||||
console.log(` liboai HTTP timeout: ${tmo} ms (llm_timeout_ms) — rebuild kbot if this was stuck at ~30s before`);
|
||||
console.log(` IPC wait deadline: ${ipcDeadlineMs} ms (HTTP + margin)`);
|
||||
const hb = classifierFeatures.has('no-heartbeat') ? 'off' : '15s';
|
||||
console.log(` (Large batches can take many minutes; heartbeat ${hb}…)\n`);
|
||||
}
|
||||
|
||||
/** @type {Array<{ index: number, wallMs: number, batchOk: boolean, parseError: string|null, usage: ReturnType<typeof usageTokens>}>} */
|
||||
const stressIterations = [];
|
||||
|
||||
let lastP = /** @type {Record<string, unknown>|null} */ (null);
|
||||
let lastDistances = [];
|
||||
let lastRawText = null;
|
||||
let lastParseError = null;
|
||||
let lastByDistance = [];
|
||||
|
||||
for (let r = 0; r < nRuns; r++) {
|
||||
if (nRuns > 1 && !quiet) {
|
||||
console.log(` ── Stress run ${r + 1}/${nRuns} ──`);
|
||||
}
|
||||
const batch = await runSingleBatch(ipc, labels, tmo, ipcDeadlineMs, waitLabel);
|
||||
lastP = batch.p;
|
||||
lastDistances = batch.distances;
|
||||
lastRawText = batch.rawText;
|
||||
lastParseError = batch.parseError;
|
||||
lastByDistance = [...batch.distances].sort((a, b) => {
|
||||
if (a.distance == null && b.distance == null) return 0;
|
||||
if (a.distance == null) return 1;
|
||||
if (b.distance == null) return -1;
|
||||
return a.distance - b.distance;
|
||||
});
|
||||
|
||||
const u = usageTokens(batch.p?.llm);
|
||||
stressIterations.push({
|
||||
index: r + 1,
|
||||
wallMs: batch.elapsedMs,
|
||||
batchOk: batch.batchOk,
|
||||
parseError: batch.parseError,
|
||||
usage: u,
|
||||
});
|
||||
|
||||
if (nRuns > 1 && !quiet) {
|
||||
const tok = u
|
||||
? `tokens p/c/t ${u.prompt ?? '—'}/${u.completion ?? '—'}/${u.total ?? '—'}`
|
||||
: 'tokens —';
|
||||
console.log(` wall: ${batch.elapsedMs} ms ${batch.batchOk ? 'OK' : 'FAIL'} ${tok}`);
|
||||
}
|
||||
}
|
||||
|
||||
const wallMsList = stressIterations.map((x) => x.wallMs);
|
||||
/** @type {null | { requestedRuns: number, wallMs: NonNullable<ReturnType<typeof summarizeMs>>, successCount: number, failCount: number, totalPromptTokens: number, totalCompletionTokens: number, totalTokens: number }} */
|
||||
let stressSummary = null;
|
||||
if (nRuns > 1) {
|
||||
const w = summarizeMs(wallMsList);
|
||||
stressSummary = {
|
||||
requestedRuns: nRuns,
|
||||
wallMs: /** @type {NonNullable<typeof w>} */ (w),
|
||||
successCount: stressIterations.filter((x) => x.batchOk).length,
|
||||
failCount: stressIterations.filter((x) => !x.batchOk).length,
|
||||
totalPromptTokens: stressIterations.reduce((s, x) => s + (Number(x.usage?.prompt) || 0), 0),
|
||||
totalCompletionTokens: stressIterations.reduce((s, x) => s + (Number(x.usage?.completion) || 0), 0),
|
||||
totalTokens: stressIterations.reduce((s, x) => s + (Number(x.usage?.total) || 0), 0),
|
||||
};
|
||||
if (quiet) {
|
||||
console.log(
|
||||
`stress ${nRuns} runs: min=${stressSummary.wallMs.min}ms max=${stressSummary.wallMs.max}ms avg=${stressSummary.wallMs.avg}ms ok=${stressSummary.successCount}/${nRuns} tokensΣ=${stressSummary.totalTokens}`
|
||||
);
|
||||
} else {
|
||||
console.log(`\n ═══════════════ Stress summary (${nRuns} batch runs) ═══════════════`);
|
||||
console.log(
|
||||
` Wall time (ms): min ${stressSummary.wallMs.min} max ${stressSummary.wallMs.max} avg ${stressSummary.wallMs.avg} p50 ${stressSummary.wallMs.p50} p95 ${stressSummary.wallMs.p95}`
|
||||
);
|
||||
console.log(
|
||||
` Batches OK: ${stressSummary.successCount} fail: ${stressSummary.failCount} (assertions: passed ${stats.passed} failed ${stats.failed})`
|
||||
);
|
||||
if (
|
||||
stressSummary.totalPromptTokens > 0 ||
|
||||
stressSummary.totalCompletionTokens > 0 ||
|
||||
stressSummary.totalTokens > 0
|
||||
) {
|
||||
console.log(
|
||||
` Token totals (sum over runs): prompt ${stressSummary.totalPromptTokens} completion ${stressSummary.totalCompletionTokens} total ${stressSummary.totalTokens}`
|
||||
);
|
||||
}
|
||||
console.log(` ═══════════════════════════════════════════════════════════════════\n`);
|
||||
}
|
||||
}
|
||||
|
||||
const p = lastP;
|
||||
const distances = lastDistances;
|
||||
const rawText = lastRawText;
|
||||
const parseError = lastParseError;
|
||||
const byDistance = lastByDistance;
|
||||
|
||||
const shutdownRes = await ipc.request({ type: 'shutdown' }, timeouts.ipcDefault);
|
||||
assert(shutdownRes.type === 'shutdown_ack', 'shutdown ack');
|
||||
await new Promise((r) => setTimeout(r, timeouts.postShutdownMs));
|
||||
@ -297,14 +563,6 @@ async function run() {
|
||||
|
||||
const finishedAt = new Date().toISOString();
|
||||
|
||||
/** Final array: sorted by distance (nulls last). */
|
||||
const byDistance = [...distances].sort((a, b) => {
|
||||
if (a.distance == null && b.distance == null) return 0;
|
||||
if (a.distance == null) return 1;
|
||||
if (b.distance == null) return -1;
|
||||
return a.distance - b.distance;
|
||||
});
|
||||
|
||||
const reportNow = new Date();
|
||||
const cwd = process.cwd();
|
||||
|
||||
@ -315,6 +573,13 @@ async function run() {
|
||||
failed: stats.failed,
|
||||
ok: stats.failed === 0,
|
||||
ipcClassifierLlama: useLlama,
|
||||
cli: {
|
||||
features: [...classifierFeatures],
|
||||
provider: process.env.KBOT_ROUTER ?? null,
|
||||
model: process.env.KBOT_IPC_MODEL ?? null,
|
||||
backend: useLlama ? 'local' : 'remote',
|
||||
stressRuns: nRuns,
|
||||
},
|
||||
env: {
|
||||
KBOT_IPC_CLASSIFIER_LLAMA: process.env.KBOT_IPC_CLASSIFIER_LLAMA ?? null,
|
||||
KBOT_IPC_LLAMA_AUTOSTART: process.env.KBOT_IPC_LLAMA_AUTOSTART ?? null,
|
||||
@ -322,6 +587,7 @@ async function run() {
|
||||
KBOT_IPC_MODEL: process.env.KBOT_IPC_MODEL ?? null,
|
||||
KBOT_CLASSIFIER_LIMIT: process.env.KBOT_CLASSIFIER_LIMIT ?? null,
|
||||
KBOT_CLASSIFIER_TIMEOUT_MS: process.env.KBOT_CLASSIFIER_TIMEOUT_MS ?? null,
|
||||
KBOT_CLASSIFIER_STRESS_RUNS: process.env.KBOT_CLASSIFIER_STRESS_RUNS ?? null,
|
||||
KBOT_LLAMA_PORT: process.env.KBOT_LLAMA_PORT ?? null,
|
||||
KBOT_LLAMA_BASE_URL: process.env.KBOT_LLAMA_BASE_URL ?? null,
|
||||
},
|
||||
@ -350,58 +616,79 @@ async function run() {
|
||||
byDistance,
|
||||
rawText,
|
||||
parseError: parseError ?? null,
|
||||
...(nRuns > 1 && stressSummary
|
||||
? {
|
||||
stress: {
|
||||
requestedRuns: nRuns,
|
||||
iterations: stressIterations,
|
||||
summary: stressSummary,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
|
||||
let jsonPath = '';
|
||||
let mdPath = '';
|
||||
try {
|
||||
const written = await writeTestReports('test-ipc-classifier', reportData, { cwd, now: reportNow });
|
||||
jsonPath = written.jsonPath;
|
||||
mdPath = written.mdPath;
|
||||
} catch (e) {
|
||||
console.error(' ⚠️ Failed to write report:', e?.message ?? e);
|
||||
let arrayPath = '';
|
||||
if (!classifierFeatures.has('no-report')) {
|
||||
try {
|
||||
const written = await writeTestReports('test-ipc-classifier', reportData, { cwd, now: reportNow });
|
||||
jsonPath = written.jsonPath;
|
||||
mdPath = written.mdPath;
|
||||
} catch (e) {
|
||||
console.error(' ⚠️ Failed to write report:', e?.message ?? e);
|
||||
}
|
||||
|
||||
/** Array-only artifact (same timestamp as main report). */
|
||||
arrayPath = reportFilePathWithExt('test-ipc-classifier-distances', '.json', { cwd, now: reportNow });
|
||||
await mkdir(dirname(arrayPath), { recursive: true });
|
||||
await writeFile(arrayPath, `${JSON.stringify(distances, null, 2)}\n`, 'utf8');
|
||||
}
|
||||
|
||||
/** Array-only artifact (same timestamp as main report). */
|
||||
const arrayPath = reportFilePathWithExt('test-ipc-classifier-distances', '.json', { cwd, now: reportNow });
|
||||
await mkdir(dirname(arrayPath), { recursive: true });
|
||||
await writeFile(arrayPath, `${JSON.stringify(distances, null, 2)}\n`, 'utf8');
|
||||
|
||||
const { label: timeLabel } = timeParts(reportNow);
|
||||
console.log(`\n────────────────────────────────`);
|
||||
console.log(` Passed: ${stats.passed} Failed: ${stats.failed}`);
|
||||
if (jsonPath) console.log(` Report JSON: ${jsonPath}`);
|
||||
if (mdPath) console.log(` Report MD: ${mdPath}`);
|
||||
console.log(` Distances JSON: ${arrayPath}`);
|
||||
console.log(` Run id: test-ipc-classifier::${timeLabel}`);
|
||||
console.log(` distances.length: ${distances.length}`);
|
||||
console.log(`────────────────────────────────\n`);
|
||||
if (!classifierFeatures.has('quiet')) {
|
||||
console.log(`\n────────────────────────────────`);
|
||||
console.log(` Passed: ${stats.passed} Failed: ${stats.failed}`);
|
||||
if (jsonPath) console.log(` Report JSON: ${jsonPath}`);
|
||||
if (mdPath) console.log(` Report MD: ${mdPath}`);
|
||||
if (arrayPath) console.log(` Distances JSON: ${arrayPath}`);
|
||||
console.log(` Run id: test-ipc-classifier::${timeLabel}`);
|
||||
console.log(` distances.length: ${distances.length}`);
|
||||
console.log(`────────────────────────────────\n`);
|
||||
} else {
|
||||
console.log(
|
||||
`done: passed=${stats.passed} failed=${stats.failed} ok=${stats.failed === 0}${jsonPath ? ` json=${jsonPath}` : ''}`
|
||||
);
|
||||
}
|
||||
|
||||
process.exit(stats.failed > 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
parseClassifierArgv();
|
||||
run().catch(async (err) => {
|
||||
console.error('Classifier error:', err);
|
||||
try {
|
||||
const finishedAt = new Date().toISOString();
|
||||
const c = classifierMetricsCollector ?? createMetricsCollector();
|
||||
const started = classifierRunStartedAt ?? finishedAt;
|
||||
await writeTestReports(
|
||||
'test-ipc-classifier',
|
||||
{
|
||||
startedAt: started,
|
||||
finishedAt,
|
||||
error: String(err?.stack ?? err),
|
||||
passed: stats.passed,
|
||||
failed: stats.failed,
|
||||
ok: false,
|
||||
ipcClassifierLlama: ipcClassifierLlamaEnabled(),
|
||||
metrics: buildMetricsBundle(c, started, finishedAt),
|
||||
},
|
||||
{ cwd: process.cwd() }
|
||||
);
|
||||
} catch (_) {
|
||||
/* ignore */
|
||||
if (!classifierFeatures.has('no-report')) {
|
||||
try {
|
||||
const finishedAt = new Date().toISOString();
|
||||
const c = classifierMetricsCollector ?? createMetricsCollector();
|
||||
const started = classifierRunStartedAt ?? finishedAt;
|
||||
await writeTestReports(
|
||||
'test-ipc-classifier',
|
||||
{
|
||||
startedAt: started,
|
||||
finishedAt,
|
||||
error: String(err?.stack ?? err),
|
||||
passed: stats.passed,
|
||||
failed: stats.failed,
|
||||
ok: false,
|
||||
ipcClassifierLlama: ipcClassifierLlamaEnabled(),
|
||||
metrics: buildMetricsBundle(c, started, finishedAt),
|
||||
},
|
||||
{ cwd: process.cwd() }
|
||||
);
|
||||
} catch (_) {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
191
packages/kbot/cpp/package-lock.json
generated
191
packages/kbot/cpp/package-lock.json
generated
@ -1,6 +1,193 @@
|
||||
{
|
||||
"name": "mono-cpp",
|
||||
"name": "kbot-cpp",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {}
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "kbot-cpp",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"yargs": "^17.7.2"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
||||
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.1",
|
||||
"wrap-ansi": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/get-caller-file": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": "6.* || 8.* || >= 10.*"
|
||||
}
|
||||
},
|
||||
"node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/y18n": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs": {
|
||||
"version": "17.7.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
||||
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cliui": "^8.0.1",
|
||||
"escalade": "^3.1.1",
|
||||
"get-caller-file": "^2.0.5",
|
||||
"require-directory": "^2.1.1",
|
||||
"string-width": "^4.2.3",
|
||||
"y18n": "^5.0.5",
|
||||
"yargs-parser": "^21.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs-parser": {
|
||||
"version": "21.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
||||
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,9 @@
|
||||
"directories": {
|
||||
"test": "tests"
|
||||
},
|
||||
"dependencies": {
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"scripts": {
|
||||
"config": "cmake --preset dev",
|
||||
"config:release": "cmake --preset release",
|
||||
@ -24,6 +27,7 @@
|
||||
"test:ipc": "node orchestrator/test-ipc.mjs",
|
||||
"test:ipc:classifier": "node orchestrator/test-ipc-classifier.mjs",
|
||||
"test:ipc:classifier:openrouter": "node orchestrator/classifier-openrouter.mjs",
|
||||
"test:ipc:classifier:openrouter:stress": "node orchestrator/classifier-openrouter-stress.mjs",
|
||||
"test:html": "cmake --preset release && cmake --build --preset release --target test_html && .\\dist\\test_html.exe"
|
||||
},
|
||||
"repository": {
|
||||
|
||||
@ -5,21 +5,6 @@
|
||||
},
|
||||
{
|
||||
"path": "../commons"
|
||||
},
|
||||
{
|
||||
"path": "../cad"
|
||||
},
|
||||
{
|
||||
"path": "../tasks"
|
||||
},
|
||||
{
|
||||
"path": "../i18n"
|
||||
},
|
||||
{
|
||||
"path": "../acl"
|
||||
},
|
||||
{
|
||||
"path": "../llm"
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"timestamp": 1774802354444,
|
||||
"timestamp": 1774879965316,
|
||||
"models": [
|
||||
{
|
||||
"id": "gpt-4-0613",
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"timestamp": 1774802355711,
|
||||
"timestamp": 1774879966823,
|
||||
"models": [
|
||||
{
|
||||
"id": "kwaipilot/kat-coder-pro-v2",
|
||||
@ -256,21 +256,13 @@
|
||||
},
|
||||
"per_request_limits": null,
|
||||
"supported_parameters": [
|
||||
"frequency_penalty",
|
||||
"include_reasoning",
|
||||
"logit_bias",
|
||||
"max_tokens",
|
||||
"min_p",
|
||||
"presence_penalty",
|
||||
"reasoning",
|
||||
"repetition_penalty",
|
||||
"response_format",
|
||||
"seed",
|
||||
"stop",
|
||||
"temperature",
|
||||
"tool_choice",
|
||||
"tools",
|
||||
"top_k",
|
||||
"top_p"
|
||||
],
|
||||
"default_parameters": {
|
||||
@ -2419,11 +2411,7 @@
|
||||
},
|
||||
"pricing": {
|
||||
"prompt": "0",
|
||||
"completion": "0",
|
||||
"request": "0",
|
||||
"image": "0",
|
||||
"web_search": "0",
|
||||
"internal_reasoning": "0"
|
||||
"completion": "0"
|
||||
},
|
||||
"top_provider": {
|
||||
"context_length": 131000,
|
||||
@ -5091,9 +5079,7 @@
|
||||
"per_request_limits": null,
|
||||
"supported_parameters": [
|
||||
"frequency_penalty",
|
||||
"logit_bias",
|
||||
"max_tokens",
|
||||
"min_p",
|
||||
"presence_penalty",
|
||||
"repetition_penalty",
|
||||
"response_format",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -1,3 +1,3 @@
|
||||
export enum E_OPENROUTER_MODEL_FREE {
|
||||
MODEL_FREE_ARCEE_AI_TRINITY_LARGE_PREVIEW_FREE = "arcee-ai/trinity-large-preview:free"
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user