@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,342 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * eval-label-rules.mjs — 评估 A 表打标判据(top-1 / top-2 / 空值 / 精度 / 覆盖 / 误标率)。
4
+ *
5
+ * 输入是一个 JSONL,每行:
6
+ * {"turn": 2056, "text": "……这一轮的正文……", "label": "跑团叙事"}
7
+ * 其中 label 是人工定的主话题(可以留空字符串表示"人工认为该判空")。
8
+ *
9
+ * 用法:
10
+ * node bin/eval-label-rules.mjs --input rows.jsonl
11
+ * node bin/eval-label-rules.mjs --input rows.jsonl --baseline-file topic-baseline.json
12
+ * node bin/eval-label-rules.mjs --input rows.jsonl --center-min 0.02 --lead 0.02 --json
13
+ *
14
+ * 选项:
15
+ * --input <file> 必填,JSONL
16
+ * --concepts <file> A 表(默认 包内 templates/concepts.md;找不到则报错)
17
+ * --baseline-file <file> 生产 baseline(支持 {"topic":0.51} 或 {version,window,samples:{topic:[...]}})。
18
+ * 不给则用输入集的 leave-one-out 均值——**仅用于评估**,不是生产语义。
19
+ * --center-min <n> centered 下限(默认 0.02)
20
+ * --lead <n> 领先第二名的下限(默认 0.02)
21
+ * --topic-min-score <n> 绝对下限保险(默认 0.33)
22
+ * --min-score <n> workflow 入选下限(默认 0.25)
23
+ * --topk <n> topic 最多返回几条(默认 2)
24
+ * --min-precision <n> 低于该精度时退出码 1(默认不设)
25
+ * --json 只输出 JSON 摘要
26
+ *
27
+ * 注意:本脚本不读会话原文,也不写任何会话文件;输入里有什么就只算什么。
28
+ */
29
+ import fs from 'node:fs';
30
+ import path from 'node:path';
31
+ import { fileURLToPath } from 'node:url';
32
+
33
+ const here = path.dirname(fileURLToPath(import.meta.url));
34
+ const pkgRoot = path.resolve(here, '..');
35
+
36
+ const argv = process.argv.slice(2);
37
+ const opt = (name, fallback = null) => {
38
+ const i = argv.indexOf(`--${name}`);
39
+ if (i < 0) return fallback;
40
+ const next = argv[i + 1];
41
+ return next === undefined || next.startsWith('--') ? true : next;
42
+ };
43
+ const num = (name, fallback) => {
44
+ const v = opt(name);
45
+ if (v === null || v === true) return fallback;
46
+ const n = Number(v);
47
+ return Number.isFinite(n) ? n : fallback;
48
+ };
49
+
50
+ const inputPath = opt('input');
51
+ if (!inputPath || inputPath === true) {
52
+ console.error('缺 --input <jsonl>');
53
+ process.exit(2);
54
+ }
55
+ const conceptsPath = (() => {
56
+ const explicit = opt('concepts');
57
+ if (explicit && explicit !== true) return explicit;
58
+ const candidate = path.join(pkgRoot, 'templates', 'concepts.md');
59
+ return fs.existsSync(candidate) ? candidate : null;
60
+ })();
61
+ if (!conceptsPath || !fs.existsSync(conceptsPath)) {
62
+ console.error('找不到 A 表:请用 --concepts <concepts.md> 指定');
63
+ process.exit(2);
64
+ }
65
+
66
+ const centerMin = num('center-min', 0.02);
67
+ const lead = num('lead', 0.02);
68
+ const topicMinScore = num('topic-min-score', 0.33);
69
+ const minScore = num('min-score', 0.25);
70
+ const topk = num('topk', 2);
71
+ const minPrecision = num('min-precision', null);
72
+ const asJson = Boolean(opt('json', false));
73
+ const baselineFile = opt('baseline-file');
74
+
75
+ const { createBLabeler } = await import('file://' + path.join(pkgRoot, 'src/memory/bc-label.mjs'));
76
+ const { loadConcepts } = await import('file://' + path.join(pkgRoot, 'src/memory/concepts-file.mjs'));
77
+ const { embedTexts } = await import('file://' + path.join(pkgRoot, 'src/facets/embedding.mjs'));
78
+
79
+ const rows = loadConcepts(conceptsPath);
80
+ if (rows.length === 0) {
81
+ console.error('A 表为空:' + conceptsPath);
82
+ process.exit(2);
83
+ }
84
+ const labels = Object.fromEntries(rows.map((r) => [r.label, r.description]));
85
+ const kinds = Object.fromEntries(rows.map((r) => [r.label, r.kind]));
86
+ const concepts = rows.map((r) => r.label);
87
+ const topics = rows.filter((r) => r.kind === 'topic').map((r) => r.label);
88
+ const workflowNames = rows.filter((r) => r.kind === 'workflow').map((r) => r.label);
89
+
90
+ const samples = fs.readFileSync(inputPath, 'utf8').replace(/^\uFEFF/, '')
91
+ .split(/\r?\n/)
92
+ .filter((line) => line.trim() !== '')
93
+ .map((line, index) => {
94
+ try {
95
+ const row = JSON.parse(line);
96
+ return {
97
+ turn: row.turn ?? index + 1,
98
+ text: String(row.text ?? ''),
99
+ label: String(row.label ?? ''),
100
+ // 给了 workflowLabel 才评 workflow:数组=人工标签([] 表示该判空);不写=不参与
101
+ workflowLabel: Array.isArray(row.workflowLabel)
102
+ ? row.workflowLabel.map((name) => String(name))
103
+ : (typeof row.workflowLabel === 'string'
104
+ ? row.workflowLabel.split(/[\/,,、]/).map((name) => name.trim()).filter(Boolean)
105
+ : undefined),
106
+ };
107
+ } catch (error) {
108
+ console.error(`第 ${index + 1} 行 JSON 解析失败:${error.message}`);
109
+ process.exit(2);
110
+ }
111
+ });
112
+ if (samples.length === 0) {
113
+ console.error('输入为空');
114
+ process.exit(2);
115
+ }
116
+
117
+ const labeler = createBLabeler({ embed: embedTexts, labels, concepts, kinds });
118
+
119
+ // baseline:生产文件优先;否则用输入集 leave-one-out 均值(仅评估用)
120
+ // v2 文件形如 {version:2, topics:{...}, workflows:{...}};v1 只有 samples(当作 topics)
121
+ let baseline = null;
122
+ let workflowBaseline = null;
123
+ let baselineSource = 'leave-one-out';
124
+ if (baselineFile && baselineFile !== true) {
125
+ const raw = JSON.parse(fs.readFileSync(baselineFile, 'utf8').replace(/^\uFEFF/, ''));
126
+ const topicSource = raw?.topics && typeof raw.topics === 'object'
127
+ ? raw.topics
128
+ : (raw?.samples && typeof raw.samples === 'object' ? raw.samples : raw);
129
+ const workflowSource = raw?.workflows && typeof raw.workflows === 'object' ? raw.workflows : null;
130
+
131
+ // 缺失必须进 NaN,不能进 0:0 会被当成有效 baseline(等于该项不居中),缺失项就会通吃
132
+ const buildMap = (source, names) => Object.fromEntries(names.map((name) => {
133
+ const value = source?.[name];
134
+ if (Array.isArray(value)) {
135
+ const nums = value.map((item) => Number(item)).filter((item) => Number.isFinite(item));
136
+ return [name, nums.length > 0 ? nums.reduce((sum, item) => sum + item, 0) / nums.length : Number.NaN];
137
+ }
138
+ if (value === undefined || value === null || value === '') return [name, Number.NaN];
139
+ const numeric = Number(value);
140
+ return [name, Number.isFinite(numeric) ? numeric : Number.NaN];
141
+ }));
142
+
143
+ const fill = (map, names) => {
144
+ const known = Object.values(map).filter((value) => Number.isFinite(value));
145
+ if (known.length === 0) return null;
146
+ const fallback = known.reduce((sum, value) => sum + value, 0) / known.length;
147
+ for (const name of names) {
148
+ if (!Number.isFinite(map[name])) map[name] = fallback;
149
+ }
150
+ return map;
151
+ };
152
+
153
+ baseline = fill(buildMap(topicSource, topics), topics);
154
+ if (workflowSource) {
155
+ workflowBaseline = fill(buildMap(workflowSource, workflowNames), workflowNames);
156
+ }
157
+ baselineSource = baselineFile;
158
+ if (!baseline && !workflowBaseline) {
159
+ // 一个样本都没有:按 raw bootstrap,不居中
160
+ baselineSource = 'raw-bootstrap';
161
+ }
162
+ }
163
+
164
+ // 先算一遍 raw,够用来做 leave-one-out 均值
165
+ const results = [];
166
+ for (const sample of samples) {
167
+ const res = await labeler.labelChunked(sample.text, {
168
+ topK: 3,
169
+ minScore,
170
+ topicMinScore,
171
+ topicCenterMin: centerMin,
172
+ topicLead: lead,
173
+ topicTopK: topk,
174
+ topicBaseline: baseline ?? undefined,
175
+ workflowBaseline: workflowBaseline ?? undefined,
176
+ });
177
+ results.push({ sample, res });
178
+ }
179
+
180
+ let effectiveBaseline = baseline;
181
+ if (!baseline) {
182
+ const sums = Object.fromEntries(topics.map((t) => [t, 0]));
183
+ let count = 0;
184
+ for (const { res } of results) {
185
+ const scores = res.topicScores ?? {};
186
+ const keys = topics.filter((t) => Number.isFinite(Number(scores[t])));
187
+ if (keys.length === 0) continue;
188
+ for (const t of keys) sums[t] += Number(scores[t]);
189
+ count += 1;
190
+ }
191
+ if (count > 0) {
192
+ effectiveBaseline = Object.fromEntries(topics.map((t) => [t, sums[t] / count]));
193
+ // leave-one-out:重新打一遍
194
+ for (const item of results) {
195
+ const scores = item.res.topicScores ?? {};
196
+ const loo = Object.fromEntries(topics.map((t) => {
197
+ const own = Number(scores[t]);
198
+ if (!Number.isFinite(own) || count <= 1) return [t, effectiveBaseline[t] ?? 0];
199
+ return [t, (sums[t] - own) / (count - 1)];
200
+ }));
201
+ item.res = await labeler.labelChunked(item.sample.text, {
202
+ topK: 3,
203
+ minScore,
204
+ topicMinScore,
205
+ topicCenterMin: centerMin,
206
+ topicLead: lead,
207
+ topicTopK: topk,
208
+ topicBaseline: loo,
209
+ workflowBaseline: workflowBaseline ?? undefined,
210
+ });
211
+ }
212
+ }
213
+ }
214
+
215
+ let nonEmpty = 0;
216
+ let empty = 0;
217
+ let correct = 0;
218
+ let wrong = 0;
219
+ let top2Hit = 0;
220
+ let labelled = 0;
221
+ const mistakes = [];
222
+ const perRow = [];
223
+
224
+ // workflow 侧统计(只统计给了 workflowLabel 的行;label=[] 表示"人工认为该判空")
225
+ let wfLabelled = 0;
226
+ let wfNonEmpty = 0;
227
+ let wfExact = 0;
228
+ let wfAnyHit = 0;
229
+ let wfMissing = 0;
230
+ let wfSpurious = 0;
231
+ let wfPrecisionSum = 0;
232
+ let wfRecallSum = 0;
233
+ let wfTop2Hit = 0;
234
+ const wfMistakes = [];
235
+
236
+ for (const { sample, res } of results) {
237
+ const picked = (res.topic ?? []).map((item) => item.concept);
238
+ const got = picked[0] ?? null;
239
+ const want = sample.label.trim();
240
+ const hasHuman = want !== '';
241
+ if (hasHuman) labelled += 1;
242
+ if (got === null) empty += 1;
243
+ else nonEmpty += 1;
244
+ const ok = got !== null && hasHuman && got === want;
245
+ const inTop2 = hasHuman && picked.slice(0, 2).includes(want);
246
+ if (ok) correct += 1;
247
+ if (got !== null && hasHuman && !ok) { wrong += 1; mistakes.push({ turn: sample.turn, got, want }); }
248
+ if (inTop2) top2Hit += 1;
249
+
250
+ const wfGot = (res.workflow ?? []).map((item) => item.concept);
251
+ const wfWant = sample.workflowLabel; // undefined = 未标注;[] = 人工认为该空
252
+ const wfHasHuman = Array.isArray(wfWant);
253
+ if (wfHasHuman) {
254
+ wfLabelled += 1;
255
+ if (wfGot.length > 0) wfNonEmpty += 1;
256
+ const inter = wfGot.filter((name) => wfWant.includes(name));
257
+ const sameSet = wfGot.length === wfWant.length && inter.length === wfGot.length;
258
+ if (sameSet) wfExact += 1;
259
+ if (inter.length > 0) wfAnyHit += 1;
260
+ if (wfWant.length > 0 && wfGot.length === 0) wfMissing += 1;
261
+ if (wfWant.length === 0 && wfGot.length > 0) wfSpurious += 1;
262
+ wfPrecisionSum += wfGot.length > 0 ? inter.length / wfGot.length : (wfWant.length === 0 ? 1 : 0);
263
+ wfRecallSum += wfWant.length > 0 ? inter.length / wfWant.length : (wfGot.length === 0 ? 1 : 0);
264
+ if (wfGot.slice(0, 2).some((name) => wfWant.includes(name))) wfTop2Hit += 1;
265
+ if (!sameSet) wfMistakes.push({ turn: sample.turn, got: wfGot, want: wfWant });
266
+ }
267
+
268
+ perRow.push({ turn: sample.turn, got, want, ok, top: picked, wfGot, wfWant: wfWant ?? null });
269
+ }
270
+
271
+ const total = results.length;
272
+ const precision = nonEmpty > 0 ? correct / nonEmpty : 0;
273
+ const coverage = total > 0 ? nonEmpty / total : 0;
274
+ const mislabelRate = total > 0 ? wrong / total : 0;
275
+ const top2Rate = labelled > 0 ? top2Hit / labelled : 0;
276
+
277
+ const wfPrecision = wfLabelled > 0 ? wfPrecisionSum / wfLabelled : 0;
278
+ const wfRecall = wfLabelled > 0 ? wfRecallSum / wfLabelled : 0;
279
+ const wfExactRate = wfLabelled > 0 ? wfExact / wfLabelled : 0;
280
+ const wfAnyRate = wfLabelled > 0 ? wfAnyHit / wfLabelled : 0;
281
+ const wfTop2Rate = wfLabelled > 0 ? wfTop2Hit / wfLabelled : 0;
282
+
283
+ const summary = {
284
+ rows: total,
285
+ labelled,
286
+ nonEmpty,
287
+ empty,
288
+ correct,
289
+ wrong,
290
+ precision: Number(precision.toFixed(3)),
291
+ coverage: Number(coverage.toFixed(3)),
292
+ mislabelRate: Number(mislabelRate.toFixed(3)),
293
+ top2Rate: Number(top2Rate.toFixed(3)),
294
+ centerMin,
295
+ lead,
296
+ topicMinScore,
297
+ baselineSource,
298
+ mistakes,
299
+ workflow: {
300
+ labelled: wfLabelled,
301
+ nonEmpty: wfNonEmpty,
302
+ exact: wfExact,
303
+ anyHit: wfAnyHit,
304
+ missing: wfMissing,
305
+ spurious: wfSpurious,
306
+ precision: Number(wfPrecision.toFixed(3)),
307
+ recall: Number(wfRecall.toFixed(3)),
308
+ exactRate: Number(wfExactRate.toFixed(3)),
309
+ anyRate: Number(wfAnyRate.toFixed(3)),
310
+ top2Rate: Number(wfTop2Rate.toFixed(3)),
311
+ mistakes: wfMistakes,
312
+ },
313
+ };
314
+
315
+ if (asJson) {
316
+ console.log(JSON.stringify({ summary, rows: perRow }, null, 2));
317
+ } else {
318
+ console.log(` A 表 ${rows.length} 条(topic ${topics.length} / workflow ${rows.length - topics.length})· 输入 ${total} 行 · 有标签 ${labelled} 行`);
319
+ console.log(` 判据 centered≥${centerMin} 且 lead≥${lead} · 绝对下限 ${topicMinScore} · baseline ${baselineSource}`);
320
+ console.log('');
321
+ console.log(` 非空 ${nonEmpty}/${total} · 命中 ${correct} · 误标 ${wrong} · 空 ${empty}`);
322
+ console.log(` 精度 ${(precision * 100).toFixed(0)}% · 覆盖率 ${(coverage * 100).toFixed(0)}% · 误标率 ${(mislabelRate * 100).toFixed(0)}% · top-2 ${(top2Rate * 100).toFixed(0)}%`);
323
+ if (mistakes.length > 0) {
324
+ console.log('');
325
+ console.log(' 误标:');
326
+ for (const m of mistakes) console.log(` turn ${m.turn}: 机器 ${m.got} / 人工 ${m.want}`);
327
+ }
328
+ if (wfLabelled > 0) {
329
+ console.log('');
330
+ console.log(` —— workflow(人工标注 ${wfLabelled} 行)——`);
331
+ console.log(` 机器非空 ${wfNonEmpty} · 集合完全一致 ${wfExact} · 至少一条命中 ${wfAnyHit} · 漏(人工有机器空)${wfMissing} · 多(人工空机器有)${wfSpurious}`);
332
+ console.log(` 平均精度 ${(wfPrecision * 100).toFixed(0)}% · 平均召回 ${(wfRecall * 100).toFixed(0)}% · 完全一致率 ${(wfExactRate * 100).toFixed(0)}% · 至少一条命中率 ${(wfAnyRate * 100).toFixed(0)}% · top-2 ${(wfTop2Rate * 100).toFixed(0)}%`);
333
+ if (wfMistakes.length > 0) {
334
+ console.log(' 不一致:');
335
+ for (const m of wfMistakes) console.log(` turn ${m.turn}: 机器 [${m.got.join('/')}] / 人工 [${m.want.join('/')}]`);
336
+ }
337
+ }
338
+ }
339
+
340
+ if (minPrecision !== null && precision < minPrecision) {
341
+ process.exitCode = 1;
342
+ }
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env python3
2
+ """Export one DSH session's human prompts as turns.jsonl for rebuild/relabel."""
3
+ import argparse
4
+ import io
5
+ import json
6
+ import sys
7
+
8
+ import zstandard
9
+
10
+
11
+ def text_of(message):
12
+ parts = []
13
+ for block in message.get('content', []):
14
+ if isinstance(block, dict) and block.get('type') == 'text' and isinstance(block.get('text'), str):
15
+ parts.append(block['text'])
16
+ return '\n'.join(parts).strip()
17
+
18
+
19
+ def main():
20
+ parser = argparse.ArgumentParser()
21
+ parser.add_argument('--session-file', required=True)
22
+ parser.add_argument('--out', required=True)
23
+ parser.add_argument('--session-id', default=None)
24
+ args = parser.parse_args()
25
+ turns = {}
26
+ current = None
27
+ with open(args.session_file, 'rb') as fh:
28
+ with zstandard.ZstdDecompressor().stream_reader(fh) as reader:
29
+ text = io.TextIOWrapper(reader, encoding='utf-8')
30
+ for line in text:
31
+ line = line.strip()
32
+ if not line:
33
+ continue
34
+ try:
35
+ event = json.loads(line)
36
+ except json.JSONDecodeError:
37
+ continue
38
+ typ = event.get('type')
39
+ if typ == 'session':
40
+ if args.session_id and event.get('id') != args.session_id:
41
+ raise SystemExit(f"session id mismatch: {event.get('id')} != {args.session_id}")
42
+ continue
43
+ if typ == 'turn/start':
44
+ turn = event.get('data', {}).get('turn')
45
+ current = turn if isinstance(turn, int) else None
46
+ continue
47
+ if typ != 'user/message' or current is None:
48
+ continue
49
+ data = event.get('data', {})
50
+ if data.get('source', {}).get('kind') != 'user':
51
+ continue
52
+ body = text_of(data)
53
+ if body and current not in turns:
54
+ turns[current] = body
55
+ with open(args.out, 'w', encoding='utf-8', newline='\n') as out:
56
+ for turn in sorted(turns):
57
+ out.write(json.dumps({'turn': turn, 'text': turns[turn]}, ensure_ascii=False) + '\n')
58
+ print(json.dumps({
59
+ 'out': args.out,
60
+ 'turns': len(turns),
61
+ 'firstTurn': min(turns) if turns else None,
62
+ 'lastTurn': max(turns) if turns else None,
63
+ }, ensure_ascii=False, indent=2))
64
+ return 0
65
+
66
+
67
+ if __name__ == '__main__':
68
+ sys.exit(main())
@@ -0,0 +1,14 @@
1
+ import fs from 'node:fs';
2
+ import { createExtractionProviderFromEnv } from '../src/memory/extraction-provider.mjs';
3
+ import { createExtractionPreStep } from '../src/memory/extraction-pre-step.mjs';
4
+
5
+ const config = 'C:/Users/35794/Documents/DeepSeek\'s Sandbox/coopanion-continuity/backups/continuity-tags-20261031/durable-memory/extraction-live.json';
6
+ const text = '我要的是全自动化。这是你之前自己说的:这一套可以让LLM什么都不额外做,就正常聊天,然后自动系统在用户说到“我们接下来去 *** 吧” (比如跑团),时,就会自动给LLM补充一个“【可回忆】灯塔 / 钥匙 ---> turn49”这类的提示词。这个提示词就是自动算出facet,自动算出高置信度区间,回填turn49,然后LLM读到这个直接去load 49那里对应的seq就能找回原始上下文,然后它就直接想起来了。';
7
+ const provider = createExtractionProviderFromEnv({ DSH_EXTRACTION_MODE: 'model', DSH_EXTRACTION_CONFIG: config });
8
+ const result = await provider.extract({ text });
9
+ const handler = createExtractionPreStep({ provider, mode: 'model' });
10
+ const decision = { kind: 'accept', messages: [{ role: 'user', content: [{ type: 'text', text }] }] };
11
+ const injected = await handler({ step: 1 }, async () => decision);
12
+ fs.writeFileSync('C:/Users/35794/Documents/DeepSeek\'s Sandbox/coopanion-continuity/backups/continuity-tags-20261031/durable-memory/build-a/EXTRACT-PARAGRAPH.json', JSON.stringify({ result, injected }, null, 2));
13
+ console.log(JSON.stringify({ ok: result.ok, latencyMs: result.latencyMs, data: result.data, injected: injected.messages.at(-1)?.content?.[0]?.text }, null, 2));
14
+ provider.close?.();
@@ -0,0 +1,28 @@
1
+ import path from 'node:path';
2
+ import { createCommandProvider } from '../src/memory/extraction-provider.mjs';
3
+ import { createCompositeProvider } from '../src/memory/extraction-composite.mjs';
4
+ import { PreTurnController } from '../src/memory/pre-turn.mjs';
5
+
6
+ const cwd = process.cwd();
7
+ const python311 = path.join(cwd, '.venvs', 'extraction311', 'Scripts', 'python.exe');
8
+ const python38 = path.join(cwd, '.venvs', 'srl38', 'Scripts', 'python.exe');
9
+ const python313 = path.join(cwd, '.venvs', 'relik313', 'Scripts', 'python.exe');
10
+ const globalHf = 'C:\\Users\\35794\\.cache\\huggingface';
11
+ const workHf = path.join(cwd, '..', 'backups', 'continuity-tags-20261031', 'durable-memory', 'hf-cache');
12
+ const nerEnv = { ...process.env, HF_HOME: globalHf, HF_HUB_OFFLINE: '1' };
13
+ const elEnv = { ...process.env, HF_HOME: workHf, HF_MODULES_CACHE: path.join(workHf, 'modules'), HF_HUB_OFFLINE: '1' };
14
+ const provider = createCompositeProvider({
15
+ ner: createCommandProvider({ command: python311, args: [path.join(cwd, 'bin', 'extraction_service.py')], timeoutMs: 120000, env: nerEnv }),
16
+ coref: createCommandProvider({ command: python313, args: [path.join(cwd, 'bin', 'coref_service.py')], timeoutMs: 120000 }),
17
+ srl: createCommandProvider({ command: python38, args: [path.join(cwd, 'bin', 'srl_service.py')], timeoutMs: 180000 }),
18
+ el: createCommandProvider({ command: python311, args: [path.join(cwd, 'bin', 'relik_service.py')], timeoutMs: 180000, env: elEnv }),
19
+ });
20
+ const controller = new PreTurnController({ records: [] });
21
+ const result = await controller.stepAsync({
22
+ extractionProvider: provider,
23
+ text: 'Michael Jordan kicked the ball into the net.',
24
+ facets: { activity: '对话', topic: '体育' },
25
+ confidence: 0.8,
26
+ nowTurn: 1,
27
+ });
28
+ console.log(JSON.stringify({ ok: result.ok, slots: result.slots, predicate: result.predicate, links: result.entityLinks, partial: result.extraction?.data?.partial }, null, 2));
@@ -0,0 +1,58 @@
1
+ import path from 'node:path';
2
+ import { createPersistentCommandProvider } from '../src/memory/extraction-provider.mjs';
3
+
4
+ const cwd = process.cwd();
5
+ const python311 = path.join(cwd, '.venvs', 'extraction311', 'Scripts', 'python.exe');
6
+ const python38 = path.join(cwd, '.venvs', 'srl38', 'Scripts', 'python.exe');
7
+ const python313 = path.join(cwd, '.venvs', 'relik313', 'Scripts', 'python.exe');
8
+ const globalHf = 'C:/Users/35794/.cache/huggingface';
9
+ const workHf = path.join(cwd, '..', 'backups', 'continuity-tags-20261031', 'durable-memory', 'hf-cache');
10
+ const services = {
11
+ ner: {
12
+ provider: createPersistentCommandProvider({
13
+ command: python311,
14
+ args: [path.join(cwd, 'bin', 'extraction_service.py'), '--serve'],
15
+ timeoutMs: 120000,
16
+ env: { ...process.env, HF_HOME: globalHf, HF_HUB_OFFLINE: '1' },
17
+ }),
18
+ text: '彭小军认为,国内银行现在走的是台湾的发卡模式',
19
+ },
20
+ coref: {
21
+ provider: createPersistentCommandProvider({
22
+ command: python313,
23
+ args: [path.join(cwd, 'bin', 'coref_service.py'), '--serve'],
24
+ timeoutMs: 120000,
25
+ }),
26
+ text: 'Coo went to the lamp room. She looked at the coat.',
27
+ },
28
+ srl: {
29
+ provider: createPersistentCommandProvider({
30
+ command: python38,
31
+ args: [path.join(cwd, 'bin', 'srl_service.py'), '--serve'],
32
+ timeoutMs: 180000,
33
+ }),
34
+ text: 'The player kicked the ball into the net.',
35
+ },
36
+ el: {
37
+ provider: createPersistentCommandProvider({
38
+ command: python311,
39
+ args: [path.join(cwd, 'bin', 'relik_service.py'), '--serve'],
40
+ timeoutMs: 180000,
41
+ env: { ...process.env, HF_HOME: workHf, HF_MODULES_CACHE: path.join(workHf, 'modules'), HF_HUB_OFFLINE: '1' },
42
+ }),
43
+ text: 'Michael Jordan played in the NBA.',
44
+ candidates: ['Michael Jordan', 'National Basketball Association'],
45
+ },
46
+ };
47
+
48
+ const result = {};
49
+ for (const [name, { provider, text, candidates }] of Object.entries(services)) {
50
+ const latencies = [];
51
+ for (let index = 0; index < 3; index += 1) {
52
+ const response = await provider.extract(candidates ? { text, candidates } : { text });
53
+ latencies.push({ ok: response.ok, latencyMs: response.latencyMs, reason: response.reason ?? null });
54
+ }
55
+ result[name] = { latencies, coldMs: latencies[0]?.latencyMs ?? null, warmMs: latencies.slice(1).map((item) => item.latencyMs) };
56
+ provider.close();
57
+ }
58
+ console.log(JSON.stringify(result, null, 2));
@@ -0,0 +1,28 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { createCommandProvider } from '../src/memory/extraction-provider.mjs';
4
+ import { PreTurnController } from '../src/memory/pre-turn.mjs';
5
+
6
+ const cwd = process.cwd();
7
+ const base = path.join(cwd, '..', 'backups', 'continuity-tags-20261031', 'durable-memory', 'build-a');
8
+ const provider = createCommandProvider({
9
+ command: path.join(cwd, '.venvs', 'extraction311', 'Scripts', 'python.exe'),
10
+ args: [path.join(cwd, 'bin', 'extraction_service.py')],
11
+ timeoutMs: 60000,
12
+ });
13
+ const controller = new PreTurnController({ records: [] });
14
+ const lines = fs.readFileSync(path.join(cwd, '..', 'backups', 'continuity-tags-20261031', 'online400', 'user-only-messages.jsonl'), 'utf8').split(/\r?\n/).filter(Boolean).slice(0, 3);
15
+ const rows = [];
16
+ for (const line of lines) {
17
+ const item = JSON.parse(line);
18
+ const result = await controller.stepAsync({
19
+ extractionProvider: provider,
20
+ text: item.text,
21
+ facets: { activity: '对话', topic: '记忆' },
22
+ confidence: 0.8,
23
+ nowTurn: 1,
24
+ });
25
+ rows.push({ text: item.text, extraction: result.extraction, gate: result.gate?.action, slots: result.slots });
26
+ }
27
+ fs.writeFileSync(path.join(base, 'USER-SERVICE-SMOKE.json'), JSON.stringify({ rows }, null, 2));
28
+ console.log(JSON.stringify({ n: rows.length, ok: rows.filter((row) => row.extraction?.ok).length }, null, 2));