@gt-fe/eap-runtime 0.0.19 → 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 +57 -24
- package/dist/api/handlers/stream.handler.d.ts +6 -0
- package/dist/api/handlers/stream.handler.js +59 -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/contracts/index.d.ts +1 -1
- package/dist/contracts/trace-collector.d.ts +16 -8
- 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 +26 -11
- package/dist/graph/adapters/langgraph.adapter.js +178 -83
- 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-isolate.d.ts +17 -0
- package/dist/graph/adapters/skill-isolate.js +50 -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.d.ts +7 -2
- package/dist/graph/adapters/subgraph-compiler.js +81 -35
- 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 +25 -1
- package/dist/graph/nodes/tool-node.js +193 -35
- 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 +1 -0
- package/dist/index.d.ts +3 -1
- 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 +105 -63
- 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/sse/trace-collector.d.ts +2 -9
- package/dist/token/index.d.ts +3 -0
- package/dist/token/index.js +2 -0
- package/dist/token/record-task-token-usage.d.ts +23 -0
- package/dist/token/record-task-token-usage.js +76 -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 +4 -2
- package/dist/token/token-stats.service.js +5 -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 +3 -2
- package/dist/tool/index.js +3 -2
- package/dist/tool/mcp-list.d.ts +12 -0
- package/dist/tool/mcp-list.js +15 -0
- 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
|
@@ -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 {
|
|
@@ -12,11 +12,15 @@ import { resolveAgentSystemPromptFromLoader } from '../registry/graph-loader.js'
|
|
|
12
12
|
import { toLoadedSkillMap } from '../tool/system-prompt-builder.js';
|
|
13
13
|
import { assembleLoadedTools } from '../tool/harness-primitives.js';
|
|
14
14
|
import { createFileLlmTurnLogFromEnv, } from '../debug/llm-turn-log.js';
|
|
15
|
-
import { buildHitlWaitingTaskOutput, buildTurnGraphMessages, buildTurnTranscriptFromStreamEvents, extractHitlThreadId, extractStreamReasoning,
|
|
15
|
+
import { buildHitlWaitingTaskOutput, buildTurnGraphMessages, buildTurnTranscriptFromStreamEvents, extractHitlThreadId, extractStreamReasoning, applyStreamTextDelta, createStreamAssistantBuckets, visibleStreamAssistantText, streamAssistantTexts, messageRecordsToGraphMessages, persistHitlWaitingPlaceholder, persistTurnTranscript, persistUserTurnMessage, resolveCheckpointThreadId, appendTerminalToolResult, } from '../session/persist-turn-messages.js';
|
|
16
|
+
import { iterSessionCompression, } from '../session/apply-session-compression.js';
|
|
17
|
+
import { applySessionMemory } from '../session/apply-session-memory.js';
|
|
18
|
+
import { resolveTokenDetail, shouldEmitTokenSse } from '../token/token-detail.js';
|
|
16
19
|
import { AppError, ErrorCodes } from '@gt-fe/eap-contracts';
|
|
17
20
|
import { resolveExecutionContext } from '../governance/governance-client.js';
|
|
18
21
|
import { resolveAgentCode } from '../common/agent-code.js';
|
|
19
22
|
import { resolveTraceId } from '../common/trace-id.js';
|
|
23
|
+
import { collectStreamNodeTokenUsage, recordTaskTokenUsages, tokenUsagesFromLlmChannel, } from '../token/record-task-token-usage.js';
|
|
20
24
|
import { resolveSkillInputFromInvokeInput } from '../registry/skill-io-guard.js';
|
|
21
25
|
const HITL_TERMINAL_STATUSES = [
|
|
22
26
|
'success',
|
|
@@ -207,6 +211,8 @@ export class AgentRuntime {
|
|
|
207
211
|
executionContext: ctx,
|
|
208
212
|
userId,
|
|
209
213
|
traceId: resolveTraceId(params),
|
|
214
|
+
ragScope: params.ragScope,
|
|
215
|
+
ragSources: params.ragSources,
|
|
210
216
|
}, userId);
|
|
211
217
|
return response.data;
|
|
212
218
|
}
|
|
@@ -273,7 +279,31 @@ export class AgentRuntime {
|
|
|
273
279
|
? (await this.deps.graphLoader.resolveAgentDir(agentCode, params.agentVersion)) ?? undefined
|
|
274
280
|
: undefined;
|
|
275
281
|
const agentSystemPrompt = await resolveAgentSystemPromptFromLoader(this.deps.graphLoader, agentCode, params.agentVersion);
|
|
276
|
-
|
|
282
|
+
let history = messageRecordsToGraphMessages(sessionContext.messages);
|
|
283
|
+
for await (const step of iterSessionCompression({
|
|
284
|
+
history,
|
|
285
|
+
sessionId,
|
|
286
|
+
userId,
|
|
287
|
+
taskId: task.id,
|
|
288
|
+
options: {
|
|
289
|
+
...this.deps.sessionCompression,
|
|
290
|
+
turnLog: this.deps.sessionCompression?.turnLog ?? this.deps.turnLog,
|
|
291
|
+
},
|
|
292
|
+
})) {
|
|
293
|
+
if (step.kind === 'history') {
|
|
294
|
+
history = step.messages;
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
yield this.toSSE(step.kind === 'started' ? 'session.compression.started' : 'session.compression.completed', correlationId, task.id, { ...step.payload });
|
|
298
|
+
}
|
|
299
|
+
history = await applySessionMemory({
|
|
300
|
+
history,
|
|
301
|
+
userId,
|
|
302
|
+
agentCode,
|
|
303
|
+
query: typeof params.input === 'string' ? params.input : undefined,
|
|
304
|
+
options: this.deps.sessionMemory,
|
|
305
|
+
});
|
|
306
|
+
const tokenDetail = resolveTokenDetail(params.config?.tokenDetail);
|
|
277
307
|
const messages = await buildTurnGraphMessages({
|
|
278
308
|
history,
|
|
279
309
|
input: params.input,
|
|
@@ -281,11 +311,12 @@ export class AgentRuntime {
|
|
|
281
311
|
threadId: sessionId,
|
|
282
312
|
});
|
|
283
313
|
const startTime = Date.now();
|
|
284
|
-
|
|
314
|
+
const assistantBuckets = createStreamAssistantBuckets();
|
|
285
315
|
let toolCallReasoningContent;
|
|
286
316
|
let assistantReasoningContent;
|
|
287
317
|
const pendingToolCalls = [];
|
|
288
318
|
const toolResults = [];
|
|
319
|
+
const nodeTokenUsages = [];
|
|
289
320
|
try {
|
|
290
321
|
for await (const runtimeEvent of this.deps.graphEngine.stream(graph, {
|
|
291
322
|
messages,
|
|
@@ -298,6 +329,8 @@ export class AgentRuntime {
|
|
|
298
329
|
sessionId,
|
|
299
330
|
executionContext: ctx,
|
|
300
331
|
forceToolServer: params.config?.forceToolServer,
|
|
332
|
+
ragScope: params.ragScope,
|
|
333
|
+
ragSources: params.ragSources,
|
|
301
334
|
loadedTools,
|
|
302
335
|
loadedSkills,
|
|
303
336
|
skillDefinitions,
|
|
@@ -321,32 +354,18 @@ export class AgentRuntime {
|
|
|
321
354
|
toolCallReasoningContent = p.reasoningContent;
|
|
322
355
|
}
|
|
323
356
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
if (p.toolCallId && p.toolName) {
|
|
327
|
-
toolResults.push({
|
|
328
|
-
toolCallId: p.toolCallId,
|
|
329
|
-
toolName: p.toolName,
|
|
330
|
-
content: p.content,
|
|
331
|
-
success: p.success !== false,
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
}
|
|
357
|
+
// 终态 tool.result / tool.error 回填历史;willRetry 的 error 不进 transcript
|
|
358
|
+
appendTerminalToolResult(runtimeEvent.type, runtimeEvent.payload, toolResults);
|
|
335
359
|
const streamedReasoning = extractStreamReasoning(runtimeEvent.type, runtimeEvent.payload);
|
|
336
360
|
if (streamedReasoning)
|
|
337
361
|
assistantReasoningContent = streamedReasoning;
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
assistantText = delta;
|
|
343
|
-
}
|
|
344
|
-
else {
|
|
345
|
-
assistantText += delta;
|
|
346
|
-
}
|
|
362
|
+
applyStreamTextDelta(assistantBuckets, runtimeEvent.type, runtimeEvent.payload);
|
|
363
|
+
collectStreamNodeTokenUsage(runtimeEvent, nodeTokenUsages);
|
|
364
|
+
if (shouldEmitTokenSse(tokenDetail, runtimeEvent.type)) {
|
|
365
|
+
yield this.runtimeEventToSSE(runtimeEvent, correlationId, task.id);
|
|
347
366
|
}
|
|
348
|
-
yield this.runtimeEventToSSE(runtimeEvent, correlationId, task.id);
|
|
349
367
|
}
|
|
368
|
+
await recordTaskTokenUsages(this.deps.taskService, task.id, nodeTokenUsages);
|
|
350
369
|
await this.deps.taskService.updateStatus(task.id, 'success');
|
|
351
370
|
await persistTurnTranscript(this.deps.messageService, {
|
|
352
371
|
sessionId,
|
|
@@ -355,7 +374,9 @@ export class AgentRuntime {
|
|
|
355
374
|
messages: buildTurnTranscriptFromStreamEvents({
|
|
356
375
|
pendingToolCalls,
|
|
357
376
|
toolResults,
|
|
358
|
-
|
|
377
|
+
assistantTexts: streamAssistantTexts(assistantBuckets),
|
|
378
|
+
assistantText: visibleStreamAssistantText(assistantBuckets),
|
|
379
|
+
finishPoints: graph.finishPoints,
|
|
359
380
|
toolCallReasoningContent,
|
|
360
381
|
assistantReasoningContent,
|
|
361
382
|
}),
|
|
@@ -368,6 +389,7 @@ export class AgentRuntime {
|
|
|
368
389
|
catch (err) {
|
|
369
390
|
const hitlPaused = unwrapHitlPaused(err);
|
|
370
391
|
if (hitlPaused) {
|
|
392
|
+
await recordTaskTokenUsages(this.deps.taskService, task.id, nodeTokenUsages);
|
|
371
393
|
await this.deps.taskService.updateStatus(task.id, 'waiting_hitl', buildHitlWaitingTaskOutput({
|
|
372
394
|
threadId: hitlPaused.threadId,
|
|
373
395
|
question: hitlPaused.question,
|
|
@@ -385,6 +407,7 @@ export class AgentRuntime {
|
|
|
385
407
|
return;
|
|
386
408
|
}
|
|
387
409
|
const message = err instanceof Error ? err.message : 'Unknown error';
|
|
410
|
+
await recordTaskTokenUsages(this.deps.taskService, task.id, nodeTokenUsages);
|
|
388
411
|
await this.deps.taskService.updateStatus(task.id, 'failed', message);
|
|
389
412
|
yield this.toSSE('task.error', correlationId, task.id, {
|
|
390
413
|
code: 'runtime.graph.execution_error',
|
|
@@ -404,7 +427,7 @@ export class AgentRuntime {
|
|
|
404
427
|
let output = '';
|
|
405
428
|
let waiting;
|
|
406
429
|
let sawWait = false;
|
|
407
|
-
|
|
430
|
+
const assistantBuckets = createStreamAssistantBuckets();
|
|
408
431
|
for await (const event of this.streamResumeHitl(taskId, response)) {
|
|
409
432
|
if (event.type === 'hitl.waited') {
|
|
410
433
|
sawWait = true;
|
|
@@ -416,21 +439,12 @@ export class AgentRuntime {
|
|
|
416
439
|
};
|
|
417
440
|
output = waiting.question;
|
|
418
441
|
}
|
|
419
|
-
|
|
420
|
-
if (delta) {
|
|
421
|
-
if (event.type === 'response.final') {
|
|
422
|
-
if (delta.trim())
|
|
423
|
-
assistantText = delta;
|
|
424
|
-
}
|
|
425
|
-
else {
|
|
426
|
-
assistantText += delta;
|
|
427
|
-
}
|
|
428
|
-
}
|
|
442
|
+
applyStreamTextDelta(assistantBuckets, event.type, event.data);
|
|
429
443
|
}
|
|
430
444
|
if (sawWait) {
|
|
431
445
|
return { taskId, status: 'waiting_hitl', output, hitl: waiting };
|
|
432
446
|
}
|
|
433
|
-
return { taskId, status: 'success', output:
|
|
447
|
+
return { taskId, status: 'success', output: visibleStreamAssistantText(assistantBuckets) || output };
|
|
434
448
|
}
|
|
435
449
|
/**
|
|
436
450
|
* HITL 恢复的流式入口:新开一条 SSE(第一条 stream 在 `hitl.waited` 后已结束)。
|
|
@@ -460,6 +474,7 @@ export class AgentRuntime {
|
|
|
460
474
|
if (!claimed) {
|
|
461
475
|
throw new AppError(ErrorCodes.TASK_HITL_INVALID_STATE, `Task ${taskId} is no longer waiting for HITL`, { exposeMessage: true });
|
|
462
476
|
}
|
|
477
|
+
const tokenDetail = resolveTokenDetail(response.tokenDetail);
|
|
463
478
|
yield this.toSSE('hitl.resumed', correlationId, taskId, {
|
|
464
479
|
choice: response.choice,
|
|
465
480
|
userId: response.userId,
|
|
@@ -511,11 +526,12 @@ export class AgentRuntime {
|
|
|
511
526
|
traceCollector: this.deps.traceCollector,
|
|
512
527
|
};
|
|
513
528
|
const startTime = Date.now();
|
|
514
|
-
|
|
529
|
+
const assistantBuckets = createStreamAssistantBuckets();
|
|
515
530
|
let toolCallReasoningContent;
|
|
516
531
|
let assistantReasoningContent;
|
|
517
532
|
const pendingToolCalls = [];
|
|
518
533
|
const toolResults = [];
|
|
534
|
+
const nodeTokenUsages = [];
|
|
519
535
|
try {
|
|
520
536
|
for await (const runtimeEvent of this.deps.graphEngine.stream(graph, continueState)) {
|
|
521
537
|
if (runtimeEvent.type === 'tool.called') {
|
|
@@ -531,33 +547,18 @@ export class AgentRuntime {
|
|
|
531
547
|
toolCallReasoningContent = p.reasoningContent;
|
|
532
548
|
}
|
|
533
549
|
}
|
|
534
|
-
|
|
535
|
-
const p = runtimeEvent.payload;
|
|
536
|
-
if (p.toolCallId && p.toolName) {
|
|
537
|
-
toolResults.push({
|
|
538
|
-
toolCallId: p.toolCallId,
|
|
539
|
-
toolName: p.toolName,
|
|
540
|
-
content: p.content ?? p.error,
|
|
541
|
-
success: runtimeEvent.type === 'tool.result' && p.success !== false,
|
|
542
|
-
});
|
|
543
|
-
}
|
|
544
|
-
}
|
|
550
|
+
appendTerminalToolResult(runtimeEvent.type, runtimeEvent.payload, toolResults);
|
|
545
551
|
const streamedReasoning = extractStreamReasoning(runtimeEvent.type, runtimeEvent.payload);
|
|
546
552
|
if (streamedReasoning)
|
|
547
553
|
assistantReasoningContent = streamedReasoning;
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
assistantText = delta;
|
|
553
|
-
}
|
|
554
|
-
else {
|
|
555
|
-
assistantText += delta;
|
|
556
|
-
}
|
|
554
|
+
applyStreamTextDelta(assistantBuckets, runtimeEvent.type, runtimeEvent.payload);
|
|
555
|
+
collectStreamNodeTokenUsage(runtimeEvent, nodeTokenUsages);
|
|
556
|
+
if (shouldEmitTokenSse(tokenDetail, runtimeEvent.type)) {
|
|
557
|
+
yield this.runtimeEventToSSE(runtimeEvent, correlationId, taskId);
|
|
557
558
|
}
|
|
558
|
-
yield this.runtimeEventToSSE(runtimeEvent, correlationId, taskId);
|
|
559
559
|
}
|
|
560
|
-
await this.deps.taskService
|
|
560
|
+
await recordTaskTokenUsages(this.deps.taskService, taskId, nodeTokenUsages);
|
|
561
|
+
await this.deps.taskService.updateStatus(taskId, 'success', visibleStreamAssistantText(assistantBuckets) || undefined);
|
|
561
562
|
if (sessionId) {
|
|
562
563
|
await persistTurnTranscript(this.deps.messageService, {
|
|
563
564
|
sessionId,
|
|
@@ -566,7 +567,9 @@ export class AgentRuntime {
|
|
|
566
567
|
messages: buildTurnTranscriptFromStreamEvents({
|
|
567
568
|
pendingToolCalls,
|
|
568
569
|
toolResults,
|
|
569
|
-
|
|
570
|
+
assistantTexts: streamAssistantTexts(assistantBuckets),
|
|
571
|
+
assistantText: visibleStreamAssistantText(assistantBuckets),
|
|
572
|
+
finishPoints: graph.finishPoints,
|
|
570
573
|
toolCallReasoningContent,
|
|
571
574
|
assistantReasoningContent,
|
|
572
575
|
}),
|
|
@@ -580,6 +583,7 @@ export class AgentRuntime {
|
|
|
580
583
|
catch (err) {
|
|
581
584
|
const hitlPaused = unwrapHitlPaused(err);
|
|
582
585
|
if (hitlPaused) {
|
|
586
|
+
await recordTaskTokenUsages(this.deps.taskService, taskId, nodeTokenUsages);
|
|
583
587
|
await this.deps.taskService.updateStatus(taskId, 'waiting_hitl', buildHitlWaitingTaskOutput({
|
|
584
588
|
threadId: hitlPaused.threadId,
|
|
585
589
|
question: hitlPaused.question,
|
|
@@ -600,6 +604,7 @@ export class AgentRuntime {
|
|
|
600
604
|
}
|
|
601
605
|
const appErr = err instanceof AppError ? err : AppError.fromUnknown(err);
|
|
602
606
|
const failMsg = appErr.exposeMessage ? appErr.message : '系统内部错误';
|
|
607
|
+
await recordTaskTokenUsages(this.deps.taskService, taskId, nodeTokenUsages);
|
|
603
608
|
await this.deps.taskService.updateStatus(taskId, 'failed', failMsg);
|
|
604
609
|
yield this.toSSE('task.error', correlationId, taskId, {
|
|
605
610
|
code: appErr.code,
|
|
@@ -662,8 +667,14 @@ export class AgentRuntime {
|
|
|
662
667
|
? forkedCheckpoint.state.agentSystemPrompt
|
|
663
668
|
: undefined);
|
|
664
669
|
try {
|
|
670
|
+
const forkState = { ...forkedCheckpoint.state };
|
|
671
|
+
delete forkState.toolSpanId;
|
|
672
|
+
delete forkState.hitlToolCallId;
|
|
665
673
|
const result = await this.deps.graphEngine.invoke(graph, {
|
|
666
|
-
|
|
674
|
+
messages: Array.isArray(forkState.messages)
|
|
675
|
+
? forkState.messages
|
|
676
|
+
: [],
|
|
677
|
+
...forkState,
|
|
667
678
|
agentVersion,
|
|
668
679
|
agentDir,
|
|
669
680
|
agentSystemPrompt,
|
|
@@ -676,6 +687,19 @@ export class AgentRuntime {
|
|
|
676
687
|
traceId,
|
|
677
688
|
traceCollector: this.deps.traceCollector,
|
|
678
689
|
});
|
|
690
|
+
if (result.tokenUsage.perNode && result.tokenUsage.perNode.length > 0) {
|
|
691
|
+
await recordTaskTokenUsages(this.deps.taskService, task.id, result.tokenUsage.perNode);
|
|
692
|
+
}
|
|
693
|
+
else if (result.tokenUsage.total > 0) {
|
|
694
|
+
await recordTaskTokenUsages(this.deps.taskService, task.id, [
|
|
695
|
+
{
|
|
696
|
+
nodeId: 'graph',
|
|
697
|
+
model: 'unknown',
|
|
698
|
+
inputTokens: result.tokenUsage.input,
|
|
699
|
+
outputTokens: result.tokenUsage.output,
|
|
700
|
+
},
|
|
701
|
+
]);
|
|
702
|
+
}
|
|
679
703
|
await this.deps.taskService.updateStatus(task.id, 'success', result.output);
|
|
680
704
|
if (this.deps.recordForkRelation) {
|
|
681
705
|
try {
|
|
@@ -707,6 +731,20 @@ export class AgentRuntime {
|
|
|
707
731
|
}
|
|
708
732
|
catch (err) {
|
|
709
733
|
const message = err instanceof Error ? err.message : 'Unknown error';
|
|
734
|
+
const sessionId = typeof forkedCheckpoint.state.sessionId === 'string'
|
|
735
|
+
? forkedCheckpoint.state.sessionId
|
|
736
|
+
: undefined;
|
|
737
|
+
const threadId = resolveCheckpointThreadId({
|
|
738
|
+
sessionId,
|
|
739
|
+
taskId: task.id,
|
|
740
|
+
});
|
|
741
|
+
const ck = await this.deps.checkpointSaver.load(threadId);
|
|
742
|
+
const baseline = tokenUsagesFromLlmChannel(forkedCheckpoint.state);
|
|
743
|
+
const latest = tokenUsagesFromLlmChannel(ck?.state ?? forkedCheckpoint.state);
|
|
744
|
+
const usages = latest.length >= baseline.length ? latest.slice(baseline.length) : latest;
|
|
745
|
+
await recordTaskTokenUsages(this.deps.taskService, task.id, usages);
|
|
746
|
+
const input_tokens = usages.reduce((sum, row) => sum + row.inputTokens, 0);
|
|
747
|
+
const output_tokens = usages.reduce((sum, row) => sum + row.outputTokens, 0);
|
|
710
748
|
await this.deps.taskService.updateStatus(task.id, 'failed', message);
|
|
711
749
|
return {
|
|
712
750
|
newTraceId: traceId,
|
|
@@ -715,7 +753,11 @@ export class AgentRuntime {
|
|
|
715
753
|
checkpointId,
|
|
716
754
|
status: 'failed',
|
|
717
755
|
output: message,
|
|
718
|
-
tokenUsage: {
|
|
756
|
+
tokenUsage: {
|
|
757
|
+
input_tokens,
|
|
758
|
+
output_tokens,
|
|
759
|
+
total_tokens: input_tokens + output_tokens,
|
|
760
|
+
},
|
|
719
761
|
};
|
|
720
762
|
}
|
|
721
763
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dev 装配的 rag 依赖 — createDevRuntime 检测显式配置后自动注入
|
|
3
|
+
*
|
|
4
|
+
* 启用条件:EAP_EMBED_API_KEY 存在(缺省不注入,行为与无 rag 的 master 一致)。
|
|
5
|
+
* retriever 惰性初始化:vector-engine(better-sqlite3+sqlite-vec,native 依赖)
|
|
6
|
+
* 只在首次检索时动态 import——装配期零 native 加载,服务端部署不受影响。
|
|
7
|
+
* import 目标可用 EAP_VECTOR_ENGINE_PATH 指定绝对路径(默认解析 @eap/vector-engine;
|
|
8
|
+
* eap-cli 发版链等无该包的环境走绝对路径注入)。
|
|
9
|
+
*
|
|
10
|
+
* 环境变量(与 .local 演示脚本同一套):
|
|
11
|
+
* - EAP_EMBED_API_KEY / EAP_EMBED_BASE_URL / EAP_EMBED_MODEL / EAP_EMBED_DIMS:embedding 网关
|
|
12
|
+
* - RAG_DB:向量库路径(缺省 ~/.eap/rag/agent.db)
|
|
13
|
+
*/
|
|
14
|
+
import type { IRetriever, RagNodeDeps } from '../graph/nodes/rag/index.js';
|
|
15
|
+
/**
|
|
16
|
+
* dev rag 装配:EAP_EMBED_API_KEY 显式配置才启用,否则 undefined(不注入)。
|
|
17
|
+
* llmProvider 复用 dev 装配已构造的实例(与 llm 节点同一体系)。
|
|
18
|
+
* EAP_RAG_TRACE=1 时接 console 观测 rag.* 事件流(轮次决策/各源检索/降噪/收束),
|
|
19
|
+
* 供多源融合行为观察;缺省静默。
|
|
20
|
+
*/
|
|
21
|
+
export declare function createDevRagNodeDeps(llmProvider: RagNodeDeps['llmProvider'], loadFn?: () => Promise<IRetriever>): RagNodeDeps | undefined;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dev 装配的 rag 依赖 — createDevRuntime 检测显式配置后自动注入
|
|
3
|
+
*
|
|
4
|
+
* 启用条件:EAP_EMBED_API_KEY 存在(缺省不注入,行为与无 rag 的 master 一致)。
|
|
5
|
+
* retriever 惰性初始化:vector-engine(better-sqlite3+sqlite-vec,native 依赖)
|
|
6
|
+
* 只在首次检索时动态 import——装配期零 native 加载,服务端部署不受影响。
|
|
7
|
+
* import 目标可用 EAP_VECTOR_ENGINE_PATH 指定绝对路径(默认解析 @eap/vector-engine;
|
|
8
|
+
* eap-cli 发版链等无该包的环境走绝对路径注入)。
|
|
9
|
+
*
|
|
10
|
+
* 环境变量(与 .local 演示脚本同一套):
|
|
11
|
+
* - EAP_EMBED_API_KEY / EAP_EMBED_BASE_URL / EAP_EMBED_MODEL / EAP_EMBED_DIMS:embedding 网关
|
|
12
|
+
* - RAG_DB:向量库路径(缺省 ~/.eap/rag/agent.db)
|
|
13
|
+
*/
|
|
14
|
+
import { homedir } from 'node:os';
|
|
15
|
+
import { dirname, join } from 'node:path';
|
|
16
|
+
import { existsSync, mkdirSync, readFileSync, watchFile } from 'node:fs';
|
|
17
|
+
import { pathToFileURL } from 'node:url';
|
|
18
|
+
import { appendFileSync } from 'node:fs';
|
|
19
|
+
// 惰性读 env:模块顶层读不到(runtime 经 sdk import 链早于 CLI 灌 .env.local 执行)
|
|
20
|
+
const trace = (line) => {
|
|
21
|
+
console.log(line);
|
|
22
|
+
const f = process.env.EAP_RAG_TRACE_FILE;
|
|
23
|
+
if (f)
|
|
24
|
+
appendFileSync(f, line + '\n', 'utf-8');
|
|
25
|
+
};
|
|
26
|
+
/** 入库日志环形缓冲(globalThis 桥接:Playground 服务同进程读,GET /api/rag-ingest-log) */
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
28
|
+
const ingestLogBuf = (globalThis.__ragIngestLog ??= []);
|
|
29
|
+
const ingestLog = (line) => {
|
|
30
|
+
const ts = new Date().toISOString().slice(11, 19);
|
|
31
|
+
ingestLogBuf.push(`[${ts}] ${line}`);
|
|
32
|
+
if (ingestLogBuf.length > 200)
|
|
33
|
+
ingestLogBuf.splice(0, ingestLogBuf.length - 200);
|
|
34
|
+
};
|
|
35
|
+
import { HttpEmbedder, LocalSqliteRetriever } from '../graph/nodes/rag/index.js';
|
|
36
|
+
/** 惰性 IRetriever:首次调用才加载真实现;加载失败不缓存,允许下轮重试 */
|
|
37
|
+
class LazyLocalRetriever {
|
|
38
|
+
load;
|
|
39
|
+
sourceId = 'local';
|
|
40
|
+
implPromise;
|
|
41
|
+
constructor(load) {
|
|
42
|
+
this.load = load;
|
|
43
|
+
}
|
|
44
|
+
ensure() {
|
|
45
|
+
this.implPromise ??= this.load().catch((err) => {
|
|
46
|
+
this.implPromise = undefined; // 失败不缓存,下次调用重试加载
|
|
47
|
+
throw err;
|
|
48
|
+
});
|
|
49
|
+
return this.implPromise;
|
|
50
|
+
}
|
|
51
|
+
async retrieve(query, opts) {
|
|
52
|
+
return (await this.ensure()).retrieve(query, opts);
|
|
53
|
+
}
|
|
54
|
+
async retrieveByKeyword(query, opts) {
|
|
55
|
+
const impl = await this.ensure();
|
|
56
|
+
if (!impl.retrieveByKeyword)
|
|
57
|
+
return [];
|
|
58
|
+
return impl.retrieveByKeyword(query, opts);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** 默认加载器:HttpEmbedder + vector-engine + LocalSqliteRetriever(单库:RAG_DB) */
|
|
62
|
+
async function loadLocalRetriever() {
|
|
63
|
+
const dims = Number(process.env.EAP_EMBED_DIMS ?? 1024);
|
|
64
|
+
const embedder = new HttpEmbedder({
|
|
65
|
+
baseUrl: String(process.env.EAP_EMBED_BASE_URL ?? 'https://gtaiapi.gtcloud.cn'),
|
|
66
|
+
model: String(process.env.EAP_EMBED_MODEL ?? 'Qwen3-Embedding-8B'),
|
|
67
|
+
apiKey: String(process.env.EAP_EMBED_API_KEY ?? ''),
|
|
68
|
+
dims,
|
|
69
|
+
dimensions: dims,
|
|
70
|
+
});
|
|
71
|
+
const moduleName = process.env.EAP_VECTOR_ENGINE_PATH ?? '@eap/vector-engine';
|
|
72
|
+
// Windows 绝对路径须转 file:// URL(盘符字母否则被解析为 URL scheme)
|
|
73
|
+
const spec = /^[a-zA-Z]:[\\/]/.test(moduleName) ? pathToFileURL(moduleName).href : moduleName;
|
|
74
|
+
const mod = (await import(spec));
|
|
75
|
+
const dbPath = process.env.RAG_DB ?? join(homedir(), '.eap', 'rag', 'agent.db');
|
|
76
|
+
mkdirSync(dirname(dbPath), { recursive: true });
|
|
77
|
+
const engine = await mod.createAsyncEngine({ dbPath, dims });
|
|
78
|
+
// 自动向量化:EAP_RAG_AUTO_INGEST=1 时常驻调度器扫授权目录,文件放入即入库
|
|
79
|
+
// (写入侧归宿主/桌面语义;dev 演示用轮询,生产为授权事件触发)。
|
|
80
|
+
// RAG_DOCS_DIR 支持 `;` 分隔多目录——"授权一个新文件夹" = 追加一个路径。
|
|
81
|
+
//
|
|
82
|
+
// 授权清单热更新(客户端授权动作的 dev 模拟):RAG_AUTHORIZED_DIRS_FILE(json
|
|
83
|
+
// {"dirs":[...]},缺省 <库目录>/authorized-dirs.json)存在则覆盖 RAG_DOCS_DIR,
|
|
84
|
+
// 文件变更 → scheduler.updateDirs 即时生效(新目录入库/移除软删),全程不重启。
|
|
85
|
+
if (process.env.EAP_RAG_AUTO_INGEST) {
|
|
86
|
+
const envDirs = (process.env.RAG_DOCS_DIR ?? join(dirname(dbPath), 'docs'))
|
|
87
|
+
.split(';')
|
|
88
|
+
.map((s) => s.trim())
|
|
89
|
+
.filter(Boolean);
|
|
90
|
+
const authFile = process.env.RAG_AUTHORIZED_DIRS_FILE ?? join(dirname(dbPath), 'authorized-dirs.json');
|
|
91
|
+
const readAuthDirs = () => {
|
|
92
|
+
try {
|
|
93
|
+
const parsed = JSON.parse(readFileSync(authFile, 'utf-8'));
|
|
94
|
+
if (Array.isArray(parsed.dirs) && parsed.dirs.every((d) => typeof d === 'string')) {
|
|
95
|
+
const dirs = parsed.dirs.map((d) => d.trim()).filter(Boolean);
|
|
96
|
+
return dirs.length ? dirs : undefined; // 空清单视为无效,防误清库
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// 文件不存在/解析失败:保持现清单
|
|
101
|
+
}
|
|
102
|
+
return undefined;
|
|
103
|
+
};
|
|
104
|
+
let currentDirs = readAuthDirs() ?? envDirs;
|
|
105
|
+
const scheduler = new mod.IngestScheduler({
|
|
106
|
+
engine,
|
|
107
|
+
embedder,
|
|
108
|
+
userId: process.env.RAG_USER ?? 'anonymous',
|
|
109
|
+
dirs: currentDirs,
|
|
110
|
+
intervalMs: Number(process.env.EAP_RAG_SCAN_INTERVAL_MS ?? 30_000),
|
|
111
|
+
watch: process.env.EAP_RAG_SCAN_MODE !== 'interval',
|
|
112
|
+
onEvent: (e) => {
|
|
113
|
+
if (e.type === 'file.ingested') {
|
|
114
|
+
const line = `[rag-ingest] 📥 ${String(e['filePath']).split('\\').pop()} → ${e['chunks']} 片`;
|
|
115
|
+
console.log(line);
|
|
116
|
+
ingestLog(line);
|
|
117
|
+
}
|
|
118
|
+
else if (e.type === 'file.failed') {
|
|
119
|
+
const line = `[rag-ingest] ✗ ${String(e['filePath']).split('\\').pop()}: ${e['error']}`;
|
|
120
|
+
console.log(line);
|
|
121
|
+
ingestLog(line);
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
scheduler.start();
|
|
126
|
+
ingestLog(`🚀 自动向量化已启动:${currentDirs.length} 个授权目录`);
|
|
127
|
+
console.log(`[rag-ingest] 自动向量化已启动:目录 ${currentDirs.join('; ')}(${Number(process.env.EAP_RAG_SCAN_INTERVAL_MS ?? 30_000) / 1000}s 轮询${existsSync(authFile) ? `,授权清单 ${authFile} 热更新` : ''})`);
|
|
128
|
+
// 授权清单文件变更 → updateDirs(编辑器保存/前端写入均触发;stat 轮询最稳)
|
|
129
|
+
watchFile(authFile, { interval: 2000 }, () => {
|
|
130
|
+
const next = readAuthDirs();
|
|
131
|
+
if (next && next.join(';') !== currentDirs.join(';')) {
|
|
132
|
+
currentDirs = next;
|
|
133
|
+
scheduler.updateDirs(next);
|
|
134
|
+
const line = `[rag-ingest] 🔑 授权目录更新(免重启生效):${next.length} 个目录`;
|
|
135
|
+
console.log(line + `(${next.join('; ')})`);
|
|
136
|
+
ingestLog(line);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return new LocalSqliteRetriever({ engine, embedder });
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* dev rag 装配:EAP_EMBED_API_KEY 显式配置才启用,否则 undefined(不注入)。
|
|
144
|
+
* llmProvider 复用 dev 装配已构造的实例(与 llm 节点同一体系)。
|
|
145
|
+
* EAP_RAG_TRACE=1 时接 console 观测 rag.* 事件流(轮次决策/各源检索/降噪/收束),
|
|
146
|
+
* 供多源融合行为观察;缺省静默。
|
|
147
|
+
*/
|
|
148
|
+
export function createDevRagNodeDeps(llmProvider, loadFn = loadLocalRetriever) {
|
|
149
|
+
if (!process.env.EAP_EMBED_API_KEY)
|
|
150
|
+
return undefined;
|
|
151
|
+
const fmt = (v, n = 40) => String(v ?? '').replace(/\s+/g, ' ').slice(0, n);
|
|
152
|
+
const emitEvent = process.env.EAP_RAG_TRACE
|
|
153
|
+
? (type, p) => {
|
|
154
|
+
if (type === 'rag.round') {
|
|
155
|
+
const calls = (p['toolCalls'] ?? [])
|
|
156
|
+
.map((t) => `${t.name}(${JSON.stringify(t.arguments)})`).join(' ');
|
|
157
|
+
trace(`[rag] 第${p['round']}轮: ${calls}`);
|
|
158
|
+
}
|
|
159
|
+
else if (type === 'rag.search') {
|
|
160
|
+
const head = p['kind'] === 'kb' ? `kb:${p['toolName']}` : `local:"${fmt(p['query'], 20)}"`;
|
|
161
|
+
trace(`[rag] search ${head} topK=${p['topK']} → hits=${p['hits']} ${p['durationMs']}ms${p['success'] ? '' : ` ✗${p['error']}`}${p['scope'] ? ` scope=${p['scope']}` : ''}`);
|
|
162
|
+
}
|
|
163
|
+
else if (type === 'rag.denoise') {
|
|
164
|
+
trace(`[rag] 降噪 候选${p['candidates']} → 保留${p['kept']}`);
|
|
165
|
+
for (const f of p['filtered'] ?? [])
|
|
166
|
+
trace(`[rag] 滤 ${fmt(f.rawId, 30)}(${fmt(f.reason, 30)})`);
|
|
167
|
+
}
|
|
168
|
+
else if (type === 'rag.rewrite') {
|
|
169
|
+
trace(`[rag] 空结果重写 "${fmt(p['from'], 20)}" → "${fmt(p['to'], 20)}"`);
|
|
170
|
+
}
|
|
171
|
+
else if (type === 'rag.finish') {
|
|
172
|
+
trace(`[rag] 收束: ${p['rounds']}轮 最终${p['docs']}条${p['empty'] ? '(空)' : ''}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
: undefined;
|
|
176
|
+
// 预热:自动向量化开启时立即后台装配一次——调度器/授权热更新/入库日志不等到首次
|
|
177
|
+
// local_search 才启动(演示"先授权后提问"场景需要授权即生效)。失败静默,检索时重试。
|
|
178
|
+
if (process.env.EAP_RAG_AUTO_INGEST) {
|
|
179
|
+
void Promise.resolve(loadFn()).catch(() => undefined);
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
llmProvider,
|
|
183
|
+
retrievers: [new LazyLocalRetriever(loadFn)],
|
|
184
|
+
emitEvent,
|
|
185
|
+
};
|
|
186
|
+
}
|