Dynamic Directives¶
Dynamic directives allow you to create sophisticated, runtime-configurable prompt processing behaviors that go beyond static //include
statements.
Overview¶
Dynamic directives are custom processing instructions that can modify prompt content based on runtime conditions, external data sources, user context, and complex business logic.
Creating Dynamic Directives¶
Basic Dynamic Directive¶
PromptManager.configure do |config|
config.directive_processor.register_directive('current_time') do |args, context|
format = args.strip.empty? ? '%Y-%m-%d %H:%M:%S' : args.strip
Time.current.strftime(format)
end
end
# Usage in prompts:
Generated at: //current_time
Custom format: //current_time %B %d, %Y at %I:%M %p
Context-Aware Directives¶
config.directive_processor.register_directive('user_greeting') do |args, context|
user = context.dig(:parameters, :user)
return 'Hello there!' unless user
time_of_day = Time.current.hour
greeting = case time_of_day
when 0..11 then 'Good morning'
when 12..17 then 'Good afternoon'
else 'Good evening'
end
"#{greeting}, #{user[:name]}!"
end
Advanced Directive Patterns¶
Data Loading Directives¶
config.directive_processor.register_directive('load_user_data') do |user_id, context|
begin
user = UserService.find(user_id)
context[:loaded_user] = user
# Return user summary
<<~USER_INFO
User: #{user.name}
Email: #{user.email}
Account Type: #{user.account_type}
USER_INFO
rescue UserNotFoundError
'User information unavailable'
end
end
API Integration Directives¶
config.directive_processor.register_directive('weather') do |location, context|
begin
response = HTTParty.get(
'https://api.weather.example.com/current',
query: {
location: location,
api_key: ENV['WEATHER_API_KEY']
}
)
if response.success?
data = response.parsed_response
"Weather in #{location}: #{data['temperature']}°F, #{data['condition']}"
else
'Weather information unavailable'
end
rescue => e
Rails.logger.error "Weather API error: #{e.message}"
'Weather service temporarily unavailable'
end
end
Database Query Directives¶
config.directive_processor.register_directive('recent_orders') do |user_id, context|
user = User.find(user_id)
recent_orders = user.orders.recent.limit(5)
if recent_orders.any?
orders_list = recent_orders.map do |order|
"- Order ##{order.id}: #{order.total_formatted} (#{order.status})"
end.join("\n")
"Your recent orders:\n#{orders_list}"
else
'You have no recent orders.'
end
rescue ActiveRecord::RecordNotFound
'User not found'
end
Conditional Logic Directives¶
IF/ELSE/ENDIF Directive System¶
class ConditionalDirectiveProcessor
def self.register_conditionals(processor)
processor.register_directive('if') do |condition, context|
context[:if_stack] ||= []
result = evaluate_condition(condition, context)
context[:if_stack].push({
condition_met: result,
in_else: false,
content: ''
})
'' # Don't output anything for the if directive itself
end
processor.register_directive('else') do |args, context|
return '' unless context[:if_stack]&.any?
current_if = context[:if_stack].last
current_if[:in_else] = true
'' # Don't output anything for the else directive
end
processor.register_directive('endif') do |args, context|
return '' unless context[:if_stack]&.any?
if_block = context[:if_stack].pop
# Process the accumulated content based on conditions
if (if_block[:condition_met] && !if_block[:in_else]) ||
(!if_block[:condition_met] && if_block[:in_else])
processor.process(if_block[:content], context)
else
''
end
end
end
def self.evaluate_condition(condition, context)
# Simple condition evaluation
# In production, use a proper expression evaluator
case condition.strip
when /\[(\w+)\]\s*(==|!=|>|<|>=|<=)\s*(.+)/
param_name = Regexp.last_match(1).downcase.to_sym
operator = Regexp.last_match(2)
expected_value = Regexp.last_match(3).strip.gsub(/['"]/, '')
actual_value = context.dig(:parameters, param_name).to_s
case operator
when '==' then actual_value == expected_value
when '!=' then actual_value != expected_value
when '>' then actual_value.to_f > expected_value.to_f
when '<' then actual_value.to_f < expected_value.to_f
when '>=' then actual_value.to_f >= expected_value.to_f
when '<=' then actual_value.to_f <= expected_value.to_f
end
else
false
end
end
end
# Register the conditional directives
ConditionalDirectiveProcessor.register_conditionals(
PromptManager.configuration.directive_processor
)
# Usage in prompts:
//if [USER_TYPE] == 'premium'
🌟 Welcome to Premium features!
//else
Upgrade to Premium for additional benefits.
//endif
//if [ORDER_TOTAL] >= 100
🚚 Free shipping applied!
//endif
Loop Directives¶
config.directive_processor.register_directive('foreach') do |args, context|
array_name, template = args.split(':', 2)
array_data = context.dig(:parameters, array_name.strip.to_sym) || []
array_data.map.with_index do |item, index|
# Create item context
item_context = context.deep_dup
item_context[:parameters].merge!({
item: item,
item_index: index,
is_first: index == 0,
is_last: index == array_data.length - 1,
total_count: array_data.length
})
# Process template with item context
processed_template = template.gsub(/\[ITEM\.(\w+)\]/i) do |match|
property = Regexp.last_match(1).downcase
item.is_a?(Hash) ? item[property.to_sym] : item.send(property)
end
PromptManager::DirectiveProcessor.new.process(processed_template, item_context)
end.join("\n")
end
# Usage in prompts:
Your order items:
//foreach order_items: - [ITEM.name]: $[ITEM.price] (Qty: [ITEM.quantity])
//foreach products:
**[ITEM.name]** - $[ITEM.price]
//if [ITEM.on_sale] == 'true'
🏷️ ON SALE!
//endif
Template System Directives¶
Layout System¶
class LayoutDirectiveProcessor
def self.register_layout_directives(processor)
processor.register_directive('layout') do |layout_name, context|
layout_content = processor.storage.read("layouts/#{layout_name}")
context[:current_layout] = layout_content
context[:sections] ||= {}
'' # Layout directive doesn't output content directly
end
processor.register_directive('section') do |args, context|
section_name, content = args.split(':', 2)
context[:sections] ||= {}
context[:sections][section_name.strip] = content.strip
'' # Section directive stores content for later use
end
processor.register_directive('yield') do |section_name, context|
section_content = context.dig(:sections, section_name.strip) || ''
processor.process(section_content, context)
end
processor.register_directive('render_layout') do |args, context|
return '' unless context[:current_layout]
processor.process(context[:current_layout], context)
end
end
end
LayoutDirectiveProcessor.register_layout_directives(
PromptManager.configuration.directive_processor
)
# child_template.txt:
//layout email_layout
//section title: Important Account Update
//section content: Your account settings have been updated successfully.
//render_layout
# layouts/email_layout.txt:
Subject: //yield title
Dear [CUSTOMER_NAME],
//yield content
Best regards,
The Support Team
Component System¶
config.directive_processor.register_directive('component') do |args, context|
component_name, props_str = args.split(':', 2)
# Parse component props
props = {}
if props_str
props_str.scan(/(\w+)="([^"]*)"/) do |key, value|
props[key.to_sym] = value
end
end
# Load component template
component_template = processor.storage.read("components/#{component_name}")
# Create component context
component_context = context.deep_dup
component_context[:parameters].merge!(props)
# Render component
processor.process(component_template, component_context)
rescue PromptManager::PromptNotFoundError
"<!-- Component '#{component_name}' not found -->"
end
# Using components:
//component button: text="Click Here" url="https://example.com" style="primary"
//component user_card: name="[USER_NAME]" email="[USER_EMAIL]"
# components/button.txt:
[Click here]([URL]) <!-- [TEXT] -->
# components/user_card.txt:
**[NAME]**
Email: [EMAIL]
External Service Integration¶
CRM Integration Directive¶
config.directive_processor.register_directive('crm_data') do |args, context|
data_type, customer_id = args.split(':', 2)
case data_type.strip
when 'customer_info'
customer = CRMService.get_customer(customer_id)
<<~INFO
Customer: #{customer.name}
Account Value: #{customer.lifetime_value}
Support Tier: #{customer.support_tier}
INFO
when 'recent_interactions'
interactions = CRMService.get_recent_interactions(customer_id, limit: 5)
interactions.map do |interaction|
"- #{interaction.date}: #{interaction.type} - #{interaction.summary}"
end.join("\n")
else
'Unknown CRM data type'
end
rescue => e
Rails.logger.error "CRM integration error: #{e.message}"
'CRM data temporarily unavailable'
end
Analytics Directive¶
config.directive_processor.register_directive('analytics') do |args, context|
metric_type, time_period = args.split(':', 2)
time_period ||= '30d'
case metric_type.strip
when 'user_activity'
user_id = context.dig(:parameters, :user_id)
activity = AnalyticsService.get_user_activity(user_id, time_period)
"Active #{activity[:active_days]} days in the last #{time_period}"
when 'feature_usage'
feature_usage = AnalyticsService.get_feature_usage(time_period)
top_features = feature_usage.first(3)
"Top features: #{top_features.map(&:name).join(', ')}"
else
'Unknown analytics metric'
end
end
Performance Optimization¶
Caching Dynamic Directives¶
class CachedDirectiveProcessor < PromptManager::DirectiveProcessor
def initialize(**options)
super(**options)
@directive_cache = Rails.cache
end
def register_cached_directive(name, cache_ttl: 300, &handler)
cached_handler = lambda do |args, context|
cache_key = generate_cache_key(name, args, context)
@directive_cache.fetch(cache_key, expires_in: cache_ttl) do
handler.call(args, context)
end
end
register_directive(name, &cached_handler)
end
private
def generate_cache_key(directive_name, args, context)
relevant_params = context[:parameters].slice(:user_id, :tenant_id)
"directive:#{directive_name}:#{args}:#{relevant_params.hash}"
end
end
Async Directive Processing¶
config.directive_processor.register_directive('async_load') do |args, context|
data_source = args.strip
cache_key = "async_data:#{data_source}:#{context[:parameters][:user_id]}"
# Try to get cached result first
cached_result = Rails.cache.read(cache_key)
return cached_result if cached_result
# Start async job if not cached
AsyncDataLoadJob.perform_later(data_source, cache_key, context[:parameters])
# Return placeholder
"Loading #{data_source} data..."
end
class AsyncDataLoadJob < ApplicationJob
def perform(data_source, cache_key, parameters)
result = load_data_from_source(data_source, parameters)
Rails.cache.write(cache_key, result, expires_in: 1.hour)
end
end
Testing Dynamic Directives¶
RSpec Examples¶
describe 'Dynamic Directives' do
let(:processor) { PromptManager::DirectiveProcessor.new(storage: storage) }
let(:storage) { instance_double(PromptManager::Storage::Base) }
before do
processor.register_directive('test_directive') do |args, context|
"processed: #{args} with #{context.dig(:parameters, :test_param)}"
end
end
it 'processes directive with context' do
content = "//test_directive hello world"
context = { parameters: { test_param: 'value' } }
result = processor.process(content, context)
expect(result).to eq "processed: hello world with value"
end
it 'handles directive errors' do
processor.register_directive('error_directive') do |args, context|
raise StandardError, 'test error'
end
expect {
processor.process("//error_directive test")
}.to raise_error(PromptManager::DirectiveProcessingError)
end
end
Integration Tests¶
describe 'Dynamic Directive Integration' do
it 'processes complex directive chains' do
storage = PromptManager::Storage::FileSystemAdapter.new(prompts_dir: 'spec/fixtures')
processor = PromptManager::DirectiveProcessor.new(storage: storage)
# Register test directives
processor.register_directive('if') { |condition, context| ... }
processor.register_directive('foreach') { |args, context| ... }
prompt_content = <<~PROMPT
//if [USER_TYPE] == 'premium'
Premium Features:
//foreach features: - [ITEM.name]: [ITEM.description]
//endif
PROMPT
result = processor.process(prompt_content, {
parameters: {
user_type: 'premium',
features: [
{ name: 'Feature 1', description: 'Description 1' },
{ name: 'Feature 2', description: 'Description 2' }
]
}
})
expect(result).to include('Feature 1: Description 1')
expect(result).to include('Feature 2: Description 2')
end
end
Best Practices¶
- Error Handling: Always handle errors gracefully in directive processors
- Performance: Cache expensive operations and use async processing when appropriate
- Security: Validate and sanitize all directive arguments
- Context Isolation: Be careful when modifying context to avoid side effects
- Documentation: Document directive syntax, parameters, and behavior
- Testing: Write comprehensive tests including error cases
- Naming: Use clear, descriptive names that indicate the directive's purpose
- Resource Management: Clean up resources and connections properly