@gt-fe/eap-runtime 0.0.22 → 0.0.24
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.d.ts +4 -0
- package/dist/api/handlers/invoke.handler.js +38 -19
- package/dist/api/handlers/stream.handler.d.ts +4 -0
- package/dist/api/handlers/stream.handler.js +24 -15
- package/dist/api/routes/agent.routes.js +6 -5
- package/dist/checkpoint/checkpoint-ref-notify.js +1 -1
- package/dist/common/types.d.ts +6 -2
- package/dist/common/types.js +3 -1
- package/dist/graph/adapters/langgraph.adapter.d.ts +6 -4
- package/dist/graph/adapters/langgraph.adapter.js +103 -18
- package/dist/graph/contracts/executable-graph.d.ts +9 -0
- package/dist/graph/execution-trace-context.d.ts +4 -0
- package/dist/graph/execution-trace-context.js +3 -1
- package/dist/graph/nodes/rag/types.d.ts +4 -1
- package/dist/graph/nodes/tool-node.js +10 -1
- package/dist/providers/http-llm-provider.js +25 -0
- package/dist/registry/graph-loader.js +65 -9
- package/dist/runtime/agent-runtime.d.ts +3 -0
- package/dist/runtime/agent-runtime.js +95 -15
- package/dist/runtime/dev-runtime.js +9 -2
- package/dist/runtime/types.d.ts +1 -1
- package/dist/sse/events/response-events.d.ts +2 -2
- package/dist/sse/events/response-events.js +1 -1
- package/dist/task/contracts/task-manager.contract.d.ts +2 -0
- package/dist/task/index.d.ts +1 -0
- package/dist/task/index.js +1 -0
- package/dist/task/resolve-task-timeout.d.ts +6 -0
- package/dist/task/resolve-task-timeout.js +16 -0
- package/dist/task/task-timeout.d.ts +1 -1
- package/dist/task/task-timeout.js +3 -2
- package/dist/task/task.service.js +1 -0
- package/dist/tool/http-tool-executor.js +7 -3
- package/dist/tool/index.d.ts +2 -1
- package/dist/tool/index.js +2 -1
- package/dist/tool/loaded-tool-definition.d.ts +1 -1
- package/dist/tool/local-mcp-client.d.ts +1 -1
- package/dist/tool/local-mcp-client.js +2 -39
- package/dist/tool/local-tool-executor.js +32 -5
- package/dist/tool/mcp-list.d.ts +21 -0
- package/dist/tool/mcp-list.js +49 -3
- package/dist/tool/mcp-tool-executor.d.ts +1 -1
- package/dist/tool/mcp-tool-executor.js +58 -22
- package/dist/tool/model-visible-tool-content.d.ts +16 -0
- package/dist/tool/model-visible-tool-content.js +266 -0
- package/dist/tool/timeout.d.ts +5 -12
- package/dist/tool/timeout.js +12 -34
- package/dist/tool/tool-executor.interface.d.ts +7 -1
- package/dist/tool/tool-orchestrator.js +6 -1
- package/dist/tool/tool-result-injector.js +2 -1
- package/package.json +2 -2
|
@@ -22,6 +22,7 @@ import type { TokenStatsService } from '../../token/token-stats.service.js';
|
|
|
22
22
|
import type { TokenBudgetGuard } from '../../token/token-budget-guard.js';
|
|
23
23
|
import type { SessionBudgetChecker } from '../../token/session-budget-checker.js';
|
|
24
24
|
import type { InvokeRequest, InvokeResponse } from '../routes/agent.routes.js';
|
|
25
|
+
import type { TaskTimeoutManager } from '../../task/task-timeout.js';
|
|
25
26
|
import { type GraphLoader } from '../../registry/graph-loader.js';
|
|
26
27
|
import type { ICheckpointSaver } from '../../checkpoint/contracts/checkpoint-saver.contract.js';
|
|
27
28
|
import type { ITraceCollector } from '../../contracts/trace-collector.js';
|
|
@@ -46,6 +47,8 @@ export interface InvokeOrchestratorDeps {
|
|
|
46
47
|
traceCollector?: ITraceCollector;
|
|
47
48
|
sessionCompression?: SessionCompressionOptions;
|
|
48
49
|
sessionMemory?: SessionMemoryOptions;
|
|
50
|
+
/** 未注入时不启动整图超时(测试桩) */
|
|
51
|
+
timeoutManager?: TaskTimeoutManager;
|
|
49
52
|
}
|
|
50
53
|
/**
|
|
51
54
|
* InvokeOrchestrator — 同步调用全流程编排
|
|
@@ -66,6 +69,7 @@ export declare class InvokeOrchestrator {
|
|
|
66
69
|
private readonly traceCollector?;
|
|
67
70
|
private readonly sessionCompression?;
|
|
68
71
|
private readonly sessionMemory?;
|
|
72
|
+
private readonly timeoutManager?;
|
|
69
73
|
constructor(deps: InvokeOrchestratorDeps);
|
|
70
74
|
/**
|
|
71
75
|
* 执行同步调用编排
|
|
@@ -21,6 +21,11 @@ import { TokenBudgetExhaustedError } from '../../token/token-budget-guard.js';
|
|
|
21
21
|
import { recordTaskTokenUsages, tokenUsagesFromLlmChannel, } from '../../token/record-task-token-usage.js';
|
|
22
22
|
import { resolveExecutionContext } from '../../governance/governance-client.js';
|
|
23
23
|
import { resolveSkillInputFromInvokeInput } from '../../registry/skill-io-guard.js';
|
|
24
|
+
import { AppError, ErrorCodes } from '@gt-fe/eap-contracts';
|
|
25
|
+
import { classifyRuntimeError } from '../../utils/http-error.js';
|
|
26
|
+
import { DEFAULT_GRAPH_TIMEOUT_MS } from '../../common/types.js';
|
|
27
|
+
import { resolveTaskTimeoutMs } from '../../task/resolve-task-timeout.js';
|
|
28
|
+
import { runWithExecutionTrace } from '../../graph/execution-trace-context.js';
|
|
24
29
|
import { resolveAgentSystemPromptFromLoader } from '../../registry/graph-loader.js';
|
|
25
30
|
import { toLoadedToolMap } from '../../tool/loaded-tool-definition.js';
|
|
26
31
|
import { toLoadedSkillMap } from '../../tool/system-prompt-builder.js';
|
|
@@ -44,7 +49,7 @@ const DEFAULT_GRAPH_DSL = {
|
|
|
44
49
|
{ from: '__start__', to: 'llm' },
|
|
45
50
|
{ from: 'llm', to: '__end__' },
|
|
46
51
|
],
|
|
47
|
-
config: { timeout:
|
|
52
|
+
config: { timeout: DEFAULT_GRAPH_TIMEOUT_MS },
|
|
48
53
|
};
|
|
49
54
|
// ─────────────────────────────────────────────
|
|
50
55
|
// InvokeOrchestrator
|
|
@@ -68,6 +73,7 @@ export class InvokeOrchestrator {
|
|
|
68
73
|
traceCollector;
|
|
69
74
|
sessionCompression;
|
|
70
75
|
sessionMemory;
|
|
76
|
+
timeoutManager;
|
|
71
77
|
constructor(deps) {
|
|
72
78
|
this.sessionContextService = deps.sessionContextService;
|
|
73
79
|
this.taskService = deps.taskService;
|
|
@@ -81,6 +87,7 @@ export class InvokeOrchestrator {
|
|
|
81
87
|
this.traceCollector = deps.traceCollector;
|
|
82
88
|
this.sessionCompression = deps.sessionCompression;
|
|
83
89
|
this.sessionMemory = deps.sessionMemory;
|
|
90
|
+
this.timeoutManager = deps.timeoutManager;
|
|
84
91
|
}
|
|
85
92
|
/**
|
|
86
93
|
* 执行同步调用编排
|
|
@@ -143,8 +150,12 @@ export class InvokeOrchestrator {
|
|
|
143
150
|
throw error;
|
|
144
151
|
}
|
|
145
152
|
}
|
|
146
|
-
// ─── Step 3:
|
|
147
|
-
const
|
|
153
|
+
// ─── Step 3: 加载图并创建 Task(Hosting 传入 taskId 时复用)───
|
|
154
|
+
const loadOpts = { agentVersion: req.agentVersion };
|
|
155
|
+
const graphDsl = this.graphLoader
|
|
156
|
+
? await this.graphLoader.loadGraph(agentCode, loadOpts)
|
|
157
|
+
: DEFAULT_GRAPH_DSL;
|
|
158
|
+
const timeout = resolveTaskTimeoutMs(req.config?.timeout, graphDsl.config?.timeout);
|
|
148
159
|
const task = await this.taskService.create({
|
|
149
160
|
id: req.taskId,
|
|
150
161
|
agentId: agentCode,
|
|
@@ -156,6 +167,9 @@ export class InvokeOrchestrator {
|
|
|
156
167
|
});
|
|
157
168
|
// ─── Step 4: 启动 Task (pending → running) ───
|
|
158
169
|
await this.taskService.updateStatus(task.id, 'running');
|
|
170
|
+
const abortSignal = this.timeoutManager?.startTimer(task.id, timeout, async () => {
|
|
171
|
+
await this.taskService.tryUpdateStatus(task.id, 'running', 'timeout', 'Task execution timed out');
|
|
172
|
+
});
|
|
159
173
|
// ─── Step 4b: 持久化本轮 user(多轮短期记忆)───
|
|
160
174
|
await persistUserTurnMessage(this.messageService, {
|
|
161
175
|
sessionId,
|
|
@@ -168,10 +182,6 @@ export class InvokeOrchestrator {
|
|
|
168
182
|
let finishPoints;
|
|
169
183
|
try {
|
|
170
184
|
// 构建图 — 每次请求创建独立 StateGraph 实例,确保请求间无共享可变状态
|
|
171
|
-
const loadOpts = { agentVersion: req.agentVersion };
|
|
172
|
-
const graphDsl = this.graphLoader
|
|
173
|
-
? await this.graphLoader.loadGraph(agentCode, loadOpts)
|
|
174
|
-
: DEFAULT_GRAPH_DSL;
|
|
175
185
|
const graph = await this.graphEngine.buildGraph(graphDsl);
|
|
176
186
|
finishPoints = graph.finishPoints;
|
|
177
187
|
const loadedToolsBase = this.graphLoader
|
|
@@ -216,7 +226,7 @@ export class InvokeOrchestrator {
|
|
|
216
226
|
});
|
|
217
227
|
const skillInput = resolveSkillInputFromInvokeInput(req.input);
|
|
218
228
|
// 同步执行图
|
|
219
|
-
graphResult = await this.graphEngine.invoke(graph, {
|
|
229
|
+
graphResult = await runWithExecutionTrace({ abortSignal }, () => this.graphEngine.invoke(graph, {
|
|
220
230
|
messages,
|
|
221
231
|
userId: effectiveUserId,
|
|
222
232
|
delegationToken: ctx.delegationToken,
|
|
@@ -238,7 +248,9 @@ export class InvokeOrchestrator {
|
|
|
238
248
|
agentSystemPrompt,
|
|
239
249
|
traceId: resolveTraceId(req),
|
|
240
250
|
traceCollector: this.traceCollector,
|
|
241
|
-
|
|
251
|
+
abortSignal,
|
|
252
|
+
abortDeadlineMs: Date.now() + timeout,
|
|
253
|
+
}));
|
|
242
254
|
}
|
|
243
255
|
catch (error) {
|
|
244
256
|
const usages = await this.persistInvokeUsages(task.id, sessionId);
|
|
@@ -286,7 +298,7 @@ export class InvokeOrchestrator {
|
|
|
286
298
|
}
|
|
287
299
|
// ─── 错误处理:Timeout ───
|
|
288
300
|
if (this.isTimeoutError(error)) {
|
|
289
|
-
await this.taskService.
|
|
301
|
+
await this.taskService.tryUpdateStatus(task.id, 'running', 'timeout');
|
|
290
302
|
return this.buildResponse(traceId, {
|
|
291
303
|
taskId: task.id,
|
|
292
304
|
sessionId,
|
|
@@ -297,7 +309,9 @@ export class InvokeOrchestrator {
|
|
|
297
309
|
});
|
|
298
310
|
}
|
|
299
311
|
// ─── 错误处理:通用错误 ───
|
|
300
|
-
const
|
|
312
|
+
const classified = classifyRuntimeError(error);
|
|
313
|
+
const fuse = classified.code === ErrorCodes.GRAPH_MAX_ITERATIONS ? classified : undefined;
|
|
314
|
+
const errorMessage = fuse?.message ?? (error instanceof Error ? error.message : 'Unknown error');
|
|
301
315
|
await this.taskService.updateStatus(task.id, 'failed', errorMessage);
|
|
302
316
|
return this.buildErrorResponse(traceId, {
|
|
303
317
|
taskId: task.id,
|
|
@@ -306,7 +320,10 @@ export class InvokeOrchestrator {
|
|
|
306
320
|
tokenUsage,
|
|
307
321
|
duration: Date.now() - startTime,
|
|
308
322
|
status: 'failed',
|
|
309
|
-
});
|
|
323
|
+
}, fuse ?? error);
|
|
324
|
+
}
|
|
325
|
+
finally {
|
|
326
|
+
this.timeoutManager?.clearTimer(task.id);
|
|
310
327
|
}
|
|
311
328
|
// ─── Step 6: 记录 Token 消耗 ───
|
|
312
329
|
//
|
|
@@ -387,15 +404,17 @@ export class InvokeOrchestrator {
|
|
|
387
404
|
buildResponse(traceId, data) {
|
|
388
405
|
return { code: 'ok', data, traceId };
|
|
389
406
|
}
|
|
390
|
-
buildErrorResponse(traceId, data) {
|
|
391
|
-
|
|
407
|
+
buildErrorResponse(traceId, data, error) {
|
|
408
|
+
if (data.status === 'token_exhausted') {
|
|
409
|
+
return { code: 'runtime.budget.task_exhausted', data, traceId };
|
|
410
|
+
}
|
|
411
|
+
if (error instanceof AppError) {
|
|
412
|
+
return { code: error.code, data, traceId };
|
|
413
|
+
}
|
|
414
|
+
return { code: 'runtime.graph.execution_error', data, traceId };
|
|
392
415
|
}
|
|
393
416
|
isTimeoutError(error) {
|
|
394
|
-
|
|
395
|
-
const msg = error.message.toLowerCase();
|
|
396
|
-
return msg.includes('timeout') || msg.includes('timed out');
|
|
397
|
-
}
|
|
398
|
-
return false;
|
|
417
|
+
return error instanceof Error && /timed out/i.test(error.message);
|
|
399
418
|
}
|
|
400
419
|
async persistInvokeUsages(taskId, sessionId) {
|
|
401
420
|
if (!this.checkpointSaver)
|
|
@@ -33,6 +33,7 @@ import type { StreamRequest } from '../routes/agent.routes.js';
|
|
|
33
33
|
import { type GraphLoader } from '../../registry/graph-loader.js';
|
|
34
34
|
import type { ICheckpointSaver } from '../../checkpoint/contracts/checkpoint-saver.contract.js';
|
|
35
35
|
import type { ITraceCollector } from '../../contracts/trace-collector.js';
|
|
36
|
+
import type { TaskTimeoutManager } from '../../task/task-timeout.js';
|
|
36
37
|
/**
|
|
37
38
|
* StreamOrchestrator 构造器依赖
|
|
38
39
|
*/
|
|
@@ -52,6 +53,8 @@ export interface StreamOrchestratorDeps {
|
|
|
52
53
|
traceCollector?: ITraceCollector;
|
|
53
54
|
sessionCompression?: SessionCompressionOptions;
|
|
54
55
|
sessionMemory?: SessionMemoryOptions;
|
|
56
|
+
/** 未注入时不启动整图超时(测试桩) */
|
|
57
|
+
timeoutManager?: TaskTimeoutManager;
|
|
55
58
|
}
|
|
56
59
|
/**
|
|
57
60
|
* StreamOrchestrator — 流式调用全流程编排
|
|
@@ -72,6 +75,7 @@ export declare class StreamOrchestrator {
|
|
|
72
75
|
private readonly traceCollector?;
|
|
73
76
|
private readonly sessionCompression?;
|
|
74
77
|
private readonly sessionMemory?;
|
|
78
|
+
private readonly timeoutManager?;
|
|
75
79
|
constructor(deps: StreamOrchestratorDeps);
|
|
76
80
|
/**
|
|
77
81
|
* 执行流式调用编排
|
|
@@ -39,6 +39,9 @@ import { resolveAgentCode } from '../../common/agent-code.js';
|
|
|
39
39
|
import { unwrapHitlPaused } from './invoke.handler.js';
|
|
40
40
|
import { buildHitlWaitedPayload } from '../../graph/hitl/hitl-handler.js';
|
|
41
41
|
import { resolveTraceId } from '../../common/trace-id.js';
|
|
42
|
+
import { DEFAULT_GRAPH_TIMEOUT_MS } from '../../common/types.js';
|
|
43
|
+
import { resolveTaskTimeoutMs } from '../../task/resolve-task-timeout.js';
|
|
44
|
+
import { runWithExecutionTrace } from '../../graph/execution-trace-context.js';
|
|
42
45
|
// ─────────────────────────────────────────────
|
|
43
46
|
// Placeholder Graph DSL (Agent Registry 接入前)
|
|
44
47
|
// ─────────────────────────────────────────────
|
|
@@ -53,7 +56,7 @@ const DEFAULT_GRAPH_DSL = {
|
|
|
53
56
|
{ from: '__start__', to: 'llm' },
|
|
54
57
|
{ from: 'llm', to: '__end__' },
|
|
55
58
|
],
|
|
56
|
-
config: { timeout:
|
|
59
|
+
config: { timeout: DEFAULT_GRAPH_TIMEOUT_MS },
|
|
57
60
|
};
|
|
58
61
|
// ─────────────────────────────────────────────
|
|
59
62
|
// StreamOrchestrator
|
|
@@ -77,6 +80,7 @@ export class StreamOrchestrator {
|
|
|
77
80
|
traceCollector;
|
|
78
81
|
sessionCompression;
|
|
79
82
|
sessionMemory;
|
|
83
|
+
timeoutManager;
|
|
80
84
|
constructor(deps) {
|
|
81
85
|
this.sessionContextService = deps.sessionContextService;
|
|
82
86
|
this.taskService = deps.taskService;
|
|
@@ -90,6 +94,7 @@ export class StreamOrchestrator {
|
|
|
90
94
|
this.traceCollector = deps.traceCollector;
|
|
91
95
|
this.sessionCompression = deps.sessionCompression;
|
|
92
96
|
this.sessionMemory = deps.sessionMemory;
|
|
97
|
+
this.timeoutManager = deps.timeoutManager;
|
|
93
98
|
}
|
|
94
99
|
/**
|
|
95
100
|
* 执行流式调用编排
|
|
@@ -166,8 +171,12 @@ export class StreamOrchestrator {
|
|
|
166
171
|
userId,
|
|
167
172
|
}));
|
|
168
173
|
}
|
|
169
|
-
// ─── Step 3:
|
|
170
|
-
const
|
|
174
|
+
// ─── Step 3: 加载图并创建 Task(Hosting 传入 taskId 时复用)───
|
|
175
|
+
const loadOpts = { agentVersion: req.agentVersion };
|
|
176
|
+
const graphDsl = this.graphLoader
|
|
177
|
+
? await this.graphLoader.loadGraph(agentCode, loadOpts)
|
|
178
|
+
: DEFAULT_GRAPH_DSL;
|
|
179
|
+
const timeout = resolveTaskTimeoutMs(req.config?.timeout, graphDsl.config?.timeout);
|
|
171
180
|
const task = await this.taskService.create({
|
|
172
181
|
id: req.taskId,
|
|
173
182
|
agentId: agentCode,
|
|
@@ -180,6 +189,9 @@ export class StreamOrchestrator {
|
|
|
180
189
|
const taskId = task.id;
|
|
181
190
|
// ─── Step 4: 启动 Task (pending → running) ───
|
|
182
191
|
await this.taskService.updateStatus(taskId, 'running');
|
|
192
|
+
const abortSignal = this.timeoutManager?.startTimer(taskId, timeout, async () => {
|
|
193
|
+
await this.taskService.tryUpdateStatus(taskId, 'running', 'timeout', 'Task execution timed out');
|
|
194
|
+
});
|
|
183
195
|
// ─── Step 4b: 持久化本轮 user(多轮短期记忆)───
|
|
184
196
|
await persistUserTurnMessage(this.messageService, {
|
|
185
197
|
sessionId,
|
|
@@ -193,10 +205,6 @@ export class StreamOrchestrator {
|
|
|
193
205
|
sessionId,
|
|
194
206
|
}));
|
|
195
207
|
// ─── Step 5: 构建 Graph — 每次请求创建独立 StateGraph 实例,确保请求间无共享可变状态 ───
|
|
196
|
-
const loadOpts = { agentVersion: req.agentVersion };
|
|
197
|
-
const graphDsl = this.graphLoader
|
|
198
|
-
? await this.graphLoader.loadGraph(agentCode, loadOpts)
|
|
199
|
-
: DEFAULT_GRAPH_DSL;
|
|
200
208
|
const graph = await this.graphEngine.buildGraph(graphDsl);
|
|
201
209
|
const loadedToolsBase = this.graphLoader
|
|
202
210
|
? toLoadedToolMap(await this.graphLoader.loadToolDefinitions(agentCode, loadOpts))
|
|
@@ -253,7 +261,7 @@ export class StreamOrchestrator {
|
|
|
253
261
|
const toolResults = [];
|
|
254
262
|
// ─── Step 6: Graph.stream() 流式执行 ───
|
|
255
263
|
try {
|
|
256
|
-
const eventStream = this.graphEngine.stream(graph, {
|
|
264
|
+
const eventStream = runWithExecutionTrace({ abortSignal }, () => this.graphEngine.stream(graph, {
|
|
257
265
|
messages,
|
|
258
266
|
userId,
|
|
259
267
|
delegationToken: executionContext.delegationToken,
|
|
@@ -275,7 +283,9 @@ export class StreamOrchestrator {
|
|
|
275
283
|
agentSystemPrompt,
|
|
276
284
|
traceId: resolveTraceId(req),
|
|
277
285
|
traceCollector: this.traceCollector,
|
|
278
|
-
|
|
286
|
+
abortSignal,
|
|
287
|
+
abortDeadlineMs: Date.now() + timeout,
|
|
288
|
+
}));
|
|
279
289
|
// 逐事件转发到 SSE;顺带把节点 token 记入 TokenStatsService(stream 原先未 record → 结尾全 0)
|
|
280
290
|
for await (const runtimeEvent of eventStream) {
|
|
281
291
|
// 连接已断开则提前退出
|
|
@@ -347,7 +357,7 @@ export class StreamOrchestrator {
|
|
|
347
357
|
// ─── 错误处理:Timeout ───
|
|
348
358
|
if (this.isTimeoutError(error)) {
|
|
349
359
|
await recordTaskTokenUsages(this.taskService, taskId, this.tokenStatsService.list(taskId));
|
|
350
|
-
await this.taskService.
|
|
360
|
+
await this.taskService.tryUpdateStatus(taskId, 'running', 'timeout');
|
|
351
361
|
emitter.emit(this.buildSSEEvent('task.error', correlationId, taskId, {
|
|
352
362
|
code: ErrorCodes.TASK_TIMEOUT,
|
|
353
363
|
message: 'Task execution timed out',
|
|
@@ -363,6 +373,9 @@ export class StreamOrchestrator {
|
|
|
363
373
|
this.emitErrorAndClose(emitter, correlationId, taskId, error);
|
|
364
374
|
return;
|
|
365
375
|
}
|
|
376
|
+
finally {
|
|
377
|
+
this.timeoutManager?.clearTimer(taskId);
|
|
378
|
+
}
|
|
366
379
|
// ─── Step 7: 发送结束事件序列 ───
|
|
367
380
|
// Emit: response.final(带完整 assistant 文本,供客户端兜底)
|
|
368
381
|
const tokenStats = this.tokenStatsService.getTaskStats(taskId);
|
|
@@ -454,10 +467,6 @@ export class StreamOrchestrator {
|
|
|
454
467
|
emitter.close();
|
|
455
468
|
}
|
|
456
469
|
isTimeoutError(error) {
|
|
457
|
-
|
|
458
|
-
const msg = error.message.toLowerCase();
|
|
459
|
-
return msg.includes('timeout') || msg.includes('timed out');
|
|
460
|
-
}
|
|
461
|
-
return false;
|
|
470
|
+
return error instanceof Error && /timed out/i.test(error.message);
|
|
462
471
|
}
|
|
463
472
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 生产环境由 Hosting 提供对外 HTTP;本模块供 `EAP_DEV_HTTP=1` 本地调试。
|
|
5
5
|
*/
|
|
6
|
+
import { classifyRuntimeError } from '../../utils/http-error.js';
|
|
6
7
|
// ─────────────────────────────────────────────
|
|
7
8
|
// Route Handlers
|
|
8
9
|
// ─────────────────────────────────────────────
|
|
@@ -85,18 +86,18 @@ export function createAgentRouteHandlers(deps) {
|
|
|
85
86
|
};
|
|
86
87
|
}
|
|
87
88
|
catch (error) {
|
|
88
|
-
// 错误处理
|
|
89
89
|
const duration = Date.now() - startTime;
|
|
90
|
-
const
|
|
90
|
+
const classified = classifyRuntimeError(error);
|
|
91
|
+
const output = classified.exposeMessage ? classified.message : '系统内部错误';
|
|
91
92
|
return {
|
|
92
|
-
code:
|
|
93
|
+
code: classified.code,
|
|
93
94
|
data: {
|
|
94
95
|
taskId: '',
|
|
95
96
|
sessionId: sessionId ?? '',
|
|
96
|
-
output
|
|
97
|
+
output,
|
|
97
98
|
tokenUsage: { input: 0, output: 0, total: 0 },
|
|
98
99
|
duration,
|
|
99
|
-
status: 'failed',
|
|
100
|
+
status: classified.code === 'runtime.task.timeout' ? 'timeout' : 'failed',
|
|
100
101
|
},
|
|
101
102
|
traceId: '',
|
|
102
103
|
};
|
|
@@ -12,7 +12,7 @@ function pickString(...values) {
|
|
|
12
12
|
return undefined;
|
|
13
13
|
}
|
|
14
14
|
function omitTraceCollector(record) {
|
|
15
|
-
const { traceCollector: _drop, ...rest } = record;
|
|
15
|
+
const { traceCollector: _drop, abortSignal: _abort, abortDeadlineMs: _deadline, ...rest } = record;
|
|
16
16
|
return rest;
|
|
17
17
|
}
|
|
18
18
|
/** 去掉不可序列化的 collector,再交给 Saver。 */
|
package/dist/common/types.d.ts
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
* - detail: 每次 LLM 调用输出 stats.node_token_usage + 结束时汇总
|
|
13
13
|
*/
|
|
14
14
|
export type TokenDetailLevel = 'none' | 'summary' | 'detail';
|
|
15
|
+
/** 未写请求/图 timeout 时的整图缺省(毫秒)。20 分钟,须大于单次 LLM HTTP 缺省 180s。 */
|
|
16
|
+
export declare const DEFAULT_GRAPH_TIMEOUT_MS = 1200000;
|
|
15
17
|
/**
|
|
16
18
|
* Runtime 执行引擎配置
|
|
17
19
|
*
|
|
@@ -20,9 +22,11 @@ export type TokenDetailLevel = 'none' | 'summary' | 'detail';
|
|
|
20
22
|
export interface RuntimeConfig {
|
|
21
23
|
/** Token 统计输出粒度 */
|
|
22
24
|
tokenDetail: TokenDetailLevel;
|
|
23
|
-
/** Task
|
|
25
|
+
/** Task 执行超时时间(毫秒)。未写请求/图 timeout 时为 {@link DEFAULT_GRAPH_TIMEOUT_MS} */
|
|
24
26
|
timeout: number;
|
|
25
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* @deprecated Runtime 忽略。图级 / 请求级步数不是限长手段。整图限长用 timeout。
|
|
29
|
+
*/
|
|
26
30
|
maxIterations: number;
|
|
27
31
|
/** SSE 心跳间隔(毫秒),默认 30000 */
|
|
28
32
|
heartbeatInterval: number;
|
package/dist/common/types.js
CHANGED
|
@@ -5,10 +5,12 @@
|
|
|
5
5
|
* - @gt-fe/eap-contracts 定义跨模块的公共协议(如 Task 实体、Session 实体、Agent Manifest)
|
|
6
6
|
* - 本文件定义 Runtime 执行引擎内部流转的配置、上下文、枚举等
|
|
7
7
|
*/
|
|
8
|
+
/** 未写请求/图 timeout 时的整图缺省(毫秒)。20 分钟,须大于单次 LLM HTTP 缺省 180s。 */
|
|
9
|
+
export const DEFAULT_GRAPH_TIMEOUT_MS = 1_200_000;
|
|
8
10
|
/** RuntimeConfig 默认值 */
|
|
9
11
|
export const DEFAULT_RUNTIME_CONFIG = {
|
|
10
12
|
tokenDetail: 'detail',
|
|
11
|
-
timeout:
|
|
13
|
+
timeout: DEFAULT_GRAPH_TIMEOUT_MS,
|
|
12
14
|
maxIterations: 10,
|
|
13
15
|
heartbeatInterval: 30000,
|
|
14
16
|
};
|
|
@@ -17,12 +17,14 @@ import type { IToolExecutor } from '../../tool/tool-executor.interface.js';
|
|
|
17
17
|
import type { IGovernanceClient } from '../../governance/governance-client.js';
|
|
18
18
|
import { type ConditionEvaluatorFn } from '../condition-evaluator.js';
|
|
19
19
|
import type { ExecutionInterceptors } from '../../execution/interceptors.js';
|
|
20
|
-
/** 图未写 `config.maxIterations` 时的 recursionLimit。`agent ⇄ tools` 一圈约 2,50 ≈ 24 次工具往返。 */
|
|
21
|
-
export declare const DEFAULT_GRAPH_RECURSION_LIMIT = 50;
|
|
22
20
|
/**
|
|
23
|
-
* LangGraph `recursionLimit
|
|
24
|
-
*
|
|
21
|
+
* 传给 LangGraph 的 `recursionLimit`:一轮调度(superstep)+1。
|
|
22
|
+
* 同一步里并行的多个节点只 +1。ReAct 的 llm 再 tool 大约 +2。
|
|
23
|
+
* 不读图级 / 请求级 `maxIterations`。整图运行时长用 `timeout`。
|
|
24
|
+
* 配了 Token 配额才会因预算停止。
|
|
25
25
|
*/
|
|
26
|
+
export declare const DEFAULT_GRAPH_RECURSION_LIMIT = 5000;
|
|
27
|
+
export declare function isGraphRecursionError(error: unknown): boolean;
|
|
26
28
|
/** 并行节点各自返回 [id] 时合并进同一路径;末项相同不重复。 */
|
|
27
29
|
export declare function mergeHistoryPath(current: unknown, update: unknown): string[];
|
|
28
30
|
/**
|
|
@@ -37,14 +37,34 @@ import { isHitlDecision, lastAssistantHasUnansweredToolCalls } from '../hitl/hit
|
|
|
37
37
|
import { classifySkillRead } from './skill-read.js';
|
|
38
38
|
import { skillEntryInvocationFromTool } from './skill-entry-span.js';
|
|
39
39
|
import { withGraphSpan } from '../trace-span.js';
|
|
40
|
-
import { executionTraceFromState, getExecutionStream, runWithExecutionTrace, } from '../execution-trace-context.js';
|
|
40
|
+
import { executionTraceFromState, getExecutionStream, getExecutionTrace, runWithExecutionTrace, } from '../execution-trace-context.js';
|
|
41
41
|
import { isRetryableToolFailure, resolveToolRetryConfig, sleepToolRetry, toolRetryDelayMs, } from '../../tool/tool-retry.js';
|
|
42
|
-
/** 图未写 `config.maxIterations` 时的 recursionLimit。`agent ⇄ tools` 一圈约 2,50 ≈ 24 次工具往返。 */
|
|
43
|
-
export const DEFAULT_GRAPH_RECURSION_LIMIT = 50;
|
|
44
42
|
/**
|
|
45
|
-
* LangGraph `recursionLimit
|
|
46
|
-
*
|
|
43
|
+
* 传给 LangGraph 的 `recursionLimit`:一轮调度(superstep)+1。
|
|
44
|
+
* 同一步里并行的多个节点只 +1。ReAct 的 llm 再 tool 大约 +2。
|
|
45
|
+
* 不读图级 / 请求级 `maxIterations`。整图运行时长用 `timeout`。
|
|
46
|
+
* 配了 Token 配额才会因预算停止。
|
|
47
47
|
*/
|
|
48
|
+
export const DEFAULT_GRAPH_RECURSION_LIMIT = 5000;
|
|
49
|
+
const GRAPH_RECURSION_LIMIT_MESSAGE = '图执行超过平台调度步上限。整图限长请用 config.timeout。';
|
|
50
|
+
export function isGraphRecursionError(error) {
|
|
51
|
+
if (!(error instanceof Error))
|
|
52
|
+
return false;
|
|
53
|
+
if (error.name === 'GraphRecursionError')
|
|
54
|
+
return true;
|
|
55
|
+
return /Recursion limit of \d+/i.test(error.message);
|
|
56
|
+
}
|
|
57
|
+
function rethrowGraphRunError(error) {
|
|
58
|
+
if (error instanceof AppError)
|
|
59
|
+
throw error;
|
|
60
|
+
if (isGraphRecursionError(error)) {
|
|
61
|
+
throw new AppError(ErrorCodes.GRAPH_MAX_ITERATIONS, GRAPH_RECURSION_LIMIT_MESSAGE, {
|
|
62
|
+
exposeMessage: true,
|
|
63
|
+
cause: error,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
48
68
|
/** 并行节点各自返回 [id] 时合并进同一路径;末项相同不重复。 */
|
|
49
69
|
export function mergeHistoryPath(current, update) {
|
|
50
70
|
const path = Array.isArray(current)
|
|
@@ -63,16 +83,54 @@ export function mergeHistoryPath(current, update) {
|
|
|
63
83
|
}
|
|
64
84
|
return path.length > 1000 ? path.slice(path.length - 1000) : path;
|
|
65
85
|
}
|
|
66
|
-
function langGraphRunOptions(
|
|
67
|
-
const raw = dsl.config?.maxIterations;
|
|
68
|
-
const recursionLimit = typeof raw === 'number' && Number.isFinite(raw) && raw > 0
|
|
69
|
-
? Math.trunc(raw)
|
|
70
|
-
: DEFAULT_GRAPH_RECURSION_LIMIT;
|
|
86
|
+
function langGraphRunOptions(extra) {
|
|
71
87
|
return {
|
|
72
88
|
...extra,
|
|
73
|
-
recursionLimit,
|
|
89
|
+
recursionLimit: DEFAULT_GRAPH_RECURSION_LIMIT,
|
|
74
90
|
};
|
|
75
91
|
}
|
|
92
|
+
function graphAbortSignal(state) {
|
|
93
|
+
const fromState = state?.abortSignal;
|
|
94
|
+
if (fromState instanceof AbortSignal)
|
|
95
|
+
return fromState;
|
|
96
|
+
return getExecutionTrace()?.abortSignal;
|
|
97
|
+
}
|
|
98
|
+
function abortReason(signal) {
|
|
99
|
+
return signal.reason instanceof Error
|
|
100
|
+
? signal.reason
|
|
101
|
+
: new Error(String(signal.reason ?? 'aborted'));
|
|
102
|
+
}
|
|
103
|
+
function graphAbortDeadlineMs(state) {
|
|
104
|
+
const fromState = state?.abortDeadlineMs;
|
|
105
|
+
if (typeof fromState === 'number' && Number.isFinite(fromState))
|
|
106
|
+
return fromState;
|
|
107
|
+
return getExecutionTrace()?.abortDeadlineMs;
|
|
108
|
+
}
|
|
109
|
+
function throwIfGraphAborted(state) {
|
|
110
|
+
const deadline = graphAbortDeadlineMs(state);
|
|
111
|
+
if (deadline !== undefined && Date.now() >= deadline) {
|
|
112
|
+
const signal = graphAbortSignal(state);
|
|
113
|
+
throw signal?.aborted
|
|
114
|
+
? abortReason(signal)
|
|
115
|
+
: new Error('Task timed out after graph deadline');
|
|
116
|
+
}
|
|
117
|
+
const signal = graphAbortSignal(state);
|
|
118
|
+
if (signal?.aborted)
|
|
119
|
+
throw abortReason(signal);
|
|
120
|
+
}
|
|
121
|
+
function withGraphAbort(promise, signal) {
|
|
122
|
+
if (!signal)
|
|
123
|
+
return promise;
|
|
124
|
+
if (signal.aborted)
|
|
125
|
+
return Promise.reject(abortReason(signal));
|
|
126
|
+
return new Promise((resolve, reject) => {
|
|
127
|
+
const onAbort = () => reject(abortReason(signal));
|
|
128
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
129
|
+
promise.then(resolve, reject).finally(() => {
|
|
130
|
+
signal.removeEventListener('abort', onAbort);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
}
|
|
76
134
|
// ─────────────────────────────────────────────
|
|
77
135
|
// LangGraphAdapter
|
|
78
136
|
// ─────────────────────────────────────────────
|
|
@@ -532,12 +590,25 @@ export class LangGraphAdapter {
|
|
|
532
590
|
streamedTextThisTurn: false,
|
|
533
591
|
};
|
|
534
592
|
const traceCtx = executionTraceFromState(state);
|
|
535
|
-
const
|
|
593
|
+
const graphSignal = graphAbortSignal(state);
|
|
594
|
+
const onGraphAbort = () => {
|
|
595
|
+
failure = graphSignal ? abortReason(graphSignal) : new Error('aborted');
|
|
596
|
+
finished = true;
|
|
597
|
+
signal();
|
|
598
|
+
};
|
|
599
|
+
graphSignal?.addEventListener('abort', onGraphAbort, { once: true });
|
|
600
|
+
const producer = runWithExecutionTrace({
|
|
601
|
+
...traceCtx,
|
|
602
|
+
stream: streamBag,
|
|
603
|
+
abortSignal: graphSignal,
|
|
604
|
+
abortDeadlineMs: graphAbortDeadlineMs(state),
|
|
605
|
+
}, () => (async () => {
|
|
536
606
|
try {
|
|
537
|
-
|
|
607
|
+
throwIfGraphAborted(state);
|
|
608
|
+
const streamIterator = await withGraphAbort(Promise.resolve(ref.compiled.stream(input, langGraphRunOptions({
|
|
538
609
|
configurable: this.checkpointConfigurable(state, graph),
|
|
539
610
|
streamMode: 'updates',
|
|
540
|
-
}));
|
|
611
|
+
}))), graphSignal);
|
|
541
612
|
for await (const update of streamIterator) {
|
|
542
613
|
for (const event of this.convertUpdatesToEvents(update, graph.id, () => streamBag.chunkIndex++, ref.silentNodeIds, ref.subgraphNodeIds)) {
|
|
543
614
|
enqueue(event);
|
|
@@ -562,8 +633,9 @@ export class LangGraphAdapter {
|
|
|
562
633
|
yield queue.shift();
|
|
563
634
|
}
|
|
564
635
|
await producer;
|
|
636
|
+
graphSignal?.removeEventListener('abort', onGraphAbort);
|
|
565
637
|
if (failure)
|
|
566
|
-
|
|
638
|
+
rethrowGraphRunError(failure);
|
|
567
639
|
}
|
|
568
640
|
/**
|
|
569
641
|
* 同步调用图执行,返回完整结果
|
|
@@ -573,10 +645,22 @@ export class LangGraphAdapter {
|
|
|
573
645
|
const input = this.graphStateToLangGraphInput(this.applyDeclaredSearchFace(state, ref.dsl));
|
|
574
646
|
const startTime = Date.now();
|
|
575
647
|
const traceCtx = executionTraceFromState(state);
|
|
648
|
+
const abortSignal = graphAbortSignal(state);
|
|
649
|
+
const abortDeadlineMs = graphAbortDeadlineMs(state);
|
|
576
650
|
// 只补 collector/traceId。子图套娃时外层 stream 袋经 ?? 保留。
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
651
|
+
let result;
|
|
652
|
+
try {
|
|
653
|
+
result = await withGraphAbort(runWithExecutionTrace({
|
|
654
|
+
...traceCtx,
|
|
655
|
+
abortSignal,
|
|
656
|
+
abortDeadlineMs,
|
|
657
|
+
}, () => ref.compiled.invoke(input, langGraphRunOptions({
|
|
658
|
+
configurable: this.checkpointConfigurable(state, graph),
|
|
659
|
+
}))), abortSignal);
|
|
660
|
+
}
|
|
661
|
+
catch (error) {
|
|
662
|
+
rethrowGraphRunError(error);
|
|
663
|
+
}
|
|
580
664
|
const duration = Date.now() - startTime;
|
|
581
665
|
const messages = Array.isArray(result['messages']) ? result['messages'] : [];
|
|
582
666
|
const lastMessage = messages[messages.length - 1];
|
|
@@ -684,6 +768,7 @@ export class LangGraphAdapter {
|
|
|
684
768
|
createNodeFunction(node) {
|
|
685
769
|
const impl = this.createNodeFunctionBody(node);
|
|
686
770
|
return async (state) => {
|
|
771
|
+
throwIfGraphAborted(state);
|
|
687
772
|
const update = await impl(state);
|
|
688
773
|
const patch = update && typeof update === 'object' ? { ...update } : {};
|
|
689
774
|
const currentNode = typeof patch['currentNode'] === 'string' && String(patch['currentNode']).trim()
|
|
@@ -243,6 +243,15 @@ export interface GraphState {
|
|
|
243
243
|
* 仅 stream/invoke 入口使用,不入持久化语义。
|
|
244
244
|
*/
|
|
245
245
|
replaceCheckpointMessages?: boolean;
|
|
246
|
+
/**
|
|
247
|
+
* 整图超时用的 AbortSignal。不入 Checkpoint。不传给 LangGraph。
|
|
248
|
+
*/
|
|
249
|
+
abortSignal?: AbortSignal;
|
|
250
|
+
/**
|
|
251
|
+
* 整图超时截止(`Date.now()`)。不入 Checkpoint。
|
|
252
|
+
* 节点入口检查,避免同步循环占满事件循环导致 timer 不能触发。
|
|
253
|
+
*/
|
|
254
|
+
abortDeadlineMs?: number;
|
|
246
255
|
/**
|
|
247
256
|
* 子图 invoke 写入 LangGraph `checkpoint_ns`。
|
|
248
257
|
* 父图为空;子图为父节点 id。存储槽与父 thread 分开,不覆盖父 latest。
|
|
@@ -16,6 +16,10 @@ export interface ExecutionRunContext {
|
|
|
16
16
|
collector?: ITraceCollector;
|
|
17
17
|
traceId?: string;
|
|
18
18
|
stream?: ExecutionStreamBag;
|
|
19
|
+
/** 整图超时 AbortSignal;不入 Checkpoint;不传给 LangGraph */
|
|
20
|
+
abortSignal?: AbortSignal;
|
|
21
|
+
/** 整图超时截止(Date.now());不入 Checkpoint */
|
|
22
|
+
abortDeadlineMs?: number;
|
|
19
23
|
}
|
|
20
24
|
/** 入口从本次入参取出可绑定的 collector+traceId;缺一则不算。 */
|
|
21
25
|
export declare function executionTraceFromState(state: GraphState | Record<string, unknown> | undefined): {
|
|
@@ -33,8 +33,10 @@ export function runWithExecutionTrace(patch, fn) {
|
|
|
33
33
|
collector: patch.collector ?? outer?.collector,
|
|
34
34
|
traceId: patch.traceId ?? outer?.traceId,
|
|
35
35
|
stream: patch.stream ?? outer?.stream,
|
|
36
|
+
abortSignal: patch.abortSignal ?? outer?.abortSignal,
|
|
37
|
+
abortDeadlineMs: patch.abortDeadlineMs ?? outer?.abortDeadlineMs,
|
|
36
38
|
};
|
|
37
|
-
if (!next.collector && !next.traceId && !next.stream)
|
|
39
|
+
if (!next.collector && !next.traceId && !next.stream && !next.abortSignal && next.abortDeadlineMs === undefined)
|
|
38
40
|
return fn();
|
|
39
41
|
return storage.run(next, fn);
|
|
40
42
|
}
|
|
@@ -173,7 +173,10 @@ export interface RagNodeConfig {
|
|
|
173
173
|
model?: string;
|
|
174
174
|
/** 融合后总条数,缺省 10 */
|
|
175
175
|
totalK?: number;
|
|
176
|
-
/**
|
|
176
|
+
/**
|
|
177
|
+
* 本节点检索轮次上限(同轮并发多个 tool_call 只计 1),缺省 5。
|
|
178
|
+
* 到上限该节点带着已有结果正常结束,后续图节点继续。不是整图停止。
|
|
179
|
+
*/
|
|
177
180
|
maxIterations?: number;
|
|
178
181
|
/** 近重判定阈值(字符 n-gram Jaccard,M4 启用),缺省 0.8 */
|
|
179
182
|
nearDupThreshold?: number;
|