Error Handling
Proper error handling is critical for building reliable AI agents. This guide covers validation, error recovery, graceful degradation, and best practices for handling uncertainty and failures.Prompt-Level Error Handling
Using withErrorHandling()
ThewithErrorHandling() method defines how your agent should handle uncertainty and errors:
import { createPromptBuilder } from 'promptsmith';
import { z } from 'zod';
const agent = createPromptBuilder()
.withIdentity('You are a customer support assistant')
.withCapabilities([
'Answer product questions',
'Help with troubleshooting',
'Process returns and refunds'
])
.withErrorHandling(`
When you encounter uncertainty or errors:
1. **Ambiguous Requests**: Ask specific clarifying questions instead of guessing
Example: "Could you clarify whether you want to cancel your order or return it after delivery?"
2. **Missing Information**: Explicitly list what information you need
Example: "To process your refund, I need your order number and the reason for return."
3. **Tool Failures**: Explain the issue in user-friendly terms and suggest alternatives
Example: "I'm unable to access the order system right now. Can you email [email protected]?"
4. **Uncertain Facts**: Acknowledge uncertainty rather than making up information
Example: "I'm not certain about that specific policy. Let me connect you with a specialist."
5. **Out of Scope**: Clearly state your limitations and suggest alternatives
Example: "I can't provide medical advice, but I can help you find customer support for our health products."
`)
.withConstraint(
'must',
'Never make up information when uncertain - acknowledge the limitation'
)
.withConstraint(
'must',
'Always provide actionable next steps when you cannot fulfill a request'
);
const prompt = agent.build();
Comprehensive Error Handling Pattern
import { createPromptBuilder } from 'promptsmith';
import { z } from 'zod';
const robustAgent = createPromptBuilder()
.withIdentity('You are a financial advisory assistant')
.withCapabilities([
'Explain investment concepts',
'Analyze portfolio performance',
'Provide market insights'
])
.withTool({
name: 'get_portfolio',
description: 'Retrieve user portfolio data',
schema: z.object({
userId: z.string().describe('User ID')
}),
execute: async ({ userId }) => {
try {
// Attempt to fetch portfolio
const portfolio = await fetchPortfolio(userId);
return portfolio;
} catch (error: any) {
// Return error info that the AI can use
return {
error: true,
message: error.message,
code: error.code,
retryable: error.code === 'TIMEOUT'
};
}
}
})
.withErrorHandling(`
# Error Handling Guidelines
## Tool Errors
When a tool returns an error:
1. Check if the error object contains 'error: true'
2. Read the error message and explain it in user-friendly language
3. If 'retryable: true', suggest trying again in a moment
4. If not retryable, suggest alternative solutions
Example tool error response:
{
"error": true,
"message": "Database connection timeout",
"code": "TIMEOUT",
"retryable": true
}
Your response should be:
"I'm experiencing a temporary connection issue. Let me try again in a moment. If the problem persists, you can also view your portfolio at app.example.com/portfolio."
## Data Quality Issues
If retrieved data seems incomplete or inconsistent:
- Acknowledge what data you have
- Note what's missing
- Explain limitations in your analysis
- Offer to help with what you can determine
## User Input Errors
If user provides invalid or ambiguous input:
- Don't assume - ask for clarification
- Provide examples of valid formats
- Suggest corrections if you can detect the likely intent
## Uncertainty in Analysis
When analyzing financial data:
- Clearly distinguish facts from interpretations
- Express confidence levels ("likely", "possibly", "uncertain")
- Provide caveats and disclaimers
- Never fabricate data points
`)
.withConstraint(
'must',
'Always include appropriate disclaimers for financial information'
)
.withConstraint(
'must',
'Never make up portfolio values, returns, or market data'
)
.withConstraint(
'should',
'Suggest contacting a human financial advisor for personalized advice'
);
function fetchPortfolio(userId: string) {
// Implementation
return { holdings: [], value: 0 };
}
Runtime Validation
Using validate() Method
import { createPromptBuilder } from 'promptsmith';
import type { ValidationResult } from 'promptsmith';
const builder = createPromptBuilder()
.withIdentity('Assistant')
.withCapability('Answer questions');
// Validate before building
const validationResult = builder.validate();
if (!validationResult.valid) {
console.error('Validation errors:', validationResult.errors);
// Handle validation errors
throw new Error(`Invalid prompt configuration: ${validationResult.errors.join(', ')}`);
}
if (validationResult.warnings.length > 0) {
console.warn('Validation warnings:', validationResult.warnings);
// Log warnings but continue
}
if (validationResult.suggestions.length > 0) {
console.info('Suggestions:', validationResult.suggestions);
}
// Safe to build
const prompt = builder.build();
Custom Validation Rules
import { createPromptBuilder } from 'promptsmith';
function buildValidatedAgent(config: any) {
const builder = createPromptBuilder();
// Apply configuration
if (config.identity) builder.withIdentity(config.identity);
if (config.capabilities) builder.withCapabilities(config.capabilities);
if (config.tools) builder.withTools(config.tools);
// Custom validation
const errors: string[] = [];
const warnings: string[] = [];
// Validate identity is set
if (!builder.hasIdentity()) {
errors.push('Identity is required');
}
// Validate capabilities are present
if (!builder.hasCapabilities()) {
warnings.push('No capabilities defined - agent may not know what it can do');
}
// Validate tools have descriptions
const tools = builder.getTools();
for (const tool of tools) {
if (!tool.description || tool.description.length < 10) {
warnings.push(`Tool '${tool.name}' has insufficient description`);
}
}
// Validate critical constraints for tools
if (builder.hasTools() && !builder.hasConstraints()) {
warnings.push('Tools present but no constraints defined');
}
// Validate guardrails for production
if (process.env.NODE_ENV === 'production' && !builder.hasGuardrails()) {
errors.push('Guardrails are required in production');
}
// Report validation results
if (errors.length > 0) {
throw new Error(`Validation failed: ${errors.join('; ')}`);
}
if (warnings.length > 0) {
console.warn('Validation warnings:', warnings);
}
return builder;
}
// Usage
try {
const agent = buildValidatedAgent({
identity: 'Assistant',
capabilities: ['Help users']
});
console.log('Agent validated successfully');
} catch (error: any) {
console.error('Failed to build agent:', error.message);
}
Graceful Degradation
Fallback Tools and Capabilities
import { createPromptBuilder } from 'promptsmith';
import { z } from 'zod';
interface ServiceHealth {
database: boolean;
externalAPI: boolean;
cache: boolean;
}
async function checkServiceHealth(): Promise<ServiceHealth> {
// Check each service
return {
database: true,
externalAPI: false, // API is down
cache: true
};
}
async function buildDegradedAgent() {
const health = await checkServiceHealth();
const builder = createPromptBuilder()
.withIdentity('You are a customer data assistant')
.withGuardrails();
// Primary tool - requires database
if (health.database) {
builder
.withTool({
name: 'query_customers',
description: 'Query customer database',
schema: z.object({
query: z.string()
}),
execute: async ({ query }) => {
return { customers: [] };
}
})
.withCapability('Search and analyze customer data in real-time');
} else {
// Fallback to cache
builder
.withTool({
name: 'query_cache',
description: 'Query cached customer data (may be slightly outdated)',
schema: z.object({
customerId: z.string()
}),
execute: async ({ customerId }) => {
return { customer: null, cached: true };
}
})
.withCapability('Search cached customer data (limited)')
.withConstraint(
'must',
'Inform users that you are using cached data due to database maintenance'
)
.withContext(`
IMPORTANT: The primary database is currently unavailable.
You are using cached data which may be up to 1 hour old.
Inform users of this limitation.
`);
}
// External API integration
if (health.externalAPI) {
builder.withTool({
name: 'enrich_customer_data',
description: 'Enrich customer data with external information',
schema: z.object({
customerId: z.string()
})
});
} else {
builder
.withConstraint(
'must_not',
'Do not offer data enrichment - the external API is unavailable'
)
.withContext('External data enrichment is temporarily unavailable');
}
// Add overall system status to context
const statusMessage = `
System Status:
- Database: ${health.database ? '✓ Operational' : '✗ Down (using cache)'}
- External API: ${health.externalAPI ? '✓ Operational' : '✗ Down'}
- Cache: ${health.cache ? '✓ Operational' : '✗ Down'}
`;
builder.withContext(statusMessage);
return builder;
}
// Usage
const agent = await buildDegradedAgent();
const prompt = agent.build();
Progressive Enhancement
import { createPromptBuilder } from 'promptsmith';
import { z } from 'zod';
// Start with minimal viable agent
function buildMinimalAgent() {
return createPromptBuilder()
.withIdentity('You are a helpful assistant')
.withCapability('Answer general questions')
.withGuardrails()
.withErrorHandling('When uncertain, acknowledge limitations and ask for clarification');
}
// Progressively enhance with additional capabilities
async function buildEnhancedAgent() {
const builder = buildMinimalAgent();
// Try to add database capability
try {
await testDatabaseConnection();
builder
.withTool({
name: 'query_db',
description: 'Query database',
schema: z.object({ query: z.string() })
})
.withCapability('Query and analyze stored data');
} catch (error) {
console.warn('Database unavailable, skipping database tools');
}
// Try to add external API capability
try {
await testAPIConnection();
builder
.withTool({
name: 'fetch_external',
description: 'Fetch external data',
schema: z.object({ url: z.string() })
})
.withCapability('Integrate with external services');
} catch (error) {
console.warn('External API unavailable, skipping API tools');
}
// Try to add advanced features
try {
const hasAdvancedFeatures = await checkFeatureFlags();
if (hasAdvancedFeatures) {
builder
.withCapability('Perform advanced analytics')
.withCapability('Generate visualizations');
}
} catch (error) {
console.warn('Advanced features unavailable');
}
return builder;
}
async function testDatabaseConnection() {
// Test connection
}
async function testAPIConnection() {
// Test API
}
async function checkFeatureFlags() {
return true;
}
// Usage with error handling
async function initializeAgent() {
try {
const agent = await buildEnhancedAgent();
console.log('Agent built with', agent.getSummary().toolsCount, 'tools');
return agent;
} catch (error) {
console.error('Failed to build enhanced agent, using minimal:', error);
return buildMinimalAgent();
}
}
Error Messages and User Communication
Tool Error Patterns
import { createPromptBuilder } from 'promptsmith';
import { z } from 'zod';
// Define consistent error response format
interface ToolErrorResponse {
success: false;
error: {
code: string;
message: string;
userMessage: string;
retryable: boolean;
retryAfter?: number;
};
}
interface ToolSuccessResponse<T> {
success: true;
data: T;
}
type ToolResponse<T> = ToolSuccessResponse<T> | ToolErrorResponse;
const agent = createPromptBuilder()
.withIdentity('You are a payment processing assistant')
.withTool({
name: 'process_payment',
description: 'Process a payment transaction',
schema: z.object({
amount: z.number().describe('Payment amount'),
currency: z.string().describe('Currency code'),
paymentMethod: z.string().describe('Payment method ID')
}),
execute: async ({ amount, currency, paymentMethod }) => {
try {
// Attempt payment processing
const result = await processPayment(amount, currency, paymentMethod);
return {
success: true,
data: result
} as ToolSuccessResponse<any>;
} catch (error: any) {
// Return structured error
return {
success: false,
error: {
code: error.code || 'UNKNOWN_ERROR',
message: error.message,
userMessage: getUserFriendlyMessage(error),
retryable: isRetryable(error),
retryAfter: error.retryAfter
}
} as ToolErrorResponse;
}
}
})
.withErrorHandling(`
# Tool Response Handling
All tools return responses in this format:
Success:
{
"success": true,
"data": { ... }
}
Error:
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Technical error message",
"userMessage": "User-friendly explanation",
"retryable": true/false,
"retryAfter": 60 (optional, seconds to wait)
}
}
## Error Handling Steps:
1. Check if response.success is false
2. Use response.error.userMessage to explain the issue to the user
3. If response.error.retryable is true:
- Suggest trying again
- If retryAfter is present, tell user how long to wait
4. If response.error.retryable is false:
- Don't suggest retrying
- Provide alternative solutions
## Common Error Codes:
- INSUFFICIENT_FUNDS: "Your payment method has insufficient funds. Please use a different payment method."
- CARD_DECLINED: "Your card was declined. Please contact your bank or try a different card."
- NETWORK_ERROR: "We're experiencing network issues. Please try again in a moment."
- INVALID_AMOUNT: "The payment amount is invalid. Please verify the amount and try again."
- RATE_LIMIT: "Too many requests. Please wait {retryAfter} seconds before trying again."
`)
.withConstraint(
'must',
'Always use the userMessage field to explain errors - never expose technical error details'
)
.withConstraint(
'must',
'For retryable errors, clearly indicate that the user can try again'
)
.withConstraint(
'must',
'For non-retryable errors, provide actionable alternative solutions'
);
function processPayment(amount: number, currency: string, method: string) {
// Implementation
return { transactionId: 'txn_123' };
}
function getUserFriendlyMessage(error: any): string {
const messages: Record<string, string> = {
INSUFFICIENT_FUNDS: 'Your payment method has insufficient funds.',
CARD_DECLINED: 'Your card was declined by your bank.',
NETWORK_ERROR: "We're experiencing network issues.",
INVALID_AMOUNT: 'The payment amount is invalid.',
RATE_LIMIT: "You've made too many requests."
};
return messages[error.code] || 'An unexpected error occurred.';
}
function isRetryable(error: any): boolean {
const retryableCodes = ['NETWORK_ERROR', 'TIMEOUT', 'RATE_LIMIT', 'SERVER_ERROR'];
return retryableCodes.includes(error.code);
}
Best Practices
Define error handling early
Use
withErrorHandling() to establish clear error handling guidelines upfrontValidate before building
Always call
.validate() to catch configuration errors before deploymentUse structured errors
Return consistent error objects from tools with user-friendly messages
Graceful degradation
Build agents that degrade gracefully when services are unavailable
Complete Example: Production-Ready Error Handling
import { createPromptBuilder } from 'promptsmith';
import { z } from 'zod';
async function buildProductionAgent() {
// Check service health
const health = await checkAllServices();
// Start with base configuration
const builder = createPromptBuilder()
.withIdentity('You are a production customer service assistant')
.withGuardrails()
.withErrorHandling(`
# Production Error Handling
## Critical Rules:
1. NEVER make up information when uncertain
2. ALWAYS acknowledge errors and limitations
3. ALWAYS provide actionable next steps
4. Use user-friendly language, not technical jargon
## Tool Errors:
- Check response.success field
- Use response.error.userMessage for user communication
- Follow retry guidance from response.error.retryable
## Missing Information:
- List exactly what information is needed
- Provide examples of valid formats
- Don't proceed without required data
## Service Outages:
- Apologize for inconvenience
- Provide estimated resolution time if known
- Offer alternative solutions (email, phone, etc.)
`)
.withConstraint(
'must',
'Log all errors internally for monitoring and debugging'
);
// Add tools with health checks
if (health.database) {
builder.withTool({
name: 'lookup_order',
description: 'Look up order details',
schema: z.object({
orderId: z.string()
}),
execute: async ({ orderId }) => {
try {
const order = await fetchOrder(orderId);
return { success: true, data: order };
} catch (error: any) {
return {
success: false,
error: {
code: error.code,
message: error.message,
userMessage: 'Unable to retrieve order details.',
retryable: true
}
};
}
}
});
} else {
builder.withContext('Database is currently unavailable. Use alternative support channels.');
}
// Validate before returning
const validation = builder.validate();
if (!validation.valid) {
throw new Error(`Agent validation failed: ${validation.errors.join(', ')}`);
}
if (validation.warnings.length > 0) {
console.warn('Agent warnings:', validation.warnings);
}
return builder;
}
async function checkAllServices() {
return { database: true, api: true };
}
async function fetchOrder(orderId: string) {
return { id: orderId, status: 'shipped' };
}
// Usage with comprehensive error handling
async function main() {
try {
const agent = await buildProductionAgent();
const prompt = agent.build();
console.log('✓ Agent built successfully');
return prompt;
} catch (error: any) {
console.error('✗ Failed to build agent:', error.message);
// Alert monitoring system
// Return fallback minimal agent
return createPromptBuilder()
.withIdentity('Minimal fallback assistant')
.withErrorHandling('Direct users to [email protected] for assistance')
.build();
}
}
Never expose sensitive error details to users. Always use user-friendly messages that don’t reveal system internals, database schemas, or security mechanisms.