@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,381 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* relabel-dry-run.mjs — 历史重打的**只读演练**:只输出"如果重打,会改哪些字段"。
|
|
4
|
+
*
|
|
5
|
+
* 不写 bc-index.jsonl、不写 baseline、不写 raw。想真正写盘必须另有写盘脚本,且先备份。
|
|
6
|
+
*
|
|
7
|
+
* 用法:
|
|
8
|
+
* node bin/relabel-dry-run.mjs --index <bc-index.jsonl> --raw <session.v4.jsonl.zstd>
|
|
9
|
+
* [--concepts <concepts.md>] [--baseline-file <file>]
|
|
10
|
+
* [--center-min 0.02] [--lead 0.02] [--limit 500] [--json]
|
|
11
|
+
*
|
|
12
|
+
* 选项:
|
|
13
|
+
* --index <file> 必填,bc-index.jsonl
|
|
14
|
+
* --raw <file> 必填,raw 会话(.jsonl 或 .jsonl.zstd,帧式也支持)
|
|
15
|
+
* --concepts <file> A 表(默认 <pkg>/templates/concepts.md)
|
|
16
|
+
* --baseline-file <file> 显式 baseline({topic:mean} 或 {version,window,samples})。
|
|
17
|
+
* 不给则用"被对齐上的那些轮的 leave-one-out 均值"——**只用于演练,
|
|
18
|
+
* 绝不能当生产 baseline**;输出里会标明来源。
|
|
19
|
+
* --center-min / --lead / --topic-min-score / --min-score / --chunk-chars / --max-chunks
|
|
20
|
+
* --limit <n> 只看前 n 条 diff(默认 60)
|
|
21
|
+
* --json 只输出 JSON
|
|
22
|
+
*
|
|
23
|
+
* 对齐规则:只用 turn 对齐 index 与 raw;raw 里找不到该轮文本 → 记 unresolved,不猜。
|
|
24
|
+
*/
|
|
25
|
+
import fs from 'node:fs';
|
|
26
|
+
import path from 'node:path';
|
|
27
|
+
import zlib from 'node:zlib';
|
|
28
|
+
import { fileURLToPath } from 'node:url';
|
|
29
|
+
|
|
30
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
31
|
+
const pkgRoot = path.resolve(here, '..');
|
|
32
|
+
|
|
33
|
+
const argv = process.argv.slice(2);
|
|
34
|
+
const opt = (name, fallback = null) => {
|
|
35
|
+
const i = argv.indexOf(`--${name}`);
|
|
36
|
+
if (i < 0) return fallback;
|
|
37
|
+
const next = argv[i + 1];
|
|
38
|
+
return next === undefined || next.startsWith('--') ? true : next;
|
|
39
|
+
};
|
|
40
|
+
const num = (name, fallback) => {
|
|
41
|
+
const v = opt(name);
|
|
42
|
+
if (v === null || v === true) return fallback;
|
|
43
|
+
const n = Number(v);
|
|
44
|
+
return Number.isFinite(n) ? n : fallback;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const indexFile = opt('index');
|
|
48
|
+
const rawFile = opt('raw');
|
|
49
|
+
if (!indexFile || indexFile === true || !rawFile || rawFile === true) {
|
|
50
|
+
console.error('用法:node bin/relabel-dry-run.mjs --index <bc-index.jsonl> --raw <session.jsonl[.zstd]>');
|
|
51
|
+
process.exit(2);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const conceptsPath = (() => {
|
|
55
|
+
const explicit = opt('concepts');
|
|
56
|
+
if (explicit && explicit !== true) return explicit;
|
|
57
|
+
const candidate = path.join(pkgRoot, 'templates', 'concepts.md');
|
|
58
|
+
return fs.existsSync(candidate) ? candidate : null;
|
|
59
|
+
})();
|
|
60
|
+
if (!conceptsPath || !fs.existsSync(conceptsPath)) {
|
|
61
|
+
console.error('找不到 A 表:请用 --concepts 指定');
|
|
62
|
+
process.exit(2);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const centerMin = num('center-min', 0.02);
|
|
66
|
+
const lead = num('lead', 0.02);
|
|
67
|
+
const topicMinScore = num('topic-min-score', 0.33);
|
|
68
|
+
const minScore = num('min-score', 0.25);
|
|
69
|
+
const chunkChars = num('chunk-chars', 600);
|
|
70
|
+
const maxChunks = num('max-chunks', 12);
|
|
71
|
+
const limit = num('limit', 60);
|
|
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
|
+
const { eventText } = await import('file://' + path.join(pkgRoot, 'src/session-log.mjs'));
|
|
79
|
+
|
|
80
|
+
const INJECTED = /^\[(?:抽取|工作线|标签|规则|习惯|技能|归位|候选|存\?|闸|回正|学习|追忆|记忆|continuity:[a-z-]+)\]/;
|
|
81
|
+
const MACHINE = /^(?:Current runtime context|Current working directory|Current DSH file policy|Approval prompts|Time sampled)/;
|
|
82
|
+
|
|
83
|
+
const conceptRows = loadConcepts(conceptsPath);
|
|
84
|
+
const labels = Object.fromEntries(conceptRows.map((r) => [r.label, r.description]));
|
|
85
|
+
const kinds = Object.fromEntries(conceptRows.map((r) => [r.label, r.kind]));
|
|
86
|
+
const concepts = conceptRows.map((r) => r.label);
|
|
87
|
+
const topics = conceptRows.filter((r) => r.kind === 'topic').map((r) => r.label);
|
|
88
|
+
const workflowNames = conceptRows.filter((r) => r.kind === 'workflow').map((r) => r.label);
|
|
89
|
+
|
|
90
|
+
// ---------- raw 读取:支持纯 jsonl 与"一串 zstd 帧" ----------
|
|
91
|
+
function readRawLines(file) {
|
|
92
|
+
const lines = [];
|
|
93
|
+
if (/\.zstd$/i.test(file)) {
|
|
94
|
+
const buf = fs.readFileSync(file);
|
|
95
|
+
const MAGIC = Buffer.from([0x28, 0xb5, 0x2f, 0xfd]);
|
|
96
|
+
const starts = [];
|
|
97
|
+
for (let i = 0; ; ) {
|
|
98
|
+
const k = buf.indexOf(MAGIC, i);
|
|
99
|
+
if (k < 0) break;
|
|
100
|
+
starts.push(k);
|
|
101
|
+
i = k + 4;
|
|
102
|
+
}
|
|
103
|
+
for (const start of starts) {
|
|
104
|
+
let out;
|
|
105
|
+
try { out = zlib.zstdDecompressSync(buf.subarray(start)); } catch { continue; }
|
|
106
|
+
for (const line of out.toString('utf8').split(/\r?\n/)) if (line) lines.push(line);
|
|
107
|
+
}
|
|
108
|
+
return lines;
|
|
109
|
+
}
|
|
110
|
+
return fs.readFileSync(file, 'utf8').split(/\r?\n/).filter(Boolean);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const rawLines = readRawLines(rawFile);
|
|
114
|
+
const events = [];
|
|
115
|
+
const seenSeq = new Set();
|
|
116
|
+
for (const line of rawLines) {
|
|
117
|
+
let event;
|
|
118
|
+
try { event = JSON.parse(line.replace(/^\uFEFF/, '')); } catch { continue; }
|
|
119
|
+
const seq = Number(event?.seq);
|
|
120
|
+
if (Number.isInteger(seq)) {
|
|
121
|
+
if (seenSeq.has(seq)) continue;
|
|
122
|
+
seenSeq.add(seq);
|
|
123
|
+
}
|
|
124
|
+
events.push(event);
|
|
125
|
+
}
|
|
126
|
+
events.sort((a, b) => Number(a.seq) - Number(b.seq));
|
|
127
|
+
|
|
128
|
+
// ---------- 与 updater 相同的 turnsFromEvents ----------
|
|
129
|
+
const markers = events
|
|
130
|
+
.map((event) => ({ seq: Number(event?.seq), turn: Number(event?.data?.turn) }))
|
|
131
|
+
.filter((item) => Number.isInteger(item.seq) && Number.isInteger(item.turn))
|
|
132
|
+
.sort((a, b) => a.seq - b.seq);
|
|
133
|
+
const turnAt = (seq) => {
|
|
134
|
+
let found = null;
|
|
135
|
+
for (const marker of markers) {
|
|
136
|
+
if (marker.seq > seq) break;
|
|
137
|
+
found = marker.turn;
|
|
138
|
+
}
|
|
139
|
+
if (found !== null) return found;
|
|
140
|
+
return markers.find((marker) => marker.seq >= seq)?.turn ?? null;
|
|
141
|
+
};
|
|
142
|
+
const isHuman = (event) => {
|
|
143
|
+
const source = event?.data?.source ?? event?.source;
|
|
144
|
+
if (source && typeof source.kind === 'string' && source.kind !== 'user') return false;
|
|
145
|
+
if (source && source.peer === true) return false;
|
|
146
|
+
const text = eventText(event, { includeReasoning: false }).trim();
|
|
147
|
+
return Boolean(text) && !INJECTED.test(text) && !MACHINE.test(text);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const turns = new Map();
|
|
151
|
+
for (const event of events) {
|
|
152
|
+
const type = event?.type;
|
|
153
|
+
if (type !== 'user/message' && type !== 'assistant/message') continue;
|
|
154
|
+
if (type === 'user/message' && !isHuman(event)) continue;
|
|
155
|
+
const text = eventText(event, { includeReasoning: false }).trim();
|
|
156
|
+
if (!text || INJECTED.test(text) || MACHINE.test(text)) continue;
|
|
157
|
+
const seq = Number(event?.seq);
|
|
158
|
+
const turn = Number.isInteger(Number(event?.data?.turn)) ? Number(event.data.turn) : turnAt(seq);
|
|
159
|
+
if (!Number.isInteger(turn)) continue;
|
|
160
|
+
const item = turns.get(turn) ?? { turn, userTexts: [], assistantTexts: [] };
|
|
161
|
+
if (type === 'user/message') item.userTexts.push(text);
|
|
162
|
+
else item.assistantTexts.push(text);
|
|
163
|
+
turns.set(turn, item);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ---------- index 读取 ----------
|
|
167
|
+
const indexRows = fs.readFileSync(indexFile, 'utf8')
|
|
168
|
+
.replace(/^\uFEFF/, '')
|
|
169
|
+
.split(/\r?\n/)
|
|
170
|
+
.filter((line) => line.trim() !== '')
|
|
171
|
+
.map((line) => JSON.parse(line));
|
|
172
|
+
|
|
173
|
+
const labeler = createBLabeler({ embed: embedTexts, labels, concepts, kinds });
|
|
174
|
+
|
|
175
|
+
const topicOfRecord = (record) => {
|
|
176
|
+
if (Array.isArray(record.genericTopic) && record.genericTopic.length > 0) return record.genericTopic[0];
|
|
177
|
+
const generic = Array.isArray(record.generic) ? record.generic : [];
|
|
178
|
+
return generic.find((name) => kinds[name] === 'topic') ?? null;
|
|
179
|
+
};
|
|
180
|
+
const workflowsOfRecord = (record) => {
|
|
181
|
+
if (Array.isArray(record.genericWorkflow)) return record.genericWorkflow;
|
|
182
|
+
const generic = Array.isArray(record.generic) ? record.generic : [];
|
|
183
|
+
return generic.filter((name) => kinds[name] === 'workflow');
|
|
184
|
+
};
|
|
185
|
+
const scoreOfRecord = (record, concept) => {
|
|
186
|
+
const scores = record.conceptScores ?? {};
|
|
187
|
+
const value = Number(scores[concept]);
|
|
188
|
+
return Number.isFinite(value) ? value : null;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// ---------- baseline ----------
|
|
192
|
+
// v2 文件形如 {version:2, topics:{...}, workflows:{...}};v1 只有 samples(当作 topics)
|
|
193
|
+
let explicitBaseline = null;
|
|
194
|
+
let workflowBaseline = null;
|
|
195
|
+
let baselineSource = 'leave-one-out(演练用)';
|
|
196
|
+
if (baselineFile && baselineFile !== true) {
|
|
197
|
+
const raw = JSON.parse(fs.readFileSync(baselineFile, 'utf8').replace(/^\uFEFF/, ''));
|
|
198
|
+
const topicSource = raw?.topics && typeof raw.topics === 'object'
|
|
199
|
+
? raw.topics
|
|
200
|
+
: (raw?.samples && typeof raw.samples === 'object' ? raw.samples : raw);
|
|
201
|
+
const workflowSource = raw?.workflows && typeof raw.workflows === 'object' ? raw.workflows : null;
|
|
202
|
+
|
|
203
|
+
const buildMap = (source, names) => Object.fromEntries(names.map((name) => {
|
|
204
|
+
const value = source?.[name];
|
|
205
|
+
if (Array.isArray(value)) {
|
|
206
|
+
const nums = value.map((x) => Number(x)).filter((x) => Number.isFinite(x));
|
|
207
|
+
return [name, nums.length > 0 ? nums.reduce((a, b) => a + b, 0) / nums.length : Number.NaN];
|
|
208
|
+
}
|
|
209
|
+
if (value === undefined || value === null || value === '') return [name, Number.NaN];
|
|
210
|
+
const n = Number(value);
|
|
211
|
+
return [name, Number.isFinite(n) ? n : Number.NaN];
|
|
212
|
+
}));
|
|
213
|
+
|
|
214
|
+
const fill = (map, names) => {
|
|
215
|
+
const known = Object.values(map).filter((v) => Number.isFinite(v));
|
|
216
|
+
if (known.length === 0) return null;
|
|
217
|
+
const fallback = known.reduce((a, b) => a + b, 0) / known.length;
|
|
218
|
+
for (const name of names) if (!Number.isFinite(map[name])) map[name] = fallback;
|
|
219
|
+
return map;
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
explicitBaseline = fill(buildMap(topicSource, topics), topics);
|
|
223
|
+
if (workflowSource) workflowBaseline = fill(buildMap(workflowSource, workflowNames), workflowNames);
|
|
224
|
+
|
|
225
|
+
if (!explicitBaseline && !workflowBaseline) {
|
|
226
|
+
baselineSource = 'raw-bootstrap(文件无样本)';
|
|
227
|
+
} else if (explicitBaseline) {
|
|
228
|
+
baselineSource = `file:${baselineFile}`;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ---------- 逐轮重打(只读) ----------
|
|
233
|
+
const evaluated = [];
|
|
234
|
+
for (const record of indexRows) {
|
|
235
|
+
const turn = Number(record.turn);
|
|
236
|
+
if (!Number.isInteger(turn)) continue;
|
|
237
|
+
const entry = turns.get(turn);
|
|
238
|
+
if (!entry) {
|
|
239
|
+
evaluated.push({ record, turn, unresolved: true });
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
const text = [...entry.userTexts, ...entry.assistantTexts].join('\n');
|
|
243
|
+
evaluated.push({ record, turn, text, unresolved: false });
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// leave-one-out baseline 需要用每轮 raw 分;没给文件时先粗算一遍
|
|
247
|
+
let looBaseline = null;
|
|
248
|
+
if (!explicitBaseline) {
|
|
249
|
+
const sums = Object.fromEntries(topics.map((t) => [t, 0]));
|
|
250
|
+
let count = 0;
|
|
251
|
+
for (const item of evaluated) {
|
|
252
|
+
if (item.unresolved) continue;
|
|
253
|
+
const res = await labeler.labelChunked(item.text, {
|
|
254
|
+
topK: 3, minScore, topicMinScore, topicCenterMin: -Infinity, topicLead: -Infinity,
|
|
255
|
+
chunkChars, maxChunks, topicTopK: 1,
|
|
256
|
+
});
|
|
257
|
+
const scores = res.topicScores ?? {};
|
|
258
|
+
const keys = topics.filter((t) => Number.isFinite(Number(scores[t])));
|
|
259
|
+
if (keys.length === 0) continue;
|
|
260
|
+
for (const t of keys) sums[t] += Number(scores[t]);
|
|
261
|
+
count += 1;
|
|
262
|
+
}
|
|
263
|
+
if (count > 0) {
|
|
264
|
+
looBaseline = Object.fromEntries(topics.map((t) => [t, sums[t] / count]));
|
|
265
|
+
baselineSource = `leave-one-out(演练用,${count} 轮)`;
|
|
266
|
+
} else {
|
|
267
|
+
baselineSource = 'raw-bootstrap(无可用轮次)';
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const diffs = [];
|
|
272
|
+
const summary = {
|
|
273
|
+
indexRows: indexRows.length,
|
|
274
|
+
evaluated: 0,
|
|
275
|
+
unresolved: 0,
|
|
276
|
+
changed: 0,
|
|
277
|
+
unchanged: 0,
|
|
278
|
+
emptyToTopic: 0,
|
|
279
|
+
topicToEmpty: 0,
|
|
280
|
+
topicChanged: 0,
|
|
281
|
+
workflowChanged: 0,
|
|
282
|
+
scoreChanged: 0,
|
|
283
|
+
baselineSource,
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
for (const item of evaluated) {
|
|
287
|
+
if (item.unresolved) { summary.unresolved += 1; continue; }
|
|
288
|
+
summary.evaluated += 1;
|
|
289
|
+
|
|
290
|
+
let baseline = explicitBaseline ?? looBaseline;
|
|
291
|
+
if (!explicitBaseline && looBaseline) {
|
|
292
|
+
// 近似 leave-one-out:把本轮 raw 分从均值里扣掉(用粗算的那一轮分不够精确,宁可标来源)
|
|
293
|
+
baseline = looBaseline;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const res = await labeler.labelChunked(item.text, {
|
|
297
|
+
topK: 3,
|
|
298
|
+
minScore,
|
|
299
|
+
topicMinScore,
|
|
300
|
+
topicCenterMin: centerMin,
|
|
301
|
+
topicLead: lead,
|
|
302
|
+
chunkChars,
|
|
303
|
+
maxChunks,
|
|
304
|
+
topicTopK: 1,
|
|
305
|
+
topicBaseline: baseline ?? undefined,
|
|
306
|
+
workflowBaseline: workflowBaseline ?? undefined,
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
const oldTopic = topicOfRecord(item.record);
|
|
310
|
+
const oldWorkflows = workflowsOfRecord(item.record);
|
|
311
|
+
const oldScore = oldTopic ? scoreOfRecord(item.record, oldTopic) : null;
|
|
312
|
+
const oldConfidence = Number.isFinite(Number(item.record.topicConfidence)) ? Number(item.record.topicConfidence) : null;
|
|
313
|
+
|
|
314
|
+
const newTopic = (res.topic ?? [])[0]?.concept ?? null;
|
|
315
|
+
const newWorkflows = (res.workflow ?? []).map((w) => w.concept);
|
|
316
|
+
const newScore = newTopic ? Number(res.topicScores?.[newTopic] ?? res.topic?.[0]?.score ?? 0) : null;
|
|
317
|
+
const newConfidence = newTopic ? Number(res.topicConfidence ?? 0) : null;
|
|
318
|
+
|
|
319
|
+
const changedTopic = (oldTopic ?? null) !== (newTopic ?? null);
|
|
320
|
+
const changedWorkflow = oldWorkflows.join('|') !== newWorkflows.join('|');
|
|
321
|
+
const changedScore = oldScore !== null && newScore !== null && Math.abs(oldScore - newScore) > 1e-6;
|
|
322
|
+
const changed = changedTopic || changedWorkflow || changedScore || (oldConfidence ?? null) !== (newConfidence ?? null);
|
|
323
|
+
|
|
324
|
+
if (changed) summary.changed += 1; else summary.unchanged += 1;
|
|
325
|
+
if (!oldTopic && newTopic) summary.emptyToTopic += 1;
|
|
326
|
+
if (oldTopic && !newTopic) summary.topicToEmpty += 1;
|
|
327
|
+
if (oldTopic && newTopic && oldTopic !== newTopic) summary.topicChanged += 1;
|
|
328
|
+
if (changedWorkflow) summary.workflowChanged += 1;
|
|
329
|
+
if (changedScore) summary.scoreChanged += 1;
|
|
330
|
+
|
|
331
|
+
diffs.push({
|
|
332
|
+
turn: item.turn,
|
|
333
|
+
oldTopic,
|
|
334
|
+
newTopic,
|
|
335
|
+
oldWorkflows,
|
|
336
|
+
newWorkflows,
|
|
337
|
+
oldTopicScore: oldScore,
|
|
338
|
+
newTopicScore: newScore,
|
|
339
|
+
oldConfidence,
|
|
340
|
+
newConfidence,
|
|
341
|
+
changed,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const payload = {
|
|
346
|
+
summary,
|
|
347
|
+
diffs,
|
|
348
|
+
note: explicitBaseline
|
|
349
|
+
? 'baseline 来自文件;本脚本只读,未写任何文件'
|
|
350
|
+
: 'baseline 由被对齐轮次自身算出(leave-one-out 近似),仅用于演练,不可当生产 baseline;本脚本只读',
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
const looRounds = Number((summary.baselineSource.match(/leave-one-out\(演练用,(\d+) 轮\)/) ?? [])[1] ?? 0);
|
|
354
|
+
const lowSample = !explicitBaseline && looRounds > 0 && looRounds < 100;
|
|
355
|
+
if (lowSample) {
|
|
356
|
+
payload.warning = `baseline 仅 ${looRounds} 轮(<100),结果仅供参考,不能当生产结论`;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (asJson) {
|
|
360
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
361
|
+
} else {
|
|
362
|
+
console.log(` index ${summary.indexRows} 行 · 可重打 ${summary.evaluated} · unresolved ${summary.unresolved}`);
|
|
363
|
+
console.log(` A 表 ${conceptRows.length} 条(topic ${topics.length})· 判据 centered≥${centerMin} lead≥${lead} · baseline ${summary.baselineSource}`);
|
|
364
|
+
if (lowSample) console.log(` ⚠ ${payload.warning}`);
|
|
365
|
+
console.log(` 变更 ${summary.changed} · 不变 ${summary.unchanged} · 空→topic ${summary.emptyToTopic} · topic→空 ${summary.topicToEmpty}`);
|
|
366
|
+
console.log(` 换 topic ${summary.topicChanged} · 换 workflow ${summary.workflowChanged} · 分数变化 ${summary.scoreChanged}`);
|
|
367
|
+
console.log('');
|
|
368
|
+
const shown = diffs.filter((d) => d.changed).slice(0, limit);
|
|
369
|
+
if (shown.length === 0) {
|
|
370
|
+
console.log(' 没有会变更的行。');
|
|
371
|
+
} else {
|
|
372
|
+
console.log(' turn oldTopic → newTopic score wf');
|
|
373
|
+
for (const d of shown) {
|
|
374
|
+
const score = `${d.oldTopicScore === null ? '-' : d.oldTopicScore.toFixed(3)}→${d.newTopicScore === null ? '-' : d.newTopicScore.toFixed(3)}`;
|
|
375
|
+
console.log(` ${String(d.turn).padStart(5)} ${String(d.oldTopic ?? '(空)').padEnd(12)} → ${String(d.newTopic ?? '(空)').padEnd(14)} ${score.padEnd(13)} ${d.oldWorkflows.join('/') || '-'} → ${d.newWorkflows.join('/') || '-'}`);
|
|
376
|
+
}
|
|
377
|
+
if (diffs.filter((d) => d.changed).length > shown.length) {
|
|
378
|
+
console.log(` … 还有 ${diffs.filter((d) => d.changed).length - shown.length} 条,用 --limit 或 --json 看全`);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""ReLiK reader-only entity linking. Input JSONL: {id,text,candidates:[...]}."""
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from relik import Relik
|
|
9
|
+
from relik.reader.pytorch_modules.span import RelikReaderForSpanExtraction
|
|
10
|
+
|
|
11
|
+
reader_path = os.environ["RELIK_READER"]
|
|
12
|
+
reader = RelikReaderForSpanExtraction(reader_path)
|
|
13
|
+
relik = Relik(reader=reader, task="span", window_size=32, window_stride=16)
|
|
14
|
+
|
|
15
|
+
src, out = sys.argv[1], sys.argv[2]
|
|
16
|
+
rows = []
|
|
17
|
+
for line in open(src, encoding="utf-8"):
|
|
18
|
+
if not line.strip():
|
|
19
|
+
continue
|
|
20
|
+
item = json.loads(line)
|
|
21
|
+
result = relik(item["text"], candidates=item.get("candidates", []))
|
|
22
|
+
spans = [
|
|
23
|
+
{"start": span.start, "end": span.end, "text": span.text, "label": span.label}
|
|
24
|
+
for span in result.spans
|
|
25
|
+
]
|
|
26
|
+
candidates = result.candidates.span if result.candidates is not None else []
|
|
27
|
+
serial_candidates = []
|
|
28
|
+
for span_group in candidates:
|
|
29
|
+
span_group_out = []
|
|
30
|
+
for candidate_group in span_group:
|
|
31
|
+
span_group_out.append([
|
|
32
|
+
{"text": getattr(doc, "text", None), "id": str(getattr(doc, "id", None))}
|
|
33
|
+
for doc in candidate_group
|
|
34
|
+
])
|
|
35
|
+
serial_candidates.append(span_group_out)
|
|
36
|
+
rows.append({"id": item.get("id"), "text": item["text"], "spans": spans, "candidates": serial_candidates})
|
|
37
|
+
with open(out, "w", encoding="utf-8") as f:
|
|
38
|
+
json.dump({"n": len(rows), "rows": rows}, f, ensure_ascii=False, indent=2)
|
|
39
|
+
print(json.dumps({"n": len(rows), "with_spans": sum(1 for row in rows if row["spans"])}, ensure_ascii=False, indent=2))
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""One-shot ReLiK reader service: stdin {text,candidates}, stdout {ok, links}."""
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
9
|
+
sys.stderr.reconfigure(encoding="utf-8")
|
|
10
|
+
|
|
11
|
+
from relik import Relik # noqa: E402
|
|
12
|
+
from relik.reader.pytorch_modules.span import RelikReaderForSpanExtraction # noqa: E402
|
|
13
|
+
|
|
14
|
+
reader = RelikReaderForSpanExtraction(os.environ["RELIK_READER"])
|
|
15
|
+
relik = Relik(reader=reader, task="span", window_size=32, window_stride=16)
|
|
16
|
+
|
|
17
|
+
def handle(request):
|
|
18
|
+
if request.get("health"):
|
|
19
|
+
return {"ok": True, "health": "ready"}
|
|
20
|
+
try:
|
|
21
|
+
result = relik(str(request.get("text", "")), candidates=request.get("candidates") or [])
|
|
22
|
+
links = []
|
|
23
|
+
candidate_groups = result.candidates.span if result.candidates is not None else []
|
|
24
|
+
for index, span in enumerate(result.spans):
|
|
25
|
+
group = []
|
|
26
|
+
if candidate_groups and index < len(candidate_groups) and candidate_groups[index]:
|
|
27
|
+
group = candidate_groups[index][0]
|
|
28
|
+
elif candidate_groups and candidate_groups[0]:
|
|
29
|
+
group = candidate_groups[0][0]
|
|
30
|
+
top = next((candidate for candidate in group if candidate.text in (span.label, span.text)), group[0] if group else None)
|
|
31
|
+
links.append({
|
|
32
|
+
"text": span.text,
|
|
33
|
+
"entityId": str(top.id) if top is not None else None,
|
|
34
|
+
"score": None,
|
|
35
|
+
"start": span.start,
|
|
36
|
+
"end": span.end,
|
|
37
|
+
})
|
|
38
|
+
return {"ok": True, "links": links, "spans": [{"text": s.text, "start": s.start, "end": s.end} for s in result.spans]}
|
|
39
|
+
except Exception as error:
|
|
40
|
+
return {"ok": False, "reason": type(error).__name__, "message": str(error)[:300]}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
if "--serve" in sys.argv:
|
|
44
|
+
for raw in sys.stdin.buffer:
|
|
45
|
+
line = raw.decode("utf-8", "replace").strip()
|
|
46
|
+
if not line:
|
|
47
|
+
continue
|
|
48
|
+
request = json.loads(line)
|
|
49
|
+
print(json.dumps({"id": request.get("id"), **handle(request)}, ensure_ascii=False), flush=True)
|
|
50
|
+
else:
|
|
51
|
+
request = json.loads(sys.stdin.buffer.read().decode("utf-8"))
|
|
52
|
+
print(json.dumps(handle(request), ensure_ascii=False))
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""SRL pipeline: transformer-srl + local CoNLL2012 archive + NLTK POS fallback."""
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
import nltk
|
|
9
|
+
import spacy
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def add_pos(doc):
|
|
13
|
+
tags = nltk.pos_tag([token.text for token in doc])
|
|
14
|
+
for token, (_, tag) in zip(doc, tags):
|
|
15
|
+
token.tag_ = tag
|
|
16
|
+
if tag.startswith("VB"):
|
|
17
|
+
token.pos_ = "VERB"
|
|
18
|
+
elif tag.startswith("NN"):
|
|
19
|
+
token.pos_ = "NOUN"
|
|
20
|
+
elif tag.startswith("JJ"):
|
|
21
|
+
token.pos_ = "ADJ"
|
|
22
|
+
elif tag.startswith("RB"):
|
|
23
|
+
token.pos_ = "ADV"
|
|
24
|
+
elif tag in ("IN", "TO"):
|
|
25
|
+
token.pos_ = "ADP"
|
|
26
|
+
elif tag in ("DT", "PRP", "PRP$"):
|
|
27
|
+
token.pos_ = "DET"
|
|
28
|
+
else:
|
|
29
|
+
token.pos_ = "X"
|
|
30
|
+
return doc
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
blank = spacy.blank("en")
|
|
34
|
+
blank.add_pipe(add_pos)
|
|
35
|
+
spacy.load = lambda *args, **kwargs: blank
|
|
36
|
+
|
|
37
|
+
import transformer_srl.dataset_readers # noqa: E402,F401
|
|
38
|
+
import transformer_srl.models # noqa: E402,F401
|
|
39
|
+
from allennlp.models.archival import load_archive # noqa: E402
|
|
40
|
+
from transformer_srl import predictors # noqa: E402
|
|
41
|
+
|
|
42
|
+
archive = load_archive(os.environ["SRL_ARCHIVE"])
|
|
43
|
+
predictor = predictors.SrlTransformersPredictor.from_archive(archive, "transformer_srl")
|
|
44
|
+
|
|
45
|
+
src, out = sys.argv[1], sys.argv[2]
|
|
46
|
+
rows = []
|
|
47
|
+
for line in open(src, encoding="utf-8"):
|
|
48
|
+
if not line.strip():
|
|
49
|
+
continue
|
|
50
|
+
item = json.loads(line)
|
|
51
|
+
result = predictor.predict(sentence=item["text"])
|
|
52
|
+
rows.append({"id": item.get("id"), "text": item["text"], **result})
|
|
53
|
+
with open(out, "w", encoding="utf-8") as f:
|
|
54
|
+
json.dump({"n": len(rows), "rows": rows}, f, ensure_ascii=False, indent=2)
|
|
55
|
+
print(json.dumps({"n": len(rows), "with_verbs": sum(1 for row in rows if row["verbs"])}, ensure_ascii=False, indent=2))
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""One-shot SRL service: stdin {text}, stdout {ok, srl}."""
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
import nltk
|
|
9
|
+
import spacy
|
|
10
|
+
|
|
11
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
12
|
+
sys.stderr.reconfigure(encoding="utf-8")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def add_pos(doc):
|
|
16
|
+
tags = nltk.pos_tag([token.text for token in doc])
|
|
17
|
+
for token, (_, tag) in zip(doc, tags):
|
|
18
|
+
token.tag_ = tag
|
|
19
|
+
if tag.startswith("VB"):
|
|
20
|
+
token.pos_ = "VERB"
|
|
21
|
+
elif tag.startswith("NN"):
|
|
22
|
+
token.pos_ = "NOUN"
|
|
23
|
+
elif tag.startswith("JJ"):
|
|
24
|
+
token.pos_ = "ADJ"
|
|
25
|
+
elif tag.startswith("RB"):
|
|
26
|
+
token.pos_ = "ADV"
|
|
27
|
+
elif tag in ("IN", "TO"):
|
|
28
|
+
token.pos_ = "ADP"
|
|
29
|
+
elif tag in ("DT", "PRP", "PRP$"):
|
|
30
|
+
token.pos_ = "DET"
|
|
31
|
+
else:
|
|
32
|
+
token.pos_ = "X"
|
|
33
|
+
return doc
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
blank = spacy.blank("en")
|
|
37
|
+
blank.add_pipe(add_pos)
|
|
38
|
+
spacy.load = lambda *args, **kwargs: blank
|
|
39
|
+
|
|
40
|
+
import transformer_srl.dataset_readers # noqa: E402,F401
|
|
41
|
+
import transformer_srl.models # noqa: E402,F401
|
|
42
|
+
from allennlp.models.archival import load_archive # noqa: E402
|
|
43
|
+
from transformer_srl import predictors # noqa: E402
|
|
44
|
+
|
|
45
|
+
archive = load_archive(os.environ["SRL_ARCHIVE"])
|
|
46
|
+
predictor = predictors.SrlTransformersPredictor.from_archive(archive, "transformer_srl")
|
|
47
|
+
|
|
48
|
+
def handle(request):
|
|
49
|
+
if request.get("health"):
|
|
50
|
+
return {"ok": True, "health": "ready"}
|
|
51
|
+
try:
|
|
52
|
+
result = predictor.predict(sentence=str(request.get("text", "")))
|
|
53
|
+
verb = result["verbs"][0] if result["verbs"] else None
|
|
54
|
+
return {"ok": True, "srl": {"words": result["words"], "verb": verb}}
|
|
55
|
+
except Exception as error:
|
|
56
|
+
return {"ok": False, "reason": type(error).__name__, "message": str(error)[:300]}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
if "--serve" in sys.argv:
|
|
60
|
+
for raw in sys.stdin.buffer:
|
|
61
|
+
line = raw.decode("utf-8", "replace").strip()
|
|
62
|
+
if not line:
|
|
63
|
+
continue
|
|
64
|
+
request = json.loads(line)
|
|
65
|
+
print(json.dumps({"id": request.get("id"), **handle(request)}, ensure_ascii=False), flush=True)
|
|
66
|
+
else:
|
|
67
|
+
request = json.loads(sys.stdin.buffer.read().decode("utf-8"))
|
|
68
|
+
print(json.dumps(handle(request), ensure_ascii=False))
|