dsh-data-domain-knowledge 0.1.0-alpha.2 → 0.1.0-alpha.4
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 +147 -19
- package/cordis.patch.yml +25 -10
- package/lib/client.js +6400 -3326
- package/lib/doc-page.js +7761 -0
- package/lib/types/convert/host.d.ts.map +1 -1
- package/lib/types/convert/host.js +63 -11
- package/lib/types/convert/host.js.map +1 -1
- package/lib/types/convert/ingest.d.ts +7 -4
- package/lib/types/convert/ingest.d.ts.map +1 -1
- package/lib/types/convert/ingest.js +52 -14
- package/lib/types/convert/ingest.js.map +1 -1
- package/lib/types/convert/types.d.ts +2 -2
- package/lib/types/convert/types.d.ts.map +1 -1
- package/lib/types/convert/types.js +2 -2
- package/lib/types/convert/types.js.map +1 -1
- package/lib/types/diagnosis/cards.d.ts +8 -7
- package/lib/types/diagnosis/cards.d.ts.map +1 -1
- package/lib/types/diagnosis/cards.js +119 -49
- package/lib/types/diagnosis/cards.js.map +1 -1
- package/lib/types/diagnosis/run.d.ts +57 -0
- package/lib/types/diagnosis/run.d.ts.map +1 -0
- package/lib/types/diagnosis/run.js +245 -0
- package/lib/types/diagnosis/run.js.map +1 -0
- package/lib/types/diagnosis/schema.d.ts +25 -2
- package/lib/types/diagnosis/schema.d.ts.map +1 -1
- package/lib/types/diagnosis/schema.js +64 -25
- package/lib/types/diagnosis/schema.js.map +1 -1
- package/lib/types/diagnosis/seed-source.d.ts +17 -2
- package/lib/types/diagnosis/seed-source.d.ts.map +1 -1
- package/lib/types/diagnosis/seed-source.js +19 -2
- package/lib/types/diagnosis/seed-source.js.map +1 -1
- package/lib/types/diagnosis/seed.d.ts +32 -42
- package/lib/types/diagnosis/seed.d.ts.map +1 -1
- package/lib/types/diagnosis/seed.js +187 -51
- package/lib/types/diagnosis/seed.js.map +1 -1
- package/lib/types/diagnosis/store.d.ts +95 -25
- package/lib/types/diagnosis/store.d.ts.map +1 -1
- package/lib/types/diagnosis/store.js +297 -166
- package/lib/types/diagnosis/store.js.map +1 -1
- package/lib/types/diagnosis/types.d.ts +168 -16
- package/lib/types/diagnosis/types.d.ts.map +1 -1
- package/lib/types/diagnosis/types.js +1 -1
- package/lib/types/diagnosis/types.js.map +1 -1
- package/lib/types/document-page.d.ts +39 -0
- package/lib/types/document-page.d.ts.map +1 -0
- package/lib/types/document-page.js +43 -0
- package/lib/types/document-page.js.map +1 -0
- package/lib/types/extract-model.d.ts +67 -27
- package/lib/types/extract-model.d.ts.map +1 -1
- package/lib/types/extract-model.js +225 -97
- package/lib/types/extract-model.js.map +1 -1
- package/lib/types/extract.d.ts +16 -0
- package/lib/types/extract.d.ts.map +1 -1
- package/lib/types/extract.js +19 -2
- package/lib/types/extract.js.map +1 -1
- package/lib/types/fs-atomic.d.ts +11 -0
- package/lib/types/fs-atomic.d.ts.map +1 -0
- package/lib/types/fs-atomic.js +41 -0
- package/lib/types/fs-atomic.js.map +1 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +466 -63
- package/lib/types/index.js.map +1 -1
- package/lib/types/intent/types.js +1 -1
- package/lib/types/intent/types.js.map +1 -1
- package/lib/types/knowledge-bases.d.ts +23 -12
- package/lib/types/knowledge-bases.d.ts.map +1 -1
- package/lib/types/knowledge-bases.js +37 -44
- package/lib/types/knowledge-bases.js.map +1 -1
- package/lib/types/layout.d.ts +3 -3
- package/lib/types/layout.d.ts.map +1 -1
- package/lib/types/layout.js +4 -4
- package/lib/types/layout.js.map +1 -1
- package/lib/types/migrate.d.ts +1 -1
- package/lib/types/migrate.js +2 -2
- package/lib/types/migrate.js.map +1 -1
- package/lib/types/model-text.d.ts +99 -0
- package/lib/types/model-text.d.ts.map +1 -0
- package/lib/types/model-text.js +103 -0
- package/lib/types/model-text.js.map +1 -0
- package/lib/types/present.d.ts +239 -0
- package/lib/types/present.d.ts.map +1 -0
- package/lib/types/present.js +255 -0
- package/lib/types/present.js.map +1 -0
- package/lib/types/recall-rank.d.ts +37 -0
- package/lib/types/recall-rank.d.ts.map +1 -1
- package/lib/types/recall-rank.js +41 -1
- package/lib/types/recall-rank.js.map +1 -1
- package/lib/types/runtime.d.ts +564 -31
- package/lib/types/runtime.d.ts.map +1 -1
- package/lib/types/runtime.js +2645 -292
- package/lib/types/runtime.js.map +1 -1
- package/lib/types/settings-schema.d.ts +4 -2
- package/lib/types/settings-schema.d.ts.map +1 -1
- package/lib/types/settings-schema.js +2 -0
- package/lib/types/settings-schema.js.map +1 -1
- package/lib/types/source-images.d.ts +118 -0
- package/lib/types/source-images.d.ts.map +1 -0
- package/lib/types/source-images.js +288 -0
- package/lib/types/source-images.js.map +1 -0
- package/lib/types/types.d.ts +356 -28
- package/lib/types/types.d.ts.map +1 -1
- package/lib/types/types.js +112 -18
- package/lib/types/types.js.map +1 -1
- package/lib/types/wiki-finalize.d.ts +140 -0
- package/lib/types/wiki-finalize.d.ts.map +1 -0
- package/lib/types/wiki-finalize.js +269 -0
- package/lib/types/wiki-finalize.js.map +1 -0
- package/lib/types/wiki-types.d.ts +10 -2
- package/lib/types/wiki-types.d.ts.map +1 -1
- package/lib/types/wiki-types.js +27 -30
- package/lib/types/wiki-types.js.map +1 -1
- package/package.json +4 -3
- package/preset/tita-data-agent/agent.cordis.yml +25 -10
- package/src/client/DirectoryPicker.tsx +12 -12
- package/src/client/DrawerSection.tsx +18 -0
- package/src/client/InfoHint.tsx +21 -8
- package/src/client/IntentTab.tsx +20 -26
- package/src/client/KnowledgeBaseTab.tsx +458 -158
- package/src/client/KnowledgePanel.tsx +44 -10
- package/src/client/KnowledgeStepRail.tsx +3 -0
- package/src/client/KnowledgeToolRow.tsx +331 -0
- package/src/client/NodeSettings.tsx +20 -22
- package/src/client/PreviewSections.tsx +178 -43
- package/src/client/RecallPanel.tsx +418 -127
- package/src/client/TypeDefinitionDrawer.tsx +154 -54
- package/src/client/WikiCardReview.tsx +266 -0
- package/src/client/WikiFlowCard.tsx +57 -95
- package/src/client/WikiTypeEditor.tsx +197 -95
- package/src/client/arch/AgentArchitectureGraph.tsx +30 -14
- package/src/client/arch/NodeLogicPanel.tsx +6 -3
- package/src/client/arch/model.ts +164 -26
- package/src/client/architecture.css +25 -10
- package/src/client/architecture.tsx +66 -41
- package/src/client/asset-images.ts +125 -0
- package/src/client/attribution/AttributionGraph.tsx +481 -34
- package/src/client/attribution/AttributionTab.tsx +501 -106
- package/src/client/attribution/CaseView.tsx +9 -14
- package/src/client/attribution/GraphStage.tsx +25 -18
- package/src/client/attribution/RunView.tsx +27 -9
- package/src/client/attribution/StageChrome.tsx +280 -140
- package/src/client/attribution/graph-options.ts +611 -47
- package/src/client/attribution/model.ts +559 -30
- package/src/client/convert/ConvertPanel.tsx +36 -4
- package/src/client/doc-page.css +134 -0
- package/src/client/doc-page.tsx +242 -0
- package/src/client/index.ts +18 -0
- package/src/client/panel-state.ts +105 -0
- package/src/client/problem-domains.ts +1 -1
- package/src/client/request.ts +36 -0
- package/src/client/styles.css +397 -54
- package/src/client/useFitScale.ts +15 -2
- package/src/client/wiki-type-groups.ts +86 -0
- package/src/convert/host.ts +68 -9
- package/src/convert/ingest.ts +61 -18
- package/src/convert/types.ts +2 -2
- package/src/diagnosis/cards.ts +120 -50
- package/src/diagnosis/run.ts +251 -0
- package/src/diagnosis/schema.ts +67 -26
- package/src/diagnosis/seed-source.ts +29 -4
- package/src/diagnosis/seed.ts +187 -85
- package/src/diagnosis/store.ts +310 -173
- package/src/diagnosis/types.ts +181 -16
- package/src/document-page.ts +51 -0
- package/src/extract-model.ts +254 -99
- package/src/extract.ts +35 -3
- package/src/fs-atomic.ts +41 -0
- package/src/index.ts +499 -62
- package/src/intent/types.ts +1 -1
- package/src/knowledge-bases.ts +50 -49
- package/src/layout.ts +4 -4
- package/src/migrate.ts +2 -2
- package/src/model-text.ts +126 -0
- package/src/present.ts +367 -0
- package/src/recall-rank.ts +57 -2
- package/src/runtime.ts +2802 -293
- package/src/settings-schema.ts +2 -0
- package/src/source-images.ts +326 -0
- package/src/types.ts +446 -45
- package/src/wiki-finalize.ts +310 -0
- package/src/wiki-types.ts +28 -32
package/src/intent/types.ts
CHANGED
|
@@ -131,7 +131,7 @@ export const DEFAULT_CATEGORIES: Array<IntentCategory & { examples: string[] }>
|
|
|
131
131
|
|
|
132
132
|
export const DEFAULT_PREDICATES: string[] = [
|
|
133
133
|
'query_babel_data', 'query_knowledge', 'query_source_truth', 'query_metadata',
|
|
134
|
-
'query_graph', 'add_source', '
|
|
134
|
+
'query_graph', 'add_source', 'propose_wiki_cards', 'rebuild_knowledge',
|
|
135
135
|
'send_message', 'export_artifact', 'unknown',
|
|
136
136
|
]
|
|
137
137
|
|
package/src/knowledge-bases.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* 多知识库注册表(0021)。
|
|
3
3
|
*
|
|
4
4
|
* 产品参照 WeKnora:**一个知识库 = 独立配置 + 独立内容**。每个库有自己的
|
|
5
|
-
* `rootDir`(沿用既有的 `
|
|
5
|
+
* `rootDir`(沿用既有的 `01_source_raw` / `02_wiki` / `03_compiled` /
|
|
6
6
|
* `04_memory` 布局)和一套「文件类型 → 解析引擎」规则;库下面挂文档列表、
|
|
7
7
|
* 文档详情与版本历史。
|
|
8
8
|
*
|
|
@@ -29,15 +29,15 @@ import { homedir } from 'node:os'
|
|
|
29
29
|
import { dirname, join, resolve } from 'node:path'
|
|
30
30
|
import {
|
|
31
31
|
BUILTIN_SOURCE_TYPES, defaultSourceTypes, FALLBACK_SOURCE_TYPE, isBuiltinSourceType,
|
|
32
|
-
MAX_SOURCE_TYPES, SOURCE_TYPE_ID_PATTERN, validateSourceTypeDraft, validateSourceTypeText, WIKI_TYPE_ID_PATTERN,
|
|
32
|
+
LEGACY_SOURCE_TYPE_IDS, MAX_SOURCE_TYPES, normalizeMetadataFields, SOURCE_TYPE_ID_PATTERN, validateSourceTypeDraft, validateSourceTypeText, WIKI_TYPE_ID_PATTERN,
|
|
33
33
|
type Envelope, type KnowledgeBase, type KnowledgeConfig,
|
|
34
|
-
type SourceTypeDef, type SourceTypeDraft, type
|
|
34
|
+
type SourceTypeDef, type SourceTypeDraft, type WikiTypeDef,
|
|
35
35
|
} from './types.js'
|
|
36
36
|
import {
|
|
37
|
-
defaultWikiTypes, isBuiltinWikiType, parseFieldLines, parseSectionLines, templateCardFor,
|
|
37
|
+
defaultWikiTypes, isBuiltinWikiType, LEGACY_WIKI_TYPE_IDS, parseFieldLines, parseSectionLines, templateCardFor,
|
|
38
38
|
validateWikiTypeDraft, validateWikiTypeText, type WikiTypeDraft,
|
|
39
39
|
} from './wiki-types.js'
|
|
40
|
-
import { defaultKnowledgeRoot, failure, KnowledgeRuntime, success } from './runtime.js'
|
|
40
|
+
import { defaultKnowledgeRoot, failure, KnowledgeRuntime, success, type KnowledgeRuntimeOptions } from './runtime.js'
|
|
41
41
|
import { knowledgeBaseRoot, memoryRoot } from './layout.js'
|
|
42
42
|
|
|
43
43
|
/** 持久化形态:配置 + 计算字段缓存分区。 */
|
|
@@ -50,8 +50,6 @@ interface KnowledgeBaseRecord {
|
|
|
50
50
|
sourceTypes: SourceTypeDef[]
|
|
51
51
|
/** Wiki 卡片类型注册表(0024)。 */
|
|
52
52
|
wikiTypes: WikiTypeDef[]
|
|
53
|
-
/** `report` 只标记问题,`strict` 拒绝不合规的写入。 */
|
|
54
|
-
wikiSchemaMode: WikiSchemaMode
|
|
55
53
|
createdAt: string
|
|
56
54
|
updatedAt: string
|
|
57
55
|
}
|
|
@@ -96,6 +94,16 @@ export interface KnowledgeBasesOptions {
|
|
|
96
94
|
defaultRuntime?: () => KnowledgeRuntime
|
|
97
95
|
/** 非默认库的 runtime 配置来源(embedding / rerank / topK 等跟随插件设置)。 */
|
|
98
96
|
config?: () => Partial<Omit<KnowledgeConfig, 'rootDir' | 'agentRoot'>>
|
|
97
|
+
/**
|
|
98
|
+
* 非默认库的 runtime **附加能力**来源(0044:模型接缝)。
|
|
99
|
+
*
|
|
100
|
+
* 与 `config` 的分工:`config` 是「这个库自己的设置」(embedding / rerank / topK),
|
|
101
|
+
* 而这里给的是「宿主才有的能力」—— 典型是 `modelSeam`,它要问插件所在的 `ctx`
|
|
102
|
+
* 才拿得到 `llm` 与默认模型。之前**只有默认库**的 runtime 带了模型接缝(那一个在
|
|
103
|
+
* `index.ts` 里直接 new),新建的库一律退回规则抽取:正文按 `##` 小节切,
|
|
104
|
+
* 界面上只留一句「运行时没有注入模型接缝」。这跟写库 id 无关,纯粹是构造点漏了。
|
|
105
|
+
*/
|
|
106
|
+
runtimeOptions?: () => Partial<KnowledgeRuntimeOptions>
|
|
99
107
|
/**
|
|
100
108
|
* 覆盖注册表文件位置(默认 `<dataHome>/knowledge-bases.json`)。
|
|
101
109
|
*
|
|
@@ -124,7 +132,7 @@ function normalizeExt(value: unknown, fallback: string): string {
|
|
|
124
132
|
function emptyBase(id: string): KnowledgeBase {
|
|
125
133
|
return {
|
|
126
134
|
id, name: id, description: '', rootDir: '', isDefault: false, sourceTypes: [],
|
|
127
|
-
wikiTypes: [],
|
|
135
|
+
wikiTypes: [], wikiTypesCustomized: false,
|
|
128
136
|
createdAt: '', updatedAt: '', sourceCount: 0, cardCount: 0, noteCount: 0, buildVersion: 0,
|
|
129
137
|
}
|
|
130
138
|
}
|
|
@@ -236,10 +244,12 @@ export class KnowledgeBases {
|
|
|
236
244
|
? this.options.defaultRuntime()
|
|
237
245
|
: new KnowledgeRuntime(base.rootDir, this.options.config?.() ?? {}, {
|
|
238
246
|
sourceTypes: () => this.sourceTypesOf(base.id),
|
|
239
|
-
wikiSchema: () => ({ types: this.wikiTypesOf(base.id)
|
|
247
|
+
wikiSchema: () => ({ types: this.wikiTypesOf(base.id) }),
|
|
240
248
|
// 0032:记忆段跟着**这个库**走,二级用 id。`agentRoot` 没给时(只测注册表
|
|
241
249
|
// 逻辑的单元测试)留空,runtime 会退回知识树里的 `04_memory`。
|
|
242
250
|
...(this.options.agentRoot ? { memoryRoot: memoryRoot(this.options.agentRoot(), base.id) } : {}),
|
|
251
|
+
// 0044:宿主级能力(模型接缝)必须一起带过来,否则非默认库静默退回规则抽取。
|
|
252
|
+
...(this.options.runtimeOptions?.() ?? {}),
|
|
243
253
|
})
|
|
244
254
|
runtime.baseId = base.id
|
|
245
255
|
this.runtimes.set(key, runtime)
|
|
@@ -333,7 +343,6 @@ export class KnowledgeBases {
|
|
|
333
343
|
isDefault: false,
|
|
334
344
|
sourceTypes: defaultSourceTypes(),
|
|
335
345
|
wikiTypes: defaultWikiTypes(),
|
|
336
|
-
wikiSchemaMode: 'report',
|
|
337
346
|
createdAt: now,
|
|
338
347
|
updatedAt: now,
|
|
339
348
|
}
|
|
@@ -354,7 +363,7 @@ export class KnowledgeBases {
|
|
|
354
363
|
* 恢复,并给出 warning)、兜底分类 `document` 不允许停用、新建自定义分类必须
|
|
355
364
|
* 提交完整判定文案。
|
|
356
365
|
*/
|
|
357
|
-
async update(id: string, patch: { name?: string; description?: string; sourceTypes?: SourceTypeDef[]; wikiTypes?: WikiTypeDef[];
|
|
366
|
+
async update(id: string, patch: { name?: string; description?: string; sourceTypes?: SourceTypeDef[]; wikiTypes?: WikiTypeDef[]; resetWikiTypes?: boolean }): Promise<Envelope<KnowledgeBase>> {
|
|
358
367
|
this.ensureLoaded()
|
|
359
368
|
const base = this.records.find(item => item.id === id)
|
|
360
369
|
if (!base) return failure(emptyBase(id), 'BASE_NOT_FOUND', `知识库不存在: ${id}`, true)
|
|
@@ -387,7 +396,6 @@ export class KnowledgeBases {
|
|
|
387
396
|
if (patch.description !== undefined) base.description = String(patch.description)
|
|
388
397
|
if (sourceTypes !== undefined) base.sourceTypes = sourceTypes
|
|
389
398
|
if (wikiTypes !== undefined) base.wikiTypes = wikiTypes
|
|
390
|
-
if (patch.wikiSchemaMode !== undefined) base.wikiSchemaMode = patch.wikiSchemaMode === 'strict' ? 'strict' : 'report'
|
|
391
399
|
base.updatedAt = new Date().toISOString()
|
|
392
400
|
await this.refreshOne(base)
|
|
393
401
|
await this.persist()
|
|
@@ -436,6 +444,7 @@ export class KnowledgeBases {
|
|
|
436
444
|
enabled: draft.enabled !== false,
|
|
437
445
|
builtin: false,
|
|
438
446
|
fallback: false,
|
|
447
|
+
...(normalizeMetadataFields(draft.metadataFields).length > 0 ? { metadataFields: normalizeMetadataFields(draft.metadataFields) } : {}),
|
|
439
448
|
sourceCount: 0,
|
|
440
449
|
}
|
|
441
450
|
base.sourceTypes = [...base.sourceTypes, created]
|
|
@@ -467,10 +476,14 @@ export class KnowledgeBases {
|
|
|
467
476
|
signals: patch.signals ?? existing.signals,
|
|
468
477
|
excludes: patch.excludes ?? existing.excludes,
|
|
469
478
|
enabled: patch.enabled === undefined ? existing.enabled : patch.enabled !== false,
|
|
479
|
+
// 0048:`metadataFields` 缺省继承磁盘上已有的(与 signals 同一条约定:
|
|
480
|
+
// 「没传」不等于「清空」,否则一次改文案就会把字段声明悄悄抹掉)。
|
|
481
|
+
metadataFields: patch.metadataFields ?? existing.metadataFields,
|
|
470
482
|
}
|
|
471
483
|
const known = new Set(base.sourceTypes.map(def => def.id))
|
|
472
484
|
const problem = validateSourceTypeText(merged, known)
|
|
473
485
|
if (!problem.ok) return failure({} as SourceTypeDef, problem.code, problem.message, false, problem.suggestion)
|
|
486
|
+
const declared = normalizeMetadataFields(merged.metadataFields)
|
|
474
487
|
const next: SourceTypeDef = {
|
|
475
488
|
...existing,
|
|
476
489
|
label: String(merged.label).trim(),
|
|
@@ -478,6 +491,7 @@ export class KnowledgeBases {
|
|
|
478
491
|
signals: (merged.signals as unknown[]).map(item => String(item).trim()).filter(Boolean),
|
|
479
492
|
excludes: (merged.excludes as unknown[]).map(item => String(item).trim()).filter(Boolean),
|
|
480
493
|
enabled: merged.enabled === true,
|
|
494
|
+
...(declared.length > 0 ? { metadataFields: declared } : { metadataFields: undefined }),
|
|
481
495
|
}
|
|
482
496
|
base.sourceTypes = base.sourceTypes.map(def => def.id === typeId ? next : def)
|
|
483
497
|
base.updatedAt = new Date().toISOString()
|
|
@@ -549,12 +563,6 @@ export class KnowledgeBases {
|
|
|
549
563
|
return base.wikiTypes.map(def => ({ ...def, cardCount: counts[def.id] ?? 0 }))
|
|
550
564
|
}
|
|
551
565
|
|
|
552
|
-
/** 卡片 schema 生效方式;未知 id 回退出厂值。 */
|
|
553
|
-
wikiSchemaModeOf(id: string): WikiSchemaMode {
|
|
554
|
-
this.ensureLoaded()
|
|
555
|
-
return this.records.find(item => item.id === id)?.wikiSchemaMode ?? 'report'
|
|
556
|
-
}
|
|
557
|
-
|
|
558
566
|
/**
|
|
559
567
|
* 新增一个卡片类型。
|
|
560
568
|
*
|
|
@@ -592,7 +600,6 @@ export class KnowledgeBases {
|
|
|
592
600
|
layer: patch.layer ?? existing.layer,
|
|
593
601
|
sections: patch.sections ?? existing.sections,
|
|
594
602
|
fields: patch.fields ?? existing.fields,
|
|
595
|
-
sourceTypes: patch.sourceTypes ?? existing.sourceTypes,
|
|
596
603
|
examples: patch.examples ?? existing.examples,
|
|
597
604
|
}
|
|
598
605
|
const problem = validateWikiTypeText(merged)
|
|
@@ -606,7 +613,6 @@ export class KnowledgeBases {
|
|
|
606
613
|
layer: merged.layer === 'instance' ? 'instance' : 'type',
|
|
607
614
|
sections,
|
|
608
615
|
fields,
|
|
609
|
-
sourceTypes: this.stringList(merged.sourceTypes),
|
|
610
616
|
examples: this.stringList(merged.examples),
|
|
611
617
|
template: templateCardFor(sections, fields),
|
|
612
618
|
enabled: patch.enabled === undefined ? existing.enabled : patch.enabled !== false,
|
|
@@ -642,17 +648,6 @@ export class KnowledgeBases {
|
|
|
642
648
|
return success({ deleted: true })
|
|
643
649
|
}
|
|
644
650
|
|
|
645
|
-
/** 切换卡片 schema 的生效方式(`report` 只标记,`strict` 拒绝不合规的写入)。 */
|
|
646
|
-
async setWikiSchemaMode(id: string, mode: WikiSchemaMode): Promise<Envelope<{ mode: WikiSchemaMode }>> {
|
|
647
|
-
this.ensureLoaded()
|
|
648
|
-
const base = this.records.find(item => item.id === id)
|
|
649
|
-
if (!base) return failure({ mode }, 'BASE_NOT_FOUND', `知识库不存在: ${id}`, true)
|
|
650
|
-
base.wikiSchemaMode = mode === 'strict' ? 'strict' : 'report'
|
|
651
|
-
base.updatedAt = new Date().toISOString()
|
|
652
|
-
await this.persist()
|
|
653
|
-
return success({ mode: base.wikiSchemaMode })
|
|
654
|
-
}
|
|
655
|
-
|
|
656
651
|
/** 把一条 draft 转成完整类型(模板卡由小节派生,不手写第二份)。 */
|
|
657
652
|
private wikiTypeFromDraft(typeId: string, draft: WikiTypeDraft): WikiTypeDef {
|
|
658
653
|
const sections = parseSectionLines(draft.sections)
|
|
@@ -664,7 +659,6 @@ export class KnowledgeBases {
|
|
|
664
659
|
layer: draft.layer === 'instance' ? 'instance' : 'type',
|
|
665
660
|
sections,
|
|
666
661
|
fields,
|
|
667
|
-
sourceTypes: this.stringList(draft.sourceTypes),
|
|
668
662
|
examples: this.stringList(draft.examples),
|
|
669
663
|
template: templateCardFor(sections, fields),
|
|
670
664
|
enabled: true,
|
|
@@ -683,15 +677,21 @@ export class KnowledgeBases {
|
|
|
683
677
|
*
|
|
684
678
|
* 与 `normalizeSourceTypes` 同一套语义:老配置(没有 `wikiTypes` 字段)升级时补齐内置,
|
|
685
679
|
* 缺失的内置类型被恢复(内置只允许停用,不允许删除)。
|
|
680
|
+
*
|
|
681
|
+
* 0040 的两次改名(`check` → `check_point`、`failure-mode` → `failure_reason`)在这里
|
|
682
|
+
* 也认别名:存量配置里那一项写着旧 id,不认的话它会被当成一个**自定义类型**留下来,
|
|
683
|
+
* 而内置的那一项会被补回来 —— 于是注册表里同时有 `diagnosis/check` 与
|
|
684
|
+
* `diagnosis/check-point` 两个类型,卡片该归哪个就再也说不清了。
|
|
686
685
|
*/
|
|
687
686
|
private normalizeWikiTypes(raw: unknown): WikiTypeDef[] {
|
|
688
687
|
const persisted = Array.isArray(raw) ? raw : []
|
|
689
688
|
const byId = new Map<string, Record<string, unknown>>()
|
|
690
689
|
for (const item of persisted) {
|
|
691
690
|
const value = record(item)
|
|
692
|
-
const
|
|
691
|
+
const rawId = typeof value.id === 'string' ? value.id.trim() : ''
|
|
692
|
+
const id = LEGACY_WIKI_TYPE_IDS[rawId] ?? rawId
|
|
693
693
|
if (!id || !WIKI_TYPE_ID_PATTERN.test(id) || byId.has(id)) continue
|
|
694
|
-
byId.set(id, value)
|
|
694
|
+
byId.set(id, { ...value, id })
|
|
695
695
|
}
|
|
696
696
|
const defaults = defaultWikiTypes()
|
|
697
697
|
const builtins = defaults.map(def => {
|
|
@@ -703,7 +703,7 @@ export class KnowledgeBases {
|
|
|
703
703
|
const customs: WikiTypeDef[] = []
|
|
704
704
|
for (const [id, value] of byId) {
|
|
705
705
|
if (isBuiltinWikiType(id)) continue
|
|
706
|
-
const base: WikiTypeDef = { id, label: id, description: '', layer: 'type', sections: [], fields: [],
|
|
706
|
+
const base: WikiTypeDef = { id, label: id, description: '', layer: 'type', sections: [], fields: [], examples: [], template: '', enabled: true, builtin: false, cardCount: 0 }
|
|
707
707
|
const draft = this.wikiTypeFromRecord(base, value)
|
|
708
708
|
// 只有能写出骨架的自定义类型才值得留下;否则它既没有模板也校验不了。
|
|
709
709
|
if (validateWikiTypeText(draft).ok) customs.push(draft)
|
|
@@ -722,7 +722,6 @@ export class KnowledgeBases {
|
|
|
722
722
|
layer: value.layer === 'instance' ? 'instance' : base.layer,
|
|
723
723
|
sections: sections.length > 0 ? sections : base.sections.map(section => ({ ...section })),
|
|
724
724
|
fields: Array.isArray(value.fields) ? fields : base.fields.map(field => ({ ...field })),
|
|
725
|
-
sourceTypes: Array.isArray(value.sourceTypes) ? this.stringList(value.sourceTypes) : [...base.sourceTypes],
|
|
726
725
|
examples: this.stringList(value.examples).length > 0 ? this.stringList(value.examples) : [...base.examples],
|
|
727
726
|
template: textOr(value.template, base.template),
|
|
728
727
|
enabled: value.enabled !== false,
|
|
@@ -735,7 +734,7 @@ export class KnowledgeBases {
|
|
|
735
734
|
private wikiTypesDifferFromDefaults(types: WikiTypeDef[]): boolean {
|
|
736
735
|
const signature = (list: WikiTypeDef[]): string => JSON.stringify(list.map(def => ({
|
|
737
736
|
id: def.id, label: def.label, description: def.description, layer: def.layer, enabled: def.enabled,
|
|
738
|
-
sections: def.sections, fields: def.fields,
|
|
737
|
+
sections: def.sections, fields: def.fields, examples: def.examples,
|
|
739
738
|
})))
|
|
740
739
|
const defaults = defaultWikiTypes()
|
|
741
740
|
if (types.length !== defaults.length) return true
|
|
@@ -753,7 +752,6 @@ export class KnowledgeBases {
|
|
|
753
752
|
const base = this.records.find(item => item.id === id)
|
|
754
753
|
if (!base) return failure(emptyBase(id), 'BASE_NOT_FOUND', `知识库不存在: ${id}`, true)
|
|
755
754
|
base.wikiTypes = defaultWikiTypes()
|
|
756
|
-
base.wikiSchemaMode = 'report'
|
|
757
755
|
base.updatedAt = new Date().toISOString()
|
|
758
756
|
await this.refreshOne(base)
|
|
759
757
|
await this.persist()
|
|
@@ -783,7 +781,6 @@ export class KnowledgeBases {
|
|
|
783
781
|
isDefault: base.isDefault,
|
|
784
782
|
sourceTypes: base.sourceTypes.map(def => ({ ...def, sourceCount: stats?.sourceTypes?.[def.id] ?? 0 })),
|
|
785
783
|
wikiTypes: base.wikiTypes.map(def => ({ ...def, cardCount: stats?.wikiTypes?.[def.id] ?? 0 })),
|
|
786
|
-
wikiSchemaMode: base.wikiSchemaMode,
|
|
787
784
|
wikiTypesCustomized: this.wikiTypesDifferFromDefaults(base.wikiTypes),
|
|
788
785
|
createdAt: base.createdAt,
|
|
789
786
|
updatedAt: base.updatedAt,
|
|
@@ -804,7 +801,6 @@ export class KnowledgeBases {
|
|
|
804
801
|
isDefault: true,
|
|
805
802
|
sourceTypes: defaultSourceTypes(),
|
|
806
803
|
wikiTypes: defaultWikiTypes(),
|
|
807
|
-
wikiSchemaMode: 'report',
|
|
808
804
|
createdAt: now,
|
|
809
805
|
updatedAt: now,
|
|
810
806
|
}
|
|
@@ -869,7 +865,6 @@ export class KnowledgeBases {
|
|
|
869
865
|
isDefault: value.isDefault === true || id === this.defaultId,
|
|
870
866
|
sourceTypes: this.normalizeSourceTypes(value.sourceTypes),
|
|
871
867
|
wikiTypes: this.normalizeWikiTypes(value.wikiTypes),
|
|
872
|
-
wikiSchemaMode: value.wikiSchemaMode === 'strict' ? 'strict' : 'report',
|
|
873
868
|
createdAt: typeof value.createdAt === 'string' && value.createdAt ? value.createdAt : now,
|
|
874
869
|
updatedAt: typeof value.updatedAt === 'string' && value.updatedAt ? value.updatedAt : now,
|
|
875
870
|
}
|
|
@@ -880,18 +875,22 @@ export class KnowledgeBases {
|
|
|
880
875
|
/**
|
|
881
876
|
* 把磁盘上的 `sourceTypes` 规整成一个**必定包含全部内置分类**的合法注册表。
|
|
882
877
|
*
|
|
883
|
-
*
|
|
878
|
+
* 三个关键行为:
|
|
884
879
|
* 1. 老注册表(没有 `sourceTypes` 字段)升级时自动补上全部内置分类;
|
|
885
|
-
* 2.
|
|
880
|
+
* 2. 任何缺失的内置分类都会被补回来——内置分类只允许停用,不允许删除;
|
|
881
|
+
* 3. **改名过的内置分类按别名认**(`LEGACY_SOURCE_TYPE_IDS`):存量配置里那一项写着
|
|
882
|
+
* 旧 id,不认的话它会被当成一个自定义分类留下来,而新的内置项同时被补回来 ——
|
|
883
|
+
* 于是「注册表里到底有什么」永远说不清。
|
|
886
884
|
*/
|
|
887
885
|
private normalizeSourceTypes(raw: unknown): SourceTypeDef[] {
|
|
888
886
|
const persisted = Array.isArray(raw) ? raw : []
|
|
889
887
|
const byId = new Map<string, Record<string, unknown>>()
|
|
890
888
|
for (const item of persisted) {
|
|
891
889
|
const value = record(item)
|
|
892
|
-
const
|
|
890
|
+
const rawId = typeof value.id === 'string' ? value.id.trim() : ''
|
|
891
|
+
const id = LEGACY_SOURCE_TYPE_IDS[rawId] ?? rawId
|
|
893
892
|
if (!id || !SOURCE_TYPE_ID_PATTERN.test(id) || byId.has(id)) continue
|
|
894
|
-
byId.set(id, value)
|
|
893
|
+
byId.set(id, { ...value, id })
|
|
895
894
|
}
|
|
896
895
|
const defaults = defaultSourceTypes()
|
|
897
896
|
const builtins = defaults.map(def => {
|
|
@@ -928,6 +927,8 @@ export class KnowledgeBases {
|
|
|
928
927
|
enabled: base.fallback ? true : value.enabled !== false,
|
|
929
928
|
builtin: base.builtin,
|
|
930
929
|
fallback: base.fallback,
|
|
930
|
+
// 0048:元数据字段声明跟着分类走。缺省继承默认值(内置分类出厂没有声明)。
|
|
931
|
+
metadataFields: normalizeMetadataFields(value.metadataFields ?? base.metadataFields),
|
|
931
932
|
sourceCount: 0,
|
|
932
933
|
}
|
|
933
934
|
}
|
|
@@ -983,6 +984,7 @@ export class KnowledgeBases {
|
|
|
983
984
|
signals: (draft.signals as unknown[]).map(item => String(item).trim()).filter(Boolean),
|
|
984
985
|
excludes: (draft.excludes as unknown[]).map(item => String(item).trim()).filter(Boolean),
|
|
985
986
|
enabled: seed.fallback ? true : draft.enabled !== false,
|
|
987
|
+
metadataFields: normalizeMetadataFields(value.metadataFields ?? previous.metadataFields),
|
|
986
988
|
sourceCount: 0,
|
|
987
989
|
})
|
|
988
990
|
}
|
|
@@ -1007,6 +1009,9 @@ export class KnowledgeBases {
|
|
|
1007
1009
|
signals: (draft.signals as unknown[]).map(item => String(item).trim()).filter(Boolean),
|
|
1008
1010
|
excludes: (draft.excludes as unknown[]).map(item => String(item).trim()).filter(Boolean),
|
|
1009
1011
|
enabled: draft.enabled !== false, builtin: false, fallback: false, sourceCount: 0,
|
|
1012
|
+
...(normalizeMetadataFields(value.metadataFields ?? previous?.metadataFields).length > 0
|
|
1013
|
+
? { metadataFields: normalizeMetadataFields(value.metadataFields ?? previous?.metadataFields) }
|
|
1014
|
+
: {}),
|
|
1010
1015
|
})
|
|
1011
1016
|
}
|
|
1012
1017
|
if (keptCustoms.length > Math.max(0, MAX_SOURCE_TYPES - BUILTIN_SOURCE_TYPES.length)) {
|
|
@@ -1065,7 +1070,6 @@ export class KnowledgeBases {
|
|
|
1065
1070
|
layer: value.layer === 'instance' ? 'instance' : previous.layer,
|
|
1066
1071
|
sections: Array.isArray(value.sections) ? value.sections : previous.sections,
|
|
1067
1072
|
fields: Array.isArray(value.fields) ? value.fields : previous.fields,
|
|
1068
|
-
sourceTypes: Array.isArray(value.sourceTypes) ? value.sourceTypes : previous.sourceTypes,
|
|
1069
1073
|
examples: Array.isArray(value.examples) ? value.examples : previous.examples,
|
|
1070
1074
|
}
|
|
1071
1075
|
const problem = validateWikiTypeText(draft)
|
|
@@ -1079,7 +1083,6 @@ export class KnowledgeBases {
|
|
|
1079
1083
|
layer: draft.layer === 'instance' ? 'instance' : 'type',
|
|
1080
1084
|
sections,
|
|
1081
1085
|
fields,
|
|
1082
|
-
sourceTypes: this.stringList(draft.sourceTypes),
|
|
1083
1086
|
examples: this.stringList(draft.examples),
|
|
1084
1087
|
template: templateCardFor(sections, fields),
|
|
1085
1088
|
enabled: value.enabled === undefined ? previous.enabled : value.enabled !== false,
|
|
@@ -1098,7 +1101,6 @@ export class KnowledgeBases {
|
|
|
1098
1101
|
layer: value.layer === 'instance' ? 'instance' : previous?.layer ?? 'type',
|
|
1099
1102
|
sections: Array.isArray(value.sections) ? value.sections : previous?.sections ?? [],
|
|
1100
1103
|
fields: Array.isArray(value.fields) ? value.fields : previous?.fields ?? [],
|
|
1101
|
-
sourceTypes: Array.isArray(value.sourceTypes) ? value.sourceTypes : previous?.sourceTypes ?? [],
|
|
1102
1104
|
examples: Array.isArray(value.examples) ? value.examples : previous?.examples ?? [],
|
|
1103
1105
|
}
|
|
1104
1106
|
const problem = previous ? validateWikiTypeText(draft) : validateWikiTypeDraft(draft, new Set(existing.map(def => def.id)))
|
|
@@ -1112,7 +1114,6 @@ export class KnowledgeBases {
|
|
|
1112
1114
|
layer: draft.layer === 'instance' ? 'instance' : 'type',
|
|
1113
1115
|
sections,
|
|
1114
1116
|
fields,
|
|
1115
|
-
sourceTypes: this.stringList(draft.sourceTypes),
|
|
1116
1117
|
examples: this.stringList(draft.examples),
|
|
1117
1118
|
template: templateCardFor(sections, fields),
|
|
1118
1119
|
enabled: value.enabled === undefined ? previous?.enabled ?? true : value.enabled !== false,
|
package/src/layout.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* <agentRoot>/
|
|
15
15
|
* domain_knowledge/ 知识本身(唯一的真相来源,必须备份)
|
|
16
16
|
* <知识库 id>/ 一个知识库 = 一棵独立的树(0021)
|
|
17
|
-
*
|
|
17
|
+
* 01_source_raw/ SourceRaw:原始可追溯文档
|
|
18
18
|
* 02_wiki/ Wiki 卡片(人可读的结论)
|
|
19
19
|
* 03_compiled/ 编译产物:wikiNotes / 向量 / 知识图谱
|
|
20
20
|
* 05_diagnosis/ 智能归因图谱(派生层,可重建)
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
*
|
|
34
34
|
* 1. **`domain_knowledge` 与 `output` / `tmp` 的边界是「能不能重建」**,不是
|
|
35
35
|
* 「谁写的」。知识树里每一样东西要么是别的层重建出来的(`03_compiled`、
|
|
36
|
-
* `05_diagnosis`),要么是不可再生的原始记录(`
|
|
36
|
+
* `05_diagnosis`),要么是不可再生的原始记录(`01_source_raw`、`02_wiki`)——
|
|
37
37
|
* 它们同属一个必须整体备份的单位,所以同在一棵树下、按知识库分叉。`output` 是
|
|
38
38
|
* **交付物**:转换出来的 Markdown 与图片,用户会把它拷走或录入知识库;`tmp` 是
|
|
39
39
|
* **中间态**:转换的工作目录与任务队列快照,删掉最多让一个未完成的任务失败。
|
|
@@ -319,10 +319,10 @@ export function intentRoot(agentRoot: string): string {
|
|
|
319
319
|
* 的空壳(注册表 `create()` 就会先 `mkdir`),空壳不该让迁移误判为「目标已占用」。
|
|
320
320
|
*/
|
|
321
321
|
export function looksLikeKnowledgeTree(path: string): boolean {
|
|
322
|
-
return existsSync(join(path, '
|
|
322
|
+
return existsSync(join(path, '01_source_raw')) || existsSync(join(path, '03_compiled'))
|
|
323
323
|
}
|
|
324
324
|
|
|
325
|
-
/** 旧布局的判据:`
|
|
325
|
+
/** 旧布局的判据:`01_source_raw` 直接躺在被检查的目录下。 */
|
|
326
326
|
export function looksLikeLegacyKnowledgeRoot(path: string): boolean {
|
|
327
327
|
if (!path) return false
|
|
328
328
|
return basename(path) !== '' && looksLikeKnowledgeTree(path)
|
package/src/migrate.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* ## 为什么要迁移
|
|
5
5
|
*
|
|
6
6
|
* 0029 之前,「知识树在哪」由用户直接设的那个 `rootDir` 决定,而它指向的是**知识树
|
|
7
|
-
* 本身** —— `
|
|
7
|
+
* 本身** —— `01_source_raw` 直接躺在它下面。新布局多了一层:`rootDir` 变成
|
|
8
8
|
* `agentRoot`,知识树退到 `<agentRoot>/domain_knowledge/<知识库 id>`。
|
|
9
9
|
*
|
|
10
10
|
* 也就是说,对**改过** `rootDir` 的用户,磁盘上那棵树的位置变了。数据不能跟着
|
|
@@ -43,7 +43,7 @@ import { knowledgeBaseRoot, looksLikeKnowledgeTree, memoryRoot, resourceRoot } f
|
|
|
43
43
|
* `<agentRoot>/memory/<库 id>`,由 `migrateLegacyMemory` 负责搬。它留在这里会让
|
|
44
44
|
* 知识树迁移把 `04_memory` 当成「一段知识」搬进新树 —— 那是搬错方向。
|
|
45
45
|
*/
|
|
46
|
-
const LAYOUT_DIRS = ['
|
|
46
|
+
const LAYOUT_DIRS = ['01_source_raw', '02_wiki', '03_compiled', '05_diagnosis'] as const
|
|
47
47
|
|
|
48
48
|
export interface MigrationOutcome {
|
|
49
49
|
/** `skipped` 无事可做 / `moved` 搬成功 / `failed` 搬失败。 */
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 把平台 `llm` 服务的 chunk 流收成一段文本(0044 实机验证第三批)。
|
|
3
|
+
*
|
|
4
|
+
* ## 为什么单独一个文件
|
|
5
|
+
*
|
|
6
|
+
* 它可以在没有 DSH 上下文的情况下被测试:喂一串假 chunk,看结论对不对。
|
|
7
|
+
* 这段逻辑躲在 `index.ts` 的接缝里时,唯一能验证它的方法是「真跑一次编译再看告警」——
|
|
8
|
+
* 而正是因为没人那样做,下面这个坑活了很久。
|
|
9
|
+
*
|
|
10
|
+
* ## 那个坑:模型失败**不是抛异常**,是一条 finish chunk
|
|
11
|
+
*
|
|
12
|
+
* `LlmRuntime.stream()` 的契约写得很清楚:adapter 选择、派发、迭代的失败都会变成
|
|
13
|
+
* **终结性的 `finish` chunk**(`reason.kind === 'error' | 'aborted'`,里面带
|
|
14
|
+
* `failure.code` / `failure.message`)。只有中间件、嵌套调用这类问题才抛。
|
|
15
|
+
*
|
|
16
|
+
* 第一版接缝只挑 `text-delta` 累加文本,于是:
|
|
17
|
+
*
|
|
18
|
+
* - 模型调用失败(比如默认模型那一行选错了 provider)→ 一个文本都没收到 → 收成空串;
|
|
19
|
+
* - 空串交给 JSON 解析 → 「模型输出不是 JSON 对象」;
|
|
20
|
+
* - **真正的失败原因(code / message)被丢掉了**,人看到的是「模型答非所问」,
|
|
21
|
+
* 而事实是「模型根本没被调到」。
|
|
22
|
+
*
|
|
23
|
+
* 这两种故障的处理方式完全不同:一个是改提示词,一个是去改设置。所以这里把
|
|
24
|
+
* finish chunk 读出来,并在没有任何文本时**主动抛**——抛出去的 message 会原样出现在
|
|
25
|
+
* `rebuild` 的告警里(`extractWithModel` 接到异常后写的是「模型抽取不可用:…」)。
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/** 平台 chunk 的最小形状。只声明这里用得到的字段,避免把平台类型引进来。 */
|
|
29
|
+
interface StreamChunkLike {
|
|
30
|
+
type?: string
|
|
31
|
+
text?: string
|
|
32
|
+
reason?: { kind?: string; failure?: { code?: string; message?: string } }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 组一次模型请求。
|
|
37
|
+
*
|
|
38
|
+
* ## 为什么单独一个函数
|
|
39
|
+
*
|
|
40
|
+
* 实机验证时这里的形状错过两次,而**两次的错误信息都完全指不出这一点**:
|
|
41
|
+
*
|
|
42
|
+
* 1. `messages: [{ role: 'user', content: prompt }]`(content 是字符串)→ 平台在适配器里
|
|
43
|
+
* 对 content 调 `.some()`,整次调用以 `content.some is not a function` 失败。
|
|
44
|
+
* 那句话里既没有 `messages`,也没有 `content` 的类型要求。
|
|
45
|
+
* 2. **没有带 `sessionId`** → OpenCode 系的路由直接回
|
|
46
|
+
* `400 MissingSessionID: Request is missing x-opencode-session`。
|
|
47
|
+
* 这个字段在 `GenerateOptions` 里是可选的,所以类型不报错;而它在
|
|
48
|
+
* 「路由与缓存亲和」这件事上是必需的(`dsh-opencode-session` 插件只在
|
|
49
|
+
* `options.sessionId` 存在时才打那个头,没有就原样放过去)。
|
|
50
|
+
*
|
|
51
|
+
* 平台的 `RequestMessage.content` 是**内容块数组**(`[{ type: 'text', text }]`),
|
|
52
|
+
* 不是字符串。把这些形状固定到一个纯函数里,就有了唯一的去处,也能被测试钉住。
|
|
53
|
+
*
|
|
54
|
+
* ## `sessionId` 给什么值
|
|
55
|
+
*
|
|
56
|
+
* 知识编译不是一段对话,但它确实需要「同一批调用被路由到同一个后端」这个性质:
|
|
57
|
+
* 反复编译同一个知识库时,提示词里有大段的 wiki 类型定义与已有卡片清单,**亲和性直接
|
|
58
|
+
* 影响命中缓存的概率**。所以给一个**按知识库稳定**的值,而不是随机的 UUID。
|
|
59
|
+
*
|
|
60
|
+
* 用 `knowledge:<库 id>` 这个形状而不是裸库 id:它明确说出这不是一个会话,
|
|
61
|
+
* 排查的人一眼能看出这些调用是谁发的(同一个头的值还会出现在上游的日志里)。
|
|
62
|
+
*/
|
|
63
|
+
export function buildModelRequest(input: {
|
|
64
|
+
provider: string
|
|
65
|
+
model: string
|
|
66
|
+
system?: string
|
|
67
|
+
prompt: string
|
|
68
|
+
reasoningEffort?: string
|
|
69
|
+
/** 路由与缓存亲和的会话身份。知识编译用 `knowledge:<库 id>`。 */
|
|
70
|
+
sessionId: string
|
|
71
|
+
}): {
|
|
72
|
+
provider: string
|
|
73
|
+
model: string
|
|
74
|
+
reasoningEffort?: string
|
|
75
|
+
system?: string
|
|
76
|
+
sessionId: string
|
|
77
|
+
messages: Array<{ role: 'user'; content: Array<{ type: 'text'; text: string }> }>
|
|
78
|
+
} {
|
|
79
|
+
const { provider, model, system, prompt, reasoningEffort, sessionId } = input
|
|
80
|
+
return {
|
|
81
|
+
provider,
|
|
82
|
+
model,
|
|
83
|
+
...(reasoningEffort ? { reasoningEffort } : {}),
|
|
84
|
+
...(system ? { system } : {}),
|
|
85
|
+
sessionId,
|
|
86
|
+
messages: [{ role: 'user', content: [{ type: 'text', text: prompt }] }],
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 收完一条模型流的文本。
|
|
92
|
+
*
|
|
93
|
+
* @param stream 平台 `llm.stream()` 返回的 chunk 流
|
|
94
|
+
* @returns 模型输出的全文(不做 trim —— 调用方可能要看空白,也方便断言)
|
|
95
|
+
* @throws 流以 error / aborted 结束时,抛出带 code 与 message 的错误;
|
|
96
|
+
* 流正常结束但一个字符都没有时,也抛 —— 「模型没说话」不是一次成功的调用。
|
|
97
|
+
*/
|
|
98
|
+
export async function collectModelText(stream: AsyncIterable<StreamChunkLike>): Promise<string> {
|
|
99
|
+
let text = ''
|
|
100
|
+
/** 流怎么结束的。用来区分「正常但空」「失败」「中止」三种情况。 */
|
|
101
|
+
let finish: string | undefined
|
|
102
|
+
let failure: { code?: string; message?: string } | undefined
|
|
103
|
+
|
|
104
|
+
for await (const chunk of stream) {
|
|
105
|
+
if (!chunk || typeof chunk !== 'object') continue
|
|
106
|
+
if (chunk.type === 'text-delta' && typeof chunk.text === 'string') {
|
|
107
|
+
text += chunk.text
|
|
108
|
+
continue
|
|
109
|
+
}
|
|
110
|
+
if (chunk.type === 'finish') {
|
|
111
|
+
finish = chunk.reason?.kind
|
|
112
|
+
if (finish === 'error' || finish === 'aborted') failure = chunk.reason?.failure
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (failure) {
|
|
117
|
+
const code = failure.code?.trim() || '未知错误码'
|
|
118
|
+
const message = failure.message?.trim() || '模型服务没有给出说明'
|
|
119
|
+
throw new Error(`模型调用失败(${code}):${message}`)
|
|
120
|
+
}
|
|
121
|
+
if (text.length === 0) {
|
|
122
|
+
// 走到这里说明流正常结束(或者压根没有 finish 块)却没有一个字符。
|
|
123
|
+
throw new Error(`模型没有返回任何文本(结束原因:${finish ?? '没有收到 finish 块'})`)
|
|
124
|
+
}
|
|
125
|
+
return text
|
|
126
|
+
}
|