Skip to main content

Dynamic Tools

PromptSmith allows you to dynamically register tools at runtime based on user context, database state, API availability, and other runtime conditions. This enables building highly adaptive agents that change their capabilities based on the situation.

Runtime Tool Registration

Building Tool Lists Dynamically

import { createPromptBuilder } from 'promptsmith';
import { z } from 'zod';
import type { ExecutableToolDefinition } from 'promptsmith';

// Define available tool definitions
const availableTools: Record<string, ExecutableToolDefinition> = {
  searchDocs: {
    name: 'search_documentation',
    description: 'Search technical documentation',
    schema: z.object({
      query: z.string().describe('Search query')
    }),
    execute: async ({ query }) => {
      return { results: [] };
    }
  },
  queryDatabase: {
    name: 'query_database',
    description: 'Query the production database',
    schema: z.object({
      sql: z.string().describe('SQL query')
    }),
    execute: async ({ sql }) => {
      return { rows: [] };
    }
  },
  sendEmail: {
    name: 'send_email',
    description: 'Send email notifications',
    schema: z.object({
      to: z.string().describe('Recipient email'),
      subject: z.string().describe('Email subject'),
      body: z.string().describe('Email body')
    }),
    execute: async ({ to, subject, body }) => {
      return { sent: true };
    }
  },
  deployCode: {
    name: 'deploy_code',
    description: 'Deploy code to production',
    schema: z.object({
      branch: z.string().describe('Git branch'),
      environment: z.enum(['staging', 'production'])
    }),
    execute: async ({ branch, environment }) => {
      return { deployed: true };
    }
  }
};

// Build agent with tools based on user role
function buildAgentForRole(role: 'viewer' | 'developer' | 'admin') {
  const builder = createPromptBuilder()
    .withIdentity(`You are a ${role} assistant`)
    .withGuardrails();
  
  // All roles get documentation search
  builder.withTool(availableTools.searchDocs);
  
  // Developers and admins get database access
  if (role === 'developer' || role === 'admin') {
    builder.withTool(availableTools.queryDatabase);
  }
  
  // Admins get additional tools
  if (role === 'admin') {
    builder.withTool(availableTools.sendEmail);
    builder.withTool(availableTools.deployCode);
  }
  
  return builder;
}

// Usage
const viewerAgent = buildAgentForRole('viewer');
console.log(viewerAgent.getSummary().toolsCount); // 1

const developerAgent = buildAgentForRole('developer');
console.log(developerAgent.getSummary().toolsCount); // 2

const adminAgent = buildAgentForRole('admin');
console.log(adminAgent.getSummary().toolsCount); // 4

Database-Driven Tool Registration

import { createPromptBuilder } from 'promptsmith';
import { z } from 'zod';

interface IntegrationConfig {
  id: string;
  name: string;
  enabled: boolean;
  type: 'crm' | 'email' | 'calendar' | 'storage';
  apiKey?: string;
}

// Fetch user's enabled integrations from database
async function fetchUserIntegrations(userId: string): Promise<IntegrationConfig[]> {
  // In real app, query database
  return [
    { id: '1', name: 'Salesforce', enabled: true, type: 'crm', apiKey: 'sk_xxx' },
    { id: '2', name: 'SendGrid', enabled: true, type: 'email', apiKey: 'sg_xxx' },
    { id: '3', name: 'Google Calendar', enabled: false, type: 'calendar' },
    { id: '4', name: 'Dropbox', enabled: true, type: 'storage', apiKey: 'db_xxx' }
  ];
}

// Build tools from integration configs
function createToolsFromIntegrations(
  integrations: IntegrationConfig[]
): ExecutableToolDefinition[] {
  const tools: ExecutableToolDefinition[] = [];
  
  for (const integration of integrations) {
    if (!integration.enabled) continue;
    
    switch (integration.type) {
      case 'crm':
        tools.push({
          name: `search_${integration.name.toLowerCase()}`,
          description: `Search ${integration.name} CRM for customer data`,
          schema: z.object({
            query: z.string().describe('Search query')
          }),
          execute: async ({ query }) => {
            // Use integration.apiKey to call API
            return { contacts: [] };
          }
        });
        tools.push({
          name: `create_${integration.name.toLowerCase()}_contact`,
          description: `Create a new contact in ${integration.name}`,
          schema: z.object({
            name: z.string(),
            email: z.string(),
            company: z.string().optional()
          }),
          execute: async (data) => {
            return { created: true };
          }
        });
        break;
        
      case 'email':
        tools.push({
          name: `send_email_${integration.name.toLowerCase()}`,
          description: `Send email via ${integration.name}`,
          schema: z.object({
            to: z.string(),
            subject: z.string(),
            body: z.string()
          }),
          execute: async ({ to, subject, body }) => {
            return { sent: true, messageId: 'msg_xxx' };
          }
        });
        break;
        
      case 'calendar':
        tools.push({
          name: `schedule_meeting_${integration.name.toLowerCase()}`,
          description: `Schedule a meeting in ${integration.name}`,
          schema: z.object({
            title: z.string(),
            startTime: z.string(),
            duration: z.number(),
            attendees: z.array(z.string())
          }),
          execute: async (data) => {
            return { eventId: 'evt_xxx' };
          }
        });
        break;
        
      case 'storage':
        tools.push({
          name: `upload_file_${integration.name.toLowerCase()}`,
          description: `Upload file to ${integration.name}`,
          schema: z.object({
            filename: z.string(),
            content: z.string()
          }),
          execute: async ({ filename, content }) => {
            return { url: 'https://...' };
          }
        });
        break;
    }
  }
  
  return tools;
}

// Build agent with user's integrations
async function buildPersonalizedAgent(userId: string) {
  const integrations = await fetchUserIntegrations(userId);
  const tools = createToolsFromIntegrations(integrations);
  
  const enabledIntegrationNames = integrations
    .filter(i => i.enabled)
    .map(i => i.name)
    .join(', ');
  
  return createPromptBuilder()
    .withIdentity('You are a personal productivity assistant')
    .withCapabilities([
      'Manage your contacts and CRM',
      'Send emails and communications',
      'Schedule meetings and events',
      'Organize files and documents'
    ])
    .withContext(`
      User has the following integrations enabled: ${enabledIntegrationNames}
      Always use the appropriate integration tools when the user requests related actions.
    `)
    .withTools(tools)
    .withConstraint(
      'must',
      'Always confirm before sending emails or creating calendar events'
    );
}

// Usage
const userAgent = await buildPersonalizedAgent('user-123');
console.log(userAgent.getSummary());
// toolsCount: 5 (Salesforce search/create, SendGrid email, Dropbox upload)

API-Driven Tool Discovery

Dynamic Tool Loading from OpenAPI Spec

import { createPromptBuilder } from 'promptsmith';
import { z } from 'zod';
import type { ExecutableToolDefinition } from 'promptsmith';

interface OpenAPIEndpoint {
  path: string;
  method: string;
  summary: string;
  operationId: string;
  parameters: any[];
}

// Parse OpenAPI spec and generate tools
async function loadToolsFromOpenAPI(
  apiUrl: string
): Promise<ExecutableToolDefinition[]> {
  // Fetch OpenAPI spec
  const spec = await fetch(`${apiUrl}/openapi.json`).then(r => r.json());
  const tools: ExecutableToolDefinition[] = [];
  
  // Parse endpoints from spec
  for (const [path, methods] of Object.entries(spec.paths || {})) {
    for (const [method, endpoint] of Object.entries(methods as any)) {
      if (method === 'parameters') continue;
      
      const operation = endpoint as OpenAPIEndpoint;
      
      // Convert parameters to Zod schema
      const schemaFields: Record<string, z.ZodType> = {};
      for (const param of operation.parameters || []) {
        if (param.schema?.type === 'string') {
          schemaFields[param.name] = z.string().describe(param.description || '');
        } else if (param.schema?.type === 'number') {
          schemaFields[param.name] = z.number().describe(param.description || '');
        }
      }
      
      tools.push({
        name: operation.operationId || `${method}_${path.replace(/\//g, '_')}`,
        description: operation.summary || `${method.toUpperCase()} ${path}`,
        schema: z.object(schemaFields),
        execute: async (params) => {
          // Make actual API call
          const response = await fetch(`${apiUrl}${path}`, {
            method: method.toUpperCase(),
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(params)
          });
          return response.json();
        }
      });
    }
  }
  
  return tools;
}

// Build agent with API-discovered tools
async function buildAPIAgent(apiUrl: string) {
  const tools = await loadToolsFromOpenAPI(apiUrl);
  
  return createPromptBuilder()
    .withIdentity('You are an API integration assistant')
    .withCapabilities([
      'Make API calls',
      'Retrieve data from external services',
      'Integrate with third-party platforms'
    ])
    .withContext(`
      You have access to the following API: ${apiUrl}
      Use the available tools to fulfill user requests.
    `)
    .withTools(tools)
    .withConstraint(
      'must',
      'Always check API response status before reporting success'
    );
}

// Usage
const apiAgent = await buildAPIAgent('https://api.example.com');
console.log(`Loaded ${apiAgent.getSummary().toolsCount} tools from API`);

Context-Aware Tool Selection

Location-Based Tools

import { createPromptBuilder } from 'promptsmith';
import { z } from 'zod';

interface UserLocation {
  country: string;
  region: string;
  timezone: string;
}

function buildLocationAwareAgent(location: UserLocation) {
  const builder = createPromptBuilder()
    .withIdentity('You are a local services assistant')
    .withContext(`
      User location: ${location.country}, ${location.region}
      Timezone: ${location.timezone}
    `);
  
  // US-specific tools
  if (location.country === 'US') {
    builder.withTool({
      name: 'search_zillow',
      description: 'Search real estate listings on Zillow',
      schema: z.object({
        location: z.string(),
        priceRange: z.object({
          min: z.number(),
          max: z.number()
        }).optional()
      })
    });
    
    builder.withTool({
      name: 'get_weather_noaa',
      description: 'Get weather from NOAA (National Weather Service)',
      schema: z.object({
        zipCode: z.string()
      })
    });
  }
  
  // EU-specific tools
  if (['UK', 'DE', 'FR', 'ES', 'IT'].includes(location.country)) {
    builder.withTool({
      name: 'search_rightmove',
      description: 'Search property listings in Europe',
      schema: z.object({
        location: z.string(),
        propertyType: z.enum(['house', 'flat', 'land'])
      })
    });
    
    builder.withTool({
      name: 'get_weather_metoffice',
      description: 'Get weather from Met Office',
      schema: z.object({
        postcode: z.string()
      })
    });
    
    builder.withConstraint(
      'must',
      'All interactions must comply with GDPR'
    );
  }
  
  // Asia-Pacific tools
  if (['AU', 'NZ', 'SG', 'JP'].includes(location.country)) {
    builder.withTool({
      name: 'search_realestate_apac',
      description: 'Search property in Asia-Pacific region',
      schema: z.object({
        city: z.string()
      })
    });
  }
  
  return builder;
}

// Usage
const usAgent = buildLocationAwareAgent({
  country: 'US',
  region: 'California',
  timezone: 'America/Los_Angeles'
});

const ukAgent = buildLocationAwareAgent({
  country: 'UK',
  region: 'London',
  timezone: 'Europe/London'
});

Time-Based Tool Availability

import { createPromptBuilder } from 'promptsmith';
import { z } from 'zod';

interface BusinessHours {
  supportAvailable: boolean;
  salesAvailable: boolean;
  emergencyOnly: boolean;
}

function getBusinessHours(timezone: string): BusinessHours {
  const now = new Date();
  const hour = now.getHours(); // Simplified - should use timezone
  
  const isBusinessHours = hour >= 9 && hour < 17; // 9 AM - 5 PM
  const isAfterHours = hour < 9 || hour >= 17;
  const isWeekend = now.getDay() === 0 || now.getDay() === 6;
  
  return {
    supportAvailable: isBusinessHours && !isWeekend,
    salesAvailable: isBusinessHours && !isWeekend,
    emergencyOnly: isAfterHours || isWeekend
  };
}

function buildTimeAwareAgent(timezone: string) {
  const hours = getBusinessHours(timezone);
  
  const builder = createPromptBuilder()
    .withIdentity('You are a customer service assistant');
  
  // Support tools - available during business hours
  if (hours.supportAvailable) {
    builder
      .withTool({
        name: 'create_support_ticket',
        description: 'Create a support ticket for technical issues',
        schema: z.object({
          title: z.string(),
          description: z.string(),
          priority: z.enum(['low', 'medium', 'high'])
        })
      })
      .withTool({
        name: 'connect_to_agent',
        description: 'Connect user to a live support agent',
        schema: z.object({
          reason: z.string()
        })
      })
      .withConstraint(
        'should',
        'Offer to connect to a live agent for complex issues'
      );
  } else {
    builder
      .withConstraint(
        'must',
        'Inform users that live support is not available and will resume during business hours'
      )
      .withTool({
        name: 'schedule_callback',
        description: 'Schedule a callback during business hours',
        schema: z.object({
          preferredTime: z.string(),
          phoneNumber: z.string()
        })
      });
  }
  
  // Sales tools - available during business hours
  if (hours.salesAvailable) {
    builder.withTool({
      name: 'request_demo',
      description: 'Schedule a product demo with sales team',
      schema: z.object({
        company: z.string(),
        email: z.string(),
        preferredTime: z.string()
      })
    });
  } else {
    builder.withConstraint(
      'should',
      'Collect contact information for sales follow-up during business hours'
    );
  }
  
  // Emergency tools - always available
  builder.withTool({
    name: 'check_system_status',
    description: 'Check current system status and outages',
    schema: z.object({
      service: z.string().optional()
    })
  });
  
  if (hours.emergencyOnly) {
    builder
      .withTool({
        name: 'report_emergency',
        description: 'Report critical system emergency',
        schema: z.object({
          severity: z.enum(['critical', 'major']),
          description: z.string()
        })
      })
      .withContext(`
        Current status: After hours / Weekend
        Live support resumes: Next business day, 9 AM
        Emergency hotline: 1-800-EMERGENCY
      `);
  }
  
  return builder;
}

// Usage - tools change based on time of day
const agent = buildTimeAwareAgent('America/New_York');
console.log(agent.getSummary());

Best Practices

1

Validate tool availability

Always verify that external services are accessible before registering tools:
async function buildAgentWithHealthChecks() {
  const builder = createPromptBuilder()
    .withIdentity('Assistant with verified integrations');
  
  // Check if database is accessible
  const dbAvailable = await checkDatabaseHealth();
  if (dbAvailable) {
    builder.withTool(databaseTool);
  } else {
    builder.withConstraint(
      'must',
      'Inform users that database is temporarily unavailable'
    );
  }
  
  // Check if external API is accessible
  const apiAvailable = await checkAPIHealth('https://api.example.com');
  if (apiAvailable) {
    builder.withTool(apiTool);
  }
  
  return builder;
}

async function checkDatabaseHealth(): Promise<boolean> {
  try {
    // Ping database
    return true;
  } catch {
    return false;
  }
}

async function checkAPIHealth(url: string): Promise<boolean> {
  try {
    const response = await fetch(`${url}/health`, { timeout: 2000 });
    return response.ok;
  } catch {
    return false;
  }
}
2

Cache tool definitions

Cache tool definitions to avoid repeated API calls:
const toolCache = new Map<string, ExecutableToolDefinition[]>();
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes

async function buildAgentWithCache(userId: string) {
  const cacheKey = `tools:${userId}`;
  const cached = toolCache.get(cacheKey);
  
  let tools: ExecutableToolDefinition[];
  
  if (cached && isCacheValid(cacheKey)) {
    tools = cached;
  } else {
    tools = await fetchUserTools(userId);
    toolCache.set(cacheKey, tools);
  }
  
  return createPromptBuilder()
    .withIdentity('Assistant')
    .withTools(tools);
}

function isCacheValid(key: string): boolean {
  // Check cache timestamp
  return true; // Simplified
}
3

Handle tool registration errors

Gracefully handle errors during tool registration:
async function buildRobustAgent(config: any) {
  const builder = createPromptBuilder()
    .withIdentity('Resilient assistant');
  
  // Try to register tools, but don't fail if one fails
  const toolRegistrations = [
    { name: 'tool1', loader: loadTool1 },
    { name: 'tool2', loader: loadTool2 },
    { name: 'tool3', loader: loadTool3 }
  ];
  
  const availableTools: string[] = [];
  const failedTools: string[] = [];
  
  for (const { name, loader } of toolRegistrations) {
    try {
      const tool = await loader(config);
      builder.withTool(tool);
      availableTools.push(name);
    } catch (error) {
      console.error(`Failed to load ${name}:`, error);
      failedTools.push(name);
    }
  }
  
  // Add context about tool availability
  if (failedTools.length > 0) {
    builder.withContext(`
      Note: The following tools are temporarily unavailable: ${failedTools.join(', ')}
      Available tools: ${availableTools.join(', ')}
    `);
  }
  
  return builder;
}
Security: When dynamically loading tools, always validate that the user has permission to access them. Never trust client-provided tool configurations.
Use .getSummary() to inspect the final tool count and verify that dynamic registration worked as expected.

Database-driven tools

Load tools based on user’s enabled integrations stored in your database

API discovery

Dynamically discover and register tools from OpenAPI specifications

Context-aware loading

Register different tools based on user location, timezone, or context

Health-check validation

Verify service availability before registering tools

Build docs developers (and LLMs) love