@gt-fe/eap-runtime 0.0.40 → 0.0.41

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.
@@ -572,6 +572,7 @@ export class LangGraphAdapter {
572
572
  traceCollector: ephemeral(undefined),
573
573
  traceId: ephemeral(undefined),
574
574
  skillSpanId: ephemeral(undefined),
575
+ llmSpanId: ephemeral(undefined),
575
576
  toolSpanId: ephemeral(undefined),
576
577
  hitlToolCallId: ephemeral(undefined),
577
578
  lastSpanId: ephemeral(undefined),
@@ -12,6 +12,7 @@ import { assertSkillPermission } from '../../governance/permission-guard.js';
12
12
  import { LangGraphAdapter } from './langgraph.adapter.js';
13
13
  import { assertSkillGraphHasNoSubgraphNodes } from './graph-nesting.js';
14
14
  import { resolveExecutionTrace, warnTraceSpanFailure, } from '../execution-trace-context.js';
15
+ import { startGraphSpan } from '../trace-span.js';
15
16
  import { skillSpanFields, skillTraceIdentity } from './skill-entry-span.js';
16
17
  import { filterLoadedToolsForSkill, spokenName, } from '../../tool/system-prompt-builder.js';
17
18
  import { unwrapHitlPaused } from '../hitl/hitl-handler.js';
@@ -96,26 +97,16 @@ export class SubGraphCompiler {
96
97
  });
97
98
  }
98
99
  let skillSpanId = existingSpanId || null;
99
- if (!resuming && traceCollector && traceId) {
100
- try {
101
- const fields = skillSpanFields(identity, { source: 'subgraph' });
102
- skillSpanId = await traceCollector.createNode({
103
- traceId,
104
- name: spanName,
105
- type: 'skill',
106
- agentCode: state['agentCode'] ?? state['agentId'],
100
+ if (!resuming) {
101
+ const fields = skillSpanFields(identity, { source: 'subgraph' });
102
+ skillSpanId = (await startGraphSpan(state, {
103
+ name: spanName,
104
+ type: 'skill',
105
+ extra: {
107
106
  ...(fields.version ? { version: fields.version } : {}),
108
107
  input: fields.input,
109
- metadata: {
110
- taskId: state['taskId'],
111
- correlationId: state['correlationId'],
112
- },
113
- });
114
- }
115
- catch (err) {
116
- warnTraceSpanFailure('createNode', { type: 'skill', name: spanName, traceId }, err);
117
- skillSpanId = null;
118
- }
108
+ },
109
+ })) ?? null;
119
110
  }
120
111
  try {
121
112
  // 1. 编译子图(每次执行时编译,确保子图实例独立)
@@ -240,6 +240,11 @@ export interface GraphState {
240
240
  skillIsolateContext?: boolean;
241
241
  /** 当前 Skill 子图 createNode 返回的 span id;子图内 Tool span 的 parentNodeId */
242
242
  skillSpanId?: string;
243
+ /**
244
+ * 本回合父图模型调用的 span id。父图 tool / 手册技能 / 子图技能入口挂到它下面。
245
+ * 只在 llm createNode 成功时更新。HITL 等待态一起带上。
246
+ */
247
+ llmSpanId?: string;
243
248
  /**
244
249
  * HITL 续跑:暂停时已 create 的 tool Span id。
245
250
  * 按 hitlToolCallId 复用,禁止再 createNode。
@@ -40,6 +40,7 @@ export interface HitlInterruptExtras {
40
40
  toolName?: string;
41
41
  toolSpanId?: string;
42
42
  skillSpanId?: string;
43
+ llmSpanId?: string;
43
44
  }
44
45
  /** HITL 等待中状态(内存中维护) */
45
46
  export interface HitlWaitingState {
@@ -55,6 +56,7 @@ export interface HitlWaitingState {
55
56
  toolName?: string;
56
57
  toolSpanId?: string;
57
58
  skillSpanId?: string;
59
+ llmSpanId?: string;
58
60
  hitlSpanId?: string;
59
61
  }
60
62
  /** HITL 超时错误(保留类型;timer 不再 throw,resume 逾期改走 AppError) */
@@ -119,6 +121,7 @@ export interface PersistedHitlWaiting {
119
121
  toolName?: string;
120
122
  toolSpanId?: string;
121
123
  skillSpanId?: string;
124
+ llmSpanId?: string;
122
125
  hitlSpanId?: string;
123
126
  /** error/cancel 超时已占用此等待;resume 必须拒绝 */
124
127
  timedOut?: boolean;
@@ -194,6 +194,7 @@ function prepareHitlCheckpointState(state) {
194
194
  correlationId: state.correlationId ?? raw.correlationId,
195
195
  delegationToken: state.delegationToken ?? raw.delegationToken,
196
196
  skillSpanId: state.skillSpanId ?? raw.skillSpanId,
197
+ llmSpanId: state.llmSpanId ?? (typeof raw.llmSpanId === 'string' ? raw.llmSpanId : undefined),
197
198
  traceId: state.traceId ?? (typeof raw.traceId === 'string' ? raw.traceId : undefined),
198
199
  lastSpanId: state.lastSpanId ?? (typeof raw.lastSpanId === 'string' ? raw.lastSpanId : undefined),
199
200
  };
@@ -227,6 +228,7 @@ function waitingFromPersisted(persisted, taskId) {
227
228
  toolName: persisted.toolName,
228
229
  toolSpanId: persisted.toolSpanId,
229
230
  skillSpanId: persisted.skillSpanId,
231
+ llmSpanId: persisted.llmSpanId,
230
232
  hitlSpanId: persisted.hitlSpanId,
231
233
  };
232
234
  }
@@ -285,6 +287,8 @@ export class HitlHandler {
285
287
  const deadlineAt = new Date(Date.now() + timeoutMs).toISOString();
286
288
  const skillSpanId = extras.skillSpanId ??
287
289
  (typeof state.skillSpanId === 'string' ? state.skillSpanId : undefined);
290
+ const llmSpanId = extras.llmSpanId ??
291
+ (typeof state.llmSpanId === 'string' ? state.llmSpanId : undefined);
288
292
  const hitlSpanId = await startGraphSpan(state, {
289
293
  name: extras.toolName ?? 'hitl',
290
294
  type: 'hitl',
@@ -306,6 +310,7 @@ export class HitlHandler {
306
310
  toolName: extras.toolName,
307
311
  toolSpanId: extras.toolSpanId,
308
312
  skillSpanId,
313
+ llmSpanId,
309
314
  hitlSpanId,
310
315
  };
311
316
  const existing = await this.checkpointSaver.load(threadId);
@@ -392,6 +397,7 @@ export class HitlHandler {
392
397
  toolName: extras.toolName,
393
398
  toolSpanId: extras.toolSpanId,
394
399
  skillSpanId,
400
+ llmSpanId,
395
401
  hitlSpanId,
396
402
  });
397
403
  }
@@ -448,6 +454,7 @@ export class HitlHandler {
448
454
  hitlRespondedBy: userId,
449
455
  ...(waiting.toolSpanId ? { toolSpanId: waiting.toolSpanId } : {}),
450
456
  ...(waiting.toolCallId ? { hitlToolCallId: waiting.toolCallId } : {}),
457
+ ...(waiting.llmSpanId ? { llmSpanId: waiting.llmSpanId } : {}),
451
458
  };
452
459
  const now = new Date().toISOString();
453
460
  await this.checkpointSaver.save(waiting.threadId, stripCheckpointCollectors({
@@ -8,6 +8,7 @@
8
8
  * @see design-v2 §5.2 / §5.3
9
9
  */
10
10
  import { resolveTraceId } from '../../../common/trace-id.js';
11
+ import { withGraphSpan } from '../../trace-span.js';
11
12
  import { buildRagTools, resolveRagSources, mergeLocalDirs } from './tools.js';
12
13
  import { runReactLoop } from './react-loop.js';
13
14
  export class RagNode {
@@ -49,33 +50,40 @@ export class RagNode {
49
50
  if (excludedConfirmToolNames.length > 0) {
50
51
  throw new Error(`RagNode "${this.nodeId}" 检索源 tools 引用了需人工确认的工具: ${excludedConfirmToolNames.join(', ')}(requireConfirmation 工具的确认流挂在图级 tool 节点,rag 循环内执行会绕过确认,不支持挂载)`);
51
52
  }
52
- const result = await runReactLoop({
53
- nodeId: this.nodeId,
54
- query: extractQuery(state),
55
- history: extractHistory(state),
56
- config: this.config,
57
- apiTools,
58
- kbToolNames,
59
- llmProvider: this.deps.llmProvider,
60
- toolOrchestrator: this.deps.toolOrchestrator,
61
- retrievers,
62
- loadedTools,
63
- authContext: this.buildAuthContext(state),
64
- executionContext: state['executionContext'],
65
- forceToolServer: state['forceToolServer'],
66
- userId: String(state['userId'] ?? ''),
67
- ragScope,
68
- emitEvent: this.deps.emitEvent,
69
- traceCollector: state['traceCollector'],
70
- traceId: resolveTraceId(state),
53
+ return withGraphSpan(state, {
54
+ name: this.nodeId,
55
+ type: 'llm',
56
+ extra: { metadata: { rag: true } },
57
+ }, async (spanId) => {
58
+ const result = await runReactLoop({
59
+ nodeId: this.nodeId,
60
+ query: extractQuery(state),
61
+ history: extractHistory(state),
62
+ config: this.config,
63
+ apiTools,
64
+ kbToolNames,
65
+ llmProvider: this.deps.llmProvider,
66
+ toolOrchestrator: this.deps.toolOrchestrator,
67
+ retrievers,
68
+ loadedTools,
69
+ authContext: this.buildAuthContext(state),
70
+ executionContext: state['executionContext'],
71
+ forceToolServer: state['forceToolServer'],
72
+ userId: String(state['userId'] ?? ''),
73
+ ragScope,
74
+ emitEvent: this.deps.emitEvent,
75
+ traceCollector: state['traceCollector'],
76
+ traceId: resolveTraceId(state),
77
+ parentSpanId: spanId,
78
+ });
79
+ // partial state 三连(与 LLMNode 同惯例):
80
+ // messages 只返回新增(channel 为 append reducer);token 走 __llmTokenUsage 累加器
81
+ return {
82
+ messages: [{ role: 'assistant', content: result.contextText }],
83
+ currentNode: this.nodeId,
84
+ __llmTokenUsage: result.tokenUsage,
85
+ };
71
86
  });
72
- // partial state 三连(与 LLMNode 同惯例):
73
- // messages 只返回新增(channel 为 append reducer);token 走 __llmTokenUsage 累加器
74
- return {
75
- messages: [{ role: 'assistant', content: result.contextText }],
76
- currentNode: this.nodeId,
77
- __llmTokenUsage: result.tokenUsage,
78
- };
79
87
  }
80
88
  /** 身份组装照 ToolNode 惯例:executionContext 优先,state 兜底 */
81
89
  buildAuthContext(state) {
@@ -45,6 +45,8 @@ export interface RagLoopInput {
45
45
  /** Trace 采集器 + 执行流 ID(缺省不建 span,照 ToolNode 模式:resolveTraceId) */
46
46
  traceCollector?: ITraceCollector;
47
47
  traceId?: string;
48
+ /** 本 RAG 节点 llm Span;内部检索/降噪挂到它下面 */
49
+ parentSpanId?: string;
48
50
  }
49
51
  export interface RagLoopOutput {
50
52
  /** 降噪上下文(近重去重 + LLM 判定 + 句子抽取后的拼装结果) */
@@ -444,6 +444,7 @@ async function startTraceSpan(input, event) {
444
444
  traceId: input.traceId,
445
445
  name: event.name,
446
446
  type: event.type,
447
+ parentNodeId: input.parentSpanId,
447
448
  input: event.input,
448
449
  metadata: { rag: true, ...event.metadata },
449
450
  });
@@ -19,6 +19,7 @@ import { resolveCheckpointThreadId } from '../../session/persist-turn-messages.j
19
19
  import { mcpToolsetCode, toolTraceIdentity } from '../../tool/mcp-list.js';
20
20
  import { exposureScopeId, exposedNamesForScope, isUnloadedDeferredCall, mcpNamesToExpose, mergeExposed, partitionToolSearchCalls, runToolSearch, unloadedCallToolContent, } from '../../tool/mcp-tool-exposure.js';
21
21
  import { resolveExecutionTrace, warnTraceSpanFailure, } from '../execution-trace-context.js';
22
+ import { resolveInvokedSpanParent } from '../trace-span.js';
22
23
  import { emitRuntimeLog } from '../../common/logger.js';
23
24
  import { toolResultHasArtifacts } from '../../tool/inject-public-artifact-links.js';
24
25
  import { decideBeforeTool, } from '../../execution/interceptors.js';
@@ -212,19 +213,21 @@ export class ToolNode {
212
213
  if (!traceCollector || !traceId)
213
214
  return;
214
215
  try {
215
- const parentNodeId = typeof state['skillSpanId'] === 'string' && state['skillSpanId']
216
- ? state['skillSpanId']
217
- : undefined;
216
+ const parentNodeId = resolveInvokedSpanParent(state);
217
+ const identity = toolTraceIdentity(loadedTools?.[name], name);
218
218
  const spanId = await traceCollector.createNode({
219
219
  traceId,
220
220
  name,
221
221
  type: 'tool',
222
222
  parentNodeId,
223
223
  agentCode: agentCode || undefined,
224
+ ...(identity.toolVersion ? { version: identity.toolVersion } : {}),
224
225
  input,
225
226
  metadata: {
226
227
  toolCallId: callId,
227
228
  ...(notExecuted ? { notExecuted: true } : {}),
229
+ ...(identity.toolset ? { toolset: identity.toolset } : {}),
230
+ ...(identity.toolDisplayName ? { toolDisplayName: identity.toolDisplayName } : {}),
228
231
  },
229
232
  });
230
233
  sideSpanIds.push(spanId);
@@ -293,9 +296,7 @@ export class ToolNode {
293
296
  if (!traceCollector || !traceId)
294
297
  return null;
295
298
  try {
296
- const parentNodeId = typeof state['skillSpanId'] === 'string' && state['skillSpanId']
297
- ? state['skillSpanId']
298
- : undefined;
299
+ const parentNodeId = resolveInvokedSpanParent(state);
299
300
  const identity = toolTraceIdentity(loadedTools?.[req.toolName], req.toolName);
300
301
  return await traceCollector.createNode({
301
302
  traceId,
@@ -366,6 +367,7 @@ export class ToolNode {
366
367
  const confirmed = await interceptor.executeWithConfirmation(req, graphState, req.taskId ?? String(state['taskId'] ?? req.toolCallId), threadId, {
367
368
  toolSpanId: spanIds[i] ?? undefined,
368
369
  skillSpanId: typeof state['skillSpanId'] === 'string' ? state['skillSpanId'] : undefined,
370
+ llmSpanId: typeof state['llmSpanId'] === 'string' ? state['llmSpanId'] : undefined,
369
371
  });
370
372
  result =
371
373
  confirmed ??
@@ -2,6 +2,11 @@
2
2
  * Graph 节点 Span 辅助:createNode / completeNode 失败不挡主路径。
3
3
  */
4
4
  import type { ITraceCollector, ExecutionNodeEvent, CompleteNodeParams } from '../contracts/trace-collector.js';
5
+ /**
6
+ * 被这次模型调用出来的 tool / skill:子图内挂技能,否则挂本回合模型调用。
7
+ * 显式 parent 优先。不用 lastSpanId(工具跑完会把它改成工具自己)。
8
+ */
9
+ export declare function resolveInvokedSpanParent(state: Record<string, unknown>, explicit?: string): string | undefined;
5
10
  export declare function startGraphSpan(state: Record<string, unknown>, input: {
6
11
  name: string;
7
12
  type: ExecutionNodeEvent['type'];
@@ -13,7 +18,7 @@ export declare function withGraphSpan(state: Record<string, unknown>, spec: {
13
18
  name: string;
14
19
  type: ExecutionNodeEvent['type'];
15
20
  extra?: Partial<Omit<ExecutionNodeEvent, 'traceId' | 'name' | 'type' | 'parentNodeId'>>;
16
- }, run: () => Promise<Record<string, unknown>>, complete?: {
21
+ }, run: (spanId?: string) => Promise<Record<string, unknown>>, complete?: {
17
22
  outputFrom?: (result: Record<string, unknown>) => unknown;
18
23
  }): Promise<Record<string, unknown>>;
19
24
  /** llm Span create 时的 input:组装后的 system、工具短目录、本回合 messages。 */
@@ -2,15 +2,27 @@
2
2
  * Graph 节点 Span 辅助:createNode / completeNode 失败不挡主路径。
3
3
  */
4
4
  import { resolveExecutionTrace, warnTraceSpanFailure, } from './execution-trace-context.js';
5
+ function pickSpanId(value) {
6
+ if (typeof value !== 'string')
7
+ return undefined;
8
+ const trimmed = value.trim();
9
+ return trimmed || undefined;
10
+ }
11
+ /**
12
+ * 被这次模型调用出来的 tool / skill:子图内挂技能,否则挂本回合模型调用。
13
+ * 显式 parent 优先。不用 lastSpanId(工具跑完会把它改成工具自己)。
14
+ */
15
+ export function resolveInvokedSpanParent(state, explicit) {
16
+ return pickSpanId(explicit) ?? pickSpanId(state['skillSpanId']) ?? pickSpanId(state['llmSpanId']);
17
+ }
5
18
  export async function startGraphSpan(state, input) {
6
19
  // collector 只从这次调用链取;state 只用来拼 parent/task 元数据。
7
20
  const { collector, traceId } = resolveExecutionTrace(state);
8
21
  if (!collector || !traceId)
9
22
  return undefined;
10
- const parentNodeId = input.parentSpanId ??
11
- (typeof state['skillSpanId'] === 'string' && state['skillSpanId']
12
- ? state['skillSpanId']
13
- : undefined);
23
+ const parentNodeId = input.type === 'tool' || input.type === 'skill'
24
+ ? resolveInvokedSpanParent(state, input.parentSpanId)
25
+ : pickSpanId(input.parentSpanId) ?? pickSpanId(state['skillSpanId']);
14
26
  try {
15
27
  return await collector.createNode({
16
28
  traceId,
@@ -23,6 +35,10 @@ export async function startGraphSpan(state, input) {
23
35
  metadata: {
24
36
  taskId: state['taskId'],
25
37
  correlationId: state['correlationId'],
38
+ // 对人展示名;name 仍是图节点 id / 函数名 / skillId。
39
+ ...(typeof state['agentName'] === 'string' && state['agentName'].trim()
40
+ ? { agentName: state['agentName'].trim() }
41
+ : {}),
26
42
  ...(input.extra?.metadata ?? {}),
27
43
  },
28
44
  });
@@ -46,7 +62,7 @@ export async function completeGraphSpan(state, spanId, params, collectorOverride
46
62
  export async function withGraphSpan(state, spec, run, complete) {
47
63
  const spanId = await startGraphSpan(state, spec);
48
64
  try {
49
- const result = await run();
65
+ const result = await run(spanId);
50
66
  const usage = Array.isArray(result['__llmTokenUsage'])
51
67
  ? result['__llmTokenUsage'].at(-1)
52
68
  : undefined;
@@ -61,7 +77,13 @@ export async function withGraphSpan(state, spec, run, complete) {
61
77
  : undefined,
62
78
  ...(typeof usage?.model === 'string' && usage.model ? { model: usage.model } : {}),
63
79
  });
64
- return spanId ? { ...result, lastSpanId: spanId } : result;
80
+ return spanId
81
+ ? {
82
+ ...result,
83
+ lastSpanId: spanId,
84
+ ...(spec.type === 'llm' ? { llmSpanId: spanId } : {}),
85
+ }
86
+ : result;
65
87
  }
66
88
  catch (err) {
67
89
  await completeGraphSpan(state, spanId, {
@@ -71,6 +71,8 @@ export class ToolConfirmationInterceptor {
71
71
  toolSpanId: extras.toolSpanId,
72
72
  skillSpanId: extras.skillSpanId ??
73
73
  (typeof state.skillSpanId === 'string' ? state.skillSpanId : undefined),
74
+ llmSpanId: extras.llmSpanId ??
75
+ (typeof state.llmSpanId === 'string' ? state.llmSpanId : undefined),
74
76
  };
75
77
  await this.hitlHandler.interrupt(taskId, resolvedThreadId, state, {
76
78
  question,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gt-fe/eap-runtime",
3
- "version": "0.0.40",
3
+ "version": "0.0.41",
4
4
  "description": "Enterprise Agent Platform — Runtime核心:Graph执行引擎、Task生命周期、Session管理、Checkpoint持久化",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",