@semantictools/ai-toolbox 0.1.0

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/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@semantictools/ai-toolbox",
3
+ "version": "0.1.0",
4
+ "description": "A flexible tool manager for managing and executing AI function calling toolboxes",
5
+ "main": "src/toolmanager.mjs",
6
+ "type": "module",
7
+ "scripts": {
8
+ "test": "echo \"Error: no test specified\" && exit 1"
9
+ },
10
+ "keywords": [
11
+ "ai",
12
+ "llm",
13
+ "function-calling",
14
+ "tool-manager",
15
+ "toolbox",
16
+ "anthropic",
17
+ "openai"
18
+ ],
19
+ "author": "Dusty Wilhelm Murray / Semantic Tools",
20
+ "license": "Apache-2.0",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/semantictools/ai-toolbox.git"
24
+ },
25
+ "bugs": {
26
+ "url": "https://github.com/semantictools/ai-toolbox/issues"
27
+ },
28
+ "homepage": "https://github.com/semantictools/ai-toolbox#readme",
29
+ "engines": {
30
+ "node": ">=14.0.0"
31
+ },
32
+ "files": [
33
+ "src",
34
+ "src/translators",
35
+ "README.md",
36
+ "LICENSE"
37
+ ]
38
+ }
@@ -0,0 +1,605 @@
1
+ import * as atfuncTranslator from './translators/atfunc.mjs';
2
+
3
+ let rootToolBox = null;
4
+ let toolBoxes = {};
5
+ let functions = {};
6
+ let converter = null;
7
+ const truncateLengthDefault = 5000;
8
+ let truncateLength = truncateLengthDefault;
9
+
10
+ let toolCalls = 0;
11
+ let functionCalls = 0;
12
+
13
+ // Case sensitivity - determined by translator (default: case-sensitive)
14
+ let caseSensitive = true;
15
+
16
+ // Standard translator registry
17
+ const standardTranslators = {
18
+ 'atfunc': atfuncTranslator
19
+ };
20
+
21
+ // Translator constants for convenience
22
+ export const TRANSLATOR_ATFUNC = 'atfunc';
23
+
24
+ // Helper function to normalize names based on case sensitivity
25
+ function normalizeName(name) {
26
+ return caseSensitive ? name : name.toLowerCase();
27
+ }
28
+
29
+ // ============================================================================
30
+ // Validation Functions
31
+ // ============================================================================
32
+
33
+ /**
34
+ * Validate that a function interface matches the expected structure
35
+ * @param {object} interfaceObj - The interface object to validate
36
+ * @param {string} toolboxName - The toolbox name for domain validation
37
+ * @throws {Error} If validation fails
38
+ */
39
+ function validateInterface(interfaceObj, toolboxName) {
40
+ if (!interfaceObj || typeof interfaceObj !== 'object') {
41
+ throw new Error('Interface must be an object');
42
+ }
43
+
44
+ // Check required fields
45
+ const requiredFields = ['domain', 'name', 'type', 'description', 'returns'];
46
+ for (const field of requiredFields) {
47
+ if (!interfaceObj[field]) {
48
+ throw new Error(`Interface missing required field: '${field}'`);
49
+ }
50
+ }
51
+
52
+ // Validate type
53
+ if (interfaceObj.type !== 'function' && interfaceObj.type !== 'api-wrapper') {
54
+ throw new Error(`Interface type must be 'function' or 'api-wrapper', got: '${interfaceObj.type}'`);
55
+ }
56
+
57
+ // Validate domain matches toolbox name
58
+ const normalizedDomain = normalizeName(interfaceObj.domain);
59
+ const normalizedToolboxName = normalizeName(toolboxName);
60
+ if (normalizedDomain !== normalizedToolboxName) {
61
+ throw new Error(`Interface domain '${interfaceObj.domain}' does not match toolbox name '${toolboxName}'`);
62
+ }
63
+
64
+ // Validate params structure (if present)
65
+ if (interfaceObj.params) {
66
+ if (typeof interfaceObj.params !== 'object') {
67
+ throw new Error('Interface params must be an object');
68
+ }
69
+
70
+ for (const [paramName, paramDef] of Object.entries(interfaceObj.params)) {
71
+ if (!paramDef || typeof paramDef !== 'object') {
72
+ throw new Error(`Parameter '${paramName}' must be an object`);
73
+ }
74
+
75
+ if (!paramDef.type) {
76
+ throw new Error(`Parameter '${paramName}' missing required field: 'type'`);
77
+ }
78
+
79
+ if (paramDef.isRequired === undefined) {
80
+ throw new Error(`Parameter '${paramName}' missing required field: 'isRequired'`);
81
+ }
82
+
83
+ if (!paramDef.description) {
84
+ throw new Error(`Parameter '${paramName}' missing required field: 'description'`);
85
+ }
86
+ }
87
+ }
88
+
89
+ // For API wrappers, validate additional fields if present
90
+ if (interfaceObj.type === 'api-wrapper') {
91
+ ['path_params', 'query_params'].forEach(fieldName => {
92
+ if (interfaceObj[fieldName]) {
93
+ if (typeof interfaceObj[fieldName] !== 'object') {
94
+ throw new Error(`Interface ${fieldName} must be an object`);
95
+ }
96
+ }
97
+ });
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Validate that required parameters are present in a function call
103
+ * @param {object} funcInterface - The function interface
104
+ * @param {object} params - The parameters passed to the function
105
+ * @throws {Error} If required parameters are missing
106
+ */
107
+ function validateParameters(funcInterface, params) {
108
+ if (!funcInterface.params) {
109
+ return; // No params defined, nothing to validate
110
+ }
111
+
112
+ const missingParams = [];
113
+
114
+ for (const [paramName, paramDef] of Object.entries(funcInterface.params)) {
115
+ if (paramDef.isRequired) {
116
+ // Check if param exists in call
117
+ if (!params || !params[paramName]) {
118
+ missingParams.push(paramName);
119
+ }
120
+ }
121
+ }
122
+
123
+ if (missingParams.length > 0) {
124
+ const functionName = `${funcInterface.domain}.${funcInterface.name}`;
125
+ throw new Error(
126
+ `Missing required parameter${missingParams.length > 1 ? 's' : ''} for function '${functionName}': ${missingParams.join(', ')}`
127
+ );
128
+ }
129
+ }
130
+
131
+ function getStats() {
132
+ return {
133
+ toolCalls,
134
+ functionCalls
135
+ }
136
+ }
137
+
138
+ function resetStats() {
139
+
140
+ toolCalls = 0;
141
+ functionCalls = 0;
142
+
143
+ }
144
+
145
+ function setRootToolBox( toolBoxName ) {
146
+ if( !toolBoxName ) {
147
+ throw new Error("Tool box name is required to set root tool box");
148
+ }
149
+ const normalizedName = normalizeName(toolBoxName);
150
+ if( !toolBoxes[normalizedName] ) {
151
+ throw new Error(`Tool box '${toolBoxName}' not registered, cannot set as root tool box`);
152
+ }
153
+ rootToolBox = normalizedName;
154
+ }
155
+
156
+ function setFunctionTranslator( converterObject ) {
157
+ // If a string is passed, treat it as a translator name and load it
158
+ if (typeof converterObject === 'string') {
159
+ converter = getTranslator(converterObject);
160
+ } else {
161
+ converter = converterObject;
162
+ }
163
+
164
+ // Read translator's case sensitivity preference (default: case-sensitive)
165
+ caseSensitive = converter.caseSensitive ?? true;
166
+ }
167
+
168
+ function getTranslator( name ) {
169
+ if (!name) {
170
+ throw new Error("Translator name is required");
171
+ }
172
+
173
+ const translatorName = name.toLowerCase();
174
+ if (!standardTranslators[translatorName]) {
175
+ throw new Error(`Translator '${name}' not found. Available translators: ${Object.keys(standardTranslators).join(', ')}`);
176
+ }
177
+
178
+ return standardTranslators[translatorName];
179
+ }
180
+
181
+ function getStandardTranslatorList() {
182
+ return Object.keys(standardTranslators);
183
+ }
184
+
185
+ function findFunction( toolPath ) {
186
+ if(!toolPath) {
187
+ throw new Error("Tool path is required");
188
+ }
189
+
190
+ const normalizedPath = normalizeName(toolPath);
191
+ let toolcomponents = normalizedPath.split('.');
192
+ let toolBox;
193
+ let toolFunction;
194
+ if( toolcomponents.length < 2) {
195
+ toolBox = toolBoxes[rootToolBox];
196
+ toolFunction = toolcomponents[0];
197
+ }
198
+ else {
199
+ toolBox = toolBoxes[toolcomponents[0]];
200
+ toolFunction = toolcomponents[1];
201
+ }
202
+
203
+ if (!toolBox) {
204
+ throw new Error(`Tool box '${toolcomponents[0]}' not registered`);
205
+ }
206
+
207
+ let tfunc = toolBox.getFunction( toolFunction );
208
+ if (!tfunc || !tfunc.f) {
209
+ return null;
210
+ }
211
+
212
+ return tfunc;
213
+ }
214
+
215
+
216
+ async function call(toolPath, args, truncateLength = 5000) {
217
+
218
+ let tfunc = findFunction( toolPath );
219
+
220
+ // Validate parameters before calling
221
+ if (tfunc.interface) {
222
+ validateParameters(tfunc.interface, args);
223
+ }
224
+
225
+ return await tfunc.f( args || {}, truncateLength );
226
+ }
227
+
228
+ function register(toolBox) {
229
+
230
+ if(!toolBox || !toolBox.getName()) {
231
+ throw new Error("Tool box with a valid name is required for registration");
232
+ }
233
+
234
+ const toolboxName = toolBox.getName();
235
+
236
+ // Validate all function interfaces in the toolbox
237
+ if (toolBox.functions) {
238
+ for (const functionName of Object.keys(toolBox.functions)) {
239
+ const funcObj = toolBox.getFunction(functionName);
240
+ if (funcObj && funcObj.interface) {
241
+ try {
242
+ validateInterface(funcObj.interface, toolboxName);
243
+ } catch (e) {
244
+ throw new Error(`Validation failed for function '${functionName}' in toolbox '${toolboxName}': ${e.message}`);
245
+ }
246
+ }
247
+ }
248
+ }
249
+
250
+ const normalizedName = normalizeName(toolboxName);
251
+ toolBoxes[normalizedName] = toolBox;
252
+ }
253
+
254
+
255
+
256
+
257
+ function getFunctions(toolBoxName) {
258
+ /* toolBoxName can also be null or undefined, then we list all functions */
259
+ let functionsList = [];
260
+ if(toolBoxName) {
261
+ const normalizedName = normalizeName(toolBoxName);
262
+ let toolBox = toolBoxes[normalizedName];
263
+ if (!toolBox) {
264
+ throw new Error(`Tool box '${toolBoxName}' not registered`);
265
+ }
266
+ for (let fname in toolBox.functions) {
267
+ functionsList.push( toolBox.getName() + "." + fname );
268
+ }
269
+ } else {
270
+ for (let tbName in toolBoxes) {
271
+ let toolBox = toolBoxes[tbName];
272
+ for (let fname in toolBox.functions) {
273
+
274
+ let fullName = fname;
275
+
276
+ if( tbName != rootToolBox ) {
277
+ fullName = tbName + "." + fname;
278
+ }
279
+ functionsList.push( fullName );
280
+ }
281
+ }
282
+ }
283
+ return functionsList;
284
+ }
285
+
286
+
287
+
288
+ function getName() {
289
+ return "toolmanager";
290
+ }
291
+
292
+ function getFunction(fname) {
293
+ if (fname in functions) {
294
+ return functions[fname];
295
+ } else {
296
+ return null;
297
+ }
298
+ };
299
+
300
+ function getRootInterfaceAsText() {
301
+
302
+ let functions = getFunctions();
303
+ let output = "";
304
+
305
+ for( let i=0; i<functions.length; i++) {
306
+ let fn = functions[i];
307
+ let f = findFunction( fn );
308
+ let descr = converter.generateFunctionIfText( f.interface );
309
+
310
+ if( descr ) {
311
+ output += descr;
312
+ }
313
+
314
+ }
315
+
316
+ return output;
317
+
318
+ }
319
+
320
+ function makeInterfaceText( def, separator ) {
321
+ return converter.generateFunctionIfText( def, separator );
322
+ }
323
+
324
+ function getFunctionCallsFromTextResponse( text ) {
325
+
326
+ let allFunctions = getFunctions();
327
+ let interfaces = [];
328
+ for( let i=0; i<allFunctions.length; i++ ) {
329
+ let tfunc = findFunction( allFunctions[i] );
330
+ interfaces.push( tfunc.interface );
331
+ }
332
+ return converter.getFunctionCallsFromText( text, interfaces, rootToolBox );
333
+ }
334
+
335
+ async function executeResponseFunctions( promptText ) {
336
+
337
+ let fCalls = getFunctionCallsFromTextResponse( promptText );
338
+
339
+ for( let f=0; f<fCalls.length; f++ ) {
340
+
341
+ let fCall = fCalls[f];
342
+ let fParams = fCall.params;
343
+
344
+ if( !fCall.error ) {
345
+ try {
346
+ let result = await call( fCall.name, fCall.params, truncateLength );
347
+ fCall.result = result;
348
+ if( fCall.interface.returns === "object" ) {
349
+
350
+
351
+ let resultString = JSON.stringify( result, null, 2 );
352
+
353
+ //truncate result if too long
354
+ if( resultString.length > truncateLength ) {
355
+ resultString = resultString.substring(0, truncateLength) + "...(truncated)";
356
+ }
357
+
358
+ fCall.result = resultString;
359
+ }
360
+ } catch (e) {
361
+ fCall.error = true;
362
+ fCall.result = `Error: ${e.message}`;
363
+ }
364
+ }
365
+ else {
366
+ fCall.result = "Error: function not found";
367
+ }
368
+ }
369
+
370
+ if(!truncateLength == truncateLengthDefault) {
371
+ truncateLength = truncateLengthDefault;
372
+ console.log("Truncate length reset to default of ", truncateLengthDefault );
373
+ }
374
+
375
+ let output = converter.formatFunctionCallResults( fCalls );
376
+
377
+ return { functionsOutput: output, functionCalCount: fCalls.length };
378
+
379
+ }
380
+
381
+
382
+
383
+ function getBoxes() {
384
+ return Object.keys(toolBoxes);
385
+ }
386
+
387
+ function setTruncate( args ) {
388
+ console.log("Warning, setting global truncate length for tool outputs to ", args.length.value, " characters." );
389
+ truncateLength = args.length.value;
390
+ }
391
+
392
+
393
+ function getFunctionInterface( args ) {
394
+ let tfunc = findFunction( args.function_name.value );
395
+
396
+ let separator = false;
397
+ if( args.separator && args.separator.value ) {
398
+ separator = args.separator.value;
399
+ }
400
+
401
+ return makeInterfaceText( tfunc.interface, separator );
402
+ }
403
+
404
+ // ============================================================================
405
+ // Control Functions Toolbox (domain: "tools")
406
+ // ============================================================================
407
+
408
+ const controlFunctions = {
409
+ get_toolboxes: {
410
+ f: async (args) => {
411
+ return getBoxes();
412
+ },
413
+ interface: {
414
+ domain: 'tools',
415
+ name: 'get_toolboxes',
416
+ type: 'function',
417
+ description: 'Get all toolboxes registered in the tool manager',
418
+ params: {},
419
+ returns: 'array'
420
+ }
421
+ },
422
+
423
+ get_functions: {
424
+ f: async (args) => {
425
+ const toolboxName = args.toolbox ? args.toolbox.value : null;
426
+ return getFunctions(toolboxName);
427
+ },
428
+ interface: {
429
+ domain: 'tools',
430
+ name: 'get_functions',
431
+ type: 'function',
432
+ description: 'Get all functions in the tool manager, or in a specific toolbox',
433
+ params: {
434
+ toolbox: {
435
+ type: 'string',
436
+ isRequired: false,
437
+ description: 'Toolbox name to filter functions'
438
+ }
439
+ },
440
+ returns: 'array'
441
+ }
442
+ },
443
+
444
+ get_function_interface: {
445
+ f: async (args) => {
446
+ return getFunctionInterface(args);
447
+ },
448
+ interface: {
449
+ domain: 'tools',
450
+ name: 'get_function_interface',
451
+ type: 'function',
452
+ description: 'Get the interface definition of a specific function',
453
+ params: {
454
+ function_name: {
455
+ type: 'string',
456
+ isRequired: true,
457
+ description: 'Full function name including toolbox, e.g., "toolbox.function"'
458
+ },
459
+ separator: {
460
+ type: 'boolean',
461
+ isRequired: false,
462
+ description: 'Add visual separator (useful when requesting multiple interfaces)'
463
+ }
464
+ },
465
+ returns: 'string'
466
+ }
467
+ },
468
+
469
+ set_truncate_length: {
470
+ f: async (args) => {
471
+ setTruncate(args);
472
+ return `Truncate length set to ${args.length.value} characters`;
473
+ },
474
+ interface: {
475
+ domain: 'tools',
476
+ name: 'set_truncate_length',
477
+ type: 'function',
478
+ description: 'Set the truncate length for all tool outputs (resets after each execution)',
479
+ params: {
480
+ length: {
481
+ type: 'number',
482
+ isRequired: true,
483
+ description: 'Truncation length in characters'
484
+ }
485
+ },
486
+ returns: 'string'
487
+ }
488
+ }
489
+ };
490
+
491
+ const controlToolbox = {
492
+ getName: () => 'tools',
493
+ getFunction: (fname) => {
494
+ return controlFunctions[fname] || null;
495
+ },
496
+ functions: Object.keys(controlFunctions).reduce((acc, key) => {
497
+ acc[key] = true;
498
+ return acc;
499
+ }, {})
500
+ };
501
+
502
+ function registerControlFunctions() {
503
+ register(controlToolbox);
504
+ }
505
+
506
+ function unregisterControlFunctions() {
507
+ const normalizedName = normalizeName('tools');
508
+ delete toolBoxes[normalizedName];
509
+ }
510
+
511
+ function registerControlFunction(functionName) {
512
+ if (!controlFunctions[functionName]) {
513
+ throw new Error(`Control function '${functionName}' not found. Available: ${Object.keys(controlFunctions).join(', ')}`);
514
+ }
515
+
516
+ // Check if tools toolbox exists
517
+ const normalizedName = normalizeName('tools');
518
+ if (!toolBoxes[normalizedName]) {
519
+ // Create a partial toolbox with just this function
520
+ const partialFunctions = { [functionName]: controlFunctions[functionName] };
521
+ const partialToolbox = {
522
+ getName: () => 'tools',
523
+ getFunction: (fname) => partialFunctions[fname] || null,
524
+ functions: { [functionName]: true }
525
+ };
526
+ register(partialToolbox);
527
+ } else {
528
+ // Add to existing toolbox
529
+ toolBoxes[normalizedName].functions[functionName] = true;
530
+ }
531
+ }
532
+
533
+ function unregisterControlFunction(functionName) {
534
+ const normalizedName = normalizeName('tools');
535
+ if (toolBoxes[normalizedName] && toolBoxes[normalizedName].functions) {
536
+ delete toolBoxes[normalizedName].functions[functionName];
537
+
538
+ // If no functions left, remove the toolbox
539
+ if (Object.keys(toolBoxes[normalizedName].functions).length === 0) {
540
+ delete toolBoxes[normalizedName];
541
+ }
542
+ }
543
+ }
544
+
545
+ // Register control functions by default
546
+ registerControlFunctions();
547
+
548
+ /*
549
+ selfRegisterFunction( "get_toolboxes", "Get all toolboxes registered in the tool manager.",
550
+ if_getBoxes,
551
+ null,
552
+ "object"
553
+ );
554
+
555
+ selfRegisterFunction( "get_functions", "Get all functions in the tool manager, or in a specific toolbox.",
556
+ if_getFunctions,
557
+ {
558
+ "toolbox": { type: "string", required: false, description: "Toolbox name to filter functions." }
559
+ },
560
+ "array"
561
+ );
562
+
563
+ selfRegisterFunction( "get_function_if", "Get the interface definition of a specific function.",
564
+ if_getFunctionInterface,
565
+ {
566
+ "function_name": { type: "string", required: true, description: "Full function name including toolbox, e.g., 'toolbox.function'." },
567
+ "separator": { type: "boolean", required: true, description: "visual separator, when asking for multiple function interaces at once." },
568
+ },
569
+ "string"
570
+
571
+ );
572
+
573
+ selfRegisterFunction( "set_truncate_length", "Set the truncatelength of all tool outputs. It will be reset after each call.",
574
+ if_setTruncate,
575
+ {
576
+ "length": {
577
+ type: "integer", required: true, description: "Truncation length in characters." },
578
+ }
579
+ );
580
+ */
581
+
582
+
583
+
584
+ //register( selfInstanceAsToolBox );
585
+ //rootToolBox = getName();
586
+
587
+ export {
588
+ register, call,
589
+ setFunctionTranslator, getTranslator, getStandardTranslatorList,
590
+ getFunctionCallsFromTextResponse,
591
+ executeResponseFunctions,
592
+ getRootInterfaceAsText,
593
+ setRootToolBox,
594
+
595
+ getFunctions, getBoxes, makeInterfaceText, setTruncate, getFunctionInterface,
596
+ findFunction,
597
+
598
+ getStats, resetStats,
599
+
600
+ // Control functions management
601
+ registerControlFunctions,
602
+ unregisterControlFunctions,
603
+ registerControlFunction,
604
+ unregisterControlFunction
605
+ }