prompt_manager 0.5.7 → 0.5.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/COMMITS.md +196 -0
- data/README.md +485 -203
- data/docs/.keep +0 -0
- data/docs/advanced/custom-keywords.md +421 -0
- data/docs/advanced/dynamic-directives.md +535 -0
- data/docs/advanced/performance.md +612 -0
- data/docs/advanced/search-integration.md +635 -0
- data/docs/api/configuration.md +355 -0
- data/docs/api/directive-processor.md +431 -0
- data/docs/api/prompt-class.md +354 -0
- data/docs/api/storage-adapters.md +462 -0
- data/docs/assets/favicon.ico +1 -0
- data/docs/assets/logo.svg +24 -0
- data/docs/core-features/comments.md +48 -0
- data/docs/core-features/directive-processing.md +38 -0
- data/docs/core-features/erb-integration.md +68 -0
- data/docs/core-features/error-handling.md +197 -0
- data/docs/core-features/parameter-history.md +76 -0
- data/docs/core-features/parameterized-prompts.md +500 -0
- data/docs/core-features/shell-integration.md +79 -0
- data/docs/development/architecture.md +544 -0
- data/docs/development/contributing.md +425 -0
- data/docs/development/roadmap.md +234 -0
- data/docs/development/testing.md +822 -0
- data/docs/examples/advanced.md +523 -0
- data/docs/examples/basic.md +688 -0
- data/docs/examples/real-world.md +776 -0
- data/docs/examples.md +337 -0
- data/docs/getting-started/basic-concepts.md +318 -0
- data/docs/getting-started/installation.md +97 -0
- data/docs/getting-started/quick-start.md +256 -0
- data/docs/index.md +230 -0
- data/docs/migration/v0.9.0.md +459 -0
- data/docs/migration/v1.0.0.md +591 -0
- data/docs/storage/activerecord-adapter.md +348 -0
- data/docs/storage/custom-adapters.md +176 -0
- data/docs/storage/filesystem-adapter.md +236 -0
- data/docs/storage/overview.md +427 -0
- data/examples/advanced_integrations.rb +52 -0
- data/examples/prompts_dir/advanced_demo.txt +79 -0
- data/examples/prompts_dir/directive_example.json +1 -0
- data/examples/prompts_dir/directive_example.txt +8 -0
- data/examples/prompts_dir/todo.json +1 -1
- data/improvement_plan.md +996 -0
- data/lib/prompt_manager/storage/file_system_adapter.rb +8 -2
- data/lib/prompt_manager/version.rb +1 -1
- data/mkdocs.yml +146 -0
- data/prompt_manager_logo.png +0 -0
- metadata +46 -3
- data/LICENSE.txt +0 -21
@@ -0,0 +1,612 @@
|
|
1
|
+
# Performance Optimization
|
2
|
+
|
3
|
+
This guide covers techniques and best practices for optimizing PromptManager performance in production environments.
|
4
|
+
|
5
|
+
## Caching Strategies
|
6
|
+
|
7
|
+
### Prompt Content Caching
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
# Enable built-in caching
|
11
|
+
PromptManager.configure do |config|
|
12
|
+
config.cache_prompts = true
|
13
|
+
config.cache_ttl = 3600 # 1 hour
|
14
|
+
config.cache_store = ActiveSupport::Cache::RedisStore.new(
|
15
|
+
url: ENV['REDIS_URL'],
|
16
|
+
namespace: 'prompt_manager'
|
17
|
+
)
|
18
|
+
end
|
19
|
+
```
|
20
|
+
|
21
|
+
### Custom Caching Layer
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
class CachedPromptManager
|
25
|
+
def self.render(prompt_id, parameters = {}, cache_options = {})
|
26
|
+
cache_key = generate_cache_key(prompt_id, parameters)
|
27
|
+
|
28
|
+
Rails.cache.fetch(cache_key, cache_options) do
|
29
|
+
prompt = PromptManager::Prompt.new(id: prompt_id)
|
30
|
+
prompt.render(parameters)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.invalidate_cache(prompt_id, parameters = nil)
|
35
|
+
if parameters
|
36
|
+
cache_key = generate_cache_key(prompt_id, parameters)
|
37
|
+
Rails.cache.delete(cache_key)
|
38
|
+
else
|
39
|
+
# Invalidate all cached versions of this prompt
|
40
|
+
Rails.cache.delete_matched("prompt:#{prompt_id}:*")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def self.generate_cache_key(prompt_id, parameters)
|
47
|
+
param_hash = Digest::MD5.hexdigest(parameters.to_json)
|
48
|
+
"prompt:#{prompt_id}:#{param_hash}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Usage
|
53
|
+
result = CachedPromptManager.render('welcome_email', { name: 'John' }, expires_in: 30.minutes)
|
54
|
+
```
|
55
|
+
|
56
|
+
### Multi-level Caching
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
class HierarchicalPromptCache
|
60
|
+
def initialize
|
61
|
+
@l1_cache = ActiveSupport::Cache::MemoryStore.new(size: 100) # Fast, small
|
62
|
+
@l2_cache = Rails.cache # Redis, larger but slower
|
63
|
+
end
|
64
|
+
|
65
|
+
def fetch(key, options = {}, &block)
|
66
|
+
# Try L1 cache first
|
67
|
+
result = @l1_cache.read(key)
|
68
|
+
return result if result
|
69
|
+
|
70
|
+
# Try L2 cache
|
71
|
+
result = @l2_cache.fetch(key, options, &block)
|
72
|
+
|
73
|
+
# Store in L1 cache for next time
|
74
|
+
@l1_cache.write(key, result, expires_in: 5.minutes) if result
|
75
|
+
|
76
|
+
result
|
77
|
+
end
|
78
|
+
|
79
|
+
def invalidate(key_pattern)
|
80
|
+
@l1_cache.clear
|
81
|
+
@l2_cache.delete_matched(key_pattern)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
```
|
85
|
+
|
86
|
+
## Storage Optimization
|
87
|
+
|
88
|
+
### Connection Pooling
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
class PooledDatabaseAdapter < PromptManager::Storage::ActiveRecordAdapter
|
92
|
+
def initialize(pool_size: 10, **options)
|
93
|
+
super(**options)
|
94
|
+
@connection_pool = ConnectionPool.new(size: pool_size) do
|
95
|
+
model_class.connection_pool.checkout
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def read(prompt_id)
|
100
|
+
@connection_pool.with do |connection|
|
101
|
+
result = connection.exec_query(
|
102
|
+
"SELECT content FROM prompts WHERE prompt_id = ?",
|
103
|
+
'PromptManager::Read',
|
104
|
+
[prompt_id]
|
105
|
+
)
|
106
|
+
|
107
|
+
raise PromptNotFoundError unless result.any?
|
108
|
+
result.first['content']
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def write(prompt_id, content)
|
113
|
+
@connection_pool.with do |connection|
|
114
|
+
connection.exec_insert(
|
115
|
+
"INSERT INTO prompts (prompt_id, content, updated_at) VALUES (?, ?, ?) " \
|
116
|
+
"ON CONFLICT (prompt_id) DO UPDATE SET content = ?, updated_at = ?",
|
117
|
+
'PromptManager::Write',
|
118
|
+
[prompt_id, content, Time.current, content, Time.current]
|
119
|
+
)
|
120
|
+
end
|
121
|
+
true
|
122
|
+
end
|
123
|
+
end
|
124
|
+
```
|
125
|
+
|
126
|
+
### Bulk Operations
|
127
|
+
|
128
|
+
```ruby
|
129
|
+
class BulkPromptOperations
|
130
|
+
def self.bulk_render(prompt_configs, batch_size: 100)
|
131
|
+
results = {}
|
132
|
+
|
133
|
+
prompt_configs.each_slice(batch_size) do |batch|
|
134
|
+
# Pre-load all prompts in the batch
|
135
|
+
prompt_contents = preload_prompts(batch.map { |config| config[:prompt_id] })
|
136
|
+
|
137
|
+
# Process batch in parallel
|
138
|
+
batch_results = Parallel.map(batch, in_threads: 4) do |config|
|
139
|
+
begin
|
140
|
+
content = prompt_contents[config[:prompt_id]]
|
141
|
+
next unless content
|
142
|
+
|
143
|
+
processor = PromptManager::DirectiveProcessor.new
|
144
|
+
result = processor.process(content, config[:parameters])
|
145
|
+
|
146
|
+
[config[:prompt_id], { success: true, result: result }]
|
147
|
+
rescue => e
|
148
|
+
[config[:prompt_id], { success: false, error: e.message }]
|
149
|
+
end
|
150
|
+
end.compact
|
151
|
+
|
152
|
+
batch_results.each do |prompt_id, result|
|
153
|
+
results[prompt_id] = result
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
results
|
158
|
+
end
|
159
|
+
|
160
|
+
private
|
161
|
+
|
162
|
+
def self.preload_prompts(prompt_ids)
|
163
|
+
# Batch load all prompts at once
|
164
|
+
if PromptManager.storage.respond_to?(:bulk_read)
|
165
|
+
PromptManager.storage.bulk_read(prompt_ids)
|
166
|
+
else
|
167
|
+
prompt_ids.each_with_object({}) do |id, hash|
|
168
|
+
begin
|
169
|
+
hash[id] = PromptManager.storage.read(id)
|
170
|
+
rescue PromptNotFoundError
|
171
|
+
# Skip missing prompts
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# Usage
|
179
|
+
configs = [
|
180
|
+
{ prompt_id: 'welcome', parameters: { name: 'Alice' } },
|
181
|
+
{ prompt_id: 'welcome', parameters: { name: 'Bob' } },
|
182
|
+
{ prompt_id: 'reminder', parameters: { task: 'Meeting' } }
|
183
|
+
]
|
184
|
+
|
185
|
+
results = BulkPromptOperations.bulk_render(configs)
|
186
|
+
```
|
187
|
+
|
188
|
+
## Directive Processing Optimization
|
189
|
+
|
190
|
+
### Lazy Evaluation
|
191
|
+
|
192
|
+
```ruby
|
193
|
+
class LazyDirectiveProcessor < PromptManager::DirectiveProcessor
|
194
|
+
def process(content, context = {})
|
195
|
+
# Only process directives that are actually needed
|
196
|
+
lazy_content = LazyContent.new(content, context)
|
197
|
+
lazy_content.to_s
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
class LazyContent
|
202
|
+
def initialize(content, context)
|
203
|
+
@content = content
|
204
|
+
@context = context
|
205
|
+
@processed = false
|
206
|
+
@result = nil
|
207
|
+
end
|
208
|
+
|
209
|
+
def to_s
|
210
|
+
return @result if @processed
|
211
|
+
|
212
|
+
# Process only when needed
|
213
|
+
@result = process_directives
|
214
|
+
@processed = true
|
215
|
+
@result
|
216
|
+
end
|
217
|
+
|
218
|
+
private
|
219
|
+
|
220
|
+
def process_directives
|
221
|
+
# Only process directives that appear in the content
|
222
|
+
directive_pattern = %r{^//(\w+)\s+(.*)$}
|
223
|
+
|
224
|
+
@content.gsub(directive_pattern) do |match|
|
225
|
+
directive_name = Regexp.last_match(1)
|
226
|
+
directive_args = Regexp.last_match(2)
|
227
|
+
|
228
|
+
# Skip processing if directive handler doesn't exist
|
229
|
+
next match unless directive_handlers.key?(directive_name)
|
230
|
+
|
231
|
+
# Process directive
|
232
|
+
handler = directive_handlers[directive_name]
|
233
|
+
handler.call(directive_args, @context)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
```
|
238
|
+
|
239
|
+
### Directive Compilation
|
240
|
+
|
241
|
+
```ruby
|
242
|
+
class CompiledDirectiveProcessor
|
243
|
+
def initialize
|
244
|
+
@compiled_templates = {}
|
245
|
+
end
|
246
|
+
|
247
|
+
def compile(content)
|
248
|
+
template_id = Digest::MD5.hexdigest(content)
|
249
|
+
|
250
|
+
@compiled_templates[template_id] ||= compile_template(content)
|
251
|
+
end
|
252
|
+
|
253
|
+
def render(template_id, context)
|
254
|
+
compiled_template = @compiled_templates[template_id]
|
255
|
+
return nil unless compiled_template
|
256
|
+
|
257
|
+
compiled_template.call(context)
|
258
|
+
end
|
259
|
+
|
260
|
+
private
|
261
|
+
|
262
|
+
def compile_template(content)
|
263
|
+
# Pre-compile template into executable code
|
264
|
+
ruby_code = convert_to_ruby(content)
|
265
|
+
|
266
|
+
# Create a proc that can be called with context
|
267
|
+
eval("lambda { |context| #{ruby_code} }")
|
268
|
+
end
|
269
|
+
|
270
|
+
def convert_to_ruby(content)
|
271
|
+
# Convert directive syntax to Ruby code
|
272
|
+
content.gsub(%r{//include\s+(.+)}) do |match|
|
273
|
+
file_path = Regexp.last_match(1).strip
|
274
|
+
%{PromptManager.storage.read("#{file_path}")}
|
275
|
+
end.gsub(/\[(\w+)\]/) do |match|
|
276
|
+
param_name = Regexp.last_match(1).downcase
|
277
|
+
%{context[:parameters][:#{param_name}]}
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
```
|
282
|
+
|
283
|
+
## Memory Management
|
284
|
+
|
285
|
+
### Memory-Efficient Prompt Loading
|
286
|
+
|
287
|
+
```ruby
|
288
|
+
class StreamingPromptProcessor
|
289
|
+
def process_large_prompt(prompt_id, parameters = {})
|
290
|
+
prompt_file = PromptManager.storage.file_path(prompt_id)
|
291
|
+
|
292
|
+
Enumerator.new do |yielder|
|
293
|
+
File.foreach(prompt_file) do |line|
|
294
|
+
processed_line = process_line(line, parameters)
|
295
|
+
yielder << processed_line unless processed_line.empty?
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
private
|
301
|
+
|
302
|
+
def process_line(line, parameters)
|
303
|
+
# Process parameters in this line
|
304
|
+
line.gsub(/\[(\w+)\]/) do |match|
|
305
|
+
param_name = Regexp.last_match(1).downcase.to_sym
|
306
|
+
parameters[param_name] || match
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
# Usage for large prompts
|
312
|
+
processor = StreamingPromptProcessor.new
|
313
|
+
prompt_stream = processor.process_large_prompt('huge_prompt', user_id: 123)
|
314
|
+
|
315
|
+
prompt_stream.each do |line|
|
316
|
+
# Process line by line without loading entire prompt into memory
|
317
|
+
output_stream.puts line
|
318
|
+
end
|
319
|
+
```
|
320
|
+
|
321
|
+
### Object Pool Pattern
|
322
|
+
|
323
|
+
```ruby
|
324
|
+
class PromptProcessorPool
|
325
|
+
def initialize(size: 10)
|
326
|
+
@pool = Queue.new
|
327
|
+
@size = size
|
328
|
+
|
329
|
+
size.times do
|
330
|
+
@pool << PromptManager::DirectiveProcessor.new
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
def with_processor
|
335
|
+
processor = @pool.pop
|
336
|
+
begin
|
337
|
+
yield processor
|
338
|
+
ensure
|
339
|
+
# Reset processor state
|
340
|
+
processor.reset_state if processor.respond_to?(:reset_state)
|
341
|
+
@pool << processor
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
# Global pool
|
347
|
+
PROCESSOR_POOL = PromptProcessorPool.new(size: 20)
|
348
|
+
|
349
|
+
# Usage
|
350
|
+
PROCESSOR_POOL.with_processor do |processor|
|
351
|
+
result = processor.process(content, context)
|
352
|
+
end
|
353
|
+
```
|
354
|
+
|
355
|
+
## Database Query Optimization
|
356
|
+
|
357
|
+
### Query Optimization for ActiveRecord Adapter
|
358
|
+
|
359
|
+
```ruby
|
360
|
+
class OptimizedActiveRecordAdapter < PromptManager::Storage::ActiveRecordAdapter
|
361
|
+
def bulk_read(prompt_ids)
|
362
|
+
# Single query instead of N+1
|
363
|
+
prompts = model_class.where(id_column => prompt_ids)
|
364
|
+
.pluck(id_column, content_column)
|
365
|
+
.to_h
|
366
|
+
|
367
|
+
# Ensure all requested IDs are present
|
368
|
+
missing_ids = prompt_ids - prompts.keys
|
369
|
+
missing_ids.each { |id| prompts[id] = nil }
|
370
|
+
|
371
|
+
prompts
|
372
|
+
end
|
373
|
+
|
374
|
+
def read_with_metadata(prompt_id)
|
375
|
+
# Fetch prompt and metadata in single query
|
376
|
+
prompt = model_class.select(:id, :content, :metadata, :updated_at)
|
377
|
+
.find_by(id_column => prompt_id)
|
378
|
+
|
379
|
+
raise PromptNotFoundError unless prompt
|
380
|
+
|
381
|
+
{
|
382
|
+
content: prompt.send(content_column),
|
383
|
+
metadata: prompt.metadata,
|
384
|
+
last_modified: prompt.updated_at
|
385
|
+
}
|
386
|
+
end
|
387
|
+
|
388
|
+
def frequently_used_prompts(limit: 100)
|
389
|
+
# Cache frequently accessed prompts
|
390
|
+
model_class.joins(:usage_logs)
|
391
|
+
.group(id_column)
|
392
|
+
.order('COUNT(usage_logs.id) DESC')
|
393
|
+
.limit(limit)
|
394
|
+
.pluck(id_column, content_column)
|
395
|
+
.to_h
|
396
|
+
end
|
397
|
+
end
|
398
|
+
```
|
399
|
+
|
400
|
+
### Index Optimization
|
401
|
+
|
402
|
+
```sql
|
403
|
+
-- Optimize database indexes for prompt queries
|
404
|
+
|
405
|
+
-- Primary lookup index
|
406
|
+
CREATE INDEX CONCURRENTLY idx_prompts_id_active
|
407
|
+
ON prompts(prompt_id) WHERE active = true;
|
408
|
+
|
409
|
+
-- Content search index (PostgreSQL)
|
410
|
+
CREATE INDEX CONCURRENTLY idx_prompts_content_gin
|
411
|
+
ON prompts USING gin(to_tsvector('english', content));
|
412
|
+
|
413
|
+
-- Metadata search index (PostgreSQL with JSONB)
|
414
|
+
CREATE INDEX CONCURRENTLY idx_prompts_metadata_gin
|
415
|
+
ON prompts USING gin(metadata);
|
416
|
+
|
417
|
+
-- Usage-based queries
|
418
|
+
CREATE INDEX CONCURRENTLY idx_prompts_usage_updated
|
419
|
+
ON prompts(usage_count DESC, updated_at DESC);
|
420
|
+
|
421
|
+
-- Composite index for filtered queries
|
422
|
+
CREATE INDEX CONCURRENTLY idx_prompts_category_status_updated
|
423
|
+
ON prompts(category, status, updated_at DESC);
|
424
|
+
```
|
425
|
+
|
426
|
+
## Monitoring and Profiling
|
427
|
+
|
428
|
+
### Performance Monitoring
|
429
|
+
|
430
|
+
```ruby
|
431
|
+
class PromptPerformanceMonitor
|
432
|
+
def self.monitor_render(prompt_id, parameters = {})
|
433
|
+
start_time = Time.current
|
434
|
+
memory_before = get_memory_usage
|
435
|
+
|
436
|
+
begin
|
437
|
+
result = yield
|
438
|
+
|
439
|
+
duration = Time.current - start_time
|
440
|
+
memory_after = get_memory_usage
|
441
|
+
memory_used = memory_after - memory_before
|
442
|
+
|
443
|
+
log_performance_metrics(prompt_id, {
|
444
|
+
duration: duration,
|
445
|
+
memory_used: memory_used,
|
446
|
+
parameters_count: parameters.size,
|
447
|
+
result_size: result.bytesize,
|
448
|
+
success: true
|
449
|
+
})
|
450
|
+
|
451
|
+
result
|
452
|
+
rescue => e
|
453
|
+
duration = Time.current - start_time
|
454
|
+
|
455
|
+
log_performance_metrics(prompt_id, {
|
456
|
+
duration: duration,
|
457
|
+
error: e.class.name,
|
458
|
+
success: false
|
459
|
+
})
|
460
|
+
|
461
|
+
raise e
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
465
|
+
private
|
466
|
+
|
467
|
+
def self.get_memory_usage
|
468
|
+
GC.stat[:heap_allocated_pages] * GC::INTERNAL_CONSTANTS[:HEAP_PAGE_SIZE]
|
469
|
+
end
|
470
|
+
|
471
|
+
def self.log_performance_metrics(prompt_id, metrics)
|
472
|
+
Rails.logger.info "PromptManager Performance: #{prompt_id}", metrics
|
473
|
+
|
474
|
+
# Send to monitoring service
|
475
|
+
if defined?(StatsD)
|
476
|
+
StatsD.histogram('prompt_manager.render_duration', metrics[:duration])
|
477
|
+
StatsD.histogram('prompt_manager.memory_usage', metrics[:memory_used]) if metrics[:memory_used]
|
478
|
+
StatsD.increment('prompt_manager.renders', tags: ["success:#{metrics[:success]}"])
|
479
|
+
end
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
# Usage
|
484
|
+
result = PromptPerformanceMonitor.monitor_render('welcome_email', name: 'John') do
|
485
|
+
prompt = PromptManager::Prompt.new(id: 'welcome_email')
|
486
|
+
prompt.render(name: 'John')
|
487
|
+
end
|
488
|
+
```
|
489
|
+
|
490
|
+
### Custom Profiling
|
491
|
+
|
492
|
+
```ruby
|
493
|
+
class PromptProfiler
|
494
|
+
def self.profile_render(prompt_id, parameters = {})
|
495
|
+
profiler = RubyProf.profile do
|
496
|
+
prompt = PromptManager::Prompt.new(id: prompt_id)
|
497
|
+
prompt.render(parameters)
|
498
|
+
end
|
499
|
+
|
500
|
+
# Generate reports
|
501
|
+
printer = RubyProf::GraphHtmlPrinter.new(profiler)
|
502
|
+
File.open("tmp/profile_#{prompt_id}_#{Time.current.to_i}.html", 'w') do |file|
|
503
|
+
printer.print(file)
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
507
|
+
def self.benchmark_operations(iterations: 100)
|
508
|
+
Benchmark.bmbm do |x|
|
509
|
+
x.report("File read:") do
|
510
|
+
iterations.times { PromptManager.storage.read('test_prompt') }
|
511
|
+
end
|
512
|
+
|
513
|
+
x.report("Template render:") do
|
514
|
+
prompt = PromptManager::Prompt.new(id: 'test_prompt')
|
515
|
+
iterations.times { prompt.render(name: 'test') }
|
516
|
+
end
|
517
|
+
|
518
|
+
x.report("Cached render:") do
|
519
|
+
iterations.times { CachedPromptManager.render('test_prompt', name: 'test') }
|
520
|
+
end
|
521
|
+
end
|
522
|
+
end
|
523
|
+
end
|
524
|
+
```
|
525
|
+
|
526
|
+
## Production Deployment Optimization
|
527
|
+
|
528
|
+
### Preloading and Warmup
|
529
|
+
|
530
|
+
```ruby
|
531
|
+
class PromptPreloader
|
532
|
+
def self.preload_critical_prompts
|
533
|
+
critical_prompts = %w[
|
534
|
+
welcome_email
|
535
|
+
password_reset
|
536
|
+
order_confirmation
|
537
|
+
error_notification
|
538
|
+
]
|
539
|
+
|
540
|
+
critical_prompts.each do |prompt_id|
|
541
|
+
begin
|
542
|
+
prompt = PromptManager::Prompt.new(id: prompt_id)
|
543
|
+
|
544
|
+
# Preload into cache
|
545
|
+
CachedPromptManager.render(prompt_id, {}, expires_in: 1.hour)
|
546
|
+
|
547
|
+
Rails.logger.info "Preloaded prompt: #{prompt_id}"
|
548
|
+
rescue => e
|
549
|
+
Rails.logger.error "Failed to preload #{prompt_id}: #{e.message}"
|
550
|
+
end
|
551
|
+
end
|
552
|
+
end
|
553
|
+
|
554
|
+
def self.warmup_processor_pool
|
555
|
+
# Initialize processor pool
|
556
|
+
PROCESSOR_POOL.with_processor do |processor|
|
557
|
+
processor.process("//include test\nWarmup content [TEST]",
|
558
|
+
parameters: { test: 'value' })
|
559
|
+
end
|
560
|
+
|
561
|
+
Rails.logger.info "Processor pool warmed up"
|
562
|
+
end
|
563
|
+
end
|
564
|
+
|
565
|
+
# In Rails initializer or deployment script
|
566
|
+
Rails.application.config.after_initialize do
|
567
|
+
PromptPreloader.preload_critical_prompts
|
568
|
+
PromptPreloader.warmup_processor_pool
|
569
|
+
end
|
570
|
+
```
|
571
|
+
|
572
|
+
### Configuration for Production
|
573
|
+
|
574
|
+
```ruby
|
575
|
+
# config/environments/production.rb
|
576
|
+
PromptManager.configure do |config|
|
577
|
+
# Use optimized storage adapter
|
578
|
+
config.storage = OptimizedActiveRecordAdapter.new
|
579
|
+
|
580
|
+
# Enable aggressive caching
|
581
|
+
config.cache_prompts = true
|
582
|
+
config.cache_ttl = 3600 # 1 hour
|
583
|
+
config.cache_store = ActiveSupport::Cache::RedisStore.new(
|
584
|
+
url: ENV['REDIS_URL'],
|
585
|
+
pool_size: 10,
|
586
|
+
pool_timeout: 5
|
587
|
+
)
|
588
|
+
|
589
|
+
# Optimize processing
|
590
|
+
config.max_include_depth = 5 # Reduce for performance
|
591
|
+
config.directive_timeout = 10 # Shorter timeout
|
592
|
+
|
593
|
+
# Error handling
|
594
|
+
config.raise_on_missing_prompts = false
|
595
|
+
config.error_handler = ->(error, context) {
|
596
|
+
Rails.logger.error "Prompt error: #{error.message}"
|
597
|
+
ErrorTracker.notify(error, context)
|
598
|
+
'Content temporarily unavailable'
|
599
|
+
}
|
600
|
+
end
|
601
|
+
```
|
602
|
+
|
603
|
+
## Best Practices Summary
|
604
|
+
|
605
|
+
1. **Cache Aggressively**: Cache rendered prompts and frequently accessed content
|
606
|
+
2. **Batch Operations**: Process multiple prompts together when possible
|
607
|
+
3. **Monitor Performance**: Track render times, memory usage, and error rates
|
608
|
+
4. **Optimize Queries**: Use proper indexes and minimize database roundtrips
|
609
|
+
5. **Pool Resources**: Reuse expensive objects like processors and connections
|
610
|
+
6. **Profile Regularly**: Identify bottlenecks in production workloads
|
611
|
+
7. **Preload Critical Content**: Warm up caches with important prompts
|
612
|
+
8. **Handle Errors Gracefully**: Provide fallbacks when performance degrades
|