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/src/diagnosis/store.ts
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
cardFromRecord, cardsToGraph, CARD_EDGE_KINDS, graphToCards, parseCard, serializeCard,
|
|
8
8
|
RUN_EDGE_KINDS, type DiagnosisCard,
|
|
9
9
|
} from './cards.js'
|
|
10
|
-
import {
|
|
10
|
+
import { caseViewOfRun, parseRunSource, renderRunSource, runReferenceProblems, runToGraph } from './run.js'
|
|
11
11
|
import type {
|
|
12
12
|
AttributionCase, AttributionEdge, AttributionEvent, AttributionGraphData, AttributionNode, AttributionNodeKind, DiagnosisRunRecord,
|
|
13
13
|
} from './types.js'
|
|
@@ -22,7 +22,7 @@ import type { SchemaType } from '../types.js'
|
|
|
22
22
|
* | 目录 | 内容 | 谁是真相 |
|
|
23
23
|
* | --- | --- | --- |
|
|
24
24
|
* | `01_source_raw/` | 手册、复盘与排查原文 | SourceRaw 的唯一真相 |
|
|
25
|
-
* | `02_wiki/diagnosis/` |
|
|
25
|
+
* | `02_wiki/diagnosis/` | 历史诊断卡片 | 仅为旧数据兼容读取;新排查不再写 diagnosis/case 卡片 |
|
|
26
26
|
*
|
|
27
27
|
* `05_diagnosis/` 在 v1.1 撤销了(0021 §0.3)。它同时装着两类东西 —— 「一次排查的产物」
|
|
28
28
|
* (源头)与「五类模板卡」(配置)—— 一个编号段混装两种性质,`rebuild --only` 的语义
|
|
@@ -58,8 +58,8 @@ const LEGACY_RUN_SHARDS: Record<string, AttributionNodeKind> = {
|
|
|
58
58
|
'causes.jsonl': 'cause',
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
/**
|
|
62
|
-
export type DiagnosisSource = 'cards' | 'jsonl' | 'empty'
|
|
61
|
+
/** 读取来源,界面据此说明当前展示的是卡片、记录、旧快照还是空库。 */
|
|
62
|
+
export type DiagnosisSource = 'cards' | 'records' | 'jsonl' | 'empty'
|
|
63
63
|
|
|
64
64
|
export interface DiagnosisSnapshot {
|
|
65
65
|
graph: AttributionGraphData
|
|
@@ -119,20 +119,21 @@ export class DiagnosisStore {
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
/**
|
|
122
|
-
* 载入**内置示例**:一份排查手册 + 三篇复盘 + 三次排查日志 → SourceRaw
|
|
122
|
+
* 载入**内置示例**:一份排查手册 + 三篇复盘 + 三次排查日志 → SourceRaw、可复用诊断卡与只读排查视图。
|
|
123
123
|
*
|
|
124
124
|
* 只有用户明确点了「载入内置示例」才走这里(`ensure()` 不再自动调用)。幂等:
|
|
125
125
|
* `addSource` 按 `externalId`、`writeCard` 按 entityId,点两次不会写出第二份。
|
|
126
126
|
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
* `confirmedBy = user:load-sample`,然后照常走 `writeCard`。这样做的两个好处:
|
|
130
|
-
* 待办区里能看到这批卡片是怎么来的;而「未确认不落 `02_wiki/`」这条规则没有被开一个
|
|
131
|
-
* 后门 —— 例外是**确认者的身份**,不是门的缺失。
|
|
127
|
+
* 示例里的可复用诊断 Knowledge 是预先整理好的演示数据,直接写入当前知识库。
|
|
128
|
+
* 三条排查记录作为 SourceRaw 写入,并由读取层派生只读 Case 视图;不创建 `diagnosis/case` WikiCard。
|
|
132
129
|
*/
|
|
133
130
|
async loadSample(): Promise<{ seeded: boolean; migrated: boolean; problems: string[] }> {
|
|
134
131
|
const { problems: sourceProblems, refs } = await this.writeSources(seedSources())
|
|
135
|
-
const cards = withSourceRefs(
|
|
132
|
+
const cards = withSourceRefs(
|
|
133
|
+
graphToCards(SEED_GRAPH),
|
|
134
|
+
refs,
|
|
135
|
+
SEED_CASES,
|
|
136
|
+
)
|
|
136
137
|
const problems = [...sourceProblems, ...await this.writeCards(cards, 'user:load-sample')]
|
|
137
138
|
return { seeded: true, migrated: false, problems }
|
|
138
139
|
}
|
|
@@ -146,40 +147,26 @@ export class DiagnosisStore {
|
|
|
146
147
|
const { seeded, migrated, problems } = await this.ensure()
|
|
147
148
|
const cards = await this.readCards()
|
|
148
149
|
const events = await this.readEvents()
|
|
150
|
+
const baseGraph = cards.length > 0 ? { nodes: [], edges: [] } : await this.readLegacyGraph()
|
|
151
|
+
const baseCases = cards.length > 0 ? [] : await this.readLegacyCases()
|
|
152
|
+
const built = await this.buildGraph(cards, baseGraph, baseCases)
|
|
153
|
+
const runRecords = (await this.readRuns()).records
|
|
149
154
|
/**
|
|
150
|
-
* 「什么都没有」=
|
|
155
|
+
* 「什么都没有」= 没有卡片、Event、排查日志和旧 jsonl。它是**可以载入内置示例**的唯一条件:
|
|
151
156
|
* 已经有知识(不管哪一代形态)时不该再往里塞示例 —— 那会变成第二份互相矛盾的知识。
|
|
152
157
|
*/
|
|
153
|
-
const canLoadSample = cards.length === 0 && events.length === 0 &&
|
|
154
|
-
|
|
155
|
-
if (cards.length > 0) {
|
|
156
|
-
const built = await this.buildGraph(cards)
|
|
157
|
-
return {
|
|
158
|
-
graph: built.graph,
|
|
159
|
-
cases: built.cases,
|
|
160
|
-
events,
|
|
161
|
-
issues: validateDiagnosis(built.graph),
|
|
162
|
-
seeded,
|
|
163
|
-
migrated,
|
|
164
|
-
source: 'cards',
|
|
165
|
-
canLoadSample: false,
|
|
166
|
-
problems: [...problems, ...built.problems],
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// 还没有卡片:旧 jsonl 就是当前可读的来源;两份都没有就是一个空库。
|
|
171
|
-
const graph = await this.readLegacyGraph()
|
|
172
|
-
const cases = await this.readLegacyCases()
|
|
158
|
+
const canLoadSample = cards.length === 0 && events.length === 0 && runRecords.length === 0
|
|
159
|
+
&& baseGraph.nodes.length === 0 && baseCases.length === 0 && (await this.legacyShardFiles()).length === 0
|
|
173
160
|
return {
|
|
174
|
-
graph,
|
|
175
|
-
cases,
|
|
161
|
+
graph: built.graph,
|
|
162
|
+
cases: built.cases,
|
|
176
163
|
events,
|
|
177
|
-
issues: validateDiagnosis(graph),
|
|
164
|
+
issues: validateDiagnosis(built.graph),
|
|
178
165
|
seeded,
|
|
179
166
|
migrated,
|
|
180
|
-
source: canLoadSample ? 'empty' :
|
|
167
|
+
source: canLoadSample ? 'empty' : cards.length > 0 ? 'cards' : events.length > 0 || runRecords.length > 0 ? 'records' : 'jsonl',
|
|
181
168
|
canLoadSample,
|
|
182
|
-
problems,
|
|
169
|
+
problems: [...problems, ...built.problems],
|
|
183
170
|
}
|
|
184
171
|
}
|
|
185
172
|
|
|
@@ -190,21 +177,27 @@ export class DiagnosisStore {
|
|
|
190
177
|
* 与四条执行边全部来自 `agent_diagnosis_case` 的 SourceRaw。合成一张图是**读**的时候
|
|
191
178
|
* 做的,不是写的时候 —— 写的时候合成,等于让一次执行的结果变成可复用的知识。
|
|
192
179
|
*/
|
|
193
|
-
private async buildGraph(cards: DiagnosisCard[]): Promise<{ graph: AttributionGraphData; cases: AttributionCase[]; problems: string[] }> {
|
|
180
|
+
private async buildGraph(cards: DiagnosisCard[], baseGraph: AttributionGraphData = { nodes: [], edges: [] }, baseCases: AttributionCase[] = []): Promise<{ graph: AttributionGraphData; cases: AttributionCase[]; problems: string[] }> {
|
|
194
181
|
const compiled = cardsToGraph(cards)
|
|
195
182
|
const problems = [...compiled.problems]
|
|
196
|
-
const nodes: AttributionNode[] = [...compiled.graph.nodes]
|
|
197
|
-
const edges: AttributionEdge[] = [...compiled.graph.edges]
|
|
183
|
+
const nodes: AttributionNode[] = [...baseGraph.nodes, ...compiled.graph.nodes]
|
|
184
|
+
const edges: AttributionEdge[] = [...baseGraph.edges, ...compiled.graph.edges]
|
|
185
|
+
const knownCardNodes = [...baseGraph.nodes, ...compiled.graph.nodes]
|
|
186
|
+
const cases = new Map<string, AttributionCase>()
|
|
187
|
+
for (const item of [...baseCases, ...compiled.cases]) cases.set(item.id, item)
|
|
198
188
|
|
|
199
189
|
const runs = await this.readRuns()
|
|
200
190
|
problems.push(...runs.problems)
|
|
201
191
|
for (const record of runs.records) {
|
|
202
192
|
const run = runToGraph(record)
|
|
203
193
|
// 引用完整性要**连这次排查自己产出的 id 一起算**:路径里的 `E1` / `C1` 正是它写下的。
|
|
204
|
-
const known = new Set([...
|
|
194
|
+
const known = new Set([...knownCardNodes.map(node => node.id), ...run.nodes.map(node => node.id)])
|
|
205
195
|
problems.push(...run.problems, ...runReferenceProblems(record, known))
|
|
206
196
|
nodes.push(...run.nodes)
|
|
207
197
|
edges.push(...run.edges)
|
|
198
|
+
const symptomTitle = knownCardNodes.find(node => node.id === record.symptomId)?.title ?? record.symptomId
|
|
199
|
+
const view = caseViewOfRun(record, symptomTitle)
|
|
200
|
+
cases.set(view.id, view)
|
|
208
201
|
}
|
|
209
202
|
|
|
210
203
|
/**
|
|
@@ -219,7 +212,7 @@ export class DiagnosisStore {
|
|
|
219
212
|
nodes.push(...legacyRun.nodes)
|
|
220
213
|
edges.push(...legacyRun.edges)
|
|
221
214
|
|
|
222
|
-
return { graph: dedupe({ nodes, edges }, problems), cases:
|
|
215
|
+
return { graph: dedupe({ nodes, edges }, problems), cases: [...cases.values()], problems }
|
|
223
216
|
}
|
|
224
217
|
|
|
225
218
|
/**
|
|
@@ -247,14 +240,15 @@ export class DiagnosisStore {
|
|
|
247
240
|
|
|
248
241
|
async readGraph(): Promise<AttributionGraphData> {
|
|
249
242
|
const cards = await this.readCards()
|
|
250
|
-
|
|
251
|
-
return await this.
|
|
243
|
+
const baseGraph = cards.length > 0 ? { nodes: [], edges: [] } : await this.readLegacyGraph()
|
|
244
|
+
return (await this.buildGraph(cards, baseGraph)).graph
|
|
252
245
|
}
|
|
253
246
|
|
|
254
247
|
async readCases(): Promise<AttributionCase[]> {
|
|
255
248
|
const cards = await this.readCards()
|
|
256
|
-
|
|
257
|
-
|
|
249
|
+
const baseGraph = cards.length > 0 ? { nodes: [], edges: [] } : await this.readLegacyGraph()
|
|
250
|
+
const baseCases = cards.length > 0 ? [] : await this.readLegacyCases()
|
|
251
|
+
return (await this.buildGraph(cards, baseGraph, baseCases)).cases
|
|
258
252
|
}
|
|
259
253
|
|
|
260
254
|
/* ────────────────────────────────────────────── 卡片:读写 ── */
|
|
@@ -326,15 +320,11 @@ export class DiagnosisStore {
|
|
|
326
320
|
}
|
|
327
321
|
|
|
328
322
|
/**
|
|
329
|
-
*
|
|
330
|
-
*
|
|
331
|
-
*
|
|
332
|
-
* 一次排查的价值就在于「当时是怎么走的」,让它停在待办队列里等于把经验丢掉。
|
|
333
|
-
* 卡片会被标成 `unreviewed`,人在「知识库 → Wiki」里 review 它。
|
|
334
|
-
*
|
|
335
|
-
* 幂等:`externalId` = runId,同一次排查重跑不会写出第二条来源;卡片按 entityId 写入。
|
|
323
|
+
* 一次排查结束只写一条 SourceRaw。案例视图从这条记录派生;Knowledge / Event 的抽取与归一
|
|
324
|
+
* 仍由知识处理阶段执行,避免把同一份排查过程再写成另一张 WikiCard。
|
|
325
|
+
* 幂等:`externalId` = runId,同一次排查重跑不会写出第二条来源。
|
|
336
326
|
*/
|
|
337
|
-
async recordRun(record: DiagnosisRunRecord): Promise<{ sourceId?: string;
|
|
327
|
+
async recordRun(record: DiagnosisRunRecord): Promise<{ sourceId?: string; problems: string[] }> {
|
|
338
328
|
const problems: string[] = []
|
|
339
329
|
const added = await this.host.addSource({
|
|
340
330
|
title: `AI 诊断日志 · ${record.symptomId} · ${record.runId}`,
|
|
@@ -345,18 +335,7 @@ export class DiagnosisStore {
|
|
|
345
335
|
if (added.ok === false) return { problems: [`诊断日志 ${record.runId} 写入失败:${added.error.code} ${added.error.message}`] }
|
|
346
336
|
const sourceId = added.data.sourceId
|
|
347
337
|
|
|
348
|
-
|
|
349
|
-
const draft = caseCardOf(record, symptomTitle)
|
|
350
|
-
const written = await this.host.writeCard({
|
|
351
|
-
entityType: draft.entityType as SchemaType,
|
|
352
|
-
entityId: draft.entityId,
|
|
353
|
-
title: draft.title,
|
|
354
|
-
content: renderCardBody(draft),
|
|
355
|
-
sourceRefs: [sourceId],
|
|
356
|
-
status: 'unreviewed',
|
|
357
|
-
})
|
|
358
|
-
if (written.ok === false) problems.push(`案例卡片 ${draft.entityId} 写入失败:${written.error.code} ${written.error.message}`)
|
|
359
|
-
return { sourceId, ...(written.ok === false ? {} : { entityId: draft.entityId }), problems }
|
|
338
|
+
return { sourceId, problems }
|
|
360
339
|
}
|
|
361
340
|
|
|
362
341
|
/* ────────────────────────────────────────────── 旧知识根迁移 ── */
|
|
@@ -499,8 +478,8 @@ export function renderCardBody(card: DiagnosisCard): string {
|
|
|
499
478
|
/**
|
|
500
479
|
* 给卡片挂出处。
|
|
501
480
|
*
|
|
502
|
-
*
|
|
503
|
-
*
|
|
481
|
+
* 规则是**数据驱动的**,不是凭印象分的:可复用诊断卡优先引用第一篇走过该节点的复盘;
|
|
482
|
+
* 没有经过复盘的节点引用手册。历史 Case 卡仍由旧数据读取,不在新示例里生成。
|
|
504
483
|
*/
|
|
505
484
|
function withSourceRefs(
|
|
506
485
|
cards: DiagnosisCard[],
|
package/src/diagnosis/types.ts
CHANGED
|
@@ -15,7 +15,8 @@ import type { ProblemClassId } from './problem-classes.js'
|
|
|
15
15
|
export type NodeLayer = 'type' | 'reference' | 'instance'
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
18
|
+
* 九类归因图节点。当前 Knowledge 节点可对应 WikiCard;`case` 是排查记录视图,
|
|
19
|
+
* `asset` 与执行产物也不对应 WikiCard。历史 Case WikiCard 仅为兼容数据。
|
|
19
20
|
*
|
|
20
21
|
* **两个名字用的是下划线**(`check_point` / `failure_reason`),卡片类型用连字符
|
|
21
22
|
* (`diagnosis/check-point` / `diagnosis/failure-reason`):kind 是代码里的标识符,
|
|
@@ -187,18 +188,9 @@ export interface AttributionGraphData {
|
|
|
187
188
|
}
|
|
188
189
|
|
|
189
190
|
/**
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
* (`cardsToGraph()` 从卡片把它算出来,`/diagnosis/graph` 把它发给界面),
|
|
194
|
-
* 而示例只是**一份**案例的取值来源。视图要渲染「服务端给的案例」,
|
|
195
|
-
* 那它 import 的形状就不该从示例模块里拿。
|
|
196
|
-
*
|
|
197
|
-
* 两段各有明确消费者,不能合并:
|
|
198
|
-
* - `sections` 是给人读、给向量召回的散文(可 diff、可评审、可追溯);
|
|
199
|
-
* - `path` 是给图和剪枝用的结构(有序、可判真假、带被否定的分支)。
|
|
200
|
-
*
|
|
201
|
-
* 序和枚举一旦写进散文,归并算法就再也读不到它们了 —— 所以路径单独成段。
|
|
191
|
+
* Case 是一次排查记录的只读展示投影,不是持久化实体。
|
|
192
|
+
* `sections` 供人阅读,`path` 保留检查顺序与被否定的分支;来源是 SourceRaw 运行记录。
|
|
193
|
+
* 历史 `diagnosis/case` WikiCard 仍可读取,但新的运行与示例载入不再生成它。
|
|
202
194
|
*/
|
|
203
195
|
export interface CaseSection {
|
|
204
196
|
heading: string
|
|
@@ -223,9 +215,11 @@ export interface AttributionCase {
|
|
|
223
215
|
/** 误导段:最贵的那条知识。没有它,下次还会有人在同一个假设上烧掉四十分钟。 */
|
|
224
216
|
misleading?: { nodeId: string; label: string; wastedMinutes: number; why: string }
|
|
225
217
|
rootCause: string
|
|
218
|
+
/** 排查记录里的根因确认状态;旧案例卡片缺省时按已有内容展示。 */
|
|
219
|
+
rootCauseStatus?: 'confirmed' | 'unconfirmed'
|
|
226
220
|
}
|
|
227
221
|
|
|
228
|
-
/**
|
|
222
|
+
/** 从独立 Event 实体读取的一次具体发生,供智能归因的事件视图展示。 */
|
|
229
223
|
export interface AttributionEvent {
|
|
230
224
|
id: string
|
|
231
225
|
title: string
|
|
@@ -323,8 +317,8 @@ export interface DiagnosisRunCause {
|
|
|
323
317
|
/**
|
|
324
318
|
* 一次排查的完整记录。落到 SourceRaw 正文里的 ```json diagnosis-run 块。
|
|
325
319
|
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
320
|
+
* 它是 Agent 跑完一圈留下的原始记录。Case 视图从这条记录派生;可复用 Knowledge 与 Event
|
|
321
|
+
* 由独立抽取和归一阶段生成,不在这条记录写成第二张卡片。
|
|
328
322
|
*/
|
|
329
323
|
export interface DiagnosisRunRecord {
|
|
330
324
|
/** 本次排查的 runId。同时用作 SourceRaw 的 `externalId`,重跑不产生第二条记录。 */
|
|
@@ -1,28 +1,58 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto'
|
|
2
|
-
import { readdirSync, statSync } from 'node:fs'
|
|
2
|
+
import { readFileSync, readdirSync, statSync } from 'node:fs'
|
|
3
3
|
import { mkdir, writeFile } from 'node:fs/promises'
|
|
4
|
-
import { dirname, isAbsolute, join } from 'node:path'
|
|
4
|
+
import { basename, dirname, isAbsolute, join, resolve } from 'node:path'
|
|
5
5
|
import { embeddingFingerprint } from '../compiler/embedding-identity.js'
|
|
6
6
|
import type { RuntimeKnowledgeConfig } from '../types.js'
|
|
7
7
|
import { installRequiredEnvironment, requirementsFile } from './install.js'
|
|
8
|
-
import { modelOptionsView, parseModelSelection, type ModelSelection } from './model-catalog.js'
|
|
8
|
+
import { EMBEDDING_MODELS, modelOptionsView, parseModelSelection, RERANK_MODELS, type ModelSelection } from './model-catalog.js'
|
|
9
9
|
import { ENVIRONMENT_PROBE_VERSION } from './manifest.js'
|
|
10
10
|
import { runProcess } from './process.js'
|
|
11
11
|
import { readModelSelection, writeModelSelection } from './selection.js'
|
|
12
|
-
import type { EnvironmentItem, EnvironmentItemId, EnvironmentReport, EnvironmentView } from './types.js'
|
|
12
|
+
import type { EnvironmentCurrentModel, EnvironmentItem, EnvironmentItemId, EnvironmentReport, EnvironmentView } from './types.js'
|
|
13
13
|
|
|
14
14
|
export interface EnvironmentOptions {
|
|
15
15
|
resourceRoot: string
|
|
16
16
|
snapshot: () => { config: RuntimeKnowledgeConfig; paths: { python: string; embeddingModel: string; rerankModel: string } }
|
|
17
17
|
probe: (kind: 'embedding' | 'rerank') => Promise<{ ok: boolean; message: string }>
|
|
18
18
|
reload: () => void
|
|
19
|
+
activateSelection?: () => Promise<() => Promise<void>>
|
|
19
20
|
run?: typeof runProcess
|
|
20
21
|
install?: typeof installRequiredEnvironment
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
const labels = { embedding: '向量模型', rerank: '重排模型' }
|
|
25
|
+
const CHECK_FILE = 'environment-last-check.json'
|
|
26
|
+
type ModelCatalogEntry = { id: string; label: string; directory: string; repo: string }
|
|
27
|
+
function currentModel(root: string, path: string, catalog: readonly ModelCatalogEntry[], disabled: boolean, provider?: string): EnvironmentCurrentModel {
|
|
28
|
+
if (disabled) return { state: 'disabled', label: '未启用' }
|
|
29
|
+
if (provider) return { state: 'active', label: `${provider} · ${basename(path) || path || '未指定模型'}` }
|
|
30
|
+
const option = catalog.find(model => isAbsolute(path)
|
|
31
|
+
? resolve(path) === resolve(join(root, 'models', model.directory))
|
|
32
|
+
: path === model.repo)
|
|
33
|
+
if (option) return { state: 'active', id: option.id, label: option.label }
|
|
34
|
+
const name = isAbsolute(path) ? basename(path) : basename(path.replaceAll('\\', '/'))
|
|
35
|
+
return { state: 'active', label: `自定义模型 · ${name || '未指定模型'}` }
|
|
36
|
+
}
|
|
24
37
|
function blankReport(): EnvironmentReport {
|
|
25
|
-
return { state: 'unchecked', required: true, items: (['embedding', 'rerank'] as const).map(id => ({ id, label: labels[id], state: 'unchecked', message: '
|
|
38
|
+
return { state: 'unchecked', required: true, items: (['embedding', 'rerank'] as const).map(id => ({ id, label: labels[id], state: 'unchecked', message: '尚未完成模型推理自检' })) }
|
|
39
|
+
}
|
|
40
|
+
function readCachedReport(resourceRoot: string, fingerprint: string): EnvironmentReport | undefined {
|
|
41
|
+
try {
|
|
42
|
+
const cached = JSON.parse(readFileSync(join(resourceRoot, CHECK_FILE), 'utf8')) as Record<string, unknown>
|
|
43
|
+
if (cached.required !== true || cached.fingerprint !== fingerprint || typeof cached.checkedAt !== 'string'
|
|
44
|
+
|| !Number.isFinite(Date.parse(cached.checkedAt)) || !Array.isArray(cached.items)) return undefined
|
|
45
|
+
const items: EnvironmentReport['items'] = []
|
|
46
|
+
for (const id of ['embedding', 'rerank'] as const) {
|
|
47
|
+
const item = cached.items.find((value): value is Record<string, unknown> => Boolean(value && typeof value === 'object' && (value as Record<string, unknown>).id === id))
|
|
48
|
+
if (!item || item.label !== labels[id] || typeof item.message !== 'string'
|
|
49
|
+
|| !['unchecked', 'ready', 'missing', 'failed'].includes(String(item.state))) return undefined
|
|
50
|
+
items.push({ id, label: labels[id], state: item.state as EnvironmentReport['items'][number]['state'], message: item.message,
|
|
51
|
+
...(typeof item.path === 'string' ? { path: item.path } : {}) })
|
|
52
|
+
}
|
|
53
|
+
return { state: items.every(item => item.state === 'ready') ? 'ready' : 'blocked', required: true,
|
|
54
|
+
checkedAt: cached.checkedAt, fingerprint, items }
|
|
55
|
+
} catch { return undefined }
|
|
26
56
|
}
|
|
27
57
|
function stamp(path: string): unknown { try { const value = statSync(path); return [value.size, value.mtimeMs] } catch { return null } }
|
|
28
58
|
function sitePackagesPath(pythonPath: string): string {
|
|
@@ -52,14 +82,16 @@ export function modelFilesProblem(path: string): string | undefined {
|
|
|
52
82
|
|
|
53
83
|
/** Preparation and readiness are shared by all bases in one deployment. */
|
|
54
84
|
export class KnowledgeEnvironment {
|
|
55
|
-
private report
|
|
85
|
+
private report: EnvironmentReport
|
|
56
86
|
private job?: EnvironmentView['job']
|
|
57
87
|
private checkPending?: Promise<EnvironmentReport>
|
|
58
88
|
private work?: Promise<void>
|
|
59
89
|
private controller?: AbortController
|
|
60
90
|
private checkController?: AbortController
|
|
61
91
|
private disposed = false
|
|
62
|
-
constructor(private readonly options: EnvironmentOptions) {
|
|
92
|
+
constructor(private readonly options: EnvironmentOptions) {
|
|
93
|
+
this.report = readCachedReport(options.resourceRoot, this.fingerprint()) ?? blankReport()
|
|
94
|
+
}
|
|
63
95
|
|
|
64
96
|
private fingerprint(): string {
|
|
65
97
|
const { config, paths } = this.options.snapshot()
|
|
@@ -73,24 +105,34 @@ export class KnowledgeEnvironment {
|
|
|
73
105
|
}
|
|
74
106
|
|
|
75
107
|
view(): EnvironmentView {
|
|
76
|
-
if (!this.checkPending && this.report.
|
|
108
|
+
if (!this.checkPending && this.report.fingerprint && this.report.fingerprint !== this.fingerprint()) {
|
|
109
|
+
if (this.report.state === 'ready') this.report.state = 'unchecked'
|
|
110
|
+
this.report.items = this.report.items.map(item => item.state === 'ready' ? { ...item, state: 'unchecked', message: '自检后配置或资源发生变化,请重新自检' } : item)
|
|
111
|
+
}
|
|
112
|
+
const { config, paths } = this.options.snapshot()
|
|
77
113
|
const selection = readModelSelection(this.options.resourceRoot)
|
|
78
|
-
|
|
114
|
+
const current = {
|
|
115
|
+
embedding: currentModel(this.options.resourceRoot, paths.embeddingModel, EMBEDDING_MODELS, config.embeddingProvider === 'none', config.embeddingProvider === 'ollama' ? 'Ollama' : undefined),
|
|
116
|
+
rerank: currentModel(this.options.resourceRoot, paths.rerankModel, RERANK_MODELS, config.rerankProvider === 'none'),
|
|
117
|
+
}
|
|
118
|
+
return structuredClone({ report: this.report, selection, current, models: modelOptionsView(), ...(this.job ? { job: this.job } : {}) })
|
|
79
119
|
}
|
|
80
120
|
invalidate(): void { if (!this.checkPending) this.report = blankReport() }
|
|
81
121
|
|
|
82
|
-
async requireReady(): Promise<void> {
|
|
122
|
+
async requireReady(kind?: EnvironmentItemId): Promise<void> {
|
|
83
123
|
if (this.disposed) throw new Error('环境服务已关闭')
|
|
84
124
|
if (this.job?.state === 'running') throw new Error('环境正在准备或自检,请等待完成后再编译和召回')
|
|
85
125
|
const view = this.view()
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
126
|
+
// A passing result stays valid while the model, provider, runtime, and device fingerprint stays the same.
|
|
127
|
+
// The operation itself still reports inference failures, and the user can always request a fresh probe.
|
|
128
|
+
const alreadyReady = kind ? view.report.items.find(item => item.id === kind)?.state === 'ready' : view.report.state === 'ready'
|
|
129
|
+
const report = alreadyReady ? view.report : await this.check(true, kind)
|
|
130
|
+
if (kind ? report.items.find(item => item.id === kind)?.state !== 'ready' : report.state !== 'ready') throw new Error(`环境未就绪:${report.items.filter(item => (!kind || item.id === kind) && item.state !== 'ready').map(item => `${item.label}:${item.message}`).join(';')}。请在 Tita 设置中完成环境准备`)
|
|
89
131
|
}
|
|
90
132
|
|
|
91
|
-
check(deep = true): Promise<EnvironmentReport> {
|
|
92
|
-
if (this.checkPending) return this.checkPending
|
|
93
|
-
const pending = this.performCheck(deep)
|
|
133
|
+
check(deep = true, kind?: EnvironmentItemId): Promise<EnvironmentReport> {
|
|
134
|
+
if (this.checkPending) return this.checkPending.then(report => !kind || report.items.find(item => item.id === kind)?.state === 'ready' ? report : this.check(deep, kind))
|
|
135
|
+
const pending = this.performCheck(deep, kind)
|
|
94
136
|
this.checkPending = pending
|
|
95
137
|
void pending.finally(() => { if (this.checkPending === pending) this.checkPending = undefined }).catch(() => undefined)
|
|
96
138
|
return pending
|
|
@@ -100,23 +142,28 @@ export class KnowledgeEnvironment {
|
|
|
100
142
|
this.report.items = this.report.items.map(item => item.id === id ? { ...item, ...patch } : item)
|
|
101
143
|
}
|
|
102
144
|
|
|
103
|
-
private async performCheck(deep: boolean): Promise<EnvironmentReport> {
|
|
145
|
+
private async performCheck(deep: boolean, kind?: EnvironmentItemId): Promise<EnvironmentReport> {
|
|
104
146
|
const checkController = new AbortController()
|
|
105
147
|
this.checkController = checkController
|
|
106
148
|
const signal = this.controller ? AbortSignal.any([this.controller.signal, checkController.signal]) : checkController.signal
|
|
107
149
|
const fingerprint = this.fingerprint()
|
|
108
150
|
const { config, paths } = this.options.snapshot()
|
|
109
|
-
this.report = blankReport()
|
|
151
|
+
if (this.report.fingerprint !== fingerprint) this.report = blankReport()
|
|
110
152
|
this.report.state = 'checking'
|
|
111
|
-
for (const id of ['embedding', 'rerank'] as const) {
|
|
153
|
+
for (const id of kind ? [kind] : ['embedding', 'rerank'] as const) {
|
|
112
154
|
if (signal.aborted || this.disposed) break
|
|
113
155
|
const path = id === 'embedding' ? paths.embeddingModel : paths.rerankModel
|
|
156
|
+
const cached = this.report.fingerprint === fingerprint ? this.report.items.find(item => item.id === id) : undefined
|
|
114
157
|
this.item(id, { path })
|
|
115
158
|
const disabled = id === 'embedding' ? config.embeddingProvider === 'none' : config.rerankProvider === 'none'
|
|
116
159
|
const problem = disabled ? '此能力是必需项,请启用模型'
|
|
117
160
|
: id === 'embedding' && config.embeddingProvider === 'ollama' ? undefined : modelFilesProblem(path)
|
|
118
161
|
if (problem) { this.item(id, { state: 'missing', message: problem }); continue }
|
|
119
|
-
if (!deep) {
|
|
162
|
+
if (!deep) {
|
|
163
|
+
if (cached && cached.state !== 'unchecked' && cached.state !== 'checking') this.item(id, { state: cached.state, message: cached.message })
|
|
164
|
+
else this.item(id, { state: 'unchecked', message: '资源已找到,尚未完成实际推理自检' })
|
|
165
|
+
continue
|
|
166
|
+
}
|
|
120
167
|
this.item(id, { state: 'checking', message: '加载模型并运行推理自检,首次加载可能较慢' })
|
|
121
168
|
try {
|
|
122
169
|
const result = await this.options.probe(id)
|
|
@@ -131,9 +178,15 @@ export class KnowledgeEnvironment {
|
|
|
131
178
|
? { ...item, state: 'unchecked', message: '自检已停止,请重新运行' }
|
|
132
179
|
: item)
|
|
133
180
|
this.report.state = this.report.items.every(item => item.state === 'ready') ? 'ready' : 'blocked'
|
|
134
|
-
this.report.checkedAt = new Date().toISOString()
|
|
181
|
+
if (deep) this.report.checkedAt = new Date().toISOString()
|
|
135
182
|
this.report.fingerprint = fingerprint
|
|
136
183
|
if (this.checkController === checkController) this.checkController = undefined
|
|
184
|
+
if (deep && !signal.aborted && !this.disposed) {
|
|
185
|
+
try {
|
|
186
|
+
await mkdir(this.options.resourceRoot, { recursive: true })
|
|
187
|
+
await writeFile(join(this.options.resourceRoot, CHECK_FILE), JSON.stringify(this.report, null, 2))
|
|
188
|
+
} catch { /* The current process can use the result; a later restart may need to probe again. */ }
|
|
189
|
+
}
|
|
137
190
|
return structuredClone(this.report)
|
|
138
191
|
}
|
|
139
192
|
|
|
@@ -151,6 +204,7 @@ export class KnowledgeEnvironment {
|
|
|
151
204
|
const job = this.job
|
|
152
205
|
const log = (line: string): void => { job.logs = [...job.logs, line.slice(-1500)].slice(-12) }
|
|
153
206
|
const work = async (): Promise<void> => {
|
|
207
|
+
let rollbackSettings: (() => Promise<void>) | undefined
|
|
154
208
|
try {
|
|
155
209
|
// A first-use guard can already be checking. Do not install into that probe.
|
|
156
210
|
if (this.checkPending) await this.checkPending
|
|
@@ -164,16 +218,18 @@ export class KnowledgeEnvironment {
|
|
|
164
218
|
const id = label.includes('向量') ? 'embedding' : label.includes('重排') ? 'rerank' : undefined
|
|
165
219
|
if (id) this.item(id, { state: 'checking', message: label })
|
|
166
220
|
}, log, this.options.run ?? runProcess, selection)
|
|
221
|
+
rollbackSettings = await this.options.activateSelection?.()
|
|
167
222
|
this.options.reload()
|
|
168
223
|
} else this.options.reload()
|
|
169
224
|
job.stage = '运行实际推理自检'
|
|
170
225
|
const report = await this.check(true)
|
|
171
226
|
if (controller.signal.aborted) throw new Error('ENVIRONMENT_CANCELLED')
|
|
172
227
|
if (report.state !== 'ready') throw new Error(report.items.filter(item => item.state !== 'ready').map(item => `${item.label}:${item.message}`).join(';'))
|
|
173
|
-
await mkdir(this.options.resourceRoot, { recursive: true })
|
|
174
|
-
await writeFile(join(this.options.resourceRoot, 'environment-last-check.json'), JSON.stringify(report, null, 2))
|
|
175
228
|
job.state = 'succeeded'; job.stage = '环境已就绪'
|
|
176
229
|
} catch (cause) {
|
|
230
|
+
if (rollbackSettings) {
|
|
231
|
+
try { await rollbackSettings() } catch (rollbackCause) { log(`恢复模型设置失败:${rollbackCause instanceof Error ? rollbackCause.message : String(rollbackCause)}`) }
|
|
232
|
+
}
|
|
177
233
|
if (action === 'install') {
|
|
178
234
|
const activeSelection = readModelSelection(this.options.resourceRoot)
|
|
179
235
|
if (activeSelection.embedding !== previousSelection.embedding || activeSelection.rerank !== previousSelection.rerank) {
|
package/src/environment/types.ts
CHANGED
|
@@ -17,6 +17,11 @@ export interface EnvironmentReport {
|
|
|
17
17
|
fingerprint?: string
|
|
18
18
|
items: EnvironmentItem[]
|
|
19
19
|
}
|
|
20
|
+
export interface EnvironmentCurrentModel {
|
|
21
|
+
id?: string
|
|
22
|
+
label: string
|
|
23
|
+
state: 'active' | 'disabled'
|
|
24
|
+
}
|
|
20
25
|
export interface EnvironmentJob {
|
|
21
26
|
id: string
|
|
22
27
|
state: 'running' | 'succeeded' | 'failed' | 'cancelled'
|
|
@@ -30,5 +35,6 @@ export interface EnvironmentView {
|
|
|
30
35
|
report: EnvironmentReport
|
|
31
36
|
job?: EnvironmentJob
|
|
32
37
|
selection: ModelSelection
|
|
38
|
+
current: { embedding: EnvironmentCurrentModel; rerank: EnvironmentCurrentModel }
|
|
33
39
|
models: { embedding: ModelOptionView[]; rerank: ModelOptionView[] }
|
|
34
40
|
}
|
package/src/events.ts
CHANGED
|
@@ -46,6 +46,18 @@ export interface EventEntity {
|
|
|
46
46
|
updatedAt: string
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
/** Remove the legacy generated Event list from a WikiCard body. References live in card_event. */
|
|
50
|
+
export function stripEventReferenceSection(content: string): string {
|
|
51
|
+
const lines = content.split('\n')
|
|
52
|
+
for (let start = lines.length - 1; start >= 0; start -= 1) {
|
|
53
|
+
if (!/^##\s+关联\s*Events?\s*$|^##\s+关联事件\s*$/.test((lines[start] ?? '').trim())) continue
|
|
54
|
+
let end = start + 1
|
|
55
|
+
while (end < lines.length && !/^#{1,2}\s+/.test(lines[end] ?? '')) end += 1
|
|
56
|
+
lines.splice(start, end - start)
|
|
57
|
+
}
|
|
58
|
+
return lines.join('\n').trim()
|
|
59
|
+
}
|
|
60
|
+
|
|
49
61
|
export function compositeEventIdentity(occurredAt: string | null, coordinate: CardCoordinate, symptom: string | null): string {
|
|
50
62
|
const payload = [occurredAt, coordinate.objectId, coordinate.actionId, coordinate.sceneId, normalizeKnowledge(symptom ?? '')]
|
|
51
63
|
return 'composite:' + createHash('sha256').update(JSON.stringify(payload)).digest('hex').slice(0, 24)
|
package/src/extract-contract.ts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { AXIS_CONTRACT_LINES } from './axis-contract.js'
|
|
16
|
+
import { TABLE_OUTPUT_CONTRACT } from './extract-table-contract.js'
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* 把骨架允许的小节拼成提示词里那一条。
|
|
@@ -37,14 +38,15 @@ function sectionRule(sections: string[] | undefined): string[] {
|
|
|
37
38
|
* 编译链路用 `buildTrialSystem(profileSections)`;试跑面板用默认实例(不下发小节清单),
|
|
38
39
|
* 因为那一层没有「本库骨架」这个概念。
|
|
39
40
|
*/
|
|
40
|
-
export function buildTrialSystem(sections?: string[]): string {
|
|
41
|
-
return trialSystemBody(sectionRule(sections))
|
|
41
|
+
export function buildTrialSystem(sections?: string[], format: 'table' | 'json' = 'table'): string {
|
|
42
|
+
return trialSystemBody(sectionRule(sections), format)
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
const trialSystemBody = (positionRule: string[]): string => [
|
|
45
|
+
const trialSystemBody = (positionRule: string[], format: 'table' | 'json'): string => [
|
|
45
46
|
'你在把一份业务文档拆成知识库要用的内容。知识以 Knowledge 为单位:',
|
|
46
47
|
'一条可独立检索、验证或执行的最小知识单元。',
|
|
47
48
|
'',
|
|
49
|
+
...(format === 'table' ? TABLE_OUTPUT_CONTRACT : [
|
|
48
50
|
'只回一个 JSON 对象,不要散文,不要代码围栏。',
|
|
49
51
|
'',
|
|
50
52
|
'输出形状:',
|
|
@@ -73,6 +75,7 @@ const trialSystemBody = (positionRule: string[]): string => [
|
|
|
73
75
|
' "dropped": [ { "quote": "原文片段", "why": "为什么丢掉" } ]',
|
|
74
76
|
'}',
|
|
75
77
|
'',
|
|
78
|
+
]),
|
|
76
79
|
'# 知识:一条只讲一个单元',
|
|
77
80
|
'- text 是一条能脱离上下文独立阅读的最小知识单元。事实要能判断真假,操作步骤要能被执行。',
|
|
78
81
|
'- 一段原文里有几个独立单元就写几条。不要压成一条,也不要拆碎。',
|
package/src/extract-model.ts
CHANGED
|
@@ -65,6 +65,8 @@ export type ModelCall = (request: {
|
|
|
65
65
|
sessionId?: string
|
|
66
66
|
/** 可选观察本次请求的 provider 用量;普通调用方可以忽略。 */
|
|
67
67
|
onUsage?: (usage: TrialTokenUsage) => void
|
|
68
|
+
/** Public response text only; reasoning chunks are not exposed. */
|
|
69
|
+
onDelta?: (text: string) => void
|
|
68
70
|
}) => Promise<string>
|
|
69
71
|
|
|
70
72
|
/** 归并条目:与规则抽取的 `NormalizedCandidates['merges']` 保持同一形状。 */
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** Kept free of server dependencies so the architecture page shows the real contract. */
|
|
2
|
+
export const TABLE_OUTPUT_CONTRACT = [
|
|
3
|
+
'只返回 TOON 固定列表格,不要 Markdown 围栏或说明。format 必须是 knowledge-table-v1。',
|
|
4
|
+
'每个数组头的 [N] 是本次实际行数;每行必须与 {列名} 的列数和顺序一致。空表写 名称[0]:。',
|
|
5
|
+
'逗号分列。字符串包含逗号、冒号、换行或首尾空格时用双引号包裹;内部双引号写 \\",换行写 \\n,反斜杠写 \\\\。',
|
|
6
|
+
'缺失值写 null。字符串列表 heading_path 和 missing 写为 JSON 数组字符串。',
|
|
7
|
+
'evidence 表每个引文只写一次。其它表的 evidence 列写出处 ID,多个 ID 用空格分隔。所有 ID 必须存在。',
|
|
8
|
+
'以下是列定义和示例;示例内容必须换成当前文档的数据:',
|
|
9
|
+
'format: knowledge-table-v1',
|
|
10
|
+
'subject: 业务主体',
|
|
11
|
+
'knowledge[1]{id,text,object,action,scene,section,order,evidence}:',
|
|
12
|
+
' k1,一条最小知识单元,对象名,动作名,场景名,字段,1,q1',
|
|
13
|
+
'events[1]{id,external_id,title,summary,object,action,scene,occurred_at,symptom,root_cause,root_cause_status,outcome,missing,evidence}:',
|
|
14
|
+
' e1,null,一次具体发生,原文明确的概述,对象名,动作名,场景名,null,null,null,null,null,"[]",q1',
|
|
15
|
+
'timeline[1]{event_id,order,type,description,result,at,evidence}:',
|
|
16
|
+
' e1,1,observation,原文记录的现象,null,null,q1',
|
|
17
|
+
'objects[1]{mention,definition,evidence,existing,parent}:',
|
|
18
|
+
' 对象名,对象的边界和含义,q1,null,null',
|
|
19
|
+
'actions[1]{mention,definition,evidence,existing,parent}:',
|
|
20
|
+
' 动作名,动作的含义,q1,null,null',
|
|
21
|
+
'scenes[1]{mention,definition,evidence,existing,parent}:',
|
|
22
|
+
' 场景名,场景的含义,q1,null,null',
|
|
23
|
+
'evidence[1]{id,quote,heading_path}:',
|
|
24
|
+
' q1,原文逐字引文,"[]"',
|
|
25
|
+
'dropped[0]:',
|
|
26
|
+
'timeline.type 只能是 observation/check/action/result/conclusion;root_cause_status 只能是 confirmed/suspected/null。',
|
|
27
|
+
'dropped 非空时列为 {quote,why}。对象 parent 只填原文有证据的父对象;existing 填已有实体名。',
|
|
28
|
+
'',
|
|
29
|
+
]
|