06. Dynamic Prompt Construction
Static prompts are strings written once, used repeatedly. Dynamic prompts are constructed at runtime from modular components selected based on context, user input, or system state. This enables adaptive behavior that static prompts cannot achieve.
The fundamental mechanism: template filling with conditional logic. A base template contains placeholders. Before rendering, logic selects which sub-templates to include, which examples to show, what constraints to add. The rendered prompt is specific to the current invocation.
Consider a support ticket classifier. A static prompt says "Classify this ticket into: billing, technical, feature_request, other." This works—but the definition of "billing" might differ between contexts. A SaaS product's billing is different from a hardware product's billing. Dynamic prompt construction allows injecting domain-specific definitions that the classifier uses for its categorization.
from string import Template
from typing import Optional
class DynamicPromptBuilder:
def __init__(self):
self.components = {
'role': {},
'context': {},
'constraints': {},
'examples': {},
'format': {}
}
def register_component(self, category: str, name: str, template: str):
"""Register a reusable template component."""
self.components[category][name] = template
def build(
self,
role: Optional[str] = None,
context_name: Optional[str] = None,
constraint_names: list[str] = None,
example_names: list[str] = None,
format_name: Optional[str] = None,
**template_vars
) -> str:
"""Construct a prompt from registered components."""
parts = []
# Select and apply role
if role and role in self.components['role']:
parts.append(self.components['role'][role])
# Select and apply context
if context_name and context_name in self.components['context']:
parts.append(self.components['context'][context_name])
# Select and apply constraints
constraint_names = constraint_names or []
for name in constraint_names:
if name in self.components['constraints']:
parts.append(self.components['constraints'][name])
# Select and apply examples
example_names = example_names or []
for name in example_names:
if name in self.components['examples']:
parts.append(self.components['examples'][name])
# Select and apply format
if format_name and format_name in self.components['format']:
parts.append(self.components['format'][format_name])
# Combine parts
prompt = "\n\n".join(parts)
# Apply additional template variables
try:
prompt = Template(prompt).substitute(**template_vars)
except KeyError as e:
raise ValueError(f"Missing template variable: {e}")
return prompt
# Register components for email classification domain
builder = DynamicPromptBuilder()
# Domain-specific role
builder.register_component('role', 'support_agent', """You are a technical support agent
analyzing customer emails. Your job is to extract the core issue and categorize it accurately.""")
# Domain-specific context
builder.register_component('context', 'saas_platform', """This is a SaaS project management platform.
Available categories reflect common issues:
- billing: subscription, payment, invoice, charge disputes
- technical: bugs, errors, performance, integrations not working
- feature_request: suggestions for new capabilities
- account: login problems, access issues, permissions
- documentation: questions about how to use features""")
# Shared constraints
builder.register_component('constraints', 'strict_classification', """Respond ONLY with the
category name. Do not explain your reasoning. Do not use quotes or punctuation.""")
builder.register_component('constraints', 'allow_uncertainty', """If the email touches multiple
categories, choose the most prominent one. If truly uncertain, respond with 'mixed'.""")
# Domain-specific examples
builder.register_component('examples', 'billing_examples', """Examples:
Email: 'I was charged twice for my subscription this month'
Category: billing
Email: 'Can you send me an invoice for Q4?'
Category: billing
Email: 'The integration with Slack stopped working after your last update'
Category: technical""")
# Format directives
builder.register_component('format', 'json_output', """Return your response as JSON:
{"category": "value"}""")
# Construct a specific prompt
prompt = builder.build(
role='support_agent',
context_name='saas_platform',
constraint_names=['strict_classification'],
example_names=['billing_examples'],
format_name='json_output'
)
print(prompt)
The component registration pattern enables separation of concerns: domain experts can define context and examples separate from engineers who build the execution logic. Updating the billing category definition requires changing only the context component, not hunting through all prompts that include that definition.
Failure modes of dynamic construction:
Combinatorial explosion: With K categories and M constraint sets and N example sets, there are K×M×N possible prompts. Not all combinations are tested. Unknown bad interactions may appear in production.
Inconsistent composition: When components are authored separately, combining them may produce logical inconsistencies. The billing definition assumes certain knowledge that the role definition doesn't establish.
Empty composition: If a requested component name doesn't exist, the dynamic builder either fails or silently produces incomplete prompts. Silent failures are worse—production prompts missing critical context are hard to debug.
Dynamic construction is most valuable when the prompt must adapt to user characteristics (expert vs. novice), domain variations (legal text vs. marketing copy), or task variations (classify vs. summarize). Static prompts handle one variant well but require duplication to handle multiple.
Identify a prompt that varies based on user type, content domain, or task subtype. Refactor it into a builder with 3-4 reusable components. Compare the resulting prompts before and after refactoring. Test at least 3 different component combinations to verify no unintended interactions.