@gt-fe/eap-runtime 0.0.20 → 0.0.21
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 +3 -2
- package/dist/api/handlers/invoke.handler.d.ts +7 -0
- package/dist/api/handlers/invoke.handler.js +48 -9
- package/dist/api/handlers/stream.handler.d.ts +6 -0
- package/dist/api/handlers/stream.handler.js +56 -39
- package/dist/api/routes/agent.routes.d.ts +8 -1
- package/dist/common/types.d.ts +2 -2
- package/dist/common/types.js +1 -1
- package/dist/execution/index.d.ts +2 -0
- package/dist/execution/index.js +2 -0
- package/dist/execution/interceptors.d.ts +40 -0
- package/dist/execution/interceptors.js +23 -0
- package/dist/execution/ports.d.ts +18 -0
- package/dist/execution/ports.js +13 -0
- package/dist/governance/audit-reporter.d.ts +14 -0
- package/dist/governance/audit-reporter.js +18 -0
- package/dist/governance/index.d.ts +2 -0
- package/dist/governance/index.js +1 -0
- package/dist/governance/permission-guard.d.ts +6 -2
- package/dist/governance/permission-guard.js +38 -5
- package/dist/graph/adapters/graph-nesting.d.ts +12 -0
- package/dist/graph/adapters/graph-nesting.js +29 -0
- package/dist/graph/adapters/index.d.ts +2 -0
- package/dist/graph/adapters/index.js +2 -0
- package/dist/graph/adapters/langgraph.adapter.d.ts +25 -11
- package/dist/graph/adapters/langgraph.adapter.js +142 -82
- package/dist/graph/adapters/skill-isolate.d.ts +17 -0
- package/dist/graph/adapters/skill-isolate.js +50 -0
- package/dist/graph/adapters/subgraph-compiler.d.ts +7 -2
- package/dist/graph/adapters/subgraph-compiler.js +68 -32
- package/dist/graph/contracts/executable-graph.d.ts +27 -5
- package/dist/graph/hitl/hitl-handler.js +2 -0
- package/dist/graph/nodes/index.d.ts +1 -0
- package/dist/graph/nodes/index.js +1 -0
- package/dist/graph/nodes/llm-node.d.ts +3 -1
- package/dist/graph/nodes/llm-node.js +26 -2
- package/dist/graph/nodes/rag/dedup.d.ts +20 -0
- package/dist/graph/nodes/rag/dedup.js +77 -0
- package/dist/graph/nodes/rag/denoise.d.ts +58 -0
- package/dist/graph/nodes/rag/denoise.js +220 -0
- package/dist/graph/nodes/rag/fusion/fuse.d.ts +11 -0
- package/dist/graph/nodes/rag/fusion/fuse.js +25 -0
- package/dist/graph/nodes/rag/fusion/normalize.d.ts +9 -0
- package/dist/graph/nodes/rag/fusion/normalize.js +135 -0
- package/dist/graph/nodes/rag/index.d.ts +18 -0
- package/dist/graph/nodes/rag/index.js +17 -0
- package/dist/graph/nodes/rag/prompts.d.ts +12 -0
- package/dist/graph/nodes/rag/prompts.js +40 -0
- package/dist/graph/nodes/rag/rag-node.d.ts +21 -0
- package/dist/graph/nodes/rag/rag-node.js +135 -0
- package/dist/graph/nodes/rag/rag-search-tool.d.ts +48 -0
- package/dist/graph/nodes/rag/rag-search-tool.js +230 -0
- package/dist/graph/nodes/rag/react-loop.d.ts +57 -0
- package/dist/graph/nodes/rag/react-loop.js +467 -0
- package/dist/graph/nodes/rag/retrievers/http-embedder.d.ts +29 -0
- package/dist/graph/nodes/rag/retrievers/http-embedder.js +66 -0
- package/dist/graph/nodes/rag/retrievers/in-memory-retriever.d.ts +17 -0
- package/dist/graph/nodes/rag/retrievers/in-memory-retriever.js +18 -0
- package/dist/graph/nodes/rag/retrievers/local-sqlite-retriever.d.ts +46 -0
- package/dist/graph/nodes/rag/retrievers/local-sqlite-retriever.js +85 -0
- package/dist/graph/nodes/rag/tools.d.ts +90 -0
- package/dist/graph/nodes/rag/tools.js +260 -0
- package/dist/graph/nodes/rag/types.d.ts +226 -0
- package/dist/graph/nodes/rag/types.js +9 -0
- package/dist/graph/nodes/tool-node.d.ts +19 -1
- package/dist/graph/nodes/tool-node.js +162 -33
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/memory/semantic-retrieval.service.d.ts +1 -1
- package/dist/memory/semantic-retrieval.service.js +13 -23
- package/dist/providers/index.d.ts +1 -1
- package/dist/providers/index.js +1 -1
- package/dist/providers/routing-llm-provider.d.ts +5 -0
- package/dist/providers/routing-llm-provider.js +66 -1
- package/dist/runtime/agent-runtime.d.ts +5 -0
- package/dist/runtime/agent-runtime.js +86 -66
- package/dist/runtime/dev-rag-deps.d.ts +21 -0
- package/dist/runtime/dev-rag-deps.js +186 -0
- package/dist/runtime/dev-runtime.d.ts +16 -1
- package/dist/runtime/dev-runtime.js +59 -5
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/types.d.ts +10 -9
- package/dist/session/apply-session-compression.d.ts +81 -0
- package/dist/session/apply-session-compression.js +262 -0
- package/dist/session/apply-session-memory.d.ts +21 -0
- package/dist/session/apply-session-memory.js +48 -0
- package/dist/session/index.d.ts +2 -0
- package/dist/session/index.js +2 -0
- package/dist/session/persist-turn-messages.d.ts +51 -0
- package/dist/session/persist-turn-messages.js +140 -3
- package/dist/sse/events/index.d.ts +13 -4
- package/dist/sse/events/index.js +9 -1
- package/dist/sse/events/rag-events.d.ts +63 -0
- package/dist/sse/events/rag-events.js +9 -0
- package/dist/sse/events/session-events.d.ts +38 -0
- package/dist/sse/events/session-events.js +4 -0
- package/dist/token/index.d.ts +2 -1
- package/dist/token/index.js +2 -1
- package/dist/token/record-task-token-usage.d.ts +2 -0
- package/dist/token/record-task-token-usage.js +24 -0
- package/dist/token/token-detail-mode.d.ts +4 -4
- package/dist/token/token-detail-mode.js +4 -4
- package/dist/token/token-detail.d.ts +8 -0
- package/dist/token/token-detail.js +22 -0
- package/dist/token/token-stats.service.d.ts +2 -2
- package/dist/token/token-stats.service.js +1 -1
- package/dist/tool/create-tool-executor.d.ts +6 -0
- package/dist/tool/create-tool-executor.js +4 -3
- package/dist/tool/harness-primitives.d.ts +9 -1
- package/dist/tool/harness-primitives.js +43 -12
- package/dist/tool/index.d.ts +2 -1
- package/dist/tool/index.js +2 -1
- package/dist/tool/tool-executor.interface.d.ts +27 -0
- package/dist/tool/tool-orchestrator.d.ts +3 -1
- package/dist/tool/tool-orchestrator.js +15 -6
- package/dist/tool/tool-retry.d.ts +15 -0
- package/dist/tool/tool-retry.js +69 -0
- package/package.json +2 -2
|
@@ -9,11 +9,13 @@
|
|
|
9
9
|
* @see design.md §1.4 ToolNode: LangGraph的ToolNode封装
|
|
10
10
|
* @see requirements.md Requirement 8 AC1, AC3, AC5
|
|
11
11
|
*/
|
|
12
|
+
import type { NodeRetryConfig } from '../contracts/executable-graph.js';
|
|
12
13
|
import type { AuthContext } from '../../tool/tool-executor.interface.js';
|
|
13
14
|
import type { ToolOrchestrator } from '../../tool/tool-orchestrator.js';
|
|
14
15
|
import type { ToolResultInjector } from '../../tool/tool-result-injector.js';
|
|
15
16
|
import type { SkillDirRef } from '../adapters/skill-read.js';
|
|
16
17
|
import type { HitlHandler } from '../hitl/hitl-handler.js';
|
|
18
|
+
import { type ExecutionInterceptors } from '../../execution/interceptors.js';
|
|
17
19
|
/**
|
|
18
20
|
* LangGraphState — LangGraph 内部状态(与 LLMNode 对齐)
|
|
19
21
|
*/
|
|
@@ -49,6 +51,8 @@ export interface ToolNodeProgress {
|
|
|
49
51
|
skillVersion?: string;
|
|
50
52
|
skillName?: string;
|
|
51
53
|
toolVersion?: string;
|
|
54
|
+
/** 本失败后是否还会再试。缺省 false */
|
|
55
|
+
willRetry?: boolean;
|
|
52
56
|
}
|
|
53
57
|
export declare class ToolNode {
|
|
54
58
|
private readonly orchestrator;
|
|
@@ -57,7 +61,20 @@ export declare class ToolNode {
|
|
|
57
61
|
private readonly onProgress?;
|
|
58
62
|
private readonly hitlHandler?;
|
|
59
63
|
private readonly nodeId?;
|
|
60
|
-
|
|
64
|
+
private readonly interceptors?;
|
|
65
|
+
/** 图声明的 rag 检索面(config.retrievalSources,工具形态声明位)——随请求转递给 rag_search 装饰器 */
|
|
66
|
+
private readonly ragSourcesConfig?;
|
|
67
|
+
/** 图声明的融合总条数(config.totalK,工具形态声明位)——同路转递 */
|
|
68
|
+
private readonly ragTotalK?;
|
|
69
|
+
/** 节点 `retry`;未设则只试一次 */
|
|
70
|
+
private readonly retry?;
|
|
71
|
+
constructor(orchestrator: ToolOrchestrator, injector: ToolResultInjector, defaultAuthContext: AuthContext, onProgress?: ((data: ToolNodeProgress) => void) | undefined, hitlHandler?: HitlHandler | undefined, nodeId?: string | undefined, interceptors?: ExecutionInterceptors | undefined,
|
|
72
|
+
/** 图声明的 rag 检索面(config.retrievalSources,工具形态声明位)——随请求转递给 rag_search 装饰器 */
|
|
73
|
+
ragSourcesConfig?: import("../nodes/rag/types.js").RagRetrievalSources | undefined,
|
|
74
|
+
/** 图声明的融合总条数(config.totalK,工具形态声明位)——同路转递 */
|
|
75
|
+
ragTotalK?: number | undefined,
|
|
76
|
+
/** 节点 `retry`;未设则只试一次 */
|
|
77
|
+
retry?: NodeRetryConfig | undefined);
|
|
61
78
|
/**
|
|
62
79
|
* execute — StateGraph 节点函数
|
|
63
80
|
*
|
|
@@ -71,6 +88,7 @@ export declare class ToolNode {
|
|
|
71
88
|
* 从 state 中提取 messages 数组
|
|
72
89
|
*/
|
|
73
90
|
private extractMessages;
|
|
91
|
+
private executeSingleWithRetry;
|
|
74
92
|
/**
|
|
75
93
|
* 从 messages 中找到最后一条 assistant 消息
|
|
76
94
|
*/
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* @see design.md §1.4 ToolNode: LangGraph的ToolNode封装
|
|
10
10
|
* @see requirements.md Requirement 8 AC1, AC3, AC5
|
|
11
11
|
*/
|
|
12
|
+
import { AppError, ErrorCodes } from '@gt-fe/eap-contracts';
|
|
12
13
|
import { resolveJailFromState } from '../../tool/harness-primitives.js';
|
|
13
14
|
import { recordSkillEntrySpan, skillEntryInvocationFromTool, skillTraceIdentity } from '../adapters/skill-entry-span.js';
|
|
14
15
|
import { inferHarnessSkillDir } from '../../tool/skill-dir-infer.js';
|
|
@@ -17,6 +18,10 @@ import { ToolConfirmationInterceptor } from '../../tool/tool-confirmation.js';
|
|
|
17
18
|
import { resolveCheckpointThreadId } from '../../session/persist-turn-messages.js';
|
|
18
19
|
import { toolTraceIdentity } from '../../tool/mcp-list.js';
|
|
19
20
|
import { resolveExecutionTrace, warnTraceSpanFailure, } from '../execution-trace-context.js';
|
|
21
|
+
import { emitRuntimeLog } from '../../common/logger.js';
|
|
22
|
+
import { decideBeforeTool, } from '../../execution/interceptors.js';
|
|
23
|
+
import { isRetryableToolFailure, resolveToolRetryConfig, sleepToolRetry, toolRetryDelayMs, } from '../../tool/tool-retry.js';
|
|
24
|
+
import { assertIsolatedSkillAllowsHitl, isIsolatedSkillState } from '../adapters/skill-isolate.js';
|
|
20
25
|
function skillDirsFromState(state) {
|
|
21
26
|
const defs = state['skillDefinitions'];
|
|
22
27
|
return Object.values(defs ?? {})
|
|
@@ -30,13 +35,27 @@ export class ToolNode {
|
|
|
30
35
|
onProgress;
|
|
31
36
|
hitlHandler;
|
|
32
37
|
nodeId;
|
|
33
|
-
|
|
38
|
+
interceptors;
|
|
39
|
+
ragSourcesConfig;
|
|
40
|
+
ragTotalK;
|
|
41
|
+
retry;
|
|
42
|
+
constructor(orchestrator, injector, defaultAuthContext, onProgress, hitlHandler, nodeId, interceptors,
|
|
43
|
+
/** 图声明的 rag 检索面(config.retrievalSources,工具形态声明位)——随请求转递给 rag_search 装饰器 */
|
|
44
|
+
ragSourcesConfig,
|
|
45
|
+
/** 图声明的融合总条数(config.totalK,工具形态声明位)——同路转递 */
|
|
46
|
+
ragTotalK,
|
|
47
|
+
/** 节点 `retry`;未设则只试一次 */
|
|
48
|
+
retry) {
|
|
34
49
|
this.orchestrator = orchestrator;
|
|
35
50
|
this.injector = injector;
|
|
36
51
|
this.defaultAuthContext = defaultAuthContext;
|
|
37
52
|
this.onProgress = onProgress;
|
|
38
53
|
this.hitlHandler = hitlHandler;
|
|
39
54
|
this.nodeId = nodeId;
|
|
55
|
+
this.interceptors = interceptors;
|
|
56
|
+
this.ragSourcesConfig = ragSourcesConfig;
|
|
57
|
+
this.ragTotalK = ragTotalK;
|
|
58
|
+
this.retry = retry;
|
|
40
59
|
}
|
|
41
60
|
/**
|
|
42
61
|
* execute — StateGraph 节点函数
|
|
@@ -100,6 +119,12 @@ export class ToolNode {
|
|
|
100
119
|
...(jail.workdir ? { jailWorkdir: jail.workdir } : {}),
|
|
101
120
|
...(jailExtraRoots.length > 0 ? { jailExtraRoots } : {}),
|
|
102
121
|
...(skillDir ? { skillDir } : {}),
|
|
122
|
+
// rag 会话级控制与工具表快照(rag_search 装饰器消费;普通工具执行器忽略)
|
|
123
|
+
...(state['ragSources'] ? { ragSources: state['ragSources'] } : {}),
|
|
124
|
+
...(state['ragScope'] ? { ragScope: state['ragScope'] } : {}),
|
|
125
|
+
...(this.ragSourcesConfig ? { ragSourcesConfig: this.ragSourcesConfig } : {}),
|
|
126
|
+
...(this.ragTotalK !== undefined ? { ragTotalK: this.ragTotalK } : {}),
|
|
127
|
+
...(loadedTools ? { loadedTools } : {}),
|
|
103
128
|
};
|
|
104
129
|
});
|
|
105
130
|
const executionContext = state['executionContext'];
|
|
@@ -120,8 +145,31 @@ export class ToolNode {
|
|
|
120
145
|
...(inSkillSubgraph ? { inSkillSubgraph: true } : {}),
|
|
121
146
|
});
|
|
122
147
|
};
|
|
148
|
+
// require_confirmation 工具走 HITL 拦截:改为顺序执行,thread_id 与 Checkpoint 对齐
|
|
149
|
+
const confirmationTools = new Set(Object.entries(loadedTools ?? {})
|
|
150
|
+
.filter(([, def]) => Boolean(def?.manifest.requireConfirmation))
|
|
151
|
+
.map(([name]) => name));
|
|
152
|
+
const needsHitl = Boolean(this.hitlHandler) &&
|
|
153
|
+
(requests.some((req) => confirmationTools.has(req.toolName)) ||
|
|
154
|
+
Boolean(this.interceptors?.beforeTool));
|
|
155
|
+
const resumingHitl = isHitlDecision(state['hitlChoice']);
|
|
156
|
+
const resumeToolSpanId = resumingHitl && typeof state['toolSpanId'] === 'string' && state['toolSpanId']
|
|
157
|
+
? state['toolSpanId']
|
|
158
|
+
: undefined;
|
|
159
|
+
const resumeToolCallId = typeof state['hitlToolCallId'] === 'string' && state['hitlToolCallId']
|
|
160
|
+
? state['hitlToolCallId']
|
|
161
|
+
: undefined;
|
|
162
|
+
const confirmationCount = requests.filter((req) => confirmationTools.has(req.toolName)).length;
|
|
123
163
|
// 5. 并行执行所有 Tool 调用(附 Trace span)
|
|
124
164
|
const spanIds = await Promise.all(requests.map(async (req) => {
|
|
165
|
+
const reusePausedSpan = Boolean(resumeToolSpanId) &&
|
|
166
|
+
((resumeToolCallId && req.toolCallId === resumeToolCallId) ||
|
|
167
|
+
(!resumeToolCallId &&
|
|
168
|
+
confirmationTools.has(req.toolName) &&
|
|
169
|
+
confirmationCount === 1));
|
|
170
|
+
if (reusePausedSpan) {
|
|
171
|
+
return resumeToolSpanId ?? null;
|
|
172
|
+
}
|
|
125
173
|
if (!traceCollector || !traceId)
|
|
126
174
|
return null;
|
|
127
175
|
try {
|
|
@@ -153,17 +201,9 @@ export class ToolNode {
|
|
|
153
201
|
return null;
|
|
154
202
|
}
|
|
155
203
|
}));
|
|
156
|
-
// 并行执行,但各自独立计时(避免共用 startedAt 导致时长不准/看起来像“写死”)
|
|
157
|
-
// require_confirmation 工具走 HITL 拦截:改为顺序执行,thread_id 与 Checkpoint 对齐
|
|
158
|
-
const confirmationTools = new Set(Object.entries(loadedTools ?? {})
|
|
159
|
-
.filter(([, def]) => Boolean(def?.manifest.requireConfirmation))
|
|
160
|
-
.map(([name]) => name));
|
|
161
|
-
const needsHitl = Boolean(this.hitlHandler) &&
|
|
162
|
-
requests.some((req) => confirmationTools.has(req.toolName));
|
|
163
204
|
const runOne = async (req, i) => {
|
|
164
205
|
const toolIdentity = toolTraceIdentity(loadedTools?.[req.toolName], req.toolName);
|
|
165
206
|
const toolset = toolIdentity.toolset;
|
|
166
|
-
const resumingHitl = isHitlDecision(state['hitlChoice']);
|
|
167
207
|
if (!resumingHitl) {
|
|
168
208
|
emitProgress({
|
|
169
209
|
toolCallId: req.toolCallId,
|
|
@@ -177,32 +217,73 @@ export class ToolNode {
|
|
|
177
217
|
const startedAt = Date.now();
|
|
178
218
|
try {
|
|
179
219
|
let result;
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
toolSpanId: spanIds[i] ?? undefined,
|
|
191
|
-
skillSpanId: typeof state['skillSpanId'] === 'string' ? state['skillSpanId'] : undefined,
|
|
192
|
-
});
|
|
193
|
-
result =
|
|
194
|
-
confirmed ??
|
|
195
|
-
{
|
|
196
|
-
toolCallId: req.toolCallId,
|
|
197
|
-
toolName: req.toolName,
|
|
198
|
-
content: `用户拒绝执行工具 ${req.toolName}`,
|
|
199
|
-
success: false,
|
|
200
|
-
error: `用户拒绝执行工具 ${req.toolName}`,
|
|
201
|
-
errorCode: 'hitl_rejected',
|
|
202
|
-
};
|
|
220
|
+
const gate = await decideBeforeTool(this.interceptors?.beforeTool, req, executionContext);
|
|
221
|
+
if (gate.action === 'deny') {
|
|
222
|
+
result = {
|
|
223
|
+
toolCallId: req.toolCallId,
|
|
224
|
+
toolName: req.toolName,
|
|
225
|
+
content: '',
|
|
226
|
+
success: false,
|
|
227
|
+
error: gate.reason,
|
|
228
|
+
errorCode: 'interceptor_denied',
|
|
229
|
+
};
|
|
203
230
|
}
|
|
204
231
|
else {
|
|
205
|
-
|
|
232
|
+
const askHitl = gate.action === 'ask' || (needsHitl && confirmationTools.has(req.toolName));
|
|
233
|
+
if (askHitl && this.hitlHandler) {
|
|
234
|
+
// 隔离子图未按独立 HITL 线程设计;interrupt 前拒绝,避免写错父 Checkpoint。
|
|
235
|
+
if (!resumingHitl) {
|
|
236
|
+
assertIsolatedSkillAllowsHitl(state);
|
|
237
|
+
}
|
|
238
|
+
const confirmSet = new Set(confirmationTools);
|
|
239
|
+
if (gate.action === 'ask')
|
|
240
|
+
confirmSet.add(req.toolName);
|
|
241
|
+
const interceptor = new ToolConfirmationInterceptor(confirmSet, this.hitlHandler, {
|
|
242
|
+
execute: (r) => this.orchestrator.executeSingle(r, executionContext),
|
|
243
|
+
});
|
|
244
|
+
const graphState = this.toGraphState(state);
|
|
245
|
+
const threadId = resolveCheckpointThreadId({
|
|
246
|
+
sessionId: typeof state['sessionId'] === 'string' ? state['sessionId'] : undefined,
|
|
247
|
+
taskId: req.taskId,
|
|
248
|
+
});
|
|
249
|
+
const confirmed = await interceptor.executeWithConfirmation(req, graphState, req.taskId ?? String(state['taskId'] ?? req.toolCallId), threadId, {
|
|
250
|
+
toolSpanId: spanIds[i] ?? undefined,
|
|
251
|
+
skillSpanId: typeof state['skillSpanId'] === 'string' ? state['skillSpanId'] : undefined,
|
|
252
|
+
});
|
|
253
|
+
result =
|
|
254
|
+
confirmed ??
|
|
255
|
+
{
|
|
256
|
+
toolCallId: req.toolCallId,
|
|
257
|
+
toolName: req.toolName,
|
|
258
|
+
content: `用户拒绝执行工具 ${req.toolName}`,
|
|
259
|
+
success: false,
|
|
260
|
+
error: `用户拒绝执行工具 ${req.toolName}`,
|
|
261
|
+
errorCode: 'hitl_rejected',
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
else if (gate.action === 'ask') {
|
|
265
|
+
result = {
|
|
266
|
+
toolCallId: req.toolCallId,
|
|
267
|
+
toolName: req.toolName,
|
|
268
|
+
content: '',
|
|
269
|
+
success: false,
|
|
270
|
+
error: '需要人工确认',
|
|
271
|
+
errorCode: 'interceptor_ask',
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
result = await this.executeSingleWithRetry(req, executionContext, emitProgress, toolset, toolIdentity);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
try {
|
|
279
|
+
await this.interceptors?.afterTool?.(req, result);
|
|
280
|
+
}
|
|
281
|
+
catch (err) {
|
|
282
|
+
emitRuntimeLog('warn', 'runtime.intercept', 'afterTool failed', {
|
|
283
|
+
event: 'runtime.intercept.after_tool_failed',
|
|
284
|
+
toolName: req.toolName,
|
|
285
|
+
error: err instanceof Error ? err.message : String(err),
|
|
286
|
+
});
|
|
206
287
|
}
|
|
207
288
|
const executionTime = Date.now() - startedAt;
|
|
208
289
|
if (result.success) {
|
|
@@ -274,6 +355,23 @@ export class ToolNode {
|
|
|
274
355
|
(typeof state['skillSpanId'] === 'string' ? state['skillSpanId'] : undefined);
|
|
275
356
|
throw hitlPaused;
|
|
276
357
|
}
|
|
358
|
+
if (err instanceof AppError &&
|
|
359
|
+
err.code === ErrorCodes.GRAPH_EXECUTION_ERROR &&
|
|
360
|
+
isIsolatedSkillState(state)) {
|
|
361
|
+
const failedSpanId = spanIds[i];
|
|
362
|
+
if (traceCollector && failedSpanId) {
|
|
363
|
+
try {
|
|
364
|
+
await traceCollector.completeNode(failedSpanId, {
|
|
365
|
+
status: 'failed',
|
|
366
|
+
error: err.message,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
catch (completeErr) {
|
|
370
|
+
warnTraceSpanFailure('completeNode', { type: 'tool', name: req.toolName, traceId, spanId: failedSpanId }, completeErr);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
throw err;
|
|
374
|
+
}
|
|
277
375
|
const executionTime = Date.now() - startedAt;
|
|
278
376
|
const message = err instanceof Error ? err.message : String(err);
|
|
279
377
|
emitProgress({
|
|
@@ -362,6 +460,37 @@ export class ToolNode {
|
|
|
362
460
|
return { role: 'user', content: String(msg) };
|
|
363
461
|
});
|
|
364
462
|
}
|
|
463
|
+
async executeSingleWithRetry(req, executionContext, emitProgress, toolset, toolIdentity) {
|
|
464
|
+
const retry = resolveToolRetryConfig(this.retry);
|
|
465
|
+
const maxRetries = retry?.maxRetries ?? 0;
|
|
466
|
+
let last;
|
|
467
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
468
|
+
last = await this.orchestrator.executeSingle(req, executionContext);
|
|
469
|
+
if (last.success)
|
|
470
|
+
return last;
|
|
471
|
+
const willRetry = attempt < maxRetries && isRetryableToolFailure(last);
|
|
472
|
+
if (!willRetry)
|
|
473
|
+
return last;
|
|
474
|
+
emitProgress({
|
|
475
|
+
toolCallId: req.toolCallId,
|
|
476
|
+
toolName: req.toolName,
|
|
477
|
+
...(toolset ? { toolset } : {}),
|
|
478
|
+
...(toolIdentity.toolVersion ? { toolVersion: toolIdentity.toolVersion } : {}),
|
|
479
|
+
status: 'error',
|
|
480
|
+
arguments: req.arguments,
|
|
481
|
+
error: last.error ?? 'unknown error',
|
|
482
|
+
willRetry: true,
|
|
483
|
+
});
|
|
484
|
+
await sleepToolRetry(toolRetryDelayMs(attempt, retry.backoff));
|
|
485
|
+
}
|
|
486
|
+
return last ?? {
|
|
487
|
+
toolCallId: req.toolCallId,
|
|
488
|
+
toolName: req.toolName,
|
|
489
|
+
content: '',
|
|
490
|
+
success: false,
|
|
491
|
+
error: 'unknown error',
|
|
492
|
+
};
|
|
493
|
+
}
|
|
365
494
|
/**
|
|
366
495
|
* 从 messages 中找到最后一条 assistant 消息
|
|
367
496
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ export type { ITraceCollector, CompleteNodeParams, TraceEvent, TraceSession, Exe
|
|
|
19
19
|
export type { ExecutionContext, CallerType, GovernancePermissionCheckParams, GovernancePermissionCheckResult, PermissionDenyReason, } from '@gt-fe/eap-contracts';
|
|
20
20
|
export { HttpGovernanceClient, AllowAllGovernanceClient, resolveExecutionContext, assertToolPermission, assertSkillPermission, permissionMessage, PermissionDeniedError, GovernanceUnavailableError, } from './governance/index.js';
|
|
21
21
|
export type { IGovernanceClient } from './governance/index.js';
|
|
22
|
+
export { decideBeforeModel, decideBeforeTool, } from './execution/index.js';
|
|
23
|
+
export type { AfterModelInterceptor, AfterToolInterceptor, BeforeModelInput, BeforeModelInterceptor, BeforeToolInterceptor, ExecutionInterceptors, ModelInterceptorDecision, ToolInterceptorDecision, } from './execution/index.js';
|
|
22
24
|
export { FileLlmTurnLog, InMemoryLlmTurnLog, createFileLlmTurnLogFromEnv, maskApiKey, resolveLlmTurnDir, } from './debug/llm-turn-log.js';
|
|
23
25
|
export { PostgresLlmTurnLog } from './debug/pg-llm-turn-log.js';
|
|
24
26
|
export type { ILlmTurnLog, LlmTurnRecord, LlmTurnIndexEntry, LlmTurnContext, LlmTurnLogKind, } from './debug/llm-turn-log.js';
|
package/dist/index.js
CHANGED
|
@@ -30,5 +30,6 @@ export * from './registry/index.js';
|
|
|
30
30
|
// LLM Providers
|
|
31
31
|
export * from './providers/index.js';
|
|
32
32
|
export { HttpGovernanceClient, AllowAllGovernanceClient, resolveExecutionContext, assertToolPermission, assertSkillPermission, permissionMessage, PermissionDeniedError, GovernanceUnavailableError, } from './governance/index.js';
|
|
33
|
+
export { decideBeforeModel, decideBeforeTool, } from './execution/index.js';
|
|
33
34
|
export { FileLlmTurnLog, InMemoryLlmTurnLog, createFileLlmTurnLogFromEnv, maskApiKey, resolveLlmTurnDir, } from './debug/llm-turn-log.js';
|
|
34
35
|
export { PostgresLlmTurnLog } from './debug/pg-llm-turn-log.js';
|
|
@@ -98,7 +98,7 @@ export type EmbeddingFunction = (text: string) => Promise<number[]>;
|
|
|
98
98
|
export declare class SemanticRetrievalService {
|
|
99
99
|
private readonly config;
|
|
100
100
|
private readonly vectorStore?;
|
|
101
|
-
private readonly embedding
|
|
101
|
+
private readonly embedding?;
|
|
102
102
|
constructor(config?: Partial<RetrievalConfig>, vectorStore?: IVectorStore, embedding?: EmbeddingFunction);
|
|
103
103
|
/**
|
|
104
104
|
* 检索相关记忆
|
|
@@ -20,21 +20,6 @@ export const DEFAULT_RETRIEVAL_CONFIG = {
|
|
|
20
20
|
keywordWeight: 0.3,
|
|
21
21
|
vectorWeight: 0.7,
|
|
22
22
|
};
|
|
23
|
-
/**
|
|
24
|
-
* 默认 Embedding 函数(占位)
|
|
25
|
-
*
|
|
26
|
-
* 实际使用时应注入真实的 Embedding 函数(如 OpenAI Embedding)。
|
|
27
|
-
*/
|
|
28
|
-
async function defaultEmbedding(text) {
|
|
29
|
-
// 简单的哈希向量(占位,实际应使用真实的 Embedding 模型)
|
|
30
|
-
const vector = new Array(128).fill(0);
|
|
31
|
-
for (let i = 0; i < text.length; i++) {
|
|
32
|
-
vector[i % 128] += text.charCodeAt(i) / 1000;
|
|
33
|
-
}
|
|
34
|
-
// 归一化
|
|
35
|
-
const norm = Math.sqrt(vector.reduce((sum, v) => sum + v * v, 0));
|
|
36
|
-
return vector.map((v) => v / norm);
|
|
37
|
-
}
|
|
38
23
|
// ─────────────────────────────────────────────
|
|
39
24
|
// 语义检索服务
|
|
40
25
|
// ─────────────────────────────────────────────
|
|
@@ -48,7 +33,7 @@ export class SemanticRetrievalService {
|
|
|
48
33
|
constructor(config, vectorStore, embedding) {
|
|
49
34
|
this.config = { ...DEFAULT_RETRIEVAL_CONFIG, ...config };
|
|
50
35
|
this.vectorStore = vectorStore;
|
|
51
|
-
this.embedding = embedding
|
|
36
|
+
this.embedding = embedding;
|
|
52
37
|
}
|
|
53
38
|
/**
|
|
54
39
|
* 检索相关记忆
|
|
@@ -106,11 +91,9 @@ export class SemanticRetrievalService {
|
|
|
106
91
|
* 基于向量相似度的检索。
|
|
107
92
|
*/
|
|
108
93
|
async vectorRetrieve(query, memories) {
|
|
109
|
-
if (!this.vectorStore) {
|
|
110
|
-
// 如果没有向量存储,回退到关键词检索
|
|
94
|
+
if (!this.vectorStore || !this.embedding) {
|
|
111
95
|
return this.keywordRetrieve(query, memories);
|
|
112
96
|
}
|
|
113
|
-
// 生成查询向量
|
|
114
97
|
const queryVector = await this.embedding(query);
|
|
115
98
|
// 查询相似向量
|
|
116
99
|
const vectorResults = await this.vectorStore.query(queryVector, this.config.limit, this.config.similarityThreshold);
|
|
@@ -183,12 +166,19 @@ export class SemanticRetrievalService {
|
|
|
183
166
|
'under', 'again', 'further', 'then', 'once',
|
|
184
167
|
]);
|
|
185
168
|
// 分词(简单按空格和标点分割)
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
.replace(/[^\w\u4e00-\u9fff]/g, ' ')
|
|
169
|
+
const cleaned = text.toLowerCase().replace(/[^\w\u4e00-\u9fff]/g, ' ');
|
|
170
|
+
const words = cleaned
|
|
189
171
|
.split(/\s+/)
|
|
190
172
|
.filter((word) => word.length > 1 && !stopWords.has(word));
|
|
191
|
-
|
|
173
|
+
const grams = [];
|
|
174
|
+
for (const word of words) {
|
|
175
|
+
if (/[\u4e00-\u9fff]/.test(word) && word.length > 2) {
|
|
176
|
+
for (let i = 0; i < word.length - 1; i++) {
|
|
177
|
+
grams.push(word.slice(i, i + 2));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return [...new Set([...words, ...grams])];
|
|
192
182
|
}
|
|
193
183
|
}
|
|
194
184
|
// ─────────────────────────────────────────────
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { EchoLLMProvider, createDefaultLLMProvider } from './echo-llm-provider.js';
|
|
2
2
|
export { HttpLLMProvider, DEFAULT_LLM_HTTP_TIMEOUT_MS, type HttpLLMProviderConfig } from './http-llm-provider.js';
|
|
3
|
-
export { RoutingLLMProvider, type RoutingLLMProviderConfig } from './routing-llm-provider.js';
|
|
3
|
+
export { RoutingLLMProvider, type RoutingLLMProviderConfig, assertFoundationModelsInvokeAllowsStream, isFoundationModelsInvokeEnabled, MODELS_INVOKE_STREAM_MESSAGE, } from './routing-llm-provider.js';
|
|
4
4
|
export { resolveLlmModelName, canOverrideGraphModel } from './resolve-llm-model.js';
|
|
5
5
|
export { createLocalLlmProvider, requireLocalLlmConnections, resolveLocalLlmConnection, parseLocalLlmConnectionRefs, hydrateLocalLlmConnections, LLM_CONNECTIONS_ENV, } from './local-llm-provider.js';
|
|
6
6
|
export type { LocalLlmConnection, LocalLlmConnectionRef, LocalLlmConnectionRefs, LocalLlmConnections, } from './local-llm-provider.js';
|
package/dist/providers/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { EchoLLMProvider, createDefaultLLMProvider } from './echo-llm-provider.js';
|
|
2
2
|
export { HttpLLMProvider, DEFAULT_LLM_HTTP_TIMEOUT_MS } from './http-llm-provider.js';
|
|
3
|
-
export { RoutingLLMProvider } from './routing-llm-provider.js';
|
|
3
|
+
export { RoutingLLMProvider, assertFoundationModelsInvokeAllowsStream, isFoundationModelsInvokeEnabled, MODELS_INVOKE_STREAM_MESSAGE, } from './routing-llm-provider.js';
|
|
4
4
|
export { resolveLlmModelName, canOverrideGraphModel } from './resolve-llm-model.js';
|
|
5
5
|
export { createLocalLlmProvider, requireLocalLlmConnections, resolveLocalLlmConnection, parseLocalLlmConnectionRefs, hydrateLocalLlmConnections, LLM_CONNECTIONS_ENV, } from './local-llm-provider.js';
|
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
import type { GraphMessage } from '../graph/contracts/executable-graph.js';
|
|
15
15
|
import type { ILLMProvider, LLMNodeConfig, LLMResponse, LLMChunk } from '../graph/nodes/llm-node.js';
|
|
16
16
|
import type { ILlmTurnLog } from '../debug/llm-turn-log.js';
|
|
17
|
+
export declare const MODELS_INVOKE_STREAM_MESSAGE = "EAP_RUNTIME_MODELS_INVOKE \u5B9E\u9A8C\u53E3\u53EA\u652F\u6301\u540C\u6B65 invoke\uFF08POST /models/invoke\uFF09\uFF0C\u4E0D\u652F\u6301 stream\u3002\u5173\u6389\u8BE5\u5F00\u5173\uFF0C\u6216\u6539\u8D70 Foundation /models/select + \u672C\u673A HTTP\u3002";
|
|
18
|
+
export declare function isFoundationModelsInvokeEnabled(env?: NodeJS.ProcessEnv): boolean;
|
|
19
|
+
/** `/models/invoke` 无真流式;开了该开关时 RoutingLLMProvider.stream 必须失败。 */
|
|
20
|
+
export declare function assertFoundationModelsInvokeAllowsStream(env?: NodeJS.ProcessEnv): void;
|
|
17
21
|
export interface RoutingLLMProviderConfig {
|
|
18
22
|
foundationUrl: string;
|
|
19
23
|
fallbackProvider?: ILLMProvider;
|
|
@@ -28,5 +32,6 @@ export declare class RoutingLLMProvider implements ILLMProvider {
|
|
|
28
32
|
stream(messages: GraphMessage[], config: LLMNodeConfig): AsyncIterableIterator<LLMChunk>;
|
|
29
33
|
private withCallDefaults;
|
|
30
34
|
private selectModel;
|
|
35
|
+
private useFoundationInvoke;
|
|
31
36
|
private createProviderForModel;
|
|
32
37
|
}
|
|
@@ -11,9 +11,22 @@
|
|
|
11
11
|
* - 支持任意 OpenAI 兼容 API(OpenAI / DeepSeek / 本地 Ollama 等)
|
|
12
12
|
* - 连接配置由 Foundation 统一管理,Runtime 无感知
|
|
13
13
|
*/
|
|
14
|
-
import { mergeModelCallParams } from '@gt-fe/eap-contracts';
|
|
14
|
+
import { AppError, ErrorCodes, mergeModelCallParams } from '@gt-fe/eap-contracts';
|
|
15
15
|
import { EchoLLMProvider } from './echo-llm-provider.js';
|
|
16
16
|
import { DEFAULT_LLM_HTTP_TIMEOUT_MS, HttpLLMProvider } from './http-llm-provider.js';
|
|
17
|
+
export const MODELS_INVOKE_STREAM_MESSAGE = 'EAP_RUNTIME_MODELS_INVOKE 实验口只支持同步 invoke(POST /models/invoke),不支持 stream。关掉该开关,或改走 Foundation /models/select + 本机 HTTP。';
|
|
18
|
+
export function isFoundationModelsInvokeEnabled(env = process.env) {
|
|
19
|
+
const raw = env.EAP_RUNTIME_MODELS_INVOKE;
|
|
20
|
+
return raw === '1' || raw === 'true';
|
|
21
|
+
}
|
|
22
|
+
/** `/models/invoke` 无真流式;开了该开关时 RoutingLLMProvider.stream 必须失败。 */
|
|
23
|
+
export function assertFoundationModelsInvokeAllowsStream(env = process.env) {
|
|
24
|
+
if (!isFoundationModelsInvokeEnabled(env))
|
|
25
|
+
return;
|
|
26
|
+
throw new AppError(ErrorCodes.GRAPH_EXECUTION_ERROR, MODELS_INVOKE_STREAM_MESSAGE, {
|
|
27
|
+
exposeMessage: true,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
17
30
|
export class RoutingLLMProvider {
|
|
18
31
|
foundationUrl;
|
|
19
32
|
fallback;
|
|
@@ -29,6 +42,7 @@ export class RoutingLLMProvider {
|
|
|
29
42
|
return provider.invoke(messages, this.withCallDefaults(config, selected));
|
|
30
43
|
}
|
|
31
44
|
async *stream(messages, config) {
|
|
45
|
+
assertFoundationModelsInvokeAllowsStream();
|
|
32
46
|
const selected = await this.selectModel(config);
|
|
33
47
|
const provider = this.createProviderForModel(selected);
|
|
34
48
|
yield* provider.stream(messages, this.withCallDefaults(config, selected));
|
|
@@ -72,7 +86,13 @@ export class RoutingLLMProvider {
|
|
|
72
86
|
}
|
|
73
87
|
return { name: config.model ?? 'echo', provider: 'local', connection: null };
|
|
74
88
|
}
|
|
89
|
+
useFoundationInvoke() {
|
|
90
|
+
return isFoundationModelsInvokeEnabled();
|
|
91
|
+
}
|
|
75
92
|
createProviderForModel(selected) {
|
|
93
|
+
if (this.useFoundationInvoke()) {
|
|
94
|
+
return new FoundationModelsInvokeProvider(this.foundationUrl, selected.name, this.turnLog);
|
|
95
|
+
}
|
|
76
96
|
// 优先使用 Foundation 返回的连接配置
|
|
77
97
|
if (selected.connection) {
|
|
78
98
|
return new HttpLLMProvider({
|
|
@@ -96,3 +116,48 @@ export class RoutingLLMProvider {
|
|
|
96
116
|
return this.fallback;
|
|
97
117
|
}
|
|
98
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Foundation `/models/invoke` 实验口。默认关。
|
|
121
|
+
* 只实现同步 invoke;stream 拒绝(无真流式、无鉴权头)。
|
|
122
|
+
* Foundation 未提供该接口时不要开 EAP_RUNTIME_MODELS_INVOKE。
|
|
123
|
+
*/
|
|
124
|
+
class FoundationModelsInvokeProvider {
|
|
125
|
+
foundationUrl;
|
|
126
|
+
model;
|
|
127
|
+
turnLog;
|
|
128
|
+
constructor(foundationUrl, model, turnLog) {
|
|
129
|
+
this.foundationUrl = foundationUrl;
|
|
130
|
+
this.model = model;
|
|
131
|
+
this.turnLog = turnLog;
|
|
132
|
+
}
|
|
133
|
+
async invoke(messages, config) {
|
|
134
|
+
const res = await fetch(`${this.foundationUrl}/api/v1/models/invoke`, {
|
|
135
|
+
method: 'POST',
|
|
136
|
+
headers: { 'Content-Type': 'application/json' },
|
|
137
|
+
body: JSON.stringify({
|
|
138
|
+
model: config.model ?? this.model,
|
|
139
|
+
messages,
|
|
140
|
+
temperature: config.temperature,
|
|
141
|
+
maxTokens: config.maxTokens,
|
|
142
|
+
}),
|
|
143
|
+
});
|
|
144
|
+
if (!res.ok) {
|
|
145
|
+
throw new Error(`Foundation /models/invoke HTTP ${res.status}`);
|
|
146
|
+
}
|
|
147
|
+
const body = (await res.json());
|
|
148
|
+
const content = body.data?.content ?? body.content ?? '';
|
|
149
|
+
void this.turnLog;
|
|
150
|
+
return {
|
|
151
|
+
content,
|
|
152
|
+
inputTokens: body.data?.inputTokens ?? 0,
|
|
153
|
+
outputTokens: body.data?.outputTokens ?? 0,
|
|
154
|
+
model: body.data?.model ?? config.model ?? this.model,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
async *stream(_messages, _config) {
|
|
158
|
+
assertFoundationModelsInvokeAllowsStream();
|
|
159
|
+
throw new AppError(ErrorCodes.GRAPH_EXECUTION_ERROR, MODELS_INVOKE_STREAM_MESSAGE, {
|
|
160
|
+
exposeMessage: true,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -16,6 +16,7 @@ import type { SessionContextService } from '../session/session-context.service.j
|
|
|
16
16
|
import type { MessageService } from '../session/message.service.js';
|
|
17
17
|
import type { SessionService, SessionRecord, MessageRecord } from '../session/session.service.js';
|
|
18
18
|
import { type ILlmTurnLog, type LlmTurnIndexEntry, type LlmTurnLogKind, type LlmTurnRecord } from '../debug/llm-turn-log.js';
|
|
19
|
+
import { type SessionCompressionOptions } from '../session/apply-session-compression.js';
|
|
19
20
|
import type { SSEEvent } from '../sse/contracts/sse-emitter.contract.js';
|
|
20
21
|
import type { TaskService } from '../task/task.service.js';
|
|
21
22
|
import type { ExecuteParams, ExecuteResult, ForkParams, ForkOverrides, ForkResult, HitlResponse, ResumeHitlResult, SeedSessionMessagesParams, SeedSessionMessagesResult } from './types.js';
|
|
@@ -46,6 +47,10 @@ export interface AgentRuntimeDeps {
|
|
|
46
47
|
*/
|
|
47
48
|
traceCollector?: ITraceCollector;
|
|
48
49
|
recordForkRelation?: RecordForkRelation;
|
|
50
|
+
/** 历史压缩;默认关。见 EAP_RUNTIME_COMPRESSION */
|
|
51
|
+
sessionCompression?: SessionCompressionOptions;
|
|
52
|
+
/** 长期记忆注入;默认关。见 EAP_RUNTIME_MEMORY */
|
|
53
|
+
sessionMemory?: import('../session/apply-session-memory.js').SessionMemoryOptions;
|
|
49
54
|
}
|
|
50
55
|
/** 一次对话详情:消息 + 每步 LLM 入参/响应。File / 内存 / Postgres 同一套结构。 */
|
|
51
56
|
export interface ConversationDetail {
|