Tool¶
Tool encapsulates functions that AI agents can execute. It provides a flexible API supporting both simple single-parameter functions and advanced multi-parameter operations.
Overview¶
Tools enable agents to interact with external systems, process data, and perform specialized tasks:
- Simple Tools: Quick function wrapping with string input
- Advanced Tools: Structured parameters with descriptions
- Reusable: Share tools across multiple agents and contexts
- Type-Safe: Full TypeScript support with generics
Key Features¶
- ✅ Two-tier API (simple and advanced)
- ✅ Sync and async function support
- ✅ Parameter schema definition
- ✅ JSON serialization
- ✅ Type-safe with TypeScript generics
- ✅ Framework-agnostic
Constructor¶
Tool has two constructor overloads for different use cases:
Simple Tool¶
Creates a tool with a single string input parameter.
Parameters: - name: Unique tool identifier - handler: Function that processes input and returns result
Example:
import { Tool } from '@arcaelas/agent';
// Weather tool
const weather_tool = new Tool('get_weather', (agent: Agent, input: string) => {
// input: "What's the weather in Madrid?"
return "Sunny, 24°C in Madrid";
});
// Time tool
const time_tool = new Tool('get_time', (agent: Agent) => {
return new Date().toLocaleString();
});
// File reader tool
const read_file = new Tool('read_file', async (agent: Agent, path: string) => {
const content = await fs.readFile(path, 'utf-8');
return content;
});
Advanced Tool¶
Creates a tool with structured parameters and detailed configuration.
Parameters: - name: Unique tool identifier - options: Advanced configuration object
ToolOptions¶
interface ToolOptions<T = Record<string, string>> {
/** Clear description of what the tool does */
description: string;
/** Parameter schema: Record<string,string> for legacy or Zod schema for typed params */
parameters?: T;
/** Function to execute (sync or async) */
func(agent: Agent, params: InferParams<T>): any;
}
parameters accepts either a Record<string, string> (values are descriptions) or a Zod schema (z.ZodObject). With Zod, full type inference and JSON Schema generation are automatic:
import { z } from 'zod';
import { Tool } from '@arcaelas/agent';
const calc = new Tool('calculate', {
description: 'Perform basic arithmetic',
parameters: z.object({
a: z.number().describe('First number'),
b: z.number().describe('Second number'),
op: z.enum(['+', '-', '*', '/']).describe('Operation'),
}),
func: (agent, { a, b, op }) => {
switch (op) {
case '+': return a + b;
case '-': return a - b;
case '*': return a * b;
case '/': return b !== 0 ? a / b : 'Division by zero';
}
},
});
Example:
import { Tool } from '@arcaelas/agent';
// Database search tool
const search_tool = new Tool('search_database', {
description: 'Search customer database with filters',
parameters: {
query: 'Search query string',
limit: 'Maximum results to return (default: 10)',
category: 'Filter by category (optional)'
},
func: async (agent, { query, limit, category }) => {
const results = await database.search({
query,
limit: parseInt(limit || '10'),
category: category || null
});
return JSON.stringify(results);
}
});
// Calculator tool
const calculator = new Tool('calculate', {
description: 'Perform basic mathematical operations',
parameters: {
operation: 'Operation to perform (+, -, *, /)',
a: 'First number',
b: 'Second number'
},
func: (agent, { operation, a, b }) => {
const num_a = parseFloat(a);
const num_b = parseFloat(b);
switch (operation) {
case '+': return (num_a + num_b).toString();
case '-': return (num_a - num_b).toString();
case '*': return (num_a * num_b).toString();
case '/': return num_b !== 0
? (num_a / num_b).toString()
: 'Error: Division by zero';
default: return 'Invalid operation';
}
}
});
Properties¶
name¶
Unique identifier for the tool. Used for deduplication in contexts.
Example:
description¶
Human-readable description of what the tool does. For simple tools, defaults to the tool name.
Example:
const tool = new Tool('search', {
description: 'Search through customer records',
func: (agent) => "..."
});
console.log(tool.description); // "Search through customer records"
parameters¶
Parameter schema describing expected inputs. Simple tools get { input: string } automatically.
Example:
// Simple tool parameters
const simple = new Tool('greet', (agent, input) => `Hello ${input}`);
console.log(simple.parameters); // { input: "<tool-input>" }
// Advanced tool parameters
const advanced = new Tool('search', {
description: 'Search',
parameters: {
query: 'Search term',
limit: 'Max results'
},
func: (agent) => "..."
});
console.log(advanced.parameters);
// { query: "Search term", limit: "Max results" }
func¶
The executable function. Can be sync or async.
Example:
const tool = new Tool('process', (agent, input) => {
return input.toUpperCase();
});
// Execute directly (passing agent instance)
const result = tool.func(agent, { input: "hello" });
console.log(result); // "HELLO"
Methods¶
toJSON()¶
toJSON(): {
type: "function";
function: {
name: string;
description: string;
parameters: Record<string, any>; // JSON Schema
};
}
Serializes the tool to OpenAI-compatible function-calling format. Called automatically by JSON.stringify().
- If
parametersis a Zod schema, it is converted to JSON Schema viaz.toJSONSchema(). - If
parametersis aRecord<string, string>, each key becomes a{ type: "string", description: ... }property, all required.
Example:
const tool = new Tool('calculator', {
description: 'Basic math operations',
parameters: {
operation: '+, -, *, /',
a: 'First number',
b: 'Second number'
},
func: (agent) => "..."
});
console.log(JSON.stringify(tool));
// {
// "type": "function",
// "function": {
// "name": "calculator",
// "description": "Basic math operations",
// "parameters": {
// "type": "object",
// "properties": {
// "operation": { "type": "string", "description": "+, -, *, /" },
// "a": { "type": "string", "description": "First number" },
// "b": { "type": "string", "description": "Second number" }
// },
// "required": ["operation", "a", "b"]
// }
// }
// }
// Pass directly to provider
const json_tool = tool.toJSON();
toString()¶
Returns human-readable string representation for debugging.
Returns: String in format "Tool(name): description"
Example:
const tool = new Tool('weather', {
description: 'Get current weather',
func: (agent) => "..."
});
console.log(tool.toString()); // "Tool(weather): Get current weather"
console.log(String(tool)); // Same result
Usage Patterns¶
Pattern: API Integration¶
Wrap external API calls:
const github_search = new Tool('github_search', {
description: 'Search GitHub repositories',
parameters: {
query: 'Search query',
language: 'Programming language filter (optional)',
stars: 'Minimum stars (optional)'
},
func: async (agent, { query, language, stars }) => {
const params = new URLSearchParams({ q: query });
if (language) params.append('language', language);
if (stars) params.append('stars', `>=${stars}`);
const response = await fetch(
`https://api.github.com/search/repositories?${params}`
);
const data = await response.json();
return JSON.stringify(data.items.slice(0, 5));
}
});
Pattern: Database Operations¶
Create CRUD tools:
const create_user = new Tool('create_user', {
description: 'Create new user account',
parameters: {
email: 'User email address',
name: 'User full name',
role: 'User role (admin, user, guest)'
},
func: async (agent, { email, name, role }) => {
const user = await db.users.create({
email,
name,
role: role || 'user'
});
return `User created with ID: ${user.id}`;
}
});
const find_user = new Tool('find_user', {
description: 'Find user by email',
parameters: {
email: 'Email address to search'
},
func: async (agent, { email }) => {
const user = await db.users.findOne({ email });
return user ? JSON.stringify(user) : 'User not found';
}
});
Pattern: Data Processing¶
Transform and analyze data:
const analyze_text = new Tool('analyze_text', {
description: 'Analyze text sentiment and extract keywords',
parameters: {
text: 'Text to analyze'
},
func: async (agent, { text }) => {
const sentiment = await nlp.sentiment(text);
const keywords = await nlp.keywords(text, { limit: 5 });
return JSON.stringify({
sentiment: sentiment.score,
keywords: keywords.map(k => k.word)
});
}
});
const summarize = new Tool('summarize', {
description: 'Generate text summary',
parameters: {
text: 'Text to summarize',
max_length: 'Maximum summary length in words (default: 100)'
},
func: async (agent, { text, max_length }) => {
const summary = await nlp.summarize(text, {
maxLength: parseInt(max_length || '100')
});
return summary;
}
});
Pattern: File Operations¶
Handle file system tasks:
const read_file = new Tool('read_file', {
description: 'Read file contents',
parameters: {
path: 'File path to read'
},
func: async (agent, { path }) => {
try {
const content = await fs.readFile(path, 'utf-8');
return content;
} catch (error) {
return `Error reading file: ${error.message}`;
}
}
});
const list_directory = new Tool('list_directory', {
description: 'List files in directory',
parameters: {
path: 'Directory path',
pattern: 'File pattern to match (optional, e.g., *.js)'
},
func: async (agent, { path, pattern }) => {
const files = await fs.readdir(path);
const filtered = pattern
? files.filter(f => minimatch(f, pattern))
: files;
return JSON.stringify(filtered);
}
});
Pattern: Tool Composition¶
Combine multiple operations:
const process_order = new Tool('process_order', {
description: 'Process customer order end-to-end',
parameters: {
order_id: 'Order ID to process',
notify_customer: 'Send notification email (true/false)'
},
func: async (agent, { order_id, notify_customer }) => {
// Validate order
const order = await db.orders.findById(order_id);
if (!order) return 'Order not found';
// Process payment
const payment = await stripe.charge(order.total, order.payment_method);
if (!payment.success) return 'Payment failed';
// Update inventory
await inventory.decrement(order.items);
// Send notification
if (notify_customer === 'true') {
await email.send(order.customer_email, 'Order Confirmed', {
order_id,
total: order.total
});
}
return `Order ${order_id} processed successfully`;
}
});
Error Handling¶
Tools should handle errors gracefully:
const safe_api_call = new Tool('api_call', {
description: 'Make API call with error handling',
parameters: {
endpoint: 'API endpoint URL',
method: 'HTTP method (GET, POST, etc.)'
},
func: async (agent, { endpoint, method }) => {
try {
const response = await fetch(endpoint, { method });
if (!response.ok) {
return JSON.stringify({
error: true,
status: response.status,
message: response.statusText
});
}
const data = await response.json();
return JSON.stringify({ success: true, data });
} catch (error) {
return JSON.stringify({
error: true,
message: error.message
});
}
}
});
Best Practices¶
1. Clear Naming¶
Use descriptive, action-oriented names:
// ✅ Good: Clear action names
new Tool('search_customers', ...);
new Tool('create_invoice', ...);
new Tool('send_email', ...);
// ❌ Bad: Vague names
new Tool('search', ...);
new Tool('create', ...);
new Tool('send', ...);
2. Detailed Descriptions¶
Write helpful descriptions for users and AI:
// ✅ Good: Detailed description
new Tool('search_products', {
description: 'Search product catalog by name, category, or SKU. Returns product details including price, stock, and images.',
parameters: {...},
func: ...
});
// ❌ Bad: Vague description
new Tool('search_products', {
description: 'Search products',
parameters: {...},
func: ...
});
3. Parameter Documentation¶
Document each parameter clearly:
// ✅ Good: Clear parameter descriptions
parameters: {
query: 'Search query string (e.g., "red shoes")',
max_price: 'Maximum price in USD (optional, e.g., "99.99")',
category: 'Product category filter (optional, e.g., "electronics")'
}
// ❌ Bad: Minimal descriptions
parameters: {
query: 'Query',
max_price: 'Price',
category: 'Category'
}
4. Return Structured Data¶
Return JSON for complex data:
// ✅ Good: Structured JSON response
func: async (agent, { user_id }) => {
const user = await db.findUser(user_id);
return JSON.stringify({
id: user.id,
name: user.name,
email: user.email,
created_at: user.created_at
});
}
// ❌ Bad: Unstructured string
func: async (agent, { user_id }) => {
const user = await db.findUser(user_id);
return `User: ${user.name}, Email: ${user.email}`;
}
5. Async for I/O¶
Use async for operations involving I/O:
// ✅ Good: Async for database/API calls
const db_tool = new Tool('query_db', {
description: 'Query database',
func: async (agent, { query }) => {
const results = await db.query(query);
return JSON.stringify(results);
}
});
// ❌ Bad: Sync with blocking operations
const bad_tool = new Tool('query_db', {
description: 'Query database',
func: (agent, { query }) => {
const results = db.querySync(query); // Blocks event loop
return JSON.stringify(results);
}
});
6. Validate Inputs¶
Validate and sanitize parameters:
const validated_tool = new Tool('update_user', {
description: 'Update user information',
parameters: {
user_id: 'User ID (numeric)',
email: 'New email address'
},
func: async (agent, { user_id, email }) => {
// Validate user_id
const id = parseInt(user_id);
if (isNaN(id) || id <= 0) {
return 'Error: Invalid user ID';
}
// Validate email
if (!email.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) {
return 'Error: Invalid email format';
}
// Process
await db.users.update(id, { email });
return 'User updated successfully';
}
});
Type Safety¶
Tool supports TypeScript generics for parameter type safety:
import { Tool, ToolOptions } from '@arcaelas/agent';
// Define parameter interface
interface SearchParams {
query: string;
limit: string;
category: string;
}
// Create typed tool
const search_tool = new Tool<SearchParams>('search', {
description: 'Search with typed parameters',
parameters: {
query: 'Search query',
limit: 'Max results',
category: 'Category filter'
},
func: (agent, { query, limit, category }) => {
// TypeScript knows the parameter types
const num_limit = parseInt(limit);
return database.search(query, num_limit, category);
}
});
// Type inference
const inferred = new Tool('inferred', {
description: 'Inferred params',
parameters: {
foo: 'Foo param',
bar: 'Bar param'
},
func: (agent, { foo, bar }) => {
// TypeScript infers foo and bar as strings
return `${foo} ${bar}`;
}
});
Testing Tools¶
Test tools independently:
import { Tool } from '@arcaelas/agent';
describe('calculator_tool', () => {
const calculator = new Tool('calculate', {
description: 'Basic calculator',
parameters: {
operation: 'Math operation',
a: 'First number',
b: 'Second number'
},
func: (agent, { operation, a, b }) => {
const num_a = parseFloat(a);
const num_b = parseFloat(b);
switch (operation) {
case '+': return (num_a + num_b).toString();
case '-': return (num_a - num_b).toString();
default: return 'Invalid operation';
}
}
});
test('addition', () => {
const result = calculator.func(agent, { operation: '+', a: '5', b: '3' });
expect(result).toBe('8');
});
test('subtraction', () => {
const result = calculator.func(agent, { operation: '-', a: '10', b: '4' });
expect(result).toBe('6');
});
test('invalid operation', () => {
const result = calculator.func(agent, { operation: '%', a: '5', b: '2' });
expect(result).toBe('Invalid operation');
});
});
Related¶
- Context - Manages tools in hierarchical contexts
- Agent - Executes tools during conversations
- Built-in Tools - Pre-built RemoteTool and TimeTool
- Custom Tools Example - Practical implementations
Next: Learn about Rule →