@gt-fe/eap-runtime 0.0.20 → 0.0.22

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.
Files changed (133) hide show
  1. package/README.md +4 -3
  2. package/dist/api/handlers/invoke.handler.d.ts +7 -0
  3. package/dist/api/handlers/invoke.handler.js +52 -10
  4. package/dist/api/handlers/stream.handler.d.ts +6 -0
  5. package/dist/api/handlers/stream.handler.js +60 -39
  6. package/dist/api/routes/agent.routes.d.ts +8 -1
  7. package/dist/checkpoint/file-checkpoint-saver.d.ts +4 -0
  8. package/dist/checkpoint/file-checkpoint-saver.js +51 -22
  9. package/dist/checkpoint/index.d.ts +1 -1
  10. package/dist/checkpoint/index.js +1 -1
  11. package/dist/common/types.d.ts +2 -2
  12. package/dist/common/types.js +1 -1
  13. package/dist/execution/index.d.ts +2 -0
  14. package/dist/execution/index.js +2 -0
  15. package/dist/execution/interceptors.d.ts +40 -0
  16. package/dist/execution/interceptors.js +23 -0
  17. package/dist/execution/ports.d.ts +18 -0
  18. package/dist/execution/ports.js +13 -0
  19. package/dist/governance/audit-reporter.d.ts +14 -0
  20. package/dist/governance/audit-reporter.js +18 -0
  21. package/dist/governance/index.d.ts +2 -0
  22. package/dist/governance/index.js +1 -0
  23. package/dist/governance/permission-guard.d.ts +6 -2
  24. package/dist/governance/permission-guard.js +38 -5
  25. package/dist/graph/adapters/graph-nesting.d.ts +12 -0
  26. package/dist/graph/adapters/graph-nesting.js +29 -0
  27. package/dist/graph/adapters/index.d.ts +2 -0
  28. package/dist/graph/adapters/index.js +2 -0
  29. package/dist/graph/adapters/langgraph-checkpointer.d.ts +11 -0
  30. package/dist/graph/adapters/langgraph-checkpointer.js +48 -9
  31. package/dist/graph/adapters/langgraph.adapter.d.ts +33 -11
  32. package/dist/graph/adapters/langgraph.adapter.js +200 -98
  33. package/dist/graph/adapters/skill-isolate.d.ts +13 -0
  34. package/dist/graph/adapters/skill-isolate.js +39 -0
  35. package/dist/graph/adapters/subgraph-compiler.d.ts +7 -2
  36. package/dist/graph/adapters/subgraph-compiler.js +63 -30
  37. package/dist/graph/contracts/executable-graph.d.ts +33 -5
  38. package/dist/graph/hitl/hitl-handler.d.ts +13 -4
  39. package/dist/graph/hitl/hitl-handler.js +80 -7
  40. package/dist/graph/nodes/index.d.ts +1 -0
  41. package/dist/graph/nodes/index.js +1 -0
  42. package/dist/graph/nodes/llm-node.d.ts +3 -1
  43. package/dist/graph/nodes/llm-node.js +26 -2
  44. package/dist/graph/nodes/rag/dedup.d.ts +20 -0
  45. package/dist/graph/nodes/rag/dedup.js +77 -0
  46. package/dist/graph/nodes/rag/denoise.d.ts +58 -0
  47. package/dist/graph/nodes/rag/denoise.js +220 -0
  48. package/dist/graph/nodes/rag/fusion/fuse.d.ts +11 -0
  49. package/dist/graph/nodes/rag/fusion/fuse.js +25 -0
  50. package/dist/graph/nodes/rag/fusion/normalize.d.ts +9 -0
  51. package/dist/graph/nodes/rag/fusion/normalize.js +135 -0
  52. package/dist/graph/nodes/rag/index.d.ts +18 -0
  53. package/dist/graph/nodes/rag/index.js +17 -0
  54. package/dist/graph/nodes/rag/prompts.d.ts +12 -0
  55. package/dist/graph/nodes/rag/prompts.js +40 -0
  56. package/dist/graph/nodes/rag/rag-node.d.ts +21 -0
  57. package/dist/graph/nodes/rag/rag-node.js +135 -0
  58. package/dist/graph/nodes/rag/rag-search-tool.d.ts +48 -0
  59. package/dist/graph/nodes/rag/rag-search-tool.js +230 -0
  60. package/dist/graph/nodes/rag/react-loop.d.ts +57 -0
  61. package/dist/graph/nodes/rag/react-loop.js +467 -0
  62. package/dist/graph/nodes/rag/retrievers/http-embedder.d.ts +29 -0
  63. package/dist/graph/nodes/rag/retrievers/http-embedder.js +66 -0
  64. package/dist/graph/nodes/rag/retrievers/in-memory-retriever.d.ts +17 -0
  65. package/dist/graph/nodes/rag/retrievers/in-memory-retriever.js +18 -0
  66. package/dist/graph/nodes/rag/retrievers/local-sqlite-retriever.d.ts +46 -0
  67. package/dist/graph/nodes/rag/retrievers/local-sqlite-retriever.js +85 -0
  68. package/dist/graph/nodes/rag/tools.d.ts +90 -0
  69. package/dist/graph/nodes/rag/tools.js +260 -0
  70. package/dist/graph/nodes/rag/types.d.ts +226 -0
  71. package/dist/graph/nodes/rag/types.js +9 -0
  72. package/dist/graph/nodes/tool-node.d.ts +19 -1
  73. package/dist/graph/nodes/tool-node.js +158 -33
  74. package/dist/index.d.ts +2 -0
  75. package/dist/index.js +1 -0
  76. package/dist/memory/semantic-retrieval.service.d.ts +1 -1
  77. package/dist/memory/semantic-retrieval.service.js +13 -23
  78. package/dist/providers/index.d.ts +2 -2
  79. package/dist/providers/index.js +2 -2
  80. package/dist/providers/local-llm-provider.d.ts +6 -0
  81. package/dist/providers/local-llm-provider.js +29 -5
  82. package/dist/providers/routing-llm-provider.d.ts +7 -2
  83. package/dist/providers/routing-llm-provider.js +71 -16
  84. package/dist/registry/deployment-snapshot.js +18 -4
  85. package/dist/registry/graph-loader.d.ts +2 -0
  86. package/dist/registry/graph-loader.js +39 -2
  87. package/dist/registry/preload-flags.d.ts +1 -1
  88. package/dist/registry/preload-flags.js +1 -1
  89. package/dist/runtime/agent-runtime.d.ts +5 -0
  90. package/dist/runtime/agent-runtime.js +107 -70
  91. package/dist/runtime/dev-rag-deps.d.ts +21 -0
  92. package/dist/runtime/dev-rag-deps.js +186 -0
  93. package/dist/runtime/dev-runtime.d.ts +18 -2
  94. package/dist/runtime/dev-runtime.js +68 -8
  95. package/dist/runtime/index.d.ts +1 -1
  96. package/dist/runtime/types.d.ts +10 -9
  97. package/dist/session/apply-session-compression.d.ts +85 -0
  98. package/dist/session/apply-session-compression.js +281 -0
  99. package/dist/session/apply-session-memory.d.ts +21 -0
  100. package/dist/session/apply-session-memory.js +48 -0
  101. package/dist/session/index.d.ts +2 -0
  102. package/dist/session/index.js +2 -0
  103. package/dist/session/persist-turn-messages.d.ts +57 -1
  104. package/dist/session/persist-turn-messages.js +160 -5
  105. package/dist/sse/events/index.d.ts +13 -4
  106. package/dist/sse/events/index.js +9 -1
  107. package/dist/sse/events/rag-events.d.ts +63 -0
  108. package/dist/sse/events/rag-events.js +9 -0
  109. package/dist/sse/events/session-events.d.ts +38 -0
  110. package/dist/sse/events/session-events.js +4 -0
  111. package/dist/token/index.d.ts +2 -1
  112. package/dist/token/index.js +2 -1
  113. package/dist/token/record-task-token-usage.d.ts +25 -0
  114. package/dist/token/record-task-token-usage.js +44 -0
  115. package/dist/token/token-detail-mode.d.ts +4 -4
  116. package/dist/token/token-detail-mode.js +4 -4
  117. package/dist/token/token-detail.d.ts +8 -0
  118. package/dist/token/token-detail.js +22 -0
  119. package/dist/token/token-stats.service.d.ts +2 -2
  120. package/dist/token/token-stats.service.js +1 -1
  121. package/dist/tool/create-tool-executor.d.ts +6 -0
  122. package/dist/tool/create-tool-executor.js +4 -3
  123. package/dist/tool/harness-primitives.d.ts +9 -1
  124. package/dist/tool/harness-primitives.js +43 -12
  125. package/dist/tool/index.d.ts +2 -1
  126. package/dist/tool/index.js +2 -1
  127. package/dist/tool/tool-confirmation.js +8 -5
  128. package/dist/tool/tool-executor.interface.d.ts +27 -0
  129. package/dist/tool/tool-orchestrator.d.ts +3 -1
  130. package/dist/tool/tool-orchestrator.js +15 -6
  131. package/dist/tool/tool-retry.d.ts +15 -0
  132. package/dist/tool/tool-retry.js +69 -0
  133. package/package.json +2 -2
@@ -0,0 +1,135 @@
1
+ /**
2
+ * RagNode — type:rag 节点壳(LangGraph 节点签名)
3
+ *
4
+ * 薄壳:从 state 取运行期上下文(ephemeral channels)→ 圈定工具表 →
5
+ * 调 react-loop 内核 → 以 runtime 节点惯用形态返回 partial state。
6
+ * 实例在编译期创建、跨请求复用,不放任何请求级状态。
7
+ *
8
+ * @see design-v2 §5.2 / §5.3
9
+ */
10
+ import { resolveTraceId } from '../../../common/trace-id.js';
11
+ import { buildRagTools, resolveRagSources, mergeLocalDirs } from './tools.js';
12
+ import { runReactLoop } from './react-loop.js';
13
+ export class RagNode {
14
+ nodeId;
15
+ config;
16
+ deps;
17
+ constructor(nodeId, config, deps) {
18
+ this.nodeId = nodeId;
19
+ this.config = config;
20
+ this.deps = deps;
21
+ }
22
+ async execute(state) {
23
+ const loadedTools = state['loadedTools'];
24
+ // 检索面解析:数据源目录(config)× 会话级勾选(state.ragSources,undefined=全部启用)。
25
+ // 外部源 → 工具表圈定;local-file → 目录白名单并入检索 scope。
26
+ // 本地选择权:请求带 ragScope.dirPaths 时以声明全量面为交集基准(ragSources 是否
27
+ // 勾 local-file id 不再单独决定本地开关);未带时维持"未勾 local id=关本地"。
28
+ const selectedIds = state['ragSources'];
29
+ const { toolScope, sourceCatalog, hasLocal, hasCatalog, localDirs, localDirsAll } = resolveRagSources(this.config, selectedIds);
30
+ // 图声明了 sources = 完整检索面:无 local-file 条目则本地源整体不可用(不进工具表)
31
+ const localEnabled = hasCatalog ? hasLocal : true;
32
+ const requestScope = state['ragScope'];
33
+ const ragScope = localEnabled
34
+ ? mergeLocalDirs(hasLocal, requestScope?.dirPaths !== undefined ? localDirsAll : localDirs, requestScope)
35
+ : undefined;
36
+ // 检索器:宿主装配注入(单库实现细节,不经 DSL 暴露);本地源未启用时不进表
37
+ const retrievers = localEnabled ? this.deps.retrievers : undefined;
38
+ // 圈定工具表:数据源目录过滤 loadedTools + local_search(挂了 retrievers 才有)+ rag_finish
39
+ // (toolScope/sourceCatalog 均为会话勾选后的解析结果,成对传入防目录与工具表错位)
40
+ const { apiTools, kbToolNames, missingToolNames, excludedConfirmToolNames } = buildRagTools(this.config, loadedTools, retrievers, { toolScope, sourceCatalog, localEnabled });
41
+ // fail-fast:声明的工具名未注册 → 首个请求立即报清晰错误(loadedTools 是请求级
42
+ // ephemeral channel,编译期拿不到,故只能在首次执行期校验)
43
+ if (missingToolNames.length > 0) {
44
+ throw new Error(`RagNode "${this.nodeId}" 检索源 tools 引用了未注册的工具: ${missingToolNames.join(', ')}(loadedTools 中不存在;外部源工具名解析 = ref 显式指定 ?? \`\${type}_search\` 约定,注册名与约定不符时在描述符加 ref)`);
45
+ }
46
+ // fail-fast:显式点名 requireConfirmation 工具 → 拒绝(HITL 确认流在图级
47
+ // ToolNode,rag 循环直调 orchestrator 会绕过确认;缺省配置时已在
48
+ // buildRagTools 静默剔除,仅显式声明视为配置错误)
49
+ if (excludedConfirmToolNames.length > 0) {
50
+ throw new Error(`RagNode "${this.nodeId}" 检索源 tools 引用了需人工确认的工具: ${excludedConfirmToolNames.join(', ')}(requireConfirmation 工具的确认流挂在图级 tool 节点,rag 循环内执行会绕过确认,不支持挂载)`);
51
+ }
52
+ const result = await runReactLoop({
53
+ nodeId: this.nodeId,
54
+ query: extractQuery(state),
55
+ history: extractHistory(state),
56
+ config: this.config,
57
+ apiTools,
58
+ kbToolNames,
59
+ llmProvider: this.deps.llmProvider,
60
+ toolOrchestrator: this.deps.toolOrchestrator,
61
+ retrievers,
62
+ loadedTools,
63
+ authContext: this.buildAuthContext(state),
64
+ executionContext: state['executionContext'],
65
+ forceToolServer: state['forceToolServer'],
66
+ userId: String(state['userId'] ?? ''),
67
+ ragScope,
68
+ emitEvent: this.deps.emitEvent,
69
+ traceCollector: state['traceCollector'],
70
+ traceId: resolveTraceId(state),
71
+ });
72
+ // partial state 三连(与 LLMNode 同惯例):
73
+ // messages 只返回新增(channel 为 append reducer);token 走 __llmTokenUsage 累加器
74
+ return {
75
+ messages: [{ role: 'assistant', content: result.contextText }],
76
+ currentNode: this.nodeId,
77
+ __llmTokenUsage: result.tokenUsage,
78
+ };
79
+ }
80
+ /** 身份组装照 ToolNode 惯例:executionContext 优先,state 兜底 */
81
+ buildAuthContext(state) {
82
+ const executionContext = state['executionContext'];
83
+ return {
84
+ userId: String(executionContext?.userId ?? state['userId'] ?? 'system'),
85
+ delegationToken: String(executionContext?.delegationToken ?? state['delegationToken'] ?? ''),
86
+ agentCode: String(state['agentCode'] ?? state['agentId'] ?? ''),
87
+ };
88
+ }
89
+ }
90
+ /**
91
+ * 检索任务 = 最后一条 user 消息(对话上下文另走 history 供首轮整定)
92
+ */
93
+ function extractQuery(state) {
94
+ const messages = state['messages'];
95
+ if (!Array.isArray(messages))
96
+ return '';
97
+ for (let i = messages.length - 1; i >= 0; i--) {
98
+ const m = messages[i];
99
+ if (m && m['role'] === 'user' && typeof m['content'] === 'string' && m['content'].trim()) {
100
+ return m['content'];
101
+ }
102
+ }
103
+ return '';
104
+ }
105
+ // 字段映射须与 LLMNode.extractMessages 对齐:tool 消息的 toolCallId 与 assistant 的
106
+ // toolCalls 缺失时 OpenAI 兼容网关直接 400,不得压缩为裸 role/content
107
+ function extractHistory(state) {
108
+ const messages = state['messages'];
109
+ if (!Array.isArray(messages))
110
+ return [];
111
+ return messages.map((msg) => {
112
+ if (typeof msg === 'object' && msg !== null) {
113
+ const m = msg;
114
+ const toolCalls = (m['toolCalls'] ?? m['tool_calls']);
115
+ return {
116
+ role: m['role'] ?? 'user',
117
+ content: m['content'] ?? '',
118
+ toolCalls,
119
+ toolCallId: typeof m['toolCallId'] === 'string'
120
+ ? m['toolCallId']
121
+ : typeof m['tool_call_id'] === 'string'
122
+ ? m['tool_call_id']
123
+ : undefined,
124
+ toolName: typeof m['toolName'] === 'string' ? m['toolName'] : undefined,
125
+ success: typeof m['success'] === 'boolean' ? m['success'] : undefined,
126
+ reasoningContent: typeof m['reasoningContent'] === 'string'
127
+ ? m['reasoningContent']
128
+ : typeof m['reasoning_content'] === 'string'
129
+ ? m['reasoning_content']
130
+ : undefined,
131
+ };
132
+ }
133
+ return { role: 'user', content: String(msg) };
134
+ });
135
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * rag_search — 工具封装入口(design-v2 §5.3「工具封装形态」开放形态之一)
3
+ *
4
+ * 主 Agent 的 llm 节点发 tool_calls: [rag_search(query)] → 本执行器拦截,
5
+ * 内部跑同一个 runReactLoop——内核同一,入口不同。query 已由主 LLM 结合
6
+ * 对话上下文整定(§5.4:工具封装入口拿到的已是整定过的查询,内核首轮
7
+ * 职责自动退化为直接检索,history 传空)。
8
+ *
9
+ * 装配(宿主三步):
10
+ * const ragSearch = new RagSearchToolExecutor(真实 executor, ragNodeDeps, ragConfig, registryTools);
11
+ * new LangGraphAdapter(checkpointer, llmProvider, ragSearch, ...); // 第 3 参
12
+ * loadedTools 里 merge ragSearchLoadedTool(); // 主 LLM 工具表可见
13
+ *
14
+ * 防递归:rag_search 永不进入内层工具表——config.tools 显式圈定时自动剔除
15
+ * rag_search 自身;未圈定时内层工具表退化为 local_search + rag_finish
16
+ * (registryTools 不传即无注册工具),杜绝套娃。
17
+ */
18
+ import type { IToolExecutor, ToolCallRequest, ToolCallResult } from '../../../tool/tool-executor.interface.js';
19
+ import type { ToolOrchestrator } from '../../../tool/tool-orchestrator.js';
20
+ import type { LoadedToolMap, LoadedToolDefinition } from '../../../tool/loaded-tool-definition.js';
21
+ import type { RagNodeConfig, RagNodeDeps } from './types.js';
22
+ export declare const RAG_SEARCH_TOOL_NAME = "rag_search";
23
+ /** 合成 loadedTools 条目:主 agent 的 llm 节点工具表由此看到 rag_search */
24
+ export declare function ragSearchLoadedTool(): LoadedToolDefinition;
25
+ export interface RagSearchToolOptions {
26
+ /** rag 内核配置(totalK/quota/maxIterations/nearDupThreshold…) */
27
+ config?: RagNodeConfig;
28
+ /**
29
+ * 注册表快照:config.tools 显式圈定注册工具源(如 kb-search)时必传
30
+ * (内层路 B 路由元数据来源);未传时内层工具表 = local_search + rag_finish。
31
+ */
32
+ registryTools?: LoadedToolMap;
33
+ }
34
+ export declare class RagSearchToolExecutor implements IToolExecutor {
35
+ private readonly inner;
36
+ private readonly deps;
37
+ private readonly config;
38
+ private readonly registryTools;
39
+ /** 装配期检索面的门禁映射(请求带图声明时按请求重建) */
40
+ private readonly sourceIdsByTool;
41
+ constructor(inner: IToolExecutor, deps: RagNodeDeps, options?: RagSearchToolOptions);
42
+ /** adapter 注入的编排器(节点形态同款回退链:deps 显式 > adapter 注入 > inner 直执) */
43
+ private attachedOrchestrator?;
44
+ /** 宿主装配(LangGraphAdapter)注入图级 ToolOrchestrator——kb 派发走与 tool 节点同一编排 */
45
+ attachToolOrchestrator(orchestrator: ToolOrchestrator): void;
46
+ private resolveOrchestrator;
47
+ execute(req: ToolCallRequest): Promise<ToolCallResult>;
48
+ }
@@ -0,0 +1,230 @@
1
+ /**
2
+ * rag_search — 工具封装入口(design-v2 §5.3「工具封装形态」开放形态之一)
3
+ *
4
+ * 主 Agent 的 llm 节点发 tool_calls: [rag_search(query)] → 本执行器拦截,
5
+ * 内部跑同一个 runReactLoop——内核同一,入口不同。query 已由主 LLM 结合
6
+ * 对话上下文整定(§5.4:工具封装入口拿到的已是整定过的查询,内核首轮
7
+ * 职责自动退化为直接检索,history 传空)。
8
+ *
9
+ * 装配(宿主三步):
10
+ * const ragSearch = new RagSearchToolExecutor(真实 executor, ragNodeDeps, ragConfig, registryTools);
11
+ * new LangGraphAdapter(checkpointer, llmProvider, ragSearch, ...); // 第 3 参
12
+ * loadedTools 里 merge ragSearchLoadedTool(); // 主 LLM 工具表可见
13
+ *
14
+ * 防递归:rag_search 永不进入内层工具表——config.tools 显式圈定时自动剔除
15
+ * rag_search 自身;未圈定时内层工具表退化为 local_search + rag_finish
16
+ * (registryTools 不传即无注册工具),杜绝套娃。
17
+ */
18
+ import { buildRagTools, resolveRagSources, mergeLocalDirs, sourceToolName } from './tools.js';
19
+ import { runReactLoop } from './react-loop.js';
20
+ export const RAG_SEARCH_TOOL_NAME = 'rag_search';
21
+ /** 合成 loadedTools 条目:主 agent 的 llm 节点工具表由此看到 rag_search */
22
+ export function ragSearchLoadedTool() {
23
+ return {
24
+ toolId: 'tool-rag-search',
25
+ name: RAG_SEARCH_TOOL_NAME,
26
+ manifest: {
27
+ id: 'tool-rag-search',
28
+ name: RAG_SEARCH_TOOL_NAME,
29
+ version: '1.0.0',
30
+ // http/server:无论 server/desktop 部署都锁定 httpExecutor 路径(装饰器位置),
31
+ // 避免被 local 安装分流绕过拦截
32
+ type: 'http',
33
+ toolRuntime: 'server',
34
+ dataScope: 'global',
35
+ description: '检索企业知识库与本地文档,返回压缩后的高相关证据上下文(内部运行 RAG 检索子智能体)。',
36
+ },
37
+ inputSchema: {
38
+ type: 'object',
39
+ properties: {
40
+ query: { type: 'string', description: '自包含的检索查询' },
41
+ topK: { type: 'number', description: '融合后返回条数上限(可选)' },
42
+ },
43
+ required: ['query'],
44
+ },
45
+ };
46
+ }
47
+ /**
48
+ * 工具名 → 数据源 id 列表(会话勾选门禁用)——从检索面声明构建。
49
+ * 解析序必须与 resolveRagSources 一致(sources 优先,无 sources 再看
50
+ * retrievalTools/tools):只遍历 sources 会让 legacy 声明位不进门禁映射,
51
+ * 直调放行而内层按 ragSources 过滤,同图两路口径分裂。legacy 字符串条目的
52
+ * id 即工具名本身——映射为 {工具名:[工具名]},勾选该名才放行。
53
+ */
54
+ function buildSourceIdsByTool(config) {
55
+ const map = new Map();
56
+ const rs = config.retrievalSources;
57
+ const entries = rs?.sources
58
+ ? rs.sources.map((e) => (typeof e === 'string' ? { type: 'external', id: e } : e))
59
+ : ((rs?.retrievalTools ?? config.tools)?.map((n) => ({ type: 'external', id: n })) ?? []);
60
+ for (const e of entries) {
61
+ const tool = sourceToolName(e);
62
+ if (!tool || tool === RAG_SEARCH_TOOL_NAME)
63
+ continue;
64
+ const ids = map.get(tool) ?? [];
65
+ ids.push(e.id);
66
+ map.set(tool, ids);
67
+ }
68
+ return map;
69
+ }
70
+ export class RagSearchToolExecutor {
71
+ inner;
72
+ deps;
73
+ config;
74
+ registryTools;
75
+ /** 装配期检索面的门禁映射(请求带图声明时按请求重建) */
76
+ sourceIdsByTool;
77
+ constructor(inner, deps, options = {}) {
78
+ this.inner = inner;
79
+ this.deps = deps;
80
+ // 防递归前置:解析到 rag_search 自身的源条目剔除,描述符原样保留
81
+ // (三字段模型下字符串化会丢 source 资源目录);三种声明位按原位回写
82
+ const config = { ...options.config };
83
+ const rs = config.retrievalSources;
84
+ if (rs?.sources) {
85
+ config.retrievalSources = {
86
+ ...rs,
87
+ sources: rs.sources.filter((e) => sourceToolName(typeof e === 'string' ? { type: 'external', id: e } : e) !== RAG_SEARCH_TOOL_NAME),
88
+ };
89
+ }
90
+ else if (rs?.retrievalTools) {
91
+ config.retrievalSources = { ...rs, retrievalTools: rs.retrievalTools.filter((n) => n !== RAG_SEARCH_TOOL_NAME) };
92
+ }
93
+ else if (config.tools) {
94
+ config.tools = config.tools.filter((n) => n !== RAG_SEARCH_TOOL_NAME);
95
+ }
96
+ this.config = config;
97
+ this.registryTools = options.registryTools;
98
+ this.sourceIdsByTool = buildSourceIdsByTool(config);
99
+ }
100
+ /** adapter 注入的编排器(节点形态同款回退链:deps 显式 > adapter 注入 > inner 直执) */
101
+ attachedOrchestrator;
102
+ /** 宿主装配(LangGraphAdapter)注入图级 ToolOrchestrator——kb 派发走与 tool 节点同一编排 */
103
+ attachToolOrchestrator(orchestrator) {
104
+ if (!this.deps.toolOrchestrator)
105
+ this.attachedOrchestrator = orchestrator;
106
+ }
107
+ resolveOrchestrator() {
108
+ if (this.deps.toolOrchestrator)
109
+ return this.deps.toolOrchestrator;
110
+ if (this.attachedOrchestrator)
111
+ return this.attachedOrchestrator;
112
+ // 兜底:kb 调用直经 inner 执行器(与 tool 节点同一条执行链,缺省编排层记账)
113
+ this.attachedOrchestrator = {
114
+ executeAll: async (reqs) => Promise.all(reqs.map(async (r) => {
115
+ try {
116
+ return await this.inner.execute(r);
117
+ }
118
+ catch (err) {
119
+ return {
120
+ toolCallId: r.toolCallId,
121
+ toolName: r.toolName,
122
+ content: '',
123
+ success: false,
124
+ error: err instanceof Error ? err.message : String(err),
125
+ };
126
+ }
127
+ })),
128
+ };
129
+ return this.attachedOrchestrator;
130
+ }
131
+ async execute(req) {
132
+ // 图声明的检索面(tool 节点 config.retrievalSources)覆盖装配期配置——图即真相;
133
+ // 图声明的 totalK(config.totalK)同为该请求缺省(调用级 topK 参数在下方仍可覆盖)
134
+ const faceConfig = {
135
+ ...this.config,
136
+ ...(req.ragSourcesConfig ? { retrievalSources: req.ragSourcesConfig } : {}),
137
+ ...(req.ragTotalK !== undefined ? { totalK: req.ragTotalK } : {}),
138
+ };
139
+ const gateMap = req.ragSourcesConfig ? buildSourceIdsByTool(faceConfig) : this.sourceIdsByTool;
140
+ if (req.toolName !== RAG_SEARCH_TOOL_NAME) {
141
+ // 会话勾选激活时,目录内工具按勾选放行(主 LLM 直调与 rag_search 内层同规则):
142
+ // 一型多源(同工具多资源)任一 id 勾选即放行;目录外工具不受影响
143
+ if (req.ragSources !== undefined) {
144
+ const ids = gateMap.get(req.toolName);
145
+ if (ids && !ids.some((id) => req.ragSources?.includes(id))) {
146
+ // 失败返回必须带 error:上层失败时丢弃 content 只读 error,缺 error 会被渲染成 unknown error
147
+ return {
148
+ toolCallId: req.toolCallId,
149
+ toolName: req.toolName,
150
+ content: `[Tool Error] 数据源(${ids.join('、')})本次会话未勾选启用(ragSources),如需使用请在面板勾选该数据源`,
151
+ success: false,
152
+ error: `数据源(${ids.join('、')})本次会话未勾选启用(ragSources),如需使用请在面板勾选该数据源`,
153
+ };
154
+ }
155
+ }
156
+ return this.inner.execute(req);
157
+ }
158
+ const query = String(req.arguments['query'] ?? '');
159
+ if (!query.trim()) {
160
+ return {
161
+ toolCallId: req.toolCallId,
162
+ toolName: req.toolName,
163
+ content: '[Tool Error] rag_search 缺少必填参数 query',
164
+ success: false,
165
+ error: 'rag_search 缺少必填参数 query',
166
+ };
167
+ }
168
+ const topK = typeof req.arguments['topK'] === 'number' ? req.arguments['topK'] : undefined;
169
+ const config = topK !== undefined ? { ...faceConfig, totalK: topK } : faceConfig;
170
+ // 会话级控制(tool 节点从 GraphState 转递,与节点形态同语义):
171
+ // 勾选按 id 过滤检索面(资源目录同步收窄);请求 ragScope 与 local-file
172
+ // 白名单合并(声明过但未勾 = fileIds:[] 显式关本地)
173
+ const { toolScope, sourceCatalog, hasLocal, hasCatalog, localDirs, localDirsAll } = resolveRagSources(faceConfig, req.ragSources);
174
+ // 图声明了 sources = 完整检索面:无 local-file 条目则本地源整体不可用(不进工具表)。
175
+ // 本地选择权与 RagNode 同规则:请求带 ragScope.dirPaths 时以声明全量面为交集基准
176
+ const localEnabled = hasCatalog ? hasLocal : true;
177
+ const ragScope = localEnabled
178
+ ? mergeLocalDirs(hasLocal, req.ragScope?.dirPaths !== undefined ? localDirsAll : localDirs, req.ragScope)
179
+ : undefined;
180
+ // 内层工具表优先用本轮 loadedTools(请求级,含会话勾选后的完整注册面);
181
+ // 剥掉 rag_search 自身防递归;缺省回退构造期 registryTools 快照
182
+ const reqTools = req.loadedTools;
183
+ const innerTools = reqTools && RAG_SEARCH_TOOL_NAME in reqTools
184
+ ? Object.fromEntries(Object.entries(reqTools).filter(([n]) => n !== RAG_SEARCH_TOOL_NAME))
185
+ : (reqTools ?? this.registryTools);
186
+ const { apiTools, kbToolNames, missingToolNames, excludedConfirmToolNames } = buildRagTools(config, innerTools, localEnabled ? this.deps.retrievers : undefined, { toolScope, sourceCatalog, localEnabled });
187
+ if (missingToolNames.length > 0 || excludedConfirmToolNames.length > 0) {
188
+ return {
189
+ toolCallId: req.toolCallId,
190
+ toolName: req.toolName,
191
+ content: `[Tool Error] rag_search 检索源 tools 引用了不可用工具: ${[
192
+ ...missingToolNames,
193
+ ...excludedConfirmToolNames,
194
+ ].join(', ')}`,
195
+ success: false,
196
+ error: `rag_search 检索源 tools 引用了不可用工具: ${[...missingToolNames, ...excludedConfirmToolNames].join(', ')}`,
197
+ };
198
+ }
199
+ // 已知限制(与图级工具派发的差异):tool 节点/llm 链路把 state.executionContext
200
+ // (含 delegationToken)透传给 ToolOrchestrator.executeAll/executeSingle,治理断言
201
+ // (assertToolPermission)可命中;工具形态的内层 kb 派发只能从 ToolCallRequest 取参,
202
+ // 而该结构无执行上下文转递位,此处未传 executionContext → orchestrator 因
203
+ // delegationToken 缺失跳过治理断言。后续 ToolCallRequest 增加执行上下文转递后此处透传。
204
+ const loop = await runReactLoop({
205
+ nodeId: `rag-search:${req.toolCallId}`,
206
+ query,
207
+ history: [], // 主 LLM 已整定 query,无需对话上下文(§5.4)
208
+ config,
209
+ apiTools,
210
+ kbToolNames,
211
+ llmProvider: this.deps.llmProvider,
212
+ toolOrchestrator: this.resolveOrchestrator(),
213
+ retrievers: this.deps.retrievers,
214
+ loadedTools: innerTools,
215
+ authContext: req.authContext ?? {},
216
+ userId: req.authContext?.userId ?? '',
217
+ ragScope,
218
+ emitEvent: this.deps.emitEvent,
219
+ });
220
+ return {
221
+ toolCallId: req.toolCallId,
222
+ toolName: req.toolName,
223
+ // 空结果是合法业务结果("查了,没查到"),不是执行失败:success 恒 true,
224
+ // 空手体现在 content 文案。此前 success:!loop.empty 且无 error 字段,会被
225
+ // tool-node 渲染成 "[Tool Error] unknown error",误导主 LLM 判定服务故障。
226
+ content: loop.contextText || `[rag] 未找到高相关结果(query: ${query})。可换个关键词重试,或确认目标内容已入库。`,
227
+ success: true,
228
+ };
229
+ }
230
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * react-loop — RagNode 内部 ReAct 循环(纯逻辑,不依赖 LangGraph)
3
+ *
4
+ * 输入输出均为普通对象,可脱离图引擎单独单测。
5
+ * 循环:LLM 决策(原生 function-calling)→ 三路派发 → 累积 perSource →
6
+ * 达终止条件后 fuse + denoise(近重去重 + LLM 判定 + 句子抽取)→ 输出;
7
+ * 判定存活集为空且未重写过 → QUERY_REWRITE 重写 query 清空重查(仅一次,§5.4)。
8
+ * 空转换源反馈:上一轮零产出 / 产出全被滤除的工具,下一轮决策上下文注入换源提示
9
+ * (只进决策输入,不落消息历史与最终输出),配合 RAG_REACT 行为规则防同源反复重查。
10
+ * LLM 永远只"点名"(name + arguments),执行全在代码侧:
11
+ * 路 A local_search → 进程内直连 IRetriever(结构化 RetrievalDoc[],零转换)
12
+ * 路 B 注册工具 → ToolOrchestrator(权限 → 路由 → 执行器),字符串结果归一化
13
+ * 路 C 未知工具 → 错误 tool 消息回给 LLM(不中断循环)
14
+ *
15
+ * @see design-v2 §5.4 / §6
16
+ */
17
+ import type { ILLMProvider, LLMToolDefinition } from '../llm-node.js';
18
+ import type { GraphMessage } from '../../contracts/executable-graph.js';
19
+ import type { AuthContext, ToolOrchestrator } from '../../../tool/tool-orchestrator.js';
20
+ import type { LoadedToolMap } from '../../../tool/loaded-tool-definition.js';
21
+ import type { ExecutionContext } from '@gt-fe/eap-contracts';
22
+ import type { ITraceCollector } from '../../../contracts/trace-collector.js';
23
+ import type { IRetriever, RagEventSink, RagNodeConfig, RagScope, RagTokenUsage } from './types.js';
24
+ export interface RagLoopInput {
25
+ nodeId: string;
26
+ query: string;
27
+ /** 对话上下文(首轮整定指代/省略用) */
28
+ history: GraphMessage[];
29
+ config: RagNodeConfig;
30
+ /** 壳已圈定好的工具表(①注册工具 + ②local_search + ③rag_finish) */
31
+ apiTools: LLMToolDefinition[];
32
+ kbToolNames: Set<string>;
33
+ llmProvider: ILLMProvider;
34
+ toolOrchestrator?: ToolOrchestrator;
35
+ retrievers?: IRetriever[];
36
+ loadedTools?: LoadedToolMap;
37
+ authContext: AuthContext;
38
+ executionContext?: ExecutionContext;
39
+ forceToolServer?: string[];
40
+ userId: string;
41
+ /** 请求级检索范围(前端圈定 fileIds/dirPaths 并集;缺省不限)——只作用于本地检索源 */
42
+ ragScope?: RagScope;
43
+ /** rag.* 事件出口(SSE 旁路,缺省静默;adapter 接线 streamSink) */
44
+ emitEvent?: RagEventSink;
45
+ /** Trace 采集器 + 执行流 ID(缺省不建 span,照 ToolNode 模式:resolveTraceId) */
46
+ traceCollector?: ITraceCollector;
47
+ traceId?: string;
48
+ }
49
+ export interface RagLoopOutput {
50
+ /** 降噪上下文(近重去重 + LLM 判定 + 句子抽取后的拼装结果) */
51
+ contextText: string;
52
+ /** 空手标识(检索全失败/全被滤,含重查后仍空) */
53
+ empty: boolean;
54
+ iterations: number;
55
+ tokenUsage: RagTokenUsage[];
56
+ }
57
+ export declare function runReactLoop(input: RagLoopInput): Promise<RagLoopOutput>;