@gt-fe/eap-runtime 0.0.18 → 0.0.20
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/dist/api/handlers/invoke.handler.js +10 -16
- package/dist/api/handlers/stream.handler.js +3 -0
- package/dist/checkpoint/checkpoint-ref-notify.js +5 -4
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/trace-collector.d.ts +16 -8
- package/dist/graph/adapters/langgraph.adapter.d.ts +5 -12
- package/dist/graph/adapters/langgraph.adapter.js +109 -64
- package/dist/graph/adapters/skill-entry-span.d.ts +37 -0
- package/dist/graph/adapters/skill-entry-span.js +78 -0
- package/dist/graph/adapters/skill-read.d.ts +10 -0
- package/dist/graph/adapters/skill-read.js +33 -0
- package/dist/graph/adapters/subgraph-compiler.js +22 -11
- package/dist/graph/contracts/executable-graph.d.ts +4 -1
- package/dist/graph/execution-trace-context.d.ts +38 -0
- package/dist/graph/execution-trace-context.js +62 -0
- package/dist/graph/hitl/hitl-handler.d.ts +2 -1
- package/dist/graph/hitl/hitl-handler.js +24 -6
- package/dist/graph/nodes/tool-node.d.ts +6 -0
- package/dist/graph/nodes/tool-node.js +51 -8
- package/dist/graph/skill-audit-recorder.d.ts +4 -4
- package/dist/graph/skill-audit-recorder.js +3 -3
- package/dist/graph/trace-span.d.ts +2 -9
- package/dist/graph/trace-span.js +9 -7
- package/dist/index.d.ts +1 -1
- package/dist/runtime/agent-runtime.js +22 -0
- package/dist/runtime/types.d.ts +1 -1
- package/dist/sse/trace-collector.d.ts +2 -9
- package/dist/token/index.d.ts +2 -0
- package/dist/token/index.js +1 -0
- package/dist/token/record-task-token-usage.d.ts +21 -0
- package/dist/token/record-task-token-usage.js +52 -0
- package/dist/token/token-stats.service.d.ts +2 -0
- package/dist/token/token-stats.service.js +4 -0
- package/dist/tool/index.d.ts +1 -1
- package/dist/tool/index.js +1 -1
- package/dist/tool/mcp-list.d.ts +12 -0
- package/dist/tool/mcp-list.js +15 -0
- package/package.json +1 -1
|
@@ -18,6 +18,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
18
18
|
import { buildHitlWaitingTaskOutput, buildTurnGraphMessages, messageRecordsToGraphMessages, persistHitlWaitingPlaceholder, persistTurnTranscript, persistUserTurnMessage, } from '../../session/persist-turn-messages.js';
|
|
19
19
|
import { SessionBudgetExhaustedError } from '../../token/session-budget-checker.js';
|
|
20
20
|
import { TokenBudgetExhaustedError } from '../../token/token-budget-guard.js';
|
|
21
|
+
import { recordTaskTokenUsages } from '../../token/record-task-token-usage.js';
|
|
21
22
|
import { resolveExecutionContext } from '../../governance/governance-client.js';
|
|
22
23
|
import { resolveSkillInputFromInvokeInput } from '../../registry/skill-io-guard.js';
|
|
23
24
|
import { resolveAgentSystemPromptFromLoader } from '../../registry/graph-loader.js';
|
|
@@ -310,24 +311,17 @@ export class InvokeOrchestrator {
|
|
|
310
311
|
};
|
|
311
312
|
// 将 Graph 层面的 token 消耗记录到 Task(按节点明细逐个记录,便于审计)
|
|
312
313
|
if (graphUsage.perNode && graphUsage.perNode.length > 0) {
|
|
313
|
-
|
|
314
|
-
await this.taskService.recordTokenUsage(task.id, {
|
|
315
|
-
nodeId: u.nodeId,
|
|
316
|
-
model: u.model,
|
|
317
|
-
inputTokens: u.inputTokens,
|
|
318
|
-
outputTokens: u.outputTokens,
|
|
319
|
-
costEstimate: 0,
|
|
320
|
-
});
|
|
321
|
-
}
|
|
314
|
+
await recordTaskTokenUsages(this.taskService, task.id, graphUsage.perNode);
|
|
322
315
|
}
|
|
323
316
|
else if (graphUsage.total > 0) {
|
|
324
|
-
await this.taskService
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
317
|
+
await recordTaskTokenUsages(this.taskService, task.id, [
|
|
318
|
+
{
|
|
319
|
+
nodeId: 'graph',
|
|
320
|
+
model: 'unknown',
|
|
321
|
+
inputTokens: graphUsage.input,
|
|
322
|
+
outputTokens: graphUsage.output,
|
|
323
|
+
},
|
|
324
|
+
]);
|
|
331
325
|
}
|
|
332
326
|
// ─── Step 7: 完成 Task (running → success) ───
|
|
333
327
|
await this.taskService.updateStatus(task.id, 'success', graphResult.output);
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
import { buildHitlWaitingTaskOutput, buildTurnGraphMessages, buildTurnTranscriptFromStreamEvents, extractStreamReasoning, extractStreamTextDelta, messageRecordsToGraphMessages, persistHitlWaitingPlaceholder, persistTurnTranscript, persistUserTurnMessage, } from '../../session/persist-turn-messages.js';
|
|
23
23
|
import { SessionBudgetExhaustedError } from '../../token/session-budget-checker.js';
|
|
24
24
|
import { TokenBudgetExhaustedError } from '../../token/token-budget-guard.js';
|
|
25
|
+
import { recordTaskTokenUsages } from '../../token/record-task-token-usage.js';
|
|
25
26
|
import { SSEEmitter } from '../../sse/sse-emitter.js';
|
|
26
27
|
import { resolveExecutionContext } from '../../governance/governance-client.js';
|
|
27
28
|
import { resolveSkillInputFromInvokeInput } from '../../registry/skill-io-guard.js';
|
|
@@ -303,6 +304,7 @@ export class StreamOrchestrator {
|
|
|
303
304
|
catch (error) {
|
|
304
305
|
const hitlPaused = unwrapHitlPaused(error);
|
|
305
306
|
if (hitlPaused) {
|
|
307
|
+
await recordTaskTokenUsages(this.taskService, taskId, this.tokenStatsService.list(taskId));
|
|
306
308
|
await this.taskService.updateStatus(taskId, 'waiting_hitl', buildHitlWaitingTaskOutput({
|
|
307
309
|
threadId: hitlPaused.threadId,
|
|
308
310
|
question: hitlPaused.question,
|
|
@@ -370,6 +372,7 @@ export class StreamOrchestrator {
|
|
|
370
372
|
taskId,
|
|
371
373
|
duration,
|
|
372
374
|
}));
|
|
375
|
+
await recordTaskTokenUsages(this.taskService, taskId, this.tokenStatsService.list(taskId));
|
|
373
376
|
// Task 状态流转 → success
|
|
374
377
|
await this.taskService.updateStatus(taskId, 'success');
|
|
375
378
|
// ─── Step 8b: 持久化 transcript(与 invoke 同序)───
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* 无 traceId 或 spanId 则跳过;回调失败不抛。
|
|
4
4
|
* collector 不得写入持久化正文。
|
|
5
5
|
*/
|
|
6
|
+
import { getExecutionTrace } from '../graph/execution-trace-context.js';
|
|
6
7
|
function pickString(...values) {
|
|
7
8
|
for (const value of values) {
|
|
8
9
|
if (typeof value === 'string' && value.trim())
|
|
@@ -34,10 +35,10 @@ export async function notifyCheckpointRef(data, collectorOverride) {
|
|
|
34
35
|
? data.engineSnapshot.channel_values
|
|
35
36
|
: {};
|
|
36
37
|
const state = (data.state ?? {});
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const traceId = pickString(state['traceId'], channels['traceId']);
|
|
38
|
+
const store = getExecutionTrace();
|
|
39
|
+
// collector 是活对象,不从 Checkpoint 正文读(落盘已剥掉)。
|
|
40
|
+
const collector = (collectorOverride ?? store?.collector);
|
|
41
|
+
const traceId = pickString(store?.traceId, state['traceId'], channels['traceId']);
|
|
41
42
|
const spanId = pickString(state['lastSpanId'], channels['lastSpanId']);
|
|
42
43
|
if (!collector?.recordCheckpointRef || !traceId || !spanId)
|
|
43
44
|
return;
|
|
@@ -7,5 +7,5 @@
|
|
|
7
7
|
* - LLM 调用:由模型路由模块实现
|
|
8
8
|
* - 存储接口:由各存储后端实现
|
|
9
9
|
*/
|
|
10
|
-
export type { ITraceCollector, TraceEvent, TraceSession, } from './trace-collector.js';
|
|
10
|
+
export type { ITraceCollector, CompleteNodeParams, TraceEvent, TraceSession, } from './trace-collector.js';
|
|
11
11
|
export type { IPermissionChecker } from './permission-checker.js';
|
|
@@ -85,6 +85,8 @@ export interface ExecutionNodeEvent {
|
|
|
85
85
|
type: 'start' | 'llm' | 'tool' | 'skill' | 'condition' | 'hitl' | 'end';
|
|
86
86
|
/** 工具运行环境(type=tool) */
|
|
87
87
|
toolRuntime?: 'local' | 'server' | 'both';
|
|
88
|
+
/** skill/tool 包版本;与 TraceClient RecordSpanParams.version 同义 */
|
|
89
|
+
version?: string;
|
|
88
90
|
/** Agent 对外主键 */
|
|
89
91
|
agentCode?: string;
|
|
90
92
|
/** 输入数据 */
|
|
@@ -92,6 +94,19 @@ export interface ExecutionNodeEvent {
|
|
|
92
94
|
/** 扩展元数据(如 skillId) */
|
|
93
95
|
metadata?: Record<string, unknown>;
|
|
94
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* 完成执行节点参数。llm Span 的 model / token 与 stats.node_token_usage 同源。
|
|
99
|
+
*/
|
|
100
|
+
export interface CompleteNodeParams {
|
|
101
|
+
status: 'completed' | 'failed';
|
|
102
|
+
output?: unknown;
|
|
103
|
+
error?: string;
|
|
104
|
+
inputTokens?: number;
|
|
105
|
+
outputTokens?: number;
|
|
106
|
+
totalTokens?: number;
|
|
107
|
+
/** llm Span:实际模型标识;与 NodeTokenUsage.model 相同 */
|
|
108
|
+
model?: string;
|
|
109
|
+
}
|
|
95
110
|
/**
|
|
96
111
|
* 链路事件
|
|
97
112
|
*
|
|
@@ -176,14 +191,7 @@ export interface ITraceCollector {
|
|
|
176
191
|
* @param nodeId - 节点 ID
|
|
177
192
|
* @param params - 完成参数
|
|
178
193
|
*/
|
|
179
|
-
completeNode(nodeId: string, params:
|
|
180
|
-
status: 'completed' | 'failed';
|
|
181
|
-
output?: unknown;
|
|
182
|
-
error?: string;
|
|
183
|
-
inputTokens?: number;
|
|
184
|
-
outputTokens?: number;
|
|
185
|
-
totalTokens?: number;
|
|
186
|
-
}): Promise<void>;
|
|
194
|
+
completeNode(nodeId: string, params: CompleteNodeParams): Promise<void>;
|
|
187
195
|
/**
|
|
188
196
|
* Checkpoint 已落盘后的引用回调。Hosting 适配 TraceClient.recordCheckpointRef。
|
|
189
197
|
* Runtime 仅在同时有 traceId 与 spanId 时调用;不传 graph state 正文。
|
|
@@ -35,13 +35,6 @@ export declare class LangGraphAdapter implements IStateGraphEngine {
|
|
|
35
35
|
private readonly conditionEvaluator;
|
|
36
36
|
private readonly systemPromptBuilder;
|
|
37
37
|
private readonly skillAuditRecorder;
|
|
38
|
-
/** 流式旁路:LLM token / Tool 执行事件在节点执行中实时入队 */
|
|
39
|
-
private streamSink;
|
|
40
|
-
private streamEventContext;
|
|
41
|
-
private streamChunkIndex;
|
|
42
|
-
private streamIteration;
|
|
43
|
-
private reasoningStartedThisTurn;
|
|
44
|
-
private streamedTextThisTurn;
|
|
45
38
|
/**
|
|
46
39
|
* @param checkpointSaver - 可选的 ICheckpointSaver
|
|
47
40
|
* @param llmProvider - LLM 调用提供者,默认 EchoLLMProvider
|
|
@@ -56,7 +49,7 @@ export declare class LangGraphAdapter implements IStateGraphEngine {
|
|
|
56
49
|
registerConditionEvaluator(name: string, fn: ConditionEvaluatorFn): void;
|
|
57
50
|
/**
|
|
58
51
|
* 节点旁路推送 RuntimeEvent(子图 Skill 生命周期、Tool 执行)。
|
|
59
|
-
*
|
|
52
|
+
* 无本次执行流式袋时为空操作(纯 invoke)。
|
|
60
53
|
*/
|
|
61
54
|
emitStreamEvent(event: RuntimeEvent): void;
|
|
62
55
|
/**
|
|
@@ -67,14 +60,14 @@ export declare class LangGraphAdapter implements IStateGraphEngine {
|
|
|
67
60
|
* Tool 节点执行旁路:started → tool.called + tool.progress;
|
|
68
61
|
* completed/error → 对应终态 + tool.progress。同一 toolCallId。
|
|
69
62
|
* 读 SKILL.md / 技能包内文件:成功后再发 skill.loaded / skill.resource_read,同时保留 tool.*。
|
|
63
|
+
* 读 SKILL.md 另发 skill.started / skill.completed(与 Trace type=skill Span 对齐);子图内不重复。
|
|
70
64
|
*/
|
|
71
65
|
private emitToolExecutionEvents;
|
|
72
66
|
/**
|
|
73
67
|
* 解析 GraphDSL 并构建可执行图
|
|
74
68
|
*
|
|
75
|
-
* **Request Isolation**: 每次调用都创建全新的 StateGraph
|
|
76
|
-
*
|
|
77
|
-
* 不缓存任何编译产物或运行时状态。
|
|
69
|
+
* **Request Isolation**: 每次调用都创建全新的 StateGraph 实例并编译。
|
|
70
|
+
* 一次执行的 collector / 流式袋跟这次调用走,不挂 Adapter 字段。
|
|
78
71
|
*
|
|
79
72
|
* 流程:
|
|
80
73
|
* 1. 创建 StateGraph 实例(配置 channels)
|
|
@@ -89,7 +82,7 @@ export declare class LangGraphAdapter implements IStateGraphEngine {
|
|
|
89
82
|
* 流式驱动图执行,逐步 yield RuntimeEvent
|
|
90
83
|
*
|
|
91
84
|
* - LangGraph `streamMode: "updates"`:节点完成后产出 response.chunk / stats 等
|
|
92
|
-
* - LLM token / Tool
|
|
85
|
+
* - LLM token / Tool 执行事件:经本次执行流式袋在节点执行中实时旁路入队
|
|
93
86
|
* (当前 @langchain/langgraph@0.2 无 messages mode,故用 LLMNode.onChunk 模拟)
|
|
94
87
|
*/
|
|
95
88
|
stream(graph: ExecutableGraph, state: GraphState): AsyncIterableIterator<RuntimeEvent>;
|
|
@@ -30,7 +30,9 @@ import { AppError, ErrorCodes } from '@gt-fe/eap-contracts';
|
|
|
30
30
|
import { pickContextFields } from '../../token/context-window.js';
|
|
31
31
|
import { isHitlDecision, lastAssistantHasUnansweredToolCalls } from '../hitl/hitl-handler.js';
|
|
32
32
|
import { classifySkillRead } from './skill-read.js';
|
|
33
|
+
import { skillEntryInvocationFromTool } from './skill-entry-span.js';
|
|
33
34
|
import { withGraphSpan } from '../trace-span.js';
|
|
35
|
+
import { executionTraceFromState, getExecutionStream, runWithExecutionTrace, } from '../execution-trace-context.js';
|
|
34
36
|
/** 图未写 `config.maxIterations` 时的 recursionLimit。`agent ⇄ tools` 一圈约 2,50 ≈ 24 次工具往返。 */
|
|
35
37
|
export const DEFAULT_GRAPH_RECURSION_LIMIT = 50;
|
|
36
38
|
/**
|
|
@@ -68,13 +70,6 @@ export class LangGraphAdapter {
|
|
|
68
70
|
conditionEvaluator;
|
|
69
71
|
systemPromptBuilder;
|
|
70
72
|
skillAuditRecorder;
|
|
71
|
-
/** 流式旁路:LLM token / Tool 执行事件在节点执行中实时入队 */
|
|
72
|
-
streamSink = null;
|
|
73
|
-
streamEventContext = {};
|
|
74
|
-
streamChunkIndex = 0;
|
|
75
|
-
streamIteration = 0;
|
|
76
|
-
reasoningStartedThisTurn = false;
|
|
77
|
-
streamedTextThisTurn = false;
|
|
78
73
|
/**
|
|
79
74
|
* @param checkpointSaver - 可选的 ICheckpointSaver
|
|
80
75
|
* @param llmProvider - LLM 调用提供者,默认 EchoLLMProvider
|
|
@@ -140,43 +135,51 @@ export class LangGraphAdapter {
|
|
|
140
135
|
}
|
|
141
136
|
/**
|
|
142
137
|
* 节点旁路推送 RuntimeEvent(子图 Skill 生命周期、Tool 执行)。
|
|
143
|
-
*
|
|
138
|
+
* 无本次执行流式袋时为空操作(纯 invoke)。
|
|
144
139
|
*/
|
|
145
140
|
emitStreamEvent(event) {
|
|
146
|
-
|
|
141
|
+
const stream = getExecutionStream();
|
|
142
|
+
if (!stream)
|
|
147
143
|
return;
|
|
148
|
-
|
|
144
|
+
stream.sink({
|
|
149
145
|
...event,
|
|
150
|
-
taskId: event.taskId ??
|
|
151
|
-
correlationId: event.correlationId ??
|
|
146
|
+
taskId: event.taskId ?? stream.eventContext.taskId,
|
|
147
|
+
correlationId: event.correlationId ?? stream.eventContext.correlationId,
|
|
152
148
|
});
|
|
153
149
|
}
|
|
154
150
|
/**
|
|
155
151
|
* 子图 LLM 可清零流式标志;结束后保留「已经 stream 过」以免父图 convertUpdates 再发一遍正文。
|
|
156
152
|
*/
|
|
157
153
|
async runIsolatedLlmStreamFlags(work) {
|
|
158
|
-
const
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
154
|
+
const stream = getExecutionStream();
|
|
155
|
+
const savedStreamed = stream?.streamedTextThisTurn ?? false;
|
|
156
|
+
const savedReasoning = stream?.reasoningStartedThisTurn ?? false;
|
|
157
|
+
if (stream) {
|
|
158
|
+
stream.reasoningStartedThisTurn = false;
|
|
159
|
+
stream.streamedTextThisTurn = false;
|
|
160
|
+
}
|
|
162
161
|
try {
|
|
163
162
|
return await work();
|
|
164
163
|
}
|
|
165
164
|
finally {
|
|
166
|
-
|
|
167
|
-
|
|
165
|
+
if (stream) {
|
|
166
|
+
stream.streamedTextThisTurn = savedStreamed || stream.streamedTextThisTurn;
|
|
167
|
+
stream.reasoningStartedThisTurn = savedReasoning || stream.reasoningStartedThisTurn;
|
|
168
|
+
}
|
|
168
169
|
}
|
|
169
170
|
}
|
|
170
171
|
/**
|
|
171
172
|
* Tool 节点执行旁路:started → tool.called + tool.progress;
|
|
172
173
|
* completed/error → 对应终态 + tool.progress。同一 toolCallId。
|
|
173
174
|
* 读 SKILL.md / 技能包内文件:成功后再发 skill.loaded / skill.resource_read,同时保留 tool.*。
|
|
175
|
+
* 读 SKILL.md 另发 skill.started / skill.completed(与 Trace type=skill Span 对齐);子图内不重复。
|
|
174
176
|
*/
|
|
175
177
|
emitToolExecutionEvents(progress) {
|
|
176
|
-
|
|
178
|
+
const stream = getExecutionStream();
|
|
179
|
+
if (!stream)
|
|
177
180
|
return;
|
|
178
181
|
const timestamp = new Date().toISOString();
|
|
179
|
-
const { taskId, correlationId } =
|
|
182
|
+
const { taskId, correlationId } = stream.eventContext;
|
|
180
183
|
const loc = {
|
|
181
184
|
...(progress.node ? { node: progress.node } : {}),
|
|
182
185
|
...(progress.skillId ? { skillId: progress.skillId } : {}),
|
|
@@ -184,19 +187,51 @@ export class LangGraphAdapter {
|
|
|
184
187
|
const skillRead = classifySkillRead(progress.toolName, progress.arguments, progress.skillDirs);
|
|
185
188
|
if (skillRead && progress.status === 'completed') {
|
|
186
189
|
if (skillRead.kind === 'loaded') {
|
|
187
|
-
|
|
190
|
+
const skillId = skillRead.skillId || loc.skillId || skillRead.path;
|
|
191
|
+
stream.sink({
|
|
188
192
|
type: 'skill.loaded',
|
|
189
193
|
taskId,
|
|
190
194
|
correlationId,
|
|
191
195
|
timestamp,
|
|
192
196
|
payload: {
|
|
193
|
-
skillId
|
|
197
|
+
skillId,
|
|
194
198
|
path: skillRead.path,
|
|
195
199
|
},
|
|
196
200
|
});
|
|
201
|
+
const catalogSkill = skillEntryInvocationFromTool({
|
|
202
|
+
toolName: progress.toolName,
|
|
203
|
+
arguments: progress.arguments,
|
|
204
|
+
skillDirs: progress.skillDirs,
|
|
205
|
+
alreadyInSkillSpan: progress.inSkillSubgraph,
|
|
206
|
+
});
|
|
207
|
+
if (catalogSkill) {
|
|
208
|
+
stream.sink({
|
|
209
|
+
type: 'skill.started',
|
|
210
|
+
taskId,
|
|
211
|
+
correlationId,
|
|
212
|
+
timestamp,
|
|
213
|
+
payload: {
|
|
214
|
+
skillId: catalogSkill.skillId,
|
|
215
|
+
skillName: progress.skillName || catalogSkill.skillId,
|
|
216
|
+
path: catalogSkill.path,
|
|
217
|
+
...(progress.skillSpace ? { skillSpace: progress.skillSpace } : {}),
|
|
218
|
+
...(progress.skillVersion ? { skillVersion: progress.skillVersion } : {}),
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
stream.sink({
|
|
222
|
+
type: 'skill.completed',
|
|
223
|
+
taskId,
|
|
224
|
+
correlationId,
|
|
225
|
+
timestamp,
|
|
226
|
+
payload: {
|
|
227
|
+
skillId: catalogSkill.skillId,
|
|
228
|
+
skillName: catalogSkill.skillId,
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
}
|
|
197
232
|
}
|
|
198
233
|
else {
|
|
199
|
-
|
|
234
|
+
stream.sink({
|
|
200
235
|
type: 'skill.resource_read',
|
|
201
236
|
taskId,
|
|
202
237
|
correlationId,
|
|
@@ -206,7 +241,7 @@ export class LangGraphAdapter {
|
|
|
206
241
|
}
|
|
207
242
|
}
|
|
208
243
|
if (progress.status === 'started') {
|
|
209
|
-
|
|
244
|
+
stream.sink({
|
|
210
245
|
type: 'tool.called',
|
|
211
246
|
taskId,
|
|
212
247
|
correlationId,
|
|
@@ -215,12 +250,13 @@ export class LangGraphAdapter {
|
|
|
215
250
|
toolCallId: progress.toolCallId,
|
|
216
251
|
toolName: progress.toolName,
|
|
217
252
|
...(progress.toolset ? { toolset: progress.toolset } : {}),
|
|
253
|
+
...(progress.toolVersion ? { toolVersion: progress.toolVersion } : {}),
|
|
218
254
|
arguments: progress.arguments ?? {},
|
|
219
255
|
...loc,
|
|
220
256
|
},
|
|
221
257
|
});
|
|
222
258
|
}
|
|
223
|
-
|
|
259
|
+
stream.sink({
|
|
224
260
|
type: 'tool.progress',
|
|
225
261
|
taskId,
|
|
226
262
|
correlationId,
|
|
@@ -228,7 +264,7 @@ export class LangGraphAdapter {
|
|
|
228
264
|
payload: { ...progress },
|
|
229
265
|
});
|
|
230
266
|
if (progress.status === 'completed') {
|
|
231
|
-
|
|
267
|
+
stream.sink({
|
|
232
268
|
type: 'tool.result',
|
|
233
269
|
taskId,
|
|
234
270
|
correlationId,
|
|
@@ -244,7 +280,7 @@ export class LangGraphAdapter {
|
|
|
244
280
|
});
|
|
245
281
|
}
|
|
246
282
|
else if (progress.status === 'error') {
|
|
247
|
-
|
|
283
|
+
stream.sink({
|
|
248
284
|
type: 'tool.error',
|
|
249
285
|
taskId,
|
|
250
286
|
correlationId,
|
|
@@ -263,9 +299,8 @@ export class LangGraphAdapter {
|
|
|
263
299
|
/**
|
|
264
300
|
* 解析 GraphDSL 并构建可执行图
|
|
265
301
|
*
|
|
266
|
-
* **Request Isolation**: 每次调用都创建全新的 StateGraph
|
|
267
|
-
*
|
|
268
|
-
* 不缓存任何编译产物或运行时状态。
|
|
302
|
+
* **Request Isolation**: 每次调用都创建全新的 StateGraph 实例并编译。
|
|
303
|
+
* 一次执行的 collector / 流式袋跟这次调用走,不挂 Adapter 字段。
|
|
269
304
|
*
|
|
270
305
|
* 流程:
|
|
271
306
|
* 1. 创建 StateGraph 实例(配置 channels)
|
|
@@ -410,7 +445,7 @@ export class LangGraphAdapter {
|
|
|
410
445
|
* 流式驱动图执行,逐步 yield RuntimeEvent
|
|
411
446
|
*
|
|
412
447
|
* - LangGraph `streamMode: "updates"`:节点完成后产出 response.chunk / stats 等
|
|
413
|
-
* - LLM token / Tool
|
|
448
|
+
* - LLM token / Tool 执行事件:经本次执行流式袋在节点执行中实时旁路入队
|
|
414
449
|
* (当前 @langchain/langgraph@0.2 无 messages mode,故用 LLMNode.onChunk 模拟)
|
|
415
450
|
*/
|
|
416
451
|
async *stream(graph, state) {
|
|
@@ -434,13 +469,17 @@ export class LangGraphAdapter {
|
|
|
434
469
|
});
|
|
435
470
|
signal();
|
|
436
471
|
};
|
|
437
|
-
this
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
472
|
+
// 流式袋跟这次调用走,不挂 this:并行 stream 各写各的 queue。
|
|
473
|
+
const streamBag = {
|
|
474
|
+
sink: enqueue,
|
|
475
|
+
eventContext: { taskId: state.taskId, correlationId: state.correlationId },
|
|
476
|
+
chunkIndex: 0,
|
|
477
|
+
iteration: 0,
|
|
478
|
+
reasoningStartedThisTurn: false,
|
|
479
|
+
streamedTextThisTurn: false,
|
|
480
|
+
};
|
|
481
|
+
const traceCtx = executionTraceFromState(state);
|
|
482
|
+
const producer = runWithExecutionTrace({ ...traceCtx, stream: streamBag }, () => (async () => {
|
|
444
483
|
try {
|
|
445
484
|
const streamIterator = await ref.compiled.stream(input, langGraphRunOptions(ref.dsl, {
|
|
446
485
|
configurable: {
|
|
@@ -453,7 +492,7 @@ export class LangGraphAdapter {
|
|
|
453
492
|
streamMode: 'updates',
|
|
454
493
|
}));
|
|
455
494
|
for await (const update of streamIterator) {
|
|
456
|
-
for (const event of this.convertUpdatesToEvents(update, graph.id, () =>
|
|
495
|
+
for (const event of this.convertUpdatesToEvents(update, graph.id, () => streamBag.chunkIndex++, ref.silentNodeIds, ref.subgraphNodeIds)) {
|
|
457
496
|
enqueue(event);
|
|
458
497
|
}
|
|
459
498
|
}
|
|
@@ -463,11 +502,9 @@ export class LangGraphAdapter {
|
|
|
463
502
|
}
|
|
464
503
|
finally {
|
|
465
504
|
finished = true;
|
|
466
|
-
this.streamSink = null;
|
|
467
|
-
this.streamEventContext = {};
|
|
468
505
|
signal();
|
|
469
506
|
}
|
|
470
|
-
})();
|
|
507
|
+
})());
|
|
471
508
|
while (!finished || queue.length > 0) {
|
|
472
509
|
if (queue.length === 0) {
|
|
473
510
|
await new Promise((resolve) => {
|
|
@@ -488,7 +525,9 @@ export class LangGraphAdapter {
|
|
|
488
525
|
const ref = this.getCompiledRef(graph);
|
|
489
526
|
const input = this.graphStateToLangGraphInput(state);
|
|
490
527
|
const startTime = Date.now();
|
|
491
|
-
const
|
|
528
|
+
const traceCtx = executionTraceFromState(state);
|
|
529
|
+
// 只补 collector/traceId。子图套娃时外层 stream 袋经 ?? 保留。
|
|
530
|
+
const result = await runWithExecutionTrace(traceCtx, () => ref.compiled.invoke(input, langGraphRunOptions(ref.dsl, {
|
|
492
531
|
configurable: {
|
|
493
532
|
thread_id: resolveCheckpointThreadId({
|
|
494
533
|
sessionId: typeof state.sessionId === 'string' ? state.sessionId : undefined,
|
|
@@ -496,7 +535,7 @@ export class LangGraphAdapter {
|
|
|
496
535
|
graphId: graph.id,
|
|
497
536
|
}),
|
|
498
537
|
},
|
|
499
|
-
}));
|
|
538
|
+
})));
|
|
500
539
|
const duration = Date.now() - startTime;
|
|
501
540
|
const messages = Array.isArray(result['messages']) ? result['messages'] : [];
|
|
502
541
|
const lastMessage = messages[messages.length - 1];
|
|
@@ -661,15 +700,16 @@ export class LangGraphAdapter {
|
|
|
661
700
|
},
|
|
662
701
|
});
|
|
663
702
|
}
|
|
664
|
-
|
|
703
|
+
const stream = getExecutionStream();
|
|
704
|
+
if (patch.skillRoute === 'clarify' && stream) {
|
|
665
705
|
const question = patch.messages.at(-1)?.content;
|
|
666
706
|
if (typeof question === 'string' && question.trim()) {
|
|
667
|
-
|
|
707
|
+
stream.sink({
|
|
668
708
|
type: 'response.chunk',
|
|
669
709
|
taskId: undefined,
|
|
670
710
|
correlationId: undefined,
|
|
671
711
|
timestamp: new Date().toISOString(),
|
|
672
|
-
payload: { content: question, index:
|
|
712
|
+
payload: { content: question, index: stream.chunkIndex++, node: node.id },
|
|
673
713
|
});
|
|
674
714
|
}
|
|
675
715
|
}
|
|
@@ -892,38 +932,42 @@ export class LangGraphAdapter {
|
|
|
892
932
|
tools: disableTools ? undefined : (apiTools.length > 0 ? apiTools : baseConfig.tools),
|
|
893
933
|
toolChoice: disableTools ? 'none' : baseConfig.toolChoice,
|
|
894
934
|
};
|
|
895
|
-
//
|
|
896
|
-
const
|
|
897
|
-
|
|
898
|
-
|
|
935
|
+
// 流式旁路:有本次 stream 袋时启用 LLM token 推送;silent 节点不推给用户
|
|
936
|
+
const stream = getExecutionStream();
|
|
937
|
+
const enableStream = Boolean(stream) && !silent;
|
|
938
|
+
if (stream) {
|
|
939
|
+
stream.reasoningStartedThisTurn = false;
|
|
940
|
+
stream.streamedTextThisTurn = false;
|
|
941
|
+
}
|
|
899
942
|
const llmNode = new LLMNode(node.id, finalConfig, this.llmProvider, enableStream, enableStream
|
|
900
943
|
? (text) => {
|
|
901
|
-
|
|
944
|
+
const live = getExecutionStream();
|
|
945
|
+
if (!live || !text)
|
|
902
946
|
return;
|
|
903
947
|
const timestamp = new Date().toISOString();
|
|
904
|
-
if (!
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
948
|
+
if (!live.reasoningStartedThisTurn) {
|
|
949
|
+
live.reasoningStartedThisTurn = true;
|
|
950
|
+
live.iteration += 1;
|
|
951
|
+
live.sink({
|
|
908
952
|
type: 'reasoning.started',
|
|
909
953
|
taskId: undefined,
|
|
910
954
|
correlationId: undefined,
|
|
911
955
|
timestamp,
|
|
912
956
|
payload: {
|
|
913
|
-
iterationCount:
|
|
957
|
+
iterationCount: live.iteration,
|
|
914
958
|
model: finalConfig.model,
|
|
915
959
|
},
|
|
916
960
|
});
|
|
917
961
|
}
|
|
918
|
-
|
|
919
|
-
|
|
962
|
+
live.streamedTextThisTurn = true;
|
|
963
|
+
live.sink({
|
|
920
964
|
type: 'response.chunk',
|
|
921
965
|
taskId: undefined,
|
|
922
966
|
correlationId: undefined,
|
|
923
967
|
timestamp,
|
|
924
968
|
payload: {
|
|
925
969
|
content: text,
|
|
926
|
-
index:
|
|
970
|
+
index: live.chunkIndex++,
|
|
927
971
|
node: node.id,
|
|
928
972
|
},
|
|
929
973
|
});
|
|
@@ -1229,6 +1273,7 @@ export class LangGraphAdapter {
|
|
|
1229
1273
|
*/
|
|
1230
1274
|
*convertUpdatesToEvents(update, graphId, nextChunkIndex, silentNodeIds = new Set(), subgraphNodeIds = new Set()) {
|
|
1231
1275
|
const timestamp = new Date().toISOString();
|
|
1276
|
+
const streamedTextThisTurn = Boolean(getExecutionStream()?.streamedTextThisTurn);
|
|
1232
1277
|
const entries = this.normalizeStreamUpdates(update);
|
|
1233
1278
|
for (const { nodeId, data } of entries) {
|
|
1234
1279
|
// LLM 节点 token 明细(写入 __llmTokenUsage)→ stats.node_token_usage
|
|
@@ -1266,8 +1311,8 @@ export class LangGraphAdapter {
|
|
|
1266
1311
|
}
|
|
1267
1312
|
const hasText = typeof msg.content === 'string' && msg.content.trim().length > 0;
|
|
1268
1313
|
if ((hasText || msg.reasoningContent) &&
|
|
1269
|
-
(!
|
|
1270
|
-
if (
|
|
1314
|
+
(!streamedTextThisTurn || msg.reasoningContent)) {
|
|
1315
|
+
if (streamedTextThisTurn && !msg.reasoningContent)
|
|
1271
1316
|
continue;
|
|
1272
1317
|
yield {
|
|
1273
1318
|
type: 'response.chunk',
|
|
@@ -1275,7 +1320,7 @@ export class LangGraphAdapter {
|
|
|
1275
1320
|
correlationId: undefined,
|
|
1276
1321
|
timestamp,
|
|
1277
1322
|
payload: {
|
|
1278
|
-
content:
|
|
1323
|
+
content: streamedTextThisTurn ? '' : (msg.content ?? ''),
|
|
1279
1324
|
index: nextChunkIndex(),
|
|
1280
1325
|
node: nodeId,
|
|
1281
1326
|
graphId,
|
|
@@ -1291,7 +1336,7 @@ export class LangGraphAdapter {
|
|
|
1291
1336
|
if (silentNodeIds.has(nodeId) || subgraphNodeIds.has(nodeId))
|
|
1292
1337
|
continue;
|
|
1293
1338
|
// 已通过 onChunk 推送过 token,避免整段重复
|
|
1294
|
-
if (
|
|
1339
|
+
if (streamedTextThisTurn) {
|
|
1295
1340
|
// token 已流过:仍补发一条带 reasoning 的标记 chunk(空 content),供落库回传
|
|
1296
1341
|
if (msg.reasoningContent) {
|
|
1297
1342
|
yield {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 成功读 SKILL.md 时写 Trace `type=skill` Span。
|
|
3
|
+
*
|
|
4
|
+
* 假设:Skill 被调用 = 一次成功读取技能包入口(SKILL.md),
|
|
5
|
+
* 与子图激活走同一 collector.createNode({ type: 'skill' })。
|
|
6
|
+
* 声明版本在 createNode.version;skillSpace 只在 input。
|
|
7
|
+
* 子图已有 skillSpanId 时不记第二条。write_file / 包内附属读 / 失败读不是调用。
|
|
8
|
+
*/
|
|
9
|
+
import { type SkillDirRef, type SkillSpace } from './skill-read.js';
|
|
10
|
+
export interface SkillTraceIdentity {
|
|
11
|
+
skillId: string;
|
|
12
|
+
skillName?: string;
|
|
13
|
+
skillVersion?: string;
|
|
14
|
+
skillSpace?: SkillSpace;
|
|
15
|
+
}
|
|
16
|
+
export declare function skillTraceIdentity(state: Record<string, unknown>, skillId: string, fallbackPath?: string): SkillTraceIdentity;
|
|
17
|
+
export declare function skillSpanFields(identity: SkillTraceIdentity, extra?: {
|
|
18
|
+
path?: string;
|
|
19
|
+
source?: string;
|
|
20
|
+
}): {
|
|
21
|
+
version?: string;
|
|
22
|
+
input: Record<string, unknown>;
|
|
23
|
+
};
|
|
24
|
+
export declare function skillEntryInvocationFromTool(input: {
|
|
25
|
+
toolName: string;
|
|
26
|
+
arguments?: Record<string, unknown>;
|
|
27
|
+
skillDirs?: SkillDirRef[];
|
|
28
|
+
alreadyInSkillSpan?: boolean;
|
|
29
|
+
}): {
|
|
30
|
+
skillId: string;
|
|
31
|
+
path: string;
|
|
32
|
+
} | null;
|
|
33
|
+
export declare function recordSkillEntrySpan(state: Record<string, unknown>, input: {
|
|
34
|
+
toolName: string;
|
|
35
|
+
arguments?: Record<string, unknown>;
|
|
36
|
+
skillDirs?: SkillDirRef[];
|
|
37
|
+
}): Promise<void>;
|