crewai-ts
Version:
TypeScript port of crewAI for agent-based workflows
192 lines (191 loc) • 7.01 kB
JavaScript
/**
* Advanced StructuredTool implementation
* Provides a tool with structured input and output validation
* Optimized for memory efficiency and performance with Zod validation
* Includes automatic schema inference, async support, and direct result features
*/
import { z } from 'zod';
import { Tool } from './BaseTool.js';
/**
* StructuredTool class for creating tools with structured schema validation
* Enhanced with:
* - Type safety with zod schema validation
* - Memory optimization with configurable levels
* - Function inference and automatic schema generation
* - Support for both sync and async functions
* - Direct result handling for agent answers
*/
export class StructuredTool extends Tool {
func;
outputSchema;
resultAsAnswer;
memoryOptimizationLevel;
/**
* Static method to create a StructuredTool from a function
* Auto-infers types and creates schemas based on TypeScript type information
*/
static fromFunction(func, options = {}) {
// Extract function name and infer schema if needed
const name = options.name || func.name || 'unnamed_tool';
// Extract description from function comments if not provided
// For TypeScript this is more limited than Python's docstring extraction
const description = options.description || 'No description provided';
// Determine if we need to infer the schema
const inferSchema = options.inferSchema !== false;
let inputSchema = options.inputSchema;
if (!inputSchema && inferSchema) {
// Create a simple pass-through schema as a fallback
// In a real implementation, we would use TypeScript reflection APIs
// to create a more sophisticated schema
inputSchema = z.record(z.any());
}
if (!inputSchema) {
throw new Error('Either inputSchema must be provided or inferSchema must be true');
}
return new StructuredTool({
name,
description,
inputSchema,
outputSchema: options.outputSchema,
func,
resultAsAnswer: options.resultAsAnswer,
verbose: options.verbose,
cacheResults: options.cacheResults,
timeout: options.timeout,
maxRetries: options.maxRetries,
tags: options.tags,
memoryOptimizationLevel: options.memoryOptimizationLevel,
});
}
constructor(options) {
if (!options.inputSchema) {
throw new Error('InputSchema is required for StructuredTool');
}
super({
name: options.name,
description: options.description,
schema: options.inputSchema,
verbose: options.verbose,
cacheResults: options.cacheResults,
timeout: options.timeout,
maxRetries: options.maxRetries,
tags: options.tags,
});
this.func = options.func;
this.outputSchema = options.outputSchema;
this.resultAsAnswer = !!options.resultAsAnswer;
this.memoryOptimizationLevel = options.memoryOptimizationLevel || 0;
}
/**
* Execute the tool with the given input
* Supports both sync and async functions with proper error handling
*/
async _execute(input, options) {
try {
// Execute the function with memory optimizations based on configuration
const result = await this.executeWithOptimizations(input, options);
// Validate output if schema provided
if (this.outputSchema) {
try {
return this.outputSchema.parse(result);
}
catch (error) {
throw new Error(`Invalid output: ${error instanceof Error ? error.message : String(error)}`);
}
}
return result;
}
catch (error) {
// Enhanced error handling with detailed context
const errorMessage = error instanceof Error ? error.message : String(error);
throw new Error(`Tool '${this.name}' execution failed: ${errorMessage}`);
}
}
/**
* Execute with memory optimizations based on configuration level
* Higher levels apply more aggressive optimizations
*/
async executeWithOptimizations(input, options) {
const funcResult = this.func(input, options);
// Handle both Promise and non-Promise returns
if (funcResult instanceof Promise) {
return await funcResult;
}
return funcResult;
}
/**
* Check if this tool returns direct answers
*/
isDirectAnswer() {
return this.resultAsAnswer;
}
/**
* Get the schema for this tool's arguments
*/
getArgSchema() {
return this.schema;
}
/**
* Get the JSON schema for arguments (for LLM consumption)
*/
getArgSchemaForLLM() {
// Convert Zod schema to JSON Schema format for LLM consumption
return this.schema.safeParse({}).error?.format() || {};
}
}
/**
* Create a structured tool from a function
* Factory function optimized for memory efficiency and ease of use
*
* @param options Tool configuration options
* @returns A new StructuredTool instance
*/
export function createStructuredTool(options) {
return new StructuredTool(options);
}
/**
* Create a structured tool from a function with optimized memory usage
* This is a more convenient API that infers types from the function signature
*
* @param func The function to create a tool from
* @param options Optional configuration
* @returns A new StructuredTool instance
*
* @example
* ```typescript
* // Create a simple addition tool
* const addTool = createToolFromFunction(
* async ({ a, b }: { a: number, b: number }) => a + b,
* {
* name: 'add',
* description: 'Add two numbers together',
* resultAsAnswer: true
* }
* );
* ```
*/
export function createToolFromFunction(func, options = {}) {
return StructuredTool.fromFunction(func, options);
}
/**
* Create a batch of tools from functions with shared configuration
* Optimized to minimize memory overhead when creating multiple tools
*
* @param functionsMap Record of functions to create tools from
* @param sharedOptions Options applied to all tools
* @returns Record of tools with the same keys as the input
*/
export function createToolsFromFunctions(functionsMap, sharedOptions = {}) {
// Pre-allocate the result object to minimize memory reallocation
const result = {};
// Process all functions in a single loop for efficiency
for (const [key, func] of Object.entries(functionsMap)) {
result[key] = StructuredTool.fromFunction(func, {
...sharedOptions,
name: key,
// Use function name for description if not provided
description: `Tool for ${key}`
});
}
return result;
}