@p-dsh-market/conversation-knowledge-map 0.1.15 → 0.1.17
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 +2 -2
- package/lib/generation-orchestrator.js +215 -32
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ DSH 的“知识视图”插件,把同一工作路径下用户明确选择的
|
|
|
11
11
|
|
|
12
12
|
配置面板会列出运行时可用的 Provider / Model,并默认带入 DSH 默认模型;本次选择会绑定到确认令牌、Agent 调用和 `manifest.json`,不会因为默认模型变化而被静默替换。提交任务后配置对话框立即关闭,失败原因会显示在知识视图页的任务条中。
|
|
13
13
|
|
|
14
|
-
摘要阶段按对话并行处理,并发上限固定为 3;每个对话按约 5000
|
|
14
|
+
摘要阶段按对话并行处理,并发上限固定为 3;每个对话按约 5000 字、优先在完整段落边界分段,只有单段本身超长时才按句末或硬边界拆分。同一对话的分段保持顺序处理。思维导图使用合并后的会话摘要;知识图谱直接使用保留来源的分段摘要,避免长对话在二次压缩后丢失实体。知识图谱按证据分段多次生成,最多 2 批并行;单批限制在 10–18 个实体、最多 24 条关系,若达到输出 Token 上限则只降低并重试当前批次。所有成功批次由 Host 按实体类型与名称、关系起点/终点/类型确定性去重并合并来源,最终结果再根据已读取文本量和有效分段数裁剪为 30–100 个实体、最多 200 条关系。单个批次连续失败只跳过该批,不会推翻其他成功批次。结构化输出失败时会完全重置并最多重试 3 次;单个对话摘要连续失败后只跳过该对话,不阻断其他对话和最终报告。“同时生成”模式下,一个最终视图失败也不会阻断另一个视图保存。进度区域和最终知识视图都会按时间线显示读取、摘要、具体失败原因、重试、跳过、合并、生成及保存过程;知识图谱时间线还会显示批次数、模型返回数量和合并去重后的保留数量。模型可以保留 thinking,但结果提取只读取最终文本并过滤 reasoning 内容;摘要和每次图谱调用分别预留 12000 与 24000 个输出 Token。最终模型若返回未选择的 Session 引用,会过滤该引用;严格模式下无有效来源的内容项会被跳过,最终页面列出实际总结的对话、失败对话和过滤数量。
|
|
15
15
|
|
|
16
16
|
生成过程时间线支持折叠和展开:进行中的任务默认展开,已完成结果中的历史时间线默认折叠。思维导图使用递归树形布局并绘制父子连接线。知识图谱按关联度使用中心节点与内外双环布局,节点卡片内显示类型和最多两行名称,关系以带方向箭头的弱化曲线呈现,并通过描边区分推测或冲突内容;画布提供 50%–200% 缩放和一键恢复 100%,缩放不会影响实体筛选、点击和详情查看,也不会产生横向滚动条。
|
|
17
17
|
|
|
@@ -19,7 +19,7 @@ DSH 的“知识视图”插件,把同一工作路径下用户明确选择的
|
|
|
19
19
|
|
|
20
20
|
## 生成失败诊断
|
|
21
21
|
|
|
22
|
-
Host 和生成编排器会输出带 `[conversation-knowledge-map]` 前缀的诊断日志。日志包含路由、Provider / Model、Agent Session、实时事件类型、surface/session 读取次数、提取文本长度、输出形状和错误原因,不记录 Prompt
|
|
22
|
+
Host 和生成编排器会输出带 `[conversation-knowledge-map]` 前缀的诊断日志。日志包含路由、Provider / Model、Agent Session、实时事件类型、surface/session 读取次数、提取文本长度、输出形状和错误原因,不记录 Prompt 或原始对话正文。为诊断结构化输出失败,当前还会记录模型实际返回文本:不超过 32000 字符时完整记录,超过时保留首尾各 16000 字符;该内容可能包含模型整理出的对话信息,排障完成后应按运行环境的日志策略清理。解析失败日志会额外列出 Markdown JSON 围栏、首末大括号位置以及每个候选对象属于未闭合、JSON 语法错误、合法但结构不符或合法目标结构。优先查看 DSH Web Runtime 的终端日志;若 Runtime 提供 logger 服务,则同时写入该 logger。重点关注 `agent output content`、`agent output parse failed`、`agent event`、`agent idle`、`agent turn failure`、`agent surface read` 和 `agent session read`。Runtime 在 `turn/end.reason.kind = error` 时会优先显示其 `code/message`;只有未发现 turn 错误且确实没有助手输出时,才会报告“模型没有返回 JSON 对象”。
|
|
23
23
|
|
|
24
24
|
## 本地验证
|
|
25
25
|
|
|
@@ -10,6 +10,9 @@ const SUMMARY_MAX_TOKENS = 12000
|
|
|
10
10
|
const VIEW_MAX_TOKENS = 24000
|
|
11
11
|
const SUMMARY_CONCURRENCY = 3
|
|
12
12
|
const MAX_MODEL_RETRIES = 3
|
|
13
|
+
const GRAPH_CONCURRENCY = 2
|
|
14
|
+
const GRAPH_BATCH_MAX_ENTITIES = 18
|
|
15
|
+
const GRAPH_BATCH_MAX_RELATIONS = 24
|
|
13
16
|
const MIN_GRAPH_ENTITIES = 30
|
|
14
17
|
const MAX_GRAPH_ENTITIES = 100
|
|
15
18
|
const MAX_GRAPH_RELATIONS = 200
|
|
@@ -125,6 +128,73 @@ function findJsonObject(text, kind = '') {
|
|
|
125
128
|
return null
|
|
126
129
|
}
|
|
127
130
|
|
|
131
|
+
function modelOutputText(value) {
|
|
132
|
+
const text = textFromContent(value)
|
|
133
|
+
if (text) return text
|
|
134
|
+
if (value && typeof value === 'object') {
|
|
135
|
+
try { return JSON.stringify(value) } catch { /* fall through to a printable scalar */ }
|
|
136
|
+
}
|
|
137
|
+
return String(value ?? '')
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function loggableModelOutput(value, max = 32000) {
|
|
141
|
+
const text = modelOutputText(value)
|
|
142
|
+
if (text.length <= max) return { text, loggedText: JSON.stringify(text), truncated: false }
|
|
143
|
+
const half = Math.floor(max / 2)
|
|
144
|
+
const bounded = `${text.slice(0, half)}\n…[中间 ${text.length - max} 个字符因日志长度限制被省略]…\n${text.slice(-half)}`
|
|
145
|
+
return { text, loggedText: JSON.stringify(bounded), truncated: true }
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function jsonParseDiagnostics(value, kind = '') {
|
|
149
|
+
const text = modelOutputText(value).replace(/^\uFEFF/, '').trim()
|
|
150
|
+
const starts = []
|
|
151
|
+
for (let index = 0; index < text.length && starts.length < 12; index += 1) if (text[index] === '{') starts.push(index)
|
|
152
|
+
const candidates = []
|
|
153
|
+
for (const start of starts) {
|
|
154
|
+
let depth = 0
|
|
155
|
+
let inString = false
|
|
156
|
+
let escaped = false
|
|
157
|
+
let end = -1
|
|
158
|
+
for (let index = start; index < text.length; index += 1) {
|
|
159
|
+
const char = text[index]
|
|
160
|
+
if (inString) {
|
|
161
|
+
if (escaped) escaped = false
|
|
162
|
+
else if (char === '\\') escaped = true
|
|
163
|
+
else if (char === '"') inString = false
|
|
164
|
+
continue
|
|
165
|
+
}
|
|
166
|
+
if (char === '"') inString = true
|
|
167
|
+
else if (char === '{') depth += 1
|
|
168
|
+
else if (char === '}' && --depth === 0) { end = index; break }
|
|
169
|
+
}
|
|
170
|
+
if (end < 0) {
|
|
171
|
+
candidates.push({ start, status: 'unclosed-object' })
|
|
172
|
+
continue
|
|
173
|
+
}
|
|
174
|
+
const fragment = text.slice(start, end + 1)
|
|
175
|
+
try {
|
|
176
|
+
const parsed = JSON.parse(fragment)
|
|
177
|
+
candidates.push({
|
|
178
|
+
start,
|
|
179
|
+
end,
|
|
180
|
+
status: expectedObject(parsed, kind) ? 'valid-expected-shape' : 'valid-json-wrong-shape',
|
|
181
|
+
keys: parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? Object.keys(parsed).slice(0, 20) : []
|
|
182
|
+
})
|
|
183
|
+
} catch (error) {
|
|
184
|
+
candidates.push({ start, end, status: 'invalid-json', error: shortText(errorMessage(error), 300) })
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
kind,
|
|
189
|
+
textLength: text.length,
|
|
190
|
+
fencedJson: /```(?:json)?\s*[\s\S]*?```/i.test(text),
|
|
191
|
+
firstBrace: text.indexOf('{'),
|
|
192
|
+
lastBrace: text.lastIndexOf('}'),
|
|
193
|
+
candidateCountInspected: candidates.length,
|
|
194
|
+
candidates
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
128
198
|
export function parseStructuredOutput(value, kind = '') {
|
|
129
199
|
const object = asObject(value)
|
|
130
200
|
if (object && expectedObject(object, kind)) return object
|
|
@@ -205,6 +275,104 @@ function graphGenerationBudget(sources, context) {
|
|
|
205
275
|
}
|
|
206
276
|
}
|
|
207
277
|
|
|
278
|
+
function graphBatchBudget(context, attempt = 0) {
|
|
279
|
+
const contextChars = JSON.stringify(context || []).length
|
|
280
|
+
const reduction = Math.max(0.5, 1 - attempt * 0.2)
|
|
281
|
+
const entities = Math.max(8, Math.floor(Math.min(GRAPH_BATCH_MAX_ENTITIES, Math.max(10, Math.ceil(contextChars / 500))) * reduction))
|
|
282
|
+
return {
|
|
283
|
+
entities,
|
|
284
|
+
relations: Math.max(entities, Math.floor(Math.min(GRAPH_BATCH_MAX_RELATIONS, Math.ceil(entities * 1.35)) * reduction))
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function mergeRefs(...groups) {
|
|
289
|
+
const bySession = new Map()
|
|
290
|
+
for (const ref of groups.flat()) {
|
|
291
|
+
const sessionId = String(ref?.sessionId || '')
|
|
292
|
+
if (!sessionId) continue
|
|
293
|
+
const seqs = bySession.get(sessionId) || new Set()
|
|
294
|
+
for (const seq of ref?.eventSeqs || []) if (Number.isInteger(seq) && seq >= 0) seqs.add(seq)
|
|
295
|
+
bySession.set(sessionId, seqs)
|
|
296
|
+
}
|
|
297
|
+
return [...bySession.entries()].slice(0, 12).map(([sessionId, seqs]) => ({ sessionId, eventSeqs: [...seqs].slice(0, 48) }))
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function mergedConfidence(left, right) {
|
|
301
|
+
const values = new Set([left, right])
|
|
302
|
+
if (values.has('conflicted')) return 'conflicted'
|
|
303
|
+
if (values.has('inferred')) return 'inferred'
|
|
304
|
+
return 'confirmed'
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function entityMergeKey(entity) {
|
|
308
|
+
const normalize = (value) => String(value || '').normalize('NFKC').trim().toLowerCase().replace(/\s+/g, ' ')
|
|
309
|
+
return `${normalize(entity?.type || 'concept')}\u0000${normalize(entity?.name)}`
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function mergeKnowledgeGraphs(graphs, budget) {
|
|
313
|
+
const entitiesByKey = new Map()
|
|
314
|
+
const idMaps = []
|
|
315
|
+
let nextEntityId = 1
|
|
316
|
+
const usedEntityIds = new Set()
|
|
317
|
+
graphs.forEach((graph, graphIndex) => {
|
|
318
|
+
const idMap = new Map()
|
|
319
|
+
for (const entity of graph.entities || []) {
|
|
320
|
+
const key = entityMergeKey(entity)
|
|
321
|
+
let merged = entitiesByKey.get(key)
|
|
322
|
+
if (!merged) {
|
|
323
|
+
const requestedId = String(entity.id || '').trim()
|
|
324
|
+
let id = requestedId && !usedEntityIds.has(requestedId) ? requestedId : `entity-${nextEntityId++}`
|
|
325
|
+
while (usedEntityIds.has(id)) id = `entity-${nextEntityId++}`
|
|
326
|
+
usedEntityIds.add(id)
|
|
327
|
+
merged = { ...entity, id, sourceRefs: mergeRefs(entity.sourceRefs), order: entitiesByKey.size }
|
|
328
|
+
entitiesByKey.set(key, merged)
|
|
329
|
+
} else {
|
|
330
|
+
merged.sourceRefs = mergeRefs(merged.sourceRefs, entity.sourceRefs)
|
|
331
|
+
merged.confidence = mergedConfidence(merged.confidence, entity.confidence)
|
|
332
|
+
if (String(entity.summary || '').length > String(merged.summary || '').length) merged.summary = entity.summary
|
|
333
|
+
}
|
|
334
|
+
idMap.set(String(entity.id), merged.id)
|
|
335
|
+
}
|
|
336
|
+
idMaps[graphIndex] = idMap
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
const relationsByKey = new Map()
|
|
340
|
+
graphs.forEach((graph, graphIndex) => {
|
|
341
|
+
const idMap = idMaps[graphIndex]
|
|
342
|
+
for (const relation of graph.relations || []) {
|
|
343
|
+
const from = idMap.get(String(relation.from))
|
|
344
|
+
const to = idMap.get(String(relation.to))
|
|
345
|
+
if (!from || !to || from === to) continue
|
|
346
|
+
const type = String(relation.type || 'related_to').trim()
|
|
347
|
+
const key = `${from}\u0000${to}\u0000${type.toLowerCase()}`
|
|
348
|
+
const existing = relationsByKey.get(key)
|
|
349
|
+
if (existing) {
|
|
350
|
+
existing.evidence = mergeRefs(existing.evidence, relation.evidence)
|
|
351
|
+
existing.confidence = mergedConfidence(existing.confidence, relation.confidence)
|
|
352
|
+
} else {
|
|
353
|
+
relationsByKey.set(key, { ...relation, from, to, type, evidence: mergeRefs(relation.evidence), order: relationsByKey.size })
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
const degree = new Map()
|
|
359
|
+
for (const relation of relationsByKey.values()) {
|
|
360
|
+
degree.set(relation.from, (degree.get(relation.from) || 0) + 1)
|
|
361
|
+
degree.set(relation.to, (degree.get(relation.to) || 0) + 1)
|
|
362
|
+
}
|
|
363
|
+
const entities = [...entitiesByKey.values()]
|
|
364
|
+
.sort((left, right) => (degree.get(right.id) || 0) - (degree.get(left.id) || 0) || right.sourceRefs.length - left.sourceRefs.length || left.order - right.order)
|
|
365
|
+
.slice(0, budget.entities)
|
|
366
|
+
.map(({ order, ...entity }) => entity)
|
|
367
|
+
const retainedIds = new Set(entities.map((entity) => entity.id))
|
|
368
|
+
const relations = [...relationsByKey.values()]
|
|
369
|
+
.filter((relation) => retainedIds.has(relation.from) && retainedIds.has(relation.to))
|
|
370
|
+
.sort((left, right) => right.evidence.length - left.evidence.length || left.order - right.order)
|
|
371
|
+
.slice(0, budget.relations)
|
|
372
|
+
.map(({ order, ...relation }, index) => ({ ...relation, id: `relation-${index + 1}` }))
|
|
373
|
+
return { entities, relations }
|
|
374
|
+
}
|
|
375
|
+
|
|
208
376
|
async function mapWithConcurrency(items, limit, mapper) {
|
|
209
377
|
const results = new Array(items.length)
|
|
210
378
|
let nextIndex = 0
|
|
@@ -771,40 +939,53 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
771
939
|
}
|
|
772
940
|
}
|
|
773
941
|
if (request.outputMode === 'knowledge-graph' || request.outputMode === 'both') {
|
|
774
|
-
|
|
775
|
-
this.
|
|
776
|
-
|
|
777
|
-
let
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
sourceWarnings.skippedRefs += sanitized.skippedRefs
|
|
789
|
-
sourceWarnings.skippedItems += sanitized.skippedItems
|
|
790
|
-
graphError = null
|
|
791
|
-
this.recordTimeline(task, 'view-complete', '知识图谱生成完成。')
|
|
792
|
-
break
|
|
793
|
-
} catch (error) {
|
|
794
|
-
this.assertNotCancelled(task)
|
|
795
|
-
graphError = error
|
|
796
|
-
logMessage(this.logger, 'warn', 'knowledge graph attempt invalid id=%s attempt=%d error=%s', logId(task.id), attempt + 1, errorMessage(error))
|
|
797
|
-
if (attempt < MAX_MODEL_RETRIES) {
|
|
798
|
-
this.recordTimeline(task, 'retry', `知识图谱生成失败(${shortText(errorMessage(graphError), 160)}),重置重试 ${attempt + 1}/${MAX_MODEL_RETRIES}。`)
|
|
799
|
-
this.update(task, 'building-knowledge-graph', {
|
|
800
|
-
message: `知识图谱结构无效,正在重置重试 ${attempt + 1}/${MAX_MODEL_RETRIES}…`,
|
|
801
|
-
progress: { percent: 88, current: sources.length, total: sources.length, label: `知识图谱重试 ${attempt + 1}/${MAX_MODEL_RETRIES}` }
|
|
942
|
+
const graphBatches = graphContext.map((item) => [item])
|
|
943
|
+
this.recordTimeline(task, 'view-start', `开始分 ${graphBatches.length} 批生成知识图谱(最多 ${GRAPH_CONCURRENCY} 批并行)。`)
|
|
944
|
+
this.update(task, 'building-knowledge-graph', { progress: { percent: 86, current: 0, total: graphBatches.length, label: '分批生成知识图谱' } })
|
|
945
|
+
let completedGraphBatches = 0
|
|
946
|
+
const graphBatchResults = await mapWithConcurrency(graphBatches, GRAPH_CONCURRENCY, async (batch, batchIndex) => {
|
|
947
|
+
let graphError = null
|
|
948
|
+
for (let attempt = 0; attempt <= MAX_MODEL_RETRIES; attempt += 1) {
|
|
949
|
+
try {
|
|
950
|
+
const batchBudget = graphBatchBudget(batch, attempt)
|
|
951
|
+
const baseGraphPrompt = outputPrompt('knowledge-graph', batch, request.prompt, request.strict === true, { graphBudget: batchBudget })
|
|
952
|
+
const prompt = attempt === 0 ? baseGraphPrompt : `${baseGraphPrompt}\n\n本批第 ${attempt} 次输出未通过校验:${shortText(errorMessage(graphError), 500)}。请完全重置并从头生成;本次预算已降低,优先保留有明确关系和来源的实体。只输出完整、严格合法的 JSON。`
|
|
953
|
+
const value = await this.runModel({
|
|
954
|
+
kind: 'knowledge-graph', prompt, cwd: request.cwd, strict: request.strict === true,
|
|
955
|
+
selectedSessionIds: sourceSessionIds, model: request.model, signal: task.controller.signal
|
|
802
956
|
})
|
|
957
|
+
const sanitized = sanitizeKnowledgeGraphSources(value, summarizedSources, request.strict === true)
|
|
958
|
+
const graph = validateKnowledgeGraph(sanitized.value, { selectedSessionIds: sourceSessionIds, strict: request.strict === true })
|
|
959
|
+
sourceWarnings.skippedRefs += sanitized.skippedRefs
|
|
960
|
+
sourceWarnings.skippedItems += sanitized.skippedItems
|
|
961
|
+
completedGraphBatches += 1
|
|
962
|
+
this.recordTimeline(task, 'graph-batch-complete', `知识图谱第 ${batchIndex + 1}/${graphBatches.length} 批完成:保留 ${graph.entities.length} 个实体、${graph.relations.length} 条关系。`, { batch: batchIndex + 1, totalBatches: graphBatches.length })
|
|
963
|
+
this.update(task, 'building-knowledge-graph', { progress: { percent: 86 + Math.floor(4 * completedGraphBatches / graphBatches.length), current: completedGraphBatches, total: graphBatches.length, label: `知识图谱批次 ${completedGraphBatches}/${graphBatches.length}` } })
|
|
964
|
+
return { graph, rawEntities: Array.isArray(value?.entities) ? value.entities.length : 0, rawRelations: Array.isArray(value?.relations) ? value.relations.length : 0, error: null }
|
|
965
|
+
} catch (error) {
|
|
966
|
+
this.assertNotCancelled(task)
|
|
967
|
+
graphError = error
|
|
968
|
+
logMessage(this.logger, 'warn', 'knowledge graph batch invalid id=%s batch=%d attempt=%d error=%s', logId(task.id), batchIndex + 1, attempt + 1, errorMessage(error))
|
|
969
|
+
if (attempt < MAX_MODEL_RETRIES) {
|
|
970
|
+
this.recordTimeline(task, 'retry', `知识图谱第 ${batchIndex + 1}/${graphBatches.length} 批失败(${shortText(errorMessage(graphError), 160)}),降低本批输出预算后重试 ${attempt + 1}/${MAX_MODEL_RETRIES}。`)
|
|
971
|
+
}
|
|
803
972
|
}
|
|
804
973
|
}
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
974
|
+
completedGraphBatches += 1
|
|
975
|
+
this.recordTimeline(task, 'graph-batch-skipped', `知识图谱第 ${batchIndex + 1}/${graphBatches.length} 批连续失败,已跳过本批。`)
|
|
976
|
+
return { graph: null, rawEntities: 0, rawRelations: 0, error: graphError }
|
|
977
|
+
})
|
|
978
|
+
const successfulGraphBatches = graphBatchResults.filter((result) => result.graph)
|
|
979
|
+
if (successfulGraphBatches.length) {
|
|
980
|
+
const mergedGraph = mergeKnowledgeGraphs(successfulGraphBatches.map((result) => result.graph), graphBudget)
|
|
981
|
+
knowledgeGraph = validateKnowledgeGraph(mergedGraph, { selectedSessionIds: sourceSessionIds, strict: request.strict === true })
|
|
982
|
+
const rawEntities = successfulGraphBatches.reduce((total, result) => total + result.rawEntities, 0)
|
|
983
|
+
const rawRelations = successfulGraphBatches.reduce((total, result) => total + result.rawRelations, 0)
|
|
984
|
+
this.recordTimeline(task, 'graph-coverage', `知识图谱 ${successfulGraphBatches.length}/${graphBatches.length} 批成功,模型共返回 ${rawEntities} 个实体、${rawRelations} 条关系;合并去重后保留 ${knowledgeGraph.entities.length} 个实体、${knowledgeGraph.relations.length} 条关系。`, { entityLimit: graphBudget.entities, relationLimit: graphBudget.relations })
|
|
985
|
+
this.recordTimeline(task, 'view-complete', '知识图谱分批生成与合并完成。')
|
|
986
|
+
} else {
|
|
987
|
+
const graphError = graphBatchResults.find((result) => result.error)?.error || new Error('所有知识图谱批次均生成失败。')
|
|
988
|
+
this.recordTimeline(task, 'view-failed', `知识图谱 ${graphBatches.length} 个批次均失败,已跳过该视图。`)
|
|
808
989
|
if (request.outputMode === 'knowledge-graph') throw graphError
|
|
809
990
|
}
|
|
810
991
|
}
|
|
@@ -862,13 +1043,15 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
862
1043
|
|
|
863
1044
|
async runModel(input) {
|
|
864
1045
|
const parseModelOutput = (value, source, diagnostics = {}) => {
|
|
1046
|
+
const output = loggableModelOutput(value)
|
|
1047
|
+
logMessage(this.logger, 'info', 'agent output content kind=%s source=%s length=%d truncated=%s content=%s', input.kind, source, output.text.length, output.truncated, output.loggedText)
|
|
865
1048
|
try {
|
|
866
1049
|
const result = parseStructuredOutput(value, input.kind)
|
|
867
1050
|
logMessage(this.logger, 'info', 'agent output parsed kind=%s source=%s value=%s result=%s', input.kind, source, diagnosticSummary(value), diagnosticSummary(result))
|
|
868
1051
|
return result
|
|
869
1052
|
} catch (error) {
|
|
870
1053
|
const surfacedError = diagnostics.agentLimit || error
|
|
871
|
-
logMessage(this.logger, 'error', 'agent output parse failed kind=%s source=%s value=%s
|
|
1054
|
+
logMessage(this.logger, 'error', 'agent output parse failed kind=%s source=%s value=%s parseDiagnostics=%s runtimeDiagnostics=%s error=%s', input.kind, source, diagnosticSummary(value), JSON.stringify(jsonParseDiagnostics(value, input.kind)), JSON.stringify(diagnostics), errorMessage(surfacedError))
|
|
872
1055
|
throw surfacedError
|
|
873
1056
|
}
|
|
874
1057
|
}
|