@librechat/agents 3.0.36 → 3.0.40
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 +71 -2
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/common/enum.cjs +2 -0
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/events.cjs +3 -0
- package/dist/cjs/events.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +5 -1
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/main.cjs +12 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +329 -0
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -0
- package/dist/cjs/tools/ToolNode.cjs +34 -3
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/ToolSearchRegex.cjs +455 -0
- package/dist/cjs/tools/ToolSearchRegex.cjs.map +1 -0
- package/dist/esm/agents/AgentContext.mjs +71 -2
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/common/enum.mjs +2 -0
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/events.mjs +4 -1
- package/dist/esm/events.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +5 -1
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/main.mjs +2 -0
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +324 -0
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -0
- package/dist/esm/tools/ToolNode.mjs +34 -3
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/ToolSearchRegex.mjs +448 -0
- package/dist/esm/tools/ToolSearchRegex.mjs.map +1 -0
- package/dist/types/agents/AgentContext.d.ts +25 -1
- package/dist/types/common/enum.d.ts +2 -0
- package/dist/types/graphs/Graph.d.ts +2 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/test/mockTools.d.ts +28 -0
- package/dist/types/tools/ProgrammaticToolCalling.d.ts +86 -0
- package/dist/types/tools/ToolNode.d.ts +7 -1
- package/dist/types/tools/ToolSearchRegex.d.ts +80 -0
- package/dist/types/types/graph.d.ts +7 -1
- package/dist/types/types/tools.d.ts +136 -0
- package/package.json +5 -1
- package/src/agents/AgentContext.ts +86 -0
- package/src/common/enum.ts +2 -0
- package/src/events.ts +5 -1
- package/src/graphs/Graph.ts +6 -0
- package/src/index.ts +2 -0
- package/src/scripts/code_exec_ptc.ts +277 -0
- package/src/scripts/programmatic_exec.ts +396 -0
- package/src/scripts/programmatic_exec_agent.ts +231 -0
- package/src/scripts/tool_search_regex.ts +162 -0
- package/src/test/mockTools.ts +366 -0
- package/src/tools/ProgrammaticToolCalling.ts +423 -0
- package/src/tools/ToolNode.ts +38 -4
- package/src/tools/ToolSearchRegex.ts +535 -0
- package/src/tools/__tests__/ProgrammaticToolCalling.integration.test.ts +318 -0
- package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +613 -0
- package/src/tools/__tests__/ToolSearchRegex.integration.test.ts +161 -0
- package/src/tools/__tests__/ToolSearchRegex.test.ts +232 -0
- package/src/types/graph.ts +7 -1
- package/src/types/tools.ts +166 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
// src/tools/__tests__/ProgrammaticToolCalling.integration.test.ts
|
|
2
|
+
/**
|
|
3
|
+
* Integration tests for Programmatic Tool Calling.
|
|
4
|
+
* These tests hit the LIVE Code API and verify end-to-end functionality.
|
|
5
|
+
*
|
|
6
|
+
* Run with: npm test -- ProgrammaticToolCalling.integration.test.ts
|
|
7
|
+
*/
|
|
8
|
+
import { config as dotenvConfig } from 'dotenv';
|
|
9
|
+
dotenvConfig();
|
|
10
|
+
|
|
11
|
+
import { describe, it, expect, beforeAll } from '@jest/globals';
|
|
12
|
+
import type * as t from '@/types';
|
|
13
|
+
import { createProgrammaticToolCallingTool } from '../ProgrammaticToolCalling';
|
|
14
|
+
import {
|
|
15
|
+
createGetTeamMembersTool,
|
|
16
|
+
createGetExpensesTool,
|
|
17
|
+
createGetWeatherTool,
|
|
18
|
+
createCalculatorTool,
|
|
19
|
+
createProgrammaticToolRegistry,
|
|
20
|
+
} from '@/test/mockTools';
|
|
21
|
+
|
|
22
|
+
describe('ProgrammaticToolCalling - Live API Integration', () => {
|
|
23
|
+
let ptcTool: ReturnType<typeof createProgrammaticToolCallingTool>;
|
|
24
|
+
let toolMap: t.ToolMap;
|
|
25
|
+
let toolDefinitions: t.LCTool[];
|
|
26
|
+
|
|
27
|
+
beforeAll(() => {
|
|
28
|
+
const apiKey = process.env.LIBRECHAT_CODE_API_KEY;
|
|
29
|
+
if (apiKey == null || apiKey === '') {
|
|
30
|
+
throw new Error(
|
|
31
|
+
'LIBRECHAT_CODE_API_KEY not set. Required for integration tests.'
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const tools = [
|
|
36
|
+
createGetTeamMembersTool(),
|
|
37
|
+
createGetExpensesTool(),
|
|
38
|
+
createGetWeatherTool(),
|
|
39
|
+
createCalculatorTool(),
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
toolMap = new Map(tools.map((t) => [t.name, t]));
|
|
43
|
+
toolDefinitions = Array.from(createProgrammaticToolRegistry().values());
|
|
44
|
+
|
|
45
|
+
ptcTool = createProgrammaticToolCallingTool({ apiKey });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('executes simple single tool call', async () => {
|
|
49
|
+
const args = {
|
|
50
|
+
code: `
|
|
51
|
+
result = await get_weather(city="San Francisco")
|
|
52
|
+
print(f"Temperature: {result['temperature']}°F")
|
|
53
|
+
print(f"Condition: {result['condition']}")
|
|
54
|
+
`,
|
|
55
|
+
tools: toolDefinitions,
|
|
56
|
+
};
|
|
57
|
+
const toolCall = {
|
|
58
|
+
name: 'programmatic_code_execution',
|
|
59
|
+
args,
|
|
60
|
+
toolMap,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const output = await ptcTool.invoke(args, { toolCall });
|
|
64
|
+
|
|
65
|
+
// When responseFormat is 'content_and_artifact', invoke() returns just the content
|
|
66
|
+
// The artifact is available via other methods or when used in a graph
|
|
67
|
+
expect(typeof output).toBe('string');
|
|
68
|
+
expect(output).toContain('stdout:');
|
|
69
|
+
expect(output).toContain('Temperature: 65°F');
|
|
70
|
+
expect(output).toContain('Condition: Foggy');
|
|
71
|
+
}, 10000);
|
|
72
|
+
|
|
73
|
+
it('executes sequential tool calls in a loop', async () => {
|
|
74
|
+
const args = {
|
|
75
|
+
code: `
|
|
76
|
+
team = await get_team_members()
|
|
77
|
+
print(f"Team size: {len(team)}")
|
|
78
|
+
|
|
79
|
+
total = 0
|
|
80
|
+
for member in team:
|
|
81
|
+
expenses = await get_expenses(user_id=member['id'])
|
|
82
|
+
member_total = sum(e['amount'] for e in expenses)
|
|
83
|
+
total += member_total
|
|
84
|
+
print(f"{member['name']}: \${member_total:.2f}")
|
|
85
|
+
|
|
86
|
+
print(f"Grand total: \${total:.2f}")
|
|
87
|
+
`,
|
|
88
|
+
tools: toolDefinitions,
|
|
89
|
+
};
|
|
90
|
+
const toolCall = {
|
|
91
|
+
name: 'programmatic_code_execution',
|
|
92
|
+
args,
|
|
93
|
+
toolMap,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const output = await ptcTool.invoke(args, { toolCall });
|
|
97
|
+
|
|
98
|
+
expect(output).toContain('Team size: 3');
|
|
99
|
+
expect(output).toContain('Alice:');
|
|
100
|
+
expect(output).toContain('Bob:');
|
|
101
|
+
expect(output).toContain('Charlie:');
|
|
102
|
+
expect(output).toContain('Grand total:');
|
|
103
|
+
}, 15000);
|
|
104
|
+
|
|
105
|
+
it('executes parallel tool calls with asyncio.gather', async () => {
|
|
106
|
+
const args = {
|
|
107
|
+
code: `
|
|
108
|
+
import asyncio
|
|
109
|
+
|
|
110
|
+
cities = ["San Francisco", "New York", "London"]
|
|
111
|
+
results = await asyncio.gather(*[
|
|
112
|
+
get_weather(city=city)
|
|
113
|
+
for city in cities
|
|
114
|
+
])
|
|
115
|
+
|
|
116
|
+
for city, weather in zip(cities, results):
|
|
117
|
+
print(f"{city}: {weather['temperature']}°F, {weather['condition']}")
|
|
118
|
+
`,
|
|
119
|
+
tools: toolDefinitions,
|
|
120
|
+
};
|
|
121
|
+
const toolCall = {
|
|
122
|
+
name: 'programmatic_code_execution',
|
|
123
|
+
args,
|
|
124
|
+
toolMap,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const output = await ptcTool.invoke(args, { toolCall });
|
|
128
|
+
|
|
129
|
+
expect(output).toContain('San Francisco: 65°F, Foggy');
|
|
130
|
+
expect(output).toContain('New York: 75°F, Sunny');
|
|
131
|
+
expect(output).toContain('London: 55°F, Rainy');
|
|
132
|
+
}, 10000);
|
|
133
|
+
|
|
134
|
+
it('handles conditional logic', async () => {
|
|
135
|
+
const args = {
|
|
136
|
+
code: `
|
|
137
|
+
team = await get_team_members()
|
|
138
|
+
high_spenders = []
|
|
139
|
+
|
|
140
|
+
for member in team:
|
|
141
|
+
expenses = await get_expenses(user_id=member['id'])
|
|
142
|
+
total = sum(e['amount'] for e in expenses)
|
|
143
|
+
if total > 300:
|
|
144
|
+
high_spenders.append((member['name'], total))
|
|
145
|
+
|
|
146
|
+
if high_spenders:
|
|
147
|
+
print("High spenders (over $300):")
|
|
148
|
+
for name, amount in high_spenders:
|
|
149
|
+
print(f" {name}: \${amount:.2f}")
|
|
150
|
+
else:
|
|
151
|
+
print("No high spenders found.")
|
|
152
|
+
`,
|
|
153
|
+
tools: toolDefinitions,
|
|
154
|
+
};
|
|
155
|
+
const toolCall = {
|
|
156
|
+
name: 'programmatic_code_execution',
|
|
157
|
+
args,
|
|
158
|
+
toolMap,
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const output = await ptcTool.invoke(args, { toolCall });
|
|
162
|
+
|
|
163
|
+
expect(output).toContain('High spenders');
|
|
164
|
+
expect(output).toContain('Bob:');
|
|
165
|
+
expect(output).toContain('Charlie:');
|
|
166
|
+
}, 15000);
|
|
167
|
+
|
|
168
|
+
it('handles early termination with break', async () => {
|
|
169
|
+
const args = {
|
|
170
|
+
code: `
|
|
171
|
+
team = await get_team_members()
|
|
172
|
+
|
|
173
|
+
for member in team:
|
|
174
|
+
expenses = await get_expenses(user_id=member['id'])
|
|
175
|
+
if any(e['category'] == 'equipment' for e in expenses):
|
|
176
|
+
print(f"First member with equipment: {member['name']}")
|
|
177
|
+
equipment_total = sum(e['amount'] for e in expenses if e['category'] == 'equipment')
|
|
178
|
+
print(f"Equipment total: \${equipment_total:.2f}")
|
|
179
|
+
break
|
|
180
|
+
else:
|
|
181
|
+
print("No equipment expenses found")
|
|
182
|
+
`,
|
|
183
|
+
tools: toolDefinitions,
|
|
184
|
+
};
|
|
185
|
+
const toolCall = {
|
|
186
|
+
name: 'programmatic_code_execution',
|
|
187
|
+
args,
|
|
188
|
+
toolMap,
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const output = await ptcTool.invoke(args, { toolCall });
|
|
192
|
+
|
|
193
|
+
expect(output).toContain('First member with equipment: Charlie');
|
|
194
|
+
expect(output).toContain('Equipment total: $500.00');
|
|
195
|
+
}, 15000);
|
|
196
|
+
|
|
197
|
+
it('handles tool execution errors gracefully', async () => {
|
|
198
|
+
const args = {
|
|
199
|
+
code: `
|
|
200
|
+
cities = ["San Francisco", "InvalidCity", "New York"]
|
|
201
|
+
|
|
202
|
+
for city in cities:
|
|
203
|
+
try:
|
|
204
|
+
weather = await get_weather(city=city)
|
|
205
|
+
print(f"{city}: {weather['temperature']}°F")
|
|
206
|
+
except Exception as e:
|
|
207
|
+
print(f"{city}: Error - {e}")
|
|
208
|
+
`,
|
|
209
|
+
tools: toolDefinitions,
|
|
210
|
+
};
|
|
211
|
+
const toolCall = {
|
|
212
|
+
name: 'programmatic_code_execution',
|
|
213
|
+
args,
|
|
214
|
+
toolMap,
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const output = await ptcTool.invoke(args, { toolCall });
|
|
218
|
+
|
|
219
|
+
expect(output).toContain('San Francisco: 65°F');
|
|
220
|
+
expect(output).toContain('InvalidCity: Error');
|
|
221
|
+
expect(output).toContain('New York: 75°F');
|
|
222
|
+
}, 15000);
|
|
223
|
+
|
|
224
|
+
it('uses calculator tool', async () => {
|
|
225
|
+
const args = {
|
|
226
|
+
code: `
|
|
227
|
+
result1 = await calculator(expression="2 + 2 * 3")
|
|
228
|
+
result2 = await calculator(expression="(10 + 5) / 3")
|
|
229
|
+
|
|
230
|
+
print(f"2 + 2 * 3 = {result1['result']}")
|
|
231
|
+
print(f"(10 + 5) / 3 = {result2['result']:.2f}")
|
|
232
|
+
`,
|
|
233
|
+
tools: toolDefinitions,
|
|
234
|
+
};
|
|
235
|
+
const toolCall = {
|
|
236
|
+
name: 'programmatic_code_execution',
|
|
237
|
+
args,
|
|
238
|
+
toolMap,
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const output = await ptcTool.invoke(args, { toolCall });
|
|
242
|
+
|
|
243
|
+
expect(output).toContain('2 + 2 * 3 = 8');
|
|
244
|
+
expect(output).toContain('(10 + 5) / 3 = 5.00');
|
|
245
|
+
}, 10000);
|
|
246
|
+
|
|
247
|
+
it('mixes parallel and sequential execution', async () => {
|
|
248
|
+
const args = {
|
|
249
|
+
code: `
|
|
250
|
+
import asyncio
|
|
251
|
+
|
|
252
|
+
# Step 1: Get team (sequential)
|
|
253
|
+
team = await get_team_members()
|
|
254
|
+
print(f"Fetched {len(team)} team members")
|
|
255
|
+
|
|
256
|
+
# Step 2: Get all expenses in parallel
|
|
257
|
+
all_expenses = await asyncio.gather(*[
|
|
258
|
+
get_expenses(user_id=member['id'])
|
|
259
|
+
for member in team
|
|
260
|
+
])
|
|
261
|
+
|
|
262
|
+
# Step 3: Process
|
|
263
|
+
print("\\nExpense summary:")
|
|
264
|
+
for member, expenses in zip(team, all_expenses):
|
|
265
|
+
total = sum(e['amount'] for e in expenses)
|
|
266
|
+
print(f" {member['name']}: \${total:.2f} ({len(expenses)} items)")
|
|
267
|
+
`,
|
|
268
|
+
tools: toolDefinitions,
|
|
269
|
+
};
|
|
270
|
+
const toolCall = {
|
|
271
|
+
name: 'programmatic_code_execution',
|
|
272
|
+
args,
|
|
273
|
+
toolMap,
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const output = await ptcTool.invoke(args, { toolCall });
|
|
277
|
+
|
|
278
|
+
expect(output).toContain('Fetched 3 team members');
|
|
279
|
+
expect(output).toContain('Expense summary:');
|
|
280
|
+
expect(output).toContain('Alice:');
|
|
281
|
+
expect(output).toContain('Bob:');
|
|
282
|
+
expect(output).toContain('Charlie:');
|
|
283
|
+
expect(output).toContain('(2 items)');
|
|
284
|
+
expect(output).toContain('(3 items)');
|
|
285
|
+
}, 15000);
|
|
286
|
+
|
|
287
|
+
it('uses only provided tool definitions (subset)', async () => {
|
|
288
|
+
const weatherToolDef = toolDefinitions.find(
|
|
289
|
+
(t) => t.name === 'get_weather'
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
const args = {
|
|
293
|
+
code: `
|
|
294
|
+
import asyncio
|
|
295
|
+
|
|
296
|
+
sf, nyc = await asyncio.gather(
|
|
297
|
+
get_weather(city="San Francisco"),
|
|
298
|
+
get_weather(city="New York")
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
print(f"SF: {sf['temperature']}°F vs NYC: {nyc['temperature']}°F")
|
|
302
|
+
difference = abs(sf['temperature'] - nyc['temperature'])
|
|
303
|
+
print(f"Temperature difference: {difference}°F")
|
|
304
|
+
`,
|
|
305
|
+
tools: [weatherToolDef!],
|
|
306
|
+
};
|
|
307
|
+
const toolCall = {
|
|
308
|
+
name: 'programmatic_code_execution',
|
|
309
|
+
args,
|
|
310
|
+
toolMap,
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
const output = await ptcTool.invoke(args, { toolCall });
|
|
314
|
+
|
|
315
|
+
expect(output).toContain('SF: 65°F vs NYC: 75°F');
|
|
316
|
+
expect(output).toContain('Temperature difference: 10°F');
|
|
317
|
+
}, 10000);
|
|
318
|
+
});
|