Constructor parameters
The ExuluTool constructor accepts a configuration object with the following parameters:
const tool = new ExuluTool ({
id: string ,
name: string ,
description: string ,
category? : string ,
inputSchema? : z . ZodType ,
type: "context" | "function" | "agent" | "web_search" ,
config: ConfigParameter [],
execute: ExecuteFunction
});
Required parameters
Unique identifier for the tool. Must start with a letter (a-z) or underscore (_). Subsequent characters can be letters, digits (0-9), or underscores. Must be 5-80 characters long.
The ID is used for database references and should not change after creation. Changing the ID requires updating all references in your database.
The ID must follow PostgreSQL identifier rules since it’s used for storing tool references.
Human-readable name used for tool invocation. Agents use this name when calling the tool.
The name is sanitized during execution to ensure compatibility with the AI SDK. Use snake_case or camelCase for consistency.
description
Clear description of what the tool does. This is shown to agents to help them understand when to use the tool.
description : "Retrieves a user's profile information including name, email, and preferences"
Write descriptions from the agent’s perspective. Explain what the tool does and when to use it. Good descriptions improve tool selection accuracy.
type
'context' | 'function' | 'agent' | 'web_search'
required
The type of tool determines its behavior and execution context
function
context
agent
web_search
Custom function tools that execute your business logic, API calls, or data processing. Use for: Database queries, calculations, API integrations, file processing Semantic search tools that query ExuluContext instances for RAG applications. Use for: Knowledge base queries, document search, semantic retrieval Context tools are typically auto-generated by ExuluContext when enableAsTool: true.
Agent delegation tools that invoke other agents for specialized tasks. Use for: Multi-agent workflows, task routing, specialized expertise Web search tools that fetch real-time information from the internet. Use for: Current events, real-time data, fact-checking
config
ConfigParameter[]
required
Array of configuration parameters that define runtime settings for the tool
config : [
{
name: "api_endpoint" ,
description: "Base URL for the API" ,
type: "string" ,
default: "https://api.example.com"
},
{
name: "enable_cache" ,
description: "Enable response caching" ,
type: "boolean" ,
default: true
},
{
name: "timeout_ms" ,
description: "Request timeout in milliseconds" ,
type: "number" ,
default: 5000
},
{
name: "api_key" ,
description: "API authentication key" ,
type: "variable" // References ExuluVariables
}
]
Config parameter properties
Parameter name used to reference this configuration value
Description of what this parameter controls
config[].type
'boolean' | 'string' | 'number' | 'variable'
required
Data type of the parameter
config[].default
string | boolean | number
Default value if not specified (not applicable for type "variable")
execute
The function that implements the tool’s logic. Can be async function or async generator.
execute : async ( inputs : any ) => {
// Your logic here
return {
result? : string ,
job? : string ,
items? : Item []
};
}
Async function
Async generator
Standard async function for single result: execute : async ({ userId , includeHistory }) => {
const user = await db . users . findOne ({ id: userId });
if ( includeHistory ) {
user . history = await db . userHistory . find ({ userId });
}
return {
result: JSON . stringify ( user )
};
}
Generator function for streaming results: execute : async function* ({ documentIds }) {
for ( const docId of documentIds ) {
const doc = await processDocument ( docId );
yield {
result: JSON . stringify ({
documentId: docId ,
status: "processed" ,
content: doc . content
})
};
}
}
Execute function return type
The execute function should return an object with one or more of these properties:
The main result to return to the agent. Typically JSON stringified data.
Optional job ID if the tool queued a background task for async processing
Optional array of items (typically used by context search tools)
return {
result: JSON . stringify ({
status: "success" ,
data: processedData
}),
job: "job-uuid-123" // If queued
};
Optional parameters
category
Category for organizing tools. Defaults to "default" if not specified.
Common categories:
"database" - Database queries and operations
"api" - External API integrations
"file" - File operations and processing
"math" - Mathematical calculations
"search" - Search and retrieval operations
"workflow" - Multi-step workflows
Zod schema that defines and validates the tool’s input parameters. If not provided, defaults to an empty object schema.
import { z } from "zod" ;
inputSchema : z . object ({
userId: z . string (). describe ( "The user ID to look up" ),
includeHistory: z . boolean ()
. optional ()
. describe ( "Include user's history in the response" ),
maxResults: z . number ()
. int ()
. positive ()
. max ( 100 )
. optional ()
. describe ( "Maximum number of results to return" )
})
Always use .describe() on schema fields. These descriptions are shown to agents and help them understand what each parameter does.
Zod schema best practices
Use descriptive field names
// Good
z . object ({
userEmail: z . string (),
includeDeletedRecords: z . boolean ()
})
// Avoid
z . object ({
email: z . string (), // Ambiguous - could be any email
deleted: z . boolean () // Unclear what this controls
})
Add descriptions to all fields
z . object ({
query: z . string ()
. min ( 1 )
. describe ( "Search query to find relevant documents" ),
limit: z . number ()
. int ()
. positive ()
. max ( 50 )
. optional ()
. describe ( "Maximum number of results to return (default: 10)" )
})
Use enums for fixed options
z . object ({
sortBy: z . enum ([ "date" , "relevance" , "popularity" ])
. describe ( "Sort order for results" ),
format: z . enum ([ "json" , "csv" , "xml" ])
. optional ()
. describe ( "Output format (default: json)" )
})
Add validation constraints
z . object ({
email: z . string ()
. email ()
. describe ( "User's email address" ),
age: z . number ()
. int ()
. min ( 0 )
. max ( 120 )
. describe ( "User's age in years" ),
username: z . string ()
. min ( 3 )
. max ( 20 )
. regex ( / ^ [ a-zA-Z0-9_ ] + $ / )
. describe ( "Username (3-20 alphanumeric characters)" )
})
Use optional for non-required fields
z . object ({
// Required
userId: z . string ()
. describe ( "User ID (required)" ),
// Optional with default behavior
includeArchived: z . boolean ()
. optional ()
. describe ( "Include archived items (default: false)" ),
// Optional with no default
filterTag: z . string ()
. optional ()
. describe ( "Optional tag to filter by" )
})
Configuration examples
import { ExuluTool } from "@exulu/backend" ;
import { z } from "zod" ;
const addTool = new ExuluTool ({
id: "add_numbers" ,
name: "add" ,
description: "Adds two numbers together" ,
type: "function" ,
category: "math" ,
inputSchema: z . object ({
a: z . number (). describe ( "First number" ),
b: z . number (). describe ( "Second number" )
}),
config: [],
execute : async ({ a , b }) => {
return {
result: JSON . stringify ({ sum: a + b })
};
}
});
const getUserTool = new ExuluTool ({
id: "get_user_profile" ,
name: "get_user_profile" ,
description: "Retrieves user profile information from the database" ,
type: "function" ,
category: "database" ,
inputSchema: z . object ({
userId: z . string (). describe ( "User ID to look up" ),
includePreferences: z . boolean ()
. optional ()
. describe ( "Include user preferences (default: false)" )
}),
config: [
{
name: "database_url" ,
description: "Database connection URL" ,
type: "variable"
},
{
name: "timeout_seconds" ,
description: "Query timeout in seconds" ,
type: "number" ,
default: 10
}
],
execute : async ({ userId , includePreferences = false }) => {
const user = await db . users . findOne ({ id: userId });
if ( ! user ) {
return {
result: JSON . stringify ({
error: "User not found" ,
userId
})
};
}
if ( includePreferences ) {
user . preferences = await db . preferences . findOne ({ userId });
}
return {
result: JSON . stringify ( user )
};
}
});
const weatherTool = new ExuluTool ({
id: "weather_lookup" ,
name: "get_weather" ,
description: "Gets current weather and forecast for a location" ,
type: "function" ,
category: "api" ,
inputSchema: z . object ({
location: z . string (). describe ( "City name or coordinates (lat,long)" ),
units: z . enum ([ "metric" , "imperial" ])
. optional ()
. describe ( "Temperature units (default: metric)" ),
includeForecast: z . boolean ()
. optional ()
. describe ( "Include 7-day forecast (default: false)" )
}),
config: [
{
name: "api_key" ,
description: "Weather API authentication key" ,
type: "variable"
},
{
name: "api_endpoint" ,
description: "Weather API base URL" ,
type: "string" ,
default: "https://api.weatherapi.com/v1"
},
{
name: "cache_duration" ,
description: "Cache responses for this many seconds" ,
type: "number" ,
default: 300
}
],
execute : async ({ location , units = "metric" , includeForecast = false }) => {
const url = ` ${ config . api_endpoint } /current.json?key= ${ config . api_key } &q= ${ location } ` ;
const response = await fetch ( url );
const data = await response . json ();
if ( includeForecast ) {
const forecastUrl = ` ${ config . api_endpoint } /forecast.json?key= ${ config . api_key } &q= ${ location } &days=7` ;
const forecastResponse = await fetch ( forecastUrl );
data . forecast = await forecastResponse . json ();
}
return {
result: JSON . stringify ( data )
};
}
});
const batchProcessTool = new ExuluTool ({
id: "batch_process_files" ,
name: "batch_process_files" ,
description: "Processes multiple files and streams results as they complete" ,
type: "function" ,
category: "file" ,
inputSchema: z . object ({
fileKeys: z . array ( z . string ())
. min ( 1 )
. max ( 50 )
. describe ( "S3 keys of files to process (max 50)" ),
operation: z . enum ([ "extract_text" , "generate_thumbnail" , "convert_format" ])
. describe ( "Operation to perform on each file" )
}),
config: [
{
name: "s3_bucket" ,
description: "S3 bucket name" ,
type: "variable"
},
{
name: "parallel_limit" ,
description: "Maximum number of parallel operations" ,
type: "number" ,
default: 5
}
],
execute : async function* ({ fileKeys , operation }) {
for ( const fileKey of fileKeys ) {
try {
const result = await processFile ( fileKey , operation );
yield {
result: JSON . stringify ({
fileKey ,
status: "success" ,
operation ,
output: result
})
};
} catch ( error ) {
yield {
result: JSON . stringify ({
fileKey ,
status: "error" ,
operation ,
error: error . message
})
};
}
}
}
});
const embedGeneratorTool = new ExuluTool ({
id: "generate_embeddings" ,
name: "generate_embeddings" ,
description: "Generates embeddings for documents (queued for background processing)" ,
type: "function" ,
category: "processing" ,
inputSchema: z . object ({
contextId: z . string (). describe ( "Context ID to generate embeddings for" ),
itemIds: z . array ( z . string ())
. optional ()
. describe ( "Specific item IDs (if not provided, processes all items)" )
}),
config: [
{
name: "queue_name" ,
description: "BullMQ queue name" ,
type: "string" ,
default: "embeddings"
},
{
name: "priority" ,
description: "Job priority (1-10, higher is more important)" ,
type: "number" ,
default: 5
}
],
execute : async ({ contextId , itemIds }) => {
const context = app . context ( contextId );
if ( ! context ) {
return {
result: JSON . stringify ({
error: "Context not found" ,
contextId
})
};
}
if ( itemIds && itemIds . length > 0 ) {
// Process specific items
const jobs = [];
for ( const itemId of itemIds ) {
const { job } = await context . embeddings . generate . one ({
item: { id: itemId },
trigger: "api" ,
config: exuluConfig
});
jobs . push ( job );
}
return {
result: JSON . stringify ({
status: "queued" ,
itemCount: itemIds . length
}),
job: jobs . join ( "," )
};
} else {
// Process all items
const { jobs , items } = await context . embeddings . generate . all (
exuluConfig
);
return {
result: JSON . stringify ({
status: "queued" ,
itemCount: items
}),
job: jobs . join ( "," )
};
}
}
});
Configuration types reference
Text configuration values {
name : "api_endpoint" ,
description : "API base URL" ,
type : "string" ,
default : "https://api.example.com"
}
Numeric configuration values {
name : "timeout_ms" ,
description : "Request timeout in milliseconds" ,
type : "number" ,
default : 5000
}
True/false flags {
name : "enable_cache" ,
description : "Enable response caching" ,
type : "boolean" ,
default : true
}
References to ExuluVariables (for secrets and environment values) {
name : "api_key" ,
description : "API authentication key" ,
type : "variable"
// No default - must be set in variables table
}
Variables are stored in the database and can be encrypted. Perfect for API keys, credentials, and environment-specific values.
Error handling patterns
execute : async ( inputs ) => {
try {
const data = await riskyOperation ( inputs );
return {
result: JSON . stringify ({ success: true , data })
};
} catch ( error ) {
return {
result: JSON . stringify ({
success: false ,
error: error . message
})
};
}
}
execute : async ( inputs ) => {
if ( ! inputs . userId ) {
throw new Error ( "userId is required" );
}
const user = await db . users . findOne ({ id: inputs . userId });
if ( ! user ) {
throw new Error ( `User not found: ${ inputs . userId } ` );
}
return { result: JSON . stringify ( user ) };
}
execute : async function* ( inputs ) {
for ( const item of inputs . items ) {
try {
const result = await processItem ( item );
yield {
result: JSON . stringify ({
item ,
status: "success" ,
data: result
})
};
} catch ( error ) {
yield {
result: JSON . stringify ({
item ,
status: "error" ,
error: error . message
})
};
}
}
}
Next steps