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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/COMMITS.md +196 -0
  4. data/README.md +485 -203
  5. data/docs/.keep +0 -0
  6. data/docs/advanced/custom-keywords.md +421 -0
  7. data/docs/advanced/dynamic-directives.md +535 -0
  8. data/docs/advanced/performance.md +612 -0
  9. data/docs/advanced/search-integration.md +635 -0
  10. data/docs/api/configuration.md +355 -0
  11. data/docs/api/directive-processor.md +431 -0
  12. data/docs/api/prompt-class.md +354 -0
  13. data/docs/api/storage-adapters.md +462 -0
  14. data/docs/assets/favicon.ico +1 -0
  15. data/docs/assets/logo.svg +24 -0
  16. data/docs/core-features/comments.md +48 -0
  17. data/docs/core-features/directive-processing.md +38 -0
  18. data/docs/core-features/erb-integration.md +68 -0
  19. data/docs/core-features/error-handling.md +197 -0
  20. data/docs/core-features/parameter-history.md +76 -0
  21. data/docs/core-features/parameterized-prompts.md +500 -0
  22. data/docs/core-features/shell-integration.md +79 -0
  23. data/docs/development/architecture.md +544 -0
  24. data/docs/development/contributing.md +425 -0
  25. data/docs/development/roadmap.md +234 -0
  26. data/docs/development/testing.md +822 -0
  27. data/docs/examples/advanced.md +523 -0
  28. data/docs/examples/basic.md +688 -0
  29. data/docs/examples/real-world.md +776 -0
  30. data/docs/examples.md +337 -0
  31. data/docs/getting-started/basic-concepts.md +318 -0
  32. data/docs/getting-started/installation.md +97 -0
  33. data/docs/getting-started/quick-start.md +256 -0
  34. data/docs/index.md +230 -0
  35. data/docs/migration/v0.9.0.md +459 -0
  36. data/docs/migration/v1.0.0.md +591 -0
  37. data/docs/storage/activerecord-adapter.md +348 -0
  38. data/docs/storage/custom-adapters.md +176 -0
  39. data/docs/storage/filesystem-adapter.md +236 -0
  40. data/docs/storage/overview.md +427 -0
  41. data/examples/advanced_integrations.rb +52 -0
  42. data/examples/prompts_dir/advanced_demo.txt +79 -0
  43. data/examples/prompts_dir/directive_example.json +1 -0
  44. data/examples/prompts_dir/directive_example.txt +8 -0
  45. data/examples/prompts_dir/todo.json +1 -1
  46. data/improvement_plan.md +996 -0
  47. data/lib/prompt_manager/storage/file_system_adapter.rb +8 -2
  48. data/lib/prompt_manager/version.rb +1 -1
  49. data/mkdocs.yml +146 -0
  50. data/prompt_manager_logo.png +0 -0
  51. metadata +46 -3
  52. data/LICENSE.txt +0 -21
@@ -0,0 +1,348 @@
1
+ # ActiveRecordAdapter
2
+
3
+ The ActiveRecordAdapter allows you to store prompts in a relational database using ActiveRecord, perfect for Rails applications and scenarios requiring database-backed prompt storage.
4
+
5
+ ## Overview
6
+
7
+ Store prompts in your application's database alongside other application data. This adapter provides full CRUD operations, query capabilities, and integration with ActiveRecord models.
8
+
9
+ ## Setup
10
+
11
+ ### Migration
12
+
13
+ Create the prompts table:
14
+
15
+ ```ruby
16
+ # Generate migration
17
+ rails generate migration CreatePrompts
18
+
19
+ # In the migration file:
20
+ class CreatePrompts < ActiveRecord::Migration[7.0]
21
+ def change
22
+ create_table :prompts do |t|
23
+ t.string :prompt_id, null: false, index: { unique: true }
24
+ t.text :content, null: false
25
+ t.text :description
26
+ t.json :metadata, default: {}
27
+ t.timestamps
28
+ end
29
+ end
30
+ end
31
+ ```
32
+
33
+ ### Model
34
+
35
+ ```ruby
36
+ # app/models/prompt.rb
37
+ class Prompt < ApplicationRecord
38
+ validates :prompt_id, presence: true, uniqueness: true
39
+ validates :content, presence: true
40
+
41
+ # Optional: Add scopes for organization
42
+ scope :by_category, ->(category) { where("metadata->>'category' = ?", category) }
43
+ scope :recent, -> { order(updated_at: :desc) }
44
+ end
45
+ ```
46
+
47
+ ### Configuration
48
+
49
+ ```ruby
50
+ # Configure PromptManager to use ActiveRecord
51
+ PromptManager.configure do |config|
52
+ config.storage = PromptManager::Storage::ActiveRecordAdapter.new(
53
+ model_class: Prompt,
54
+ id_column: :prompt_id,
55
+ content_column: :content
56
+ )
57
+ end
58
+ ```
59
+
60
+ ## Basic Usage
61
+
62
+ ### Creating Prompts
63
+
64
+ ```ruby
65
+ # Create via PromptManager
66
+ prompt = PromptManager::Prompt.new(id: 'welcome_email')
67
+ prompt.save(
68
+ content: 'Welcome to our service, [USER_NAME]!',
69
+ metadata: {
70
+ category: 'email',
71
+ author: 'marketing_team',
72
+ version: '1.0'
73
+ }
74
+ )
75
+
76
+ # Or create via ActiveRecord
77
+ Prompt.create!(
78
+ prompt_id: 'goodbye_email',
79
+ content: 'Thanks for using our service, [USER_NAME]!',
80
+ description: 'Farewell email template',
81
+ metadata: { category: 'email', priority: 'low' }
82
+ )
83
+ ```
84
+
85
+ ### Reading Prompts
86
+
87
+ ```ruby
88
+ # Via PromptManager (recommended)
89
+ prompt = PromptManager::Prompt.new(id: 'welcome_email')
90
+ result = prompt.render(user_name: 'Alice')
91
+
92
+ # Via ActiveRecord
93
+ prompt_record = Prompt.find_by(prompt_id: 'welcome_email')
94
+ puts prompt_record.content
95
+ ```
96
+
97
+ ### Updating Prompts
98
+
99
+ ```ruby
100
+ # Via PromptManager
101
+ prompt = PromptManager::Prompt.new(id: 'welcome_email')
102
+ prompt.save('Updated content: Welcome [USER_NAME] to our platform!')
103
+
104
+ # Via ActiveRecord
105
+ prompt_record = Prompt.find_by(prompt_id: 'welcome_email')
106
+ prompt_record.update!(
107
+ content: 'Updated content...',
108
+ metadata: prompt_record.metadata.merge(version: '1.1')
109
+ )
110
+ ```
111
+
112
+ ## Advanced Features
113
+
114
+ ### Query Interface
115
+
116
+ ```ruby
117
+ # Configure with query support
118
+ PromptManager.configure do |config|
119
+ config.storage = PromptManager::Storage::ActiveRecordAdapter.new(
120
+ model_class: Prompt,
121
+ id_column: :prompt_id,
122
+ content_column: :content,
123
+ enable_queries: true
124
+ )
125
+ end
126
+
127
+ # Search prompts
128
+ results = PromptManager.storage.search(
129
+ category: 'email',
130
+ content_contains: 'welcome'
131
+ )
132
+
133
+ # List by metadata
134
+ email_prompts = PromptManager.storage.where(
135
+ "metadata->>'category' = ?", 'email'
136
+ )
137
+ ```
138
+
139
+ ### Versioning
140
+
141
+ ```ruby
142
+ # Add versioning to your model
143
+ class Prompt < ApplicationRecord
144
+ has_many :prompt_versions, dependent: :destroy
145
+
146
+ before_update :create_version
147
+
148
+ private
149
+
150
+ def create_version
151
+ if content_changed?
152
+ prompt_versions.create!(
153
+ content: content_was,
154
+ version_number: (prompt_versions.maximum(:version_number) || 0) + 1,
155
+ created_at: updated_at_was
156
+ )
157
+ end
158
+ end
159
+ end
160
+
161
+ # Version model
162
+ class PromptVersion < ApplicationRecord
163
+ belongs_to :prompt
164
+ end
165
+
166
+ # Migration for versions
167
+ class CreatePromptVersions < ActiveRecord::Migration[7.0]
168
+ def change
169
+ create_table :prompt_versions do |t|
170
+ t.references :prompt, null: false, foreign_key: true
171
+ t.text :content, null: false
172
+ t.integer :version_number, null: false
173
+ t.timestamps
174
+ end
175
+
176
+ add_index :prompt_versions, [:prompt_id, :version_number], unique: true
177
+ end
178
+ end
179
+ ```
180
+
181
+ ### Multi-tenancy
182
+
183
+ ```ruby
184
+ # Add tenant support
185
+ class Prompt < ApplicationRecord
186
+ belongs_to :tenant
187
+
188
+ validates :prompt_id, uniqueness: { scope: :tenant_id }
189
+
190
+ scope :for_tenant, ->(tenant) { where(tenant: tenant) }
191
+ end
192
+
193
+ # Configure with tenant scope
194
+ PromptManager.configure do |config|
195
+ config.storage = PromptManager::Storage::ActiveRecordAdapter.new(
196
+ model_class: Prompt,
197
+ id_column: :prompt_id,
198
+ content_column: :content,
199
+ scope: -> { Prompt.for_tenant(Current.tenant) }
200
+ )
201
+ end
202
+ ```
203
+
204
+ ## Performance Optimization
205
+
206
+ ### Indexing
207
+
208
+ ```ruby
209
+ # Add performance indexes
210
+ class AddPromptIndexes < ActiveRecord::Migration[7.0]
211
+ def change
212
+ add_index :prompts, :prompt_id, unique: true
213
+ add_index :prompts, :updated_at
214
+ add_index :prompts, "((metadata->>'category'))", name: 'index_prompts_on_category'
215
+ add_index :prompts, :content, type: :gin # For full-text search (PostgreSQL)
216
+ end
217
+ end
218
+ ```
219
+
220
+ ### Caching
221
+
222
+ ```ruby
223
+ # Configure caching
224
+ PromptManager.configure do |config|
225
+ config.storage = PromptManager::Storage::ActiveRecordAdapter.new(
226
+ model_class: Prompt,
227
+ id_column: :prompt_id,
228
+ content_column: :content,
229
+ cache_queries: true,
230
+ cache_ttl: 300 # 5 minutes
231
+ )
232
+ end
233
+
234
+ # Add caching to your model
235
+ class Prompt < ApplicationRecord
236
+ after_update :clear_cache
237
+ after_destroy :clear_cache
238
+
239
+ private
240
+
241
+ def clear_cache
242
+ Rails.cache.delete("prompt:#{prompt_id}")
243
+ end
244
+ end
245
+ ```
246
+
247
+ ### Connection Pooling
248
+
249
+ ```ruby
250
+ # For high-traffic applications
251
+ class PromptReadOnlyRecord < ApplicationRecord
252
+ self.abstract_class = true
253
+ connects_to database: { reading: :prompt_replica }
254
+ end
255
+
256
+ class Prompt < PromptReadOnlyRecord
257
+ # Read operations use replica database
258
+ end
259
+ ```
260
+
261
+ ## Integration Examples
262
+
263
+ ### Rails Controller
264
+
265
+ ```ruby
266
+ class PromptsController < ApplicationController
267
+ def show
268
+ prompt = PromptManager::Prompt.new(id: params[:id])
269
+ @content = prompt.render(params.permit(:user_name, :product_name))
270
+ rescue PromptManager::PromptNotFoundError
271
+ render json: { error: 'Prompt not found' }, status: 404
272
+ end
273
+
274
+ def create
275
+ prompt = PromptManager::Prompt.new(id: prompt_params[:id])
276
+ prompt.save(prompt_params[:content])
277
+
278
+ render json: { message: 'Prompt created successfully' }
279
+ rescue => e
280
+ render json: { error: e.message }, status: 422
281
+ end
282
+
283
+ private
284
+
285
+ def prompt_params
286
+ params.require(:prompt).permit(:id, :content, :description, metadata: {})
287
+ end
288
+ end
289
+ ```
290
+
291
+ ### Background Jobs
292
+
293
+ ```ruby
294
+ class ProcessPromptJob < ApplicationJob
295
+ def perform(prompt_id, parameters)
296
+ prompt = PromptManager::Prompt.new(id: prompt_id)
297
+ result = prompt.render(parameters)
298
+
299
+ # Process the result
300
+ NotificationService.send_message(result)
301
+ rescue PromptManager::PromptNotFoundError => e
302
+ logger.error "Prompt not found: #{prompt_id}"
303
+ # Handle gracefully
304
+ end
305
+ end
306
+
307
+ # Usage
308
+ ProcessPromptJob.perform_later('daily_report', user_id: user.id)
309
+ ```
310
+
311
+ ## Best Practices
312
+
313
+ 1. **Use Transactions**: Wrap prompt operations in database transactions
314
+ 2. **Validate Content**: Add model validations for prompt content
315
+ 3. **Index Strategically**: Index frequently queried fields
316
+ 4. **Cache Wisely**: Cache frequently accessed prompts
317
+ 5. **Monitor Performance**: Track database query performance
318
+ 6. **Backup Regularly**: Include prompts in your database backup strategy
319
+ 7. **Version Control**: Consider versioning for important prompts
320
+ 8. **Secure Access**: Use database-level permissions and encryption
321
+
322
+ ## Migration from FileSystem
323
+
324
+ ```ruby
325
+ # Migrate from filesystem to database
326
+ class MigratePromptsToDatabase
327
+ def self.perform(prompts_dir)
328
+ Dir.glob(File.join(prompts_dir, '**/*.txt')).each do |file_path|
329
+ relative_path = Pathname.new(file_path).relative_path_from(Pathname.new(prompts_dir))
330
+ prompt_id = relative_path.sub_ext('').to_s
331
+ content = File.read(file_path)
332
+
333
+ Prompt.create!(
334
+ prompt_id: prompt_id,
335
+ content: content,
336
+ description: "Migrated from #{file_path}",
337
+ metadata: {
338
+ original_file: file_path,
339
+ migrated_at: Time.current
340
+ }
341
+ )
342
+ end
343
+ end
344
+ end
345
+
346
+ # Run migration
347
+ MigratePromptsToDatabase.perform('/path/to/prompts')
348
+ ```
@@ -0,0 +1,176 @@
1
+ # Custom Storage Adapters
2
+
3
+ Create custom storage adapters to integrate PromptManager with any storage system.
4
+
5
+ ## Adapter Interface
6
+
7
+ All storage adapters must inherit from `PromptManager::Storage::Base` and implement the required methods:
8
+
9
+ ```ruby
10
+ class CustomAdapter < PromptManager::Storage::Base
11
+ def initialize(**options)
12
+ # Initialize your storage connection
13
+ super
14
+ end
15
+
16
+ def read(prompt_id)
17
+ # Return the prompt content as a string
18
+ # Raise PromptManager::PromptNotFoundError if not found
19
+ end
20
+
21
+ def write(prompt_id, content)
22
+ # Save the prompt content
23
+ # Return true on success
24
+ end
25
+
26
+ def exist?(prompt_id)
27
+ # Return true if prompt exists
28
+ end
29
+
30
+ def delete(prompt_id)
31
+ # Remove the prompt
32
+ # Return true on success
33
+ end
34
+
35
+ def list
36
+ # Return array of all prompt IDs
37
+ end
38
+ end
39
+ ```
40
+
41
+ ## Example: Redis Adapter
42
+
43
+ ```ruby
44
+ require 'redis'
45
+
46
+ class RedisAdapter < PromptManager::Storage::Base
47
+ def initialize(redis_url: 'redis://localhost:6379', key_prefix: 'prompts:', **options)
48
+ @redis = Redis.new(url: redis_url)
49
+ @key_prefix = key_prefix
50
+ super(**options)
51
+ end
52
+
53
+ def read(prompt_id)
54
+ content = @redis.get(redis_key(prompt_id))
55
+ raise PromptManager::PromptNotFoundError.new("Prompt '#{prompt_id}' not found") unless content
56
+ content
57
+ end
58
+
59
+ def write(prompt_id, content)
60
+ @redis.set(redis_key(prompt_id), content)
61
+ true
62
+ end
63
+
64
+ def exist?(prompt_id)
65
+ @redis.exists?(redis_key(prompt_id)) > 0
66
+ end
67
+
68
+ def delete(prompt_id)
69
+ @redis.del(redis_key(prompt_id)) > 0
70
+ end
71
+
72
+ def list
73
+ keys = @redis.keys("#{@key_prefix}*")
74
+ keys.map { |key| key.sub(@key_prefix, '') }
75
+ end
76
+
77
+ private
78
+
79
+ def redis_key(prompt_id)
80
+ "#{@key_prefix}#{prompt_id}"
81
+ end
82
+ end
83
+
84
+ # Configure PromptManager to use Redis
85
+ PromptManager.configure do |config|
86
+ config.storage = RedisAdapter.new(
87
+ redis_url: ENV['REDIS_URL'],
88
+ key_prefix: 'myapp:prompts:'
89
+ )
90
+ end
91
+ ```
92
+
93
+ ## Example: S3 Adapter
94
+
95
+ ```ruby
96
+ require 'aws-sdk-s3'
97
+
98
+ class S3Adapter < PromptManager::Storage::Base
99
+ def initialize(bucket:, region: 'us-east-1', key_prefix: 'prompts/', **options)
100
+ @bucket = bucket
101
+ @key_prefix = key_prefix
102
+ @s3 = Aws::S3::Client.new(region: region)
103
+ super(**options)
104
+ end
105
+
106
+ def read(prompt_id)
107
+ response = @s3.get_object(
108
+ bucket: @bucket,
109
+ key: s3_key(prompt_id)
110
+ )
111
+ response.body.read
112
+ rescue Aws::S3::Errors::NoSuchKey
113
+ raise PromptManager::PromptNotFoundError.new("Prompt '#{prompt_id}' not found")
114
+ end
115
+
116
+ def write(prompt_id, content)
117
+ @s3.put_object(
118
+ bucket: @bucket,
119
+ key: s3_key(prompt_id),
120
+ body: content,
121
+ content_type: 'text/plain'
122
+ )
123
+ true
124
+ end
125
+
126
+ def exist?(prompt_id)
127
+ @s3.head_object(bucket: @bucket, key: s3_key(prompt_id))
128
+ true
129
+ rescue Aws::S3::Errors::NotFound
130
+ false
131
+ end
132
+
133
+ def delete(prompt_id)
134
+ @s3.delete_object(bucket: @bucket, key: s3_key(prompt_id))
135
+ true
136
+ end
137
+
138
+ def list
139
+ response = @s3.list_objects_v2(
140
+ bucket: @bucket,
141
+ prefix: @key_prefix
142
+ )
143
+
144
+ response.contents.map do |object|
145
+ object.key.sub(@key_prefix, '')
146
+ end
147
+ end
148
+
149
+ private
150
+
151
+ def s3_key(prompt_id)
152
+ "#{@key_prefix}#{prompt_id}.txt"
153
+ end
154
+ end
155
+ ```
156
+
157
+ ## Best Practices
158
+
159
+ 1. **Error Handling**: Always raise appropriate exceptions
160
+ 2. **Connection Management**: Handle connection failures gracefully
161
+ 3. **Performance**: Implement connection pooling where appropriate
162
+ 4. **Security**: Use proper authentication and encryption
163
+ 5. **Testing**: Write comprehensive tests for your adapter
164
+ 6. **Documentation**: Document configuration options and requirements
165
+
166
+ ## Configuration
167
+
168
+ Register your custom adapter:
169
+
170
+ ```ruby
171
+ PromptManager.configure do |config|
172
+ config.storage = CustomAdapter.new(
173
+ # Your adapter configuration
174
+ )
175
+ end
176
+ ```