Skip to main content

Tool Integration

PromptSmith allows you to register tools that AI agents can invoke during conversations. Tools are defined with Zod schemas for full type safety and automatic parameter documentation.

Understanding Tool Architecture

PromptSmith separates two concerns:
  1. Tool Documentation: Metadata included in the system prompt that teaches the AI when and how to use tools
  2. Tool Execution: Optional runtime logic that executes when the AI invokes a tool
You can register tools for documentation-only purposes (the AI learns about them) or with execution logic for full end-to-end integration.

Basic Tool Definition

1
Import Dependencies
2
import { createPromptBuilder } from 'promptsmith-ts/builder';
import { z } from 'zod';
3
Define a Tool
4
Create a tool with a name, description, and Zod schema:
5
const builder = createPromptBuilder()
  .withIdentity('You are a weather assistant')
  .withTool({
    name: 'get_weather',
    description: 'Retrieves current weather for a location. Use when user asks about weather conditions.',
    schema: z.object({
      location: z.string().describe('City name or ZIP code'),
      units: z.enum(['celsius', 'fahrenheit']).optional().describe('Temperature units')
    })
  });
6
Generated Documentation
7
The tool automatically appears in the system prompt with parameter documentation:
8
# Available Tools

## get_weather
Retrieves current weather for a location. Use when user asks about weather conditions.

**Parameters:**
- `location` (string, required): City name or ZIP code
- `units` (enum, optional): Temperature units

Adding Execution Logic

For full integration with AI SDK, add an execute function:
import { createPromptBuilder } from 'promptsmith-ts/builder';
import { z } from 'zod';

const weatherTool = {
  name: 'get_weather',
  description: 'Get current weather for a location',
  schema: z.object({
    location: z.string().describe('City name'),
    units: z.enum(['celsius', 'fahrenheit']).default('celsius')
  }),
  execute: async ({ location, units }) => {
    // ✅ Full type safety: TypeScript infers parameter types from schema
    const response = await fetch(
      `https://api.weather.com/v3/weather?location=${location}&units=${units}`,
      { headers: { 'API-Key': process.env.WEATHER_API_KEY } }
    );
    return response.json();
  }
};

const builder = createPromptBuilder()
  .withIdentity('You are a weather assistant')
  .withTool(weatherTool);
The execute function receives arguments typed according to your Zod schema. TypeScript automatically infers the correct types!

Using with Vercel AI SDK

Export tools in AI SDK format for seamless integration:
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { createPromptBuilder } from 'promptsmith-ts/builder';
import { z } from 'zod';

const builder = createPromptBuilder()
  .withIdentity('You are a helpful research assistant')
  .withCapabilities([
    'Search academic papers',
    'Summarize research findings',
    'Provide citations'
  ])
  .withTool({
    name: 'search_papers',
    description: 'Search for academic papers by keyword or topic',
    schema: z.object({
      query: z.string().describe('Search query'),
      year_from: z.number().optional().describe('Earliest publication year'),
      limit: z.number().default(10).describe('Maximum results')
    }),
    execute: async ({ query, year_from, limit }) => {
      const response = await fetch('https://api.semanticscholar.org/search', {
        method: 'POST',
        body: JSON.stringify({ query, yearFrom: year_from, limit })
      });
      return response.json();
    }
  });

// Use spread operator for clean integration
const response = await generateText({
  model: openai('gpt-4'),
  ...builder.toAiSdk(), // Includes both system prompt and tools
  prompt: 'Find recent papers on large language models',
  maxSteps: 5
});

Multiple Tools

Add multiple tools to create sophisticated agents:
const builder = createPromptBuilder()
  .withIdentity('You are a data analysis assistant')
  .withTool({
    name: 'query_database',
    description: 'Execute SQL queries against the analytics database',
    schema: z.object({
      query: z.string().describe('SQL SELECT query'),
      limit: z.number().max(1000).default(100)
    }),
    execute: async ({ query, limit }) => {
      return await db.execute(query + ` LIMIT ${limit}`);
    }
  })
  .withTool({
    name: 'generate_chart',
    description: 'Create a visualization from data',
    schema: z.object({
      data: z.array(z.record(z.any())).describe('Array of data points'),
      chart_type: z.enum(['bar', 'line', 'pie', 'scatter']),
      title: z.string().describe('Chart title')
    }),
    execute: async ({ data, chart_type, title }) => {
      return await chartService.create({ data, type: chart_type, title });
    }
  })
  .withTool({
    name: 'export_report',
    description: 'Export analysis as PDF or Excel',
    schema: z.object({
      format: z.enum(['pdf', 'xlsx']),
      content: z.string().describe('Report content in markdown'),
      filename: z.string()
    }),
    execute: async ({ format, content, filename }) => {
      if (format === 'pdf') {
        return await pdfGenerator.create(content, filename);
      }
      return await excelGenerator.create(content, filename);
    }
  });

Conditional Tools

Add tools based on runtime conditions:
const hasDatabase = config.databaseEnabled;
const hasEmailService = config.emailApiKey !== null;
const isPremium = user.subscriptionTier === 'premium';

const builder = createPromptBuilder()
  .withIdentity('You are a business automation assistant')
  .withToolIf(hasDatabase, {
    name: 'query_customers',
    description: 'Search customer database',
    schema: z.object({ query: z.string() }),
    execute: async ({ query }) => await db.customers.search(query)
  })
  .withToolIf(hasEmailService, {
    name: 'send_email',
    description: 'Send email to customer',
    schema: z.object({
      to: z.string().email(),
      subject: z.string(),
      body: z.string()
    }),
    execute: async ({ to, subject, body }) => {
      return await emailService.send({ to, subject, body });
    }
  })
  .withToolIf(isPremium, {
    name: 'advanced_analytics',
    description: 'Run advanced ML-powered analytics (Premium only)',
    schema: z.object({ dataset: z.string() }),
    execute: async ({ dataset }) => await mlService.analyze(dataset)
  });

Complex Schemas

Use Zod’s full power for sophisticated parameter validation:
const builder = createPromptBuilder()
  .withIdentity('You are a calendar scheduling assistant')
  .withTool({
    name: 'create_meeting',
    description: 'Schedule a new meeting with participants',
    schema: z.object({
      title: z.string().min(1).max(100),
      date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/)
        .describe('Date in YYYY-MM-DD format'),
      start_time: z.string().regex(/^\d{2}:\d{2}$/)
        .describe('Start time in HH:MM format'),
      duration_minutes: z.number().min(15).max(480)
        .describe('Meeting duration (15-480 minutes)'),
      attendees: z.array(
        z.object({
          email: z.string().email(),
          required: z.boolean().default(true)
        })
      ).min(1).describe('List of meeting participants'),
      location: z.union([
        z.object({
          type: z.literal('physical'),
          address: z.string()
        }),
        z.object({
          type: z.literal('virtual'),
          meeting_url: z.string().url()
        })
      ]).optional(),
      recurrence: z.object({
        frequency: z.enum(['daily', 'weekly', 'monthly']),
        interval: z.number().min(1),
        end_date: z.string().optional()
      }).optional()
    }),
    execute: async (params) => {
      // Full type inference for complex nested structure
      return await calendar.createMeeting(params);
    }
  });

Mastra Tool Compatibility

PromptSmith automatically detects and converts Mastra tools:
import { createTool } from '@mastra/core/tools';
import { createPromptBuilder } from 'promptsmith-ts/builder';
import { z } from 'zod';

// Create tool with Mastra
const mastraTool = createTool({
  id: 'weather-tool',
  description: 'Get current weather',
  inputSchema: z.object({
    location: z.string()
  }),
  execute: async ({ context }) => {
    return await fetchWeather(context.location);
  }
});

// Use directly with PromptSmith - automatically converted!
const builder = createPromptBuilder()
  .withIdentity('Weather assistant')
  .withTool(mastraTool); // ✅ Auto-detected as Mastra tool

// Export back to Mastra format if needed
const { instructions, tools } = builder.toMastra();

Accessing Registered Tools

Inspect or export tools programmatically:
const builder = createPromptBuilder()
  .withTool({ name: 'tool1', description: 'First', schema: z.object({}) })
  .withTool({ name: 'tool2', description: 'Second', schema: z.object({}), execute: async () => {} });

// Check if tools are registered
if (builder.hasTools()) {
  const tools = builder.getTools();
  console.log(`Registered ${tools.length} tools`);
  
  tools.forEach(tool => {
    console.log(`- ${tool.name}: ${tool.description}`);
  });
}

// Export for AI SDK
const aiSdkTools = builder.toAiSdkTools();
// { tool1: { description, parameters, execute }, tool2: { ... } }

Best Practices

1. Write Clear Descriptions

Descriptions guide the AI on when to use tools:
// ❌ Vague
.withTool({
  name: 'search',
  description: 'Search for things',
  schema: z.object({ query: z.string() })
})

// ✅ Specific
.withTool({
  name: 'search_products',
  description: 'Search the product catalog by name, category, or SKU. Use when customer asks about product availability or specifications.',
  schema: z.object({
    query: z.string().describe('Product name, category, or SKU')
  })
})

2. Use .describe() on Schema Fields

Descriptions appear in the generated prompt:
// ❌ No descriptions
schema: z.object({
  query: z.string(),
  limit: z.number()
})

// ✅ Documented parameters
schema: z.object({
  query: z.string().describe('Search keywords or phrase'),
  limit: z.number().describe('Maximum number of results (1-100)')
})

3. Provide Sensible Defaults

Make tools easier to use:
schema: z.object({
  query: z.string().describe('Search query'),
  limit: z.number().default(10).describe('Results limit'),
  sort_by: z.enum(['relevance', 'date', 'popularity'])
    .default('relevance')
    .describe('Sort order')
})

4. Handle Errors Gracefully

execute: async ({ query }) => {
  try {
    const results = await searchService.search(query);
    return results;
  } catch (error) {
    return {
      error: true,
      message: 'Search service temporarily unavailable',
      suggestion: 'Please try again in a moment'
    };
  }
}

5. Add Usage Examples

Show the AI how to use tools properly:
const builder = createPromptBuilder()
  .withIdentity('You are a travel booking assistant')
  .withTool({
    name: 'search_flights',
    description: 'Search for flights',
    schema: z.object({
      from: z.string(),
      to: z.string(),
      date: z.string()
    })
  })
  .withExamples([
    {
      user: 'Find me flights from NYC to London on March 15',
      assistant: 'I\'ll search for flights from NYC to London on March 15. *calls search_flights with from: "NYC", to: "London", date: "2024-03-15"*',
      explanation: 'Demonstrates proper tool usage with user queries'
    }
  ]);

Common Pitfalls

Duplicate Tool Names: Each tool must have a unique name.
// ❌ Will throw error
builder
  .withTool({ name: 'search', description: 'Search products', schema: z.object({}) })
  .withTool({ name: 'search', description: 'Search users', schema: z.object({}) });

// ✅ Unique names
builder
  .withTool({ name: 'search_products', description: 'Search products', schema: z.object({}) })
  .withTool({ name: 'search_users', description: 'Search users', schema: z.object({}) });
Missing Descriptions on Schema Fields: Without .describe(), the AI doesn’t understand parameters:
// ❌ No context for AI
schema: z.object({ q: z.string() })

// ✅ Clear documentation
schema: z.object({
  q: z.string().describe('Search query keywords')
})

Next Steps

Security Guardrails

Protect tools from misuse with security constraints

Testing Tools

Validate tool invocation with PromptTester

Composition

Share tools across multiple agents

Examples

See complete tool integration examples

Build docs developers (and LLMs) love