@librechat/agents 3.4.1 → 3.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +57 -33
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +81 -33
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/graphs/applyGraphRuntimeConfig.cjs +14 -0
- package/dist/cjs/graphs/applyGraphRuntimeConfig.cjs.map +1 -0
- package/dist/cjs/graphs/createGraph.cjs +15 -0
- package/dist/cjs/graphs/createGraph.cjs.map +1 -0
- package/dist/cjs/graphs/index.cjs +1 -0
- package/dist/cjs/hooks/createWorkspacePolicyHook.cjs +1 -1
- package/dist/cjs/main.cjs +20 -11
- package/dist/cjs/run.cjs +48 -38
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/session/JsonlSessionStore.cjs +2 -2
- package/dist/cjs/tools/SubagentTool.cjs +4 -4
- package/dist/cjs/tools/SubagentTool.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +13 -9
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/eagerEventExecution.cjs +1 -0
- package/dist/cjs/tools/local/LocalExecutionEngine.cjs +3 -3
- package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs +2 -2
- package/dist/cjs/tools/search/rerankers.cjs +154 -1
- package/dist/cjs/tools/search/rerankers.cjs.map +1 -1
- package/dist/cjs/tools/search/tool.cjs +4 -1
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs +601 -0
- package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs.map +1 -0
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +672 -343
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentReplay.cjs +3 -1
- package/dist/cjs/tools/subagent/SubagentReplay.cjs.map +1 -1
- package/dist/cjs/tools/subagent/childGraphConfig.cjs +278 -0
- package/dist/cjs/tools/subagent/childGraphConfig.cjs.map +1 -0
- package/dist/cjs/tools/subagent/index.cjs +1 -0
- package/dist/cjs/tools/subagent/runtimeLimits.cjs +5 -0
- package/dist/cjs/tools/subagent/runtimeLimits.cjs.map +1 -0
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +57 -33
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +81 -33
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/graphs/applyGraphRuntimeConfig.mjs +14 -0
- package/dist/esm/graphs/applyGraphRuntimeConfig.mjs.map +1 -0
- package/dist/esm/graphs/createGraph.mjs +15 -0
- package/dist/esm/graphs/createGraph.mjs.map +1 -0
- package/dist/esm/graphs/index.mjs +1 -0
- package/dist/esm/hooks/createWorkspacePolicyHook.mjs +1 -1
- package/dist/esm/main.mjs +12 -10
- package/dist/esm/run.mjs +48 -38
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/session/JsonlSessionStore.mjs +2 -2
- package/dist/esm/tools/SubagentTool.mjs +4 -4
- package/dist/esm/tools/SubagentTool.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +13 -9
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/eagerEventExecution.mjs +1 -1
- package/dist/esm/tools/local/LocalExecutionEngine.mjs +3 -3
- package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs +2 -2
- package/dist/esm/tools/search/rerankers.mjs +154 -1
- package/dist/esm/tools/search/rerankers.mjs.map +1 -1
- package/dist/esm/tools/search/tool.mjs +4 -1
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs +597 -0
- package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs.map +1 -0
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +672 -343
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentReplay.mjs +3 -1
- package/dist/esm/tools/subagent/SubagentReplay.mjs.map +1 -1
- package/dist/esm/tools/subagent/childGraphConfig.mjs +272 -0
- package/dist/esm/tools/subagent/childGraphConfig.mjs.map +1 -0
- package/dist/esm/tools/subagent/index.mjs +1 -0
- package/dist/esm/tools/subagent/runtimeLimits.mjs +5 -0
- package/dist/esm/tools/subagent/runtimeLimits.mjs.map +1 -0
- package/dist/types/agents/AgentContext.d.ts +1 -1
- package/dist/types/graphs/Graph.d.ts +7 -2
- package/dist/types/graphs/MultiAgentGraph.d.ts +7 -1
- package/dist/types/graphs/applyGraphRuntimeConfig.d.ts +3 -0
- package/dist/types/graphs/createGraph.d.ts +13 -0
- package/dist/types/graphs/graphFactory.d.ts +13 -0
- package/dist/types/graphs/index.d.ts +2 -0
- package/dist/types/run.d.ts +2 -2
- package/dist/types/tools/SubagentTool.d.ts +1 -1
- package/dist/types/tools/search/rerankers.d.ts +28 -0
- package/dist/types/tools/search/types.d.ts +38 -1
- package/dist/types/tools/subagent/SubagentExecutionRegistry.d.ts +171 -0
- package/dist/types/tools/subagent/SubagentExecutor.d.ts +32 -37
- package/dist/types/tools/subagent/SubagentReplay.d.ts +5 -1
- package/dist/types/tools/subagent/childGraphConfig.d.ts +37 -0
- package/dist/types/tools/subagent/index.d.ts +1 -1
- package/dist/types/tools/subagent/runtimeLimits.d.ts +4 -0
- package/dist/types/types/graph.d.ts +184 -19
- package/package.json +3 -1
- package/src/agents/AgentContext.ts +1 -1
- package/src/graphs/Graph.ts +111 -60
- package/src/graphs/MultiAgentGraph.ts +149 -47
- package/src/graphs/__tests__/MultiAgentGraph.test.ts +86 -0
- package/src/graphs/__tests__/composition.smoke.test.ts +152 -0
- package/src/graphs/__tests__/createGraph.test.ts +206 -0
- package/src/graphs/applyGraphRuntimeConfig.ts +25 -0
- package/src/graphs/createGraph.ts +32 -0
- package/src/graphs/graphFactory.ts +12 -0
- package/src/graphs/index.ts +2 -0
- package/src/run.ts +50 -38
- package/src/specs/graph-subagent.live.test.ts +242 -0
- package/src/specs/graph-subagent.test.ts +322 -0
- package/src/specs/langfuse-routing.integration.test.ts +120 -1
- package/src/specs/subagent.test.ts +130 -4
- package/src/tools/SubagentTool.ts +4 -4
- package/src/tools/ToolNode.ts +22 -10
- package/src/tools/__tests__/SubagentExecutor.test.ts +198 -9
- package/src/tools/__tests__/SubagentReplay.test.ts +29 -0
- package/src/tools/__tests__/directToolHITLResumeScope.test.ts +28 -2
- package/src/tools/__tests__/subagentHooks.test.ts +90 -3
- package/src/tools/search/rag-api-reranker.test.ts +891 -0
- package/src/tools/search/rerankers.ts +260 -0
- package/src/tools/search/tool.ts +6 -0
- package/src/tools/search/types.ts +45 -1
- package/src/tools/subagent/SubagentExecutionRegistry.ts +1159 -0
- package/src/tools/subagent/SubagentExecutor.ts +1245 -563
- package/src/tools/subagent/SubagentReplay.ts +10 -1
- package/src/tools/subagent/__tests__/SubagentExecutionRegistry.test.ts +637 -0
- package/src/tools/subagent/__tests__/SubagentExecutor.lazy.test.ts +3054 -0
- package/src/tools/subagent/__tests__/childGraphConfig.test.ts +521 -0
- package/src/tools/subagent/childGraphConfig.ts +694 -0
- package/src/tools/subagent/index.ts +6 -0
- package/src/tools/subagent/runtimeLimits.ts +7 -0
- package/src/types/graph.ts +217 -19
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import type { RunnableConfig } from '@langchain/core/runnables';
|
|
2
|
+
import type { ToolCall } from '@langchain/core/messages/tool';
|
|
3
|
+
import type { GraphFactory, GraphFactoryRequest } from '@/graphs/graphFactory';
|
|
4
|
+
import type * as t from '@/types';
|
|
5
|
+
import { MultiAgentGraph } from '@/graphs/MultiAgentGraph';
|
|
6
|
+
import { createFakeStreamingLLM } from '@/llm/fake';
|
|
7
|
+
import { createGraph } from '@/graphs/createGraph';
|
|
8
|
+
import { Constants, Providers } from '@/common';
|
|
9
|
+
import { StandardGraph } from '@/graphs/Graph';
|
|
10
|
+
|
|
11
|
+
const invokeConfig: RunnableConfig = {
|
|
12
|
+
configurable: { thread_id: 'graph-factory-test' },
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const makeAgent = (agentId: string): t.AgentInputs => ({
|
|
16
|
+
agentId,
|
|
17
|
+
provider: Providers.OPENAI,
|
|
18
|
+
instructions: `You are ${agentId}.`,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const getSubagentTool = (
|
|
22
|
+
graph: StandardGraph,
|
|
23
|
+
agentId: string
|
|
24
|
+
): t.GenericTool => {
|
|
25
|
+
graph.createWorkflow();
|
|
26
|
+
const tools = graph.agentContexts.get(agentId)?.graphTools as
|
|
27
|
+
| t.GenericTool[]
|
|
28
|
+
| undefined;
|
|
29
|
+
const tool = tools?.find(
|
|
30
|
+
(candidate) => 'name' in candidate && candidate.name === Constants.SUBAGENT
|
|
31
|
+
);
|
|
32
|
+
if (tool == null) {
|
|
33
|
+
throw new Error(`Expected subagent tool for ${agentId}`);
|
|
34
|
+
}
|
|
35
|
+
return tool;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
describe('graph factory', () => {
|
|
39
|
+
it('constructs standard and multi-agent graph adapters', () => {
|
|
40
|
+
const standard = createGraph({
|
|
41
|
+
kind: 'standard',
|
|
42
|
+
input: { runId: 'standard-factory', agents: [makeAgent('standard')] },
|
|
43
|
+
});
|
|
44
|
+
const multiAgent = createGraph({
|
|
45
|
+
kind: 'multi-agent',
|
|
46
|
+
input: {
|
|
47
|
+
runId: 'multi-agent-factory',
|
|
48
|
+
agents: [makeAgent('multi')],
|
|
49
|
+
edges: [],
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
expect(standard).toBeInstanceOf(StandardGraph);
|
|
54
|
+
expect(standard).not.toBeInstanceOf(MultiAgentGraph);
|
|
55
|
+
expect(multiAgent).toBeInstanceOf(MultiAgentGraph);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('accepts a union-typed graph factory request', () => {
|
|
59
|
+
const construct = (request: GraphFactoryRequest): StandardGraph =>
|
|
60
|
+
createGraph(request);
|
|
61
|
+
const graph = construct({
|
|
62
|
+
kind: 'standard',
|
|
63
|
+
input: { runId: 'union-factory', agents: [makeAgent('union')] },
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
expect(graph).toBeInstanceOf(StandardGraph);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('rejects invalid per-member recursion limits', () => {
|
|
70
|
+
expect(() =>
|
|
71
|
+
createGraph({
|
|
72
|
+
kind: 'multi-agent',
|
|
73
|
+
input: {
|
|
74
|
+
runId: 'invalid-member-limit',
|
|
75
|
+
agents: [makeAgent('member')],
|
|
76
|
+
edges: [],
|
|
77
|
+
memberRecursionLimit: 0,
|
|
78
|
+
},
|
|
79
|
+
})
|
|
80
|
+
).toThrow(/memberRecursionLimit must be a positive safe integer/);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('keeps direct StandardGraph construction compatible with agent children', async () => {
|
|
84
|
+
const parent = {
|
|
85
|
+
...makeAgent('parent'),
|
|
86
|
+
subagentConfigs: [
|
|
87
|
+
{
|
|
88
|
+
type: 'worker',
|
|
89
|
+
name: 'Worker',
|
|
90
|
+
description: 'Completes one isolated task.',
|
|
91
|
+
agentInputs: makeAgent('worker'),
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
} satisfies t.AgentInputs;
|
|
95
|
+
const graph = new StandardGraph({
|
|
96
|
+
runId: 'direct-standard-factory',
|
|
97
|
+
agents: [parent],
|
|
98
|
+
});
|
|
99
|
+
graph.setSubagentModelOverride(
|
|
100
|
+
createFakeStreamingLLM({ responses: ['worker complete'] })
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const result = await getSubagentTool(graph, 'parent').invoke(
|
|
104
|
+
{ description: 'Complete the task.', subagent_type: 'worker' },
|
|
105
|
+
invokeConfig
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
expect(result).toBe('worker complete');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('fails early when direct construction cannot instantiate a graph child', () => {
|
|
112
|
+
const parent = {
|
|
113
|
+
...makeAgent('parent'),
|
|
114
|
+
subagentConfigs: [
|
|
115
|
+
{
|
|
116
|
+
kind: 'graph' as const,
|
|
117
|
+
type: 'team',
|
|
118
|
+
name: 'Team',
|
|
119
|
+
description: 'Runs one member.',
|
|
120
|
+
agents: [makeAgent('member')],
|
|
121
|
+
edges: [],
|
|
122
|
+
entryAgentId: 'member',
|
|
123
|
+
resultAgentId: 'member',
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
} satisfies t.AgentInputs;
|
|
127
|
+
const graph = new StandardGraph({
|
|
128
|
+
runId: 'direct-graph-child-factory',
|
|
129
|
+
agents: [parent],
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
expect(() => graph.createWorkflow()).toThrow(
|
|
133
|
+
/constructing the parent with createGraph\(\)/
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('propagates an injected factory through child and grandchild graphs', async () => {
|
|
138
|
+
const requests: GraphFactoryRequest[] = [];
|
|
139
|
+
const graphFactory: GraphFactory = (request) => {
|
|
140
|
+
requests.push(request);
|
|
141
|
+
if (request.kind === 'multi-agent') {
|
|
142
|
+
return new MultiAgentGraph(request.input, { graphFactory });
|
|
143
|
+
}
|
|
144
|
+
return new StandardGraph(request.input, { graphFactory });
|
|
145
|
+
};
|
|
146
|
+
const nestedToolCall: ToolCall = {
|
|
147
|
+
id: 'call_nested_specialist',
|
|
148
|
+
name: Constants.SUBAGENT,
|
|
149
|
+
args: {
|
|
150
|
+
description: 'Complete the specialist step.',
|
|
151
|
+
subagent_type: 'specialist',
|
|
152
|
+
},
|
|
153
|
+
type: 'tool_call',
|
|
154
|
+
};
|
|
155
|
+
const router = {
|
|
156
|
+
...makeAgent('router'),
|
|
157
|
+
subagentConfigs: [
|
|
158
|
+
{
|
|
159
|
+
type: 'specialist',
|
|
160
|
+
name: 'Specialist',
|
|
161
|
+
description: 'Completes the nested specialist step.',
|
|
162
|
+
agentInputs: makeAgent('specialist'),
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
} satisfies t.AgentInputs;
|
|
166
|
+
const root = {
|
|
167
|
+
...makeAgent('root'),
|
|
168
|
+
maxSubagentDepth: 2,
|
|
169
|
+
subagentConfigs: [
|
|
170
|
+
{
|
|
171
|
+
type: 'router',
|
|
172
|
+
name: 'Router',
|
|
173
|
+
description: 'Delegates one nested specialist step.',
|
|
174
|
+
allowNested: true,
|
|
175
|
+
agentInputs: router,
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
} satisfies t.AgentInputs;
|
|
179
|
+
const graph = new StandardGraph(
|
|
180
|
+
{ runId: 'recursive-graph-factory', agents: [root] },
|
|
181
|
+
{ graphFactory }
|
|
182
|
+
);
|
|
183
|
+
graph.setSubagentModelOverride(
|
|
184
|
+
createFakeStreamingLLM({
|
|
185
|
+
responses: ['delegating', 'specialist complete', 'router complete'],
|
|
186
|
+
toolCalls: [nestedToolCall],
|
|
187
|
+
})
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
const result = await getSubagentTool(graph, 'root').invoke(
|
|
191
|
+
{ description: 'Route the task.', subagent_type: 'router' },
|
|
192
|
+
invokeConfig
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
expect(result).toBe('router complete');
|
|
196
|
+
expect(
|
|
197
|
+
requests.map(({ kind, input }) => ({
|
|
198
|
+
kind,
|
|
199
|
+
agentIds: input.agents.map(({ agentId }) => agentId),
|
|
200
|
+
}))
|
|
201
|
+
).toEqual([
|
|
202
|
+
{ kind: 'standard', agentIds: ['router'] },
|
|
203
|
+
{ kind: 'standard', agentIds: ['specialist'] },
|
|
204
|
+
]);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { StandardGraph } from './Graph';
|
|
2
|
+
|
|
3
|
+
export type GraphRuntimeConfig = Pick<
|
|
4
|
+
StandardGraph,
|
|
5
|
+
| 'hookRegistry'
|
|
6
|
+
| 'humanInTheLoop'
|
|
7
|
+
| 'toolOutputReferences'
|
|
8
|
+
| 'eagerEventToolExecution'
|
|
9
|
+
| 'codeSessionToolNames'
|
|
10
|
+
| 'interruptingToolNames'
|
|
11
|
+
| 'toolExecution'
|
|
12
|
+
>;
|
|
13
|
+
|
|
14
|
+
export function applyGraphRuntimeConfig(
|
|
15
|
+
graph: StandardGraph,
|
|
16
|
+
config: GraphRuntimeConfig
|
|
17
|
+
): void {
|
|
18
|
+
graph.hookRegistry = config.hookRegistry;
|
|
19
|
+
graph.humanInTheLoop = config.humanInTheLoop;
|
|
20
|
+
graph.toolOutputReferences = config.toolOutputReferences;
|
|
21
|
+
graph.eagerEventToolExecution = config.eagerEventToolExecution;
|
|
22
|
+
graph.codeSessionToolNames = config.codeSessionToolNames;
|
|
23
|
+
graph.interruptingToolNames = config.interruptingToolNames;
|
|
24
|
+
graph.toolExecution = config.toolExecution;
|
|
25
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
GraphFactory,
|
|
3
|
+
GraphFactoryDependencies,
|
|
4
|
+
GraphFactoryRequest,
|
|
5
|
+
} from '@/graphs/graphFactory';
|
|
6
|
+
import type { MultiAgentGraphInput, StandardGraphInput } from '@/types';
|
|
7
|
+
import { MultiAgentGraph } from '@/graphs/MultiAgentGraph';
|
|
8
|
+
import { StandardGraph } from '@/graphs/Graph';
|
|
9
|
+
|
|
10
|
+
const createGraphByKind: GraphFactory = (request) => {
|
|
11
|
+
if (request.kind === 'multi-agent') {
|
|
12
|
+
return new MultiAgentGraph(request.input, dependencies);
|
|
13
|
+
}
|
|
14
|
+
return new StandardGraph(request.input, dependencies);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const dependencies: GraphFactoryDependencies = {
|
|
18
|
+
graphFactory: createGraphByKind,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export function createGraph(request: {
|
|
22
|
+
kind: 'standard';
|
|
23
|
+
input: StandardGraphInput;
|
|
24
|
+
}): StandardGraph;
|
|
25
|
+
export function createGraph(request: {
|
|
26
|
+
kind: 'multi-agent';
|
|
27
|
+
input: MultiAgentGraphInput;
|
|
28
|
+
}): MultiAgentGraph;
|
|
29
|
+
export function createGraph(request: GraphFactoryRequest): StandardGraph;
|
|
30
|
+
export function createGraph(request: GraphFactoryRequest): StandardGraph {
|
|
31
|
+
return createGraphByKind(request);
|
|
32
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { StandardGraph } from './Graph';
|
|
2
|
+
import type * as t from '@/types';
|
|
3
|
+
|
|
4
|
+
export type GraphFactoryRequest =
|
|
5
|
+
| { kind: 'standard'; input: t.StandardGraphInput }
|
|
6
|
+
| { kind: 'multi-agent'; input: t.MultiAgentGraphInput };
|
|
7
|
+
|
|
8
|
+
export type GraphFactory = (request: GraphFactoryRequest) => StandardGraph;
|
|
9
|
+
|
|
10
|
+
export type GraphFactoryDependencies = {
|
|
11
|
+
graphFactory: GraphFactory;
|
|
12
|
+
};
|
package/src/graphs/index.ts
CHANGED
package/src/run.ts
CHANGED
|
@@ -18,6 +18,8 @@ import {
|
|
|
18
18
|
import type { StringPromptValue } from '@langchain/core/prompt_values';
|
|
19
19
|
import type { MessageContentComplex } from '@langchain/core/messages';
|
|
20
20
|
import type { RunnableConfig } from '@langchain/core/runnables';
|
|
21
|
+
import type { MultiAgentGraph } from '@/graphs/MultiAgentGraph';
|
|
22
|
+
import type { StandardGraph } from '@/graphs/Graph';
|
|
21
23
|
import type { HookRegistry } from '@/hooks';
|
|
22
24
|
import type * as t from '@/types';
|
|
23
25
|
import {
|
|
@@ -62,12 +64,12 @@ import {
|
|
|
62
64
|
createCompletionTitleRunnable,
|
|
63
65
|
createTitleRunnable,
|
|
64
66
|
} from '@/utils/title';
|
|
67
|
+
import { applyGraphRuntimeConfig } from '@/graphs/applyGraphRuntimeConfig';
|
|
65
68
|
import { createTokenCounter, encodingForModel } from '@/utils/tokens';
|
|
66
69
|
import { initializeLangfuseTracing } from './instrumentation';
|
|
67
|
-
import { MultiAgentGraph } from '@/graphs/MultiAgentGraph';
|
|
68
70
|
import { getTraceIdSeed } from '@/langfuseRuntimeContext';
|
|
71
|
+
import { createGraph } from '@/graphs/createGraph';
|
|
69
72
|
import { resolveMaxSeals } from '@/llm/preempt';
|
|
70
|
-
import { StandardGraph } from '@/graphs/Graph';
|
|
71
73
|
import { initializeModel } from '@/llm/init';
|
|
72
74
|
import { HandlerRegistry } from '@/events';
|
|
73
75
|
import { isOpenAILike } from '@/utils/llm';
|
|
@@ -346,30 +348,35 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
346
348
|
signal = legacySignal;
|
|
347
349
|
}
|
|
348
350
|
|
|
349
|
-
const standardGraph =
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
351
|
+
const standardGraph = createGraph({
|
|
352
|
+
kind: 'standard',
|
|
353
|
+
input: {
|
|
354
|
+
signal,
|
|
355
|
+
runId: this.id,
|
|
356
|
+
agents: [agentConfig],
|
|
357
|
+
langfuse: this.langfuse,
|
|
358
|
+
tokenCounter: this.tokenCounter,
|
|
359
|
+
indexTokenCountMap: this.indexTokenCountMap,
|
|
360
|
+
calibrationRatio: this.calibrationRatio,
|
|
361
|
+
subagentUsageSink: this.subagentUsageSink,
|
|
362
|
+
preemption: this.preemption,
|
|
363
|
+
streamLimits: this.streamLimits,
|
|
364
|
+
},
|
|
360
365
|
});
|
|
361
366
|
/** Propagate compile options from graph config */
|
|
362
367
|
standardGraph.compileOptions = this.applyHITLCheckpointerFallback(
|
|
363
368
|
config.compileOptions
|
|
364
369
|
);
|
|
365
370
|
this.hasCheckpointer = standardGraph.compileOptions?.checkpointer != null;
|
|
366
|
-
standardGraph
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
371
|
+
applyGraphRuntimeConfig(standardGraph, {
|
|
372
|
+
hookRegistry: this.hookRegistry,
|
|
373
|
+
humanInTheLoop: this.humanInTheLoop,
|
|
374
|
+
toolOutputReferences: this.toolOutputReferences,
|
|
375
|
+
eagerEventToolExecution: this.eagerEventToolExecution,
|
|
376
|
+
codeSessionToolNames: this.codeSessionToolNames,
|
|
377
|
+
interruptingToolNames: this.interruptingToolNames,
|
|
378
|
+
toolExecution: this.toolExecution,
|
|
379
|
+
});
|
|
373
380
|
this.Graph = standardGraph;
|
|
374
381
|
return standardGraph.createWorkflow();
|
|
375
382
|
}
|
|
@@ -379,30 +386,35 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
379
386
|
): t.CompiledStateWorkflow {
|
|
380
387
|
const { agents, edges, compileOptions } = config;
|
|
381
388
|
|
|
382
|
-
const multiAgentGraph =
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
389
|
+
const multiAgentGraph = createGraph({
|
|
390
|
+
kind: 'multi-agent',
|
|
391
|
+
input: {
|
|
392
|
+
runId: this.id,
|
|
393
|
+
agents,
|
|
394
|
+
edges,
|
|
395
|
+
langfuse: this.langfuse,
|
|
396
|
+
tokenCounter: this.tokenCounter,
|
|
397
|
+
indexTokenCountMap: this.indexTokenCountMap,
|
|
398
|
+
calibrationRatio: this.calibrationRatio,
|
|
399
|
+
subagentUsageSink: this.subagentUsageSink,
|
|
400
|
+
preemption: this.preemption,
|
|
401
|
+
streamLimits: this.streamLimits,
|
|
402
|
+
},
|
|
393
403
|
});
|
|
394
404
|
|
|
395
405
|
multiAgentGraph.compileOptions =
|
|
396
406
|
this.applyHITLCheckpointerFallback(compileOptions);
|
|
397
407
|
this.hasCheckpointer = multiAgentGraph.compileOptions?.checkpointer != null;
|
|
398
408
|
|
|
399
|
-
multiAgentGraph
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
409
|
+
applyGraphRuntimeConfig(multiAgentGraph, {
|
|
410
|
+
hookRegistry: this.hookRegistry,
|
|
411
|
+
humanInTheLoop: this.humanInTheLoop,
|
|
412
|
+
toolOutputReferences: this.toolOutputReferences,
|
|
413
|
+
eagerEventToolExecution: this.eagerEventToolExecution,
|
|
414
|
+
codeSessionToolNames: this.codeSessionToolNames,
|
|
415
|
+
interruptingToolNames: this.interruptingToolNames,
|
|
416
|
+
toolExecution: this.toolExecution,
|
|
417
|
+
});
|
|
406
418
|
this.Graph = multiAgentGraph;
|
|
407
419
|
return multiAgentGraph.createWorkflow();
|
|
408
420
|
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { config as dotenvConfig } from 'dotenv';
|
|
2
|
+
|
|
3
|
+
dotenvConfig(
|
|
4
|
+
process.env.LIVE_ENV_PATH == null
|
|
5
|
+
? undefined
|
|
6
|
+
: { path: process.env.LIVE_ENV_PATH }
|
|
7
|
+
);
|
|
8
|
+
dotenvConfig();
|
|
9
|
+
|
|
10
|
+
import { describe, expect, it, jest } from '@jest/globals';
|
|
11
|
+
import type { RunnableConfig } from '@langchain/core/runnables';
|
|
12
|
+
import type * as t from '@/types';
|
|
13
|
+
import { Constants, GraphEvents, Providers } from '@/common';
|
|
14
|
+
import { StandardGraph } from '@/graphs/Graph';
|
|
15
|
+
import { Run } from '@/run';
|
|
16
|
+
|
|
17
|
+
jest.setTimeout(180_000);
|
|
18
|
+
|
|
19
|
+
const liveEnabled = process.env.RUN_GRAPH_SUBAGENT_LIVE_TESTS === '1';
|
|
20
|
+
const requestedProvider = process.env.GRAPH_SUBAGENT_LIVE_PROVIDER;
|
|
21
|
+
const describeIfLive = liveEnabled ? describe : describe.skip;
|
|
22
|
+
|
|
23
|
+
type LiveProviderCase = {
|
|
24
|
+
label: string;
|
|
25
|
+
provider: Providers;
|
|
26
|
+
model: string;
|
|
27
|
+
apiKey?: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const LIVE_PROVIDER_CASES: readonly LiveProviderCase[] = [
|
|
31
|
+
{
|
|
32
|
+
label: 'Anthropic Haiku',
|
|
33
|
+
provider: Providers.ANTHROPIC,
|
|
34
|
+
model:
|
|
35
|
+
process.env.ANTHROPIC_GRAPH_SUBAGENT_LIVE_MODEL ?? 'claude-haiku-4-5',
|
|
36
|
+
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
label: 'OpenAI GPT-4.1 mini',
|
|
40
|
+
provider: Providers.OPENAI,
|
|
41
|
+
model: process.env.OPENAI_GRAPH_SUBAGENT_LIVE_MODEL ?? 'gpt-4.1-mini',
|
|
42
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const explicitlyRequestedCase =
|
|
47
|
+
requestedProvider == null || requestedProvider === ''
|
|
48
|
+
? undefined
|
|
49
|
+
: LIVE_PROVIDER_CASES.find(
|
|
50
|
+
({ provider }) =>
|
|
51
|
+
provider.toLowerCase() === requestedProvider.toLowerCase()
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
if (
|
|
55
|
+
liveEnabled &&
|
|
56
|
+
requestedProvider != null &&
|
|
57
|
+
requestedProvider !== '' &&
|
|
58
|
+
explicitlyRequestedCase == null
|
|
59
|
+
) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`Unknown GRAPH_SUBAGENT_LIVE_PROVIDER "${requestedProvider}".`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
if (
|
|
65
|
+
liveEnabled &&
|
|
66
|
+
explicitlyRequestedCase != null &&
|
|
67
|
+
(explicitlyRequestedCase.apiKey == null ||
|
|
68
|
+
explicitlyRequestedCase.apiKey === '')
|
|
69
|
+
) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
`Missing credentials for GRAPH_SUBAGENT_LIVE_PROVIDER "${requestedProvider}".`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
if (
|
|
75
|
+
liveEnabled &&
|
|
76
|
+
explicitlyRequestedCase == null &&
|
|
77
|
+
!LIVE_PROVIDER_CASES.some(({ apiKey }) => apiKey != null && apiKey !== '')
|
|
78
|
+
) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
'No graph-subagent live-provider credentials are configured.'
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function createLiveAgent(
|
|
85
|
+
testCase: LiveProviderCase,
|
|
86
|
+
agentId: string,
|
|
87
|
+
marker: string
|
|
88
|
+
): t.AgentInputs {
|
|
89
|
+
return {
|
|
90
|
+
agentId,
|
|
91
|
+
provider: testCase.provider,
|
|
92
|
+
clientOptions: {
|
|
93
|
+
modelName: testCase.model,
|
|
94
|
+
apiKey: testCase.apiKey,
|
|
95
|
+
temperature: 0,
|
|
96
|
+
maxTokens: 64,
|
|
97
|
+
streaming: true,
|
|
98
|
+
streamUsage: true,
|
|
99
|
+
},
|
|
100
|
+
instructions: `You are the ${agentId} validation stage. When asked to proceed, reply with exactly ${marker} and no other text.`,
|
|
101
|
+
maxContextTokens: 8_000,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function getGraphSubagentTool(run: Run<t.IState>): t.GenericTool {
|
|
106
|
+
const tools = (run.Graph as StandardGraph).agentContexts.get('parent')
|
|
107
|
+
?.graphTools as t.GenericTool[] | undefined;
|
|
108
|
+
const subagentTool = tools?.find(
|
|
109
|
+
(tool) => 'name' in tool && tool.name === Constants.SUBAGENT
|
|
110
|
+
);
|
|
111
|
+
if (subagentTool == null) {
|
|
112
|
+
throw new Error('Expected graph subagent tool');
|
|
113
|
+
}
|
|
114
|
+
return subagentTool;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
describeIfLive('Graph subagent live providers', () => {
|
|
118
|
+
for (const testCase of LIVE_PROVIDER_CASES) {
|
|
119
|
+
const providerSelected =
|
|
120
|
+
requestedProvider == null ||
|
|
121
|
+
requestedProvider === '' ||
|
|
122
|
+
requestedProvider.toLowerCase() === testCase.provider.toLowerCase();
|
|
123
|
+
const itIfAvailable =
|
|
124
|
+
providerSelected && testCase.apiKey != null && testCase.apiKey !== ''
|
|
125
|
+
? it
|
|
126
|
+
: it.skip;
|
|
127
|
+
|
|
128
|
+
itIfAvailable(
|
|
129
|
+
`${testCase.label} runs a bounded team and returns only its result member`,
|
|
130
|
+
async () => {
|
|
131
|
+
const usageEvents: t.SubagentUsageEvent[] = [];
|
|
132
|
+
const updateEvents: t.SubagentUpdateEvent[] = [];
|
|
133
|
+
const entry = createLiveAgent(testCase, 'entry', 'ENTRY_MARKER');
|
|
134
|
+
const worker = createLiveAgent(testCase, 'worker', 'WORKER_MARKER');
|
|
135
|
+
const resultAgent = createLiveAgent(testCase, 'result', 'FINAL_MARKER');
|
|
136
|
+
const parent: t.AgentInputs = {
|
|
137
|
+
...createLiveAgent(testCase, 'parent', 'PARENT_MARKER'),
|
|
138
|
+
maxSubagentDepth: 1,
|
|
139
|
+
subagentConfigs: [
|
|
140
|
+
{
|
|
141
|
+
kind: 'graph',
|
|
142
|
+
type: 'live-team',
|
|
143
|
+
name: 'Live Team',
|
|
144
|
+
description: 'Runs a three-member live-provider chain.',
|
|
145
|
+
maxTurns: 2,
|
|
146
|
+
agents: [entry, worker, resultAgent],
|
|
147
|
+
edges: [
|
|
148
|
+
{
|
|
149
|
+
from: 'entry',
|
|
150
|
+
to: 'worker',
|
|
151
|
+
edgeType: 'direct',
|
|
152
|
+
prompt: 'Proceed with the worker validation stage.',
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
from: 'worker',
|
|
156
|
+
to: 'result',
|
|
157
|
+
edgeType: 'direct',
|
|
158
|
+
prompt: 'Proceed with the result validation stage.',
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
entryAgentId: 'entry',
|
|
162
|
+
resultAgentId: 'result',
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
};
|
|
166
|
+
const rootRunId = `graph-subagent-live-${testCase.provider}-${Date.now()}`;
|
|
167
|
+
const run = await Run.create<t.IState>({
|
|
168
|
+
runId: rootRunId,
|
|
169
|
+
graphConfig: { type: 'standard', agents: [parent] },
|
|
170
|
+
returnContent: true,
|
|
171
|
+
skipCleanup: true,
|
|
172
|
+
subagentUsageSink: (event) => {
|
|
173
|
+
usageEvents.push(event);
|
|
174
|
+
},
|
|
175
|
+
customHandlers: {
|
|
176
|
+
[GraphEvents.ON_SUBAGENT_UPDATE]: {
|
|
177
|
+
handle: (_event, data): void => {
|
|
178
|
+
updateEvents.push(data as t.SubagentUpdateEvent);
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
const invokeConfig: RunnableConfig = {
|
|
184
|
+
configurable: {
|
|
185
|
+
thread_id: `graph-subagent-live-${testCase.provider}`,
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const output = await getGraphSubagentTool(run).invoke(
|
|
190
|
+
{
|
|
191
|
+
description: 'Begin the entry validation stage.',
|
|
192
|
+
subagent_type: 'live-team',
|
|
193
|
+
},
|
|
194
|
+
invokeConfig
|
|
195
|
+
);
|
|
196
|
+
const outputText = String(output);
|
|
197
|
+
|
|
198
|
+
expect(outputText).toContain('FINAL_MARKER');
|
|
199
|
+
expect(outputText).not.toContain('ENTRY_MARKER');
|
|
200
|
+
expect(outputText).not.toContain('WORKER_MARKER');
|
|
201
|
+
expect(usageEvents.map((event) => event.memberAgentId).sort()).toEqual([
|
|
202
|
+
'entry',
|
|
203
|
+
'result',
|
|
204
|
+
'worker',
|
|
205
|
+
]);
|
|
206
|
+
for (const event of usageEvents) {
|
|
207
|
+
expect(event.runId).toBe(rootRunId);
|
|
208
|
+
expect(event.parentRunId).toBe(rootRunId);
|
|
209
|
+
expect(event.depth).toBe(1);
|
|
210
|
+
expect(event.ancestry?.map((entry) => entry.subagentType)).toEqual([
|
|
211
|
+
'live-team',
|
|
212
|
+
]);
|
|
213
|
+
expect(event.subagentKind).toBe('graph');
|
|
214
|
+
expect(event.provider).toBe(testCase.provider);
|
|
215
|
+
expect(event.model).toBeTruthy();
|
|
216
|
+
expect(event.usage.total_tokens).toBeGreaterThan(0);
|
|
217
|
+
}
|
|
218
|
+
expect(
|
|
219
|
+
updateEvents.filter((event) => event.phase === 'start')
|
|
220
|
+
).toHaveLength(1);
|
|
221
|
+
expect(
|
|
222
|
+
updateEvents.filter((event) => event.phase === 'stop')
|
|
223
|
+
).toHaveLength(1);
|
|
224
|
+
for (const event of updateEvents) {
|
|
225
|
+
expect(event.runId).toBe(rootRunId);
|
|
226
|
+
expect(event.parentRunId).toBe(rootRunId);
|
|
227
|
+
expect(event.depth).toBe(1);
|
|
228
|
+
expect(event.ancestry?.map((entry) => entry.subagentType)).toEqual([
|
|
229
|
+
'live-team',
|
|
230
|
+
]);
|
|
231
|
+
}
|
|
232
|
+
expect(
|
|
233
|
+
new Set(
|
|
234
|
+
updateEvents
|
|
235
|
+
.map((event) => event.memberAgentId)
|
|
236
|
+
.filter((agentId): agentId is string => agentId != null)
|
|
237
|
+
)
|
|
238
|
+
).toEqual(new Set(['entry', 'worker', 'result']));
|
|
239
|
+
}
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
});
|