dsh-data-domain-knowledge 0.1.0-alpha.13 → 0.1.0-alpha.14
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/README.md +84 -52
- package/cordis.patch.yml +21 -17
- package/eval/benchmark-extraction-format.mjs +20 -0
- package/lib/client.js +2846 -3872
- package/lib/doc-page.js +1 -1
- package/lib/pages.js +1299 -1660
- package/lib/types/axis-registry.d.ts +6 -0
- package/lib/types/axis-registry.d.ts.map +1 -1
- package/lib/types/axis-registry.js +45 -0
- package/lib/types/axis-registry.js.map +1 -1
- package/lib/types/diagnosis/cards.d.ts +7 -3
- package/lib/types/diagnosis/cards.d.ts.map +1 -1
- package/lib/types/diagnosis/cards.js +13 -8
- package/lib/types/diagnosis/cards.js.map +1 -1
- package/lib/types/diagnosis/run.d.ts +4 -13
- package/lib/types/diagnosis/run.d.ts.map +1 -1
- package/lib/types/diagnosis/run.js +26 -16
- package/lib/types/diagnosis/run.js.map +1 -1
- package/lib/types/diagnosis/schema.js +2 -2
- package/lib/types/diagnosis/schema.js.map +1 -1
- package/lib/types/diagnosis/seed-source.d.ts +1 -1
- package/lib/types/diagnosis/seed-source.d.ts.map +1 -1
- package/lib/types/diagnosis/seed-source.js +5 -4
- package/lib/types/diagnosis/seed-source.js.map +1 -1
- package/lib/types/diagnosis/seed.d.ts +4 -4
- package/lib/types/diagnosis/seed.d.ts.map +1 -1
- package/lib/types/diagnosis/seed.js +6 -8
- package/lib/types/diagnosis/seed.js.map +1 -1
- package/lib/types/diagnosis/store.d.ts +8 -16
- package/lib/types/diagnosis/store.d.ts.map +1 -1
- package/lib/types/diagnosis/store.js +41 -66
- package/lib/types/diagnosis/store.js.map +1 -1
- package/lib/types/diagnosis/types.d.ts +10 -16
- package/lib/types/diagnosis/types.d.ts.map +1 -1
- package/lib/types/diagnosis/types.js.map +1 -1
- package/lib/types/environment/service.d.ts +4 -3
- package/lib/types/environment/service.d.ts.map +1 -1
- package/lib/types/environment/service.js +91 -23
- package/lib/types/environment/service.js.map +1 -1
- package/lib/types/environment/types.d.ts +9 -0
- package/lib/types/environment/types.d.ts.map +1 -1
- package/lib/types/events.d.ts +2 -0
- package/lib/types/events.d.ts.map +1 -1
- package/lib/types/events.js +13 -0
- package/lib/types/events.js.map +1 -1
- package/lib/types/extract-contract.d.ts +1 -1
- package/lib/types/extract-contract.d.ts.map +1 -1
- package/lib/types/extract-contract.js +34 -31
- package/lib/types/extract-contract.js.map +1 -1
- package/lib/types/extract-model.d.ts +2 -0
- package/lib/types/extract-model.d.ts.map +1 -1
- package/lib/types/extract-model.js.map +1 -1
- package/lib/types/extract-table-contract.d.ts +3 -0
- package/lib/types/extract-table-contract.d.ts.map +1 -0
- package/lib/types/extract-table-contract.js +30 -0
- package/lib/types/extract-table-contract.js.map +1 -0
- package/lib/types/extract-table.d.ts +8 -0
- package/lib/types/extract-table.d.ts.map +1 -0
- package/lib/types/extract-table.js +151 -0
- package/lib/types/extract-table.js.map +1 -0
- package/lib/types/extraction-runs.d.ts +3 -0
- package/lib/types/extraction-runs.d.ts.map +1 -1
- package/lib/types/extraction-runs.js +6 -0
- package/lib/types/extraction-runs.js.map +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +196 -44
- package/lib/types/index.js.map +1 -1
- package/lib/types/intent/scope.d.ts.map +1 -1
- package/lib/types/intent/scope.js +1 -0
- package/lib/types/intent/scope.js.map +1 -1
- package/lib/types/knowledge-bases.d.ts.map +1 -1
- package/lib/types/knowledge-bases.js +3 -2
- package/lib/types/knowledge-bases.js.map +1 -1
- package/lib/types/knowledge.d.ts +3 -0
- package/lib/types/knowledge.d.ts.map +1 -1
- package/lib/types/knowledge.js +12 -3
- package/lib/types/knowledge.js.map +1 -1
- package/lib/types/model-text.d.ts +1 -1
- package/lib/types/model-text.d.ts.map +1 -1
- package/lib/types/model-text.js +5 -4
- package/lib/types/model-text.js.map +1 -1
- package/lib/types/pipeline/engine.d.ts +57 -0
- package/lib/types/pipeline/engine.d.ts.map +1 -0
- package/lib/types/pipeline/engine.js +268 -0
- package/lib/types/pipeline/engine.js.map +1 -0
- package/lib/types/pipeline/store.d.ts +56 -0
- package/lib/types/pipeline/store.d.ts.map +1 -0
- package/lib/types/pipeline/store.js +115 -0
- package/lib/types/pipeline/store.js.map +1 -0
- package/lib/types/pipeline/types.d.ts +82 -0
- package/lib/types/pipeline/types.d.ts.map +1 -0
- package/lib/types/pipeline/types.js +5 -0
- package/lib/types/pipeline/types.js.map +1 -0
- package/lib/types/present.d.ts +2 -0
- package/lib/types/present.d.ts.map +1 -1
- package/lib/types/present.js +9 -4
- package/lib/types/present.js.map +1 -1
- package/lib/types/runtime.d.ts +20 -1
- package/lib/types/runtime.d.ts.map +1 -1
- package/lib/types/runtime.js +389 -112
- package/lib/types/runtime.js.map +1 -1
- package/lib/types/source-evidence.d.ts.map +1 -1
- package/lib/types/source-evidence.js +2 -0
- package/lib/types/source-evidence.js.map +1 -1
- package/lib/types/store/axis-references.js +3 -3
- package/lib/types/store/axis-references.js.map +1 -1
- package/lib/types/store/migrate.js +1 -1
- package/lib/types/store/migrate.js.map +1 -1
- package/lib/types/types.d.ts +3 -4
- package/lib/types/types.d.ts.map +1 -1
- package/lib/types/types.js.map +1 -1
- package/lib/types/wiki-materialize.d.ts.map +1 -1
- package/lib/types/wiki-materialize.js +3 -15
- package/lib/types/wiki-materialize.js.map +1 -1
- package/lib/types/wiki-trial-types.d.ts +3 -0
- package/lib/types/wiki-trial-types.d.ts.map +1 -1
- package/lib/types/wiki-trial.d.ts +2 -0
- package/lib/types/wiki-trial.d.ts.map +1 -1
- package/lib/types/wiki-trial.js +31 -7
- package/lib/types/wiki-trial.js.map +1 -1
- package/package.json +6 -3
- package/preset/tita-data-agent/agent.cordis.yml +21 -17
- package/src/axis-registry.ts +38 -0
- package/src/client/AttributionEventGuide.tsx +10 -9
- package/src/client/EnvironmentSetup.tsx +35 -15
- package/src/client/ExtractionTasksPage.tsx +129 -176
- package/src/client/KnowledgeBaseTab.tsx +31 -76
- package/src/client/NodeSettings.tsx +2 -3
- package/src/client/PipelineNotice.tsx +26 -0
- package/src/client/TabGuide.tsx +4 -4
- package/src/client/TypeDefinitionDrawer.tsx +23 -8
- package/src/client/WikiCardPreview.tsx +43 -10
- package/src/client/WikiFlowPanel.tsx +4 -4
- package/src/client/architecture.tsx +1 -1
- package/src/client/attribution/AttributionTab.tsx +6 -6
- package/src/client/attribution/CaseView.tsx +3 -1
- package/src/client/attribution/EventView.tsx +14 -4
- package/src/client/attribution/RunView.tsx +2 -2
- package/src/client/attribution/graph-options.ts +2 -2
- package/src/client/pages.tsx +2 -2
- package/src/diagnosis/cards.ts +13 -8
- package/src/diagnosis/run.ts +27 -17
- package/src/diagnosis/schema.ts +2 -2
- package/src/diagnosis/seed-source.ts +5 -4
- package/src/diagnosis/seed.ts +6 -8
- package/src/diagnosis/store.ts +47 -68
- package/src/diagnosis/types.ts +10 -16
- package/src/environment/service.ts +79 -23
- package/src/environment/types.ts +6 -0
- package/src/events.ts +12 -0
- package/src/extract-contract.ts +6 -3
- package/src/extract-model.ts +2 -0
- package/src/extract-table-contract.ts +29 -0
- package/src/extract-table.ts +126 -0
- package/src/extraction-runs.ts +6 -0
- package/src/index.ts +156 -45
- package/src/intent/scope.ts +1 -0
- package/src/knowledge-bases.ts +3 -2
- package/src/knowledge.ts +13 -3
- package/src/model-text.ts +5 -3
- package/src/pipeline/engine.ts +238 -0
- package/src/pipeline/store.ts +119 -0
- package/src/pipeline/types.ts +74 -0
- package/src/present.ts +10 -4
- package/src/runtime.ts +350 -110
- package/src/source-evidence.ts +1 -0
- package/src/store/axis-references.ts +3 -3
- package/src/store/migrate.ts +1 -1
- package/src/types.ts +3 -4
- package/src/wiki-materialize.ts +3 -14
- package/src/wiki-trial-types.ts +3 -0
- package/src/wiki-trial.ts +26 -7
package/lib/types/runtime.js
CHANGED
|
@@ -13,11 +13,14 @@ import { MAX_LOG_ITEMS, OperationLog, operationId } from './oplog.js';
|
|
|
13
13
|
import { ExtractionRunStore, extractionRunId } from './extraction-runs.js';
|
|
14
14
|
import { AxisRegistry, normalizeAxisName } from './axis-registry.js';
|
|
15
15
|
import { KNOWLEDGE_STATE_LABEL, KnowledgeStore } from './knowledge.js';
|
|
16
|
-
import { compositeEventIdentity, eventEntityFromRecords } from './events.js';
|
|
16
|
+
import { compositeEventIdentity, eventEntityFromRecords, stripEventReferenceSection } from './events.js';
|
|
17
17
|
import { buildAliasIndex, candidateEntityId, extractCandidates, normalizeCandidates, RELATES_TO } from './extract.js';
|
|
18
18
|
import { skeletonBody } from './extract-model.js';
|
|
19
|
-
import { applyTrialAxisResolution, normalizeKnowledge, runWikiTrial, trialSummary } from './wiki-trial.js';
|
|
20
|
-
import {
|
|
19
|
+
import { applyTrialAxisResolution, cardNameFor, normalizeKnowledge, runWikiTrial, trialSummary } from './wiki-trial.js';
|
|
20
|
+
import { EXTRACTION_CONTRACT_VERSION } from './extract-table.js';
|
|
21
|
+
import { KnowledgePipeline } from './pipeline/engine.js';
|
|
22
|
+
import { PipelineStore } from './pipeline/store.js';
|
|
23
|
+
import { candidatesFromReport, coordinateIdsOf, coordinateIsEmpty } from './wiki-materialize.js';
|
|
21
24
|
import { buildOrganizePrompt, buildOrganizeSystem, checkOrganize, citationsFrom, layoutHash, layoutOf, parseOrganize, placementsOf, renderFootnoteDefinitions, renderOrganizeBody, } from './card-organize.js';
|
|
22
25
|
import { FTS_FILE, loadSqlite, segment } from './fts.js';
|
|
23
26
|
import { attachImagesToKnowledge, insertImage, planSourceImages } from './source-images.js';
|
|
@@ -684,38 +687,6 @@ function appendExtracted(existing, additions) {
|
|
|
684
687
|
}
|
|
685
688
|
return content;
|
|
686
689
|
}
|
|
687
|
-
/** 坐标 WikiCard 的事件摘要段由 Event 引用派生;全文与时间线留在独立 Event 实体。 */
|
|
688
|
-
function withEventReferences(content, rawRefs, eventsById) {
|
|
689
|
-
let refs = [];
|
|
690
|
-
if (Array.isArray(rawRefs))
|
|
691
|
-
refs = rawRefs.filter((item) => typeof item === 'string');
|
|
692
|
-
else if (typeof rawRefs === 'string') {
|
|
693
|
-
try {
|
|
694
|
-
const parsed = JSON.parse(rawRefs);
|
|
695
|
-
if (Array.isArray(parsed))
|
|
696
|
-
refs = parsed.filter((item) => typeof item === 'string');
|
|
697
|
-
}
|
|
698
|
-
catch { /* 无效引用会作为空列表处理 */ }
|
|
699
|
-
}
|
|
700
|
-
const lines = content.split('\n');
|
|
701
|
-
const start = lines.findIndex(line => line.trim() === '## 关联 Event');
|
|
702
|
-
if (start >= 0) {
|
|
703
|
-
let end = start + 1;
|
|
704
|
-
while (end < lines.length && !/^#{1,2}\s+/.test(lines[end] ?? ''))
|
|
705
|
-
end += 1;
|
|
706
|
-
lines.splice(start, end - start);
|
|
707
|
-
}
|
|
708
|
-
const base = lines.join('\n').trimEnd();
|
|
709
|
-
const entries = [...new Set(refs)].map(id => eventsById.get(id)).filter((event) => event !== undefined);
|
|
710
|
-
if (entries.length === 0)
|
|
711
|
-
return base;
|
|
712
|
-
const bullets = entries.map(event => {
|
|
713
|
-
const occurredAt = event.occurredAt ? ` · ${event.occurredAt}` : '';
|
|
714
|
-
const summary = event.summary.trim() ? `:${event.summary.trim()}` : '';
|
|
715
|
-
return `- **${event.title}**${occurredAt}${summary}(${event.id})`;
|
|
716
|
-
});
|
|
717
|
-
return `${base ? `${base}\n\n` : ''}## 关联 Event\n\n${bullets.join('\n')}`;
|
|
718
|
-
}
|
|
719
690
|
/** 把 `incoming` 按小节折进 `existing`;结构对不上时整段追加。 */
|
|
720
691
|
function foldBody(existing, incoming) {
|
|
721
692
|
if (!existing)
|
|
@@ -1008,9 +979,10 @@ function markdownImageRefs(content) {
|
|
|
1008
979
|
* `tags` / `generated` / `sources`。它们是真 YAML 键,人能读、`grep` 得到、
|
|
1009
980
|
* 外部工具能按公开规范读走。`generated.at` 就是**采集时间**(OKF 里没有「抓取
|
|
1010
981
|
* 时间」这个概念,本文件何时被谁写出来就是它)。
|
|
1011
|
-
* 2. **我们自己的运行键**:`
|
|
1012
|
-
* `
|
|
1013
|
-
*
|
|
982
|
+
* 2. **我们自己的运行键**:`created_at` / `updated_at` / `metadata` / `lineage`。
|
|
983
|
+
* 来源身份保存在 SQLite 的 `entity_id` / `external_id` 列,不在 front matter 重复一份。
|
|
984
|
+
* OKF 明确允许自定义键,且要求消费者不得因为未知键拒收,所以运行元数据可以同处
|
|
985
|
+
* 一个 front matter。
|
|
1014
986
|
*
|
|
1015
987
|
* `metadata` 只剩**类型专属的扩展键**(转换任务 id、内容哈希、资产清单、格式…):
|
|
1016
988
|
* 那些东西 OKF 没有对应概念,但也不该混进上面那组公开字段里。
|
|
@@ -1043,8 +1015,6 @@ function sourceFrontMatter(record, provenance) {
|
|
|
1043
1015
|
...(origin.lastModified?.trim() ? { last_modified: origin.lastModified.trim() } : {}),
|
|
1044
1016
|
}));
|
|
1045
1017
|
}
|
|
1046
|
-
frontMatter.source_id = record.sourceId;
|
|
1047
|
-
frontMatter.external_id = record.externalId ?? '';
|
|
1048
1018
|
frontMatter.created_at = record.createdAt;
|
|
1049
1019
|
frontMatter.updated_at = record.updatedAt;
|
|
1050
1020
|
frontMatter.metadata = record.metadata;
|
|
@@ -1551,6 +1521,9 @@ export class KnowledgeRuntime {
|
|
|
1551
1521
|
/** 抽取轨迹(`<memoryDir>/extraction-runs.sqlite`)。同一个懒打开的理由。 */
|
|
1552
1522
|
extractionRunsStore = null;
|
|
1553
1523
|
extractionRunsReady = null;
|
|
1524
|
+
closed = false;
|
|
1525
|
+
pipelineEngine;
|
|
1526
|
+
pipelineOpening;
|
|
1554
1527
|
/** 轴注册表(`<memoryDir>/knowledge.sqlite`)。与抽取轨迹同一个懒打开的理由。 */
|
|
1555
1528
|
axesStore = null;
|
|
1556
1529
|
axesReady = null;
|
|
@@ -2292,6 +2265,10 @@ export class KnowledgeRuntime {
|
|
|
2292
2265
|
return { vectors, device };
|
|
2293
2266
|
}
|
|
2294
2267
|
close() {
|
|
2268
|
+
if (this.closed)
|
|
2269
|
+
return;
|
|
2270
|
+
this.closed = true;
|
|
2271
|
+
this.pipelineEngine?.close();
|
|
2295
2272
|
for (const task of this.compileTasks.values()) {
|
|
2296
2273
|
if (['succeeded', 'failed', 'cancelled'].includes(task.progress.state))
|
|
2297
2274
|
continue;
|
|
@@ -2299,6 +2276,13 @@ export class KnowledgeRuntime {
|
|
|
2299
2276
|
task.pauseRequested = false;
|
|
2300
2277
|
task.resume?.();
|
|
2301
2278
|
}
|
|
2279
|
+
if (this.compilePending > 0) {
|
|
2280
|
+
void this.compileTail.finally(() => this.closeStores());
|
|
2281
|
+
return;
|
|
2282
|
+
}
|
|
2283
|
+
this.closeStores();
|
|
2284
|
+
}
|
|
2285
|
+
closeStores() {
|
|
2302
2286
|
this.embeddingWorker?.close();
|
|
2303
2287
|
this.embeddingWorker = null;
|
|
2304
2288
|
this.rerankWorker?.close();
|
|
@@ -2680,39 +2664,26 @@ export class KnowledgeRuntime {
|
|
|
2680
2664
|
if (written.length === 0) {
|
|
2681
2665
|
return failure({ ...empty, failed, attempted: items.length }, 'IMPORT_ALL_FAILED', `${items.length} 份来源全部写入失败,没有任何东西进入知识库`, true, failed[0] ? `第一份的原因:${failed[0].message}` : undefined);
|
|
2682
2666
|
}
|
|
2683
|
-
/**
|
|
2684
|
-
* 编译。**只要有一份写成就编**:成功的那几份已经是库里的知识,
|
|
2685
|
-
* 「因为同批里有一份失败就不编」等于把已经进来的知识又挡在门外一次。
|
|
2686
|
-
*/
|
|
2687
2667
|
let compile = null;
|
|
2688
|
-
if (input.compile
|
|
2689
|
-
let rebuilt;
|
|
2668
|
+
if (input.compile === true) {
|
|
2690
2669
|
try {
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
}
|
|
2696
|
-
if (rebuilt === undefined)
|
|
2697
|
-
return success({ written, failed, attempted: items.length, compile }, warnings);
|
|
2698
|
-
if (rebuilt.ok === false) {
|
|
2699
|
-
warnings.push(`来源已写入,但编译失败:${rebuilt.error?.message ?? '未知原因'}`);
|
|
2700
|
-
}
|
|
2701
|
-
else {
|
|
2702
|
-
const data = rebuilt.data;
|
|
2670
|
+
const before = new Map((await this.loadCards()).map(card => [card.entityId, this.cardHashOf(card)]));
|
|
2671
|
+
const pipeline = await this.pipeline();
|
|
2672
|
+
const task = await pipeline.start('all', { sourceIds: written.map(source => source.sourceId) }).completion;
|
|
2673
|
+
const after = await this.loadCards();
|
|
2703
2674
|
compile = {
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
sourcesExtracted: data.sourcesExtracted,
|
|
2710
|
-
sourcesSkipped: data.sourcesSkipped,
|
|
2711
|
-
cardsRemoved: data.cardsRemoved,
|
|
2712
|
-
embedding: data.embedding,
|
|
2713
|
-
version: data.version,
|
|
2675
|
+
taskId: task.id, state: task.state,
|
|
2676
|
+
cardsCreated: after.filter(card => !before.has(card.entityId)).length,
|
|
2677
|
+
cardsUpdated: after.filter(card => before.has(card.entityId) && before.get(card.entityId) !== this.cardHashOf(card)).length,
|
|
2678
|
+
sourcesExtracted: Math.max(0, (task.results?.extract?.done ?? 0) - (task.results?.extract?.reused ?? 0) - (task.results?.extract?.failed ?? 0)),
|
|
2679
|
+
sourcesSkipped: task.results?.extract?.reused ?? 0,
|
|
2714
2680
|
};
|
|
2715
|
-
warnings.push(...
|
|
2681
|
+
warnings.push(...task.warnings);
|
|
2682
|
+
if (task.state !== 'succeeded')
|
|
2683
|
+
warnings.push(`来源已保存;全链路未完成:${task.summary}。可在知识处理页面继续。`);
|
|
2684
|
+
}
|
|
2685
|
+
catch (cause) {
|
|
2686
|
+
warnings.push(`来源已保存;知识处理未完成:${cause instanceof Error ? cause.message : String(cause)}`);
|
|
2716
2687
|
}
|
|
2717
2688
|
}
|
|
2718
2689
|
return success({ written, failed, attempted: items.length, compile }, warnings);
|
|
@@ -3894,10 +3865,11 @@ export class KnowledgeRuntime {
|
|
|
3894
3865
|
const store = await this.knowledgeStore();
|
|
3895
3866
|
if (store === null)
|
|
3896
3867
|
return { ok: false, reason: '知识库不可用,这次没有组织卡片(正文退回确定性布局)' };
|
|
3897
|
-
const listed = await store.list({
|
|
3868
|
+
const listed = input.knowledge ? { knowledge: input.knowledge, total: input.knowledge.length } : await store.list({
|
|
3898
3869
|
objectId: input.coordinate.objectId,
|
|
3899
3870
|
actionId: input.coordinate.actionId,
|
|
3900
3871
|
sceneId: input.coordinate.sceneId,
|
|
3872
|
+
excludeWithdrawn: true,
|
|
3901
3873
|
limit: MAX_ORGANIZE_KNOWLEDGE,
|
|
3902
3874
|
});
|
|
3903
3875
|
if (listed.knowledge.length === 0) {
|
|
@@ -4075,7 +4047,8 @@ export class KnowledgeRuntime {
|
|
|
4075
4047
|
if (call === undefined)
|
|
4076
4048
|
return failure({ card, warnings: [] }, 'MODEL_UNAVAILABLE', '当前没有可用的模型,无法重新组织这张卡片', true, '检查模型配置后再试。');
|
|
4077
4049
|
const profile = this.organizeProfile();
|
|
4078
|
-
const
|
|
4050
|
+
const organizationCard = { ...card, content: stripEventReferenceSection(card.content) };
|
|
4051
|
+
const related = await this.relatedForCard(organizationCard);
|
|
4079
4052
|
const images = await this.imagesForCard(card);
|
|
4080
4053
|
const organized = await this.organizeCard({
|
|
4081
4054
|
cardTitle: card.title,
|
|
@@ -4086,7 +4059,7 @@ export class KnowledgeRuntime {
|
|
|
4086
4059
|
...(profile === undefined ? {} : { requiredSections: profile.sections.filter(section => section.required).map(section => section.label) }),
|
|
4087
4060
|
...(related.related === undefined ? {} : { related: related.related }),
|
|
4088
4061
|
...(images.images.length === 0 ? {} : { images: images.images }),
|
|
4089
|
-
existing:
|
|
4062
|
+
existing: organizationCard,
|
|
4090
4063
|
call,
|
|
4091
4064
|
});
|
|
4092
4065
|
if (organized.kind !== 'applied') {
|
|
@@ -4097,7 +4070,7 @@ export class KnowledgeRuntime {
|
|
|
4097
4070
|
entityType: card.entityType,
|
|
4098
4071
|
entityId: card.entityId,
|
|
4099
4072
|
title: organized.title,
|
|
4100
|
-
content: organized.content,
|
|
4073
|
+
content: stripEventReferenceSection(organized.content),
|
|
4101
4074
|
sourceRefs: card.sourceRefs,
|
|
4102
4075
|
summary: organized.summary,
|
|
4103
4076
|
status: card.status,
|
|
@@ -4183,6 +4156,7 @@ export class KnowledgeRuntime {
|
|
|
4183
4156
|
}
|
|
4184
4157
|
async organizeCard(input) {
|
|
4185
4158
|
const context = await this.organizeContext({
|
|
4159
|
+
knowledge: input.knowledge,
|
|
4186
4160
|
cardTitle: input.cardTitle,
|
|
4187
4161
|
coordinate: input.coordinate,
|
|
4188
4162
|
profileId: input.profileId,
|
|
@@ -4511,7 +4485,7 @@ export class KnowledgeRuntime {
|
|
|
4511
4485
|
return cards.flatMap(card => this.compileCardNotes(card));
|
|
4512
4486
|
}
|
|
4513
4487
|
compileCardNotes(card) {
|
|
4514
|
-
if (card.status === 'problem')
|
|
4488
|
+
if (card.status === 'problem' || card.status === 'deprecated')
|
|
4515
4489
|
return [];
|
|
4516
4490
|
const notes = [];
|
|
4517
4491
|
/**
|
|
@@ -4529,7 +4503,7 @@ export class KnowledgeRuntime {
|
|
|
4529
4503
|
* `metadata.related_cards`;这里继续剥离旧版本正文里的标记,保证存量卡片迁移期间
|
|
4530
4504
|
* 也不会把导航送进召回。
|
|
4531
4505
|
*/
|
|
4532
|
-
const recallBody = stripRelatedSection(card.content);
|
|
4506
|
+
const recallBody = stripEventReferenceSection(stripRelatedSection(card.content));
|
|
4533
4507
|
for (const [index, chunk] of splitWikiNotes({ ...card, content: recallBody }).entries()) {
|
|
4534
4508
|
const baseId = `${card.entityType}:${card.entityId}`;
|
|
4535
4509
|
const unitId = index === 0 ? baseId : `${baseId}#${index}`;
|
|
@@ -4595,7 +4569,7 @@ export class KnowledgeRuntime {
|
|
|
4595
4569
|
const provider = this.config.rerankProvider;
|
|
4596
4570
|
if (provider === 'none')
|
|
4597
4571
|
return { hits, result: { status: 'skipped', provider, threshold } };
|
|
4598
|
-
if (hits.length
|
|
4572
|
+
if (hits.length === 0)
|
|
4599
4573
|
return { hits, result: { status: 'skipped', provider, model: this.rerankModelPath, scored: hits.length, threshold } };
|
|
4600
4574
|
if (this.rerankUnavailable)
|
|
4601
4575
|
return { hits, result: { status: 'unavailable', provider, model: this.rerankModelPath, error: this.rerankUnavailable, threshold }, warning: `重排不可用,已按融合分数排序:${this.rerankUnavailable}` };
|
|
@@ -4843,7 +4817,6 @@ export class KnowledgeRuntime {
|
|
|
4843
4817
|
*/
|
|
4844
4818
|
const profile = this.organizeProfile();
|
|
4845
4819
|
const profileId = profile?.id ?? 'concept';
|
|
4846
|
-
const eventsById = new Map((await this.loadEvents()).map(event => [event.id, event]));
|
|
4847
4820
|
for (const item of pending.values()) {
|
|
4848
4821
|
if (runId)
|
|
4849
4822
|
await this.checkpointCompileTask(runId);
|
|
@@ -4892,7 +4865,7 @@ export class KnowledgeRuntime {
|
|
|
4892
4865
|
&& layoutOf(existing.metadata)?.origin === 'model';
|
|
4893
4866
|
const finalTitle = preserveModelLayout ? existing.title : item.title;
|
|
4894
4867
|
const contentBeforeEvents = preserveModelLayout ? existing.content : content;
|
|
4895
|
-
const finalContent =
|
|
4868
|
+
const finalContent = stripEventReferenceSection(contentBeforeEvents);
|
|
4896
4869
|
const finalSummary = preserveModelLayout ? existing.summary : item.summary;
|
|
4897
4870
|
let finalPlacements = preserveModelLayout || item.placements === undefined
|
|
4898
4871
|
? undefined
|
|
@@ -4986,7 +4959,7 @@ export class KnowledgeRuntime {
|
|
|
4986
4959
|
for (const warning of written.warnings ?? [])
|
|
4987
4960
|
outcome.warnings.push(`卡片 ${card.entityId}:${warning}`);
|
|
4988
4961
|
}
|
|
4989
|
-
// WikiCard
|
|
4962
|
+
// WikiCard 只由 Knowledge 物化;Event 作为独立事实按坐标挂引用。
|
|
4990
4963
|
const eventSync = await this.syncEventReferences();
|
|
4991
4964
|
outcome.cardsUpdated += eventSync.updated;
|
|
4992
4965
|
outcome.warnings.push(...eventSync.warnings);
|
|
@@ -5035,8 +5008,9 @@ export class KnowledgeRuntime {
|
|
|
5035
5008
|
* 用的输入与收尾阶段那个相关块**完全相同**(同一份 `relations` + 同一个坐标投影),
|
|
5036
5009
|
* 所以「正文里能提到谁」与「末尾相关块列了谁」不会是两个答案。
|
|
5037
5010
|
*/
|
|
5038
|
-
const
|
|
5039
|
-
const
|
|
5011
|
+
const cardsWithoutLegacyEventSections = input.cards.map(card => ({ ...card, content: stripEventReferenceSection(card.content) }));
|
|
5012
|
+
const relatedPlan = planRelated(cardsWithoutLegacyEventSections, input.relations, await this.projectedRelated(input.cards));
|
|
5013
|
+
const withCoordinate = cardsWithoutLegacyEventSections
|
|
5040
5014
|
.filter(card => card.coordinate !== undefined)
|
|
5041
5015
|
.sort((left, right) => left.entityId.localeCompare(right.entityId));
|
|
5042
5016
|
if (withCoordinate.length === 0)
|
|
@@ -5674,7 +5648,7 @@ export class KnowledgeRuntime {
|
|
|
5674
5648
|
const projected = await this.projectedRelated(cards);
|
|
5675
5649
|
const related = planRelated(cards, relations, projected);
|
|
5676
5650
|
/** 统计清理旧导航块之后的正文断链;相关卡片本身不再伪装成正文链接。 */
|
|
5677
|
-
const nextCards = cards.map(card => ({ ...card, content: stripRelatedSection(card.content) }));
|
|
5651
|
+
const nextCards = cards.map(card => ({ ...card, content: stripEventReferenceSection(stripRelatedSection(card.content)) }));
|
|
5678
5652
|
const deadLinks = deadLinksOf(nextCards);
|
|
5679
5653
|
let rewritten = 0;
|
|
5680
5654
|
for (const [index, card] of cards.entries()) {
|
|
@@ -6390,6 +6364,7 @@ export class KnowledgeRuntime {
|
|
|
6390
6364
|
* 不能当成「没命中」。
|
|
6391
6365
|
*/
|
|
6392
6366
|
async resolveTrialAxisSet(input) {
|
|
6367
|
+
const resolve = (value) => this.resolveAxisMention({ ...value, registry: input.registry });
|
|
6393
6368
|
const groups = [
|
|
6394
6369
|
{ kind: 'object', key: 'objects' },
|
|
6395
6370
|
{ kind: 'action', key: 'actions' },
|
|
@@ -6400,7 +6375,7 @@ export class KnowledgeRuntime {
|
|
|
6400
6375
|
const axisKey = (kind, mention) => `${kind}\u0000${normalizeAxisName(mention)}`;
|
|
6401
6376
|
const supplementalEntries = { object: [], action: [], scene: [] };
|
|
6402
6377
|
try {
|
|
6403
|
-
const registry = await this.axisRegistry();
|
|
6378
|
+
const registry = input.registry ?? await this.axisRegistry();
|
|
6404
6379
|
if (registry !== null) {
|
|
6405
6380
|
// 初次抽取的候选清单可能被截断。再按本批轴 mention 做一次本地召回,补进未下发的已有坐标。
|
|
6406
6381
|
const query = groups.flatMap(group => input.axes[group.key].flatMap(axis => [
|
|
@@ -6455,7 +6430,7 @@ export class KnowledgeRuntime {
|
|
|
6455
6430
|
const key = axisKey(group.kind, axis.name);
|
|
6456
6431
|
let lookup;
|
|
6457
6432
|
try {
|
|
6458
|
-
lookup = await
|
|
6433
|
+
lookup = await resolve({
|
|
6459
6434
|
kind: group.kind,
|
|
6460
6435
|
mention: axis.name,
|
|
6461
6436
|
parentMention: axis.parentMention,
|
|
@@ -6519,7 +6494,7 @@ export class KnowledgeRuntime {
|
|
|
6519
6494
|
continue;
|
|
6520
6495
|
}
|
|
6521
6496
|
// 没有可供模型比较的现有候选时,沿用注册表的新建候选路径。
|
|
6522
|
-
const created = await
|
|
6497
|
+
const created = await resolve({
|
|
6523
6498
|
kind: group.kind,
|
|
6524
6499
|
mention: axis.name,
|
|
6525
6500
|
parentMention: axis.parentMention,
|
|
@@ -6629,7 +6604,7 @@ export class KnowledgeRuntime {
|
|
|
6629
6604
|
const key = axisKey(item.kind, item.axis.name);
|
|
6630
6605
|
const judgment = decisions.get(key);
|
|
6631
6606
|
if (judgment?.decision === 'new') {
|
|
6632
|
-
const resolution = await
|
|
6607
|
+
const resolution = await resolve({
|
|
6633
6608
|
kind: item.kind,
|
|
6634
6609
|
mention: item.axis.name,
|
|
6635
6610
|
parentMention: item.axis.parentMention,
|
|
@@ -6643,7 +6618,7 @@ export class KnowledgeRuntime {
|
|
|
6643
6618
|
if (judgment?.decision === 'match') {
|
|
6644
6619
|
const candidate = item.candidates.find(value => value.id === judgment.candidateId);
|
|
6645
6620
|
if (candidate) {
|
|
6646
|
-
const resolution = await
|
|
6621
|
+
const resolution = await resolve({
|
|
6647
6622
|
kind: item.kind,
|
|
6648
6623
|
mention: item.axis.name,
|
|
6649
6624
|
parentMention: item.axis.parentMention,
|
|
@@ -6672,7 +6647,7 @@ export class KnowledgeRuntime {
|
|
|
6672
6647
|
};
|
|
6673
6648
|
}
|
|
6674
6649
|
async resolveAxisMention(input) {
|
|
6675
|
-
const registry = await this.axisRegistry();
|
|
6650
|
+
const registry = input.registry ?? await this.axisRegistry();
|
|
6676
6651
|
if (registry === null)
|
|
6677
6652
|
return null;
|
|
6678
6653
|
const runId = input.runId ?? '';
|
|
@@ -6688,7 +6663,7 @@ export class KnowledgeRuntime {
|
|
|
6688
6663
|
if (parent.entity)
|
|
6689
6664
|
parentId = parent.entity.id;
|
|
6690
6665
|
}
|
|
6691
|
-
const preferId = await this.axisIdByName(input.kind, input.preferMention);
|
|
6666
|
+
const preferId = await this.axisIdByName(input.kind, input.preferMention, registry);
|
|
6692
6667
|
return registry.resolve({
|
|
6693
6668
|
kind: input.kind, mention: input.mention,
|
|
6694
6669
|
...(parentId ? { parentId } : {}), ...(runId ? { runId } : {}),
|
|
@@ -6704,11 +6679,11 @@ export class KnowledgeRuntime {
|
|
|
6704
6679
|
* 「是已有的某个东西」而那个名字在库里指两个实体时,把它当成指认就等于替人做了
|
|
6705
6680
|
* 「哪一个」的决定 —— 而注册表的规矩是「不唯一就不猜」。
|
|
6706
6681
|
*/
|
|
6707
|
-
async axisIdByName(kind, mention) {
|
|
6682
|
+
async axisIdByName(kind, mention, supplied) {
|
|
6708
6683
|
const name = (mention ?? '').trim();
|
|
6709
6684
|
if (!name)
|
|
6710
6685
|
return undefined;
|
|
6711
|
-
const registry = await this.axisRegistry();
|
|
6686
|
+
const registry = supplied ?? await this.axisRegistry();
|
|
6712
6687
|
if (registry === null)
|
|
6713
6688
|
return undefined;
|
|
6714
6689
|
try {
|
|
@@ -7461,14 +7436,12 @@ export class KnowledgeRuntime {
|
|
|
7461
7436
|
refs.push(event.id);
|
|
7462
7437
|
eventsByCoordinate.set(key, refs);
|
|
7463
7438
|
}
|
|
7464
|
-
const eventIndex = new Map(events.map(event => [event.id, event]));
|
|
7465
7439
|
const warnings = [];
|
|
7466
7440
|
let updated = 0;
|
|
7467
7441
|
for (const card of await this.loadCards()) {
|
|
7468
|
-
const refs = card.coordinate === undefined
|
|
7442
|
+
const refs = card.status === 'deprecated' || card.coordinate === undefined || card.entityType === 'diagnosis/case'
|
|
7469
7443
|
? []
|
|
7470
7444
|
: (eventsByCoordinate.get(coordinateKeyOf(card.coordinate)) ?? []).sort();
|
|
7471
|
-
db.setCardEvents(card.entityId, refs);
|
|
7472
7445
|
const oldRaw = card.metadata.event_refs;
|
|
7473
7446
|
let oldRefs = [];
|
|
7474
7447
|
let oldRefsValid = false;
|
|
@@ -7486,13 +7459,17 @@ export class KnowledgeRuntime {
|
|
|
7486
7459
|
oldRefs = oldRaw.filter((item) => typeof item === 'string').sort();
|
|
7487
7460
|
oldRefsValid = true;
|
|
7488
7461
|
}
|
|
7489
|
-
const content =
|
|
7462
|
+
const content = stripEventReferenceSection(card.content);
|
|
7490
7463
|
const contentChanged = content !== card.content;
|
|
7491
7464
|
const refsChanged = !oldRefsValid || JSON.stringify(refs) !== JSON.stringify(oldRefs);
|
|
7492
|
-
if (!refsChanged && !contentChanged)
|
|
7465
|
+
if (!refsChanged && !contentChanged) {
|
|
7466
|
+
db.setCardEvents(card.entityId, refs);
|
|
7493
7467
|
continue;
|
|
7494
|
-
|
|
7468
|
+
}
|
|
7469
|
+
if (!oldRefsValid && refs.length === 0 && oldRaw === undefined && !contentChanged) {
|
|
7470
|
+
db.setCardEvents(card.entityId, refs);
|
|
7495
7471
|
continue;
|
|
7472
|
+
}
|
|
7496
7473
|
const written = await this.writeCard({
|
|
7497
7474
|
...card,
|
|
7498
7475
|
sourceRefs: card.sourceRefs,
|
|
@@ -7501,9 +7478,11 @@ export class KnowledgeRuntime {
|
|
|
7501
7478
|
origin: 'internal',
|
|
7502
7479
|
});
|
|
7503
7480
|
if (written.ok === false)
|
|
7504
|
-
warnings.push(
|
|
7505
|
-
else
|
|
7481
|
+
warnings.push(`WikiCard ${card.title} 的 Event 引用未更新:${written.error.message}`);
|
|
7482
|
+
else {
|
|
7483
|
+
db.setCardEvents(card.entityId, refs);
|
|
7506
7484
|
updated += 1;
|
|
7485
|
+
}
|
|
7507
7486
|
}
|
|
7508
7487
|
return { updated, warnings };
|
|
7509
7488
|
}
|
|
@@ -7524,10 +7503,6 @@ export class KnowledgeRuntime {
|
|
|
7524
7503
|
const ids = {};
|
|
7525
7504
|
for (const event of db.events()) {
|
|
7526
7505
|
const remaining = event.records.filter(record => record.sourceId !== input.sourceId);
|
|
7527
|
-
if (remaining.length === 0) {
|
|
7528
|
-
db.removeEvent(event.id);
|
|
7529
|
-
continue;
|
|
7530
|
-
}
|
|
7531
7506
|
const state = {
|
|
7532
7507
|
id: event.id,
|
|
7533
7508
|
identityKey: event.identityKey,
|
|
@@ -7629,8 +7604,12 @@ export class KnowledgeRuntime {
|
|
|
7629
7604
|
saved += 1;
|
|
7630
7605
|
}
|
|
7631
7606
|
for (const state of states.values()) {
|
|
7632
|
-
if (!state.dirty
|
|
7607
|
+
if (!state.dirty)
|
|
7608
|
+
continue;
|
|
7609
|
+
if (state.records.length === 0) {
|
|
7610
|
+
db.removeEvent(state.id);
|
|
7633
7611
|
continue;
|
|
7612
|
+
}
|
|
7634
7613
|
const records = state.records;
|
|
7635
7614
|
const entity = eventEntityFromRecords({
|
|
7636
7615
|
id: state.id,
|
|
@@ -7650,6 +7629,298 @@ export class KnowledgeRuntime {
|
|
|
7650
7629
|
}
|
|
7651
7630
|
return { saved, merged, skipped, ids, warnings };
|
|
7652
7631
|
}
|
|
7632
|
+
/** The stage runner shares this base's existing writer queue and audit database. */
|
|
7633
|
+
async pipeline() {
|
|
7634
|
+
if (this.closed)
|
|
7635
|
+
throw new Error('知识库已关闭');
|
|
7636
|
+
this.pipelineOpening ??= (async () => {
|
|
7637
|
+
const db = await this.storeHandle();
|
|
7638
|
+
const audit = await this.extractionRuns();
|
|
7639
|
+
if (this.closed)
|
|
7640
|
+
throw new Error('知识库已关闭');
|
|
7641
|
+
if (!db || !audit || !await this.knowledgeStore())
|
|
7642
|
+
throw new Error('知识库或任务历史不可用,无法启动处理任务');
|
|
7643
|
+
const store = new PipelineStore(db.connection(), audit.connection());
|
|
7644
|
+
const engine = new KnowledgePipeline(store, {
|
|
7645
|
+
capture: () => ({ model: this.modelSeamNow(), note: this.extractionNote(), profile: structuredClone(this.organizeProfile()), organize: this.config.cardOrganize, format: 'table' }),
|
|
7646
|
+
serialize: work => {
|
|
7647
|
+
this.compilePending += 1;
|
|
7648
|
+
const run = this.compileTail.then(work, work);
|
|
7649
|
+
this.compileTail = run.then(() => undefined, () => undefined).finally(() => { this.compilePending -= 1; });
|
|
7650
|
+
return run;
|
|
7651
|
+
},
|
|
7652
|
+
sources: () => this.pipelineSources(),
|
|
7653
|
+
fingerprint: (source, context) => contentHashOf(JSON.stringify({ source: source.sourceHash, title: source.title, contract: EXTRACTION_CONTRACT_VERSION, note: context.note, format: context.format, model: { provider: context.model.provider, model: context.model.model, reasoningEffort: context.model.reasoningEffort } })),
|
|
7654
|
+
extract: async (source, context, onDelta) => {
|
|
7655
|
+
if (!context.model.call)
|
|
7656
|
+
throw new Error(`抽取模型不可用:${context.model.why ?? '请在编译设置中选择模型'}`);
|
|
7657
|
+
const axes = await this.axisPromptBlock(source.content);
|
|
7658
|
+
return runWikiTrial({ title: source.title, text: source.content, docId: source.sourceId, axes, note: context.note, call: context.model.call, modelRequest: { provider: context.model.provider, model: context.model.model, reasoningEffort: context.model.reasoningEffort }, outputFormat: context.format, sessionId: `knowledge:${this.baseId || 'default'}`, onDelta });
|
|
7659
|
+
},
|
|
7660
|
+
normalize: (source, checkpoint, context, force, output) => this.normalizePipelineSource(store, source, checkpoint, context, force, output),
|
|
7661
|
+
groups: context => this.materializationGroups(store, context),
|
|
7662
|
+
materialize: (group, context, output) => this.materializePipelineGroup(store, group, context, output),
|
|
7663
|
+
build: async (taskId, checkpoint) => {
|
|
7664
|
+
let result;
|
|
7665
|
+
try {
|
|
7666
|
+
result = await this.compileOnce(taskId, 'all', undefined, { extract: false, checkpoint });
|
|
7667
|
+
}
|
|
7668
|
+
catch (cause) {
|
|
7669
|
+
await this.settleProgressFailure(taskId, cause);
|
|
7670
|
+
throw cause;
|
|
7671
|
+
}
|
|
7672
|
+
this.settleProgressReport(taskId, result);
|
|
7673
|
+
if (!result.ok)
|
|
7674
|
+
throw new Error(result.error.message);
|
|
7675
|
+
return `检索索引已发布:v${result.data.version}`;
|
|
7676
|
+
},
|
|
7677
|
+
record: async (checkpoint) => {
|
|
7678
|
+
const audit = await this.recordExtractionRun({ id: checkpoint.id, docId: checkpoint.sourceId, title: checkpoint.report.title, summary: trialSummary(checkpoint.report), report: checkpoint.report });
|
|
7679
|
+
if (!audit.saved)
|
|
7680
|
+
throw new Error(`抽取报告保存失败:${audit.why}`);
|
|
7681
|
+
},
|
|
7682
|
+
});
|
|
7683
|
+
this.pipelineEngine = engine;
|
|
7684
|
+
if (this.closed)
|
|
7685
|
+
engine.close();
|
|
7686
|
+
return engine;
|
|
7687
|
+
})();
|
|
7688
|
+
return this.pipelineOpening;
|
|
7689
|
+
}
|
|
7690
|
+
async pipelineSources() {
|
|
7691
|
+
const db = await this.storeHandle();
|
|
7692
|
+
if (!db)
|
|
7693
|
+
throw new Error('来源存储不可用');
|
|
7694
|
+
return db.sources().map(row => ({ ...row.record, version: row.version, sourceHash: contentHashOf(row.record.content) })).sort((a, b) => a.sourceId.localeCompare(b.sourceId));
|
|
7695
|
+
}
|
|
7696
|
+
async normalizationFingerprint(checkpoint, context) {
|
|
7697
|
+
const registry = await this.axisRegistry();
|
|
7698
|
+
const report = checkpoint.normalized?.report ?? checkpoint.report;
|
|
7699
|
+
const query = [...report.objects, ...report.actions, ...report.scenes].flatMap(axis => [axis.name, axis.existing ?? '', axis.definition ?? '', axis.parentMention ?? '']).join('\n');
|
|
7700
|
+
const candidates = await registry?.promptEntries(24, query);
|
|
7701
|
+
const mentions = [...checkpoint.report.objects, ...checkpoint.report.actions, ...checkpoint.report.scenes].flatMap(axis => [axis.name, axis.existing ?? '', axis.parentMention ?? '']).map(normalizeAxisName).filter(Boolean);
|
|
7702
|
+
const resolvedIds = new Set([...report.objects, ...report.actions, ...report.scenes].map(axis => axis.id).filter(Boolean));
|
|
7703
|
+
// Ignore unrelated filler rows and registry recency; retain candidates that can affect these mentions.
|
|
7704
|
+
const relevant = Object.fromEntries(Object.entries(candidates?.entries ?? {}).map(([kind, entries]) => [kind, entries.filter(entry => resolvedIds.has(entry.id) || mentions.some(mention => [entry.name, ...entry.aliases, entry.description].map(normalizeAxisName).some(value => value.includes(mention) || (value.length > 1 && mention.includes(value))))).sort((a, b) => a.id.localeCompare(b.id))]));
|
|
7705
|
+
return contentHashOf(JSON.stringify({ extraction: checkpoint.id, model: { provider: context.model.provider, model: context.model.model, reasoningEffort: context.model.reasoningEffort }, candidates: relevant }));
|
|
7706
|
+
}
|
|
7707
|
+
async normalizePipelineSource(store, source, checkpoint, context, force, output) {
|
|
7708
|
+
const fingerprint = await this.normalizationFingerprint(checkpoint, context);
|
|
7709
|
+
if (!force && checkpoint.normalized?.fingerprint === fingerprint && store.head(source.sourceId).normalized?.id === checkpoint.id)
|
|
7710
|
+
return { reused: true, warnings: [] };
|
|
7711
|
+
if (!checkpoint.report.model.response)
|
|
7712
|
+
throw new Error('抽取记录没有原始返回,无法归一;请重新抽取该文档');
|
|
7713
|
+
const registry = await this.axisRegistry();
|
|
7714
|
+
if (!registry)
|
|
7715
|
+
throw new Error('实体注册表不可用');
|
|
7716
|
+
const draft = await registry.draft();
|
|
7717
|
+
try {
|
|
7718
|
+
const axesBlock = await this.axisPromptBlock(source.content);
|
|
7719
|
+
const call = context.model.call ? request => context.model.call({ ...request, onDelta: output }) : undefined;
|
|
7720
|
+
const report = await runWikiTrial({
|
|
7721
|
+
title: source.title, text: source.content, docId: source.sourceId, axes: axesBlock,
|
|
7722
|
+
// Reparse the captured response; this callback never requests another extraction.
|
|
7723
|
+
call: async () => checkpoint.report.model.response, modelRequest: checkpoint.report.model.request,
|
|
7724
|
+
resolveAxisSet: axes => this.resolveTrialAxisSet({ registry: draft.registry, axes, axesBlock, runId: checkpoint.id, call, modelRequest: context.model }),
|
|
7725
|
+
});
|
|
7726
|
+
if (!report.model.parsed)
|
|
7727
|
+
throw new Error(report.model.why ?? '抽取结果无法归一');
|
|
7728
|
+
const db = await this.storeHandle();
|
|
7729
|
+
const current = db?.source(source.sourceId);
|
|
7730
|
+
if (this.closed)
|
|
7731
|
+
throw new Error('知识库已关闭,本次归一未提交');
|
|
7732
|
+
if (!db || !current || current.version !== source.version || contentHashOf(current.record.content) !== source.sourceHash)
|
|
7733
|
+
throw new Error('来源在归一期间已变化,请重新抽取最新版本');
|
|
7734
|
+
const sql = db.connection();
|
|
7735
|
+
sql.exec('SAVEPOINT pipeline_normalize');
|
|
7736
|
+
try {
|
|
7737
|
+
draft.commit();
|
|
7738
|
+
// Preserve evidence history while replacing this source's active contribution.
|
|
7739
|
+
sql.prepare('UPDATE knowledge_sources SET withdrawn=1 WHERE document_id=?').run(source.sourceId);
|
|
7740
|
+
const knowledge = await this.ingestTrialKnowledge({ runId: checkpoint.id, knowledge: report.knowledge, objects: report.objects, actions: report.actions, scenes: report.scenes, sourceVersions: { [source.sourceId]: source.version } });
|
|
7741
|
+
const events = await this.ingestTrialEvents({ sourceId: source.sourceId, runId: checkpoint.id, events: report.events, objects: report.objects, actions: report.actions, scenes: report.scenes });
|
|
7742
|
+
if (knowledge.skipped || events.skipped)
|
|
7743
|
+
throw new Error(`出处核验或写入失败:${knowledge.skipped} 条 Knowledge、${events.skipped} 个 Event;本次归一没有提交`);
|
|
7744
|
+
const unresolvedKnowledgeIds = report.knowledge.filter(item => [['object', report.objects], ['action', report.actions], ['scene', report.scenes]].some(([kind, axes]) => {
|
|
7745
|
+
const name = item[kind];
|
|
7746
|
+
const axis = name ? axes.find(axis => axis.name === name) : undefined;
|
|
7747
|
+
return name !== null && (!axis || axis.outcome === 'ambiguous' || axis.outcome === 'no-registry');
|
|
7748
|
+
})).map(item => knowledge.ids[item.id]).filter(Boolean);
|
|
7749
|
+
const normalized = { report, knowledgeIds: knowledge.ids, eventIds: events.ids, unresolvedKnowledgeIds, fingerprint: '' };
|
|
7750
|
+
// Store the post-resolution candidate snapshot so learning its own entities does not invalidate this run.
|
|
7751
|
+
normalized.fingerprint = await this.normalizationFingerprint({ ...checkpoint, normalized }, context);
|
|
7752
|
+
store.saveNormalization(checkpoint, normalized);
|
|
7753
|
+
sql.exec('RELEASE pipeline_normalize');
|
|
7754
|
+
// Updating Event references is a cheap relation refresh. It does not reorganize or index card content.
|
|
7755
|
+
const eventReferences = await this.syncEventReferences();
|
|
7756
|
+
return { reused: false, warnings: [...events.warnings, ...eventReferences.warnings, ...(unresolvedKnowledgeIds.length ? [`${unresolvedKnowledgeIds.length} 条 Knowledge 的坐标仍有歧义,保留事实与出处,暂不物化。`] : [])] };
|
|
7757
|
+
}
|
|
7758
|
+
catch (cause) {
|
|
7759
|
+
sql.exec('ROLLBACK TO pipeline_normalize; RELEASE pipeline_normalize');
|
|
7760
|
+
throw cause;
|
|
7761
|
+
}
|
|
7762
|
+
}
|
|
7763
|
+
finally {
|
|
7764
|
+
draft.close();
|
|
7765
|
+
}
|
|
7766
|
+
}
|
|
7767
|
+
async activePipelineKnowledge() {
|
|
7768
|
+
const knowledge = await this.knowledgeStore();
|
|
7769
|
+
const db = await this.storeHandle();
|
|
7770
|
+
if (!knowledge || !db)
|
|
7771
|
+
return [];
|
|
7772
|
+
const rows = [];
|
|
7773
|
+
for (let offset = 0;; offset += 500) {
|
|
7774
|
+
const page = await knowledge.list({ excludeWithdrawn: true, limit: 500, offset });
|
|
7775
|
+
rows.push(...page.knowledge.filter(row => row.status !== 'deprecated').map(row => assessKnowledgeEvidence(row, db)).map(row => ({ ...row, sources: row.sources.filter(source => !source.withdrawn && source.evidenceStatus !== 'missing') })).filter(row => row.sources.length > 0));
|
|
7776
|
+
if (offset + page.knowledge.length >= page.total)
|
|
7777
|
+
break;
|
|
7778
|
+
}
|
|
7779
|
+
return rows;
|
|
7780
|
+
}
|
|
7781
|
+
async materializationGroups(store, context) {
|
|
7782
|
+
const unresolved = store.unresolvedKnowledge();
|
|
7783
|
+
const groups = new Map();
|
|
7784
|
+
let blocked = 0;
|
|
7785
|
+
const groupFor = (coordinate) => {
|
|
7786
|
+
const key = coordinateKeyOf(coordinate);
|
|
7787
|
+
if (!groups.has(key))
|
|
7788
|
+
groups.set(key, { coordinate, knowledge: [] });
|
|
7789
|
+
return groups.get(key);
|
|
7790
|
+
};
|
|
7791
|
+
for (const row of await this.activePipelineKnowledge()) {
|
|
7792
|
+
const coordinate = { objectId: row.objectId, actionId: row.actionId, sceneId: row.sceneId };
|
|
7793
|
+
if (coordinateIsEmpty(coordinate)) {
|
|
7794
|
+
blocked += 1;
|
|
7795
|
+
continue;
|
|
7796
|
+
}
|
|
7797
|
+
groupFor(coordinate).knowledge.push(row);
|
|
7798
|
+
}
|
|
7799
|
+
const db = await this.storeHandle();
|
|
7800
|
+
const eventsByCoordinate = new Map();
|
|
7801
|
+
for (const event of db?.events() ?? []) {
|
|
7802
|
+
const coordinate = event.coordinateIds;
|
|
7803
|
+
if (!coordinate.objectId || !coordinate.actionId || !coordinate.sceneId)
|
|
7804
|
+
continue;
|
|
7805
|
+
const key = coordinateKeyOf(coordinate);
|
|
7806
|
+
const bucket = eventsByCoordinate.get(key) ?? [];
|
|
7807
|
+
bucket.push(event);
|
|
7808
|
+
eventsByCoordinate.set(key, bucket);
|
|
7809
|
+
}
|
|
7810
|
+
const cards = await this.loadCards();
|
|
7811
|
+
const byCoordinate = new Map(cards.filter(card => card.coordinate).map(card => [coordinateKeyOf(card.coordinate), card]));
|
|
7812
|
+
const result = [];
|
|
7813
|
+
for (const [key, value] of groups) {
|
|
7814
|
+
const names = await this.coordinateNames(value.coordinate);
|
|
7815
|
+
const existing = byCoordinate.get(key);
|
|
7816
|
+
// Event 只作为卡片引用;它的增删和正文变化不得触发 Knowledge 重新组织。
|
|
7817
|
+
const fingerprint = contentHashOf(JSON.stringify({ knowledge: value.knowledge.map(row => ({ id: row.id, text: row.text, status: row.status, sources: row.sources.filter(source => !source.withdrawn) })).sort((a, b) => a.id.localeCompare(b.id)), profile: context.profile, organize: context.organize, model: context.organize ? { provider: context.model.provider, model: context.model.model, reasoningEffort: context.model.reasoningEffort } : undefined }));
|
|
7818
|
+
const receipt = store.materialization(key);
|
|
7819
|
+
const current = existing && receipt?.fingerprint === fingerprint && receipt.cardId === existing.entityId;
|
|
7820
|
+
const contributingSources = [...new Set(value.knowledge.flatMap(row => row.sources.map(source => source.documentId)))].filter(Boolean).sort();
|
|
7821
|
+
const needsNormalization = value.knowledge.some(row => unresolved.ids.has(row.id)) || contributingSources.some(id => {
|
|
7822
|
+
const source = db?.source(id);
|
|
7823
|
+
const head = store.head(id);
|
|
7824
|
+
return head.normalized ? head.normalized.sourceVersion !== source?.version || head.extracted?.id !== head.normalized.id : value.knowledge.some(row => row.sources.some(evidence => evidence.documentId === id && evidence.toVersion !== undefined && evidence.toVersion !== source?.version));
|
|
7825
|
+
});
|
|
7826
|
+
if (needsNormalization)
|
|
7827
|
+
blocked += value.knowledge.length;
|
|
7828
|
+
result.push({ key, title: existing?.title ?? cardNameFor(names.object || null, names.action || null, names.scene || null), coordinate: value.coordinate, knowledgeCount: value.knowledge.length, eventCount: eventsByCoordinate.get(key)?.length ?? 0, sourceIds: contributingSources, ...(existing ? { cardId: existing.entityId } : {}), state: needsNormalization ? 'blocked' : current ? 'current' : existing ? 'changed' : 'new', ...(needsNormalization ? { reason: '分组中的来源已变化、新抽取待归一或坐标有歧义,请先完成归一' } : {}), fingerprint });
|
|
7829
|
+
}
|
|
7830
|
+
for (const card of cards) {
|
|
7831
|
+
if (!card.coordinate || groups.has(coordinateKeyOf(card.coordinate)) || !store.materialization(coordinateKeyOf(card.coordinate)) || card.status === 'deprecated')
|
|
7832
|
+
continue;
|
|
7833
|
+
result.push({ key: coordinateKeyOf(card.coordinate), title: card.title, coordinate: card.coordinate, knowledgeCount: 0, eventCount: 0, sourceIds: card.sourceRefs, cardId: card.entityId, state: card.sourceRefs.some(id => unresolved.sources.has(id)) ? 'blocked' : 'withdrawn', ...(card.sourceRefs.some(id => unresolved.sources.has(id)) ? { reason: '来源中的坐标仍有歧义,请先完成归一' } : {}), fingerprint: 'withdrawn' });
|
|
7834
|
+
}
|
|
7835
|
+
return { groups: result.sort((a, b) => a.title.localeCompare(b.title)), blocked };
|
|
7836
|
+
}
|
|
7837
|
+
async materializePipelineGroup(store, group, context, output) {
|
|
7838
|
+
const db = await this.storeHandle();
|
|
7839
|
+
if (!db)
|
|
7840
|
+
throw new Error('知识库不可用');
|
|
7841
|
+
const fresh = (await this.materializationGroups(store, context)).groups.find(item => item.key === group.key);
|
|
7842
|
+
if (!fresh || fresh.fingerprint !== group.fingerprint)
|
|
7843
|
+
throw new Error('卡片分组在处理期间已变化,请刷新并重试');
|
|
7844
|
+
if (fresh.state === 'blocked')
|
|
7845
|
+
throw new Error(fresh.reason);
|
|
7846
|
+
const storedExisting = group.cardId ? await this.readCard(group.cardId) : undefined;
|
|
7847
|
+
const existing = storedExisting
|
|
7848
|
+
? { ...storedExisting, content: stripEventReferenceSection(storedExisting.content) }
|
|
7849
|
+
: undefined;
|
|
7850
|
+
if (group.state === 'withdrawn') {
|
|
7851
|
+
if (!existing)
|
|
7852
|
+
return;
|
|
7853
|
+
const written = await this.writeCard({ ...existing, status: 'deprecated', origin: 'compile' });
|
|
7854
|
+
if (!written.ok)
|
|
7855
|
+
throw new Error(written.error.message);
|
|
7856
|
+
store.materialized(group.key, group.fingerprint, existing.entityId);
|
|
7857
|
+
return;
|
|
7858
|
+
}
|
|
7859
|
+
const allKnowledge = await this.activePipelineKnowledge();
|
|
7860
|
+
const knowledge = allKnowledge.filter(row => coordinateKeyOf({ objectId: row.objectId, actionId: row.actionId, sceneId: row.sceneId }) === group.key);
|
|
7861
|
+
const events = db.events().filter(event => coordinateKeyOf(event.coordinateIds) === group.key);
|
|
7862
|
+
const profile = context.profile;
|
|
7863
|
+
const entityType = (profile?.id ?? 'concept');
|
|
7864
|
+
let title = group.title;
|
|
7865
|
+
let content = `## 知识\n\n${knowledge.map(row => `- ${row.text}`).join('\n')}`;
|
|
7866
|
+
let summary = knowledge[0]?.text ?? '';
|
|
7867
|
+
let placements = knowledge.map((row, index) => ({ knowledgeId: row.id, section: '知识', ordinal: index + 1, origin: 'compiler' }));
|
|
7868
|
+
let metadata = { ...(existing?.metadata ?? {}), pipeline_materialization: true };
|
|
7869
|
+
const entityId = existing?.entityId ?? `coord-${hash(group.key)}`;
|
|
7870
|
+
const imagePlan = planSourceImages({
|
|
7871
|
+
cards: (await this.materializationGroups(store, context)).groups.filter(item => item.state !== 'withdrawn').map(item => ({ key: item.key, content: allKnowledge.filter(row => coordinateKeyOf({ objectId: row.objectId, actionId: row.actionId, sceneId: row.sceneId }) === item.key).map(row => row.text).join('\n\n'), sourceRefs: item.sourceIds })),
|
|
7872
|
+
sources: (await this.loadSources()).map(source => ({ sourceId: source.sourceId, content: source.content })),
|
|
7873
|
+
});
|
|
7874
|
+
const images = imagePlan.byCard.get(group.key) ?? [];
|
|
7875
|
+
const related = existing ? await this.relatedForCard(existing) : {};
|
|
7876
|
+
metadata.citations = citationsFrom({ placements, knowledge: new Map(knowledge.map(row => [row.id, { source: row.sources[0]?.documentId, heading: row.sources[0]?.headingPath.at(-1) }])) });
|
|
7877
|
+
if (context.organize && knowledge.length > 0) {
|
|
7878
|
+
if (!context.model.call)
|
|
7879
|
+
throw new Error(`卡片组织模型不可用:${context.model.why ?? '请选择模型'}`);
|
|
7880
|
+
const result = await this.organizeCard({ knowledge, images, ...related, cardTitle: title, coordinate: group.coordinate, profileId: entityType, force: true, suggestedSections: profile?.sections.map(section => section.label), requiredSections: profile?.sections.filter(section => section.required).map(section => section.label), existing, call: request => context.model.call({ ...request, onDelta: output }) });
|
|
7881
|
+
if (result.kind !== 'applied')
|
|
7882
|
+
throw new Error(result.kind === 'reused' ? '组织结果已变化,请刷新后重试' : result.warnings.join(';'));
|
|
7883
|
+
title = result.title;
|
|
7884
|
+
content = result.content;
|
|
7885
|
+
summary = result.summary;
|
|
7886
|
+
placements = result.placements;
|
|
7887
|
+
metadata = { ...metadata, layout: result.layout, citations: result.citations };
|
|
7888
|
+
}
|
|
7889
|
+
for (const image of images)
|
|
7890
|
+
content = insertImage(content, image);
|
|
7891
|
+
const eventIds = events.map(event => event.id).sort();
|
|
7892
|
+
content = stripEventReferenceSection(content);
|
|
7893
|
+
metadata.event_refs = JSON.stringify(eventIds);
|
|
7894
|
+
// Freeze the selected group's facts, not an arbitrary subset of its rows.
|
|
7895
|
+
const latest = (await this.materializationGroups(store, context)).groups.find(item => item.key === group.key);
|
|
7896
|
+
if (!latest || latest.fingerprint !== group.fingerprint || latest.state === 'blocked')
|
|
7897
|
+
throw new Error('卡片组织期间事实发生变化,结果未提交,请重试');
|
|
7898
|
+
if (this.closed)
|
|
7899
|
+
throw new Error('知识库已关闭,本次物化未提交');
|
|
7900
|
+
const written = await this.writeCard({ entityType, entityId, title, content, summary, sourceRefs: group.sourceIds, status: existing?.status === 'deprecated' ? 'unreviewed' : existing?.status ?? 'unreviewed', coordinate: group.coordinate, placements, metadata, origin: 'compile' });
|
|
7901
|
+
if (!written.ok)
|
|
7902
|
+
throw new Error(written.error.message);
|
|
7903
|
+
db.setCardEvents(entityId, eventIds);
|
|
7904
|
+
store.materialized(group.key, group.fingerprint, entityId);
|
|
7905
|
+
}
|
|
7906
|
+
async pipelineStatus() {
|
|
7907
|
+
const pipeline = await this.pipeline();
|
|
7908
|
+
const sources = await this.pipelineSources();
|
|
7909
|
+
const context = { model: this.modelSeamNow(), note: this.extractionNote(), profile: this.organizeProfile(), organize: this.config.cardOrganize, format: 'table' };
|
|
7910
|
+
const rows = await Promise.all(sources.map(async (source) => {
|
|
7911
|
+
const head = pipeline.store.head(source.sourceId);
|
|
7912
|
+
const extracted = head.extracted?.sourceVersion === source.version && head.extracted.sourceHash === source.sourceHash;
|
|
7913
|
+
const normalized = extracted && head.normalized?.id === head.extracted?.id && head.normalized?.normalized?.fingerprint === await this.normalizationFingerprint(head.normalized, context);
|
|
7914
|
+
return { id: source.sourceId, title: source.title, version: source.version, extractionId: head.extracted?.id, normalizedId: head.normalized?.id, state: normalized ? 'normalized' : extracted ? 'extracted' : head.extracted ? 'outdated' : 'pending' };
|
|
7915
|
+
}));
|
|
7916
|
+
const { groups, blocked } = await this.materializationGroups(pipeline.store, context);
|
|
7917
|
+
const db = await this.derivedHandle();
|
|
7918
|
+
const manifest = db?.artifact('build-manifest.json');
|
|
7919
|
+
const hashes = manifest ? JSON.parse(manifest).cardHashes ?? {} : {};
|
|
7920
|
+
const cards = await this.loadCards();
|
|
7921
|
+
const indexPending = cards.some(card => hashes[`${card.entityType}:${card.entityId}`] !== this.cardHashOf(card)) || Object.keys(hashes).length !== cards.length;
|
|
7922
|
+
return { sources: rows, groups, blocked, pendingNew: groups.filter(group => group.state === 'new').length, pendingUpdated: groups.filter(group => group.state === 'changed').length, pendingWithdrawn: groups.filter(group => group.state === 'withdrawn').length, indexPending };
|
|
7923
|
+
}
|
|
7653
7924
|
/** 抽取轨迹列表(新→旧,不含报告本体)。 */
|
|
7654
7925
|
async listExtractionRuns(query = {}) {
|
|
7655
7926
|
const store = await this.extractionRuns();
|
|
@@ -7853,10 +8124,12 @@ export class KnowledgeRuntime {
|
|
|
7853
8124
|
* 它」—— 那两件事分别在 `enqueueCompile()` 与 `rebuild()` / `startCompile()` 里。
|
|
7854
8125
|
*/
|
|
7855
8126
|
async compileOnce(runId, only = 'all', lineage, options = {}) {
|
|
7856
|
-
|
|
8127
|
+
if (only === 'all' || only === 'embeddings')
|
|
8128
|
+
await this.environmentGuard?.('embedding');
|
|
7857
8129
|
this.publication = { artifacts: new Map() };
|
|
7858
8130
|
const configAtStart = JSON.stringify({ config: this.config, fingerprint: this.embeddingFingerprint() });
|
|
7859
8131
|
try {
|
|
8132
|
+
await options.checkpoint?.();
|
|
7860
8133
|
await this.loadCompiledState();
|
|
7861
8134
|
const result = await this.prepareCompile(runId, only, lineage, options);
|
|
7862
8135
|
await this.checkpointCompileTask(runId);
|
|
@@ -7889,6 +8162,7 @@ export class KnowledgeRuntime {
|
|
|
7889
8162
|
await this.checkpointCompileTask(runId);
|
|
7890
8163
|
if (JSON.stringify({ config: this.config, fingerprint: this.embeddingFingerprint() }) !== configAtStart)
|
|
7891
8164
|
throw new Error('COMPILE_CONFIG_CHANGED: 编译期间设置已变化,请重新编译');
|
|
8165
|
+
await options.checkpoint?.();
|
|
7892
8166
|
db.publish(this.publication);
|
|
7893
8167
|
this.vectorIndex = null;
|
|
7894
8168
|
}
|
|
@@ -8042,7 +8316,7 @@ export class KnowledgeRuntime {
|
|
|
8042
8316
|
tally.knowledge > 0
|
|
8043
8317
|
? `抽出知识 ${tally.knowledge} 条`
|
|
8044
8318
|
: '未用模型:正文按 ## 小节切',
|
|
8045
|
-
tally.skipped > 0 ?
|
|
8319
|
+
tally.skipped > 0 ? `复用未变来源 ${tally.skipped} 份` : options.forceExtract ? '本次为手动强制重抽' : '正在检查新增或变化的来源',
|
|
8046
8320
|
];
|
|
8047
8321
|
if (done < total) {
|
|
8048
8322
|
setStep('knowledge', 'running', knowledgeFacts);
|
|
@@ -9043,7 +9317,7 @@ export class KnowledgeRuntime {
|
|
|
9043
9317
|
* 一次召回十来张卡,而库里可能有上千张。
|
|
9044
9318
|
*/
|
|
9045
9319
|
try {
|
|
9046
|
-
placements = snapshot
|
|
9320
|
+
placements = snapshot ? new Map(snapshot.placements) : store.placementsOf([...new Set(hits.map(hit => hit.entityId))]);
|
|
9047
9321
|
}
|
|
9048
9322
|
catch {
|
|
9049
9323
|
return;
|
|
@@ -9065,7 +9339,7 @@ export class KnowledgeRuntime {
|
|
|
9065
9339
|
const wanted = new Set(perHit.flat().map(row => row.knowledgeId));
|
|
9066
9340
|
if (wanted.size === 0)
|
|
9067
9341
|
return;
|
|
9068
|
-
const rows = snapshot
|
|
9342
|
+
const rows = snapshot
|
|
9069
9343
|
? new Map(snapshot.knowledge.filter(row => wanted.has(row.id) && knowledge !== null)
|
|
9070
9344
|
.flatMap(row => {
|
|
9071
9345
|
// Deleted facts never reappear through a historical snapshot.
|
|
@@ -9159,14 +9433,17 @@ export class KnowledgeRuntime {
|
|
|
9159
9433
|
if (!Number.isInteger(requestedTopN) || requestedTopN < 1 || (options.contextTopN !== undefined && requestedTopN > recallTopK))
|
|
9160
9434
|
return failure(empty(), 'CONTEXT_TOP_N_INVALID', `上下文 top_n 必须是 1 到 ${recallTopK} 的整数`, false);
|
|
9161
9435
|
try {
|
|
9162
|
-
|
|
9436
|
+
if (recallMode !== 'fts')
|
|
9437
|
+
await this.environmentGuard?.('embedding');
|
|
9438
|
+
if (options.rerank !== false && this.config.rerankProvider !== 'none')
|
|
9439
|
+
await this.environmentGuard?.('rerank');
|
|
9163
9440
|
}
|
|
9164
9441
|
catch (cause) {
|
|
9165
9442
|
return failure(empty(), 'ENVIRONMENT_NOT_READY', cause instanceof Error ? cause.message : String(cause), true, '在 Tita 设置中完成环境准备并重新自检。');
|
|
9166
9443
|
}
|
|
9167
9444
|
const derived = await this.derivedHandle();
|
|
9168
9445
|
const liveCards = await this.loadCards();
|
|
9169
|
-
const liveKeys = new Set(liveCards.map(card => cardKey(card)));
|
|
9446
|
+
const liveKeys = new Set(liveCards.filter(card => card.status !== 'deprecated').map(card => cardKey(card)));
|
|
9170
9447
|
const snapshotHash = derived?.artifactHash(COMPILE_SNAPSHOT);
|
|
9171
9448
|
const cached = snapshotHash && this.recallSnapshotCache?.hash === snapshotHash ? this.recallSnapshotCache : undefined;
|
|
9172
9449
|
const raw = cached ? undefined : derived?.artifact(COMPILE_SNAPSHOT);
|
|
@@ -9327,7 +9604,7 @@ export class KnowledgeRuntime {
|
|
|
9327
9604
|
].join('\n');
|
|
9328
9605
|
const reranked = await this.rerankNotes(rerankQuery, hits, this.config.rerankThreshold);
|
|
9329
9606
|
rerankScored = reranked.scored ?? [];
|
|
9330
|
-
hits = reranked.hits
|
|
9607
|
+
hits = reranked.hits;
|
|
9331
9608
|
rerank = reranked.result;
|
|
9332
9609
|
if (reranked.warning)
|
|
9333
9610
|
warnings.push(reranked.warning);
|
|
@@ -9337,7 +9614,7 @@ export class KnowledgeRuntime {
|
|
|
9337
9614
|
}
|
|
9338
9615
|
// 重排换的是相关性顺序,评分加权仍然要压在最上面 —— 否则「review 过的排前面一点」
|
|
9339
9616
|
// 只在关掉重排时成立,而重排默认是开着的。
|
|
9340
|
-
hits = [...hits].sort((left, right) => rankKey(right) - rankKey(left) || left.unitId.localeCompare(right.unitId));
|
|
9617
|
+
hits = [...hits].sort((left, right) => rankKey(right) - rankKey(left) || left.unitId.localeCompare(right.unitId)).slice(0, recallTopK);
|
|
9341
9618
|
// --- R4 · 证据片段 -----------------------------------------------------
|
|
9342
9619
|
const truth = await this.storeHandle();
|
|
9343
9620
|
cards = cards.filter(card => truth?.card(card.entityId) !== undefined && card.sourceRefs.every(ref => truth?.source(ref) !== undefined));
|