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
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { decode, encode } from '@toon-format/toon'
|
|
2
|
+
|
|
3
|
+
export const EXTRACTION_CONTRACT_VERSION = 'knowledge-table-v1'
|
|
4
|
+
type Row = Record<string, unknown>
|
|
5
|
+
const object = (value: unknown): Row => value !== null && typeof value === 'object' && !Array.isArray(value) ? value as Row : {}
|
|
6
|
+
const list = (value: unknown): Row[] => Array.isArray(value) ? value.map(object) : []
|
|
7
|
+
const string = (value: unknown): string => typeof value === 'string' ? value : ''
|
|
8
|
+
|
|
9
|
+
/** Strict tabular transport. The decoded result retains the existing extraction JSON model. */
|
|
10
|
+
export function decodeExtractionTable(raw: string): Row {
|
|
11
|
+
const record = object(decode(raw, { strict: true }))
|
|
12
|
+
if (record.format !== EXTRACTION_CONTRACT_VERSION) throw new Error(`表格 format 必须为 ${EXTRACTION_CONTRACT_VERSION}`)
|
|
13
|
+
for (const key of ['knowledge', 'events', 'timeline', 'objects', 'actions', 'scenes', 'evidence', 'dropped']) {
|
|
14
|
+
if (!Array.isArray(record[key])) throw new Error(`表格缺少 ${key};没有内容时写 ${key}[0]:`)
|
|
15
|
+
if ((record[key] as unknown[]).some(row => row === null || typeof row !== 'object' || Array.isArray(row))) throw new Error(`${key} 必须由固定列的记录组成`)
|
|
16
|
+
}
|
|
17
|
+
const columns: Record<string, string[]> = {
|
|
18
|
+
knowledge: ['id', 'text', 'object', 'action', 'scene', 'section', 'order', 'evidence'],
|
|
19
|
+
events: ['id', 'external_id', 'title', 'summary', 'object', 'action', 'scene', 'occurred_at', 'symptom', 'root_cause', 'root_cause_status', 'outcome', 'missing', 'evidence'],
|
|
20
|
+
timeline: ['event_id', 'order', 'type', 'description', 'result', 'at', 'evidence'],
|
|
21
|
+
objects: ['mention', 'definition', 'evidence', 'existing', 'parent'],
|
|
22
|
+
actions: ['mention', 'definition', 'evidence', 'existing', 'parent'],
|
|
23
|
+
scenes: ['mention', 'definition', 'evidence', 'existing', 'parent'],
|
|
24
|
+
evidence: ['id', 'quote', 'heading_path'], dropped: ['quote', 'why'],
|
|
25
|
+
}
|
|
26
|
+
for (const [key, fields] of Object.entries(columns)) {
|
|
27
|
+
for (const row of list(record[key])) {
|
|
28
|
+
if (fields.some(field => !(field in row))) throw new Error(`${key} 缺少固定列`)
|
|
29
|
+
for (const field of fields) {
|
|
30
|
+
if (field === 'order') {
|
|
31
|
+
if (!Number.isInteger(row[field]) || Number(row[field]) < 1) throw new Error(`${key}.order 必须为正整数`)
|
|
32
|
+
} else if (row[field] !== null && typeof row[field] !== 'string') throw new Error(`${key}.${field} 必须为字符串或 null`)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const evidence = new Map<string, Row>()
|
|
37
|
+
for (const row of list(record.evidence)) {
|
|
38
|
+
const id = string(row.id)
|
|
39
|
+
if (!id || evidence.has(id) || !string(row.quote)) throw new Error('出处 ID 重复、为空或缺少 quote')
|
|
40
|
+
let headings: unknown
|
|
41
|
+
try { headings = JSON.parse(string(row.heading_path) || '[]') } catch { throw new Error(`出处 ${id} 的 heading_path 不是字符串数组`) }
|
|
42
|
+
if (!Array.isArray(headings) || headings.some(value => typeof value !== 'string')) throw new Error(`出处 ${id} 的 heading_path 不是字符串数组`)
|
|
43
|
+
evidence.set(id, { quote: row.quote, heading_path: headings })
|
|
44
|
+
}
|
|
45
|
+
const refs = (value: unknown): Row[] => {
|
|
46
|
+
const ids = string(value).trim().split(/\s+/).filter(Boolean)
|
|
47
|
+
return ids.map(id => {
|
|
48
|
+
const source = evidence.get(id)
|
|
49
|
+
if (!source) throw new Error(`引用了不存在的出处 ${id}`)
|
|
50
|
+
return source
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
const assertIds = (rows: Row[], label: string): Set<string> => {
|
|
54
|
+
const ids = new Set<string>()
|
|
55
|
+
for (const row of rows) {
|
|
56
|
+
const id = string(row.id)
|
|
57
|
+
if (!id || ids.has(id)) throw new Error(`${label} 的 id 重复或为空`)
|
|
58
|
+
ids.add(id)
|
|
59
|
+
}
|
|
60
|
+
return ids
|
|
61
|
+
}
|
|
62
|
+
const knowledge = list(record.knowledge)
|
|
63
|
+
const events = list(record.events)
|
|
64
|
+
assertIds(knowledge, 'Knowledge')
|
|
65
|
+
const eventIds = assertIds(events, 'Event')
|
|
66
|
+
if (knowledge.some(row => !string(row.text).trim() || !string(row.evidence).trim())) throw new Error('Knowledge 缺少正文或出处')
|
|
67
|
+
const orders = new Set<string>()
|
|
68
|
+
for (const row of list(record.timeline)) {
|
|
69
|
+
if (!eventIds.has(string(row.event_id))) throw new Error('timeline 引用了不存在的 Event')
|
|
70
|
+
const key = `${row.event_id}:${row.order}`
|
|
71
|
+
if (orders.has(key) || !string(row.description).trim() || !['observation', 'check', 'action', 'result', 'conclusion'].includes(string(row.type))) throw new Error('timeline 顺序重复、描述为空或类型不合法')
|
|
72
|
+
orders.add(key)
|
|
73
|
+
}
|
|
74
|
+
for (const row of events) if (!string(row.title).trim() || (row.root_cause_status !== null && !['confirmed', 'suspected'].includes(string(row.root_cause_status)))) throw new Error('Event 标题为空或根因状态不合法')
|
|
75
|
+
return {
|
|
76
|
+
subject: record.subject,
|
|
77
|
+
knowledge: knowledge.map(row => ({ ...row, sources: refs(row.evidence) })),
|
|
78
|
+
events: events.map(row => {
|
|
79
|
+
let missing: unknown
|
|
80
|
+
try { missing = JSON.parse(string(row.missing) || '[]') } catch { throw new Error(`Event ${row.id} 的 missing 不是字符串数组`) }
|
|
81
|
+
if (!Array.isArray(missing) || missing.some(value => typeof value !== 'string')) throw new Error(`Event ${row.id} 的 missing 不是字符串数组`)
|
|
82
|
+
return {
|
|
83
|
+
...row,
|
|
84
|
+
coordinate: { object_mention: row.object, action_mention: row.action, scene_mention: row.scene },
|
|
85
|
+
root_cause: row.root_cause === null || row.root_cause === '' ? null : { text: row.root_cause, status: row.root_cause_status },
|
|
86
|
+
missing,
|
|
87
|
+
sources: refs(row.evidence),
|
|
88
|
+
timeline: list(record.timeline).filter(step => step.event_id === row.id).sort((a, b) => Number(a.order) - Number(b.order)).map(step => ({ ...step, sources: refs(step.evidence) })),
|
|
89
|
+
}
|
|
90
|
+
}),
|
|
91
|
+
...Object.fromEntries(['objects', 'actions', 'scenes'].map(key => [key, list(record[key]).map(row => ({ ...row, parent_mention: row.parent, evidence: refs(row.evidence).map(source => source.quote).join('\n') }))])),
|
|
92
|
+
dropped: record.dropped,
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Used by the format benchmark and fixtures; evidence is emitted once and referenced by ID. */
|
|
97
|
+
export function encodeExtractionTable(value: Row): string {
|
|
98
|
+
const evidence: Row[] = []
|
|
99
|
+
const byQuote = new Map<string, string>()
|
|
100
|
+
const references = (sources: unknown): string => list(sources).map(source => {
|
|
101
|
+
const key = JSON.stringify([source.quote, source.heading_path ?? []])
|
|
102
|
+
const existing = byQuote.get(key)
|
|
103
|
+
if (existing) return existing
|
|
104
|
+
const id = `q${evidence.length + 1}`
|
|
105
|
+
evidence.push({ id, quote: source.quote, heading_path: JSON.stringify(source.heading_path ?? []) })
|
|
106
|
+
byQuote.set(key, id)
|
|
107
|
+
return id
|
|
108
|
+
}).join(' ')
|
|
109
|
+
const timeline: Row[] = []
|
|
110
|
+
const table: Row = {
|
|
111
|
+
format: EXTRACTION_CONTRACT_VERSION,
|
|
112
|
+
subject: value.subject ?? '',
|
|
113
|
+
knowledge: list(value.knowledge).map(row => ({ id: row.id, text: row.text, object: row.object_mention ?? row.object ?? null, action: row.action_mention ?? row.action ?? null, scene: row.scene_mention ?? row.scene ?? null, section: row.section ?? '字段', order: row.order ?? 1, evidence: references(row.sources) })),
|
|
114
|
+
events: list(value.events).map(row => {
|
|
115
|
+
list(row.timeline).forEach((step, index) => timeline.push({ event_id: row.id, order: index + 1, type: step.type, description: step.description, result: step.result ?? null, at: step.at ?? null, evidence: references(step.sources) }))
|
|
116
|
+
const coordinate = object(row.coordinate)
|
|
117
|
+
const cause = object(row.root_cause)
|
|
118
|
+
return { id: row.id, external_id: row.external_id ?? null, title: row.title, summary: row.summary, object: coordinate.object_mention ?? null, action: coordinate.action_mention ?? null, scene: coordinate.scene_mention ?? null, occurred_at: row.occurred_at ?? null, symptom: row.symptom ?? null, root_cause: cause.text ?? null, root_cause_status: cause.status ?? null, outcome: row.outcome ?? null, missing: JSON.stringify(row.missing ?? []), evidence: references(row.sources) }
|
|
119
|
+
}),
|
|
120
|
+
timeline,
|
|
121
|
+
...Object.fromEntries(['objects', 'actions', 'scenes'].map(key => [key, list(value[key]).map(row => ({ mention: row.mention, definition: row.definition ?? '', evidence: row.evidence ? references([{ quote: row.evidence, heading_path: [] }]) : '', existing: row.existing ?? null, parent: row.parent_mention ?? row.parent ?? null }))])),
|
|
122
|
+
evidence,
|
|
123
|
+
dropped: value.dropped ?? [],
|
|
124
|
+
}
|
|
125
|
+
return encode(table)
|
|
126
|
+
}
|
package/src/extraction-runs.ts
CHANGED
|
@@ -122,6 +122,12 @@ export class ExtractionRunStore {
|
|
|
122
122
|
return this.db !== null
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
/** Shared audit connection; callers must not close it. */
|
|
126
|
+
connection(): SqliteDatabase {
|
|
127
|
+
if (!this.db) throw new Error('EXTRACTION_RUNS_UNAVAILABLE')
|
|
128
|
+
return this.db
|
|
129
|
+
}
|
|
130
|
+
|
|
125
131
|
/** 落一条。报告整份序列化进 `report` 列。 */
|
|
126
132
|
async append(entry: ExtractionRunRecord): Promise<void> {
|
|
127
133
|
if (this.db === null) throw new Error('EXTRACTION_RUNS_UNAVAILABLE')
|
package/src/index.ts
CHANGED
|
@@ -32,6 +32,7 @@ import { DOC_PAGE_CHARS, DOC_PAGE_CHARS_MAX, documentContentPage } from './docum
|
|
|
32
32
|
import type { ModelCall } from './extract-model.js'
|
|
33
33
|
import { runWikiTrial, trialSummary } from './wiki-trial.js'
|
|
34
34
|
import { extractionRunId } from './extraction-runs.js'
|
|
35
|
+
import type { PipelineStage } from './pipeline/types.js'
|
|
35
36
|
import {
|
|
36
37
|
agentLayout, defaultAgentRoot, ensureAgentLayout, intentRoot, knowledgeBaseRoot,
|
|
37
38
|
memoryRoot, presetSourceDir, resourceRegistry, resourceRoot, type AgentLayout,
|
|
@@ -474,7 +475,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
474
475
|
}
|
|
475
476
|
const provider = configured.mode === 'fixed' ? configured.provider : defaultProvider
|
|
476
477
|
const model = configured.mode === 'fixed' ? configured.model : defaultModel
|
|
477
|
-
const effectiveReasoningEffort = configured.mode === 'default' ? reasoningEffort : undefined
|
|
478
|
+
const effectiveReasoningEffort = configured.reasoningEffort || (configured.mode === 'default' ? reasoningEffort : undefined)
|
|
478
479
|
if (!provider || !model) {
|
|
479
480
|
return { why: `编译模型没有选好(provider=${provider ?? '空'} / model=${model ?? '空'})` }
|
|
480
481
|
}
|
|
@@ -482,7 +483,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
482
483
|
provider,
|
|
483
484
|
model,
|
|
484
485
|
...(effectiveReasoningEffort ? { reasoningEffort: effectiveReasoningEffort } : {}),
|
|
485
|
-
call: async ({ system, prompt, sessionId, onUsage }) => {
|
|
486
|
+
call: async ({ system, prompt, sessionId, onUsage, onDelta }) => {
|
|
486
487
|
/**
|
|
487
488
|
* 先走 DSH 的公开解析接口,再开始发 prompt。这样固定模型过期、默认模型失效时,
|
|
488
489
|
* 编译会在模型调用边界明确失败,而不是落成一次看似成功的规则抽取。
|
|
@@ -491,7 +492,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
491
492
|
try {
|
|
492
493
|
await llm.resolveCallConfig({
|
|
493
494
|
provider, model,
|
|
494
|
-
...(
|
|
495
|
+
...(effectiveReasoningEffort ? { reasoningEffort: effectiveReasoningEffort } : {}),
|
|
495
496
|
})
|
|
496
497
|
} catch (cause) {
|
|
497
498
|
const detail = cause instanceof Error ? cause.message : String(cause)
|
|
@@ -513,13 +514,13 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
513
514
|
// 而不是留空 —— 留空会在 OpenCode 路由上 400。
|
|
514
515
|
sessionId: sessionId ?? 'knowledge:default',
|
|
515
516
|
})
|
|
516
|
-
return collectModelText(llm.stream(request) as AsyncIterable<never>, onUsage)
|
|
517
|
+
return collectModelText(llm.stream(request) as AsyncIterable<never>, onUsage, onDelta)
|
|
517
518
|
},
|
|
518
519
|
}
|
|
519
520
|
}
|
|
520
521
|
let environment: KnowledgeEnvironment
|
|
521
522
|
const runtimeOptions = (): KnowledgeRuntimeOptions => ({
|
|
522
|
-
environmentGuard:
|
|
523
|
+
environmentGuard: kind => environment.requireReady(kind),
|
|
523
524
|
// 0029:家当目录。配置里三个路径为空时,运行时从这里解析模型与解释器。
|
|
524
525
|
resourceRoot: resourceRoot(config.agentRoot),
|
|
525
526
|
// 0032:记忆段。默认库的记忆落在 `<agentRoot>/memory/default`,与知识树平级。
|
|
@@ -556,7 +557,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
556
557
|
// 0044:非默认库的 runtime 也要拿到模型接缝。只传 `config` 不够 —— 默认库走上面
|
|
557
558
|
// 那个 `new KnowledgeRuntime(... runtimeOptions())`,别的库走注册表这条支路,
|
|
558
559
|
// 两边必须都带上,否则「新建一个库 → 导入文档」静默变成规则抽取。
|
|
559
|
-
runtimeOptions: () => ({ resourceRoot: resourceRoot(config.agentRoot), modelSeam: modelSeamFor, environmentGuard:
|
|
560
|
+
runtimeOptions: () => ({ resourceRoot: resourceRoot(config.agentRoot), modelSeam: modelSeamFor, environmentGuard: kind => environment.requireReady(kind) }),
|
|
560
561
|
})
|
|
561
562
|
compileModelForBase = id => bases.compileModelOf(id)
|
|
562
563
|
runtime.baseId = bases.defaultId
|
|
@@ -566,12 +567,35 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
566
567
|
snapshot: () => ({ config: runtime.config, paths: runtime.resourcePaths() }),
|
|
567
568
|
probe: kind => runtime.environmentProbe(kind),
|
|
568
569
|
reload: () => { runtime.reloadResources(); bases.reloadResources() },
|
|
570
|
+
activateSelection: async () => {
|
|
571
|
+
const keys = ['embeddingProvider', 'embeddingModel', 'rerankProvider', 'rerankModel'] as const
|
|
572
|
+
const previous = Object.fromEntries(keys.map(key => [key, config[key]]))
|
|
573
|
+
const managed = { embeddingProvider: 'wemm-python', embeddingModel: '', rerankProvider: 'bert-python', rerankModel: '' }
|
|
574
|
+
const write = async (values: Record<string, unknown>): Promise<void> => {
|
|
575
|
+
const group = pluginConfig.knowledge as unknown as Record<string, unknown> | undefined
|
|
576
|
+
if (!group) throw new Error('模型设置不可写')
|
|
577
|
+
for (const key of keys) {
|
|
578
|
+
const reference = group[key] as { write?: (value: unknown) => unknown } | undefined
|
|
579
|
+
if (reference && typeof reference.write === 'function') await reference.write(values[key])
|
|
580
|
+
else group[key] = values[key]
|
|
581
|
+
}
|
|
582
|
+
config = normalizeKnowledge(pluginConfig.knowledge)
|
|
583
|
+
runtime.updateConfig(config); bases.updateRuntimeConfig(config)
|
|
584
|
+
}
|
|
585
|
+
try { await write(managed) } catch (cause) { await write(previous); throw cause }
|
|
586
|
+
return () => {
|
|
587
|
+
const current = normalizeKnowledge(pluginConfig.knowledge)
|
|
588
|
+
// A later manual setting change belongs to the user and must survive rollback.
|
|
589
|
+
const restored = Object.fromEntries(keys.map(key => [key, current[key] === managed[key] ? previous[key] : current[key]]))
|
|
590
|
+
return write(restored)
|
|
591
|
+
}
|
|
592
|
+
},
|
|
569
593
|
})
|
|
570
|
-
//
|
|
571
|
-
void environment.check(false).catch(() => undefined)
|
|
594
|
+
// Reuse a matching persisted probe after restart; only stale or missing reports need a resource check.
|
|
595
|
+
if (environment.view().report.state !== 'ready') void environment.check(false).catch(() => undefined)
|
|
572
596
|
|
|
573
|
-
type CompileModelCatalogItem = { provider: string; id: string; name: string; description?: string; available: boolean; current?: boolean }
|
|
574
|
-
const compileModelCatalog = async (kbId: string): Promise<{
|
|
597
|
+
type CompileModelCatalogItem = { provider: string; id: string; name: string; description?: string; available: boolean; current?: boolean; reasoning?: { efforts: Array<{ id: string; name: string; description?: string }>; defaultEffort?: string } }
|
|
598
|
+
const compileModelCatalog = async (kbId: string, queryModelRoute: { provider?: string; model?: string } = {}): Promise<{
|
|
575
599
|
selection: CompileModelSelection
|
|
576
600
|
default: { provider?: string; model?: string; reasoningEffort?: string; available: boolean }
|
|
577
601
|
models: CompileModelCatalogItem[]
|
|
@@ -582,8 +606,10 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
582
606
|
const defaultRoute = service?.currentSelection?.() ?? {}
|
|
583
607
|
const llm = ctx.get('llm') as {
|
|
584
608
|
listModels?: (provider: string) => Promise<Array<{ id: string; name?: string; description?: string }>>
|
|
609
|
+
listProviders?: () => Array<{ id: string; name: string }>
|
|
610
|
+
resolveModelInfo?: (provider: string, model: string) => Promise<{ reasoning?: CompileModelCatalogItem['reasoning'] }>
|
|
585
611
|
} | undefined
|
|
586
|
-
const providers = [...new Set([defaultRoute.provider, compileModelForBase(kbId).provider].filter((value): value is string => Boolean(value)))]
|
|
612
|
+
const providers = [...new Set([...(llm?.listProviders?.() ?? []).map(provider => provider.id), defaultRoute.provider, compileModelForBase(kbId).provider].filter((value): value is string => Boolean(value)))]
|
|
587
613
|
const models: CompileModelCatalogItem[] = []
|
|
588
614
|
const failures: string[] = []
|
|
589
615
|
if (!llm?.listModels) {
|
|
@@ -610,6 +636,17 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
610
636
|
}
|
|
611
637
|
|
|
612
638
|
const selection = compileModelForBase(kbId)
|
|
639
|
+
const effortProvider = selection.mode === 'fixed' ? selection.provider : defaultRoute.provider
|
|
640
|
+
const effortModel = selection.mode === 'fixed' ? selection.model : defaultRoute.model
|
|
641
|
+
const requestedProvider = queryModelRoute.provider || effortProvider
|
|
642
|
+
const requestedModel = queryModelRoute.model || effortModel
|
|
643
|
+
if (requestedProvider && requestedModel && llm?.resolveModelInfo) {
|
|
644
|
+
const item = models.find(model => model.provider === requestedProvider && model.id === requestedModel)
|
|
645
|
+
if (item) {
|
|
646
|
+
try { item.reasoning = (await llm.resolveModelInfo(requestedProvider, requestedModel)).reasoning }
|
|
647
|
+
catch (cause) { failures.push(`思考强度目录:${cause instanceof Error ? cause.message : String(cause)}`) }
|
|
648
|
+
}
|
|
649
|
+
}
|
|
613
650
|
const selected = selection.mode === 'fixed' && selection.provider && selection.model
|
|
614
651
|
? models.find(item => item.provider === selection.provider && item.id === selection.model)
|
|
615
652
|
: undefined
|
|
@@ -783,17 +820,50 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
783
820
|
* 只读、卡片由编译直接写入;`list_card_proposals` 也退役了,因为待办队列不存在了。
|
|
784
821
|
* 新增的两个是**入库之后**的动作:加备注、review 抬分。
|
|
785
822
|
*/
|
|
786
|
-
|
|
823
|
+
ctx.tools.register(tool('run_knowledge_pipeline', 'Run one independent knowledge stage. Import only saves sources by default. Stages: extract Knowledge/Event, normalize existing results, materialize selected complete card groups, build retrieval indexes, or all. Returns a durable background task; inspect it with get_knowledge_pipeline.', {
|
|
824
|
+
stage: { type: 'string', required: true, enum: ['extract', 'normalize', 'materialize', 'build', 'all'] },
|
|
825
|
+
kb: { type: 'string' }, source_ids: { type: 'array', description: 'Optional explicit source IDs; omit for all sources. An empty selection is rejected.' },
|
|
826
|
+
group_keys: { type: 'array', description: 'Optional full card group keys returned by get_knowledge_pipeline.' }, force: { type: 'boolean', description: 'Explicitly rerun the selected stage despite an unchanged checkpoint.' },
|
|
827
|
+
}, async args => withRuntime(args.kb, async target => {
|
|
828
|
+
const pipeline = await target.pipeline()
|
|
829
|
+
const { task } = pipeline.start(args.stage as PipelineStage, { sourceIds: args.source_ids, groupKeys: args.group_keys, force: args.force === true })
|
|
830
|
+
return success({ task, url: `/knowledge/pages/tasks?kb=${encodeURIComponent(target.baseId)}&task=${encodeURIComponent(task.id)}` })
|
|
831
|
+
})))
|
|
832
|
+
ctx.tools.register(tool('get_knowledge_pipeline', 'Read pending stages, materializable card groups, persistent task history, or incremental public model output. Use the returned next cursor as after on the next call.', {
|
|
833
|
+
kb: { type: 'string' }, task_id: { type: 'string' }, after: { type: 'integer' },
|
|
834
|
+
}, async args => withRuntime(args.kb, async (target): Promise<Envelope<Record<string, unknown>>> => {
|
|
835
|
+
const pipeline = await target.pipeline()
|
|
836
|
+
return args.task_id
|
|
837
|
+
? success({ task: pipeline.store.task(args.task_id) ?? null, ...pipeline.store.events(args.task_id, Math.max(0, args.after ?? 0)) })
|
|
838
|
+
: success({ status: await target.pipelineStatus(), ...pipeline.store.tasks() })
|
|
839
|
+
})))
|
|
840
|
+
const writeTools = new Set(['add_source', 'import_sources', 'propose_wiki_cards', 'annotate_wiki_card', 'review_wiki_card', 'set_knowledge_status', 'rebuild_knowledge', 'run_knowledge_pipeline', 'record_feedback'])
|
|
787
841
|
ctx.on('tools/pre-execute', (exec, next): Promise<PreToolDecision> => {
|
|
788
842
|
if (writeTools.has(exec.name)) return Promise.resolve({ kind: 'ask', reason: '该操作会写入 Tita Knowledge Store,需要本轮用户审批。' })
|
|
789
843
|
return next()
|
|
790
844
|
})
|
|
791
|
-
ctx.tools.register(tool('recall_knowledge', 'Recall compiled
|
|
845
|
+
ctx.tools.register(tool('recall_knowledge', 'Recall compiled Knowledge through keyword and vector candidate routes, rerank WikiCard sections, then return cards with matched sections, provenance, and Event reference IDs. Use get_knowledge_events to read referenced Events.', { query: { type: 'string', required: true }, limit: { type: 'integer' }, top_k: { type: 'integer' }, top_n: { type: 'integer' }, rerank: { type: 'boolean' }, context_max_chars: { type: 'integer', description: '这次召回的上下文字符上限;省略则用设置里的值(默认 20000,0 = 不限)。超出部分落在系统临时目录,响应里的 budget.overflow_path 就是它的绝对路径 —— 需要更多信息时直接读那个文件。' }, include_problems: { type: 'boolean' }, mode: { type: 'string', enum: ['fts', 'vector', 'hybrid'] }, kb: { type: 'string' }, session_key: { type: 'string' }, task_id: { type: 'string' }, subtask_id: { type: 'string' }, parent_subtask_id: { type: 'string' }, run_id: { type: 'string' }, turn_id: { type: 'string' }, trace_id: { type: 'string' }, domain: { type: 'string' } }, async (args, exec) => withRuntime(args.kb, target => target.recall(args.query, args.top_k ?? args.limit, args.include_problems ?? false, args.mode as RecallMode | undefined, lineageOf(args, exec), { contextTopN: args.top_n, rerank: args.rerank, ...(typeof args.context_max_chars === 'number' ? { contextMaxChars: args.context_max_chars } : {}) })), {
|
|
792
846
|
call: recallCallView,
|
|
793
847
|
// 查询词从参数来,命中从结果来 —— 两者拼成一张能看懂的账本。
|
|
794
848
|
meta: (args, value) => recallMetaOf({ ...dataOf(value), query: args?.query }),
|
|
795
849
|
result: resultViewOf(recallResultMarkdown),
|
|
796
850
|
}))
|
|
851
|
+
ctx.tools.register(tool('get_knowledge_events', 'Read Event records referenced by recalled WikiCards. Pass event IDs from card metadata.event_refs; the result includes full timelines, evidence, outcomes, and missing fields.', {
|
|
852
|
+
event_ids: { type: 'array', required: true, description: 'Event IDs from WikiCard metadata.event_refs; maximum 50 IDs.' },
|
|
853
|
+
kb: { type: 'string' }, session_key: { type: 'string' }, task_id: { type: 'string' }, subtask_id: { type: 'string' },
|
|
854
|
+
run_id: { type: 'string' }, turn_id: { type: 'string' }, trace_id: { type: 'string' }, domain: { type: 'string' },
|
|
855
|
+
}, async args => withRuntime(args.kb, async target => {
|
|
856
|
+
const ids = [...new Set((Array.isArray(args.event_ids) ? args.event_ids : [])
|
|
857
|
+
.filter((id): id is string => typeof id === 'string' && id.trim() !== '').map(id => id.trim()))]
|
|
858
|
+
if (ids.length === 0) return failure({ events: [], missing_ids: [] }, 'EVENT_IDS_REQUIRED', '请传入 WikiCard 的 event_refs 中的 Event ID。', false)
|
|
859
|
+
if (ids.length > 50) return failure({ events: [], missing_ids: [] }, 'EVENT_IDS_LIMIT', '一次最多读取 50 个 Event ID;请分批读取该 WikiCard 的 event_refs。', false)
|
|
860
|
+
const byId = new Map((await target.loadEvents()).map(event => [event.id, event]))
|
|
861
|
+
const events = ids.flatMap(id => {
|
|
862
|
+
const event = byId.get(id)
|
|
863
|
+
return event ? [event] : []
|
|
864
|
+
})
|
|
865
|
+
return success({ events, missing_ids: ids.filter(id => !byId.has(id)) })
|
|
866
|
+
})))
|
|
797
867
|
ctx.tools.register(tool('check_knowledge_embeddings', 'Inspect the local embedding provider and binary/int8 vector index without loading the model.', { kb: { type: 'string' }, session_key: { type: 'string' } }, async args => withRuntime(args.kb, target => target.embeddingHealth())))
|
|
798
868
|
ctx.tools.register(tool('query_knowledge_graph', 'Query entities, neighbors, paths, lists and relation types in the compiled knowledge graph.', { action: { type: 'string', enum: ['entity', 'neighbors', 'path', 'list', 'types'], required: true }, entity_id: { type: 'string' }, id: { type: 'string' }, entity_type: { type: 'string' }, from_id: { type: 'string' }, to_id: { type: 'string' }, type: { type: 'string' }, status: { type: 'string' }, include_problem: { type: 'boolean' }, limit: { type: 'integer' }, depth: { type: 'integer' }, kb: { type: 'string' }, session_key: { type: 'string' }, task_id: { type: 'string' }, subtask_id: { type: 'string' }, parent_subtask_id: { type: 'string' }, run_id: { type: 'string' }, turn_id: { type: 'string' }, trace_id: { type: 'string' }, domain: { type: 'string' } }, async (args, exec) => withRuntime(args.kb, target => target.graphQuery(args.action, args.entity_id ?? args.id, args.entity_type ?? args.type, args.depth ?? 1, { fromId: args.from_id, toId: args.to_id, status: args.status, includeProblem: args.include_problem ?? false, limit: args.limit ?? 50 }, lineageOf(args, exec)))))
|
|
799
869
|
ctx.tools.register(tool('query_source_truth', 'Read SourceRaw (原始可追溯文档) with access audit. Each call returns up to `limit` matched sources and `max_chars` total characters. If `hasMoreInCurrentPage` is true, continue with `offset += returnedChars`. When it is false and `hasMoreSources` is true, continue with `source_offset = nextSourceOffset` and reset `offset` to 0. `truncated` stays true until both the current source page and all matching source pages are complete. Do not answer from partial content.', { query: { type: 'string', required: true }, limit: { type: 'integer' }, max_chars: { type: 'integer' }, offset: { type: 'integer' }, source_offset: { type: 'integer', description: '来源分页游标;当前页字符已读完后才使用 nextSourceOffset,并将 offset 重置为 0' }, kb: { type: 'string' }, session_key: { type: 'string' }, task_id: { type: 'string' }, subtask_id: { type: 'string' }, run_id: { type: 'string' }, turn_id: { type: 'string' }, trace_id: { type: 'string' }, domain: { type: 'string' } }, async (args, exec) => withRuntime(args.kb, target => target.sourceTruth(args.query, args.limit ?? 20, args.max_chars ?? 40000, lineageOf(args, exec), args.offset ?? 0, args.source_offset ?? 0))))
|
|
@@ -868,7 +938,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
868
938
|
* `compile` 默认开:批量导入的意图几乎总是「进来就能用」。关掉它的场景是「先全部
|
|
869
939
|
* 落盘、稍后统一编译」——那时卡片还没生成,工具返回里会明说 `compile: null`。
|
|
870
940
|
*/
|
|
871
|
-
ctx.tools.register(tool('import_sources', 'Import many SourceRaw documents
|
|
941
|
+
ctx.tools.register(tool('import_sources', 'Import many SourceRaw documents in one call. By default only stores sources. Explicit compile:true runs extract, normalize, materialize, and index publication for the imported documents. Per-source import failures are reported without aborting the batch. DSH requests one user approval before execution.', { sources: { type: 'array', required: true, description: `来源数组,每项 { title?, content, external_id?, metadata?, allow_duplicate_title? };一次最多 ${MAX_IMPORT_SOURCES} 份,超了请分批。标题与已有来源重复的那一份会被拒(SOURCE_TITLE_DUPLICATED),逐条报出来而不是整批失败` }, compile: { type: 'boolean', description: '默认 false:只保存来源;显式 true 才运行完整抽取、归一与物化' }, kb: { type: 'string' }, session_key: { type: 'string' }, task_id: { type: 'string' }, subtask_id: { type: 'string' }, run_id: { type: 'string' }, turn_id: { type: 'string' }, trace_id: { type: 'string' }, domain: { type: 'string' } }, async (args, exec) => withRuntime(args.kb, target => target.importSources({ sources: (Array.isArray(args.sources) ? args.sources : []).map(item => ({ ...(item?.title ? { title: item.title } : {}), content: item?.content, ...(item?.external_id ? { externalId: item.external_id } : {}), ...(item?.metadata ? { metadata: item.metadata } : {}), ...(item?.allow_duplicate_title === true ? { allowDuplicateTitle: true } : {}) })), compile: args.compile === true, lineage: lineageOf(args, exec) })), { call: importCallView, meta: (_args, value) => importMetaOf(dataOf(value)), result: resultViewOf(importResultMarkdown) }))
|
|
872
942
|
/**
|
|
873
943
|
* `propose_wiki_cards` —— 把一批卡片**直接写进 `02_wiki`**(工具名不变,语义在 0044 改了)。
|
|
874
944
|
*
|
|
@@ -924,7 +994,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
924
994
|
lineage: lineageOf(args, exec),
|
|
925
995
|
}))))
|
|
926
996
|
ctx.tools.register(tool('set_knowledge_status', 'Change a Wiki card status. This status belongs to the Card lifecycle; it does not change Knowledge truth status. problem requires problem_note and recovery must pass through unreviewed. DSH requests one user approval before execution.', { entity_id: { type: 'string', required: true }, status: { type: 'string', enum: ['unreviewed', 'draft', 'reviewed', 'active', 'deprecated', 'problem'], required: true }, problem_note: { type: 'string' }, kb: { type: 'string' }, session_key: { type: 'string' }, task_id: { type: 'string' }, subtask_id: { type: 'string' }, run_id: { type: 'string' }, turn_id: { type: 'string' }, trace_id: { type: 'string' }, domain: { type: 'string' } }, async (args, exec) => withRuntime(args.kb, target => target.setStatus(args.entity_id, args.status as CardStatus, args.problem_note, lineageOf(args, exec)))))
|
|
927
|
-
ctx.tools.register(tool('rebuild_knowledge', 'Rebuild schema/wiki/compiled knowledge indexes. DSH requests one user approval before execution.', { only: { type: 'string', enum: ['all', 'schema', 'wiki', 'embeddings'] }, kb: { type: 'string' }, session_key: { type: 'string' }, task_id: { type: 'string' }, subtask_id: { type: 'string' }, run_id: { type: 'string' }, turn_id: { type: 'string' }, trace_id: { type: 'string' }, domain: { type: 'string' } }, async (args, exec) => withRuntime(args.kb, target => target.rebuild(args.only ?? 'all', lineageOf(args, exec))), {
|
|
997
|
+
ctx.tools.register(tool('rebuild_knowledge', 'Rebuild schema/wiki/compiled knowledge indexes from saved cards without document extraction. Use run_knowledge_pipeline for extract, normalize, materialize, or the full chain. DSH requests one user approval before execution.', { only: { type: 'string', enum: ['all', 'schema', 'wiki', 'embeddings'] }, kb: { type: 'string' }, session_key: { type: 'string' }, task_id: { type: 'string' }, subtask_id: { type: 'string' }, run_id: { type: 'string' }, turn_id: { type: 'string' }, trace_id: { type: 'string' }, domain: { type: 'string' } }, async (args, exec) => withRuntime(args.kb, target => target.rebuild(args.only ?? 'all', lineageOf(args, exec), { extract: false })), {
|
|
928
998
|
call: rebuildCallView,
|
|
929
999
|
meta: (args, value) => rebuildMetaOf(args?.only ?? 'all', dataOf(value)),
|
|
930
1000
|
result: resultViewOf(rebuildResultMarkdown),
|
|
@@ -1004,7 +1074,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1004
1074
|
const url = query(req)
|
|
1005
1075
|
const id = kbIdOf(url.searchParams.get('kb'))
|
|
1006
1076
|
if (!bases.get(id)) return sendEnvelope(res, failure({}, 'BASE_NOT_FOUND', `知识库不存在: ${id}`, true, '先 GET /knowledge/bases 拿到可用 id。'))
|
|
1007
|
-
return send(res, 200, success(await compileModelCatalog(id)))
|
|
1077
|
+
return send(res, 200, success(await compileModelCatalog(id, { provider: url.searchParams.get('provider') ?? undefined, model: url.searchParams.get('model') ?? undefined })))
|
|
1008
1078
|
} },
|
|
1009
1079
|
{ kind: 'exact' as const, path: '/knowledge/stats', handler: async (req: IncomingMessage, res: ServerResponse) => {
|
|
1010
1080
|
const resolved = resolveBase(query(req).searchParams.get('kb'))
|
|
@@ -1093,6 +1163,47 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1093
1163
|
if ('failure' in resolved) return sendEnvelope(res, resolved.failure)
|
|
1094
1164
|
return send(res, 200, success({ progress: resolved.runtime.compileProgressView(url.searchParams.get('runId') ?? undefined) }))
|
|
1095
1165
|
} },
|
|
1166
|
+
{ kind: 'exact' as const, path: '/knowledge/pipeline', handler: async (req: IncomingMessage, res: ServerResponse) => {
|
|
1167
|
+
try {
|
|
1168
|
+
const body = req.method === 'POST' ? await readBody(req, 100_000) : {}
|
|
1169
|
+
const resolved = resolveBase(body.kb ?? query(req).searchParams.get('kb'))
|
|
1170
|
+
if ('failure' in resolved) return sendEnvelope(res, resolved.failure)
|
|
1171
|
+
if (req.method === 'GET') return send(res, 200, success(await resolved.runtime.pipelineStatus()))
|
|
1172
|
+
if (req.method !== 'POST') return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '知识处理只接受 GET 或 POST'))
|
|
1173
|
+
const pipeline = await resolved.runtime.pipeline()
|
|
1174
|
+
const started = pipeline.start(body.stage as PipelineStage, { sourceIds: body.sourceIds, groupKeys: body.groupKeys, force: body.force === true })
|
|
1175
|
+
return send(res, 202, success({ task: started.task }))
|
|
1176
|
+
} catch (cause) { return send(res, 400, envelopeError('PIPELINE_INVALID', cause instanceof Error ? cause.message : String(cause))) }
|
|
1177
|
+
} },
|
|
1178
|
+
{ kind: 'exact' as const, path: '/knowledge/pipeline-tasks', handler: async (req: IncomingMessage, res: ServerResponse) => {
|
|
1179
|
+
try {
|
|
1180
|
+
if (req.method !== 'GET') return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '任务历史只接受 GET'))
|
|
1181
|
+
const url = query(req)
|
|
1182
|
+
const resolved = resolveBase(url.searchParams.get('kb'))
|
|
1183
|
+
if ('failure' in resolved) return sendEnvelope(res, resolved.failure)
|
|
1184
|
+
const pipeline = await resolved.runtime.pipeline()
|
|
1185
|
+
const limit = Math.min(100, Math.max(1, Number(url.searchParams.get('limit')) || 20))
|
|
1186
|
+
const offset = Math.max(0, Number(url.searchParams.get('offset')) || 0)
|
|
1187
|
+
return send(res, 200, success({ ...pipeline.store.tasks(limit, offset), extractions: await resolved.runtime.listExtractionRuns({ limit, offset }) }))
|
|
1188
|
+
} catch (cause) { return send(res, 400, envelopeError('PIPELINE_HISTORY_FAILED', cause instanceof Error ? cause.message : String(cause))) }
|
|
1189
|
+
} },
|
|
1190
|
+
{ kind: 'exact' as const, path: '/knowledge/pipeline-task', handler: async (req: IncomingMessage, res: ServerResponse) => {
|
|
1191
|
+
try {
|
|
1192
|
+
const url = query(req)
|
|
1193
|
+
const body = req.method === 'POST' ? await readBody(req, 20_000) : {}
|
|
1194
|
+
const resolved = resolveBase(body.kb ?? url.searchParams.get('kb'))
|
|
1195
|
+
if ('failure' in resolved) return sendEnvelope(res, resolved.failure)
|
|
1196
|
+
const pipeline = await resolved.runtime.pipeline()
|
|
1197
|
+
const id = typeof body.id === 'string' ? body.id : url.searchParams.get('id') ?? ''
|
|
1198
|
+
const task = pipeline.store.task(id)
|
|
1199
|
+
if (!task) return send(res, 404, envelopeError('PIPELINE_NOT_FOUND', '任务不存在'))
|
|
1200
|
+
if (req.method === 'GET') return send(res, 200, success({ task, ...pipeline.store.events(id, Math.max(0, Number(url.searchParams.get('after')) || 0)) }))
|
|
1201
|
+
if (req.method !== 'POST') return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '任务只接受 GET 或 POST'))
|
|
1202
|
+
if (body.action === 'retry') return send(res, 202, success({ task: pipeline.start(task.stage, { ...task.selection, force: false }).task }))
|
|
1203
|
+
if (!['pause', 'resume', 'cancel'].includes(body.action)) throw new Error('不支持的任务操作')
|
|
1204
|
+
return send(res, 200, success({ task: pipeline.control(id, body.action) }))
|
|
1205
|
+
} catch (cause) { return send(res, 409, envelopeError('PIPELINE_TASK_REJECTED', cause instanceof Error ? cause.message : String(cause))) }
|
|
1206
|
+
} },
|
|
1096
1207
|
{ kind: 'exact' as const, path: '/knowledge/extraction-tasks', handler: async (req: IncomingMessage, res: ServerResponse) => {
|
|
1097
1208
|
if (req.method !== 'GET') return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '抽取任务只接受 GET'))
|
|
1098
1209
|
const resolved = resolveBase(query(req).searchParams.get('kb'))
|
|
@@ -1156,7 +1267,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1156
1267
|
const resolved = resolveBase(body.kb ?? query(req).searchParams.get('kb'))
|
|
1157
1268
|
if ('failure' in resolved) return sendEnvelope(res, resolved.failure)
|
|
1158
1269
|
return send(res, 202, success(resolved.runtime.startCompile(body.only ?? 'all', lineageOf(body), {
|
|
1159
|
-
|
|
1270
|
+
extract: body.extract === true,
|
|
1160
1271
|
...(body.forceExtract === true ? { forceExtract: true } : {}),
|
|
1161
1272
|
})))
|
|
1162
1273
|
} catch (cause) {
|
|
@@ -1183,7 +1294,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1183
1294
|
...(item?.external_id ? { externalId: String(item.external_id) } : {}),
|
|
1184
1295
|
...(item?.metadata && typeof item.metadata === 'object' ? { metadata: item.metadata as Record<string, unknown> } : {}),
|
|
1185
1296
|
})),
|
|
1186
|
-
|
|
1297
|
+
compile: body.compile === true,
|
|
1187
1298
|
lineage: lineageOf(body),
|
|
1188
1299
|
}))
|
|
1189
1300
|
} catch (cause) {
|
|
@@ -1276,7 +1387,8 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1276
1387
|
* 任何一条轨迹 —— 「这个实体是哪次抽出来的」就答不上了。
|
|
1277
1388
|
*/
|
|
1278
1389
|
const runId = extractionRunId()
|
|
1279
|
-
const seam = modelSeamFor()
|
|
1390
|
+
const seam = modelSeamFor(resolved.runtime.baseId)
|
|
1391
|
+
const draft = await (await resolved.runtime.axisRegistry())?.draft()
|
|
1280
1392
|
const axesBlock = await resolved.runtime.axisPromptBlockView()
|
|
1281
1393
|
const note = resolved.runtime.extractionNote()
|
|
1282
1394
|
const report = await runWikiTrial({
|
|
@@ -1286,7 +1398,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1286
1398
|
...(typeof body.subject === 'string' && body.subject.trim() ? { subject: body.subject.trim() } : {}),
|
|
1287
1399
|
/** 与编译相同:先查注册表,再把有候选的坐标合并成一次模型补判。 */
|
|
1288
1400
|
resolveAxisSet: axes => resolved.runtime.resolveTrialAxisSet({
|
|
1289
|
-
axes,
|
|
1401
|
+
registry: draft?.registry, axes,
|
|
1290
1402
|
axesBlock,
|
|
1291
1403
|
runId,
|
|
1292
1404
|
call: seam.call,
|
|
@@ -1309,7 +1421,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1309
1421
|
},
|
|
1310
1422
|
...('call' in seam ? { call: seam.call } : {}),
|
|
1311
1423
|
...('why' in seam ? { callWhy: seam.why } : {}),
|
|
1312
|
-
})
|
|
1424
|
+
}).finally(() => draft?.close())
|
|
1313
1425
|
const summary = trialSummary(report)
|
|
1314
1426
|
|
|
1315
1427
|
/**
|
|
@@ -1325,24 +1437,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1325
1437
|
lineage: lineageOf(body),
|
|
1326
1438
|
})
|
|
1327
1439
|
|
|
1328
|
-
|
|
1329
|
-
* 把这次抽出来的 Knowledge 收进真源(0063 §2)。
|
|
1330
|
-
*
|
|
1331
|
-
* **只在跑一份已入库的文档时收。** 理由有两条:来源要指得回一份**真实的**
|
|
1332
|
-
* 文档(临时正文的 `documentId` 是一个标题字符串,存下来就是悬空引用);
|
|
1333
|
-
* 而「这条知识是哪份文档说的」正是它可验证的前提。
|
|
1334
|
-
*
|
|
1335
|
-
* **只收引文核对得上的那些。** 引文对不上原文 = 模型改写了,那条判断不是从
|
|
1336
|
-
* 原文推出来的(§5.7 的引文核验)。把它当知识存进真源,等于让一句编出来的话
|
|
1337
|
-
* 看起来像有出处 —— 而报告里已经把它报成例外了,那里才是它该待的地方。
|
|
1338
|
-
*/
|
|
1339
|
-
const ingest = docId
|
|
1340
|
-
? await resolved.runtime.ingestTrialKnowledge({
|
|
1341
|
-
runId, knowledge: report.knowledge,
|
|
1342
|
-
objects: report.objects, actions: report.actions, scenes: report.scenes,
|
|
1343
|
-
})
|
|
1344
|
-
: { saved: 0, merged: 0, skipped: 0 }
|
|
1345
|
-
return send(res, 200, success({ ...report, summary, run, ingest }))
|
|
1440
|
+
return send(res, 200, success({ ...report, summary, run }))
|
|
1346
1441
|
} catch (cause) {
|
|
1347
1442
|
return send(res, 400, envelopeError('REQUEST_INVALID', cause instanceof Error ? cause.message : String(cause)))
|
|
1348
1443
|
}
|
|
@@ -1696,10 +1791,8 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1696
1791
|
return send(res, 200, success({ cases: (await storeOf(resolved.runtime).snapshot()).cases }))
|
|
1697
1792
|
} },
|
|
1698
1793
|
/**
|
|
1699
|
-
*
|
|
1700
|
-
*
|
|
1701
|
-
* 0044:卡片不再只提议 —— 它直接入库并标成 `unreviewed`。响应给 `sourceId` 与
|
|
1702
|
-
* `entityId`,界面据此说清「记录留下了,卡片也建了,等人 review」。
|
|
1794
|
+
* 一次排查结束只落一条 SourceRaw。Case 视图从记录派生;Knowledge / Event 的抽取、
|
|
1795
|
+
* 归一和 WikiCard 物化由知识处理阶段分别执行。
|
|
1703
1796
|
*/
|
|
1704
1797
|
{ kind: 'exact' as const, path: '/diagnosis/run', handler: async (req: IncomingMessage, res: ServerResponse) => {
|
|
1705
1798
|
if (req.method !== 'POST') return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '该 endpoint 只接受 POST'))
|
|
@@ -1712,9 +1805,9 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1712
1805
|
}
|
|
1713
1806
|
const outcome = await storeOf(resolved.runtime).recordRun(record)
|
|
1714
1807
|
if (outcome.problems.length > 0 && outcome.sourceId === undefined) {
|
|
1715
|
-
return send(res, 200, failure({ sourceId: ''
|
|
1808
|
+
return send(res, 200, failure({ sourceId: '' }, 'RUN_WRITE_FAILED', outcome.problems.join(';'), true))
|
|
1716
1809
|
}
|
|
1717
|
-
return send(res, 200, success({ sourceId: outcome.sourceId ?? ''
|
|
1810
|
+
return send(res, 200, success({ sourceId: outcome.sourceId ?? '' }, outcome.problems))
|
|
1718
1811
|
} },
|
|
1719
1812
|
/**
|
|
1720
1813
|
* 入库之后的两个人工动作(0044):加备注、review 抬分。
|
|
@@ -1862,6 +1955,24 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1862
1955
|
if (rest.length === 2) {
|
|
1863
1956
|
if (method === 'PATCH') {
|
|
1864
1957
|
const body = await readBody(req)
|
|
1958
|
+
if (body.compileModel && typeof body.compileModel === 'object') {
|
|
1959
|
+
const selection = body.compileModel as CompileModelSelection
|
|
1960
|
+
const catalog = await compileModelCatalog(baseId)
|
|
1961
|
+
const provider = selection.mode === 'fixed' ? selection.provider : catalog.default.provider
|
|
1962
|
+
const model = selection.mode === 'fixed' ? selection.model : catalog.default.model
|
|
1963
|
+
if (selection.mode !== 'fixed' && selection.mode !== 'default') return send(res, 400, envelopeError('MODEL_INVALID', '请选择跟随默认或固定模型'))
|
|
1964
|
+
if (selection.mode === 'fixed' && (!provider || !model)) return send(res, 400, envelopeError('MODEL_INVALID', '固定模型需要 provider 和 model'))
|
|
1965
|
+
const llm = ctx.get('llm')
|
|
1966
|
+
if (provider && model && llm?.resolveCallConfig) {
|
|
1967
|
+
try {
|
|
1968
|
+
const info = selection.reasoningEffort ? await llm.resolveModelInfo(provider, model) : undefined
|
|
1969
|
+
const effort = info?.reasoning?.efforts.find(item => item.id === selection.reasoningEffort)
|
|
1970
|
+
if (selection.reasoningEffort && !effort) throw new Error('所选模型不支持这个思考强度,请重新选择')
|
|
1971
|
+
await llm.resolveCallConfig({ provider, model, ...(effort ? { reasoningEffort: effort.id } : {}) })
|
|
1972
|
+
}
|
|
1973
|
+
catch (cause) { return send(res, 400, envelopeError('MODEL_INVALID', cause instanceof Error ? cause.message : String(cause))) }
|
|
1974
|
+
} else if (selection.reasoningEffort) return send(res, 400, envelopeError('MODEL_INVALID', '模型不可用,无法校验思考强度'))
|
|
1975
|
+
}
|
|
1865
1976
|
const updated = await bases.update(baseId, {
|
|
1866
1977
|
name: body.name, description: body.description,
|
|
1867
1978
|
extractionNote: typeof body.extractionNote === 'string' ? body.extractionNote : undefined,
|
package/src/intent/scope.ts
CHANGED
package/src/knowledge-bases.ts
CHANGED
|
@@ -118,12 +118,13 @@ function textOr(input: unknown, fallback: string): string {
|
|
|
118
118
|
|
|
119
119
|
function compileModelOf(input: unknown): CompileModelSelection {
|
|
120
120
|
const value = record(input)
|
|
121
|
-
|
|
121
|
+
const effort = typeof value.reasoningEffort === 'string' && value.reasoningEffort.trim() ? { reasoningEffort: value.reasoningEffort.trim() } : {}
|
|
122
|
+
if (value.mode !== 'fixed') return { ...DEFAULT_COMPILE_MODEL, ...effort }
|
|
122
123
|
const provider = typeof value.provider === 'string' ? value.provider.trim() : ''
|
|
123
124
|
const model = typeof value.model === 'string' ? value.model.trim() : ''
|
|
124
125
|
// 旧版本没有这个字段,或留下了半截配置时,安全地回到 DSH 默认模型。
|
|
125
126
|
if (!provider || !model) return { ...DEFAULT_COMPILE_MODEL }
|
|
126
|
-
return { mode: 'fixed', provider, model }
|
|
127
|
+
return { mode: 'fixed', provider, model, ...effort }
|
|
127
128
|
}
|
|
128
129
|
|
|
129
130
|
function normalizeExt(value: unknown, fallback: string): string {
|