@theseus-whale/dsh-continuity 0.0.0-stage → 4.12.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/INSTALL.md +25 -0
- package/PANEL.md +92 -0
- package/README.md +286 -2
- package/RELEASE-NOTES.md +732 -0
- package/bin/bc-acceptance-full.mjs +91 -0
- package/bin/bc-acceptance-tight.mjs +66 -0
- package/bin/bc-acceptance.mjs +22 -0
- package/bin/bc-add-keywords.mjs +21 -0
- package/bin/bc-demo.mjs +47 -0
- package/bin/bc-expand-hint.mjs +179 -0
- package/bin/bc-ingest-index.mjs +35 -0
- package/bin/bc-ingest.mjs +31 -0
- package/bin/bc-label-demo.mjs +18 -0
- package/bin/bc-relabel.mjs +150 -0
- package/bin/bc-sessionize.mjs +59 -0
- package/bin/coref_pipeline.py +47 -0
- package/bin/coref_service.py +57 -0
- package/bin/debug_ckip.py +16 -0
- package/bin/debug_ner_zh.py +18 -0
- package/bin/doctor.mjs +275 -0
- package/bin/en_ner_error_audit.py +33 -0
- package/bin/eval-label-rules.mjs +342 -0
- package/bin/export-turns.py +68 -0
- package/bin/extract-user-paragraph.mjs +14 -0
- package/bin/extraction-composite-smoke.mjs +28 -0
- package/bin/extraction-latency.mjs +58 -0
- package/bin/extraction-user-smoke.mjs +28 -0
- package/bin/extraction_service.py +164 -0
- package/bin/facet-active-experiment.mjs +110 -0
- package/bin/facet-covid.mjs +8 -0
- package/bin/facet-curve.mjs +27 -0
- package/bin/facet-embedding-train.mjs +20 -0
- package/bin/facet-embedding-validate.mjs +83 -0
- package/bin/facet-eval-roleplay.mjs +42 -0
- package/bin/facet-eval.mjs +38 -0
- package/bin/facet-fold.mjs +31 -0
- package/bin/facet-load.mjs +16 -0
- package/bin/facet-mlp-scan.mjs +45 -0
- package/bin/facet-mvp.mjs +34 -0
- package/bin/facet-online-experiments.mjs +78 -0
- package/bin/facet-online-race.mjs +99 -0
- package/bin/facet-online-validate.mjs +51 -0
- package/bin/facet-panel.mjs +22 -0
- package/bin/facet-passage.mjs +41 -0
- package/bin/facet-random-validate.mjs +48 -0
- package/bin/facet-roleplay.mjs +33 -0
- package/bin/facet-scan.mjs +56 -0
- package/bin/facet-segment.mjs +34 -0
- package/bin/facet-train.mjs +19 -0
- package/bin/facet-validate.mjs +103 -0
- package/bin/facet-zeroshot.mjs +14 -0
- package/bin/install-templates.mjs +72 -0
- package/bin/memory-bench.mjs +53 -0
- package/bin/memory-build-summary.mjs +43 -0
- package/bin/memory-build-vector.mjs +31 -0
- package/bin/memory-build.mjs +41 -0
- package/bin/memory-calibration.mjs +70 -0
- package/bin/memory-closed-loop.mjs +119 -0
- package/bin/memory-dry-run.mjs +70 -0
- package/bin/memory-eval.mjs +104 -0
- package/bin/memory-event-alias-test.mjs +89 -0
- package/bin/memory-extraction-bench.mjs +58 -0
- package/bin/memory-load-gate-replay.mjs +137 -0
- package/bin/memory-loop-test.mjs +90 -0
- package/bin/memory-pre-turn-harness.mjs +63 -0
- package/bin/memory-query.mjs +39 -0
- package/bin/memory-replay-hints.mjs +84 -0
- package/bin/memory-scale.mjs +58 -0
- package/bin/memory-scenario-test.mjs +146 -0
- package/bin/memory-user-loop.mjs +95 -0
- package/bin/ner_bench_tiny_zh.py +59 -0
- package/bin/ner_bench_zh.py +60 -0
- package/bin/ner_conll_en.py +139 -0
- package/bin/ner_direct_en.py +123 -0
- package/bin/ner_direct_zh.py +100 -0
- package/bin/ner_pipeline.py +208 -0
- package/bin/noise-selfcheck.mjs +50 -0
- package/bin/pack-integrity.mjs +47 -0
- package/bin/panel-check.mjs +57 -0
- package/bin/plugin-integrity.mjs +74 -0
- package/bin/preview-live.mjs +13 -0
- package/bin/public-install-check.mjs +222 -0
- package/bin/relabel-apply.mjs +272 -0
- package/bin/relabel-dry-run.mjs +381 -0
- package/bin/relik_pipeline.py +39 -0
- package/bin/relik_service.py +52 -0
- package/bin/srl_pipeline.py +55 -0
- package/bin/srl_service.py +68 -0
- package/bin/summary-index.mjs +151 -0
- package/bin/summary-query.mjs +79 -0
- package/bin/vec-check.mjs +27 -0
- package/bin/zh_ner_error_audit.py +36 -0
- package/bin/zh_ner_slot_eval.py +38 -0
- package/client.js +760 -0
- package/cordis.patch.yml +378 -0
- package/docs/API-NOTES.md +1743 -0
- package/docs/ARCHITECTURE.md +355 -0
- package/docs/CACHE.md +253 -0
- package/docs/CONDUCT.md +30 -0
- package/docs/CREATOR-HANDOFF.md +102 -0
- package/docs/EXPERIMENT.md +136 -0
- package/docs/FACET-MVP-REPORT.md +473 -0
- package/docs/GUIDE-0.4.66.md +195 -0
- package/docs/GUIDE.md +127 -0
- package/docs/MECHANISM.md +243 -0
- package/docs/NEW-INSTANCE-HANDOFF.md +219 -0
- package/docs/README.md +37 -0
- package/docs/SESSION-LAYOUT.md +151 -0
- package/docs/history-map.md +249 -0
- package/docs/relabel-migration-wiring.md +366 -0
- package/guides/00-READ-FIRST.md +206 -0
- package/guides/01-INSTALL.md +207 -0
- package/guides/02-TABLES.md +225 -0
- package/guides/03-AUDIT.md +201 -0
- package/guides/04-REBUILD.md +306 -0
- package/guides/05-FIRST-RUN.md +191 -0
- package/guides/06-EMBEDDING.md +146 -0
- package/guides/07-PTC-AND-ASSETS.md +221 -0
- package/icon.svg +7 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/models/README.md +13 -0
- package/models/Xenova/bge-small-zh-v1.5/config.json +31 -0
- package/models/Xenova/bge-small-zh-v1.5/onnx/model_quantized.onnx +0 -0
- package/models/Xenova/bge-small-zh-v1.5/tokenizer.json +21278 -0
- package/models/Xenova/bge-small-zh-v1.5/tokenizer_config.json +15 -0
- package/package.json +139 -4
- package/src/asset-tool.mjs +237 -0
- package/src/assets.mjs +244 -0
- package/src/backfill.mjs +74 -0
- package/src/backup.mjs +80 -0
- package/src/deadend.mjs +95 -0
- package/src/deleted-store.mjs +35 -0
- package/src/engine.mjs +3831 -0
- package/src/facets/classifier.mjs +146 -0
- package/src/facets/embedding-service.mjs +53 -0
- package/src/facets/embedding-worker.mjs +27 -0
- package/src/facets/embedding.mjs +163 -0
- package/src/facets/extract.mjs +80 -0
- package/src/facets/fold-plan.mjs +84 -0
- package/src/facets/hybrid.mjs +29 -0
- package/src/facets/index.mjs +113 -0
- package/src/facets/linear-probe.mjs +43 -0
- package/src/facets/load.mjs +37 -0
- package/src/facets/mlp-probe.mjs +54 -0
- package/src/facets/mlp-service.mjs +53 -0
- package/src/facets/mlp-worker.mjs +26 -0
- package/src/facets/online-knn.mjs +58 -0
- package/src/facets/online-models.mjs +227 -0
- package/src/facets/panel-state.mjs +23 -0
- package/src/facets/panel.mjs +73 -0
- package/src/facets/random.mjs +26 -0
- package/src/facets/scanner.mjs +35 -0
- package/src/facets/schema.mjs +75 -0
- package/src/facets/seed-examples.mjs +58 -0
- package/src/facets/sensitivity.mjs +98 -0
- package/src/facets/service.mjs +53 -0
- package/src/facets/store.mjs +39 -0
- package/src/facets/worker.mjs +23 -0
- package/src/folds-store.mjs +109 -0
- package/src/groups.mjs +167 -0
- package/src/handoff-tool.mjs +51 -0
- package/src/handoff.mjs +127 -0
- package/src/index.mjs +649 -0
- package/src/inject-policy.mjs +17 -0
- package/src/learning.mjs +88 -0
- package/src/memory/alias-proposal.mjs +40 -0
- package/src/memory/bc-audit.mjs +245 -0
- package/src/memory/bc-index-updater.mjs +404 -0
- package/src/memory/bc-index.mjs +48 -0
- package/src/memory/bc-label.mjs +220 -0
- package/src/memory/bc-panel-state.mjs +107 -0
- package/src/memory/bc-pre-step.mjs +463 -0
- package/src/memory/bc-recall.mjs +470 -0
- package/src/memory/bc-shift.mjs +123 -0
- package/src/memory/bc-store.mjs +41 -0
- package/src/memory/bm25.mjs +112 -0
- package/src/memory/calibration.mjs +54 -0
- package/src/memory/chatter.mjs +29 -0
- package/src/memory/cmap-file.mjs +107 -0
- package/src/memory/concepts-file.mjs +41 -0
- package/src/memory/coref-slots.mjs +40 -0
- package/src/memory/event-alias-ledger.mjs +87 -0
- package/src/memory/event-log.mjs +95 -0
- package/src/memory/event-model.mjs +132 -0
- package/src/memory/event-segmenter.mjs +36 -0
- package/src/memory/event-summary-tree.mjs +122 -0
- package/src/memory/extraction-adapter.mjs +76 -0
- package/src/memory/extraction-composite.mjs +75 -0
- package/src/memory/extraction-core.mjs +76 -0
- package/src/memory/extraction-pre-step.mjs +161 -0
- package/src/memory/extraction-provider.mjs +300 -0
- package/src/memory/extraction-score.mjs +36 -0
- package/src/memory/facet-stabilizer.mjs +59 -0
- package/src/memory/history-map.mjs +139 -0
- package/src/memory/language-router.mjs +30 -0
- package/src/memory/load-gate.mjs +161 -0
- package/src/memory/mechanical-note.mjs +104 -0
- package/src/memory/onboarding.mjs +163 -0
- package/src/memory/persona.mjs +78 -0
- package/src/memory/planner.mjs +249 -0
- package/src/memory/pre-turn.mjs +80 -0
- package/src/memory/query-aliases.mjs +39 -0
- package/src/memory/recall-cue.mjs +43 -0
- package/src/memory/recall.mjs +65 -0
- package/src/memory/referent-candidates.mjs +55 -0
- package/src/memory/referent-ledger.mjs +86 -0
- package/src/memory/relabel-migrate.mjs +376 -0
- package/src/memory/relik-adapter.mjs +25 -0
- package/src/memory/special-keywords.mjs +60 -0
- package/src/memory/structured-adapters.mjs +75 -0
- package/src/memory/summary-index.mjs +335 -0
- package/src/memory/summary-tree.mjs +110 -0
- package/src/memory/term-extractor.mjs +44 -0
- package/src/memory/tokenize.mjs +9 -0
- package/src/memory/vector-index.mjs +54 -0
- package/src/memory/window-summary.mjs +133 -0
- package/src/mood.mjs +133 -0
- package/src/peer-tool.mjs +63 -0
- package/src/peer.mjs +144 -0
- package/src/policy.mjs +170 -0
- package/src/preflight.mjs +113 -0
- package/src/provenance.mjs +94 -0
- package/src/range.mjs +75 -0
- package/src/recall-exact-tool.mjs +267 -0
- package/src/recall-tool.mjs +521 -0
- package/src/recheck.mjs +99 -0
- package/src/save-start.mjs +29 -0
- package/src/session-guard.mjs +140 -0
- package/src/session-log-tool.mjs +322 -0
- package/src/session-log.mjs +606 -0
- package/src/skills.mjs +111 -0
- package/src/split-range.mjs +115 -0
- package/src/state.mjs +198 -0
- package/src/t0.mjs +149 -0
- package/src/tag-confirm.mjs +78 -0
- package/src/tag-detect.mjs +42 -0
- package/src/tag-maintenance.mjs +285 -0
- package/src/tag-reject.mjs +73 -0
- package/src/tags-store.mjs +94 -0
- package/src/tags.mjs +692 -0
- package/src/threads-backend.mjs +26 -0
- package/src/threads-core.mjs +1887 -0
- package/src/threads-store.mjs +207 -0
- package/src/threads-tool.mjs +522 -0
- package/src/threads.mjs +929 -0
- package/src/tool-preview.mjs +427 -0
- package/src/topic.mjs +63 -0
- package/src/training.mjs +75 -0
- package/src/turn-tag.mjs +62 -0
- package/src/user-turn.mjs +39 -0
- package/templates/cmap.md +11 -0
- package/templates/concepts.md +12 -0
- package/templates/habits.md +40 -0
- package/templates/keywords.md +6 -0
- package/templates/rebuild.md +134 -0
- package/templates/rules-safe.md +184 -0
- package/templates/rules.md +177 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import { embedTexts } from '../src/facets/embedding.mjs';
|
|
4
|
+
import { OnlineFacetKnn } from '../src/facets/online-knn.mjs';
|
|
5
|
+
import { seedRows } from '../src/facets/seed-examples.mjs';
|
|
6
|
+
import { detectRandom } from '../src/facets/random.mjs';
|
|
7
|
+
import { SLOTS, normalizeFacets } from '../src/facets/schema.mjs';
|
|
8
|
+
|
|
9
|
+
const args = process.argv.slice(2);
|
|
10
|
+
const argOf = (name) => {
|
|
11
|
+
const i = args.indexOf(name);
|
|
12
|
+
return i >= 0 ? args[i + 1] : undefined;
|
|
13
|
+
};
|
|
14
|
+
const input = argOf('--input');
|
|
15
|
+
if (!input) {
|
|
16
|
+
console.error('usage: node bin/facet-online-experiments.mjs --input early50-labeled.jsonl');
|
|
17
|
+
process.exit(2);
|
|
18
|
+
}
|
|
19
|
+
const rows = fs.readFileSync(input, 'utf8').split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
|
|
20
|
+
const vectors = await embedTexts(rows.map((row) => row.text));
|
|
21
|
+
const seedData = seedRows();
|
|
22
|
+
const seedVectors = await embedTexts(seedData.map((row) => row.text));
|
|
23
|
+
|
|
24
|
+
function makeLearner(useSeeds) {
|
|
25
|
+
const learner = new OnlineFacetKnn({ k: 5, minSimilarity: 0.15, minVote: 0.5 });
|
|
26
|
+
if (useSeeds) seedData.forEach((row, index) => learner.add(seedVectors[index], row.facets));
|
|
27
|
+
return learner;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function evaluate({ useSeeds = false, warm = 0, temporal = false } = {}) {
|
|
31
|
+
const learner = makeLearner(useSeeds);
|
|
32
|
+
const report = Object.fromEntries(SLOTS.map((slot) => [slot, { total: 0, covered: 0, correct: 0 }]));
|
|
33
|
+
const timeline = [];
|
|
34
|
+
let previous = {};
|
|
35
|
+
for (let index = 0; index < rows.length; index += 1) {
|
|
36
|
+
const row = rows[index];
|
|
37
|
+
const truth = normalizeFacets(row.facets ?? {});
|
|
38
|
+
if (index >= warm) {
|
|
39
|
+
const predicted = learner.predict(vectors[index]);
|
|
40
|
+
if (temporal) {
|
|
41
|
+
for (const slot of SLOTS) {
|
|
42
|
+
if (predicted.facets[slot] === undefined && previous[slot] !== undefined) {
|
|
43
|
+
predicted.facets[slot] = previous[slot];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
predicted.facets.random = detectRandom(row.text).value;
|
|
48
|
+
previous = { ...predicted.facets };
|
|
49
|
+
timeline.push({ turn: row.turn, facets: predicted.facets, unknown: predicted.unknown });
|
|
50
|
+
for (const slot of SLOTS) {
|
|
51
|
+
if (truth[slot] === undefined) continue;
|
|
52
|
+
report[slot].total += 1;
|
|
53
|
+
if (predicted.facets[slot] === undefined) continue;
|
|
54
|
+
report[slot].covered += 1;
|
|
55
|
+
if (predicted.facets[slot] === truth[slot]) report[slot].correct += 1;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
learner.add(vectors[index], truth);
|
|
59
|
+
}
|
|
60
|
+
for (const slot of SLOTS) {
|
|
61
|
+
const row = report[slot];
|
|
62
|
+
row.coverage = row.total === 0 ? 0 : row.covered / row.total;
|
|
63
|
+
row.accuracy = row.covered === 0 ? 0 : row.correct / row.covered;
|
|
64
|
+
}
|
|
65
|
+
return { report, timeline };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const result = {
|
|
69
|
+
baseline: evaluate(),
|
|
70
|
+
schemaSeeds: evaluate({ useSeeds: true }),
|
|
71
|
+
warm10: evaluate({ warm: 10 }),
|
|
72
|
+
warm10Seeds: evaluate({ useSeeds: true, warm: 10 }),
|
|
73
|
+
warm10Temporal: evaluate({ warm: 10, temporal: true }),
|
|
74
|
+
};
|
|
75
|
+
fs.writeFileSync(`${input}.online-experiments.json`, `${JSON.stringify(result, null, 2)}\n`, 'utf8');
|
|
76
|
+
const out = Object.fromEntries(Object.entries(result).map(([name, value]) => [name, Object.fromEntries(Object.entries(value.report).map(([slot, row]) => [slot, `${(row.accuracy * 100).toFixed(1)}% @ ${(row.coverage * 100).toFixed(1)}%`]))]));
|
|
77
|
+
console.log(JSON.stringify(out, null, 2));
|
|
78
|
+
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Race true incremental learners from turn 1.
|
|
4
|
+
*/
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { performance } from 'node:perf_hooks';
|
|
8
|
+
import { OnlineFacetKnn } from '../src/facets/online-knn.mjs';
|
|
9
|
+
import { OnlineCentroid, OnlineCentroidVariance, OnlineNaiveBayes } from '../src/facets/online-models.mjs';
|
|
10
|
+
import { detectRandom } from '../src/facets/random.mjs';
|
|
11
|
+
import { SLOTS } from '../src/facets/schema.mjs';
|
|
12
|
+
|
|
13
|
+
const args = process.argv.slice(2);
|
|
14
|
+
const argOf = (name) => {
|
|
15
|
+
const i = args.indexOf(name);
|
|
16
|
+
return i >= 0 ? args[i + 1] : undefined;
|
|
17
|
+
};
|
|
18
|
+
const dir = argOf('--dir') ?? 'C:/Users/35794/Documents/DeepSeek\'s Sandbox/coopanion-continuity/backups/continuity-tags-20261031/online400';
|
|
19
|
+
const rows = fs.readFileSync(path.join(dir, 'continuity-1500.jsonl'), 'utf8').split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
|
|
20
|
+
const embeddings = JSON.parse(fs.readFileSync(path.join(dir, 'embeddings-1500.json'), 'utf8'));
|
|
21
|
+
const vectorByTurn = new Map(embeddings.map((row) => [row.turn, row.vector]));
|
|
22
|
+
const checkpoints = [50, 100, 200, 400, 800, 1200, 1480];
|
|
23
|
+
|
|
24
|
+
function evaluate(name, learner, predictFn, updateFn) {
|
|
25
|
+
const report = Object.fromEntries(SLOTS.map((slot) => [slot, { total: 0, covered: 0, correct: 0 }]));
|
|
26
|
+
const checkpointsOut = [];
|
|
27
|
+
let unknown = 0;
|
|
28
|
+
let checkpointIndex = 0;
|
|
29
|
+
const t0 = performance.now();
|
|
30
|
+
for (let rowIndex = 0; rowIndex < rows.length; rowIndex += 1) {
|
|
31
|
+
const row = rows[rowIndex];
|
|
32
|
+
const vector = vectorByTurn.get(row.turn);
|
|
33
|
+
const predicted = predictFn(learner, row.text, vector);
|
|
34
|
+
predicted.facets.random = detectRandom(row.text).value;
|
|
35
|
+
if (predicted.unknown) unknown += 1;
|
|
36
|
+
for (const slot of SLOTS) {
|
|
37
|
+
if (row.facets[slot] === undefined) continue;
|
|
38
|
+
report[slot].total += 1;
|
|
39
|
+
if (predicted.facets[slot] === undefined) continue;
|
|
40
|
+
report[slot].covered += 1;
|
|
41
|
+
if (predicted.facets[slot] === row.facets[slot]) report[slot].correct += 1;
|
|
42
|
+
}
|
|
43
|
+
updateFn(learner, vector, row.text, row.facets);
|
|
44
|
+
while (checkpointIndex < checkpoints.length && row.turn >= checkpoints[checkpointIndex]) {
|
|
45
|
+
const checkpoint = checkpoints[checkpointIndex];
|
|
46
|
+
checkpointIndex += 1;
|
|
47
|
+
const rep = {};
|
|
48
|
+
for (const slot of SLOTS) {
|
|
49
|
+
const x = report[slot];
|
|
50
|
+
rep[slot] = {
|
|
51
|
+
total: x.total,
|
|
52
|
+
covered: x.covered,
|
|
53
|
+
correct: x.correct,
|
|
54
|
+
coverage: x.total === 0 ? 0 : x.covered / x.total,
|
|
55
|
+
accuracy: x.covered === 0 ? 0 : x.correct / x.covered,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
checkpointsOut.push({ turn: row.turn, checkpoint, indexed: rowIndex + 1, unknown, report: rep });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const mem = process.memoryUsage();
|
|
62
|
+
const elapsed = performance.now() - t0;
|
|
63
|
+
return {
|
|
64
|
+
name,
|
|
65
|
+
rows: rows.length,
|
|
66
|
+
timeMs: Math.round(elapsed),
|
|
67
|
+
msPerTurn: +(elapsed / rows.length).toFixed(2),
|
|
68
|
+
memory: { heapMB: +(mem.heapUsed / 1048576).toFixed(1), rssMB: +(mem.rss / 1048576).toFixed(1) },
|
|
69
|
+
checkpoints: checkpointsOut,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const results = [];
|
|
74
|
+
results.push(evaluate('centroid', new OnlineCentroid(),
|
|
75
|
+
(m, text, vector) => m.predict(vector),
|
|
76
|
+
(m, vector, text, facets) => m.update(vector, facets)));
|
|
77
|
+
results.push(evaluate('centroidVariance', new OnlineCentroidVariance(),
|
|
78
|
+
(m, text, vector) => m.predict(vector),
|
|
79
|
+
(m, vector, text, facets) => m.update(vector, facets)));
|
|
80
|
+
results.push(evaluate('naiveBayes', new OnlineNaiveBayes(),
|
|
81
|
+
(m, text) => m.predict(text),
|
|
82
|
+
(m, vector, text, facets) => m.update(text, facets)));
|
|
83
|
+
results.push(evaluate('knn', new OnlineFacetKnn({ k: 5, minSimilarity: 0.15, minVote: 0.5 }),
|
|
84
|
+
(m, text, vector) => m.predict(vector),
|
|
85
|
+
(m, vector, text, facets) => m.add(vector, facets)));
|
|
86
|
+
|
|
87
|
+
fs.writeFileSync(path.join(dir, 'ONLINE-RACE-1462.json'), JSON.stringify(results, null, 2));
|
|
88
|
+
const summary = results.map((r) => ({
|
|
89
|
+
name: r.name,
|
|
90
|
+
msPerTurn: r.msPerTurn,
|
|
91
|
+
memory: r.memory,
|
|
92
|
+
checkpoints: r.checkpoints.map((c) => ({
|
|
93
|
+
checkpoint: c.checkpoint,
|
|
94
|
+
activity: c.report.activity.accuracy,
|
|
95
|
+
topic: c.report.topic.accuracy,
|
|
96
|
+
coverage: [c.report.activity.coverage, c.report.topic.coverage],
|
|
97
|
+
})),
|
|
98
|
+
}));
|
|
99
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Prequential validation: predict turn t from turns < t, then learn t.
|
|
4
|
+
*/
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import { embedTexts } from '../src/facets/embedding.mjs';
|
|
7
|
+
import { OnlineFacetKnn } from '../src/facets/online-knn.mjs';
|
|
8
|
+
import { detectRandom } from '../src/facets/random.mjs';
|
|
9
|
+
import { SLOTS, normalizeFacets } from '../src/facets/schema.mjs';
|
|
10
|
+
|
|
11
|
+
const args = process.argv.slice(2);
|
|
12
|
+
const argOf = (name) => {
|
|
13
|
+
const i = args.indexOf(name);
|
|
14
|
+
return i >= 0 ? args[i + 1] : undefined;
|
|
15
|
+
};
|
|
16
|
+
const input = argOf('--input');
|
|
17
|
+
const model = argOf('--model') ?? 'Xenova/bge-small-zh-v1.5';
|
|
18
|
+
if (!input) {
|
|
19
|
+
console.error('usage: node bin/facet-online-validate.mjs --input early50-labeled.jsonl');
|
|
20
|
+
process.exit(2);
|
|
21
|
+
}
|
|
22
|
+
const rows = fs.readFileSync(input, 'utf8').split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
|
|
23
|
+
const vectors = await embedTexts(rows.map((row) => row.text), model);
|
|
24
|
+
const learner = new OnlineFacetKnn({ k: 5, minSimilarity: 0.15, minVote: 0.5 });
|
|
25
|
+
const report = {};
|
|
26
|
+
for (const slot of SLOTS) report[slot] = { total: 0, covered: 0, correct: 0 };
|
|
27
|
+
let unknownTurns = 0;
|
|
28
|
+
const timeline = [];
|
|
29
|
+
for (let index = 0; index < rows.length; index += 1) {
|
|
30
|
+
const row = rows[index];
|
|
31
|
+
const truth = normalizeFacets(row.facets ?? {});
|
|
32
|
+
const predicted = learner.predict(vectors[index]);
|
|
33
|
+
if (predicted.unknown) unknownTurns += 1;
|
|
34
|
+
predicted.facets.random = detectRandom(row.text).value;
|
|
35
|
+
const entry = { turn: row.turn, unknown: predicted.unknown, facets: predicted.facets };
|
|
36
|
+
timeline.push(entry);
|
|
37
|
+
for (const slot of SLOTS) {
|
|
38
|
+
if (truth[slot] === undefined) continue;
|
|
39
|
+
report[slot].total += 1;
|
|
40
|
+
if (predicted.facets[slot] === undefined) continue;
|
|
41
|
+
report[slot].covered += 1;
|
|
42
|
+
if (predicted.facets[slot] === truth[slot]) report[slot].correct += 1;
|
|
43
|
+
}
|
|
44
|
+
learner.add(vectors[index], truth);
|
|
45
|
+
}
|
|
46
|
+
for (const slot of SLOTS) {
|
|
47
|
+
const row = report[slot];
|
|
48
|
+
row.coverage = row.total === 0 ? 0 : row.covered / row.total;
|
|
49
|
+
row.accuracy = row.covered === 0 ? 0 : row.correct / row.covered;
|
|
50
|
+
}
|
|
51
|
+
console.log(JSON.stringify({ rows: rows.length, unknownTurns, report, timeline }, null, 2));
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import { buildFacetIndex } from '../src/facets/index.mjs';
|
|
4
|
+
import { buildFacetPanel } from '../src/facets/panel.mjs';
|
|
5
|
+
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
const argOf = (name) => {
|
|
8
|
+
const i = args.indexOf(name);
|
|
9
|
+
return i >= 0 ? args[i + 1] : undefined;
|
|
10
|
+
};
|
|
11
|
+
const input = argOf('--input');
|
|
12
|
+
const out = argOf('--out');
|
|
13
|
+
const alias = argOf('--alias') ?? '跑团';
|
|
14
|
+
if (!input || !out) {
|
|
15
|
+
console.error('usage: node bin/facet-panel.mjs --input rows.jsonl --out facet-panel.json --alias 跑团');
|
|
16
|
+
process.exit(2);
|
|
17
|
+
}
|
|
18
|
+
const rows = fs.readFileSync(input, 'utf8').split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
|
|
19
|
+
const index = buildFacetIndex(rows);
|
|
20
|
+
const panel = buildFacetPanel(index.entries, { idf: index.idf, currentAlias: alias });
|
|
21
|
+
fs.writeFileSync(out, `${JSON.stringify(panel, null, 2)}\n`, 'utf8');
|
|
22
|
+
console.log(JSON.stringify({ out, total: panel.totalTurns, facetSum: panel.facetSum, currentTopic: panel.currentTopic?.alias }, null, 2));
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { deserializePrototypes, predictEmbedding } from '../src/facets/embedding.mjs';
|
|
3
|
+
import { deserializeClassifier, predictFacets } from '../src/facets/classifier.mjs';
|
|
4
|
+
|
|
5
|
+
const full = `宁把手里那只凉箍交给柯林,箍口朝上塞进他布袋:
|
|
6
|
+
|
|
7
|
+
「你拿着,别落地,也别让它见水。」
|
|
8
|
+
|
|
9
|
+
然后他自己转到闸门侧面去找那个起闸的轮子——不是去接,是去卡:
|
|
10
|
+
|
|
11
|
+
「闸一开就接人是吧?」他蹲下去看链条和卡齿,「那今晚就先让闸开不了。」
|
|
12
|
+
|
|
13
|
+
(@Coo:闸的起落机构是什么样的?能卡在哪儿?)`;
|
|
14
|
+
|
|
15
|
+
const variants = {
|
|
16
|
+
full,
|
|
17
|
+
no_ooc: full.replace(/(@Coo:[\s\S]*?)\s*$/, '').trim(),
|
|
18
|
+
action_only: `宁把手里那只凉箍交给柯林,箍口朝上塞进他布袋:「你拿着,别落地,也别让它见水。」然后他自己转到闸门侧面去找那个起闸的轮子——不是去接,是去卡:「闸一开就接人是吧?」他蹲下去看链条和卡齿,「那今晚就先让闸开不了。」`,
|
|
19
|
+
first_para: `宁把手里那只凉箍交给柯林,箍口朝上塞进他布袋:「你拿着,别落地,也别让它见水。」`,
|
|
20
|
+
second_para: `然后他自己转到闸门侧面去找那个起闸的轮子——不是去接,是去卡:「闸一开就接人是吧?」他蹲下去看链条和卡齿,「那今晚就先让闸开不了。」`,
|
|
21
|
+
ooc_only: `(@Coo:闸的起落机构是什么样的?能卡在哪儿?)`,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const prototypes = deserializePrototypes(JSON.parse(fs.readFileSync('C:/Users/35794/Documents/DeepSeek\'s Sandbox/coopanion-continuity/backups/continuity-tags-20261031/last100/facet-prototypes.json', 'utf8')));
|
|
25
|
+
const nb = deserializeClassifier(JSON.parse(fs.readFileSync('C:/Users/35794/Documents/DeepSeek\'s Sandbox/coopanion-continuity/backups/continuity-tags-20261031/last100/facet-model.json', 'utf8')));
|
|
26
|
+
|
|
27
|
+
const out = {};
|
|
28
|
+
for (const [name, text] of Object.entries(variants)) {
|
|
29
|
+
const started = Date.now();
|
|
30
|
+
const embedding = await predictEmbedding(prototypes, text);
|
|
31
|
+
const embMs = Date.now() - started;
|
|
32
|
+
const n = predictFacets(nb, text);
|
|
33
|
+
out[name] = {
|
|
34
|
+
chars: [...text].length,
|
|
35
|
+
embedding: embedding.facets,
|
|
36
|
+
confidence: embedding.confidence,
|
|
37
|
+
ms: embMs,
|
|
38
|
+
nb: n.facets,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
console.log(JSON.stringify(out, null, 2));
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Random-facet sanity check.
|
|
4
|
+
*
|
|
5
|
+
* Positive labels are explicit mechanics words (rule-derived, not human gold).
|
|
6
|
+
* The detector is intentionally allowed to use the same signal; this verifies
|
|
7
|
+
* that the old all-"无" collapse is gone, not that the rule generalizes.
|
|
8
|
+
*/
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
import { detectRandom } from '../src/facets/random.mjs';
|
|
11
|
+
|
|
12
|
+
const args = process.argv.slice(2);
|
|
13
|
+
const argOf = (name) => {
|
|
14
|
+
const i = args.indexOf(name);
|
|
15
|
+
return i >= 0 ? args[i + 1] : undefined;
|
|
16
|
+
};
|
|
17
|
+
const input = argOf('--input');
|
|
18
|
+
if (!input) {
|
|
19
|
+
console.error('usage: node bin/facet-random-validate.mjs --input rows.jsonl');
|
|
20
|
+
process.exit(2);
|
|
21
|
+
}
|
|
22
|
+
const rows = fs.readFileSync(input, 'utf8').split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
|
|
23
|
+
const STRONG = ['掷骰', '骰子', '骰', 'd20', '检定', '抽卡'];
|
|
24
|
+
const isPositiveLabel = (text) => STRONG.some((hint) => String(text ?? '').toLowerCase().includes(hint));
|
|
25
|
+
let positive = 0;
|
|
26
|
+
let hit = 0;
|
|
27
|
+
let falsePositive = 0;
|
|
28
|
+
let negative = 0;
|
|
29
|
+
const examples = [];
|
|
30
|
+
for (const row of rows) {
|
|
31
|
+
const label = isPositiveLabel(row.text) ? '有' : '无';
|
|
32
|
+
const predicted = detectRandom(row.text);
|
|
33
|
+
if (label === '有') positive += 1;
|
|
34
|
+
else negative += 1;
|
|
35
|
+
if (label === '有' && predicted.value === '有') hit += 1;
|
|
36
|
+
if (label === '无' && predicted.value === '有') falsePositive += 1;
|
|
37
|
+
if (label === '有') examples.push({ turn: row.turn, hits: predicted.hits });
|
|
38
|
+
}
|
|
39
|
+
console.log(JSON.stringify({
|
|
40
|
+
total: rows.length,
|
|
41
|
+
positive,
|
|
42
|
+
negative,
|
|
43
|
+
recall: positive === 0 ? 0 : hit / positive,
|
|
44
|
+
falsePositive,
|
|
45
|
+
precision: hit + falsePositive === 0 ? 0 : hit / (hit + falsePositive),
|
|
46
|
+
positiveRate: rows.length === 0 ? 0 : positive / rows.length,
|
|
47
|
+
examples: examples.slice(0, 10),
|
|
48
|
+
}, null, 2));
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { extractFacets } from '../src/facets/extract.mjs';
|
|
3
|
+
import { deserializeClassifier, predictFacets } from '../src/facets/classifier.mjs';
|
|
4
|
+
import { deserializePrototypes, predictEmbedding } from '../src/facets/embedding.mjs';
|
|
5
|
+
|
|
6
|
+
const rows = fs.readFileSync('C:/Users/35794/Documents/DeepSeek\'s Sandbox/coopanion-continuity/backups/continuity-tags-20261031/online400/continuity-1500.jsonl', 'utf8')
|
|
7
|
+
.split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
|
|
8
|
+
const nb = deserializeClassifier(JSON.parse(fs.readFileSync('C:/Users/35794/Documents/DeepSeek\'s Sandbox/coopanion-continuity/backups/continuity-tags-20261031/last100/facet-model.json', 'utf8')));
|
|
9
|
+
const prototypes = deserializePrototypes(JSON.parse(fs.readFileSync('C:/Users/35794/Documents/DeepSeek\'s Sandbox/coopanion-continuity/backups/continuity-tags-20261031/last100/facet-prototypes.json', 'utf8')));
|
|
10
|
+
const turns = [2, 3, 40, 41, 42, 43, 49, 81, 82, 84];
|
|
11
|
+
const out = [];
|
|
12
|
+
for (const turn of turns) {
|
|
13
|
+
const row = rows.find((item) => Number(item.turn) === turn);
|
|
14
|
+
if (!row) continue;
|
|
15
|
+
const before = Date.now();
|
|
16
|
+
const rule = extractFacets(row.text);
|
|
17
|
+
const ruleMs = Date.now() - before;
|
|
18
|
+
const started = Date.now();
|
|
19
|
+
const nbResult = predictFacets(nb, row.text);
|
|
20
|
+
const nbMs = Date.now() - started;
|
|
21
|
+
const embStarted = Date.now();
|
|
22
|
+
const emb = await predictEmbedding(prototypes, row.text);
|
|
23
|
+
out.push({
|
|
24
|
+
turn,
|
|
25
|
+
truth: row.facets,
|
|
26
|
+
rule: { facets: rule.facets, confidence: rule.confidence, ms: ruleMs },
|
|
27
|
+
nb: { facets: nbResult.facets, confidence: nbResult.confidence, ms: nbMs },
|
|
28
|
+
embedding: { facets: emb.facets, confidence: emb.confidence, ms: Date.now() - embStarted },
|
|
29
|
+
head: String(row.text).replace(/\s+/g, ' ').slice(0, 240),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
fs.writeFileSync('C:/Users/35794/Documents/DeepSeek\'s Sandbox/coopanion-continuity/backups/continuity-tags-20261031/durable-memory/build-a/FACET-ROLEPLAY.json', JSON.stringify(out, null, 2));
|
|
33
|
+
console.log(JSON.stringify(out, null, 2));
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Incremental facet scan CLI.
|
|
4
|
+
*
|
|
5
|
+
* node bin/facet-scan.mjs --input rows.jsonl --dir .facet --query 跑团
|
|
6
|
+
*
|
|
7
|
+
* rows: { turn, seqStart, seqEnd, text?, facets? }
|
|
8
|
+
* Files: facet-index.json, facet-cursor.json, facet-residual.jsonl
|
|
9
|
+
*/
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { buildFacetIndex, queryFacets, indexStats } from '../src/facets/index.mjs';
|
|
13
|
+
import { emptyCursor, scanRows, residualCount } from '../src/facets/scanner.mjs';
|
|
14
|
+
import { loadCursor, loadIndex, saveCursor, saveIndex } from '../src/facets/store.mjs';
|
|
15
|
+
import { classifierExtractor, deserializeClassifier } from '../src/facets/classifier.mjs';
|
|
16
|
+
|
|
17
|
+
const args = process.argv.slice(2);
|
|
18
|
+
const argOf = (name) => {
|
|
19
|
+
const i = args.indexOf(name);
|
|
20
|
+
return i >= 0 ? args[i + 1] : undefined;
|
|
21
|
+
};
|
|
22
|
+
const input = argOf('--input');
|
|
23
|
+
const dir = argOf('--dir') ?? '.facet';
|
|
24
|
+
const query = argOf('--query') ?? '跑团';
|
|
25
|
+
const modelFile = argOf('--model');
|
|
26
|
+
if (!input) {
|
|
27
|
+
console.error('usage: node bin/facet-scan.mjs --input rows.jsonl --dir .facet --query 跑团 [--model facet-model.json]');
|
|
28
|
+
process.exit(2);
|
|
29
|
+
}
|
|
30
|
+
const extract = modelFile !== undefined
|
|
31
|
+
? classifierExtractor(deserializeClassifier(JSON.parse(fs.readFileSync(modelFile, 'utf8'))))
|
|
32
|
+
: undefined;
|
|
33
|
+
const rows = fs.readFileSync(input, 'utf8').split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
|
|
34
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
35
|
+
const indexFile = path.join(dir, 'facet-index.json');
|
|
36
|
+
const cursorFile = path.join(dir, 'facet-cursor.json');
|
|
37
|
+
const residualFile = path.join(dir, 'facet-residual.jsonl');
|
|
38
|
+
|
|
39
|
+
const previous = fs.existsSync(indexFile) ? loadIndex(indexFile) : { entries: [] };
|
|
40
|
+
const cursor = loadCursor(cursorFile) ?? emptyCursor();
|
|
41
|
+
const scanResult = scanRows(rows, cursor, extract === undefined ? {} : { extract });
|
|
42
|
+
const merged = [...previous.entries, ...scanResult.entries];
|
|
43
|
+
const index = buildFacetIndex(merged);
|
|
44
|
+
saveIndex(indexFile, index);
|
|
45
|
+
saveCursor(cursorFile, scanResult.cursor);
|
|
46
|
+
fs.writeFileSync(residualFile, (scanResult.cursor.residual ?? []).map((row) => JSON.stringify(row)).join('\n') + '\n', 'utf8');
|
|
47
|
+
|
|
48
|
+
const result = queryFacets(index, query, { topK: 50 });
|
|
49
|
+
console.log(JSON.stringify({
|
|
50
|
+
stats: indexStats(index),
|
|
51
|
+
scanned: scanResult.entries.length,
|
|
52
|
+
residual: residualCount(scanResult.cursor),
|
|
53
|
+
query: { name: result.query, facets: result.facets },
|
|
54
|
+
matchCount: result.matches.length,
|
|
55
|
+
ranges: result.ranges,
|
|
56
|
+
}, null, 2));
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { deserializePrototypes, predictEmbedding } from '../src/facets/embedding.mjs';
|
|
3
|
+
|
|
4
|
+
const prototypes = deserializePrototypes(JSON.parse(fs.readFileSync('C:/Users/35794/Documents/DeepSeek\'s Sandbox/coopanion-continuity/backups/continuity-tags-20261031/last100/facet-prototypes.json', 'utf8')));
|
|
5
|
+
const text = `宁把手里那只凉箍交给柯林,箍口朝上塞进他布袋:
|
|
6
|
+
|
|
7
|
+
「你拿着,别落地,也别让它见水。」
|
|
8
|
+
|
|
9
|
+
然后他自己转到闸门侧面去找那个起闸的轮子——不是去接,是去卡:
|
|
10
|
+
|
|
11
|
+
「闸一开就接人是吧?」他蹲下去看链条和卡齿,「那今晚就先让闸开不了。」
|
|
12
|
+
|
|
13
|
+
(@Coo:闸的起落机构是什么样的?能卡在哪儿?)`;
|
|
14
|
+
|
|
15
|
+
const segments = text.split(/\n{2,}/).map((part) => part.trim()).filter(Boolean).map((part) => ({
|
|
16
|
+
kind: /^[((]\s*@/.test(part) ? 'ooc' : 'story',
|
|
17
|
+
text: part,
|
|
18
|
+
}));
|
|
19
|
+
const out = [];
|
|
20
|
+
for (const segment of segments) {
|
|
21
|
+
if (segment.kind === 'ooc') {
|
|
22
|
+
out.push({ ...segment, facets: null, reason: 'ooc-skip' });
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const result = await predictEmbedding(prototypes, segment.text);
|
|
26
|
+
const accepted = (result.confidence.activity ?? 0) >= 0.05 && (result.confidence.topic ?? 0) >= 0.03;
|
|
27
|
+
out.push({
|
|
28
|
+
...segment,
|
|
29
|
+
facets: accepted ? result.facets : null,
|
|
30
|
+
confidence: result.confidence,
|
|
31
|
+
reason: accepted ? 'accepted' : 'low-confidence',
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
console.log(JSON.stringify(out, null, 2));
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import { trainFacetClassifier, evaluate, serializeClassifier } from '../src/facets/classifier.mjs';
|
|
4
|
+
|
|
5
|
+
const args = process.argv.slice(2);
|
|
6
|
+
const argOf = (name) => {
|
|
7
|
+
const i = args.indexOf(name);
|
|
8
|
+
return i >= 0 ? args[i + 1] : undefined;
|
|
9
|
+
};
|
|
10
|
+
const input = argOf('--input');
|
|
11
|
+
const out = argOf('--out') ?? 'facet-model.json';
|
|
12
|
+
if (!input) {
|
|
13
|
+
console.error('usage: node bin/facet-train.mjs --input rows.jsonl --out facet-model.json');
|
|
14
|
+
process.exit(2);
|
|
15
|
+
}
|
|
16
|
+
const rows = fs.readFileSync(input, 'utf8').split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
|
|
17
|
+
const model = trainFacetClassifier(rows);
|
|
18
|
+
fs.writeFileSync(out, `${JSON.stringify(serializeClassifier(model))}\n`, 'utf8');
|
|
19
|
+
console.log(JSON.stringify({ input: rows.length, out, report: evaluate(model, rows) }, null, 2));
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Closed-loop validation:
|
|
4
|
+
* stratified holdout -> train local NB -> hybrid extract -> index -> alias query.
|
|
5
|
+
*
|
|
6
|
+
* Input rows: { turn, seqStart, seqEnd, text, facets }
|
|
7
|
+
* `facets` is the accepted model label used only for training/evaluation.
|
|
8
|
+
*/
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
import { trainFacetClassifier } from '../src/facets/classifier.mjs';
|
|
11
|
+
import { hybridExtractor } from '../src/facets/hybrid.mjs';
|
|
12
|
+
import { buildFacetIndex, queryFacets, indexStats } from '../src/facets/index.mjs';
|
|
13
|
+
import { SLOTS, normalizeFacets } from '../src/facets/schema.mjs';
|
|
14
|
+
|
|
15
|
+
const args = process.argv.slice(2);
|
|
16
|
+
const argOf = (name) => {
|
|
17
|
+
const i = args.indexOf(name);
|
|
18
|
+
return i >= 0 ? args[i + 1] : undefined;
|
|
19
|
+
};
|
|
20
|
+
const input = argOf('--input');
|
|
21
|
+
const query = argOf('--query') ?? '跑团';
|
|
22
|
+
if (!input) {
|
|
23
|
+
console.error('usage: node bin/facet-validate.mjs --input rows.jsonl [--query 跑团]');
|
|
24
|
+
process.exit(2);
|
|
25
|
+
}
|
|
26
|
+
const rows = fs.readFileSync(input, 'utf8').split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
|
|
27
|
+
|
|
28
|
+
const groups = new Map();
|
|
29
|
+
for (const row of rows) {
|
|
30
|
+
const activity = normalizeFacets(row.facets ?? {}).activity ?? '其他';
|
|
31
|
+
if (!groups.has(activity)) groups.set(activity, []);
|
|
32
|
+
groups.get(activity).push(row);
|
|
33
|
+
}
|
|
34
|
+
const train = [];
|
|
35
|
+
const test = [];
|
|
36
|
+
for (const list of groups.values()) {
|
|
37
|
+
list.sort((a, b) => a.turn - b.turn);
|
|
38
|
+
list.forEach((row, index) => (index % 2 === 0 ? train : test).push(row));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const model = trainFacetClassifier(train);
|
|
42
|
+
const extract = hybridExtractor(model);
|
|
43
|
+
|
|
44
|
+
function evaluate(rowsToTest) {
|
|
45
|
+
const report = {};
|
|
46
|
+
for (const slot of SLOTS) {
|
|
47
|
+
let total = 0;
|
|
48
|
+
let correct = 0;
|
|
49
|
+
let covered = 0;
|
|
50
|
+
for (const row of rowsToTest) {
|
|
51
|
+
const truth = normalizeFacets(row.facets ?? {})[slot];
|
|
52
|
+
if (truth === undefined) continue;
|
|
53
|
+
total += 1;
|
|
54
|
+
const predicted = extract(row.text).facets[slot];
|
|
55
|
+
if (predicted === undefined) continue;
|
|
56
|
+
covered += 1;
|
|
57
|
+
if (predicted === truth) correct += 1;
|
|
58
|
+
}
|
|
59
|
+
report[slot] = {
|
|
60
|
+
total,
|
|
61
|
+
covered,
|
|
62
|
+
correct,
|
|
63
|
+
coverage: total === 0 ? 0 : covered / total,
|
|
64
|
+
accuracy: covered === 0 ? 0 : correct / covered,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return report;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const testRows = test.map((row) => ({
|
|
71
|
+
turn: row.turn,
|
|
72
|
+
seqStart: row.seqStart,
|
|
73
|
+
seqEnd: row.seqEnd,
|
|
74
|
+
text: row.text,
|
|
75
|
+
}));
|
|
76
|
+
const index = buildFacetIndex(testRows, { extract });
|
|
77
|
+
const result = queryFacets(index, query, { topK: testRows.length, minCoverage: 0.6 });
|
|
78
|
+
const truthRun = new Set(test
|
|
79
|
+
.filter((row) => normalizeFacets(row.facets ?? {}).activity === '跑团')
|
|
80
|
+
.map((row) => row.turn));
|
|
81
|
+
const foundRun = new Set(result.matches
|
|
82
|
+
.filter((hit) => truthRun.has(hit.turn) || true)
|
|
83
|
+
.map((hit) => hit.turn)
|
|
84
|
+
.filter((turn) => truthRun.has(turn)));
|
|
85
|
+
const truePositive = result.matches.filter((hit) => truthRun.has(hit.turn)).length;
|
|
86
|
+
const precision = result.matches.length === 0 ? 0 : truePositive / result.matches.length;
|
|
87
|
+
const recall = truthRun.size === 0 ? 0 : truePositive / truthRun.size;
|
|
88
|
+
|
|
89
|
+
console.log(JSON.stringify({
|
|
90
|
+
split: { train: train.length, test: test.length },
|
|
91
|
+
accuracy: evaluate(test),
|
|
92
|
+
index: indexStats(index),
|
|
93
|
+
query: {
|
|
94
|
+
name: result.query,
|
|
95
|
+
facets: result.facets,
|
|
96
|
+
matches: result.matches.length,
|
|
97
|
+
truthRun: truthRun.size,
|
|
98
|
+
truePositive,
|
|
99
|
+
precision,
|
|
100
|
+
recall,
|
|
101
|
+
ranges: result.ranges,
|
|
102
|
+
},
|
|
103
|
+
}, null, 2));
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { embedTexts } from '../src/facets/embedding.mjs';
|
|
2
|
+
|
|
3
|
+
const keywords = ['COVID-19', '实验室泄漏', '自然宿主', '冷链传播'];
|
|
4
|
+
const labels = ['病毒', '疫情', '公共卫生', '技术', '设备', '剧情', '其他'];
|
|
5
|
+
const vectors = await embedTexts([...keywords, ...labels]);
|
|
6
|
+
const k = vectors.slice(0, keywords.length);
|
|
7
|
+
const l = vectors.slice(keywords.length);
|
|
8
|
+
const cos = (a, b) => a.reduce((sum, x, i) => sum + x * b[i], 0);
|
|
9
|
+
const rows = labels.map((label, i) => ({
|
|
10
|
+
label,
|
|
11
|
+
score: k.reduce((sum, vec) => sum + cos(vec, l[i]), 0) / k.length,
|
|
12
|
+
per: Object.fromEntries(keywords.map((word, j) => [word, Number(cos(k[j], l[i]).toFixed(3))])),
|
|
13
|
+
})).sort((a, b) => b.score - a.score);
|
|
14
|
+
console.log(JSON.stringify(rows, null, 2));
|