@librechat/agents 3.0.75 → 3.0.76
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/graphs/Graph.cjs +34 -0
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +22 -21
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +14 -11
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +28 -1
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +35 -1
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +22 -21
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +14 -11
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +28 -1
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/types/graphs/Graph.d.ts +6 -0
- package/dist/types/tools/CodeExecutor.d.ts +0 -3
- package/dist/types/tools/ProgrammaticToolCalling.d.ts +0 -3
- package/dist/types/tools/ToolNode.d.ts +3 -1
- package/dist/types/types/tools.d.ts +32 -0
- package/package.json +3 -2
- package/src/graphs/Graph.ts +44 -0
- package/src/scripts/code_exec_files.ts +58 -15
- package/src/scripts/code_exec_session.ts +282 -0
- package/src/scripts/test_code_api.ts +361 -0
- package/src/tools/CodeExecutor.ts +26 -23
- package/src/tools/ProgrammaticToolCalling.ts +18 -14
- package/src/tools/ToolNode.ts +33 -0
- package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +0 -2
- package/src/types/tools.ts +40 -0
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
// src/scripts/
|
|
1
|
+
// src/scripts/code_exec_files.ts
|
|
2
|
+
/**
|
|
3
|
+
* Tests automatic session tracking for code execution file persistence.
|
|
4
|
+
* Files created in one execution are automatically available in subsequent executions
|
|
5
|
+
* without the LLM needing to track or pass session_id.
|
|
6
|
+
*
|
|
7
|
+
* Run with: npm run code_exec_files
|
|
8
|
+
*/
|
|
2
9
|
import { config } from 'dotenv';
|
|
3
10
|
config();
|
|
4
11
|
import { HumanMessage, BaseMessage } from '@langchain/core/messages';
|
|
@@ -12,12 +19,39 @@ import {
|
|
|
12
19
|
} from '@/events';
|
|
13
20
|
import { getLLMConfig } from '@/utils/llmConfig';
|
|
14
21
|
import { getArgs } from '@/scripts/args';
|
|
15
|
-
import { GraphEvents } from '@/common';
|
|
22
|
+
import { Constants, GraphEvents } from '@/common';
|
|
16
23
|
import { Run } from '@/run';
|
|
17
24
|
import { createCodeExecutionTool } from '@/tools/CodeExecutor';
|
|
18
25
|
|
|
19
26
|
const conversationHistory: BaseMessage[] = [];
|
|
20
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Prints session context from the graph for debugging
|
|
30
|
+
*/
|
|
31
|
+
function printSessionContext(run: Run<t.IState>): void {
|
|
32
|
+
const graph = run.Graph;
|
|
33
|
+
if (!graph) {
|
|
34
|
+
console.log('[Session] No graph available');
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const session = graph.sessions.get(Constants.EXECUTE_CODE) as
|
|
39
|
+
| t.CodeSessionContext
|
|
40
|
+
| undefined;
|
|
41
|
+
|
|
42
|
+
if (!session) {
|
|
43
|
+
console.log('[Session] No session context stored yet');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
console.log('[Session] Current session context:');
|
|
48
|
+
console.log(` - session_id: ${session.session_id}`);
|
|
49
|
+
console.log(` - files: ${JSON.stringify(session.files, null, 2)}`);
|
|
50
|
+
console.log(
|
|
51
|
+
` - lastUpdated: ${new Date(session.lastUpdated).toISOString()}`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
21
55
|
async function testCodeExecution(): Promise<void> {
|
|
22
56
|
const { userName, location, provider, currentDate } = await getArgs();
|
|
23
57
|
const { contentParts, aggregateContent } = createContentAggregator();
|
|
@@ -72,7 +106,7 @@ async function testCodeExecution(): Promise<void> {
|
|
|
72
106
|
handle: (
|
|
73
107
|
_event: string,
|
|
74
108
|
data: t.StreamEventData,
|
|
75
|
-
|
|
109
|
+
_metadata?: Record<string, unknown>
|
|
76
110
|
): void => {
|
|
77
111
|
console.log('====== TOOL_START ======');
|
|
78
112
|
console.dir(data, { depth: null });
|
|
@@ -96,7 +130,7 @@ async function testCodeExecution(): Promise<void> {
|
|
|
96
130
|
customHandlers,
|
|
97
131
|
});
|
|
98
132
|
|
|
99
|
-
const
|
|
133
|
+
const streamConfig: Partial<RunnableConfig> & {
|
|
100
134
|
version: 'v1' | 'v2';
|
|
101
135
|
run_id?: string;
|
|
102
136
|
streamMode: string;
|
|
@@ -107,10 +141,12 @@ async function testCodeExecution(): Promise<void> {
|
|
|
107
141
|
},
|
|
108
142
|
streamMode: 'values',
|
|
109
143
|
version: 'v2' as const,
|
|
110
|
-
// recursionLimit: 3,
|
|
111
144
|
};
|
|
112
145
|
|
|
113
|
-
console.log('Test 1: Create Project Plan');
|
|
146
|
+
console.log('\n========== Test 1: Create Project Plan ==========\n');
|
|
147
|
+
console.log(
|
|
148
|
+
'Creating initial file - this establishes the session context.\n'
|
|
149
|
+
);
|
|
114
150
|
|
|
115
151
|
const userMessage1 = `
|
|
116
152
|
Hi ${userName} here. We are testing your file capabilities.
|
|
@@ -125,36 +161,43 @@ async function testCodeExecution(): Promise<void> {
|
|
|
125
161
|
let inputs = {
|
|
126
162
|
messages: conversationHistory,
|
|
127
163
|
};
|
|
128
|
-
|
|
164
|
+
await run.processStream(inputs, streamConfig);
|
|
129
165
|
const finalMessages1 = run.getRunMessages();
|
|
130
166
|
if (finalMessages1) {
|
|
131
167
|
conversationHistory.push(...finalMessages1);
|
|
132
168
|
}
|
|
133
|
-
|
|
169
|
+
|
|
170
|
+
console.log('\n\n========== Session Context After Test 1 ==========\n');
|
|
171
|
+
printSessionContext(run);
|
|
134
172
|
console.dir(contentParts, { depth: null });
|
|
135
173
|
|
|
136
|
-
console.log('Test 2: Edit Project Plan');
|
|
174
|
+
console.log('\n========== Test 2: Edit Project Plan ==========\n');
|
|
175
|
+
console.log(
|
|
176
|
+
'Editing the file from Test 1 - session_id is automatically injected.\n'
|
|
177
|
+
);
|
|
137
178
|
|
|
138
179
|
const userMessage2 = `
|
|
139
180
|
Thanks for creating the project plan. Now I'd like you to edit the same plan to:
|
|
140
181
|
|
|
141
|
-
1.
|
|
142
|
-
|
|
182
|
+
1. Read the existing project_plan.txt file
|
|
183
|
+
2. Add a new section called "Technology Stack" that contains: "The technology stack for this project includes the following technologies" and nothing more.
|
|
184
|
+
3. Save this as a new file called "project_plan_v2.txt" (remember files are read-only)
|
|
185
|
+
4. Print the contents of both files to verify
|
|
143
186
|
`;
|
|
144
187
|
|
|
145
|
-
// Make sure to pass the file ID of the previous file you created and explicitly duplicate or rename the file in your code so we can then access it. Also print the contents of the new file to ensure we did what we wanted.`;
|
|
146
|
-
|
|
147
188
|
conversationHistory.push(new HumanMessage(userMessage2));
|
|
148
189
|
|
|
149
190
|
inputs = {
|
|
150
191
|
messages: conversationHistory,
|
|
151
192
|
};
|
|
152
|
-
|
|
193
|
+
await run.processStream(inputs, streamConfig);
|
|
153
194
|
const finalMessages2 = run.getRunMessages();
|
|
154
195
|
if (finalMessages2) {
|
|
155
196
|
conversationHistory.push(...finalMessages2);
|
|
156
197
|
}
|
|
157
|
-
|
|
198
|
+
|
|
199
|
+
console.log('\n\n========== Session Context After Test 2 ==========\n');
|
|
200
|
+
printSessionContext(run);
|
|
158
201
|
console.dir(contentParts, { depth: null });
|
|
159
202
|
|
|
160
203
|
const { handleLLMEnd, collected } = createMetadataAggregator();
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
// src/scripts/code_exec_session.ts
|
|
2
|
+
/**
|
|
3
|
+
* Test script for automatic session tracking in code execution tools.
|
|
4
|
+
*
|
|
5
|
+
* This tests the automatic session_id injection feature where:
|
|
6
|
+
* 1. First code execution generates files and returns a session_id
|
|
7
|
+
* 2. Session context is stored in Graph.sessions
|
|
8
|
+
* 3. Subsequent code executions automatically have access to previous files
|
|
9
|
+
* without the LLM needing to explicitly pass session_id
|
|
10
|
+
*
|
|
11
|
+
* Run with: npm run code_exec_session
|
|
12
|
+
*/
|
|
13
|
+
import { config } from 'dotenv';
|
|
14
|
+
config();
|
|
15
|
+
import { HumanMessage, BaseMessage } from '@langchain/core/messages';
|
|
16
|
+
import type { RunnableConfig } from '@langchain/core/runnables';
|
|
17
|
+
import type * as t from '@/types';
|
|
18
|
+
import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
|
|
19
|
+
import {
|
|
20
|
+
ToolEndHandler,
|
|
21
|
+
ModelEndHandler,
|
|
22
|
+
createMetadataAggregator,
|
|
23
|
+
} from '@/events';
|
|
24
|
+
import { getLLMConfig } from '@/utils/llmConfig';
|
|
25
|
+
import { getArgs } from '@/scripts/args';
|
|
26
|
+
import { Constants, GraphEvents } from '@/common';
|
|
27
|
+
import { Run } from '@/run';
|
|
28
|
+
import { createCodeExecutionTool } from '@/tools/CodeExecutor';
|
|
29
|
+
|
|
30
|
+
const conversationHistory: BaseMessage[] = [];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Prints a formatted section header for test output
|
|
34
|
+
*/
|
|
35
|
+
function printSection(title: string): void {
|
|
36
|
+
console.log('\n' + '='.repeat(60));
|
|
37
|
+
console.log(` ${title}`);
|
|
38
|
+
console.log('='.repeat(60) + '\n');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Prints session context from the graph for debugging
|
|
43
|
+
*/
|
|
44
|
+
function printSessionContext(run: Run<t.IState>): void {
|
|
45
|
+
const graph = run.Graph;
|
|
46
|
+
if (!graph) {
|
|
47
|
+
console.log('[Session] No graph available');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const session = graph.sessions.get(Constants.EXECUTE_CODE) as
|
|
52
|
+
| t.CodeSessionContext
|
|
53
|
+
| undefined;
|
|
54
|
+
|
|
55
|
+
if (!session) {
|
|
56
|
+
console.log('[Session] No session context stored yet');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log('[Session] Current session context:');
|
|
61
|
+
console.log(` - session_id: ${session.session_id}`);
|
|
62
|
+
console.log(` - files: ${JSON.stringify(session.files, null, 2)}`);
|
|
63
|
+
console.log(
|
|
64
|
+
` - lastUpdated: ${new Date(session.lastUpdated).toISOString()}`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function testAutomaticSessionTracking(): Promise<void> {
|
|
69
|
+
const { userName, location, provider, currentDate } = await getArgs();
|
|
70
|
+
const { contentParts, aggregateContent } = createContentAggregator();
|
|
71
|
+
|
|
72
|
+
const customHandlers = {
|
|
73
|
+
[GraphEvents.TOOL_END]: new ToolEndHandler(),
|
|
74
|
+
[GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
|
|
75
|
+
[GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
|
|
76
|
+
[GraphEvents.ON_RUN_STEP_COMPLETED]: {
|
|
77
|
+
handle: (
|
|
78
|
+
event: GraphEvents.ON_RUN_STEP_COMPLETED,
|
|
79
|
+
data: t.StreamEventData
|
|
80
|
+
): void => {
|
|
81
|
+
console.log('====== ON_RUN_STEP_COMPLETED ======');
|
|
82
|
+
console.dir(data, { depth: null });
|
|
83
|
+
aggregateContent({
|
|
84
|
+
event,
|
|
85
|
+
data: data as unknown as { result: t.ToolEndEvent },
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
[GraphEvents.ON_RUN_STEP]: {
|
|
90
|
+
handle: (
|
|
91
|
+
event: GraphEvents.ON_RUN_STEP,
|
|
92
|
+
data: t.StreamEventData
|
|
93
|
+
): void => {
|
|
94
|
+
console.log('====== ON_RUN_STEP ======');
|
|
95
|
+
console.dir(data, { depth: null });
|
|
96
|
+
aggregateContent({ event, data: data as t.RunStep });
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
[GraphEvents.ON_RUN_STEP_DELTA]: {
|
|
100
|
+
handle: (
|
|
101
|
+
event: GraphEvents.ON_RUN_STEP_DELTA,
|
|
102
|
+
data: t.StreamEventData
|
|
103
|
+
): void => {
|
|
104
|
+
aggregateContent({ event, data: data as t.RunStepDeltaEvent });
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
[GraphEvents.ON_MESSAGE_DELTA]: {
|
|
108
|
+
handle: (
|
|
109
|
+
event: GraphEvents.ON_MESSAGE_DELTA,
|
|
110
|
+
data: t.StreamEventData
|
|
111
|
+
): void => {
|
|
112
|
+
aggregateContent({ event, data: data as t.MessageDeltaEvent });
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
[GraphEvents.TOOL_START]: {
|
|
116
|
+
handle: (
|
|
117
|
+
_event: string,
|
|
118
|
+
data: t.StreamEventData,
|
|
119
|
+
_metadata?: Record<string, unknown>
|
|
120
|
+
): void => {
|
|
121
|
+
console.log('====== TOOL_START ======');
|
|
122
|
+
console.dir(data, { depth: null });
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const llmConfig = getLLMConfig(provider);
|
|
128
|
+
|
|
129
|
+
const run = await Run.create<t.IState>({
|
|
130
|
+
runId: 'session-tracking-test-1',
|
|
131
|
+
graphConfig: {
|
|
132
|
+
type: 'standard',
|
|
133
|
+
llmConfig,
|
|
134
|
+
tools: [createCodeExecutionTool()],
|
|
135
|
+
instructions: `You are an AI assistant testing automatic file persistence.
|
|
136
|
+
When writing Python code:
|
|
137
|
+
- Use print() for all outputs
|
|
138
|
+
- Files from previous executions are automatically available in /mnt/data/
|
|
139
|
+
- Files are READ-ONLY; write modifications to NEW filenames
|
|
140
|
+
- IMPORTANT: Do NOT include session_id in your tool calls - it's handled automatically.`,
|
|
141
|
+
additional_instructions: `User: ${userName}, Location: ${location}, Date: ${currentDate}.`,
|
|
142
|
+
},
|
|
143
|
+
returnContent: true,
|
|
144
|
+
customHandlers,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const streamConfig: Partial<RunnableConfig> & {
|
|
148
|
+
version: 'v1' | 'v2';
|
|
149
|
+
run_id?: string;
|
|
150
|
+
streamMode: string;
|
|
151
|
+
} = {
|
|
152
|
+
configurable: {
|
|
153
|
+
provider,
|
|
154
|
+
thread_id: 'session-tracking-test',
|
|
155
|
+
},
|
|
156
|
+
streamMode: 'values',
|
|
157
|
+
version: 'v2' as const,
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// =========================================================================
|
|
161
|
+
// Test 1: Create initial file (establishes session)
|
|
162
|
+
// =========================================================================
|
|
163
|
+
printSection('Test 1: Create Initial File');
|
|
164
|
+
console.log(
|
|
165
|
+
'This test creates a file, which should establish a session context.\n'
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const userMessage1 = `
|
|
169
|
+
Create a Python file that writes a simple JSON config file named "app_config.json" with the following content:
|
|
170
|
+
{
|
|
171
|
+
"app_name": "TestApp",
|
|
172
|
+
"version": "1.0.0",
|
|
173
|
+
"debug": true
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
After writing, print the contents to confirm it was created correctly.
|
|
177
|
+
`;
|
|
178
|
+
|
|
179
|
+
conversationHistory.push(new HumanMessage(userMessage1));
|
|
180
|
+
await run.processStream({ messages: conversationHistory }, streamConfig);
|
|
181
|
+
|
|
182
|
+
const finalMessages1 = run.getRunMessages();
|
|
183
|
+
if (finalMessages1) {
|
|
184
|
+
conversationHistory.push(...finalMessages1);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
printSection('Session Context After Test 1');
|
|
188
|
+
printSessionContext(run);
|
|
189
|
+
|
|
190
|
+
// =========================================================================
|
|
191
|
+
// Test 2: Access previously created file (uses automatic session injection)
|
|
192
|
+
// =========================================================================
|
|
193
|
+
printSection('Test 2: Access Previous File (Automatic Session)');
|
|
194
|
+
console.log('This test reads the file created in Test 1.');
|
|
195
|
+
console.log(
|
|
196
|
+
'The LLM does NOT need to provide session_id - it should be injected automatically.\n'
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
const userMessage2 = `
|
|
200
|
+
Now read the app_config.json file that was just created and:
|
|
201
|
+
1. Print its contents
|
|
202
|
+
2. Confirm the version is "1.0.0"
|
|
203
|
+
|
|
204
|
+
Note: You should be able to access this file from the previous execution automatically.
|
|
205
|
+
`;
|
|
206
|
+
|
|
207
|
+
conversationHistory.push(new HumanMessage(userMessage2));
|
|
208
|
+
await run.processStream({ messages: conversationHistory }, streamConfig);
|
|
209
|
+
|
|
210
|
+
const finalMessages2 = run.getRunMessages();
|
|
211
|
+
if (finalMessages2) {
|
|
212
|
+
conversationHistory.push(...finalMessages2);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
printSection('Session Context After Test 2');
|
|
216
|
+
printSessionContext(run);
|
|
217
|
+
|
|
218
|
+
// =========================================================================
|
|
219
|
+
// Test 3: Modify file (write to new filename)
|
|
220
|
+
// =========================================================================
|
|
221
|
+
printSection('Test 3: Modify File (Write to New Filename)');
|
|
222
|
+
console.log(
|
|
223
|
+
'This test modifies the config by reading the old file and writing a new one.\n'
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
const userMessage3 = `
|
|
227
|
+
Read app_config.json, update the version to "2.0.0" and debug to false,
|
|
228
|
+
then save it as "app_config_v2.json". Print both the old and new contents.
|
|
229
|
+
`;
|
|
230
|
+
|
|
231
|
+
conversationHistory.push(new HumanMessage(userMessage3));
|
|
232
|
+
await run.processStream({ messages: conversationHistory }, streamConfig);
|
|
233
|
+
|
|
234
|
+
const finalMessages3 = run.getRunMessages();
|
|
235
|
+
if (finalMessages3) {
|
|
236
|
+
conversationHistory.push(...finalMessages3);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
printSection('Session Context After Test 3');
|
|
240
|
+
printSessionContext(run);
|
|
241
|
+
|
|
242
|
+
// =========================================================================
|
|
243
|
+
// Summary
|
|
244
|
+
// =========================================================================
|
|
245
|
+
printSection('Test Summary');
|
|
246
|
+
console.log('The automatic session tracking feature should have:');
|
|
247
|
+
console.log('1. Stored the session_id after the first code execution');
|
|
248
|
+
console.log('2. Automatically injected it into subsequent executions');
|
|
249
|
+
console.log('3. Accumulated file references across all executions');
|
|
250
|
+
console.log('\nCheck the session context output above to verify.\n');
|
|
251
|
+
|
|
252
|
+
// Generate title
|
|
253
|
+
const { handleLLMEnd, collected } = createMetadataAggregator();
|
|
254
|
+
const titleResult = await run.generateTitle({
|
|
255
|
+
provider,
|
|
256
|
+
inputText: 'Testing automatic session tracking for code execution',
|
|
257
|
+
contentParts,
|
|
258
|
+
chainOptions: {
|
|
259
|
+
callbacks: [{ handleLLMEnd }],
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
console.log('Generated Title:', titleResult);
|
|
263
|
+
console.log('Collected metadata:', collected);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
267
|
+
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
268
|
+
console.log('Conversation history:');
|
|
269
|
+
console.dir(conversationHistory, { depth: null });
|
|
270
|
+
process.exit(1);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
process.on('uncaughtException', (err) => {
|
|
274
|
+
console.error('Uncaught Exception:', err);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
testAutomaticSessionTracking().catch((err) => {
|
|
278
|
+
console.error(err);
|
|
279
|
+
console.log('Conversation history:');
|
|
280
|
+
console.dir(conversationHistory, { depth: null });
|
|
281
|
+
process.exit(1);
|
|
282
|
+
});
|