Skip to content

Tools ​

Tools are the building blocks that allow AI agents to interact with the world. AgentForge provides a comprehensive tool system with rich metadata, automatic validation, and seamless LangChain integration.

Related Concepts

What is a Tool? ​

A tool is a function that an AI agent can call to perform actions or retrieve information. Tools enable agents to:

  • Read and write files
  • Make HTTP requests
  • Query databases
  • Perform calculations
  • Search the web
  • And much more...

Security Boundary

Many tools are intentionally powerful. Read the repository Security Policy before exposing filesystem, network, shell, or data-mutation tools to model-controlled input. For filesystem tools, configure createFileSystemPolicy({ workspaceRoot, allowedRoots }) on both file and directory factories; reserve unrestricted exports and allowOutsideRoots: true for trusted local automation.

Why AgentForge Tools? ​

AgentForge's tool system goes beyond simple function calling:

Rich Metadata ​

Every tool includes comprehensive metadata that helps both LLMs and developers understand what the tool does:

  • Name & Description - Clear identification
  • Category & Tags - Organization and discovery
  • Examples - Usage demonstrations
  • Usage Notes - Important context
  • Limitations - What the tool can't do
  • Version & Author - Tracking and attribution

Type Safety ​

Full TypeScript support with Zod schema validation ensures:

  • Input validation at runtime
  • Type inference for better DX
  • Automatic error messages
  • IDE autocomplete

LangChain Compatible ​

Tools can be seamlessly converted to LangChain's DynamicStructuredTool format, allowing you to use them with any LangChain agent or workflow.

Creating Tools ​

The fluent builder API is the easiest way to create tools:

typescript
import { toolBuilder, ToolCategory } from '@agentforge/core';
import { z } from 'zod';

const readFileTool = toolBuilder()
  .name('read-file')
  .description('Read the contents of a file from the file system')
  .category(ToolCategory.FILE_SYSTEM)
  .tags(['file', 'read', 'io'])
  .usageNotes('Paths are relative to the current working directory')
  .limitation('Cannot read binary files larger than 10MB')
  .example({
    description: 'Read a text file',
    input: { path: './README.md' },
    explanation: 'Reads the README file in the current directory',
  })
  .schema(z.object({
    path: z.string().describe('Path to the file to read'),
    encoding: z.string().default('utf-8').describe('File encoding (default: utf-8)'),
  }))
  .implement(async ({ path, encoding }) => {
    const fs = await import('fs/promises');
    return await fs.readFile(path, encoding);
  })
  .build();

Builder Methods ​

MethodRequiredDescription
.name(string)YesTool identifier (kebab-case)
.description(string)YesWhat the tool does (min 10 chars)
.category(ToolCategory)YesPrimary category
.schema(ZodSchema)YesInput validation schema
.implement(function)YesAsync implementation function
.tags(string[])NoTags for discovery
.displayName(string)NoHuman-readable name
.usageNotes(string)NoImportant usage information
.limitation(string)NoWhat the tool can't do
.example(ToolExample)NoUsage example
.version(string)NoTool version
.author(string)NoTool author

Schema Descriptions Required

All schema fields must include .describe() for LLM understanding:

typescript
// ✅ Good
z.string().describe('Path to the file')

// ❌ Bad - missing description
z.string()

Tool Categories ​

AgentForge provides predefined categories for organizing tools:

typescript
enum ToolCategory {
  FILE_SYSTEM = 'file-system',  // File system operations
  WEB = 'web',                   // Web/HTTP operations
  CODE = 'code',                 // Code execution, analysis
  DATABASE = 'database',         // Database operations
  API = 'api',                   // API integrations
  UTILITY = 'utility',           // General utilities
  CUSTOM = 'custom'              // Custom tools
}

Choose the category that best fits your tool's primary purpose.

Tool Registry ​

The ToolRegistry provides centralized tool management with CRUD operations, querying, and events.

Basic Usage ​

typescript
import { ToolRegistry } from '@agentforge/core';

const registry = new ToolRegistry();

// Register tools
registry.register(readFileTool);
registry.register(writeFileTool);

// Get a specific tool
const tool = registry.get('read-file');

// Get all tools
const allTools = registry.getAll();

// Query by category
const fileTools = registry.getByCategory(ToolCategory.FILE_SYSTEM);

// Query by tag
const readTools = registry.getByTag('read');

// Search
const results = registry.search('file');

Bulk Operations ​

typescript
// Register multiple tools at once
registry.registerMany([tool1, tool2, tool3]);

// Clear all tools
registry.clear();

Events ​

Listen to registry events for observability:

typescript
import { RegistryEvent } from '@agentforge/core';

registry.on(RegistryEvent.TOOL_REGISTERED, (tool) => {
  console.log(`Tool registered: ${tool.metadata.name}`);
});

registry.on(RegistryEvent.TOOL_REMOVED, ({ name }) => {
  console.log(`Tool removed: ${name}`);
});

LangChain Integration ​

AgentForge tools can be converted to LangChain format for use with LangChain agents:

typescript
// Convert a single tool
const langchainTool = readFileTool.toLangChainTool();

// Convert all tools in registry
const langchainTools = registry.toLangChainTools();

// Use with LangChain agent
import { ChatOpenAI } from '@langchain/openai';
import { createReActAgent } from '@agentforge/patterns';

const agent = createReActAgent({
  model: new ChatOpenAI({ model: 'gpt-4' }),
  tools: registry.toLangChainTools(), // All tools from registry
});

Prompt Generation ​

The registry can generate formatted prompts for LLMs:

typescript
// Generate prompt for all tools
const prompt = registry.generatePrompt();

// Generate prompt with options
const detailedPrompt = registry.generatePrompt({
  includeExamples: true,      // Include usage examples
  includeUsageNotes: true,    // Include usage notes
  includeLimitations: true,   // Include limitations
  format: 'detailed',         // 'minimal' | 'standard' | 'detailed'
});

// Generate prompt for specific tools
const fileToolsPrompt = registry.generatePrompt({
  tools: registry.getByCategory(ToolCategory.FILE_SYSTEM),
});

Best Practices ​

1. Clear Descriptions ​

Write descriptions that help LLMs understand when to use the tool:

typescript
// ✅ Good - specific and actionable
.description('Read the contents of a text file from the file system')

// ❌ Bad - too vague
.description('Reads files')

2. Comprehensive Examples ​

Provide examples that demonstrate typical usage:

typescript
.example({
  description: 'Read a configuration file',
  input: { path: './config.json', encoding: 'utf-8' },
  explanation: 'Reads a JSON configuration file with UTF-8 encoding',
})

3. Document Limitations ​

Be explicit about what the tool cannot do:

typescript
.limitation('Cannot read binary files larger than 10MB')
.limitation('Requires read permissions on the file')
.limitation('Does not follow symbolic links')

4. Use Appropriate Categories ​

Choose categories that match the tool's primary purpose:

typescript
// ✅ Good - primary purpose is file operations
toolBuilder()
  .name('read-json-file')
  .category(ToolCategory.FILE_SYSTEM)
  .tags(['file', 'json', 'read'])

// ❌ Bad - DATABASE is not the primary purpose (file operations should use FILE_SYSTEM)
toolBuilder()
  .name('read-json-file')
  .category(ToolCategory.DATABASE)

5. Validate Inputs ​

Use Zod schemas to validate and transform inputs:

typescript
.schema(z.object({
  path: z.string()
    .min(1, 'Path cannot be empty')
    .describe('Path to the file'),
  encoding: z.enum(['utf-8', 'ascii', 'base64'])
    .default('utf-8')
    .describe('File encoding'),
  maxSize: z.number()
    .positive()
    .optional()
    .describe('Maximum file size in bytes'),
}))

Common Patterns ​

Error Handling ​

Recommended: Automatic Error Handling (v0.7.0+)

Use .implementSafe() for automatic error handling with consistent response format:

typescript
.implementSafe(async ({ path }) => {
  // No try-catch needed! Just write the happy path
  const fs = await import('fs/promises');
  const content = await fs.readFile(path, 'utf-8');
  return content;  // Return raw value - implementSafe wraps it automatically
})
// Returns: { success: true, data: "..." } or { success: false, error: "..." }

Alternative: Manual Error Handling

Use .implement() when you need custom error handling logic:

typescript
.implement(async ({ path }) => {
  try {
    const fs = await import('fs/promises');
    return await fs.readFile(path, 'utf-8');
  } catch (error) {
    if (error.code === 'ENOENT') {
      throw new Error(`File not found: ${path}`);
    }
    if (error.code === 'EACCES') {
      throw new Error(`Permission denied: ${path}`);
    }
    throw error;
  }
})

Async Operations ​

All tool implementations must be async:

typescript
// ✅ Good - async function
.implement(async ({ url }) => {
  const response = await fetch(url);
  return await response.text();
})

// ❌ Bad - sync function
.implement(({ value }) => {
  return value * 2; // Should be: return Promise.resolve(value * 2)
})

Composing Tools ​

Build complex tools from simpler ones:

typescript
const readJsonTool = toolBuilder()
  .name('read-json-file')
  .description('Read and parse a JSON file')
  .category(ToolCategory.FILE_SYSTEM)
  .schema(z.object({
    path: z.string().describe('Path to JSON file'),
  }))
  .implement(async ({ path }) => {
    // Use the read-file tool
    const content = await readFileTool.invoke({ path, encoding: 'utf-8' });
    return JSON.parse(content);
  })
  .build();

Next Steps ​

Released under the MIT License.