@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
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill 子图可选隔离(实验口)。仅节点 `isolateContext: true`。
|
|
3
|
+
* 只把本轮输入给子图,回写最后一条带正文的 assistant;没有则不回写。
|
|
4
|
+
* 隔离时不写父 session Checkpoint。
|
|
5
|
+
* 子图内 HITL 未按隔离线程设计:skillIsolateContext 为 true 时禁止 interrupt。
|
|
6
|
+
*/
|
|
7
|
+
import type { GraphMessage } from '../contracts/executable-graph.js';
|
|
8
|
+
import type { GraphNode } from '../contracts/executable-graph.js';
|
|
9
|
+
export declare const SKILL_ISOLATE_HITL_MESSAGE = "Skill isolateContext \u4E0D\u80FD\u4E0E\u5B50\u56FE\u5185\u4EBA\u5DE5\u786E\u8BA4\u4E00\u8D77\u4F7F\u7528\u3002\u5173\u6389 isolate\uFF0C\u6216\u4E0D\u8981\u5728\u8BE5 Skill \u91CC\u4F7F\u7528 require_confirmation / beforeTool ask\u3002";
|
|
10
|
+
/** 只认节点配置。进程环境变量不会给所有 subgraph 开隔离。 */
|
|
11
|
+
export declare function isSkillContextIsolated(node: GraphNode | undefined): boolean;
|
|
12
|
+
export declare function isolatedSkillMessages(parentMessages: GraphMessage[], skillInput: unknown): GraphMessage[];
|
|
13
|
+
/** 只回写最后一条带正文的 assistant;没有则空数组,避免把 tool 行丢给父图。 */
|
|
14
|
+
export declare function isolatedSkillOutput(added: GraphMessage[]): GraphMessage[];
|
|
15
|
+
export declare function isIsolatedSkillState(state: Record<string, unknown> | undefined): boolean;
|
|
16
|
+
/** 隔离子图即将 HITL 时拒绝,避免写错父线程 Checkpoint。 */
|
|
17
|
+
export declare function assertIsolatedSkillAllowsHitl(state: Record<string, unknown> | undefined): void;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill 子图可选隔离(实验口)。仅节点 `isolateContext: true`。
|
|
3
|
+
* 只把本轮输入给子图,回写最后一条带正文的 assistant;没有则不回写。
|
|
4
|
+
* 隔离时不写父 session Checkpoint。
|
|
5
|
+
* 子图内 HITL 未按隔离线程设计:skillIsolateContext 为 true 时禁止 interrupt。
|
|
6
|
+
*/
|
|
7
|
+
import { AppError, ErrorCodes } from '@gt-fe/eap-contracts';
|
|
8
|
+
export const SKILL_ISOLATE_HITL_MESSAGE = 'Skill isolateContext 不能与子图内人工确认一起使用。关掉 isolate,或不要在该 Skill 里使用 require_confirmation / beforeTool ask。';
|
|
9
|
+
/** 只认节点配置。进程环境变量不会给所有 subgraph 开隔离。 */
|
|
10
|
+
export function isSkillContextIsolated(node) {
|
|
11
|
+
return node?.config?.['isolateContext'] === true;
|
|
12
|
+
}
|
|
13
|
+
export function isolatedSkillMessages(parentMessages, skillInput) {
|
|
14
|
+
let text = '';
|
|
15
|
+
if (typeof skillInput === 'string' && skillInput.trim()) {
|
|
16
|
+
text = skillInput.trim();
|
|
17
|
+
}
|
|
18
|
+
else if (skillInput && typeof skillInput === 'object' && 'text' in skillInput) {
|
|
19
|
+
text = String(skillInput.text ?? '').trim();
|
|
20
|
+
}
|
|
21
|
+
if (!text) {
|
|
22
|
+
for (let i = parentMessages.length - 1; i >= 0; i--) {
|
|
23
|
+
if (parentMessages[i]?.role === 'user' && parentMessages[i]?.content) {
|
|
24
|
+
text = parentMessages[i].content;
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return [{ role: 'user', content: text || '' }];
|
|
30
|
+
}
|
|
31
|
+
/** 只回写最后一条带正文的 assistant;没有则空数组,避免把 tool 行丢给父图。 */
|
|
32
|
+
export function isolatedSkillOutput(added) {
|
|
33
|
+
for (let i = added.length - 1; i >= 0; i--) {
|
|
34
|
+
if (added[i]?.role === 'assistant' && added[i]?.content) {
|
|
35
|
+
return [{ role: 'assistant', content: added[i].content }];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
export function isIsolatedSkillState(state) {
|
|
41
|
+
return state?.['skillIsolateContext'] === true;
|
|
42
|
+
}
|
|
43
|
+
/** 隔离子图即将 HITL 时拒绝,避免写错父线程 Checkpoint。 */
|
|
44
|
+
export function assertIsolatedSkillAllowsHitl(state) {
|
|
45
|
+
if (!isIsolatedSkillState(state))
|
|
46
|
+
return;
|
|
47
|
+
throw new AppError(ErrorCodes.GRAPH_EXECUTION_ERROR, SKILL_ISOLATE_HITL_MESSAGE, {
|
|
48
|
+
exposeMessage: true,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 将 Skill DSL 编译为子图节点函数,嵌入父 Graph 执行。
|
|
5
5
|
* 子图作为独立 StateGraph 编译,父图通过节点函数调用子图并合并输出。
|
|
6
|
+
* Skill 子图不得再含 type=subgraph(G-3 / B14)。
|
|
6
7
|
*
|
|
7
8
|
* @see design.md §1.4 子图嵌套: Skill作为子图嵌入主Graph
|
|
8
9
|
* @see requirements.md Requirement 1 AC2 — 子图嵌套(Skill作为子图)
|
|
@@ -24,16 +25,20 @@ type LangGraphState = Record<string, unknown>;
|
|
|
24
25
|
* 4. 执行时,子图内部运行完整的 graph invoke 流程,输出合并回父图 state
|
|
25
26
|
*/
|
|
26
27
|
export declare class SubGraphCompiler {
|
|
28
|
+
private readonly auditReporter?;
|
|
27
29
|
private readonly adapter;
|
|
28
30
|
private readonly governanceClient?;
|
|
29
31
|
private readonly skillAuditRecorder?;
|
|
30
|
-
constructor(adapter?: LangGraphAdapter, governanceClient?: IGovernanceClient, skillAuditRecorder?: SkillAuditRecorder);
|
|
32
|
+
constructor(adapter?: LangGraphAdapter, governanceClient?: IGovernanceClient, skillAuditRecorder?: SkillAuditRecorder, auditReporter?: import("../../governance/audit-reporter.js").IAuditReporter | undefined);
|
|
31
33
|
/**
|
|
32
34
|
* 将 Skill DSL 编译为可嵌入父图的 NodeFunction
|
|
33
35
|
*
|
|
34
36
|
* @param dsl - Skill 的 GraphDSL 定义
|
|
35
37
|
* @param skillId - Skill 资源 ID(用于 checkPermission skill:use)
|
|
36
38
|
*/
|
|
37
|
-
compileSubGraph(dsl: GraphDSL, skillId?: string
|
|
39
|
+
compileSubGraph(dsl: GraphDSL, skillId?: string, options?: {
|
|
40
|
+
isolateContext?: boolean;
|
|
41
|
+
parentNodeId?: string;
|
|
42
|
+
}): NodeFunction<LangGraphState>;
|
|
38
43
|
}
|
|
39
44
|
export {};
|
|
@@ -3,18 +3,22 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 将 Skill DSL 编译为子图节点函数,嵌入父 Graph 执行。
|
|
5
5
|
* 子图作为独立 StateGraph 编译,父图通过节点函数调用子图并合并输出。
|
|
6
|
+
* Skill 子图不得再含 type=subgraph(G-3 / B14)。
|
|
6
7
|
*
|
|
7
8
|
* @see design.md §1.4 子图嵌套: Skill作为子图嵌入主Graph
|
|
8
9
|
* @see requirements.md Requirement 1 AC2 — 子图嵌套(Skill作为子图)
|
|
9
10
|
*/
|
|
11
|
+
import { AppError, ErrorCodes } from '@gt-fe/eap-contracts';
|
|
10
12
|
import { assertSkillPermission } from '../../governance/permission-guard.js';
|
|
11
13
|
import { LangGraphAdapter } from './langgraph.adapter.js';
|
|
14
|
+
import { assertSkillGraphHasNoSubgraphNodes } from './graph-nesting.js';
|
|
12
15
|
import { resolveExecutionTrace, warnTraceSpanFailure, } from '../execution-trace-context.js';
|
|
13
16
|
import { skillSpanFields, skillTraceIdentity } from './skill-entry-span.js';
|
|
14
17
|
import { filterLoadedToolsForSkill, } from '../../tool/system-prompt-builder.js';
|
|
15
18
|
import { unwrapHitlPaused } from '../hitl/hitl-handler.js';
|
|
16
19
|
import { pickContextFields } from '../../token/context-window.js';
|
|
17
20
|
import { assertSkillIoIfEnabled, coerceSkillInputFromMessages, } from '../../registry/skill-io-guard.js';
|
|
21
|
+
import { isolatedSkillMessages, isolatedSkillOutput, SKILL_ISOLATE_HITL_MESSAGE, } from './skill-isolate.js';
|
|
18
22
|
/**
|
|
19
23
|
* SubGraphCompiler — 子图编译器
|
|
20
24
|
*
|
|
@@ -25,10 +29,12 @@ import { assertSkillIoIfEnabled, coerceSkillInputFromMessages, } from '../../reg
|
|
|
25
29
|
* 4. 执行时,子图内部运行完整的 graph invoke 流程,输出合并回父图 state
|
|
26
30
|
*/
|
|
27
31
|
export class SubGraphCompiler {
|
|
32
|
+
auditReporter;
|
|
28
33
|
adapter;
|
|
29
34
|
governanceClient;
|
|
30
35
|
skillAuditRecorder;
|
|
31
|
-
constructor(adapter, governanceClient, skillAuditRecorder) {
|
|
36
|
+
constructor(adapter, governanceClient, skillAuditRecorder, auditReporter) {
|
|
37
|
+
this.auditReporter = auditReporter;
|
|
32
38
|
this.adapter = adapter ?? new LangGraphAdapter();
|
|
33
39
|
this.governanceClient = governanceClient;
|
|
34
40
|
this.skillAuditRecorder = skillAuditRecorder;
|
|
@@ -39,14 +45,23 @@ export class SubGraphCompiler {
|
|
|
39
45
|
* @param dsl - Skill 的 GraphDSL 定义
|
|
40
46
|
* @param skillId - Skill 资源 ID(用于 checkPermission skill:use)
|
|
41
47
|
*/
|
|
42
|
-
compileSubGraph(dsl, skillId) {
|
|
48
|
+
compileSubGraph(dsl, skillId, options) {
|
|
49
|
+
assertSkillGraphHasNoSubgraphNodes(dsl, skillId ?? dsl.name);
|
|
43
50
|
const adapter = this.adapter;
|
|
51
|
+
const isolateContext = options?.isolateContext === true;
|
|
44
52
|
const resolvedSkillId = skillId ?? dsl.name ?? '';
|
|
45
53
|
const subGraphNodeFn = async (state) => {
|
|
46
|
-
const executionContext = state['executionContext']
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
54
|
+
const executionContext = state['executionContext'] ?? {
|
|
55
|
+
userId: String(state['userId'] ?? ''),
|
|
56
|
+
callerType: 'user',
|
|
57
|
+
roles: [],
|
|
58
|
+
permissions: [],
|
|
59
|
+
delegationToken: String(state['delegationToken'] ?? ''),
|
|
60
|
+
correlationId: String(state['correlationId'] ?? ''),
|
|
61
|
+
};
|
|
62
|
+
await assertSkillPermission(this.governanceClient, executionContext, resolvedSkillId, {
|
|
63
|
+
auditReporter: this.auditReporter,
|
|
64
|
+
});
|
|
50
65
|
const existingSpanId = typeof state['skillSpanId'] === 'string' && state['skillSpanId']
|
|
51
66
|
? state['skillSpanId']
|
|
52
67
|
: '';
|
|
@@ -119,26 +134,32 @@ export class SubGraphCompiler {
|
|
|
119
134
|
if (skillInfo) {
|
|
120
135
|
assertSkillIoIfEnabled(skillInfo, skillInput);
|
|
121
136
|
}
|
|
137
|
+
const parentMessages = messages.map((msg) => ({
|
|
138
|
+
role: msg['role'] ?? 'user',
|
|
139
|
+
content: msg['content'] ?? '',
|
|
140
|
+
toolCalls: msg['tool_calls'] ?? msg['toolCalls'],
|
|
141
|
+
...(typeof msg['toolCallId'] === 'string' ? { toolCallId: msg['toolCallId'] } : {}),
|
|
142
|
+
...(typeof msg['toolName'] === 'string' ? { toolName: msg['toolName'] } : {}),
|
|
143
|
+
...(typeof msg['success'] === 'boolean' ? { success: msg['success'] } : {}),
|
|
144
|
+
...(typeof msg['reasoningContent'] === 'string'
|
|
145
|
+
? { reasoningContent: msg['reasoningContent'] }
|
|
146
|
+
: {}),
|
|
147
|
+
}));
|
|
148
|
+
const subMessagesIn = isolateContext
|
|
149
|
+
? isolatedSkillMessages(parentMessages, skillInput)
|
|
150
|
+
: parentMessages;
|
|
122
151
|
const subGraphState = {
|
|
123
|
-
messages:
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
toolCalls: msg['tool_calls'] ?? msg['toolCalls'],
|
|
127
|
-
...(typeof msg['toolCallId'] === 'string' ? { toolCallId: msg['toolCallId'] } : {}),
|
|
128
|
-
...(typeof msg['toolName'] === 'string' ? { toolName: msg['toolName'] } : {}),
|
|
129
|
-
...(typeof msg['success'] === 'boolean' ? { success: msg['success'] } : {}),
|
|
130
|
-
...(typeof msg['reasoningContent'] === 'string'
|
|
131
|
-
? { reasoningContent: msg['reasoningContent'] }
|
|
132
|
-
: {}),
|
|
133
|
-
})),
|
|
134
|
-
// 子图与父图共用 session thread;入口必须 __set,否则 checkpointer 会把父图 messages 再 append 一遍
|
|
135
|
-
replaceCheckpointMessages: true,
|
|
152
|
+
messages: subMessagesIn,
|
|
153
|
+
// 默认同父 session thread;隔离时不写父 Checkpoint,避免短历史覆盖父对话
|
|
154
|
+
replaceCheckpointMessages: !isolateContext,
|
|
136
155
|
userId,
|
|
137
156
|
delegationToken,
|
|
138
157
|
agentCode,
|
|
139
158
|
taskId: state['taskId'],
|
|
140
159
|
correlationId: state['correlationId'],
|
|
141
|
-
sessionId: state['sessionId'],
|
|
160
|
+
sessionId: isolateContext ? undefined : state['sessionId'],
|
|
161
|
+
// ToolNode 用此标志在 interrupt 前拒绝 HITL
|
|
162
|
+
skillIsolateContext: isolateContext || undefined,
|
|
142
163
|
executionContext: state['executionContext'],
|
|
143
164
|
loadedTools: scopedTools,
|
|
144
165
|
loadedSkills: state['loadedSkills'],
|
|
@@ -192,19 +213,26 @@ export class SubGraphCompiler {
|
|
|
192
213
|
...(reasoningContent ? { reasoningContent } : {}),
|
|
193
214
|
},
|
|
194
215
|
});
|
|
195
|
-
const added = subMessages.slice(
|
|
196
|
-
const outputMessages =
|
|
197
|
-
? added
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
216
|
+
const added = subMessages.slice(subMessagesIn.length);
|
|
217
|
+
const outputMessages = isolateContext
|
|
218
|
+
? isolatedSkillOutput(added.length > 0
|
|
219
|
+
? added
|
|
220
|
+
: result.output
|
|
221
|
+
? [{ role: 'assistant', content: result.output }]
|
|
222
|
+
: [])
|
|
223
|
+
: added.length > 0
|
|
224
|
+
? added
|
|
225
|
+
: result.output
|
|
226
|
+
? [{ role: 'assistant', content: result.output }]
|
|
227
|
+
: [];
|
|
201
228
|
const perNode = result.tokenUsage?.perNode;
|
|
202
229
|
const nestedLastSpanId = (typeof result.finalState?.lastSpanId === 'string' && result.finalState.lastSpanId) ||
|
|
203
230
|
skillSpanId ||
|
|
204
231
|
undefined;
|
|
205
232
|
return {
|
|
206
233
|
messages: outputMessages,
|
|
207
|
-
currentNode:
|
|
234
|
+
currentNode: options?.parentNodeId
|
|
235
|
+
|| (dsl.name ? `subgraph:${dsl.name}` : 'subgraph'),
|
|
208
236
|
...(nestedLastSpanId ? { lastSpanId: nestedLastSpanId } : {}),
|
|
209
237
|
...(Array.isArray(perNode) && perNode.length > 0
|
|
210
238
|
? {
|
|
@@ -221,18 +249,26 @@ export class SubGraphCompiler {
|
|
|
221
249
|
}
|
|
222
250
|
catch (err) {
|
|
223
251
|
const hitlPaused = unwrapHitlPaused(err);
|
|
224
|
-
|
|
252
|
+
// 未隔离:HITL 暂停原样上抛。隔离:转 GRAPH_EXECUTION_ERROR,走 skill.failed,不当 waiting_hitl。
|
|
253
|
+
if (hitlPaused && !isolateContext) {
|
|
225
254
|
throw err;
|
|
226
255
|
}
|
|
256
|
+
const failErr = hitlPaused && isolateContext
|
|
257
|
+
? new AppError(ErrorCodes.GRAPH_EXECUTION_ERROR, SKILL_ISOLATE_HITL_MESSAGE, {
|
|
258
|
+
exposeMessage: true,
|
|
259
|
+
cause: err,
|
|
260
|
+
})
|
|
261
|
+
: err;
|
|
262
|
+
const failMessage = failErr instanceof Error ? failErr.message : String(failErr);
|
|
227
263
|
await auditCtx?.finish({
|
|
228
264
|
status: 'failed',
|
|
229
|
-
errorMessage:
|
|
265
|
+
errorMessage: failMessage,
|
|
230
266
|
});
|
|
231
267
|
if (traceCollector && skillSpanId) {
|
|
232
268
|
try {
|
|
233
269
|
await traceCollector.completeNode(skillSpanId, {
|
|
234
270
|
status: 'failed',
|
|
235
|
-
error:
|
|
271
|
+
error: failMessage,
|
|
236
272
|
});
|
|
237
273
|
}
|
|
238
274
|
catch (completeErr) {
|
|
@@ -247,10 +283,10 @@ export class SubGraphCompiler {
|
|
|
247
283
|
payload: {
|
|
248
284
|
skillId: resolvedSkillId,
|
|
249
285
|
skillName,
|
|
250
|
-
error:
|
|
286
|
+
error: failMessage,
|
|
251
287
|
},
|
|
252
288
|
});
|
|
253
|
-
throw
|
|
289
|
+
throw failErr;
|
|
254
290
|
}
|
|
255
291
|
};
|
|
256
292
|
return subGraphNodeFn;
|
|
@@ -7,8 +7,14 @@
|
|
|
7
7
|
* @see design.md §1.4 StateGraph Contract
|
|
8
8
|
* @see requirements.md Requirement 1 AC1-AC2
|
|
9
9
|
*/
|
|
10
|
-
import type { GraphDSL as BaseGraphDSL, GraphNode as BaseGraphNode, GraphEdge as BaseGraphEdge, GraphNodeType, GraphConfig } from '@gt-fe/eap-contracts';
|
|
11
|
-
export type {
|
|
10
|
+
import type { GraphDSL as BaseGraphDSL, GraphNode as BaseGraphNode, GraphEdge as BaseGraphEdge, GraphNodeType as ContractsGraphNodeType, GraphConfig } from '@gt-fe/eap-contracts';
|
|
11
|
+
export type { GraphConfig };
|
|
12
|
+
/**
|
|
13
|
+
* GraphNodeType — runtime 侧扩展联合:contracts 官方包的 'rag' 类型尚未合入
|
|
14
|
+
* (客户 MR 跟进中),在此扩展使官方 contracts 零 patch 可用;
|
|
15
|
+
* contracts 合入后此扩展退化为无害超集,可直接删除回归 re-export。
|
|
16
|
+
*/
|
|
17
|
+
export type GraphNodeType = ContractsGraphNodeType | 'rag';
|
|
12
18
|
/**
|
|
13
19
|
* RuntimeGraphConfig — Runtime 层扩展的 Graph 配置
|
|
14
20
|
*
|
|
@@ -26,7 +32,9 @@ export interface RuntimeGraphConfig extends GraphConfig {
|
|
|
26
32
|
* - HITL 标记(interrupt_before / interrupt_after)
|
|
27
33
|
* - 描述信息(调试/Trace 用)
|
|
28
34
|
*/
|
|
29
|
-
export interface GraphNode extends BaseGraphNode {
|
|
35
|
+
export interface GraphNode extends Omit<BaseGraphNode, 'type'> {
|
|
36
|
+
/** 节点类型(contracts 联合 + runtime 扩展 'rag',见 GraphNodeType) */
|
|
37
|
+
type: GraphNodeType;
|
|
30
38
|
/** 节点显示名称(调试/Trace 用) */
|
|
31
39
|
label?: string;
|
|
32
40
|
/** 节点描述 */
|
|
@@ -37,8 +45,6 @@ export interface GraphNode extends BaseGraphNode {
|
|
|
37
45
|
interrupt?: NodeInterruptConfig;
|
|
38
46
|
/** 子图引用 ID(type=subgraph 时必填) */
|
|
39
47
|
subgraphId?: string;
|
|
40
|
-
/** 并行分支定义(type=parallel 时使用) */
|
|
41
|
-
branches?: string[];
|
|
42
48
|
}
|
|
43
49
|
/**
|
|
44
50
|
* GraphEdge — 基础边定义
|
|
@@ -183,6 +189,10 @@ export interface GraphState {
|
|
|
183
189
|
executionContext?: PlatformExecutionContext;
|
|
184
190
|
/** ExecuteConfig.forceToolServer — 单次 invoke 强制走 Gateway 的 toolName */
|
|
185
191
|
forceToolServer?: string[];
|
|
192
|
+
/** 请求级检索范围(前端圈定 fileIds/dirPaths;rag 节点读取,仅本地检索源生效) */
|
|
193
|
+
ragScope?: import('../../graph/nodes/rag/types.js').RagScope;
|
|
194
|
+
/** 会话级数据源勾选(数据源 id 列表;undefined = 全部启用) */
|
|
195
|
+
ragSources?: string[];
|
|
186
196
|
/** GraphLoader 注入:toolName → 运行期 manifest 引用 */
|
|
187
197
|
loadedTools?: import('../../tool/loaded-tool-definition.js').LoadedToolMap;
|
|
188
198
|
/** GraphLoader 注入:已装配 Skill 列表 */
|
|
@@ -212,8 +222,20 @@ export interface GraphState {
|
|
|
212
222
|
* 不入 Checkpoint。
|
|
213
223
|
*/
|
|
214
224
|
traceCollector?: import('../../contracts/trace-collector.js').ITraceCollector;
|
|
225
|
+
/**
|
|
226
|
+
* 当前节点跑在 Skill 隔离子图内。为 true 时禁止 HITL interrupt
|
|
227
|
+
*(隔离不写父 session Checkpoint,确认态会丢)。
|
|
228
|
+
*/
|
|
229
|
+
skillIsolateContext?: boolean;
|
|
215
230
|
/** 当前 Skill 子图 createNode 返回的 span id;子图内 Tool span 的 parentNodeId */
|
|
216
231
|
skillSpanId?: string;
|
|
232
|
+
/**
|
|
233
|
+
* HITL 续跑:暂停时已 create 的 tool Span id。
|
|
234
|
+
* 按 hitlToolCallId 复用,禁止再 createNode。
|
|
235
|
+
*/
|
|
236
|
+
toolSpanId?: string;
|
|
237
|
+
/** 与 toolSpanId 配对的 toolCallId(同一 assistant 多 tool 时只复用这一次调用) */
|
|
238
|
+
hitlToolCallId?: string;
|
|
217
239
|
/** 最近一次 createNode 的 span id;Checkpoint 引用挂在此 Span 上 */
|
|
218
240
|
lastSpanId?: string;
|
|
219
241
|
/**
|
|
@@ -384,6 +384,8 @@ export class HitlHandler {
|
|
|
384
384
|
...cleaned,
|
|
385
385
|
hitlChoice: choice,
|
|
386
386
|
hitlRespondedBy: userId,
|
|
387
|
+
...(waiting.toolSpanId ? { toolSpanId: waiting.toolSpanId } : {}),
|
|
388
|
+
...(waiting.toolCallId ? { hitlToolCallId: waiting.toolCallId } : {}),
|
|
387
389
|
};
|
|
388
390
|
const now = new Date().toISOString();
|
|
389
391
|
await this.checkpointSaver.save(waiting.threadId, stripCheckpointCollectors({
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* @see requirements.md Requirement 1 AC6, Requirement 9
|
|
11
11
|
*/
|
|
12
12
|
import type { NodeRetryConfig, GraphMessage } from '../contracts/executable-graph.js';
|
|
13
|
+
import { type ExecutionInterceptors } from '../../execution/interceptors.js';
|
|
13
14
|
/**
|
|
14
15
|
* LLMNodeConfig — LLM 节点配置
|
|
15
16
|
*/
|
|
@@ -132,6 +133,7 @@ type LangGraphState = Record<string, unknown>;
|
|
|
132
133
|
* 5. 将 LLM 响应追加到 state.messages 并返回
|
|
133
134
|
*/
|
|
134
135
|
export declare class LLMNode {
|
|
136
|
+
private readonly interceptors?;
|
|
135
137
|
private readonly config;
|
|
136
138
|
private readonly provider;
|
|
137
139
|
private readonly retryConfig;
|
|
@@ -145,7 +147,7 @@ export declare class LLMNode {
|
|
|
145
147
|
* @param streaming - 是否启用流式输出(默认 false)
|
|
146
148
|
* @param onChunk - 流式模式下每个文本片段回调(用于 SSE response.chunk)
|
|
147
149
|
*/
|
|
148
|
-
constructor(nodeId: string, config: LLMNodeConfig, provider: ILLMProvider, streaming?: boolean, onChunk?: (chunk: string) => void);
|
|
150
|
+
constructor(nodeId: string, config: LLMNodeConfig, provider: ILLMProvider, streaming?: boolean, onChunk?: (chunk: string) => void, interceptors?: Pick<ExecutionInterceptors, "beforeModel" | "afterModel"> | undefined);
|
|
149
151
|
/**
|
|
150
152
|
* execute — StateGraph 节点函数
|
|
151
153
|
*
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
import { formatUnknownError } from '../../utils/format-error.js';
|
|
13
13
|
import { buildContextUsage, turnSectionChars } from '../../token/context-window.js';
|
|
14
14
|
import { isAbortError } from '../../tool/timeout.js';
|
|
15
|
+
import { emitRuntimeLog } from '../../common/logger.js';
|
|
16
|
+
import { decideBeforeModel, } from '../../execution/interceptors.js';
|
|
15
17
|
// ─────────────────────────────────────────────
|
|
16
18
|
// Default retry config
|
|
17
19
|
// ─────────────────────────────────────────────
|
|
@@ -38,6 +40,7 @@ const DEFAULT_RETRY_CONFIG = {
|
|
|
38
40
|
* 5. 将 LLM 响应追加到 state.messages 并返回
|
|
39
41
|
*/
|
|
40
42
|
export class LLMNode {
|
|
43
|
+
interceptors;
|
|
41
44
|
config;
|
|
42
45
|
provider;
|
|
43
46
|
retryConfig;
|
|
@@ -51,7 +54,8 @@ export class LLMNode {
|
|
|
51
54
|
* @param streaming - 是否启用流式输出(默认 false)
|
|
52
55
|
* @param onChunk - 流式模式下每个文本片段回调(用于 SSE response.chunk)
|
|
53
56
|
*/
|
|
54
|
-
constructor(nodeId, config, provider, streaming = false, onChunk) {
|
|
57
|
+
constructor(nodeId, config, provider, streaming = false, onChunk, interceptors) {
|
|
58
|
+
this.interceptors = interceptors;
|
|
55
59
|
this.nodeId = nodeId;
|
|
56
60
|
this.config = config;
|
|
57
61
|
this.provider = provider;
|
|
@@ -71,10 +75,30 @@ export class LLMNode {
|
|
|
71
75
|
const messages = this.extractMessages(state);
|
|
72
76
|
// 注入 system prompt
|
|
73
77
|
const messagesWithSystem = this.prependSystemPrompt(messages);
|
|
78
|
+
const modelInput = {
|
|
79
|
+
messages: messagesWithSystem,
|
|
80
|
+
config: this.config,
|
|
81
|
+
nodeId: this.nodeId,
|
|
82
|
+
};
|
|
83
|
+
const gate = await decideBeforeModel(this.interceptors?.beforeModel, modelInput);
|
|
84
|
+
if (gate.action === 'deny') {
|
|
85
|
+
throw new Error(gate.reason);
|
|
86
|
+
}
|
|
87
|
+
const messagesForModel = gate.messages ?? messagesWithSystem;
|
|
74
88
|
const startedAt = Date.now();
|
|
75
89
|
// 带重试的 LLM 调用
|
|
76
|
-
const response = await this.invokeWithRetry(
|
|
90
|
+
const response = await this.invokeWithRetry(messagesForModel);
|
|
77
91
|
const durationMs = Date.now() - startedAt;
|
|
92
|
+
try {
|
|
93
|
+
await this.interceptors?.afterModel?.({ ...modelInput, messages: messagesForModel }, response);
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
emitRuntimeLog('warn', 'runtime.intercept', 'afterModel failed', {
|
|
97
|
+
event: 'runtime.intercept.after_model_failed',
|
|
98
|
+
nodeId: this.nodeId,
|
|
99
|
+
error: err instanceof Error ? err.message : String(err),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
78
102
|
// 构建 assistant message(保留 reasoningContent 供后续 HTTP 回传)
|
|
79
103
|
const assistantMessage = {
|
|
80
104
|
role: 'assistant',
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dedup — 确定性去重(纯函数,不经 LLM)
|
|
3
|
+
*
|
|
4
|
+
* 精确去重(源内、fuse 前):sourceId:rawId + 归一化 content hash。
|
|
5
|
+
* 约束:入参须先经收集侧(react-loop pushDocs)消除"同 id 不同 content"撞号
|
|
6
|
+
* (重编号 `${rawId}#r${n}`)——本函数 id 命中即弃仅对真重复成立;
|
|
7
|
+
* fuse 的跨源键 sourceId:rawId 同样依赖该唯一性。
|
|
8
|
+
* 近重去重(融合后、降噪判定前,M4):字符 3-gram Jaccard 超 nearDupThreshold
|
|
9
|
+
* 判近重,保留序列靠前者(design-v2 §6.3 ①;跨源候选集几乎不重叠,属低频兜底)。
|
|
10
|
+
*/
|
|
11
|
+
import type { RetrievalDoc } from './types.js';
|
|
12
|
+
export declare function dedupExact(docs: RetrievalDoc[]): RetrievalDoc[];
|
|
13
|
+
/**
|
|
14
|
+
* 归一化内容相等(与 dedupExact 内容去重同口径):"重复"判定单一出处——
|
|
15
|
+
* 收集侧(react-loop pushDocs)跳过同 id 真重复时必须与融合前去重用同一口径,
|
|
16
|
+
* 两处口径分叉会导致同 id 同排版差异的文档一处跳过一处保留。
|
|
17
|
+
*/
|
|
18
|
+
export declare function sameContent(a: string, b: string): boolean;
|
|
19
|
+
/** 近重去重:字符 n-gram Jaccard 超阈值即判近重,保留序列靠前者 */
|
|
20
|
+
export declare function dedupNearDup(docs: RetrievalDoc[], threshold?: number): RetrievalDoc[];
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dedup — 确定性去重(纯函数,不经 LLM)
|
|
3
|
+
*
|
|
4
|
+
* 精确去重(源内、fuse 前):sourceId:rawId + 归一化 content hash。
|
|
5
|
+
* 约束:入参须先经收集侧(react-loop pushDocs)消除"同 id 不同 content"撞号
|
|
6
|
+
* (重编号 `${rawId}#r${n}`)——本函数 id 命中即弃仅对真重复成立;
|
|
7
|
+
* fuse 的跨源键 sourceId:rawId 同样依赖该唯一性。
|
|
8
|
+
* 近重去重(融合后、降噪判定前,M4):字符 3-gram Jaccard 超 nearDupThreshold
|
|
9
|
+
* 判近重,保留序列靠前者(design-v2 §6.3 ①;跨源候选集几乎不重叠,属低频兜底)。
|
|
10
|
+
*/
|
|
11
|
+
import { createHash } from 'node:crypto';
|
|
12
|
+
export function dedupExact(docs) {
|
|
13
|
+
const seenIds = new Set();
|
|
14
|
+
const seenContent = new Set();
|
|
15
|
+
const out = [];
|
|
16
|
+
for (const d of docs) {
|
|
17
|
+
const idKey = `${d.sourceId}:${d.rawId}`;
|
|
18
|
+
const contentKey = hashContent(d.content);
|
|
19
|
+
if (seenIds.has(idKey) || seenContent.has(contentKey))
|
|
20
|
+
continue;
|
|
21
|
+
seenIds.add(idKey);
|
|
22
|
+
seenContent.add(contentKey);
|
|
23
|
+
out.push(d);
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
function hashContent(content) {
|
|
28
|
+
// 归一化:压空白后取 hash,挡"内容相同仅排版不同"
|
|
29
|
+
const normalized = content.replace(/\s+/g, ' ').trim().toLowerCase();
|
|
30
|
+
return createHash('sha1').update(normalized).digest('hex');
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* 归一化内容相等(与 dedupExact 内容去重同口径):"重复"判定单一出处——
|
|
34
|
+
* 收集侧(react-loop pushDocs)跳过同 id 真重复时必须与融合前去重用同一口径,
|
|
35
|
+
* 两处口径分叉会导致同 id 同排版差异的文档一处跳过一处保留。
|
|
36
|
+
*/
|
|
37
|
+
export function sameContent(a, b) {
|
|
38
|
+
return hashContent(a) === hashContent(b);
|
|
39
|
+
}
|
|
40
|
+
/** 近重去重:字符 n-gram Jaccard 超阈值即判近重,保留序列靠前者 */
|
|
41
|
+
export function dedupNearDup(docs, threshold = 0.8) {
|
|
42
|
+
const kept = [];
|
|
43
|
+
const keptGrams = [];
|
|
44
|
+
for (const d of docs) {
|
|
45
|
+
const grams = charGrams(d.content);
|
|
46
|
+
let near = false;
|
|
47
|
+
for (const g of keptGrams) {
|
|
48
|
+
if (jaccard(grams, g) > threshold) {
|
|
49
|
+
near = true;
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (near)
|
|
54
|
+
continue;
|
|
55
|
+
kept.push(d);
|
|
56
|
+
keptGrams.push(grams);
|
|
57
|
+
}
|
|
58
|
+
return kept;
|
|
59
|
+
}
|
|
60
|
+
const NGRAM_SIZE = 3;
|
|
61
|
+
function charGrams(content) {
|
|
62
|
+
const norm = content.replace(/\s+/g, ' ').trim();
|
|
63
|
+
const grams = new Set();
|
|
64
|
+
for (let i = 0; i + NGRAM_SIZE <= norm.length; i++) {
|
|
65
|
+
grams.add(norm.slice(i, i + NGRAM_SIZE));
|
|
66
|
+
}
|
|
67
|
+
return grams;
|
|
68
|
+
}
|
|
69
|
+
function jaccard(a, b) {
|
|
70
|
+
if (a.size === 0 || b.size === 0)
|
|
71
|
+
return 0;
|
|
72
|
+
let inter = 0;
|
|
73
|
+
for (const x of a)
|
|
74
|
+
if (b.has(x))
|
|
75
|
+
inter++;
|
|
76
|
+
return inter / (a.size + b.size - inter);
|
|
77
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* denoise — 降噪管线(design-v2 §6.3,M4)
|
|
3
|
+
*
|
|
4
|
+
* 数万 token 候选压成数百 token,LLM 只做判定与选择、绝不产出正文:
|
|
5
|
+
* ① 近重去重(字符 n-gram Jaccard,纯函数,确定性优先)
|
|
6
|
+
* ② 一次结构化 LLM 判定(主力模型,temp 0)→ DenoiseVerdict[]
|
|
7
|
+
* { rawId, relevant, reason, keepSentences }——rawId 为复合键 sourceId:rawId
|
|
8
|
+
* (rawId 仅源内唯一;payload 标签、verdict 匹配、filtered 项三处必须同用复合键,
|
|
9
|
+
* 裸 id 跨源碰撞会让两份文档共享同一 verdict:错滤另一源、按别家句子编号截正文)
|
|
10
|
+
* ③ 代码按句子编号拼装原文(零改写:返回文本逐字出自原文句子)
|
|
11
|
+
*
|
|
12
|
+
* 防御策略(§6.5 误滤宁保守):
|
|
13
|
+
* verdict 调用或解析失败 / 文档缺 verdict 项 / keepSentences 无有效编号 → 整篇保留;
|
|
14
|
+
* 判定存活集为空 → react-loop 走 QUERY_REWRITE 重写兜底(本模块只报 verdictEmpty)。
|
|
15
|
+
*/
|
|
16
|
+
import type { ILLMProvider } from '../llm-node.js';
|
|
17
|
+
import type { DenoiseVerdict, RagTokenUsage, RetrievalDoc } from './types.js';
|
|
18
|
+
export declare const RAG_EMPTY_MARKER = "[rag] \u672A\u627E\u5230\u9AD8\u76F8\u5173\u7ED3\u679C";
|
|
19
|
+
/** 格式化(降噪后的)文档集为最终上下文 */
|
|
20
|
+
export declare function formatContext(query: string, docs: RetrievalDoc[]): string;
|
|
21
|
+
export interface DenoiseInput {
|
|
22
|
+
query: string;
|
|
23
|
+
/** 融合后的候选文档集(可能数万 token) */
|
|
24
|
+
docs: RetrievalDoc[];
|
|
25
|
+
llmProvider: ILLMProvider;
|
|
26
|
+
/** 主力模型(与决策同款,不分档) */
|
|
27
|
+
model: string;
|
|
28
|
+
nodeId: string;
|
|
29
|
+
/** 近重判定阈值,缺省 0.8 */
|
|
30
|
+
nearDupThreshold?: number;
|
|
31
|
+
tokenUsage: RagTokenUsage[];
|
|
32
|
+
}
|
|
33
|
+
export interface DenoiseResult {
|
|
34
|
+
/** 存活文档(正文已按 keepSentences 截取) */
|
|
35
|
+
docs: RetrievalDoc[];
|
|
36
|
+
contextText: string;
|
|
37
|
+
/** 判定后存活集是否为空(react-loop 据此触发重写兜底) */
|
|
38
|
+
verdictEmpty: boolean;
|
|
39
|
+
/** 被滤文档与原因(空结果重写定向 / rag.denoise 事件用);rawId 为复合键 sourceId:rawId(rewrite 归因整键匹配用) */
|
|
40
|
+
filtered: Array<{
|
|
41
|
+
rawId: string;
|
|
42
|
+
reason: string;
|
|
43
|
+
}>;
|
|
44
|
+
}
|
|
45
|
+
export declare function denoise(input: DenoiseInput): Promise<DenoiseResult>;
|
|
46
|
+
export declare function rewriteQuery(input: {
|
|
47
|
+
query: string;
|
|
48
|
+
reasons: string[];
|
|
49
|
+
llmProvider: ILLMProvider;
|
|
50
|
+
model: string;
|
|
51
|
+
nodeId: string;
|
|
52
|
+
tokenUsage: RagTokenUsage[];
|
|
53
|
+
}): Promise<string | undefined>;
|
|
54
|
+
/** 确定性分句:按中英文句终符与换行切,句符随句保留(零改写:句子为原文子串) */
|
|
55
|
+
export declare function splitSentences(content: string): string[];
|
|
56
|
+
/** 解析判定输出:剥代码围栏、截取首尾方括号;解析失败返回 undefined(保守放行全部)。
|
|
57
|
+
* rawId 不校验复合键形态——LLM 抄错(裸 id/截断)时 find 不中,该文档无 verdict 即整篇保留,仍走保守路径 */
|
|
58
|
+
export declare function parseVerdicts(text: string): DenoiseVerdict[] | undefined;
|