Overview
The SystemPromptBuilder class is the core of PromptSmith. It implements a fluent API pattern that lets you incrementally build comprehensive system prompts by chaining method calls. Think of it as a recipe builder for AI agent behavior - you add ingredients (identity, capabilities, tools, constraints) and the builder generates a complete system prompt optimized for AI models.
Design Philosophy : The builder separates prompt generation (text for the AI model) from tool execution (runtime logic). Tools are registered with metadata only - actual execution happens in your application when the AI requests to use a tool.
Why Use a Builder?
Manually writing system prompts leads to several problems:
Inconsistent structure : Missing sections, poor organization
Token waste : Verbose formatting, redundant text
No validation : Duplicate tool names, conflicting constraints
Hard to maintain : Changes require editing long text blocks
No reusability : Can’t compose or extend prompts programmatically
The builder solves these by:
Enforcing structure : Sections only appear when you add content
Optimizing tokens : Choice of formats (markdown, TOON, compact)
Validating config : Catches duplicate tools, missing sections, conflicts
Enabling composition : Merge, extend, and conditional logic
Type safety : Full TypeScript inference for tools and parameters
Core Workflow
Create Builder
Initialize a new builder instance using the factory function: import { createPromptBuilder } from 'promptsmith' ;
const builder = createPromptBuilder ();
Configure Agent
Chain method calls to define your agent’s behavior: builder
. withIdentity ( "You are a helpful coding assistant" )
. withCapabilities ([
"Explain code concepts" ,
"Write code examples" ,
"Debug issues"
])
. withConstraint ( "must" , "Always provide working code examples" );
Build Prompt
Generate the final system prompt string: const prompt = builder . build ();
// Use with OpenAI, Anthropic, or any LLM API
Method Chaining Pattern
Every builder method returns this, enabling fluent chaining:
const prompt = createPromptBuilder ()
. withIdentity ( "Expert travel assistant" )
. withCapabilities ([
"Search for flights and hotels" ,
"Provide destination recommendations" ,
"Handle booking modifications"
])
. withTool ({
name: "search_flights" ,
description: "Search available flights" ,
schema: z . object ({
origin: z . string (). describe ( "Departure airport code" ),
destination: z . string (). describe ( "Arrival airport code" ),
date: z . string (). describe ( "Departure date (YYYY-MM-DD)" )
})
})
. withConstraint ( "must" , "Always verify travel dates before searching" )
. withConstraint ( "must_not" , "Never proceed with bookings without explicit confirmation" )
. withGuardrails ()
. build ();
See builder.ts:75-2472 for the complete implementation.
Builder State & Caching
Internal State
The builder maintains private state for all configuration:
private _identity = "" ;
private readonly _capabilities : string [] = [];
private readonly _tools : ExecutableToolDefinition [] = [];
private readonly _constraints : Constraint [] = [];
private _format : PromptFormat = "markdown" ;
// ... and more
This state is mutable - calling methods modifies the builder instance. If you need immutability, use .extend() to create a copy first.
Automatic Caching
The builder caches generated prompts to avoid redundant work:
First Build (Generates)
Cache Invalidation
Format-Specific Caching
const prompt1 = builder . build (); // Generates prompt
const prompt2 = builder . build (); // Returns cached prompt (instant)
See cache.ts:1-90 for the caching implementation.
Composition & Reusability
Extending Builders
Create variations without modifying the original:
// Base support assistant
const baseSupport = createPromptBuilder ()
. withIdentity ( "You are a customer support assistant" )
. withCapabilities ([ "Answer questions" , "Resolve issues" ])
. withGuardrails ()
. withTone ( "Professional and empathetic" );
// Extend for technical support
const techSupport = baseSupport . extend ()
. withIdentity ( "You are a technical support specialist" )
. withCapabilities ([
"Debug technical issues" ,
"Explain technical concepts"
])
. withContext ( "Product: SaaS Platform, Tech: React + Node.js" );
// Original remains unchanged
console . log ( baseSupport . hasCapabilities ()); // Still has original capabilities
See builder.ts:961-979 for the .extend() implementation.
Merging Builders
Combine reusable patterns:
// Reusable security pattern
const securityBuilder = createPromptBuilder ()
. withGuardrails ()
. withConstraint ( "must" , "Always verify user identity before sharing data" )
. withConstraint ( "must_not" , "Never log or store personal information" )
. withForbiddenTopics ([ "Internal system details" , "Other users' data" ]);
// Domain-specific builder
const customerService = createPromptBuilder ()
. withIdentity ( "Customer service assistant" )
. withCapabilities ([ "Process returns" , "Track orders" ])
. withTone ( "Empathetic and solution-oriented" );
// Merge security into customer service
const secureCustomerService = customerService . merge ( securityBuilder );
// Now has both customer service features AND security constraints
Merge Conflicts : Merging throws an error if both builders have tools with the same name. This prevents accidental overwrites.
See builder.ts:1020-1076 for merge rules and implementation.
Conditional Configuration
Use conditional methods for environment-specific behavior:
const isProd = process . env . NODE_ENV === 'production' ;
const hasAuth = config . authEnabled ;
const builder = createPromptBuilder ()
. withIdentity ( "Customer assistant" )
. withToolIf ( hasAuth , {
name: "access_user_data" ,
description: "Access authenticated user data" ,
schema: z . object ({ userId: z . string () })
})
. withConstraintIf ( isProd , "must" , "Log all security events" )
. withConstraintIf ( ! isProd , "should" , "Provide verbose debug information" );
See builder.ts:399-405 and builder.ts:506-516 for conditional methods.
Introspection & Debugging
Query Builder State
Check what’s configured without building:
Boolean Checks
Get Collections
Summary
builder . hasIdentity (); // true/false
builder . hasTools (); // true/false
builder . hasConstraints (); // true/false
builder . hasGuardrails (); // true/false
builder . hasForbiddenTopics (); // true/false
builder . hasExamples (); // true/false
builder . hasCapabilities (); // true/false
const tools = builder . getTools ();
const musts = builder . getConstraintsByType ( 'must' );
const mustNots = builder . getConstraintsByType ( 'must_not' );
const shoulds = builder . getConstraintsByType ( 'should' );
const shouldNots = builder . getConstraintsByType ( 'should_not' );
const summary = builder . getSummary ();
console . log ( summary );
// {
// hasIdentity: true,
// capabilitiesCount: 3,
// toolsCount: 2,
// constraintsCount: 5,
// constraintsByType: { must: 2, must_not: 1, should: 2, should_not: 0 },
// examplesCount: 1,
// hasGuardrails: true,
// forbiddenTopicsCount: 0,
// hasContext: true,
// hasTone: true,
// hasOutputFormat: false,
// hasErrorHandling: true,
// format: 'markdown'
// }
See builder.ts:1098-1344 for introspection methods.
Debug Output
Get comprehensive diagnostics:
Outputs detailed information including:
Configuration summary with counts
Section previews
Validation warnings (missing sections, potential issues)
Suggestions for improvement
Prompt size estimate (~tokens)
Token savings comparison (markdown vs TOON)
See builder.ts:1379-1491 for debug implementation.
Validation
Catch configuration issues before building:
const result = builder . validate ();
if ( ! result . valid ) {
console . error ( 'Validation errors:' , result . errors );
// Example error:
// { severity: 'error', code: 'DUPLICATE_TOOL', message: 'Duplicate tool name: "search"' }
}
if ( result . warnings . length > 0 ) {
console . warn ( 'Warnings:' , result . warnings );
// Example warning:
// { severity: 'warning', code: 'MISSING_IDENTITY', message: 'No identity set' }
}
if ( result . info . length > 0 ) {
console . info ( 'Suggestions:' , result . info );
// Example info:
// { severity: 'info', code: 'TOOLS_WITHOUT_EXAMPLES',
// message: 'Tools defined without usage examples' }
}
Duplicate tool names (error)
Missing identity (warning)
Empty capabilities (warning)
Empty constraints (warning)
Tools without examples (info)
Tools without guardrails (info)
No ‘must’ constraints (info)
Conflicting constraints (warning)
See validation.ts:61-284 for validation logic.
AI SDK Integration
Export for Vercel AI SDK:
Spread Pattern (Recommended)
Destructured Pattern
Tools Only
import { generateText } from 'ai' ;
import { openai } from '@ai-sdk/openai' ;
const response = await generateText ({
model: openai ( 'gpt-4' ),
... builder . toAiSdk (), // Spreads { system, tools }
prompt: "User's question"
});
See builder.ts:1618-1709 for AI SDK exports.
Mastra Integration
Export for Mastra agents:
import { Agent } from '@mastra/core/agent' ;
const { instructions , tools } = builder . toMastra ();
const agent = new Agent ({
name: 'my-agent' ,
instructions ,
model: 'openai/gpt-4o' ,
tools // Already in Mastra format
});
See builder.ts:1757-1801 for Mastra export implementation.
JSON Export
Export configuration as plain object:
const config = builder . toJSON ();
fs . writeFileSync ( 'agent-config.json' , JSON . stringify ( config , null , 2 ));
Zod schemas in tool definitions may not serialize perfectly to JSON due to their internal structure.
See builder.ts:1827-1842 for JSON export.
Best Practices
Start Simple Begin with identity and capabilities. Add complexity only as needed.
Use Templates Start from templates (codingAssistant, customerService) and customize.
Validate Early Call .validate() or .debug() before deploying to catch issues.
Optimize Format Use markdown during development, toon in production for 30-60% token savings.
Forgetting to call .build() : The builder returns itself from methods, not the prompt. Always call .build() at the end.
Mutating shared builders : Builders are mutable. Use .extend() if you need to create variations.
Ignoring validation warnings : Warnings like missing examples or guardrails can impact quality.
Overly complex prompts : More isn’t always better. Keep prompts focused on essential behaviors.
Tools Learn about tool integration and Zod schemas
Constraints Understand behavioral constraints and severity levels
Formats Explore output formats and token optimization