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/present.ts
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 知识工具在**对话流**里的样子(0045)。
|
|
3
|
+
*
|
|
4
|
+
* ## 为什么要有这个文件
|
|
5
|
+
*
|
|
6
|
+
* 0045 之前,这些工具的 `output.render` 统一是 `JSON.stringify(value)`,而 Web 端没有
|
|
7
|
+
* 给它们注册 toolview —— 于是「让 Agent 批量编译入库」在对话流里长成一条
|
|
8
|
+
* `import_sources` 加一坨 JSON,「召回知识库」同理。人看不到「导了几份、编出了几张卡、
|
|
9
|
+
* 命中了哪几条」,只能自己去右侧面板里找。
|
|
10
|
+
*
|
|
11
|
+
* 这一层做两件事,都在**纯函数**里,所以能被测试钉住:
|
|
12
|
+
*
|
|
13
|
+
* 1. `presentationMeta` —— 结构化的小账本,随 `tool/result` 落进会话日志,
|
|
14
|
+
* Web 端的卡片读它渲染(`src/client/KnowledgeToolRow.tsx`)。它必须
|
|
15
|
+
* **可 JSON 序列化**(会话日志会整份校验),所以这里不塞函数、不塞 Date;
|
|
16
|
+
* 2. `present*View` —— 与工具名无关的渲染意图(标题 / 分类 / 一段 Markdown),
|
|
17
|
+
* 给终端界面和「没有专用卡片的界面」用。
|
|
18
|
+
*
|
|
19
|
+
* ## 一条规矩:数字必须来自真实的产物
|
|
20
|
+
*
|
|
21
|
+
* 这些账本里出现的每一个数都对应一次真的写入或一次真的编译(见 `RebuildReport`
|
|
22
|
+
* 的注释)。**没有估算、没有占位** —— 面板与对话流是同一份诚实标准的两个出口。
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** 工具调用分类:影响图标与语气。与平台的 `ToolCallKind` 同词表。 */
|
|
26
|
+
type Kind = 'read' | 'edit' | 'delete' | 'move' | 'search' | 'execute' | 'fetch' | 'other'
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 一段 Markdown 内容块。
|
|
30
|
+
*
|
|
31
|
+
* 单独包一层是为了让 `present*View` 不必各自去构造平台的内容块形状 ——
|
|
32
|
+
* 那些字段(`type: 'text'`)一旦写错,界面会静默显示空白。
|
|
33
|
+
*/
|
|
34
|
+
function markdown(text: string): Array<{ type: 'text'; text: string }> {
|
|
35
|
+
return [{ type: 'text', text }]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** 把数字与名字排成一行「摘要」。空数组返回空串,调用方据此跳过这一段。 */
|
|
39
|
+
function facts(entries: Array<[string, string | number | undefined | null]>): string {
|
|
40
|
+
return entries
|
|
41
|
+
.filter(([, value]) => value !== undefined && value !== null && value !== '')
|
|
42
|
+
.map(([label, value]) => `${label} ${value}`)
|
|
43
|
+
.join(' · ')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** 截断长标题:卡片标题要能排成一行,`title` 属性里仍给全文。 */
|
|
47
|
+
function short(text: string, max = 48): string {
|
|
48
|
+
const flat = text.replace(/\s+/g, ' ').trim()
|
|
49
|
+
return flat.length > max ? `${flat.slice(0, max)}…` : flat
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ------------------------------------------------------------------ 批量导入
|
|
53
|
+
|
|
54
|
+
export interface ImportMetaSource { sourceId: string; title?: string; bytes: number }
|
|
55
|
+
export interface ImportMetaFailure { index: number; title?: string; code: string; message: string }
|
|
56
|
+
export interface ImportMetaCompile {
|
|
57
|
+
cardsCreated: number; cardsUpdated: number; cardsReused?: number
|
|
58
|
+
notesReused?: number; notesRecompiled?: number
|
|
59
|
+
sourcesExtracted?: number; sourcesSkipped?: number; cardsRemoved?: number
|
|
60
|
+
embedding?: string; version?: number
|
|
61
|
+
}
|
|
62
|
+
export interface ImportMeta {
|
|
63
|
+
kind: 'import'
|
|
64
|
+
attempted: number
|
|
65
|
+
written: ImportMetaSource[]
|
|
66
|
+
failed: ImportMetaFailure[]
|
|
67
|
+
compile: ImportMetaCompile | null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** `import_sources` 的账本。字段逐个点名,不用展开运算符 —— 形状是契约,要看得见。 */
|
|
71
|
+
export function importMetaOf(value: {
|
|
72
|
+
attempted?: number
|
|
73
|
+
written?: Array<{ sourceId: string; title?: string; bytes?: number }>
|
|
74
|
+
failed?: ImportMetaFailure[]
|
|
75
|
+
compile?: ImportMetaCompile | null
|
|
76
|
+
}): ImportMeta {
|
|
77
|
+
return {
|
|
78
|
+
kind: 'import',
|
|
79
|
+
attempted: value.attempted ?? (value.written?.length ?? 0) + (value.failed?.length ?? 0),
|
|
80
|
+
written: (value.written ?? []).map(item => ({
|
|
81
|
+
sourceId: item.sourceId,
|
|
82
|
+
...(item.title ? { title: item.title } : {}),
|
|
83
|
+
bytes: item.bytes ?? 0,
|
|
84
|
+
})),
|
|
85
|
+
failed: (value.failed ?? []).map(item => ({
|
|
86
|
+
index: item.index,
|
|
87
|
+
...(item.title ? { title: item.title } : {}),
|
|
88
|
+
code: item.code,
|
|
89
|
+
message: item.message,
|
|
90
|
+
})),
|
|
91
|
+
compile: value.compile ?? null,
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** 运行中的标题:**先说清楚这一下要动多少东西**。 */
|
|
96
|
+
export function importCallView(args: { sources?: unknown[]; compile?: boolean }): { card: 'generic'; kind: Kind; title: string; rawInput?: unknown } {
|
|
97
|
+
const count = Array.isArray(args?.sources) ? args.sources.length : 0
|
|
98
|
+
const titles = (args?.sources ?? [])
|
|
99
|
+
.map(item => (item && typeof item === 'object' && typeof (item as { title?: unknown }).title === 'string' ? (item as { title: string }).title : ''))
|
|
100
|
+
.filter(Boolean)
|
|
101
|
+
return {
|
|
102
|
+
card: 'generic',
|
|
103
|
+
kind: 'edit',
|
|
104
|
+
title: `导入 ${count} 份来源${args?.compile === false ? '' : '并编译'}`,
|
|
105
|
+
rawInput: titles.length > 0 ? titles.map(title => short(title)) : { 份数: count },
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** 完成后的 Markdown:终端界面与「没有专用卡片」的界面都用它。 */
|
|
110
|
+
export function importResultMarkdown(meta: ImportMeta): string {
|
|
111
|
+
const lines = [`**导入 ${meta.attempted} 份来源** —— 写入 ${meta.written.length} 份,失败 ${meta.failed.length} 份。`]
|
|
112
|
+
for (const item of meta.written) lines.push(`- ${item.title ? `**${short(item.title)}**` : item.sourceId} · ${item.bytes} 字符`)
|
|
113
|
+
for (const item of meta.failed) lines.push(`- 第 ${item.index + 1} 份${item.title ? `「${short(item.title)}」` : ''}失败:${item.code} ${item.message}`)
|
|
114
|
+
if (meta.compile) {
|
|
115
|
+
const { cardsCreated, cardsUpdated, cardsReused, notesReused, notesRecompiled, embedding, version } = meta.compile
|
|
116
|
+
lines.push('', `编译:新建 ${cardsCreated} 张、并入 ${cardsUpdated} 张、复用 ${cardsReused ?? 0} 张;wikiNotes 复用 ${notesReused ?? 0} 条、重编 ${notesRecompiled ?? 0} 条;索引 v${version ?? '?'}${embedding ? `(向量 ${embedding})` : ''}。`)
|
|
117
|
+
} else {
|
|
118
|
+
lines.push('', '这次没有编译(`compile: false`)—— 卡片还没生成,需要时再调 `rebuild_knowledge`。')
|
|
119
|
+
}
|
|
120
|
+
return lines.join('\n')
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ------------------------------------------------------------------ 召回
|
|
124
|
+
|
|
125
|
+
export interface RecallMetaHit {
|
|
126
|
+
entityId: string
|
|
127
|
+
entityType: string
|
|
128
|
+
title: string
|
|
129
|
+
score: number
|
|
130
|
+
rerankScore?: number
|
|
131
|
+
reviewScore?: number
|
|
132
|
+
}
|
|
133
|
+
export interface RecallMeta {
|
|
134
|
+
kind: 'recall'
|
|
135
|
+
query: string
|
|
136
|
+
backend: string
|
|
137
|
+
recallMode: string
|
|
138
|
+
recallTopK: number
|
|
139
|
+
contextTopN: number
|
|
140
|
+
/** 上下文里的卡片数:命中 N 张 + 1 跳扩展 M 张(0050)。 */
|
|
141
|
+
hitCards?: number
|
|
142
|
+
expandedCards?: number
|
|
143
|
+
/** 因为上下文预算没进去的卡片数(0050)。 */
|
|
144
|
+
omittedCards?: number
|
|
145
|
+
insufficientEvidence: boolean
|
|
146
|
+
rerankStatus: string
|
|
147
|
+
hits: RecallMetaHit[]
|
|
148
|
+
/** 路由名字,按这一轮真的跑过的顺序。 */
|
|
149
|
+
routes: string[]
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* `recall_knowledge` 的账本。
|
|
154
|
+
*
|
|
155
|
+
* `hits` 只带前 8 条:对话流里的卡片要能一眼看完,而完整的上下文包本来就在工具返回里
|
|
156
|
+
* 给模型了。**不改这个数**(不四舍五入、不归一),因为大小关系就是排序依据。
|
|
157
|
+
*/
|
|
158
|
+
export function recallMetaOf(value: {
|
|
159
|
+
hits?: Array<{ entityId?: string; entityType?: string; title?: string; score?: number; rerankScore?: number; reviewScore?: number }>
|
|
160
|
+
query?: string
|
|
161
|
+
backend?: string
|
|
162
|
+
recallMode?: string
|
|
163
|
+
recallTopK?: number
|
|
164
|
+
contextTopN?: number
|
|
165
|
+
/** 上下文预算的真实账(0050):命中几张卡、扩展几张、几张超预算。 */
|
|
166
|
+
budget?: { hitCards?: number; expandedCards?: number; omittedCards?: number }
|
|
167
|
+
insufficientEvidence?: boolean
|
|
168
|
+
rerank?: { status?: string }
|
|
169
|
+
routes?: Array<{ route?: string; status?: string } | string>
|
|
170
|
+
}): RecallMeta {
|
|
171
|
+
const hits = (value.hits ?? []).slice(0, 8).map(hit => ({
|
|
172
|
+
entityId: hit.entityId ?? '',
|
|
173
|
+
entityType: hit.entityType ?? '',
|
|
174
|
+
title: hit.title ?? hit.entityId ?? '',
|
|
175
|
+
score: typeof hit.score === 'number' ? hit.score : 0,
|
|
176
|
+
...(typeof hit.rerankScore === 'number' ? { rerankScore: hit.rerankScore } : {}),
|
|
177
|
+
...(typeof hit.reviewScore === 'number' ? { reviewScore: hit.reviewScore } : {}),
|
|
178
|
+
}))
|
|
179
|
+
return {
|
|
180
|
+
kind: 'recall',
|
|
181
|
+
query: value.query ?? '',
|
|
182
|
+
backend: value.backend ?? 'fts',
|
|
183
|
+
recallMode: value.recallMode ?? 'hybrid',
|
|
184
|
+
recallTopK: value.recallTopK ?? 0,
|
|
185
|
+
contextTopN: value.contextTopN ?? 0,
|
|
186
|
+
// 缺字段而不是 `undefined` 字段:这份账本要能原样进会话日志(`undefined` 会被
|
|
187
|
+
// 序列化检查拦下,见 0046)。
|
|
188
|
+
...(value.budget?.hitCards === undefined ? {} : { hitCards: value.budget.hitCards }),
|
|
189
|
+
...(value.budget?.expandedCards === undefined ? {} : { expandedCards: value.budget.expandedCards }),
|
|
190
|
+
...(value.budget?.omittedCards === undefined ? {} : { omittedCards: value.budget.omittedCards }),
|
|
191
|
+
insufficientEvidence: value.insufficientEvidence === true,
|
|
192
|
+
rerankStatus: value.rerank?.status ?? 'skipped',
|
|
193
|
+
hits,
|
|
194
|
+
routes: (value.routes ?? []).map(route => (typeof route === 'string' ? route : route.route ?? '')).filter(Boolean),
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function recallCallView(args: { query?: string; mode?: string; top_k?: number }): { card: 'generic'; kind: Kind; title: string; rawInput?: unknown } {
|
|
199
|
+
const query = typeof args?.query === 'string' && args.query.trim() ? args.query.trim() : '(空查询)'
|
|
200
|
+
return {
|
|
201
|
+
card: 'generic',
|
|
202
|
+
kind: 'search',
|
|
203
|
+
title: `召回「${short(query, 40)}」`,
|
|
204
|
+
rawInput: { 模式: args?.mode ?? '默认', topK: args?.top_k ?? '默认' },
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function recallResultMarkdown(meta: RecallMeta): string {
|
|
209
|
+
if (meta.insufficientEvidence) {
|
|
210
|
+
return `**没有可回答这个问题的内容**(查询:「${short(meta.query, 40)}」)。库里没有一条越过相关性阈值 —— 这是结论,不是错误;把来源导进来再问。`
|
|
211
|
+
}
|
|
212
|
+
const head = facts([
|
|
213
|
+
['命中', `${meta.hits.length} 条`],
|
|
214
|
+
['进上下文', meta.hitCards === undefined
|
|
215
|
+
? `${meta.contextTopN} 条`
|
|
216
|
+
: `${meta.hitCards} 张卡${meta.expandedCards ? ` + ${meta.expandedCards} 张扩展` : ''}${meta.omittedCards ? `(${meta.omittedCards} 张超预算,在溢出文件里)` : ''}`],
|
|
217
|
+
['检索', meta.backend],
|
|
218
|
+
['路由', meta.routes.join('+') || undefined],
|
|
219
|
+
['重排', meta.rerankStatus],
|
|
220
|
+
])
|
|
221
|
+
const lines = [`**召回「${short(meta.query, 40)}」** —— ${head}`]
|
|
222
|
+
for (const hit of meta.hits) {
|
|
223
|
+
const score = typeof hit.rerankScore === 'number' ? hit.rerankScore : hit.score
|
|
224
|
+
const review = hit.reviewScore !== undefined ? ` · 评分 ${hit.reviewScore.toFixed(2)}` : ''
|
|
225
|
+
lines.push(`- **${short(hit.title)}** \`${hit.entityType}:${hit.entityId}\` · ${score.toFixed(3)}${review}`)
|
|
226
|
+
}
|
|
227
|
+
return lines.join('\n')
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ------------------------------------------------------------------ 编译
|
|
231
|
+
|
|
232
|
+
export interface RebuildMeta {
|
|
233
|
+
kind: 'rebuild'
|
|
234
|
+
only: string
|
|
235
|
+
version?: number
|
|
236
|
+
cardsCreated: number
|
|
237
|
+
cardsUpdated: number
|
|
238
|
+
cardsReused?: number
|
|
239
|
+
notesReused?: number
|
|
240
|
+
notesRecompiled?: number
|
|
241
|
+
sourcesExtracted?: number
|
|
242
|
+
sourcesSkipped?: number
|
|
243
|
+
cardsRemoved?: number
|
|
244
|
+
embedding?: string
|
|
245
|
+
skipped?: boolean
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function rebuildMetaOf(only: string, value: Partial<Omit<RebuildMeta, 'kind' | 'only'>>): RebuildMeta {
|
|
249
|
+
return {
|
|
250
|
+
kind: 'rebuild',
|
|
251
|
+
only,
|
|
252
|
+
cardsCreated: value.cardsCreated ?? 0,
|
|
253
|
+
cardsUpdated: value.cardsUpdated ?? 0,
|
|
254
|
+
...(value.version !== undefined ? { version: value.version } : {}),
|
|
255
|
+
...(value.cardsReused !== undefined ? { cardsReused: value.cardsReused } : {}),
|
|
256
|
+
...(value.notesReused !== undefined ? { notesReused: value.notesReused } : {}),
|
|
257
|
+
...(value.notesRecompiled !== undefined ? { notesRecompiled: value.notesRecompiled } : {}),
|
|
258
|
+
...(value.sourcesExtracted !== undefined ? { sourcesExtracted: value.sourcesExtracted } : {}),
|
|
259
|
+
...(value.sourcesSkipped !== undefined ? { sourcesSkipped: value.sourcesSkipped } : {}),
|
|
260
|
+
...(value.cardsRemoved !== undefined ? { cardsRemoved: value.cardsRemoved } : {}),
|
|
261
|
+
...(value.embedding !== undefined ? { embedding: value.embedding } : {}),
|
|
262
|
+
...(value.skipped !== undefined ? { skipped: value.skipped } : {}),
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function rebuildCallView(args: { only?: string }): { card: 'generic'; kind: Kind; title: string } {
|
|
267
|
+
const only = args?.only ?? 'all'
|
|
268
|
+
const what = only === 'all' ? '全链路' : only === 'wiki' ? 'wiki 与 wikiNotes' : only === 'embeddings' ? '向量索引' : '注册表与 schema'
|
|
269
|
+
return { card: 'generic', kind: 'execute', title: `编译知识库 · ${what}` }
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function rebuildResultMarkdown(meta: RebuildMeta): string {
|
|
273
|
+
if (meta.skipped) return `**知识库没有变化** —— 复用了全部产物,没有生成新的索引版本(当前 v${meta.version ?? '?'})。`
|
|
274
|
+
const head = facts([
|
|
275
|
+
['新建卡片', meta.cardsCreated],
|
|
276
|
+
['并入卡片', meta.cardsUpdated],
|
|
277
|
+
['复用卡片', meta.cardsReused],
|
|
278
|
+
['wikiNotes', `复用 ${meta.notesReused ?? 0} / 重编 ${meta.notesRecompiled ?? 0}`],
|
|
279
|
+
['重抽来源', meta.sourcesExtracted],
|
|
280
|
+
['跳过来源', meta.sourcesSkipped],
|
|
281
|
+
['向量', meta.embedding],
|
|
282
|
+
['索引', meta.version !== undefined ? `v${meta.version}` : undefined],
|
|
283
|
+
])
|
|
284
|
+
return `**编译完成** —— ${head}。`
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ------------------------------------------------------------------ 单份来源 / 写卡
|
|
288
|
+
|
|
289
|
+
export interface AddSourceMeta { kind: 'addSource'; sourceId: string; path: string; title?: string; sourceType: string; bytes: number }
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* `value` 用偏松的类型:失败信封里的 `data` 是空壳(`{ sourceId: '', path: '' }`)。
|
|
293
|
+
* 呈现层宁可显示一个空 id,也不该在渲染时抛 —— 卡片渲染抛异常会把整条工具记录弄没。
|
|
294
|
+
*/
|
|
295
|
+
export function addSourceMetaOf(args: { source_type?: string; title?: string; content?: string }, value: { sourceId?: string; path?: string }): AddSourceMeta {
|
|
296
|
+
const content = typeof args?.content === 'string' ? args.content : ''
|
|
297
|
+
return {
|
|
298
|
+
kind: 'addSource',
|
|
299
|
+
sourceId: value?.sourceId ?? '',
|
|
300
|
+
path: value?.path ?? '',
|
|
301
|
+
sourceType: args?.source_type ?? 'document',
|
|
302
|
+
...(args?.title ? { title: args.title } : {}),
|
|
303
|
+
bytes: content.length,
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export function addSourceCallView(args: { source_type?: string; title?: string; content?: string }): { card: 'generic'; kind: Kind; title: string; rawInput?: unknown } {
|
|
308
|
+
const title = typeof args?.title === 'string' && args.title.trim() ? short(args.title) : '(未命名)'
|
|
309
|
+
return {
|
|
310
|
+
card: 'generic',
|
|
311
|
+
kind: 'edit',
|
|
312
|
+
title: `写入来源「${title}」`,
|
|
313
|
+
rawInput: { 分类: args?.source_type ?? 'document', 字符: typeof args?.content === 'string' ? args.content.length : 0 },
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export function addSourceResultMarkdown(meta: AddSourceMeta): string {
|
|
318
|
+
return `**来源已写入** —— 「${meta.title ? short(meta.title) : meta.sourceId}」(分类 ${meta.sourceType},${meta.bytes} 字符)。\n\n下一步:\`rebuild_knowledge\` 把它编译成 wiki 卡片。`
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export interface WriteCardsMeta {
|
|
322
|
+
kind: 'writeCards'
|
|
323
|
+
sourceId: string
|
|
324
|
+
/**
|
|
325
|
+
* 落库的卡片 id。
|
|
326
|
+
*
|
|
327
|
+
* 字段名叫 `ids` 而不是 `written`:`import` 那份账本里 `written` 是**来源对象数组**
|
|
328
|
+
* (带标题与字符数),两个工具共用同一个 meta 类型时同名不同形状会让类型系统直接拒绝。
|
|
329
|
+
*/
|
|
330
|
+
ids: string[]
|
|
331
|
+
rejected: Array<{ index?: number; title?: string; reason?: string; code?: string }>
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export function writeCardsMetaOf(value: { written?: string[]; ids?: string[]; rejected?: unknown[] }, sourceId: string): WriteCardsMeta {
|
|
335
|
+
const rejected = (value.rejected ?? []).map(item => {
|
|
336
|
+
if (typeof item === 'string') return { reason: item }
|
|
337
|
+
const record = item && typeof item === 'object' ? item as Record<string, unknown> : {}
|
|
338
|
+
return {
|
|
339
|
+
...(typeof record.index === 'number' ? { index: record.index } : {}),
|
|
340
|
+
...(typeof record.title === 'string' ? { title: record.title } : {}),
|
|
341
|
+
...(typeof record.reason === 'string' ? { reason: record.reason } : {}),
|
|
342
|
+
...(typeof record.code === 'string' ? { code: record.code } : {}),
|
|
343
|
+
}
|
|
344
|
+
})
|
|
345
|
+
return { kind: 'writeCards', sourceId, ids: value.written ?? value.ids ?? [], rejected }
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export function writeCardsCallView(args: { source_id?: string; proposals?: unknown[] }): { card: 'generic'; kind: Kind; title: string; rawInput?: unknown } {
|
|
349
|
+
const count = Array.isArray(args?.proposals) ? args.proposals.length : 0
|
|
350
|
+
const titles = (args?.proposals ?? [])
|
|
351
|
+
.map(item => (item && typeof item === 'object' && typeof (item as { title?: unknown }).title === 'string' ? (item as { title: string }).title : ''))
|
|
352
|
+
.filter(Boolean)
|
|
353
|
+
return {
|
|
354
|
+
card: 'generic',
|
|
355
|
+
kind: 'edit',
|
|
356
|
+
title: `写入 ${count} 张 wiki 卡片`,
|
|
357
|
+
rawInput: titles.length > 0 ? titles.map(title => short(title)) : { source_id: args?.source_id ?? '' },
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export function writeCardsResultMarkdown(meta: WriteCardsMeta): string {
|
|
362
|
+
const head = facts([['写入', `${meta.ids.length} 张`], ['被拒', meta.rejected.length > 0 ? `${meta.rejected.length} 张` : undefined]])
|
|
363
|
+
const lines = [`**卡片已直接入库** —— ${head}(来源 \`${meta.sourceId}\`)。`, '', '正文只读;要补信息就加备注,要否掉就改状态。']
|
|
364
|
+
for (const id of meta.ids.slice(0, 12)) lines.push(`- \`${id}\``)
|
|
365
|
+
for (const item of meta.rejected) lines.push(`- 被拒${item.title ? `「${short(item.title)}」` : ''}:${item.reason ?? item.code ?? '原因未知'}`)
|
|
366
|
+
return lines.join('\n')
|
|
367
|
+
}
|
package/src/recall-rank.ts
CHANGED
|
@@ -113,10 +113,21 @@ export function graphBoost(fused: FusedHit[], graphRanking: RouteRanking[], opti
|
|
|
113
113
|
* 「弱命中」、挡不住「整批都弱」;向量路的分是原始余弦,是绝对的。两者都要如实
|
|
114
114
|
* 写进诊断,不能让人以为「过了阈值」等于「相关」。
|
|
115
115
|
*/
|
|
116
|
-
export function applyThreshold(ranking: RouteRanking[], threshold: number): {
|
|
116
|
+
export function applyThreshold(ranking: RouteRanking[], threshold: number): {
|
|
117
|
+
kept: RouteRanking[]
|
|
118
|
+
dropped: number
|
|
119
|
+
topScore: number
|
|
120
|
+
/** 阈值前后的**全部**候选(0050):面板要显示「哪些被丢了、各自多少分」。 */
|
|
121
|
+
rows: Array<RouteRanking & { kept: boolean }>
|
|
122
|
+
} {
|
|
117
123
|
const topScore = ranking.length > 0 ? Math.max(...ranking.map(item => item.score)) : 0
|
|
118
124
|
const kept = ranking.filter(item => item.score >= threshold)
|
|
119
|
-
return {
|
|
125
|
+
return {
|
|
126
|
+
kept,
|
|
127
|
+
dropped: ranking.length - kept.length,
|
|
128
|
+
topScore,
|
|
129
|
+
rows: ranking.map(item => ({ ...item, kept: item.score >= threshold })),
|
|
130
|
+
}
|
|
120
131
|
}
|
|
121
132
|
|
|
122
133
|
/**
|
|
@@ -255,6 +266,36 @@ export function matchOf(ranks: Partial<Record<RecallRoute, number>>): 'fts' | 'v
|
|
|
255
266
|
return used[0] ?? 'fts'
|
|
256
267
|
}
|
|
257
268
|
|
|
269
|
+
/**
|
|
270
|
+
* review 评分对融合分的加权区间(0044)。
|
|
271
|
+
*
|
|
272
|
+
* **它是有界的小幅加权,不是准入闸门。** 三个数各有理由:
|
|
273
|
+
* - 下限 `0.85`:一张被标成异常点的卡片降到 0.85 倍,还是召回得到。评分不该等于删除,
|
|
274
|
+
* 否则「人点一下」与「删掉一条知识」变成同一个动作,而后者是不可逆的。
|
|
275
|
+
* - 上限 `1.2`:review 过的人认为对,但它不该压过「这条根本更相关」。相关性由 RRF 与
|
|
276
|
+
* 重排回答,评分只回答「同类命中里先看谁」。
|
|
277
|
+
* - 中性 `0.5` → 倍率 1.0。这样「没有评分」与「评分 0.5」行为一致,缺字段不需要特判。
|
|
278
|
+
*/
|
|
279
|
+
export const REVIEW_WEIGHT_MIN = 0.85
|
|
280
|
+
export const REVIEW_WEIGHT_MAX = 1.2
|
|
281
|
+
export const REVIEW_SCORE_NEUTRAL = 0.5
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* 评分 → 融合分倍率。
|
|
285
|
+
*
|
|
286
|
+
* 纯函数,所以「review 一下到底能改多少排序」可以在 `node:test` 里被钉住 ——
|
|
287
|
+
* 这类加权最容易悄悄漂成「评分即一切」。
|
|
288
|
+
*/
|
|
289
|
+
export function reviewWeight(score: number | undefined): number {
|
|
290
|
+
// 缺字段(旧版本编译出来的 note)按中性处理:升级不该让老语料的排序突然变化。
|
|
291
|
+
if (score === undefined || !Number.isFinite(score)) return 1
|
|
292
|
+
const clamped = Math.min(1, Math.max(0, score))
|
|
293
|
+
if (clamped >= REVIEW_SCORE_NEUTRAL) {
|
|
294
|
+
return 1 + (REVIEW_WEIGHT_MAX - 1) * ((clamped - REVIEW_SCORE_NEUTRAL) / (1 - REVIEW_SCORE_NEUTRAL))
|
|
295
|
+
}
|
|
296
|
+
return 1 - (1 - REVIEW_WEIGHT_MIN) * ((REVIEW_SCORE_NEUTRAL - clamped) / REVIEW_SCORE_NEUTRAL)
|
|
297
|
+
}
|
|
298
|
+
|
|
258
299
|
/** 展示用的概率:`rerankScore` 是 logit 的 sigmoid。 */
|
|
259
300
|
export function sigmoidLogit(logit: number): number {
|
|
260
301
|
return 1 / (1 + Math.exp(-logit))
|
|
@@ -267,6 +308,14 @@ export interface RerankOutcome<T> {
|
|
|
267
308
|
dropped: number
|
|
268
309
|
/** 候选池里最高的 logit —— 跨查询可比的「这一批到底有多相关」。 */
|
|
269
310
|
topLogit: number
|
|
311
|
+
/**
|
|
312
|
+
* **逐条**的打分(0050)。
|
|
313
|
+
*
|
|
314
|
+
* `kept` 只给活下来的那些,而面板要回答的是「哪些被丢了、丢在哪一步」——
|
|
315
|
+
* 少了这份账,界面上就只能显示「14 条候选、全部被丢弃」,人看不到那 14 条分别多少分。
|
|
316
|
+
* 顺序与入参一致(不是排序后的),因为「重排前后的名次变化」本身也是信息。
|
|
317
|
+
*/
|
|
318
|
+
scored: Array<{ unitId: string; logit: number; rerankScore: number; kept: boolean }>
|
|
270
319
|
}
|
|
271
320
|
|
|
272
321
|
/**
|
|
@@ -295,5 +344,11 @@ export function applyRerank<T extends { unitId: string }>(scores: number[], item
|
|
|
295
344
|
.sort((left, right) => right.rerankScore - left.rerankScore || left.unitId.localeCompare(right.unitId)),
|
|
296
345
|
dropped: scored.length - kept.length,
|
|
297
346
|
topLogit: scored.length > 0 ? Math.max(...scored.map(entry => entry.logit)) : 0,
|
|
347
|
+
scored: scored.map(entry => ({
|
|
348
|
+
unitId: entry.item.unitId,
|
|
349
|
+
logit: entry.logit,
|
|
350
|
+
rerankScore: sigmoidLogit(entry.logit),
|
|
351
|
+
kept: entry.logit >= threshold,
|
|
352
|
+
})),
|
|
298
353
|
}
|
|
299
354
|
}
|