Overview
ExuluTool represents a function that agents can call to perform specific actions or retrieve information. Tools are the primary way to extend agent capabilities beyond text generation, enabling them to interact with external systems, query databases, perform calculations, or execute custom business logic.
Key features
AI SDK integration Built on Vercelβs AI SDK with native streaming support
Zod schema validation Type-safe input validation with Zod schemas
Multiple tool types Context search, function execution, agent delegation, web search
Configurable parameters Dynamic tool configuration with variables and defaults
Async & streaming Support for async execution and streaming responses
Direct execution Execute tools directly or through agent workflows
A tool is a structured function that:
Defines inputs using Zod schemas for type safety and validation
Executes logic through an async function or generator
Returns results that agents can use to respond to users
Integrates with AI SDK for seamless agent tool calling
Supports configuration with dynamic parameters and variables
Think of tools as the βhandsβ of your agents - they enable AI to take actions in your application.
Quick start
import { ExuluTool } from "@exulu/backend" ;
import { z } from "zod" ;
// Create a simple calculator tool
const calculatorTool = new ExuluTool ({
id: "calculator" ,
name: "calculator" ,
description: "Performs basic arithmetic operations" ,
type: "function" ,
category: "math" ,
inputSchema: z . object ({
operation: z . enum ([ "add" , "subtract" , "multiply" , "divide" ]),
a: z . number (). describe ( "First number" ),
b: z . number (). describe ( "Second number" )
}),
config: [],
execute : async ({ operation , a , b }) => {
let result ;
switch ( operation ) {
case "add" :
result = a + b ;
break ;
case "subtract" :
result = a - b ;
break ;
case "multiply" :
result = a * b ;
break ;
case "divide" :
result = a / b ;
break ;
}
return {
result: ` ${ a } ${ operation } ${ b } = ${ result } `
};
}
});
// Register with ExuluApp
const app = new ExuluApp ();
await app . create ({
tools: {
calculator: calculatorTool
},
config: { /* ... */ }
});
ExuluTool supports four distinct types:
function
context
agent
web_search
Custom functions that execute business logic, API calls, or data processing. Best for: Custom integrations, calculations, data transformations, API calls Semantic search tools that query ExuluContext instances. Best for: RAG (retrieval-augmented generation), knowledge base queries Context tools are automatically generated when you create an ExuluContext with enableAsTool: true.
Delegation tools that invoke other agents for specialized tasks. Best for: Multi-agent workflows, task delegation, specialized expertise Web search tools that fetch real-time information from the internet. Best for: Current events, fact-checking, research
Architecture
AI SDK integration
ExuluTool wraps Vercelβs AI SDK tool() function, providing:
import { tool } from "ai" ;
// Inside ExuluTool constructor
this . tool = tool ({
description: description ,
inputSchema: inputSchema || z . object ({}),
execute
});
This enables seamless integration with AI SDKβs streaming and tool calling features.
Tools use Zod schemas to define and validate inputs:
inputSchema : z . object ({
userId: z . string (). describe ( "The user ID to look up" ),
includeHistory: z . boolean (). optional (). describe ( "Include user history" )
})
The schema serves three purposes:
Type safety - TypeScript types are inferred from the schema
Validation - Inputs are validated before execution
Documentation - Agents receive schema descriptions to understand parameters
Execution patterns
Tools support two execution patterns:
Standard async function that returns a result: execute : async ( inputs ) => {
const data = await fetchData ( inputs . userId );
return {
result: JSON . stringify ( data )
};
}
Generator function for streaming responses: execute : async function* ( inputs ) {
for await ( const chunk of processData ( inputs )) {
yield {
result: chunk
};
}
}
Core concepts
Each tool has three identifiers:
Unique identifier used for database references. Must start with a letter or underscore, contain only alphanumeric characters and underscores, 5-80 characters long. Should not change after creation.
Human-readable name shown to agents and in UI. Used for tool invocation.
Optional category for organizing tools (e.g., βdatabaseβ, βapiβ, βmathβ)
{
id : "get_user_profile" ,
name : "get_user_profile" ,
category : "database"
}
The config array defines runtime parameters that can be configured per tool instance:
config : [
{
name: "api_endpoint" ,
description: "Base URL for the API" ,
type: "string" ,
default: "https://api.example.com"
},
{
name: "enable_caching" ,
description: "Enable response caching" ,
type: "boolean" ,
default: true
},
{
name: "timeout" ,
description: "Request timeout in milliseconds" ,
type: "number" ,
default: 5000
},
{
name: "api_key" ,
description: "API authentication key" ,
type: "variable" // References ExuluVariables
}
]
Configuration types:
string - Text values
number - Numeric values
boolean - True/false flags
variable - References to ExuluVariables (for secrets/environment values)
Return values
Tool execution can return:
The main result to return to the agent (often JSON stringified)
Optional job ID if the tool queued a background task
Optional array of items (typically used by context search tools)
return {
result: JSON . stringify ({ status: "success" , data: userData }),
job: "job-123-456" // If queued for background processing
};
Usage patterns
Tools are automatically available to agents when registered with ExuluApp:
const app = new ExuluApp ();
await app . create ({
tools: {
calculator: calculatorTool ,
weatherLookup: weatherTool ,
databaseQuery: dbTool
},
agents: {
assistant: assistantAgent
},
config: { /* ... */ }
});
Agents will automatically receive these tools and can call them during conversations based on user requests.
Direct execution
You can also execute tools directly in your code:
const result = await calculatorTool . execute ({
agent: "assistant" ,
config: exuluConfig ,
user: currentUser ,
inputs: {
operation: "add" ,
a: 5 ,
b: 3
}
});
console . log ( result . result ); // "5 add 3 = 8"
With streaming
For long-running operations, use generators to stream results:
const processTool = new ExuluTool ({
id: "process_documents" ,
name: "process_documents" ,
description: "Processes multiple documents" ,
type: "function" ,
inputSchema: z . object ({
documentIds: z . array ( z . string ())
}),
config: [],
execute : async function* ({ documentIds }) {
for ( const docId of documentIds ) {
const result = await processDocument ( docId );
yield {
result: `Processed document ${ docId } : ${ result } `
};
}
}
});
Common use cases
Create tools that query your database: const getUserTool = new ExuluTool ({
id: "get_user" ,
name: "get_user" ,
description: "Retrieves user information from database" ,
type: "function" ,
inputSchema: z . object ({
userId: z . string ()
}),
config: [],
execute : async ({ userId }) => {
const user = await db . users . findOne ({ id: userId });
return { result: JSON . stringify ( user ) };
}
});
Wrap external APIs as tools: const weatherTool = new ExuluTool ({
id: "weather" ,
name: "get_weather" ,
description: "Gets current weather for a location" ,
type: "function" ,
inputSchema: z . object ({
location: z . string (),
units: z . enum ([ "metric" , "imperial" ]). optional ()
}),
config: [
{
name: "api_key" ,
description: "Weather API key" ,
type: "variable"
}
],
execute : async ({ location , units = "metric" }) => {
const response = await fetch (
`https://api.weather.com?location= ${ location } &units= ${ units } `
);
const data = await response . json ();
return { result: JSON . stringify ( data ) };
}
});
Encapsulate complex business logic: const orderTool = new ExuluTool ({
id: "create_order" ,
name: "create_order" ,
description: "Creates a new order with validation and inventory check" ,
type: "function" ,
inputSchema: z . object ({
productId: z . string (),
quantity: z . number (). positive (),
userId: z . string ()
}),
config: [],
execute : async ({ productId , quantity , userId }) => {
// Check inventory
const available = await checkInventory ( productId );
if ( available < quantity ) {
return {
result: JSON . stringify ({
success: false ,
error: "Insufficient inventory"
})
};
}
// Create order
const order = await createOrder ({ productId , quantity , userId });
// Update inventory
await updateInventory ( productId , - quantity );
return {
result: JSON . stringify ({
success: true ,
orderId: order . id
})
};
}
});
Tools for file uploads, processing, and retrieval: const fileProcessorTool = new ExuluTool ({
id: "process_pdf" ,
name: "process_pdf" ,
description: "Extracts text from PDF files" ,
type: "function" ,
inputSchema: z . object ({
fileKey: z . string (). describe ( "S3 key of the PDF file" )
}),
config: [],
execute : async ({ fileKey }) => {
const text = await extractTextFromPDF ( fileKey );
return {
result: JSON . stringify ({
fileKey ,
extractedText: text ,
wordCount: text . split ( / \s + / ). length
})
};
}
});
Best practices
Input descriptions : Always add .describe() to Zod schema fields. These descriptions help agents understand what each parameter does.
ID stability : Never change a toolβs id after creation. The ID is used in database references and changing it will break existing integrations.
Error handling : Always handle errors gracefully and return meaningful error messages in the result:execute : async ( inputs ) => {
try {
const data = await riskyOperation ( inputs );
return { result: JSON . stringify ( data ) };
} catch ( error ) {
return {
result: JSON . stringify ({
error: error . message ,
success: false
})
};
}
}
JSON stringification : Always stringify non-string results. Agents expect string results and will parse JSON automatically.
Next steps