@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.
Files changed (258) hide show
  1. package/INSTALL.md +25 -0
  2. package/PANEL.md +92 -0
  3. package/README.md +286 -2
  4. package/RELEASE-NOTES.md +732 -0
  5. package/bin/bc-acceptance-full.mjs +91 -0
  6. package/bin/bc-acceptance-tight.mjs +66 -0
  7. package/bin/bc-acceptance.mjs +22 -0
  8. package/bin/bc-add-keywords.mjs +21 -0
  9. package/bin/bc-demo.mjs +47 -0
  10. package/bin/bc-expand-hint.mjs +179 -0
  11. package/bin/bc-ingest-index.mjs +35 -0
  12. package/bin/bc-ingest.mjs +31 -0
  13. package/bin/bc-label-demo.mjs +18 -0
  14. package/bin/bc-relabel.mjs +150 -0
  15. package/bin/bc-sessionize.mjs +59 -0
  16. package/bin/coref_pipeline.py +47 -0
  17. package/bin/coref_service.py +57 -0
  18. package/bin/debug_ckip.py +16 -0
  19. package/bin/debug_ner_zh.py +18 -0
  20. package/bin/doctor.mjs +275 -0
  21. package/bin/en_ner_error_audit.py +33 -0
  22. package/bin/eval-label-rules.mjs +342 -0
  23. package/bin/export-turns.py +68 -0
  24. package/bin/extract-user-paragraph.mjs +14 -0
  25. package/bin/extraction-composite-smoke.mjs +28 -0
  26. package/bin/extraction-latency.mjs +58 -0
  27. package/bin/extraction-user-smoke.mjs +28 -0
  28. package/bin/extraction_service.py +164 -0
  29. package/bin/facet-active-experiment.mjs +110 -0
  30. package/bin/facet-covid.mjs +8 -0
  31. package/bin/facet-curve.mjs +27 -0
  32. package/bin/facet-embedding-train.mjs +20 -0
  33. package/bin/facet-embedding-validate.mjs +83 -0
  34. package/bin/facet-eval-roleplay.mjs +42 -0
  35. package/bin/facet-eval.mjs +38 -0
  36. package/bin/facet-fold.mjs +31 -0
  37. package/bin/facet-load.mjs +16 -0
  38. package/bin/facet-mlp-scan.mjs +45 -0
  39. package/bin/facet-mvp.mjs +34 -0
  40. package/bin/facet-online-experiments.mjs +78 -0
  41. package/bin/facet-online-race.mjs +99 -0
  42. package/bin/facet-online-validate.mjs +51 -0
  43. package/bin/facet-panel.mjs +22 -0
  44. package/bin/facet-passage.mjs +41 -0
  45. package/bin/facet-random-validate.mjs +48 -0
  46. package/bin/facet-roleplay.mjs +33 -0
  47. package/bin/facet-scan.mjs +56 -0
  48. package/bin/facet-segment.mjs +34 -0
  49. package/bin/facet-train.mjs +19 -0
  50. package/bin/facet-validate.mjs +103 -0
  51. package/bin/facet-zeroshot.mjs +14 -0
  52. package/bin/install-templates.mjs +72 -0
  53. package/bin/memory-bench.mjs +53 -0
  54. package/bin/memory-build-summary.mjs +43 -0
  55. package/bin/memory-build-vector.mjs +31 -0
  56. package/bin/memory-build.mjs +41 -0
  57. package/bin/memory-calibration.mjs +70 -0
  58. package/bin/memory-closed-loop.mjs +119 -0
  59. package/bin/memory-dry-run.mjs +70 -0
  60. package/bin/memory-eval.mjs +104 -0
  61. package/bin/memory-event-alias-test.mjs +89 -0
  62. package/bin/memory-extraction-bench.mjs +58 -0
  63. package/bin/memory-load-gate-replay.mjs +137 -0
  64. package/bin/memory-loop-test.mjs +90 -0
  65. package/bin/memory-pre-turn-harness.mjs +63 -0
  66. package/bin/memory-query.mjs +39 -0
  67. package/bin/memory-replay-hints.mjs +84 -0
  68. package/bin/memory-scale.mjs +58 -0
  69. package/bin/memory-scenario-test.mjs +146 -0
  70. package/bin/memory-user-loop.mjs +95 -0
  71. package/bin/ner_bench_tiny_zh.py +59 -0
  72. package/bin/ner_bench_zh.py +60 -0
  73. package/bin/ner_conll_en.py +139 -0
  74. package/bin/ner_direct_en.py +123 -0
  75. package/bin/ner_direct_zh.py +100 -0
  76. package/bin/ner_pipeline.py +208 -0
  77. package/bin/noise-selfcheck.mjs +50 -0
  78. package/bin/pack-integrity.mjs +47 -0
  79. package/bin/panel-check.mjs +57 -0
  80. package/bin/plugin-integrity.mjs +74 -0
  81. package/bin/preview-live.mjs +13 -0
  82. package/bin/public-install-check.mjs +222 -0
  83. package/bin/relabel-apply.mjs +272 -0
  84. package/bin/relabel-dry-run.mjs +381 -0
  85. package/bin/relik_pipeline.py +39 -0
  86. package/bin/relik_service.py +52 -0
  87. package/bin/srl_pipeline.py +55 -0
  88. package/bin/srl_service.py +68 -0
  89. package/bin/summary-index.mjs +151 -0
  90. package/bin/summary-query.mjs +79 -0
  91. package/bin/vec-check.mjs +27 -0
  92. package/bin/zh_ner_error_audit.py +36 -0
  93. package/bin/zh_ner_slot_eval.py +38 -0
  94. package/client.js +760 -0
  95. package/cordis.patch.yml +378 -0
  96. package/docs/API-NOTES.md +1743 -0
  97. package/docs/ARCHITECTURE.md +355 -0
  98. package/docs/CACHE.md +253 -0
  99. package/docs/CONDUCT.md +30 -0
  100. package/docs/CREATOR-HANDOFF.md +102 -0
  101. package/docs/EXPERIMENT.md +136 -0
  102. package/docs/FACET-MVP-REPORT.md +473 -0
  103. package/docs/GUIDE-0.4.66.md +195 -0
  104. package/docs/GUIDE.md +127 -0
  105. package/docs/MECHANISM.md +243 -0
  106. package/docs/NEW-INSTANCE-HANDOFF.md +219 -0
  107. package/docs/README.md +37 -0
  108. package/docs/SESSION-LAYOUT.md +151 -0
  109. package/docs/history-map.md +249 -0
  110. package/docs/relabel-migration-wiring.md +366 -0
  111. package/guides/00-READ-FIRST.md +206 -0
  112. package/guides/01-INSTALL.md +207 -0
  113. package/guides/02-TABLES.md +225 -0
  114. package/guides/03-AUDIT.md +201 -0
  115. package/guides/04-REBUILD.md +306 -0
  116. package/guides/05-FIRST-RUN.md +191 -0
  117. package/guides/06-EMBEDDING.md +146 -0
  118. package/guides/07-PTC-AND-ASSETS.md +221 -0
  119. package/icon.svg +7 -0
  120. package/locale/en.json +6 -0
  121. package/locale/zh.json +6 -0
  122. package/models/README.md +13 -0
  123. package/models/Xenova/bge-small-zh-v1.5/config.json +31 -0
  124. package/models/Xenova/bge-small-zh-v1.5/onnx/model_quantized.onnx +0 -0
  125. package/models/Xenova/bge-small-zh-v1.5/tokenizer.json +21278 -0
  126. package/models/Xenova/bge-small-zh-v1.5/tokenizer_config.json +15 -0
  127. package/package.json +139 -4
  128. package/src/asset-tool.mjs +237 -0
  129. package/src/assets.mjs +244 -0
  130. package/src/backfill.mjs +74 -0
  131. package/src/backup.mjs +80 -0
  132. package/src/deadend.mjs +95 -0
  133. package/src/deleted-store.mjs +35 -0
  134. package/src/engine.mjs +3831 -0
  135. package/src/facets/classifier.mjs +146 -0
  136. package/src/facets/embedding-service.mjs +53 -0
  137. package/src/facets/embedding-worker.mjs +27 -0
  138. package/src/facets/embedding.mjs +163 -0
  139. package/src/facets/extract.mjs +80 -0
  140. package/src/facets/fold-plan.mjs +84 -0
  141. package/src/facets/hybrid.mjs +29 -0
  142. package/src/facets/index.mjs +113 -0
  143. package/src/facets/linear-probe.mjs +43 -0
  144. package/src/facets/load.mjs +37 -0
  145. package/src/facets/mlp-probe.mjs +54 -0
  146. package/src/facets/mlp-service.mjs +53 -0
  147. package/src/facets/mlp-worker.mjs +26 -0
  148. package/src/facets/online-knn.mjs +58 -0
  149. package/src/facets/online-models.mjs +227 -0
  150. package/src/facets/panel-state.mjs +23 -0
  151. package/src/facets/panel.mjs +73 -0
  152. package/src/facets/random.mjs +26 -0
  153. package/src/facets/scanner.mjs +35 -0
  154. package/src/facets/schema.mjs +75 -0
  155. package/src/facets/seed-examples.mjs +58 -0
  156. package/src/facets/sensitivity.mjs +98 -0
  157. package/src/facets/service.mjs +53 -0
  158. package/src/facets/store.mjs +39 -0
  159. package/src/facets/worker.mjs +23 -0
  160. package/src/folds-store.mjs +109 -0
  161. package/src/groups.mjs +167 -0
  162. package/src/handoff-tool.mjs +51 -0
  163. package/src/handoff.mjs +127 -0
  164. package/src/index.mjs +649 -0
  165. package/src/inject-policy.mjs +17 -0
  166. package/src/learning.mjs +88 -0
  167. package/src/memory/alias-proposal.mjs +40 -0
  168. package/src/memory/bc-audit.mjs +245 -0
  169. package/src/memory/bc-index-updater.mjs +404 -0
  170. package/src/memory/bc-index.mjs +48 -0
  171. package/src/memory/bc-label.mjs +220 -0
  172. package/src/memory/bc-panel-state.mjs +107 -0
  173. package/src/memory/bc-pre-step.mjs +463 -0
  174. package/src/memory/bc-recall.mjs +470 -0
  175. package/src/memory/bc-shift.mjs +123 -0
  176. package/src/memory/bc-store.mjs +41 -0
  177. package/src/memory/bm25.mjs +112 -0
  178. package/src/memory/calibration.mjs +54 -0
  179. package/src/memory/chatter.mjs +29 -0
  180. package/src/memory/cmap-file.mjs +107 -0
  181. package/src/memory/concepts-file.mjs +41 -0
  182. package/src/memory/coref-slots.mjs +40 -0
  183. package/src/memory/event-alias-ledger.mjs +87 -0
  184. package/src/memory/event-log.mjs +95 -0
  185. package/src/memory/event-model.mjs +132 -0
  186. package/src/memory/event-segmenter.mjs +36 -0
  187. package/src/memory/event-summary-tree.mjs +122 -0
  188. package/src/memory/extraction-adapter.mjs +76 -0
  189. package/src/memory/extraction-composite.mjs +75 -0
  190. package/src/memory/extraction-core.mjs +76 -0
  191. package/src/memory/extraction-pre-step.mjs +161 -0
  192. package/src/memory/extraction-provider.mjs +300 -0
  193. package/src/memory/extraction-score.mjs +36 -0
  194. package/src/memory/facet-stabilizer.mjs +59 -0
  195. package/src/memory/history-map.mjs +139 -0
  196. package/src/memory/language-router.mjs +30 -0
  197. package/src/memory/load-gate.mjs +161 -0
  198. package/src/memory/mechanical-note.mjs +104 -0
  199. package/src/memory/onboarding.mjs +163 -0
  200. package/src/memory/persona.mjs +78 -0
  201. package/src/memory/planner.mjs +249 -0
  202. package/src/memory/pre-turn.mjs +80 -0
  203. package/src/memory/query-aliases.mjs +39 -0
  204. package/src/memory/recall-cue.mjs +43 -0
  205. package/src/memory/recall.mjs +65 -0
  206. package/src/memory/referent-candidates.mjs +55 -0
  207. package/src/memory/referent-ledger.mjs +86 -0
  208. package/src/memory/relabel-migrate.mjs +376 -0
  209. package/src/memory/relik-adapter.mjs +25 -0
  210. package/src/memory/special-keywords.mjs +60 -0
  211. package/src/memory/structured-adapters.mjs +75 -0
  212. package/src/memory/summary-index.mjs +335 -0
  213. package/src/memory/summary-tree.mjs +110 -0
  214. package/src/memory/term-extractor.mjs +44 -0
  215. package/src/memory/tokenize.mjs +9 -0
  216. package/src/memory/vector-index.mjs +54 -0
  217. package/src/memory/window-summary.mjs +133 -0
  218. package/src/mood.mjs +133 -0
  219. package/src/peer-tool.mjs +63 -0
  220. package/src/peer.mjs +144 -0
  221. package/src/policy.mjs +170 -0
  222. package/src/preflight.mjs +113 -0
  223. package/src/provenance.mjs +94 -0
  224. package/src/range.mjs +75 -0
  225. package/src/recall-exact-tool.mjs +267 -0
  226. package/src/recall-tool.mjs +521 -0
  227. package/src/recheck.mjs +99 -0
  228. package/src/save-start.mjs +29 -0
  229. package/src/session-guard.mjs +140 -0
  230. package/src/session-log-tool.mjs +322 -0
  231. package/src/session-log.mjs +606 -0
  232. package/src/skills.mjs +111 -0
  233. package/src/split-range.mjs +115 -0
  234. package/src/state.mjs +198 -0
  235. package/src/t0.mjs +149 -0
  236. package/src/tag-confirm.mjs +78 -0
  237. package/src/tag-detect.mjs +42 -0
  238. package/src/tag-maintenance.mjs +285 -0
  239. package/src/tag-reject.mjs +73 -0
  240. package/src/tags-store.mjs +94 -0
  241. package/src/tags.mjs +692 -0
  242. package/src/threads-backend.mjs +26 -0
  243. package/src/threads-core.mjs +1887 -0
  244. package/src/threads-store.mjs +207 -0
  245. package/src/threads-tool.mjs +522 -0
  246. package/src/threads.mjs +929 -0
  247. package/src/tool-preview.mjs +427 -0
  248. package/src/topic.mjs +63 -0
  249. package/src/training.mjs +75 -0
  250. package/src/turn-tag.mjs +62 -0
  251. package/src/user-turn.mjs +39 -0
  252. package/templates/cmap.md +11 -0
  253. package/templates/concepts.md +12 -0
  254. package/templates/habits.md +40 -0
  255. package/templates/keywords.md +6 -0
  256. package/templates/rebuild.md +134 -0
  257. package/templates/rules-safe.md +184 -0
  258. package/templates/rules.md +177 -0
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Scenario replay: new term, new→old, old→new→old, repeated recall,
4
+ * Coo interjection. Gate is before the turn; alias proposal is after it.
5
+ */
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ import { readEventLog } from '../src/memory/event-log.mjs';
9
+ import { MemoryLoadState, latestCandidate, candidateSegment } from '../src/memory/load-gate.mjs';
10
+ import { AliasProposalState } from '../src/memory/alias-proposal.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 dir = argOf('--dir');
18
+ if (!dir) {
19
+ console.error('usage: node bin/memory-scenario-test.mjs --dir .durable-memory');
20
+ process.exit(2);
21
+ }
22
+ const records = readEventLog(path.join(dir, 'events.jsonl'));
23
+ const dev = { activity: '开发', topic: '技术' };
24
+ const run = { activity: '跑团', topic: '剧情' };
25
+
26
+ const scenarios = [
27
+ {
28
+ id: 'new-term',
29
+ steps: [
30
+ { text: '我们开个新团,叫星港', facets: run, term: '星港', gate: 'load', alias: 'none' },
31
+ { text: '星港的规则是掷骰', facets: run, term: '星港', gate: 'none', alias: 'propose' },
32
+ { text: '星港继续', facets: run, term: '星港', gate: 'none', alias: 'none' },
33
+ ],
34
+ },
35
+ {
36
+ id: 'new-old-back',
37
+ steps: [
38
+ { text: '我们开个新团,叫星港', facets: run, term: '星港', gate: 'load', alias: 'none' },
39
+ { text: '星港里有一扇门', facets: run, term: '星港', gate: 'none', alias: 'propose' },
40
+ { text: '上次跑团怎么样了', facets: run, gate: 'load', alias: 'none' },
41
+ { text: '回到星港', facets: run, term: '星港', gate: 'none', alias: 'none' },
42
+ ],
43
+ },
44
+ {
45
+ id: 'old-new-old',
46
+ steps: [
47
+ { text: '规则怎么写', facets: dev, gate: 'none', alias: 'none' },
48
+ { text: '开始跑团', facets: run, gate: 'load', alias: 'none' },
49
+ { text: '星港的规则是掷骰', facets: run, term: '星港', gate: 'none', alias: 'none' },
50
+ { text: '星港继续', facets: run, term: '星港', gate: 'none', alias: 'propose' },
51
+ { text: '上次规则写哪了', facets: dev, gate: 'load', alias: 'none' },
52
+ ],
53
+ },
54
+ {
55
+ id: 'repeat-recall',
56
+ steps: [
57
+ { text: '上次跑团怎么样了', facets: run, gate: 'load', alias: 'none' },
58
+ { text: '上次跑团后来呢', facets: run, gate: 'load', alias: 'none' },
59
+ { text: '继续跑团', facets: run, gate: 'none', alias: 'none' },
60
+ ],
61
+ },
62
+ {
63
+ id: 'coo-interject',
64
+ steps: [
65
+ { text: 'Coo:来一局', facets: run, gate: 'load', alias: 'none', role: 'coo' },
66
+ { text: 'Coo:灯室见', facets: run, gate: 'none', alias: 'none', role: 'coo' },
67
+ { text: '我推门进去', facets: run, gate: 'none', alias: 'none' },
68
+ ],
69
+ },
70
+ {
71
+ id: 'known-alias',
72
+ steps: [
73
+ { text: 'TRPG 开一把', facets: run, term: 'TRPG', gate: 'load', alias: 'none' },
74
+ ],
75
+ },
76
+ ];
77
+
78
+ const output = [];
79
+ for (const scenario of scenarios) {
80
+ const loadState = new MemoryLoadState({ loadedFacets: dev, loadedAtTurn: 0 });
81
+ const aliasState = new AliasProposalState();
82
+ const events = [];
83
+ for (let index = 0; index < scenario.steps.length; index += 1) {
84
+ const step = scenario.steps[index];
85
+ const nowTurn = 100 + index;
86
+ const candidate = latestCandidate(records, 'activity', step.facets.activity);
87
+ const segment = candidateSegment(records, candidate);
88
+ const gate = loadState.decide({
89
+ text: step.text,
90
+ currentFacets: step.facets,
91
+ confidence: 0.9,
92
+ nowTurn,
93
+ hasCandidate: candidate !== null,
94
+ candidate,
95
+ role: step.role ?? 'user',
96
+ });
97
+ if (gate.action === 'load') {
98
+ loadState.recordLoad({ facets: step.facets, seq: segment?.seqStart ?? null, nowTurn, reason: gate.reason });
99
+ }
100
+ const alias = aliasState.observe({ term: step.term, facets: step.facets, nowTurn });
101
+ events.push({
102
+ text: step.text,
103
+ gate: gate.action,
104
+ gateReason: gate.reason,
105
+ expectedGate: step.gate,
106
+ alias: alias.action,
107
+ aliasReason: alias.reason,
108
+ expectedAlias: step.alias,
109
+ aliasText: alias.text ?? null,
110
+ candidateTurn: candidate?.turn ?? null,
111
+ segmentTurns: segment?.turns ?? 0,
112
+ position: { gate: 'before-response', alias: 'after-turn' },
113
+ });
114
+ }
115
+ const gatePass = events.filter((event) => event.gate === event.expectedGate).length;
116
+ const aliasPass = events.filter((event) => event.alias === event.expectedAlias).length;
117
+ output.push({
118
+ id: scenario.id,
119
+ steps: events.length,
120
+ gatePass,
121
+ aliasPass,
122
+ pass: gatePass === events.length && aliasPass === events.length,
123
+ events,
124
+ });
125
+ }
126
+ const summary = {
127
+ scenarios: output.length,
128
+ steps: output.reduce((sum, item) => sum + item.steps, 0),
129
+ passScenarios: output.filter((item) => item.pass).length,
130
+ failedScenarios: output.filter((item) => !item.pass).map((item) => item.id),
131
+ missedAliasProposals: output.flatMap((item) => item.events.filter((event) => event.expectedAlias === 'propose' && event.alias !== 'propose')).length,
132
+ extraAliasProposals: output.flatMap((item) => item.events.filter((event) => event.expectedAlias !== 'propose' && event.alias === 'propose')).length,
133
+ };
134
+ const result = { dir, summary, output };
135
+ fs.writeFileSync(path.join(dir, 'SCENARIO-TEST.json'), JSON.stringify(result, null, 2));
136
+ console.log(JSON.stringify(summary, null, 2));
137
+ for (const scenario of output) {
138
+ if (!scenario.pass) {
139
+ console.log('FAIL', scenario.id);
140
+ for (const event of scenario.events) {
141
+ if (event.gate !== event.expectedGate || event.alias !== event.expectedAlias) {
142
+ console.log(' ', event.text, 'gate', event.gate, 'expected', event.expectedGate, 'alias', event.alias, 'expected', event.expectedAlias);
143
+ }
144
+ }
145
+ }
146
+ }
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Real user-message loop: BGE + MLP facets + NER slots + pre-turn gate.
4
+ * No live session IO.
5
+ */
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ import { readEventLog } from '../src/memory/event-log.mjs';
9
+ import { PreTurnController } from '../src/memory/pre-turn.mjs';
10
+ import { MemoryLoadState } from '../src/memory/load-gate.mjs';
11
+ import { attachExtraction } from '../src/memory/extraction-adapter.mjs';
12
+ import { embedTexts } from '../src/facets/embedding.mjs';
13
+ import { deserializeMlp, predictMlp } from '../src/facets/mlp-probe.mjs';
14
+ import { BM25Index } from '../src/memory/bm25.mjs';
15
+ import { VectorIndex } from '../src/memory/vector-index.mjs';
16
+ import { summaryIndex } from '../src/memory/planner.mjs';
17
+ import { stabilizeFacets } from '../src/memory/facet-stabilizer.mjs';
18
+
19
+ const args = process.argv.slice(2);
20
+ const argOf = (name) => {
21
+ const i = args.indexOf(name);
22
+ return i >= 0 ? args[i + 1] : undefined;
23
+ };
24
+ const dir = argOf('--dir');
25
+ const nerFile = argOf('--ner');
26
+ const modelPath = argOf('--model');
27
+ if (!dir || !nerFile || !modelPath) {
28
+ console.error('usage: node bin/memory-user-loop.mjs --dir .durable-memory --ner USER-NER.json --model MLP-MODEL.json');
29
+ process.exit(2);
30
+ }
31
+ const records = readEventLog(path.join(dir, 'events.jsonl'));
32
+ const ner = JSON.parse(fs.readFileSync(nerFile, 'utf8'));
33
+ const model = deserializeMlp(JSON.parse(fs.readFileSync(modelPath, 'utf8')));
34
+ const bm25 = BM25Index.fromJSON(JSON.parse(fs.readFileSync(path.join(dir, 'bm25.json'), 'utf8')));
35
+ const vectorIndex = VectorIndex.fromJSON(JSON.parse(fs.readFileSync(path.join(dir, 'vectors.json'), 'utf8')));
36
+ const tree = JSON.parse(fs.readFileSync(path.join(dir, 'summary-tree.json'), 'utf8'));
37
+ const summaries = summaryIndex(tree);
38
+ const texts = ner.rows.map((row) => row.text);
39
+ const vectors = await embedTexts(texts);
40
+ const controller = new PreTurnController({
41
+ records,
42
+ gate: new MemoryLoadState({ loadedFacets: { activity: '开发', topic: '技术' }, loadedAtTurn: 0 }),
43
+ });
44
+ const rows = [];
45
+ for (let index = 0; index < ner.rows.length; index += 1) {
46
+ const item = ner.rows[index];
47
+ const prediction = predictMlp(model, vectors[index]);
48
+ const keyword = bm25.search(item.text, { topK: 5, minCoverage: 0.3 });
49
+ const semantic = vectorIndex.search(vectors[index], { topK: 5 });
50
+ const summary = summaries.search(item.text, { topK: 3, minCoverage: 0.3 });
51
+ const stable = stabilizeFacets({
52
+ raw: prediction,
53
+ text: item.text,
54
+ records,
55
+ keyword,
56
+ semantic,
57
+ summary,
58
+ });
59
+ const result = attachExtraction(controller, {
60
+ text: item.text,
61
+ entities: item.entities,
62
+ slots: item.slots,
63
+ facets: stable.facets,
64
+ confidence: stable.confidence.activity,
65
+ nowTurn: 1000 + index,
66
+ });
67
+ rows.push({
68
+ turn: item.turn,
69
+ text: item.text,
70
+ facets: stable.facets,
71
+ rawFacets: prediction.facets,
72
+ confidence: +stable.confidence.activity.toFixed(2),
73
+ stabilizeReason: stable.reason,
74
+ slots: item.slots,
75
+ entities: item.entities,
76
+ ok: result.ok,
77
+ action: result.gate?.action ?? 'invalid',
78
+ reason: result.gate?.reason ?? result.reason,
79
+ boundaryScore: result.gate?.boundaryScore ?? null,
80
+ candidateTurn: result.gate?.candidateTurn ?? null,
81
+ segmentTurns: result.gate?.segment?.turns ?? 0,
82
+ });
83
+ }
84
+ const summary = {
85
+ messages: rows.length,
86
+ loads: rows.filter((row) => row.action === 'load').length,
87
+ hints: rows.filter((row) => row.action === 'hint').length,
88
+ none: rows.filter((row) => row.action === 'none').length,
89
+ invalid: rows.filter((row) => row.action === 'invalid').length,
90
+ withEntities: rows.filter((row) => row.entities.length > 0).length,
91
+ loadsSample: rows.filter((row) => row.action === 'load').slice(0, 8).map((row) => ({ turn: row.turn, text: row.text.slice(0, 80), reason: row.reason })),
92
+ };
93
+ const output = { summary, rows };
94
+ fs.writeFileSync(path.join(dir, 'USER-LOOP.json'), JSON.stringify(output, null, 2));
95
+ console.log(JSON.stringify(summary, null, 2));
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """Tiny local Chinese NER smoke benchmark on CLUE-NER validation (boundary-only)."""
4
+ import json
5
+ import os
6
+ import sys
7
+ from datasets import load_dataset
8
+ from tokenizers import BertWordPieceTokenizer
9
+ from transformers import AutoModelForTokenClassification, PreTrainedTokenizerFast, pipeline
10
+
11
+ MODEL_DIR = os.path.join(os.environ["TEMP"], "ckip-tiny")
12
+ LIMIT = int(sys.argv[1]) if len(sys.argv) > 1 else 20
13
+ OUT = sys.argv[2] if len(sys.argv) > 2 else None
14
+
15
+ bw = BertWordPieceTokenizer(vocab=os.path.join(MODEL_DIR, "vocab.txt"), lowercase=False)
16
+ tok = PreTrainedTokenizerFast(
17
+ tokenizer_object=bw,
18
+ unk_token="[UNK]",
19
+ sep_token="[SEP]",
20
+ pad_token="[PAD]",
21
+ cls_token="[CLS]",
22
+ mask_token="[MASK]",
23
+ )
24
+ model = AutoModelForTokenClassification.from_pretrained(MODEL_DIR)
25
+ ner = pipeline("ner", model=model, tokenizer=tok, aggregation_strategy="simple", device=-1)
26
+ ds = load_dataset("xusenlin/clue-ner", split="validation")
27
+
28
+ rows = []
29
+ tp = fp = fn = 0
30
+ for i in range(min(LIMIT, len(ds))):
31
+ item = ds[i]
32
+ pred = ner(item["text"])
33
+ pred_spans = {p["word"].replace(" ", "") for p in pred}
34
+ gold_spans = {e["entity"] for e in item["entities"]}
35
+ tp += len(pred_spans & gold_spans)
36
+ fp += len(pred_spans - gold_spans)
37
+ fn += len(gold_spans - pred_spans)
38
+ rows.append({
39
+ "text": item["text"],
40
+ "pred": [{"text": p["word"].replace(" ", ""), "label": p["entity_group"]} for p in pred],
41
+ "gold": [{"text": e["entity"], "label": e["label"]} for e in item["entities"]],
42
+ "pred_spans": sorted(pred_spans),
43
+ "gold_spans": sorted(gold_spans),
44
+ })
45
+ precision = 0 if tp + fp == 0 else tp / (tp + fp)
46
+ recall = 0 if tp + fn == 0 else tp / (tp + fn)
47
+ f1 = 0 if precision + recall == 0 else 2 * precision * recall / (precision + recall)
48
+ result = {
49
+ "model": "ckiplab/bert-tiny-chinese-ner (local)",
50
+ "dataset": "xusenlin/clue-ner validation",
51
+ "n": len(rows),
52
+ "boundary_only": {"tp": tp, "fp": fp, "fn": fn, "precision": round(precision, 4), "recall": round(recall, 4), "f1": round(f1, 4)},
53
+ "rows": rows,
54
+ }
55
+ if OUT:
56
+ with open(OUT, "w", encoding="utf-8") as f:
57
+ json.dump(result, f, ensure_ascii=False, indent=2)
58
+ print(json.dumps({k: v for k, v in result.items() if k != "rows"}, ensure_ascii=False, indent=2))
59
+
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """Chinese NER benchmark: UER CLUENER model on clean CLUE-NER validation."""
4
+ import json
5
+ import sys
6
+ import torch
7
+ from datasets import load_dataset
8
+ from transformers import AutoModelForTokenClassification, AutoTokenizer, pipeline
9
+
10
+ MODEL = "uer/roberta-base-finetuned-cluener2020-chinese"
11
+ DATASET = "xusenlin/clue-ner"
12
+ LIMIT = int(sys.argv[1]) if len(sys.argv) > 1 else 100
13
+ OUT = sys.argv[2] if len(sys.argv) > 2 else None
14
+
15
+ torch.set_num_threads(1)
16
+ print("loading model", flush=True)
17
+ tok = AutoTokenizer.from_pretrained(MODEL)
18
+ model = AutoModelForTokenClassification.from_pretrained(MODEL)
19
+ print("labels", model.config.id2label, flush=True)
20
+ ner = pipeline("ner", model=model, tokenizer=tok, aggregation_strategy="simple", device=-1, truncation=True, max_length=512)
21
+ print("loading data", flush=True)
22
+ ds = load_dataset(DATASET, split="validation")
23
+
24
+
25
+ def spans(pred, gold):
26
+ gold_keys = {(g["entity"], g["label"]) for g in gold}
27
+ pred_keys = {(p["word"].replace(" ", ""), p["entity_group"]) for p in pred}
28
+ tp = len(gold_keys & pred_keys)
29
+ fp = len(pred_keys - gold_keys)
30
+ fn = len(gold_keys - pred_keys)
31
+ precision = 0 if not pred_keys else tp / len(pred_keys)
32
+ recall = 0 if not gold_keys else tp / len(gold_keys)
33
+ f1 = 0 if precision + recall == 0 else 2 * precision * recall / (precision + recall)
34
+ return {"tp": tp, "fp": fp, "fn": fn, "precision": round(precision, 4), "recall": round(recall, 4), "f1": round(f1, 4)}
35
+
36
+
37
+ rows = []
38
+ for i in range(min(LIMIT, len(ds))):
39
+ item = ds[i]
40
+ pred = ner(item["text"])
41
+ gold = [{"entity": e["entity"], "label": e["label"]} for e in item["entities"]]
42
+ rows.append({
43
+ "text": item["text"],
44
+ "pred": [{"text": p["word"].replace(" ", ""), "label": p["entity_group"]} for p in pred],
45
+ "gold": gold,
46
+ })
47
+ if i < 2:
48
+ print("sample", i, rows[-1]["pred"], rows[-1]["gold"], flush=True)
49
+
50
+ result = {
51
+ "model": MODEL,
52
+ "dataset": DATASET,
53
+ "n": len(rows),
54
+ "score": spans([p for r in rows for p in r["pred"]], [g for r in rows for g in r["gold"]]),
55
+ "rows": rows,
56
+ }
57
+ if OUT:
58
+ with open(OUT, "w", encoding="utf-8") as f:
59
+ json.dump(result, f, ensure_ascii=False, indent=2)
60
+ print(json.dumps({k: v for k, v in result.items() if k != "rows"}, ensure_ascii=False, indent=2), flush=True)
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """English NER benchmark: DistilBERT CoNLL03 on the official eng.testa file."""
4
+ import json
5
+ import sys
6
+
7
+ import torch
8
+ from transformers import AutoModelForTokenClassification, AutoTokenizer
9
+
10
+ MODEL = "elastic/distilbert-base-uncased-finetuned-conll03-english"
11
+ FILE = sys.argv[1]
12
+ LIMIT = int(sys.argv[2]) if len(sys.argv) > 2 else 200
13
+ OUT = sys.argv[3] if len(sys.argv) > 3 else None
14
+ MAX_LEN = 256
15
+
16
+ torch.set_num_threads(1)
17
+ tok = AutoTokenizer.from_pretrained(MODEL)
18
+ model = AutoModelForTokenClassification.from_pretrained(MODEL)
19
+ model.eval()
20
+ id2label = model.config.id2label
21
+
22
+
23
+ def read_sentences(path):
24
+ sentences = []
25
+ tokens, tags = [], []
26
+ for line in open(path, encoding="utf-8"):
27
+ line = line.rstrip("\n")
28
+ if not line.strip():
29
+ if tokens:
30
+ sentences.append((tokens, tags))
31
+ tokens, tags = [], []
32
+ continue
33
+ parts = line.split()
34
+ if len(parts) < 2:
35
+ continue
36
+ tokens.append(parts[0])
37
+ tags.append(parts[-1])
38
+ if tokens:
39
+ sentences.append((tokens, tags))
40
+ return sentences
41
+
42
+
43
+ def predict(text):
44
+ enc = tok(text, return_tensors="pt", truncation=True, max_length=MAX_LEN, return_offsets_mapping=True)
45
+ offsets = enc.pop("offset_mapping")[0].tolist()
46
+ with torch.no_grad():
47
+ logits = model(**enc).logits[0]
48
+ ids = logits.argmax(-1).tolist()
49
+ entities = []
50
+ current = None
51
+ for idx, label_id in enumerate(ids):
52
+ label = id2label[int(label_id)]
53
+ start, end = offsets[idx]
54
+ if start == end:
55
+ continue
56
+ if label == "O":
57
+ if current is not None:
58
+ entities.append(current)
59
+ current = None
60
+ continue
61
+ prefix, _, kind = label.partition("-")
62
+ if prefix == "B":
63
+ if current is not None:
64
+ entities.append(current)
65
+ current = {"text": text[start:end], "label": kind, "start": start, "end": end}
66
+ elif prefix == "I" and current is not None and current["label"] == kind:
67
+ current["text"] += text[start:end]
68
+ current["end"] = end
69
+ else:
70
+ if current is not None:
71
+ entities.append(current)
72
+ current = {"text": text[start:end], "label": kind, "start": start, "end": end}
73
+ if current is not None:
74
+ entities.append(current)
75
+ merged = []
76
+ for entity in entities:
77
+ last = merged[-1] if merged else None
78
+ if last is not None and last["label"] == entity["label"] and entity["start"] - last["end"] <= 1:
79
+ last["text"] += entity["text"]
80
+ last["end"] = entity["end"]
81
+ else:
82
+ merged.append(entity)
83
+ return merged
84
+
85
+
86
+ def gold_entities(tokens, tags):
87
+ out = []
88
+ current = None
89
+ for token, tag in zip(tokens, tags):
90
+ if tag == "O":
91
+ if current is not None:
92
+ out.append(current)
93
+ current = None
94
+ continue
95
+ prefix, _, kind = tag.partition("-")
96
+ if prefix == "B":
97
+ if current is not None:
98
+ out.append(current)
99
+ current = {"text": token, "label": kind}
100
+ elif prefix == "I" and current is not None:
101
+ current["text"] += " " + token
102
+ else:
103
+ if current is not None:
104
+ out.append(current)
105
+ current = {"text": token, "label": kind}
106
+ if current is not None:
107
+ out.append(current)
108
+ return out
109
+
110
+
111
+ def score(pred, gold):
112
+ pred_keys = {(p["text"].replace(" ", ""), p["label"]) for p in pred}
113
+ gold_keys = {(g["text"].replace(" ", ""), g["label"]) for g in gold}
114
+ tp = len(pred_keys & gold_keys)
115
+ fp = len(pred_keys - gold_keys)
116
+ fn = len(gold_keys - pred_keys)
117
+ precision = 0 if not pred_keys else tp / len(pred_keys)
118
+ recall = 0 if not gold_keys else tp / len(gold_keys)
119
+ f1 = 0 if precision + recall == 0 else 2 * precision * recall / (precision + recall)
120
+ return {"tp": tp, "fp": fp, "fn": fn, "precision": round(precision, 4), "recall": round(recall, 4), "f1": round(f1, 4)}
121
+
122
+
123
+ sentences = read_sentences(FILE)[:LIMIT]
124
+ rows = []
125
+ for i, (tokens, tags) in enumerate(sentences):
126
+ text = " ".join(tokens)
127
+ rows.append({"text": text, "pred": predict(text), "gold": gold_entities(tokens, tags)})
128
+
129
+ result = {
130
+ "model": MODEL,
131
+ "dataset": "CoNLL2003 eng.testa",
132
+ "n": len(rows),
133
+ "score": score([p for r in rows for p in r["pred"]], [g for r in rows for g in r["gold"]]),
134
+ "rows": rows,
135
+ }
136
+ if OUT:
137
+ with open(OUT, "w", encoding="utf-8") as f:
138
+ json.dump(result, f, ensure_ascii=False, indent=2)
139
+ print(json.dumps({k: v for k, v in result.items() if k != "rows"}, ensure_ascii=False, indent=2))
@@ -0,0 +1,123 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """English NER benchmark: DistilBERT CoNLL03 on CoNLL2003 validation."""
4
+ import json
5
+ import sys
6
+
7
+ import torch
8
+ from datasets import load_dataset
9
+ from transformers import AutoModelForTokenClassification, AutoTokenizer
10
+
11
+ MODEL = "elastic/distilbert-base-uncased-finetuned-conll03-english"
12
+ DATASET = "jon-fernandes/conll2003"
13
+ LIMIT = int(sys.argv[1]) if len(sys.argv) > 1 else 200
14
+ OUT = sys.argv[2] if len(sys.argv) > 2 else None
15
+ MAX_LEN = 256
16
+
17
+ torch.set_num_threads(1)
18
+ print("tokenizer", flush=True)
19
+ tok = AutoTokenizer.from_pretrained(MODEL)
20
+ print("model", flush=True)
21
+ model = AutoModelForTokenClassification.from_pretrained(MODEL)
22
+ model.eval()
23
+ id2label = model.config.id2label
24
+ print("data", flush=True)
25
+ ds = load_dataset(DATASET, split="validation")
26
+ print("data ok", len(ds), flush=True)
27
+
28
+
29
+ def decode(text, offsets, ids):
30
+ entities = []
31
+ current = None
32
+ for idx, label_id in enumerate(ids):
33
+ label = id2label[int(label_id)]
34
+ start, end = offsets[idx]
35
+ if start == end:
36
+ continue
37
+ if label == "O":
38
+ if current is not None:
39
+ entities.append(current)
40
+ current = None
41
+ continue
42
+ prefix, _, kind = label.partition("-")
43
+ if prefix == "B":
44
+ if current is not None:
45
+ entities.append(current)
46
+ current = {"text": text[start:end], "label": kind, "start": start, "end": end}
47
+ elif prefix == "I" and current is not None and current["label"] == kind:
48
+ current["text"] += text[start:end]
49
+ current["end"] = end
50
+ else:
51
+ if current is not None:
52
+ entities.append(current)
53
+ current = {"text": text[start:end], "label": kind, "start": start, "end": end}
54
+ if current is not None:
55
+ entities.append(current)
56
+ return entities
57
+
58
+
59
+ def predict(text):
60
+ enc = tok(text, return_tensors="pt", truncation=True, max_length=MAX_LEN, return_offsets_mapping=True)
61
+ offsets = enc.pop("offset_mapping")[0].tolist()
62
+ with torch.no_grad():
63
+ logits = model(**enc).logits[0]
64
+ return decode(text, offsets, logits.argmax(-1).tolist())
65
+
66
+
67
+ def gold_entities(tokens, tags):
68
+ out = []
69
+ current = None
70
+ for token, tag in zip(tokens, tags):
71
+ if tag == "O":
72
+ if current is not None:
73
+ out.append(current)
74
+ current = None
75
+ continue
76
+ prefix, _, kind = tag.partition("-")
77
+ if prefix == "B":
78
+ if current is not None:
79
+ out.append(current)
80
+ current = {"text": token, "label": kind}
81
+ elif prefix == "I" and current is not None:
82
+ current["text"] += " " + token
83
+ else:
84
+ if current is not None:
85
+ out.append(current)
86
+ current = {"text": token, "label": kind}
87
+ if current is not None:
88
+ out.append(current)
89
+ return out
90
+
91
+
92
+ def score(pred, gold):
93
+ pred_keys = {(p["text"].replace(" ", ""), p["label"]) for p in pred}
94
+ gold_keys = {(g["text"].replace(" ", ""), g["label"]) for g in gold}
95
+ tp = len(pred_keys & gold_keys)
96
+ fp = len(pred_keys - gold_keys)
97
+ fn = len(gold_keys - pred_keys)
98
+ precision = 0 if not pred_keys else tp / len(pred_keys)
99
+ recall = 0 if not gold_keys else tp / len(gold_keys)
100
+ f1 = 0 if precision + recall == 0 else 2 * precision * recall / (precision + recall)
101
+ return {"tp": tp, "fp": fp, "fn": fn, "precision": round(precision, 4), "recall": round(recall, 4), "f1": round(f1, 4)}
102
+
103
+
104
+ rows = []
105
+ for i in range(min(LIMIT, len(ds))):
106
+ print("sample", i, flush=True)
107
+ item = ds[i]
108
+ text = " ".join(item["tokens"])
109
+ pred = predict(text)
110
+ gold = gold_entities(item["tokens"], item["ner_tags"])
111
+ rows.append({"text": text, "pred": pred, "gold": gold})
112
+
113
+ result = {
114
+ "model": MODEL,
115
+ "dataset": DATASET,
116
+ "n": len(rows),
117
+ "score": score([p for r in rows for p in r["pred"]], [g for r in rows for g in r["gold"]]),
118
+ "rows": rows,
119
+ }
120
+ if OUT:
121
+ with open(OUT, "w", encoding="utf-8") as f:
122
+ json.dump(result, f, ensure_ascii=False, indent=2)
123
+ print(json.dumps({k: v for k, v in result.items() if k != "rows"}, ensure_ascii=False, indent=2))