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.
PromptSmith separates two concerns:
Tool Documentation : Metadata included in the system prompt that teaches the AI when and how to use tools
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.
import { createPromptBuilder } from 'promptsmith-ts/builder' ;
import { z } from 'zod' ;
Create a tool with a name, description, and Zod schema:
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' )
})
});
The tool automatically appears in the system prompt with parameter documentation:
# 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
});
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 );
}
});
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 );
}
});
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 ();
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