Skip to main content

Prompt Composition

PromptSmith enables powerful composition patterns through .extend() and .merge(), allowing you to create reusable prompt components and specialized variants without duplication.

Understanding Composition

Composition solves two key problems:
  1. Code Reuse: Define common patterns once and reuse across multiple agents
  2. Specialization: Create variants of base prompts without modifying the original
Both .extend() and .merge() create new builder instances - the original remains unchanged.

Extension: Creating Variants

The .extend() method creates a copy of a builder that you can then specialize:
1
Create a Base Prompt
2
import { createPromptBuilder } from 'promptsmith-ts/builder';

const baseSupport = createPromptBuilder()
  .withIdentity('You are a customer support assistant')
  .withCapabilities([
    'Answer product questions',
    'Help with account issues',
    'Provide general assistance'
  ])
  .withGuardrails()
  .withTone('Professional and friendly');
3
Extend for Specialization
4
// Create specialized variant for technical support
const technicalSupport = baseSupport.extend()
  .withIdentity('You are a technical support specialist') // Overrides base identity
  .withCapabilities([
    'Debug technical issues',
    'Explain complex technical concepts',
    'Guide through troubleshooting steps'
  ]) // Adds to existing capabilities
  .withContext(`
Technical Knowledge Base:
- Product: CloudSync Pro
- Tech Stack: React, Node.js, PostgreSQL
- Common issues: sync failures, authentication errors, database timeouts
  `);

// Original remains unchanged
const basePrompt = baseSupport.build();
const techPrompt = technicalSupport.build();
5
Create Multiple Variants
6
// Billing support variant
const billingSupport = baseSupport.extend()
  .withIdentity('You are a billing support specialist')
  .withCapabilities([
    'Explain billing statements',
    'Process refund requests',
    'Update payment information'
  ])
  .withContext('Payment processor: Stripe. Billing cycle: monthly on the 1st.');

// Returns specialist variant
const returnsSupport = baseSupport.extend()
  .withIdentity('You are a returns and exchanges specialist')
  .withCapabilities([
    'Process return requests',
    'Arrange exchanges',
    'Issue refunds'
  ])
  .withContext('Return window: 30 days. Free return shipping on all orders.');

Merge: Combining Behaviors

The .merge() method combines two builders, allowing you to compose reusable components:

Merge Rules

When merging builder A into builder B:
PropertyBehavior
IdentityB’s identity kept (A’s ignored)
CapabilitiesCombined, duplicates removed
ToolsCombined (error if duplicate names)
ConstraintsAll combined
ExamplesAll combined
ContextA’s context appended to B’s
Tone/Output/ErrorB’s values kept if set, otherwise A’s
GuardrailsEnabled if either has it
Forbidden TopicsCombined, duplicates removed

Basic Merge Example

import { createPromptBuilder } from 'promptsmith-ts/builder';
import { security } from 'promptsmith-ts/templates';

// Domain-specific prompt
const ecommerce = createPromptBuilder()
  .withIdentity('You are an e-commerce assistant')
  .withCapabilities(['Process orders', 'Track shipments']);

// Merge security template
const secureEcommerce = ecommerce.merge(security());

// Now has:
// - E-commerce identity (from ecommerce)
// - E-commerce capabilities (from ecommerce)
// - Security guardrails (from security template)
// - Security constraints (from security template)
// - Security forbidden topics (from security template)

Creating Reusable Templates

Build a library of composable templates:
// templates/security.ts
export function securityTemplate() {
  return createPromptBuilder()
    .withGuardrails()
    .withConstraint('must', 'Always verify user identity before sharing sensitive data')
    .withConstraint('must_not', 'Never log or store personally identifiable information')
    .withForbiddenTopics(['Internal system details', 'Other users\' data']);
}

// templates/professional-tone.ts
export function professionalTone() {
  return createPromptBuilder()
    .withTone('Maintain a professional, courteous tone. Be clear and concise.')
    .withConstraint('should', 'Use formal language and avoid slang')
    .withConstraint('should', 'Address users respectfully');
}

// templates/error-handling.ts
export function robustErrorHandling() {
  return createPromptBuilder()
    .withErrorHandling(`
Error Handling:
- When uncertain, ask clarifying questions
- For errors, explain issue in user-friendly terms
- Always suggest next steps or alternatives
- Acknowledge user frustration empathetically
    `.trim())
    .withConstraint('must', 'Never leave users without a path forward');
}

// templates/multilingual.ts
export function multilingualSupport() {
  return createPromptBuilder()
    .withCapability('Communicate in multiple languages')
    .withCapability('Detect user language and respond accordingly')
    .withConstraint('should', 'Match user\'s language preference automatically');
}

Composing Templates

import { createPromptBuilder } from 'promptsmith-ts/builder';
import {
  securityTemplate,
  professionalTone,
  robustErrorHandling,
  multilingualSupport
} from './templates';

const customerService = createPromptBuilder()
  .withIdentity('You are a global customer service assistant')
  .withCapabilities([
    'Answer product questions',
    'Process orders and returns',
    'Provide technical support'
  ])
  .merge(securityTemplate())       // Add security
  .merge(professionalTone())        // Add professional communication
  .merge(robustErrorHandling())     // Add error handling
  .merge(multilingualSupport());    // Add language support

// Final prompt has all capabilities from all templates
const prompt = customerService.build();

Tool Composition

Share tools across multiple agents:
import { z } from 'zod';

// Reusable tool collections
function databaseTools() {
  return createPromptBuilder()
    .withTool({
      name: 'query_database',
      description: 'Query the database',
      schema: z.object({ query: z.string() }),
      execute: async ({ query }) => await db.execute(query)
    })
    .withTool({
      name: 'get_record',
      description: 'Get a record by ID',
      schema: z.object({ id: z.string() }),
      execute: async ({ id }) => await db.findById(id)
    });
}

function emailTools() {
  return createPromptBuilder()
    .withTool({
      name: 'send_email',
      description: 'Send an email',
      schema: z.object({
        to: z.string().email(),
        subject: z.string(),
        body: z.string()
      }),
      execute: async (params) => await emailService.send(params)
    });
}

// Compose tools into different agents
const dataAnalyst = createPromptBuilder()
  .withIdentity('You are a data analyst')
  .merge(databaseTools()); // Gets database tools

const notificationBot = createPromptBuilder()
  .withIdentity('You are a notification bot')
  .merge(emailTools());    // Gets email tools

const adminAssistant = createPromptBuilder()
  .withIdentity('You are an admin assistant')
  .merge(databaseTools())  // Gets both
  .merge(emailTools());    // Gets both
Duplicate Tool Names: Merging fails if both builders have tools with the same name:
const builder1 = createPromptBuilder()
  .withTool({ name: 'search', description: 'Search users', schema: z.object({}) });

const builder2 = createPromptBuilder()
  .withTool({ name: 'search', description: 'Search products', schema: z.object({}) });

// ❌ Throws error: duplicate tool name "search"
const merged = builder1.merge(builder2);

// ✅ Use unique names
const builder2 = createPromptBuilder()
  .withTool({ name: 'search_products', description: 'Search products', schema: z.object({}) });

const merged = builder1.merge(builder2); // Works!

Organizational Patterns

Pattern 1: Base + Specializations

Create a base prompt and multiple specialized variants:
// Base assistant
const baseAssistant = createPromptBuilder()
  .withGuardrails()
  .withTone('Helpful and professional')
  .withErrorHandling('Ask clarifying questions when uncertain');

// Specialized variants
const salesAssistant = baseAssistant.extend()
  .withIdentity('You are a sales assistant')
  .withCapabilities(['Recommend products', 'Explain pricing']);

const supportAssistant = baseAssistant.extend()
  .withIdentity('You are a support assistant')
  .withCapabilities(['Troubleshoot issues', 'Process returns']);

const techAssistant = baseAssistant.extend()
  .withIdentity('You are a technical assistant')
  .withCapabilities(['Debug problems', 'Explain technical concepts']);

Pattern 2: Feature Composition

Build agents by composing features:
// Feature modules
const withAnalytics = () => createPromptBuilder()
  .withTool({ name: 'track_event', /* ... */ });

const withReporting = () => createPromptBuilder()
  .withTool({ name: 'generate_report', /* ... */ });

const withNotifications = () => createPromptBuilder()
  .withTool({ name: 'send_notification', /* ... */ });

// Compose features based on tier
function createAgent(tier: 'basic' | 'pro' | 'enterprise') {
  let agent = createPromptBuilder()
    .withIdentity('You are a business assistant');
  
  if (tier === 'pro' || tier === 'enterprise') {
    agent = agent.merge(withAnalytics());
  }
  
  if (tier === 'enterprise') {
    agent = agent
      .merge(withReporting())
      .merge(withNotifications());
  }
  
  return agent;
}

const basicAgent = createAgent('basic');
const proAgent = createAgent('pro');
const enterpriseAgent = createAgent('enterprise');

Pattern 3: Environment-Based Composition

function createProductionAgent() {
  const base = createPromptBuilder()
    .withIdentity('Production assistant')
    .withCapabilities(['Handle requests']);
  
  return base
    .merge(securityTemplate())
    .merge(robustErrorHandling())
    .withConstraint('must', 'Log all security events')
    .withFormat('toon'); // Optimize for cost
}

function createDevelopmentAgent() {
  const base = createPromptBuilder()
    .withIdentity('Development assistant')
    .withCapabilities(['Handle requests']);
  
  return base
    .withConstraint('should', 'Provide verbose debug information')
    .withFormat('markdown'); // Readability over cost
}

const agent = process.env.NODE_ENV === 'production'
  ? createProductionAgent()
  : createDevelopmentAgent();

Context Merging

When merging, contexts are combined:
const businessContext = createPromptBuilder()
  .withContext(`
Company: TechCorp
Industry: SaaS
Customers: B2B enterprises
  `);

const productContext = createPromptBuilder()
  .withContext(`
Product: CloudSync Pro
Features: Real-time sync, collaboration, security
  `);

const agent = createPromptBuilder()
  .withIdentity('You are a sales engineer')
  .merge(businessContext)
  .merge(productContext);

// Context in final prompt:
// Company: TechCorp
// Industry: SaaS
// Customers: B2B enterprises
//
// Product: CloudSync Pro
// Features: Real-time sync, collaboration, security

Best Practices

  1. Single Responsibility: Each template should focus on one concern (security, tone, tools, etc.)
  2. Descriptive Names: Name templates clearly: securityTemplate(), professionalTone()
  3. Document Intent: Add comments explaining what each template provides
  4. Avoid Deep Merging: Keep composition shallow (2-3 levels) for maintainability
  5. Test Composed Prompts: Use PromptTester to validate composed behaviors
  6. Version Templates: Treat templates as versioned components
  7. Unique Tool Names: Ensure tools have unique names when merging

Common Patterns

Security Layer

const agent = createMyAgent()
  .merge(security()); // Always last for override protection

A/B Testing Variants

const variantA = baseAgent.extend()
  .withTone('Casual and friendly');

const variantB = baseAgent.extend()
  .withTone('Professional and formal');

// Test which performs better

User Tier-Based Features

function createUserAgent(user: User) {
  const base = createPromptBuilder()
    .withIdentity('Assistant');
  
  if (user.isPremium) {
    return base.merge(premiumFeatures());
  }
  
  return base.merge(basicFeatures());
}

Next Steps

Token Optimization

Optimize composed prompts with TOON format

Testing

Test composed prompts with PromptTester

Security

Learn about the security template

Examples

See complete composition examples

Build docs developers (and LLMs) love