@librechat/agents 3.1.67-dev.0 → 3.1.67-dev.4
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/common/enum.cjs +2 -0
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +19 -0
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/main.cjs +1 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +267 -17
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/esm/common/enum.mjs +2 -0
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +19 -0
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/main.mjs +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +267 -18
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/types/common/enum.d.ts +2 -0
- package/dist/types/tools/subagent/SubagentExecutor.d.ts +56 -2
- package/dist/types/tools/subagent/index.d.ts +1 -1
- package/dist/types/types/graph.d.ts +36 -2
- package/package.json +4 -1
- package/src/common/enum.ts +2 -0
- package/src/graphs/Graph.ts +21 -0
- package/src/scripts/subagent-event-driven-debug.ts +190 -0
- package/src/scripts/subagent-tools-debug.ts +160 -0
- package/src/tools/__tests__/SubagentExecutor.test.ts +534 -1
- package/src/tools/subagent/SubagentExecutor.ts +349 -17
- package/src/tools/subagent/index.ts +1 -0
- package/src/types/graph.ts +53 -1
|
@@ -4,7 +4,7 @@ import type { BaseMessage, AIMessageChunk, SystemMessage } from '@langchain/core
|
|
|
4
4
|
import type { RunnableConfig, Runnable } from '@langchain/core/runnables';
|
|
5
5
|
import type { ChatGenerationChunk } from '@langchain/core/outputs';
|
|
6
6
|
import type { GoogleAIToolType } from '@langchain/google-common';
|
|
7
|
-
import type { ToolMap, ToolEndEvent, GenericTool, LCTool } from '@/types/tools';
|
|
7
|
+
import type { ToolMap, ToolEndEvent, GenericTool, LCTool, ToolExecuteBatchRequest } from '@/types/tools';
|
|
8
8
|
import type { Providers, Callback, GraphNodeKeys } from '@/common';
|
|
9
9
|
import type { StandardGraph, MultiAgentGraph } from '@/graphs';
|
|
10
10
|
import type { ClientOptions } from '@/types/llm';
|
|
@@ -45,7 +45,7 @@ export interface AgentLogEvent {
|
|
|
45
45
|
agentId?: string;
|
|
46
46
|
}
|
|
47
47
|
export interface EventHandler {
|
|
48
|
-
handle(event: string, data: StreamEventData | ModelEndData | RunStep | RunStepDeltaEvent | MessageDeltaEvent | ReasoningDeltaEvent | SummarizeStartEvent | SummarizeDeltaEvent | SummarizeCompleteEvent | AgentLogEvent | {
|
|
48
|
+
handle(event: string, data: StreamEventData | ModelEndData | RunStep | RunStepDeltaEvent | MessageDeltaEvent | ReasoningDeltaEvent | SummarizeStartEvent | SummarizeDeltaEvent | SummarizeCompleteEvent | SubagentUpdateEvent | AgentLogEvent | ToolExecuteBatchRequest | {
|
|
49
49
|
result: ToolEndEvent;
|
|
50
50
|
}, metadata?: Record<string, unknown>, graph?: StandardGraph | MultiAgentGraph): void | Promise<void>;
|
|
51
51
|
}
|
|
@@ -269,6 +269,40 @@ export type SubagentConfig = {
|
|
|
269
269
|
export type ResolvedSubagentConfig = SubagentConfig & {
|
|
270
270
|
agentInputs: AgentInputs;
|
|
271
271
|
};
|
|
272
|
+
/** Lifecycle phase carried on {@link SubagentUpdateEvent}. */
|
|
273
|
+
export type SubagentUpdatePhase = 'start' | 'run_step' | 'run_step_delta' | 'run_step_completed' | 'message_delta' | 'reasoning_delta' | 'stop' | 'error';
|
|
274
|
+
/**
|
|
275
|
+
* Wrapper event emitted when a subagent's child graph dispatches activity.
|
|
276
|
+
* Lets hosts show subagent progress in a UI surface separate from the parent
|
|
277
|
+
* conversation without having to untangle events by agent ID.
|
|
278
|
+
*/
|
|
279
|
+
export interface SubagentUpdateEvent {
|
|
280
|
+
/** Parent run ID. */
|
|
281
|
+
runId: string;
|
|
282
|
+
/** Child run ID (unique per subagent execution). */
|
|
283
|
+
subagentRunId: string;
|
|
284
|
+
/**
|
|
285
|
+
* Parent-side `tool_call_id` for the `subagent` tool invocation that
|
|
286
|
+
* triggered this run. Stable for the duration of the child; lets hosts
|
|
287
|
+
* correlate updates deterministically instead of inferring by ordering.
|
|
288
|
+
* Omitted when the executor was invoked outside of a tool-call context.
|
|
289
|
+
*/
|
|
290
|
+
parentToolCallId?: string;
|
|
291
|
+
/** Subagent `type` identifier from the SubagentConfig. */
|
|
292
|
+
subagentType: string;
|
|
293
|
+
/** Child agent ID assigned to this subagent execution. */
|
|
294
|
+
subagentAgentId: string;
|
|
295
|
+
/** Parent agent ID that spawned this subagent. */
|
|
296
|
+
parentAgentId?: string;
|
|
297
|
+
/** Lifecycle phase carried by this update. */
|
|
298
|
+
phase: SubagentUpdatePhase;
|
|
299
|
+
/** Underlying event payload (shape depends on phase). */
|
|
300
|
+
data?: unknown;
|
|
301
|
+
/** Short human-readable description. Hosts can render this directly. */
|
|
302
|
+
label?: string;
|
|
303
|
+
/** ISO timestamp for ordering / display. */
|
|
304
|
+
timestamp: string;
|
|
305
|
+
}
|
|
272
306
|
export interface AgentInputs {
|
|
273
307
|
agentId: string;
|
|
274
308
|
/** Human-readable name for the agent (used in handoff context). Defaults to agentId if not provided. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@librechat/agents",
|
|
3
|
-
"version": "3.1.67-dev.
|
|
3
|
+
"version": "3.1.67-dev.4",
|
|
4
4
|
"main": "./dist/cjs/main.cjs",
|
|
5
5
|
"module": "./dist/esm/main.mjs",
|
|
6
6
|
"types": "./dist/types/index.d.ts",
|
|
@@ -61,6 +61,9 @@
|
|
|
61
61
|
"tool": "node --trace-warnings -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/tools.ts --provider 'bedrock' --name 'Jo' --location 'New York, NY'",
|
|
62
62
|
"search": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/search.ts --provider 'bedrock' --name 'Jo' --location 'New York, NY'",
|
|
63
63
|
"tool_search": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/tool_search.ts",
|
|
64
|
+
"subagent": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/multi-agent-subagent.ts",
|
|
65
|
+
"subagent:events": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/subagent-event-driven-debug.ts",
|
|
66
|
+
"subagent:tools": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/subagent-tools-debug.ts",
|
|
64
67
|
"programmatic_exec": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/programmatic_exec.ts",
|
|
65
68
|
"code_exec_ptc": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/code_exec_ptc.ts --provider 'openAI' --name 'Jo' --location 'New York, NY'",
|
|
66
69
|
"programmatic_exec_agent": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/programmatic_exec_agent.ts --provider 'openAI' --name 'Jo' --location 'New York, NY'",
|
package/src/common/enum.ts
CHANGED
|
@@ -27,6 +27,8 @@ export enum GraphEvents {
|
|
|
27
27
|
ON_SUMMARIZE_DELTA = 'on_summarize_delta',
|
|
28
28
|
/** [Custom] Emitted when the summarize node completes with the final summary */
|
|
29
29
|
ON_SUMMARIZE_COMPLETE = 'on_summarize_complete',
|
|
30
|
+
/** [Custom] Progress update from a running subagent (wraps child-graph events so hosts can display activity separately from parent). */
|
|
31
|
+
ON_SUBAGENT_UPDATE = 'on_subagent_update',
|
|
30
32
|
/** [Custom] Diagnostic logging event for context management observability */
|
|
31
33
|
ON_AGENT_LOG = 'on_agent_log',
|
|
32
34
|
|
package/src/graphs/Graph.ts
CHANGED
|
@@ -1176,10 +1176,16 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1176
1176
|
agentContext
|
|
1177
1177
|
);
|
|
1178
1178
|
if (resolvedConfigs.length > 0) {
|
|
1179
|
+
const getParentHandlerRegistry = (): HandlerRegistry | undefined =>
|
|
1180
|
+
this.handlerRegistry;
|
|
1179
1181
|
const executor = new SubagentExecutor({
|
|
1180
1182
|
configs: new Map(resolvedConfigs.map((c) => [c.type, c])),
|
|
1181
1183
|
parentSignal: this.signal,
|
|
1182
1184
|
hookRegistry: this.hookRegistry,
|
|
1185
|
+
/** Lazy — Run wires the registry onto the graph AFTER
|
|
1186
|
+
* `createWorkflow()` runs, so a direct capture here would be
|
|
1187
|
+
* `undefined` at construction time. */
|
|
1188
|
+
parentHandlerRegistry: getParentHandlerRegistry,
|
|
1183
1189
|
parentRunId: this.runId ?? '',
|
|
1184
1190
|
parentAgentId: agentContext.agentId,
|
|
1185
1191
|
tokenCounter: agentContext.tokenCounter,
|
|
@@ -1200,10 +1206,25 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1200
1206
|
const subagentType =
|
|
1201
1207
|
typeof input.subagent_type === 'string' ? input.subagent_type : '';
|
|
1202
1208
|
const threadId = config.configurable?.thread_id as string | undefined;
|
|
1209
|
+
/**
|
|
1210
|
+
* When the tool is dispatched from an LLM's `tool_call`, LangChain
|
|
1211
|
+
* threads the originating `ToolCall` onto the RunnableConfig as
|
|
1212
|
+
* `config.toolCall` (see `ToolRunnableConfig` in
|
|
1213
|
+
* `@langchain/core/tools` — internal but stable since ≥0.3.x).
|
|
1214
|
+
* Surfacing its id lets hosts correlate `SubagentUpdateEvent`s
|
|
1215
|
+
* back to the parent's `tool_call_id` deterministically — no
|
|
1216
|
+
* temporal heuristics needed. If a future LangChain version
|
|
1217
|
+
* changes the threading, the type-guarded read falls back to
|
|
1218
|
+
* `undefined` and the correlation degrades gracefully.
|
|
1219
|
+
*/
|
|
1220
|
+
const toolCall = (config as { toolCall?: { id?: string } }).toolCall;
|
|
1221
|
+
const parentToolCallId =
|
|
1222
|
+
typeof toolCall?.id === 'string' ? toolCall.id : undefined;
|
|
1203
1223
|
const result = await executor.execute({
|
|
1204
1224
|
description,
|
|
1205
1225
|
subagentType,
|
|
1206
1226
|
threadId,
|
|
1227
|
+
parentToolCallId,
|
|
1207
1228
|
});
|
|
1208
1229
|
return result.content;
|
|
1209
1230
|
}, buildSubagentToolParams(resolvedConfigs));
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { config } from 'dotenv';
|
|
2
|
+
config();
|
|
3
|
+
|
|
4
|
+
import { HumanMessage } from '@langchain/core/messages';
|
|
5
|
+
import type { BaseMessage } from '@langchain/core/messages';
|
|
6
|
+
import type * as t from '@/types';
|
|
7
|
+
import { ChatModelStreamHandler } from '@/stream';
|
|
8
|
+
import { ToolEndHandler, ModelEndHandler } from '@/events';
|
|
9
|
+
import { Providers, GraphEvents, Constants } from '@/common';
|
|
10
|
+
import { Run } from '@/run';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Repro for LibreChat's actual setup: event-driven tools via `toolDefinitions`
|
|
14
|
+
* + an ON_TOOL_EXECUTE handler that runs the tool. Self-spawn subagent must
|
|
15
|
+
* be able to drive the SAME tool pipeline.
|
|
16
|
+
*/
|
|
17
|
+
const apiKey = process.env.OPENAI_API_KEY!;
|
|
18
|
+
if (!apiKey) {
|
|
19
|
+
console.error('Missing OPENAI_API_KEY');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Simulate LibreChat: tool definitions only, execution routed via event.
|
|
24
|
+
const calculatorDef: t.LCTool = {
|
|
25
|
+
name: 'calculator',
|
|
26
|
+
description: 'Evaluate a math expression. Use for any arithmetic.',
|
|
27
|
+
parameters: {
|
|
28
|
+
type: 'object',
|
|
29
|
+
properties: {
|
|
30
|
+
expression: {
|
|
31
|
+
type: 'string',
|
|
32
|
+
description: "A JS math expression, e.g. '42 * 58'",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
required: ['expression'],
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
async function main() {
|
|
40
|
+
console.log('=== Subagent Event-Driven Tool Diagnostic ===\n');
|
|
41
|
+
|
|
42
|
+
const parentAgent: t.AgentInputs = {
|
|
43
|
+
agentId: 'supervisor',
|
|
44
|
+
provider: Providers.OPENAI,
|
|
45
|
+
clientOptions: { modelName: 'gpt-4o-mini', apiKey },
|
|
46
|
+
instructions: `You have calculator AND can spawn a "self" subagent in an isolated context.
|
|
47
|
+
For any arithmetic question, spawn the "self" subagent with the math task.
|
|
48
|
+
The subagent MUST use the calculator tool — never estimate.`,
|
|
49
|
+
maxContextTokens: 8000,
|
|
50
|
+
toolDefinitions: [calculatorDef],
|
|
51
|
+
subagentConfigs: [
|
|
52
|
+
{
|
|
53
|
+
type: 'self',
|
|
54
|
+
self: true,
|
|
55
|
+
name: 'supervisor',
|
|
56
|
+
description:
|
|
57
|
+
'Spawn a copy of this agent in an isolated context for a focused math subtask.',
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
let toolCallCount = 0;
|
|
63
|
+
const customHandlers: Record<string, t.EventHandler> = {
|
|
64
|
+
[GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
|
|
65
|
+
[GraphEvents.TOOL_END]: new ToolEndHandler(),
|
|
66
|
+
[GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
|
|
67
|
+
[GraphEvents.ON_TOOL_EXECUTE]: {
|
|
68
|
+
handle: (_event, rawData): void => {
|
|
69
|
+
const data = rawData as t.ToolExecuteBatchRequest;
|
|
70
|
+
console.log(
|
|
71
|
+
`[PARENT ON_TOOL_EXECUTE] agentId=${data.agentId} calls=${data.toolCalls
|
|
72
|
+
.map((c) => c.name)
|
|
73
|
+
.join(',')}`
|
|
74
|
+
);
|
|
75
|
+
const results: t.ToolExecuteResult[] = data.toolCalls.map((call) => {
|
|
76
|
+
toolCallCount += 1;
|
|
77
|
+
const args = call.args as { expression?: string };
|
|
78
|
+
const expression = args.expression ?? '';
|
|
79
|
+
let content: string;
|
|
80
|
+
try {
|
|
81
|
+
// eslint-disable-next-line no-eval
|
|
82
|
+
const result = eval(expression);
|
|
83
|
+
content = `${expression} = ${result}`;
|
|
84
|
+
} catch (err) {
|
|
85
|
+
content = `Error: ${String(err)}`;
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
toolCallId: call.id!,
|
|
89
|
+
status: 'success',
|
|
90
|
+
content,
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
data.resolve(results);
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
[GraphEvents.ON_RUN_STEP]: {
|
|
97
|
+
handle: (event, data): void => {
|
|
98
|
+
const d = data as { type?: string; runId?: string; agentId?: string };
|
|
99
|
+
console.log(
|
|
100
|
+
`[PARENT ${event}] type=${d.type} agentId=${d.agentId ?? '-'} runId=${d.runId ?? '-'}`
|
|
101
|
+
);
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
[GraphEvents.ON_RUN_STEP_COMPLETED]: {
|
|
105
|
+
handle: (event, data): void => {
|
|
106
|
+
const r = (
|
|
107
|
+
data as { result: { type: string; tool_call?: { name?: string } } }
|
|
108
|
+
).result;
|
|
109
|
+
console.log(
|
|
110
|
+
`[PARENT ${event}] type=${r.type} tool=${r.tool_call?.name ?? '-'}`
|
|
111
|
+
);
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
[GraphEvents.ON_SUBAGENT_UPDATE]: {
|
|
115
|
+
handle: (_event, rawData): void => {
|
|
116
|
+
const d = rawData as t.SubagentUpdateEvent;
|
|
117
|
+
console.log(
|
|
118
|
+
`[SUBAGENT ${d.phase}] [${d.subagentType}] tool_call_id=${d.parentToolCallId ?? '-'} ${d.label ?? ''}`
|
|
119
|
+
);
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const run = await Run.create<t.IState>({
|
|
125
|
+
runId: `sub-evt-${Date.now()}`,
|
|
126
|
+
graphConfig: { type: 'standard', agents: [parentAgent] },
|
|
127
|
+
customHandlers,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const question = new HumanMessage(
|
|
131
|
+
'Compute (42 * 58) + (13 ** 3). Use the self subagent, and have it use the calculator.'
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
console.log('User:', question.content, '\n');
|
|
135
|
+
|
|
136
|
+
await run.processStream(
|
|
137
|
+
{ messages: [question] },
|
|
138
|
+
{
|
|
139
|
+
configurable: { thread_id: `sub-evt` },
|
|
140
|
+
version: 'v2' as const,
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const msgs = (run.getRunMessages() ?? []) as BaseMessage[];
|
|
145
|
+
console.log('\n--- Run messages ---\n');
|
|
146
|
+
for (const msg of msgs) {
|
|
147
|
+
const type = msg._getType();
|
|
148
|
+
const name = 'name' in msg ? (msg as { name?: string }).name : undefined;
|
|
149
|
+
const content =
|
|
150
|
+
typeof msg.content === 'string'
|
|
151
|
+
? msg.content.slice(0, 400)
|
|
152
|
+
: JSON.stringify(msg.content).slice(0, 400);
|
|
153
|
+
const toolCalls =
|
|
154
|
+
'tool_calls' in msg
|
|
155
|
+
? (msg as { tool_calls?: Array<{ name: string; args: unknown }> })
|
|
156
|
+
.tool_calls
|
|
157
|
+
: undefined;
|
|
158
|
+
console.log(`[${type}]${name ? ` name=${name}` : ''}`);
|
|
159
|
+
if (toolCalls?.length) {
|
|
160
|
+
for (const tc of toolCalls) {
|
|
161
|
+
console.log(
|
|
162
|
+
` tool_call: ${tc.name}(${JSON.stringify(tc.args).slice(0, 150)})`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
console.log(` content: ${content}\n`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const subagentMsgs = msgs.filter(
|
|
170
|
+
(m) =>
|
|
171
|
+
m._getType() === 'tool' &&
|
|
172
|
+
(m as { name?: string }).name === Constants.SUBAGENT
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
console.log('--- Verification ---');
|
|
176
|
+
console.log(`subagent tool calls seen (parent): ${subagentMsgs.length}`);
|
|
177
|
+
console.log(
|
|
178
|
+
`ON_TOOL_EXECUTE dispatched (parent saw): ${toolCallCount} (expected >= 1 if subagent used calculator)`
|
|
179
|
+
);
|
|
180
|
+
if (subagentMsgs[0]) {
|
|
181
|
+
console.log(
|
|
182
|
+
`\nsubagent result:\n${(subagentMsgs[0].content as string).slice(0, 600)}`
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
main().catch((err) => {
|
|
188
|
+
console.error('Script error:', err);
|
|
189
|
+
process.exit(1);
|
|
190
|
+
});
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { config } from 'dotenv';
|
|
2
|
+
config();
|
|
3
|
+
|
|
4
|
+
import { HumanMessage } from '@langchain/core/messages';
|
|
5
|
+
import type { BaseMessage } from '@langchain/core/messages';
|
|
6
|
+
import { tool } from '@langchain/core/tools';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import type * as t from '@/types';
|
|
9
|
+
import { ChatModelStreamHandler } from '@/stream';
|
|
10
|
+
import { ToolEndHandler, ModelEndHandler } from '@/events';
|
|
11
|
+
import { Providers, GraphEvents, Constants } from '@/common';
|
|
12
|
+
import { Run } from '@/run';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Diagnostic: verify a self-spawned subagent can call parent's real tools.
|
|
16
|
+
* Expected before-fix: parent delegates to self; child cannot invoke calculator.
|
|
17
|
+
*/
|
|
18
|
+
const apiKey = process.env.OPENAI_API_KEY!;
|
|
19
|
+
if (!apiKey) {
|
|
20
|
+
console.error('Missing OPENAI_API_KEY');
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const calculator = tool(
|
|
25
|
+
async ({ expression }) => {
|
|
26
|
+
const result = eval(expression); // don't do this in prod
|
|
27
|
+
return `${expression} = ${result}`;
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'calculator',
|
|
31
|
+
description: 'Evaluate a math expression. Use for any arithmetic.',
|
|
32
|
+
schema: z.object({
|
|
33
|
+
expression: z.string().describe("A JS math expression, e.g. '42 * 58'"),
|
|
34
|
+
}),
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
async function main() {
|
|
39
|
+
console.log('=== Subagent Tool-Access Diagnostic ===\n');
|
|
40
|
+
|
|
41
|
+
const parentAgent: t.AgentInputs = {
|
|
42
|
+
agentId: 'supervisor',
|
|
43
|
+
provider: Providers.OPENAI,
|
|
44
|
+
clientOptions: { modelName: 'gpt-4o-mini', apiKey },
|
|
45
|
+
instructions: `You have calculator AND can spawn a "self" subagent in an isolated context.
|
|
46
|
+
For any arithmetic question that would bloat your context, spawn the "self" subagent with the math task.
|
|
47
|
+
The subagent must use the calculator tool — never estimate.`,
|
|
48
|
+
maxContextTokens: 8000,
|
|
49
|
+
tools: [calculator],
|
|
50
|
+
subagentConfigs: [
|
|
51
|
+
{
|
|
52
|
+
type: 'self',
|
|
53
|
+
self: true,
|
|
54
|
+
name: 'supervisor',
|
|
55
|
+
description:
|
|
56
|
+
'Spawn a copy of this agent in an isolated context for a focused math subtask.',
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const customHandlers: Record<string, t.EventHandler> = {
|
|
62
|
+
[GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
|
|
63
|
+
[GraphEvents.TOOL_END]: new ToolEndHandler(),
|
|
64
|
+
[GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
|
|
65
|
+
[GraphEvents.ON_RUN_STEP]: {
|
|
66
|
+
handle: (event, data): void => {
|
|
67
|
+
console.log(
|
|
68
|
+
`[PARENT EVENT] ${event}`,
|
|
69
|
+
JSON.stringify(data).slice(0, 200)
|
|
70
|
+
);
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
[GraphEvents.ON_RUN_STEP_COMPLETED]: {
|
|
74
|
+
handle: (event, data): void => {
|
|
75
|
+
console.log(
|
|
76
|
+
`[PARENT EVENT] ${event}`,
|
|
77
|
+
JSON.stringify(data).slice(0, 200)
|
|
78
|
+
);
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const run = await Run.create<t.IState>({
|
|
84
|
+
runId: `subagent-debug-${Date.now()}`,
|
|
85
|
+
graphConfig: { type: 'standard', agents: [parentAgent] },
|
|
86
|
+
customHandlers,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const question = new HumanMessage(
|
|
90
|
+
'Compute (42 * 58) + (13 ^ 3). Spawn the self subagent to do this, and have IT use calculator.'
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
console.log('User:', question.content, '\n');
|
|
94
|
+
|
|
95
|
+
await run.processStream(
|
|
96
|
+
{ messages: [question] },
|
|
97
|
+
{
|
|
98
|
+
configurable: { thread_id: `subagent-debug` },
|
|
99
|
+
version: 'v2' as const,
|
|
100
|
+
}
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const msgs = (run.getRunMessages() ?? []) as BaseMessage[];
|
|
104
|
+
console.log('\n--- Run messages ---\n');
|
|
105
|
+
for (const msg of msgs) {
|
|
106
|
+
const type = msg._getType();
|
|
107
|
+
const name = 'name' in msg ? (msg as { name?: string }).name : undefined;
|
|
108
|
+
const content =
|
|
109
|
+
typeof msg.content === 'string'
|
|
110
|
+
? msg.content.slice(0, 400)
|
|
111
|
+
: JSON.stringify(msg.content).slice(0, 400);
|
|
112
|
+
const toolCalls =
|
|
113
|
+
'tool_calls' in msg
|
|
114
|
+
? (msg as { tool_calls?: Array<{ name: string; args: unknown }> })
|
|
115
|
+
.tool_calls
|
|
116
|
+
: undefined;
|
|
117
|
+
console.log(`[${type}]${name ? ` name=${name}` : ''}`);
|
|
118
|
+
if (toolCalls?.length) {
|
|
119
|
+
for (const tc of toolCalls) {
|
|
120
|
+
console.log(
|
|
121
|
+
` tool_call: ${tc.name}(${JSON.stringify(tc.args).slice(0, 150)})`
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
console.log(` content: ${content}\n`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const subagentCalls = msgs.filter(
|
|
129
|
+
(m) =>
|
|
130
|
+
m._getType() === 'tool' &&
|
|
131
|
+
'name' in m &&
|
|
132
|
+
(m as { name?: string }).name === Constants.SUBAGENT
|
|
133
|
+
);
|
|
134
|
+
const calculatorCalls = msgs.filter(
|
|
135
|
+
(m) =>
|
|
136
|
+
m._getType() === 'tool' &&
|
|
137
|
+
'name' in m &&
|
|
138
|
+
(m as { name?: string }).name === 'calculator'
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
console.log('--- Verification ---');
|
|
142
|
+
console.log(`subagent tool calls seen (parent): ${subagentCalls.length}`);
|
|
143
|
+
console.log(
|
|
144
|
+
`calculator tool calls seen (parent): ${calculatorCalls.length} (expected: 0 if subagent did the math)`
|
|
145
|
+
);
|
|
146
|
+
if (subagentCalls.length > 0) {
|
|
147
|
+
const subResult = subagentCalls[0].content as string;
|
|
148
|
+
console.log(`\nsubagent result snippet:\n${subResult.slice(0, 600)}\n`);
|
|
149
|
+
if (/\berror\b/i.test(subResult) && /tool/i.test(subResult)) {
|
|
150
|
+
console.log('⚠️ BUG CONFIRMED: subagent result mentions tool error');
|
|
151
|
+
} else if (!/\d/.test(subResult)) {
|
|
152
|
+
console.log('⚠️ POSSIBLY BUGGY: subagent result has no numbers');
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
main().catch((err) => {
|
|
158
|
+
console.error('Script error:', err);
|
|
159
|
+
process.exit(1);
|
|
160
|
+
});
|