@librechat/agents 3.1.0 → 3.1.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 +2 -5
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +1 -0
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +26 -17
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +1 -0
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +3 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +37 -27
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +21 -17
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +1 -0
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/ToolSearch.cjs +37 -30
- package/dist/cjs/tools/ToolSearch.cjs.map +1 -1
- package/dist/cjs/tools/search/schema.cjs +25 -23
- package/dist/cjs/tools/search/schema.cjs.map +1 -1
- package/dist/cjs/tools/search/tool.cjs +9 -33
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/cjs/utils/schema.cjs +27 -0
- package/dist/cjs/utils/schema.cjs.map +1 -0
- package/dist/cjs/utils/title.cjs +28 -14
- package/dist/cjs/utils/title.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs +2 -5
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +1 -0
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +26 -17
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +1 -0
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/main.mjs +1 -0
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +37 -27
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +21 -17
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +1 -0
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/ToolSearch.mjs +37 -30
- package/dist/esm/tools/ToolSearch.mjs.map +1 -1
- package/dist/esm/tools/search/schema.mjs +25 -23
- package/dist/esm/tools/search/schema.mjs.map +1 -1
- package/dist/esm/tools/search/tool.mjs +10 -34
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/esm/utils/schema.mjs +24 -0
- package/dist/esm/utils/schema.mjs.map +1 -0
- package/dist/esm/utils/title.mjs +28 -14
- package/dist/esm/utils/title.mjs.map +1 -1
- package/dist/types/tools/CodeExecutor.d.ts +1 -15
- package/dist/types/tools/ProgrammaticToolCalling.d.ts +1 -13
- package/dist/types/tools/ToolSearch.d.ts +1 -15
- package/dist/types/tools/search/schema.d.ts +25 -7
- package/dist/types/tools/search/tool.d.ts +1 -52
- package/dist/types/tools/search/types.d.ts +5 -23
- package/dist/types/utils/index.d.ts +1 -0
- package/dist/types/utils/schema.d.ts +8 -0
- package/package.json +1 -1
- package/src/agents/AgentContext.ts +5 -11
- package/src/graphs/MultiAgentGraph.ts +26 -17
- package/src/specs/agent-handoffs.test.ts +1 -2
- package/src/specs/azure.simple.test.ts +214 -175
- package/src/specs/tool-error.test.ts +7 -2
- package/src/test/mockTools.ts +34 -14
- package/src/tools/CodeExecutor.ts +48 -31
- package/src/tools/ProgrammaticToolCalling.ts +24 -23
- package/src/tools/ToolSearch.ts +54 -43
- package/src/tools/__tests__/ProgrammaticToolCalling.integration.test.ts +10 -9
- package/src/tools/__tests__/ToolSearch.integration.test.ts +10 -9
- package/src/tools/search/schema.ts +30 -25
- package/src/tools/search/tool.ts +23 -16
- package/src/tools/search/types.ts +5 -29
- package/src/utils/index.ts +1 -0
- package/src/utils/schema.ts +35 -0
- package/src/utils/title.ts +31 -19
|
@@ -19,7 +19,6 @@ import { ContentTypes, GraphEvents, Providers, TitleMethod } from '@/common';
|
|
|
19
19
|
import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
|
|
20
20
|
import { capitalizeFirstLetter } from './spec.utils';
|
|
21
21
|
import { getLLMConfig } from '@/utils/llmConfig';
|
|
22
|
-
import { getArgs } from '@/scripts/args';
|
|
23
22
|
import { Run } from '@/run';
|
|
24
23
|
|
|
25
24
|
// Auto-skip this suite if Azure env vars are not present
|
|
@@ -34,15 +33,24 @@ const hasAzure = requiredAzureEnv.every(
|
|
|
34
33
|
);
|
|
35
34
|
const describeIfAzure = hasAzure ? describe : describe.skip;
|
|
36
35
|
|
|
36
|
+
const isContentFilterError = (error: unknown): boolean => {
|
|
37
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
38
|
+
return (
|
|
39
|
+
message.includes('content management policy') ||
|
|
40
|
+
message.includes('content filtering')
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
37
44
|
const provider = Providers.AZURE;
|
|
45
|
+
let contentFilterTriggered = false;
|
|
38
46
|
describeIfAzure(`${capitalizeFirstLetter(provider)} Streaming Tests`, () => {
|
|
39
47
|
jest.setTimeout(30000);
|
|
40
48
|
let run: Run<t.IState>;
|
|
41
|
-
let runningHistory: BaseMessage[];
|
|
42
49
|
let collectedUsage: UsageMetadata[];
|
|
43
50
|
let conversationHistory: BaseMessage[];
|
|
44
51
|
let aggregateContent: t.ContentAggregator;
|
|
45
52
|
let contentParts: t.MessageContentComplex[];
|
|
53
|
+
let runningHistory: BaseMessage[] | null = null;
|
|
46
54
|
|
|
47
55
|
const config = {
|
|
48
56
|
configurable: {
|
|
@@ -129,186 +137,217 @@ describeIfAzure(`${capitalizeFirstLetter(provider)} Streaming Tests`, () => {
|
|
|
129
137
|
});
|
|
130
138
|
|
|
131
139
|
test(`${capitalizeFirstLetter(provider)}: should process a simple message, generate title`, async () => {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
const userMessage = 'hi';
|
|
151
|
-
conversationHistory.push(new HumanMessage(userMessage));
|
|
152
|
-
|
|
153
|
-
const inputs = {
|
|
154
|
-
messages: conversationHistory,
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
const finalContentParts = await run.processStream(inputs, config);
|
|
158
|
-
expect(finalContentParts).toBeDefined();
|
|
159
|
-
const allTextParts = finalContentParts?.every(
|
|
160
|
-
(part) => part.type === ContentTypes.TEXT
|
|
161
|
-
);
|
|
162
|
-
expect(allTextParts).toBe(true);
|
|
163
|
-
expect(collectedUsage.length).toBeGreaterThan(0);
|
|
164
|
-
expect(collectedUsage[0].input_tokens).toBeGreaterThan(0);
|
|
165
|
-
expect(collectedUsage[0].output_tokens).toBeGreaterThan(0);
|
|
166
|
-
|
|
167
|
-
const finalMessages = run.getRunMessages();
|
|
168
|
-
expect(finalMessages).toBeDefined();
|
|
169
|
-
conversationHistory.push(...(finalMessages ?? []));
|
|
170
|
-
expect(conversationHistory.length).toBeGreaterThan(1);
|
|
171
|
-
runningHistory = conversationHistory.slice();
|
|
172
|
-
|
|
173
|
-
expect(onMessageDeltaSpy).toHaveBeenCalled();
|
|
174
|
-
expect(onMessageDeltaSpy.mock.calls.length).toBeGreaterThan(1);
|
|
175
|
-
expect(onMessageDeltaSpy.mock.calls[0][3]).toBeDefined(); // Graph exists
|
|
176
|
-
|
|
177
|
-
expect(onRunStepSpy).toHaveBeenCalled();
|
|
178
|
-
expect(onRunStepSpy.mock.calls.length).toBeGreaterThan(0);
|
|
179
|
-
expect(onRunStepSpy.mock.calls[0][3]).toBeDefined(); // Graph exists
|
|
180
|
-
|
|
181
|
-
const { handleLLMEnd, collected } = createMetadataAggregator();
|
|
182
|
-
const titleResult = await run.generateTitle({
|
|
183
|
-
provider,
|
|
184
|
-
inputText: userMessage,
|
|
185
|
-
titleMethod: TitleMethod.STRUCTURED,
|
|
186
|
-
contentParts,
|
|
187
|
-
clientOptions: llmConfig,
|
|
188
|
-
chainOptions: {
|
|
189
|
-
callbacks: [
|
|
190
|
-
{
|
|
191
|
-
handleLLMEnd,
|
|
192
|
-
},
|
|
193
|
-
],
|
|
194
|
-
},
|
|
195
|
-
});
|
|
140
|
+
try {
|
|
141
|
+
const llmConfig = getLLMConfig(provider);
|
|
142
|
+
const customHandlers = setupCustomHandlers();
|
|
143
|
+
|
|
144
|
+
run = await Run.create<t.IState>({
|
|
145
|
+
runId: 'test-run-id',
|
|
146
|
+
graphConfig: {
|
|
147
|
+
type: 'standard',
|
|
148
|
+
llmConfig,
|
|
149
|
+
tools: [new Calculator()],
|
|
150
|
+
instructions:
|
|
151
|
+
'You are a helpful AI assistant. Keep responses concise and friendly.',
|
|
152
|
+
},
|
|
153
|
+
returnContent: true,
|
|
154
|
+
customHandlers,
|
|
155
|
+
});
|
|
196
156
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
157
|
+
const userMessage = 'Hello, how are you today?';
|
|
158
|
+
conversationHistory.push(new HumanMessage(userMessage));
|
|
159
|
+
|
|
160
|
+
const inputs = {
|
|
161
|
+
messages: conversationHistory,
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const finalContentParts = await run.processStream(inputs, config);
|
|
165
|
+
expect(finalContentParts).toBeDefined();
|
|
166
|
+
const allTextParts = finalContentParts?.every(
|
|
167
|
+
(part) => part.type === ContentTypes.TEXT
|
|
168
|
+
);
|
|
169
|
+
expect(allTextParts).toBe(true);
|
|
170
|
+
expect(collectedUsage.length).toBeGreaterThan(0);
|
|
171
|
+
expect(collectedUsage[0].input_tokens).toBeGreaterThan(0);
|
|
172
|
+
expect(collectedUsage[0].output_tokens).toBeGreaterThan(0);
|
|
173
|
+
|
|
174
|
+
const finalMessages = run.getRunMessages();
|
|
175
|
+
expect(finalMessages).toBeDefined();
|
|
176
|
+
conversationHistory.push(...(finalMessages ?? []));
|
|
177
|
+
expect(conversationHistory.length).toBeGreaterThan(1);
|
|
178
|
+
runningHistory = conversationHistory.slice();
|
|
179
|
+
|
|
180
|
+
expect(onMessageDeltaSpy).toHaveBeenCalled();
|
|
181
|
+
expect(onMessageDeltaSpy.mock.calls.length).toBeGreaterThan(1);
|
|
182
|
+
expect(onMessageDeltaSpy.mock.calls[0][3]).toBeDefined(); // Graph exists
|
|
183
|
+
|
|
184
|
+
expect(onRunStepSpy).toHaveBeenCalled();
|
|
185
|
+
expect(onRunStepSpy.mock.calls.length).toBeGreaterThan(0);
|
|
186
|
+
expect(onRunStepSpy.mock.calls[0][3]).toBeDefined(); // Graph exists
|
|
187
|
+
|
|
188
|
+
const { handleLLMEnd, collected } = createMetadataAggregator();
|
|
189
|
+
const titleResult = await run.generateTitle({
|
|
190
|
+
provider,
|
|
191
|
+
inputText: userMessage,
|
|
192
|
+
titleMethod: TitleMethod.STRUCTURED,
|
|
193
|
+
contentParts,
|
|
194
|
+
clientOptions: llmConfig,
|
|
195
|
+
chainOptions: {
|
|
196
|
+
callbacks: [
|
|
197
|
+
{
|
|
198
|
+
handleLLMEnd,
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
expect(titleResult).toBeDefined();
|
|
205
|
+
expect(titleResult.title).toBeDefined();
|
|
206
|
+
expect(titleResult.language).toBeDefined();
|
|
207
|
+
expect(collected).toBeDefined();
|
|
208
|
+
} catch (error) {
|
|
209
|
+
if (isContentFilterError(error)) {
|
|
210
|
+
contentFilterTriggered = true;
|
|
211
|
+
console.warn('Skipping test: Azure content filter triggered');
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
throw error;
|
|
215
|
+
}
|
|
201
216
|
});
|
|
202
217
|
|
|
203
218
|
test(`${capitalizeFirstLetter(provider)}: should generate title using completion method`, async () => {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
219
|
+
if (contentFilterTriggered) {
|
|
220
|
+
console.warn(
|
|
221
|
+
'Skipping test: Azure content filter was triggered in previous test'
|
|
222
|
+
);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
try {
|
|
226
|
+
const llmConfig = getLLMConfig(provider);
|
|
227
|
+
const customHandlers = setupCustomHandlers();
|
|
228
|
+
|
|
229
|
+
run = await Run.create<t.IState>({
|
|
230
|
+
runId: 'test-run-id-completion',
|
|
231
|
+
graphConfig: {
|
|
232
|
+
type: 'standard',
|
|
233
|
+
llmConfig,
|
|
234
|
+
tools: [new Calculator()],
|
|
235
|
+
instructions:
|
|
236
|
+
'You are a helpful AI assistant. Keep responses concise and friendly.',
|
|
237
|
+
},
|
|
238
|
+
returnContent: true,
|
|
239
|
+
customHandlers,
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const userMessage = 'What can you help me with today?';
|
|
243
|
+
conversationHistory = [];
|
|
244
|
+
conversationHistory.push(new HumanMessage(userMessage));
|
|
245
|
+
|
|
246
|
+
const inputs = {
|
|
247
|
+
messages: conversationHistory,
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const finalContentParts = await run.processStream(inputs, config);
|
|
251
|
+
expect(finalContentParts).toBeDefined();
|
|
252
|
+
|
|
253
|
+
const { handleLLMEnd, collected } = createMetadataAggregator();
|
|
254
|
+
const titleResult = await run.generateTitle({
|
|
255
|
+
provider,
|
|
256
|
+
inputText: userMessage,
|
|
257
|
+
titleMethod: TitleMethod.COMPLETION,
|
|
258
|
+
contentParts,
|
|
259
|
+
clientOptions: llmConfig,
|
|
260
|
+
chainOptions: {
|
|
261
|
+
callbacks: [
|
|
262
|
+
{
|
|
263
|
+
handleLLMEnd,
|
|
264
|
+
},
|
|
265
|
+
],
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
expect(titleResult).toBeDefined();
|
|
270
|
+
expect(titleResult.title).toBeDefined();
|
|
271
|
+
expect(titleResult.title).not.toBe('');
|
|
272
|
+
expect(titleResult.language).toBeUndefined();
|
|
273
|
+
expect(collected).toBeDefined();
|
|
274
|
+
console.log(`Completion method generated title: "${titleResult.title}"`);
|
|
275
|
+
} catch (error) {
|
|
276
|
+
if (isContentFilterError(error)) {
|
|
277
|
+
contentFilterTriggered = true;
|
|
278
|
+
console.warn('Skipping test: Azure content filter triggered');
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
throw error;
|
|
282
|
+
}
|
|
256
283
|
});
|
|
257
284
|
|
|
258
285
|
test(`${capitalizeFirstLetter(provider)}: should follow-up`, async () => {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
finalMessages
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
286
|
+
if (contentFilterTriggered || runningHistory == null) {
|
|
287
|
+
console.warn(
|
|
288
|
+
'Skipping test: Azure content filter was triggered or no conversation history'
|
|
289
|
+
);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
try {
|
|
293
|
+
console.log('Previous conversation length:', runningHistory.length);
|
|
294
|
+
console.log(
|
|
295
|
+
'Last message:',
|
|
296
|
+
runningHistory[runningHistory.length - 1].content
|
|
297
|
+
);
|
|
298
|
+
const llmConfig = getLLMConfig(provider);
|
|
299
|
+
const customHandlers = setupCustomHandlers();
|
|
300
|
+
|
|
301
|
+
run = await Run.create<t.IState>({
|
|
302
|
+
runId: 'test-run-id',
|
|
303
|
+
graphConfig: {
|
|
304
|
+
type: 'standard',
|
|
305
|
+
llmConfig,
|
|
306
|
+
tools: [new Calculator()],
|
|
307
|
+
instructions:
|
|
308
|
+
'You are a helpful AI assistant. Keep responses concise and friendly.',
|
|
309
|
+
},
|
|
310
|
+
returnContent: true,
|
|
311
|
+
customHandlers,
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
conversationHistory = runningHistory.slice();
|
|
315
|
+
conversationHistory.push(new HumanMessage('What else can you tell me?'));
|
|
316
|
+
|
|
317
|
+
const inputs = {
|
|
318
|
+
messages: conversationHistory,
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const finalContentParts = await run.processStream(inputs, config);
|
|
322
|
+
expect(finalContentParts).toBeDefined();
|
|
323
|
+
const allTextParts = finalContentParts?.every(
|
|
324
|
+
(part) => part.type === ContentTypes.TEXT
|
|
325
|
+
);
|
|
326
|
+
expect(allTextParts).toBe(true);
|
|
327
|
+
expect(collectedUsage.length).toBeGreaterThan(0);
|
|
328
|
+
expect(collectedUsage[0].input_tokens).toBeGreaterThan(0);
|
|
329
|
+
expect(collectedUsage[0].output_tokens).toBeGreaterThan(0);
|
|
330
|
+
|
|
331
|
+
const finalMessages = run.getRunMessages();
|
|
332
|
+
expect(finalMessages).toBeDefined();
|
|
333
|
+
expect(finalMessages?.length).toBeGreaterThan(0);
|
|
334
|
+
console.log(
|
|
335
|
+
`${capitalizeFirstLetter(provider)} follow-up message:`,
|
|
336
|
+
finalMessages?.[finalMessages.length - 1]?.content
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
expect(onMessageDeltaSpy).toHaveBeenCalled();
|
|
340
|
+
expect(onMessageDeltaSpy.mock.calls.length).toBeGreaterThan(1);
|
|
341
|
+
|
|
342
|
+
expect(onRunStepSpy).toHaveBeenCalled();
|
|
343
|
+
expect(onRunStepSpy.mock.calls.length).toBeGreaterThan(0);
|
|
344
|
+
} catch (error) {
|
|
345
|
+
if (isContentFilterError(error)) {
|
|
346
|
+
console.warn('Skipping test: Azure content filter triggered');
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
throw error;
|
|
350
|
+
}
|
|
312
351
|
});
|
|
313
352
|
|
|
314
353
|
test('should handle errors appropriately', async () => {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
1
|
import { config } from 'dotenv';
|
|
3
2
|
config();
|
|
4
3
|
import { tool } from '@langchain/core/tools';
|
|
@@ -21,7 +20,13 @@ const errorTool = tool(
|
|
|
21
20
|
{
|
|
22
21
|
name: 'errorTool',
|
|
23
22
|
description: 'A tool that always throws an error',
|
|
24
|
-
schema:
|
|
23
|
+
schema: {
|
|
24
|
+
type: 'object',
|
|
25
|
+
properties: {
|
|
26
|
+
input: { type: 'string' },
|
|
27
|
+
},
|
|
28
|
+
required: [],
|
|
29
|
+
},
|
|
25
30
|
}
|
|
26
31
|
);
|
|
27
32
|
|
package/src/test/mockTools.ts
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* Shared mock tools for testing across all test scripts.
|
|
4
4
|
* Centralizes tool definitions to follow DRY principles.
|
|
5
5
|
*/
|
|
6
|
-
import { z } from 'zod';
|
|
7
6
|
import { tool } from '@langchain/core/tools';
|
|
8
7
|
import type { StructuredToolInterface } from '@langchain/core/tools';
|
|
9
8
|
import type { LCTool, LCToolRegistry } from '@/types';
|
|
@@ -29,7 +28,7 @@ export function createGetTeamMembersTool(): StructuredToolInterface {
|
|
|
29
28
|
name: 'get_team_members',
|
|
30
29
|
description:
|
|
31
30
|
'Get list of team members. Returns array of objects with id, name, and department fields.',
|
|
32
|
-
schema:
|
|
31
|
+
schema: { type: 'object', properties: {}, required: [] },
|
|
33
32
|
}
|
|
34
33
|
);
|
|
35
34
|
}
|
|
@@ -59,7 +58,8 @@ export function createGetExpensesTool(): StructuredToolInterface {
|
|
|
59
58
|
};
|
|
60
59
|
|
|
61
60
|
return tool(
|
|
62
|
-
async (
|
|
61
|
+
async (input) => {
|
|
62
|
+
const { user_id } = input as { user_id: string };
|
|
63
63
|
await new Promise((resolve) => setTimeout(resolve, 30));
|
|
64
64
|
return expenseData[user_id] ?? [];
|
|
65
65
|
},
|
|
@@ -67,9 +67,16 @@ export function createGetExpensesTool(): StructuredToolInterface {
|
|
|
67
67
|
name: 'get_expenses',
|
|
68
68
|
description:
|
|
69
69
|
'Get expense records for a user. Returns array of objects with amount and category fields.',
|
|
70
|
-
schema:
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
schema: {
|
|
71
|
+
type: 'object',
|
|
72
|
+
properties: {
|
|
73
|
+
user_id: {
|
|
74
|
+
type: 'string',
|
|
75
|
+
description: 'The user ID to fetch expenses for',
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
required: ['user_id'],
|
|
79
|
+
},
|
|
73
80
|
}
|
|
74
81
|
);
|
|
75
82
|
}
|
|
@@ -91,7 +98,8 @@ export function createGetWeatherTool(): StructuredToolInterface {
|
|
|
91
98
|
};
|
|
92
99
|
|
|
93
100
|
return tool(
|
|
94
|
-
async (
|
|
101
|
+
async (input) => {
|
|
102
|
+
const { city } = input as { city: string };
|
|
95
103
|
await new Promise((resolve) => setTimeout(resolve, 40));
|
|
96
104
|
const weather = weatherData[city];
|
|
97
105
|
if (!weather) {
|
|
@@ -103,9 +111,13 @@ export function createGetWeatherTool(): StructuredToolInterface {
|
|
|
103
111
|
name: 'get_weather',
|
|
104
112
|
description:
|
|
105
113
|
'Get current weather for a city. Returns object with temperature (number) and condition (string) fields.',
|
|
106
|
-
schema:
|
|
107
|
-
|
|
108
|
-
|
|
114
|
+
schema: {
|
|
115
|
+
type: 'object',
|
|
116
|
+
properties: {
|
|
117
|
+
city: { type: 'string', description: 'City name' },
|
|
118
|
+
},
|
|
119
|
+
required: ['city'],
|
|
120
|
+
},
|
|
109
121
|
}
|
|
110
122
|
);
|
|
111
123
|
}
|
|
@@ -115,7 +127,8 @@ export function createGetWeatherTool(): StructuredToolInterface {
|
|
|
115
127
|
*/
|
|
116
128
|
export function createCalculatorTool(): StructuredToolInterface {
|
|
117
129
|
return tool(
|
|
118
|
-
async (
|
|
130
|
+
async (input) => {
|
|
131
|
+
const { expression } = input as { expression: string };
|
|
119
132
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
120
133
|
// Simple eval for demo (in production, use a proper math parser)
|
|
121
134
|
|
|
@@ -125,9 +138,16 @@ export function createCalculatorTool(): StructuredToolInterface {
|
|
|
125
138
|
{
|
|
126
139
|
name: 'calculator',
|
|
127
140
|
description: 'Evaluate a mathematical expression',
|
|
128
|
-
schema:
|
|
129
|
-
|
|
130
|
-
|
|
141
|
+
schema: {
|
|
142
|
+
type: 'object',
|
|
143
|
+
properties: {
|
|
144
|
+
expression: {
|
|
145
|
+
type: 'string',
|
|
146
|
+
description: 'Mathematical expression to evaluate',
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
required: ['expression'],
|
|
150
|
+
},
|
|
131
151
|
}
|
|
132
152
|
);
|
|
133
153
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
1
|
import { config } from 'dotenv';
|
|
3
2
|
import fetch, { RequestInit } from 'node-fetch';
|
|
4
3
|
import { HttpsProxyAgent } from 'https-proxy-agent';
|
|
@@ -21,25 +20,33 @@ const accessMessage =
|
|
|
21
20
|
const emptyOutputMessage =
|
|
22
21
|
'stdout: Empty. Ensure you\'re writing output explicitly.\n';
|
|
23
22
|
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
23
|
+
const SUPPORTED_LANGUAGES = [
|
|
24
|
+
'py',
|
|
25
|
+
'js',
|
|
26
|
+
'ts',
|
|
27
|
+
'c',
|
|
28
|
+
'cpp',
|
|
29
|
+
'java',
|
|
30
|
+
'php',
|
|
31
|
+
'rs',
|
|
32
|
+
'go',
|
|
33
|
+
'd',
|
|
34
|
+
'f90',
|
|
35
|
+
'r',
|
|
36
|
+
] as const;
|
|
37
|
+
|
|
38
|
+
const CodeExecutionToolSchema = {
|
|
39
|
+
type: 'object',
|
|
40
|
+
properties: {
|
|
41
|
+
lang: {
|
|
42
|
+
type: 'string',
|
|
43
|
+
enum: SUPPORTED_LANGUAGES,
|
|
44
|
+
description:
|
|
45
|
+
'The programming language or runtime to execute the code in.',
|
|
46
|
+
},
|
|
47
|
+
code: {
|
|
48
|
+
type: 'string',
|
|
49
|
+
description: `The complete, self-contained code to execute, without any truncation or minimization.
|
|
43
50
|
- The environment is stateless; variables and imports don't persist between executions.
|
|
44
51
|
- Generated files from previous executions are automatically available in "/mnt/data/".
|
|
45
52
|
- Files from previous executions are automatically available and can be modified in place.
|
|
@@ -50,21 +57,26 @@ const CodeExecutionToolSchema = z.object({
|
|
|
50
57
|
- py: Matplotlib: Use \`plt.savefig()\` to save plots as files.
|
|
51
58
|
- js: use the \`console\` or \`process\` methods for all outputs.
|
|
52
59
|
- r: IMPORTANT: No X11 display available. ALL graphics MUST use Cairo library (library(Cairo)).
|
|
53
|
-
- Other languages: use appropriate output functions
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
60
|
+
- Other languages: use appropriate output functions.`,
|
|
61
|
+
},
|
|
62
|
+
args: {
|
|
63
|
+
type: 'array',
|
|
64
|
+
items: { type: 'string' },
|
|
65
|
+
description:
|
|
66
|
+
'Additional arguments to execute the code with. This should only be used if the input code requires additional arguments to run.',
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
required: ['lang', 'code'],
|
|
70
|
+
} as const;
|
|
61
71
|
|
|
62
72
|
const baseEndpoint = getCodeBaseURL();
|
|
63
73
|
const EXEC_ENDPOINT = `${baseEndpoint}/exec`;
|
|
64
74
|
|
|
75
|
+
type SupportedLanguage = (typeof SUPPORTED_LANGUAGES)[number];
|
|
76
|
+
|
|
65
77
|
function createCodeExecutionTool(
|
|
66
78
|
params: t.CodeExecutionToolParams = {}
|
|
67
|
-
): DynamicStructuredTool
|
|
79
|
+
): DynamicStructuredTool {
|
|
68
80
|
const apiKey =
|
|
69
81
|
params[EnvVar.CODE_API_KEY] ??
|
|
70
82
|
params.apiKey ??
|
|
@@ -83,8 +95,13 @@ Usage:
|
|
|
83
95
|
- NEVER use this tool to execute malicious code.
|
|
84
96
|
`.trim();
|
|
85
97
|
|
|
86
|
-
return tool
|
|
87
|
-
async (
|
|
98
|
+
return tool(
|
|
99
|
+
async (rawInput, config) => {
|
|
100
|
+
const { lang, code, ...rest } = rawInput as {
|
|
101
|
+
lang: SupportedLanguage;
|
|
102
|
+
code: string;
|
|
103
|
+
args?: string[];
|
|
104
|
+
};
|
|
88
105
|
/**
|
|
89
106
|
* Extract session context from config.toolCall (injected by ToolNode).
|
|
90
107
|
* - session_id: For API to associate with previous session
|