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,236 @@
|
|
1
|
+
# FileSystemAdapter
|
2
|
+
|
3
|
+
The FileSystemAdapter is the default storage adapter for PromptManager, storing prompts as files in a directory structure.
|
4
|
+
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
The FileSystemAdapter stores prompts as individual text files in a configurable directory. This is the simplest and most common storage method.
|
8
|
+
|
9
|
+
## Configuration
|
10
|
+
|
11
|
+
### Basic Setup
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
require 'prompt_manager'
|
15
|
+
|
16
|
+
# Use default filesystem storage (~/prompts_dir/)
|
17
|
+
prompt = PromptManager::Prompt.new(id: 'welcome_message')
|
18
|
+
```
|
19
|
+
|
20
|
+
### Custom Directory
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
PromptManager.configure do |config|
|
24
|
+
config.storage = PromptManager::Storage::FileSystemAdapter.new(
|
25
|
+
prompts_dir: '/path/to/your/prompts'
|
26
|
+
)
|
27
|
+
end
|
28
|
+
```
|
29
|
+
|
30
|
+
### Multiple Directories
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
# Search multiple directories in order
|
34
|
+
PromptManager.configure do |config|
|
35
|
+
config.storage = PromptManager::Storage::FileSystemAdapter.new(
|
36
|
+
prompts_dir: [
|
37
|
+
'/home/user/project_prompts',
|
38
|
+
'/shared/common_prompts',
|
39
|
+
'/system/default_prompts'
|
40
|
+
]
|
41
|
+
)
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
## Directory Structure
|
46
|
+
|
47
|
+
### Basic Structure
|
48
|
+
|
49
|
+
```
|
50
|
+
prompts_dir/
|
51
|
+
├── welcome_message.txt
|
52
|
+
├── error_response.txt
|
53
|
+
├── customer_service/
|
54
|
+
│ ├── greeting.txt
|
55
|
+
│ └── farewell.txt
|
56
|
+
└── templates/
|
57
|
+
├── email_header.txt
|
58
|
+
└── email_footer.txt
|
59
|
+
```
|
60
|
+
|
61
|
+
### Organizing Prompts
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
# Access nested prompts using path separators
|
65
|
+
customer_greeting = PromptManager::Prompt.new(id: 'customer_service/greeting')
|
66
|
+
email_header = PromptManager::Prompt.new(id: 'templates/email_header')
|
67
|
+
```
|
68
|
+
|
69
|
+
## File Operations
|
70
|
+
|
71
|
+
### Creating Prompts
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
# Prompts are created as .txt files
|
75
|
+
prompt = PromptManager::Prompt.new(id: 'new_prompt')
|
76
|
+
prompt.save("Your prompt content here...")
|
77
|
+
# Creates: prompts_dir/new_prompt.txt
|
78
|
+
```
|
79
|
+
|
80
|
+
### Reading Prompts
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
# Automatically reads from filesystem
|
84
|
+
prompt = PromptManager::Prompt.new(id: 'existing_prompt')
|
85
|
+
content = prompt.render
|
86
|
+
```
|
87
|
+
|
88
|
+
### Updating Prompts
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
# Modify the file directly or use save method
|
92
|
+
prompt = PromptManager::Prompt.new(id: 'existing_prompt')
|
93
|
+
prompt.save("Updated content...")
|
94
|
+
```
|
95
|
+
|
96
|
+
### Deleting Prompts
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
# Remove the prompt file
|
100
|
+
prompt = PromptManager::Prompt.new(id: 'old_prompt')
|
101
|
+
prompt.delete
|
102
|
+
```
|
103
|
+
|
104
|
+
## Advanced Features
|
105
|
+
|
106
|
+
### File Extensions
|
107
|
+
|
108
|
+
The adapter supports different file extensions:
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
# These all work:
|
112
|
+
# - welcome.txt
|
113
|
+
# - welcome.md
|
114
|
+
# - welcome.prompt
|
115
|
+
# - welcome (no extension)
|
116
|
+
|
117
|
+
prompt = PromptManager::Prompt.new(id: 'welcome')
|
118
|
+
# Searches for welcome.txt, then welcome.md, etc.
|
119
|
+
```
|
120
|
+
|
121
|
+
### Directory Search Order
|
122
|
+
|
123
|
+
When using multiple directories, the adapter searches in order:
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
config.storage = PromptManager::Storage::FileSystemAdapter.new(
|
127
|
+
prompts_dir: [
|
128
|
+
'./project_prompts', # First priority
|
129
|
+
'~/shared_prompts', # Second priority
|
130
|
+
'/system/prompts' # Last resort
|
131
|
+
]
|
132
|
+
)
|
133
|
+
```
|
134
|
+
|
135
|
+
### Permissions and Security
|
136
|
+
|
137
|
+
```ruby
|
138
|
+
# Set directory permissions
|
139
|
+
config.storage = PromptManager::Storage::FileSystemAdapter.new(
|
140
|
+
prompts_dir: '/secure/prompts',
|
141
|
+
file_mode: 0600, # Read/write for owner only
|
142
|
+
dir_mode: 0700 # Access for owner only
|
143
|
+
)
|
144
|
+
```
|
145
|
+
|
146
|
+
## Error Handling
|
147
|
+
|
148
|
+
### Common Issues
|
149
|
+
|
150
|
+
```ruby
|
151
|
+
begin
|
152
|
+
prompt = PromptManager::Prompt.new(id: 'missing_prompt')
|
153
|
+
rescue PromptManager::PromptNotFoundError => e
|
154
|
+
puts "Prompt file not found: #{e.message}"
|
155
|
+
end
|
156
|
+
|
157
|
+
begin
|
158
|
+
prompt.save("content")
|
159
|
+
rescue PromptManager::StorageError => e
|
160
|
+
puts "Cannot write file: #{e.message}"
|
161
|
+
# Check permissions, disk space, etc.
|
162
|
+
end
|
163
|
+
```
|
164
|
+
|
165
|
+
### File System Monitoring
|
166
|
+
|
167
|
+
```ruby
|
168
|
+
# Watch for file changes (requires additional gems)
|
169
|
+
require 'listen'
|
170
|
+
|
171
|
+
listener = Listen.to('/path/to/prompts') do |modified, added, removed|
|
172
|
+
puts "Prompts changed: #{modified + added + removed}"
|
173
|
+
# Reload prompts if needed
|
174
|
+
end
|
175
|
+
|
176
|
+
listener.start
|
177
|
+
```
|
178
|
+
|
179
|
+
## Performance Considerations
|
180
|
+
|
181
|
+
### Caching
|
182
|
+
|
183
|
+
```ruby
|
184
|
+
# Enable file content caching
|
185
|
+
PromptManager.configure do |config|
|
186
|
+
config.cache_prompts = true
|
187
|
+
config.cache_ttl = 300 # 5 minutes
|
188
|
+
end
|
189
|
+
```
|
190
|
+
|
191
|
+
### Large Directories
|
192
|
+
|
193
|
+
For directories with many prompts:
|
194
|
+
|
195
|
+
```ruby
|
196
|
+
# Use indexing for faster lookups
|
197
|
+
config.storage = PromptManager::Storage::FileSystemAdapter.new(
|
198
|
+
prompts_dir: '/large/prompt/directory',
|
199
|
+
enable_indexing: true,
|
200
|
+
index_file: '.prompt_index'
|
201
|
+
)
|
202
|
+
```
|
203
|
+
|
204
|
+
## Best Practices
|
205
|
+
|
206
|
+
1. **Organize by Purpose**: Use subdirectories to group related prompts
|
207
|
+
2. **Consistent Naming**: Use clear, descriptive prompt IDs
|
208
|
+
3. **Version Control**: Store your prompts directory in git
|
209
|
+
4. **Backup Strategy**: Regular backups of your prompts directory
|
210
|
+
5. **File Permissions**: Secure sensitive prompts with appropriate permissions
|
211
|
+
6. **Documentation**: Use comments in prompt files to document purpose and usage
|
212
|
+
|
213
|
+
## Migration from Other Storage
|
214
|
+
|
215
|
+
### From Database
|
216
|
+
|
217
|
+
```ruby
|
218
|
+
# Export database prompts to filesystem
|
219
|
+
database_adapter.all_prompts.each do |prompt_id, content|
|
220
|
+
file_path = File.join(prompts_dir, "#{prompt_id}.txt")
|
221
|
+
File.write(file_path, content)
|
222
|
+
end
|
223
|
+
```
|
224
|
+
|
225
|
+
### Bulk Import
|
226
|
+
|
227
|
+
```ruby
|
228
|
+
# Import multiple files
|
229
|
+
Dir.glob('/old/prompts/*.txt').each do |file_path|
|
230
|
+
prompt_id = File.basename(file_path, '.txt')
|
231
|
+
content = File.read(file_path)
|
232
|
+
|
233
|
+
prompt = PromptManager::Prompt.new(id: prompt_id)
|
234
|
+
prompt.save(content)
|
235
|
+
end
|
236
|
+
```
|
@@ -0,0 +1,427 @@
|
|
1
|
+
# Storage Adapters Overview
|
2
|
+
|
3
|
+
Storage adapters are the backbone of PromptManager, providing the interface between your prompts and their persistence layer. They allow you to store prompts in various backends while maintaining a consistent API.
|
4
|
+
|
5
|
+
## What are Storage Adapters?
|
6
|
+
|
7
|
+
Storage adapters implement a common interface that handles:
|
8
|
+
|
9
|
+
- **Loading** prompt text and parameters
|
10
|
+
- **Saving** changes back to storage
|
11
|
+
- **Listing** available prompts
|
12
|
+
- **Searching** through prompt collections
|
13
|
+
- **Managing** prompt metadata
|
14
|
+
|
15
|
+
Think of them as the "database drivers" for your prompts - they handle the specifics of where and how your prompts are stored.
|
16
|
+
|
17
|
+
## Adapter Architecture
|
18
|
+
|
19
|
+
```mermaid
|
20
|
+
graph TB
|
21
|
+
subgraph "Your Application"
|
22
|
+
APP[Application Code]
|
23
|
+
PROMPT[PromptManager::Prompt]
|
24
|
+
end
|
25
|
+
|
26
|
+
subgraph "Adapter Layer"
|
27
|
+
INTERFACE[Storage Adapter Interface]
|
28
|
+
FS[FileSystemAdapter]
|
29
|
+
AR[ActiveRecordAdapter]
|
30
|
+
CUSTOM[Custom Adapters]
|
31
|
+
end
|
32
|
+
|
33
|
+
subgraph "Storage Backends"
|
34
|
+
FILES[File System]
|
35
|
+
POSTGRES[PostgreSQL]
|
36
|
+
MYSQL[MySQL]
|
37
|
+
REDIS[Redis]
|
38
|
+
S3[AWS S3]
|
39
|
+
end
|
40
|
+
|
41
|
+
APP --> PROMPT
|
42
|
+
PROMPT --> INTERFACE
|
43
|
+
INTERFACE --> FS
|
44
|
+
INTERFACE --> AR
|
45
|
+
INTERFACE --> CUSTOM
|
46
|
+
|
47
|
+
FS --> FILES
|
48
|
+
AR --> POSTGRES
|
49
|
+
AR --> MYSQL
|
50
|
+
CUSTOM --> REDIS
|
51
|
+
CUSTOM --> S3
|
52
|
+
```
|
53
|
+
|
54
|
+
## Available Adapters
|
55
|
+
|
56
|
+
PromptManager includes two built-in adapters:
|
57
|
+
|
58
|
+
### FileSystemAdapter
|
59
|
+
- **Best for**: Development, small teams, file-based workflows
|
60
|
+
- **Storage**: Local files (`.txt` for prompts, `.json` for parameters)
|
61
|
+
- **Features**: Simple setup, version control friendly, human-readable
|
62
|
+
- **Use cases**: Personal projects, documentation, prototype development
|
63
|
+
|
64
|
+
### ActiveRecordAdapter
|
65
|
+
- **Best for**: Web applications, enterprise deployments, shared access
|
66
|
+
- **Storage**: Any database supported by ActiveRecord (PostgreSQL, MySQL, SQLite, etc.)
|
67
|
+
- **Features**: Transactions, concurrent access, advanced querying
|
68
|
+
- **Use cases**: Production applications, multi-user systems, API backends
|
69
|
+
|
70
|
+
## Common Interface
|
71
|
+
|
72
|
+
All storage adapters implement the same core methods:
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
module StorageAdapterInterface
|
76
|
+
# Load a prompt's text and parameters
|
77
|
+
def get(id)
|
78
|
+
# Returns: [text_string, parameters_hash]
|
79
|
+
end
|
80
|
+
|
81
|
+
# Save a prompt's text and parameters
|
82
|
+
def save(id, text, parameters)
|
83
|
+
# Persists changes to storage
|
84
|
+
end
|
85
|
+
|
86
|
+
# Delete a prompt
|
87
|
+
def delete(id)
|
88
|
+
# Removes prompt from storage
|
89
|
+
end
|
90
|
+
|
91
|
+
# List all available prompt IDs
|
92
|
+
def list
|
93
|
+
# Returns: array of prompt ID strings
|
94
|
+
end
|
95
|
+
|
96
|
+
# Search for prompts (optional)
|
97
|
+
def search(query)
|
98
|
+
# Returns: array of matching prompt IDs
|
99
|
+
end
|
100
|
+
end
|
101
|
+
```
|
102
|
+
|
103
|
+
## Choosing an Adapter
|
104
|
+
|
105
|
+
### Use FileSystemAdapter When:
|
106
|
+
|
107
|
+
✅ **Starting a new project** - Simple setup and configuration
|
108
|
+
✅ **Working solo or small team** - Easy to manage and understand
|
109
|
+
✅ **Version control integration** - Files work well with Git
|
110
|
+
✅ **Human-readable storage** - Can edit prompts directly
|
111
|
+
✅ **Prototyping** - Quick to set up and iterate
|
112
|
+
|
113
|
+
❌ **Avoid when**: High concurrency, complex querying, or web deployment
|
114
|
+
|
115
|
+
### Use ActiveRecordAdapter When:
|
116
|
+
|
117
|
+
✅ **Web applications** - Integrates with existing Rails/web apps
|
118
|
+
✅ **Multi-user systems** - Handles concurrent access properly
|
119
|
+
✅ **Complex queries** - SQL-based searching and filtering
|
120
|
+
✅ **Production deployment** - Scalable and reliable
|
121
|
+
✅ **Advanced features** - Transactions, migrations, backups
|
122
|
+
|
123
|
+
❌ **Avoid when**: Simple scripts, file-based workflows, or development experimentation
|
124
|
+
|
125
|
+
## Configuration Patterns
|
126
|
+
|
127
|
+
### Global Configuration
|
128
|
+
Set the adapter once for your entire application:
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
# At application startup
|
132
|
+
PromptManager::Prompt.storage_adapter =
|
133
|
+
PromptManager::Storage::FileSystemAdapter.config do |config|
|
134
|
+
config.prompts_dir = Rails.root.join('prompts')
|
135
|
+
end.new
|
136
|
+
```
|
137
|
+
|
138
|
+
### Per-Instance Configuration
|
139
|
+
Use different adapters for different prompt types:
|
140
|
+
|
141
|
+
```ruby
|
142
|
+
# Customer service prompts from filesystem
|
143
|
+
customer_service = PromptManager::Prompt.new(id: 'greeting')
|
144
|
+
customer_service.storage_adapter = filesystem_adapter
|
145
|
+
|
146
|
+
# System prompts from database
|
147
|
+
system_prompt = PromptManager::Prompt.new(id: 'api_template')
|
148
|
+
system_prompt.storage_adapter = activerecord_adapter
|
149
|
+
```
|
150
|
+
|
151
|
+
### Environment-Based Configuration
|
152
|
+
Different adapters for different environments:
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
adapter = case Rails.env
|
156
|
+
when 'development'
|
157
|
+
PromptManager::Storage::FileSystemAdapter.config do |config|
|
158
|
+
config.prompts_dir = 'prompts'
|
159
|
+
end.new
|
160
|
+
when 'production'
|
161
|
+
PromptManager::Storage::ActiveRecordAdapter.config do |config|
|
162
|
+
config.model = PromptModel
|
163
|
+
end.new
|
164
|
+
end
|
165
|
+
|
166
|
+
PromptManager::Prompt.storage_adapter = adapter
|
167
|
+
```
|
168
|
+
|
169
|
+
## Adapter Comparison
|
170
|
+
|
171
|
+
| Feature | FileSystemAdapter | ActiveRecordAdapter | Custom Adapters |
|
172
|
+
|---------|-------------------|---------------------|-----------------|
|
173
|
+
| **Setup Complexity** | Low | Medium | Varies |
|
174
|
+
| **Concurrent Access** | Limited | Excellent | Depends |
|
175
|
+
| **Search Performance** | Good with tools | Excellent | Varies |
|
176
|
+
| **Version Control** | Excellent | Poor | Varies |
|
177
|
+
| **Backup/Recovery** | File-based | Database tools | Depends |
|
178
|
+
| **Human Readable** | Yes | No | Depends |
|
179
|
+
| **Scalability** | Limited | High | Varies |
|
180
|
+
| **Query Complexity** | Limited | High | Varies |
|
181
|
+
|
182
|
+
## Migration Between Adapters
|
183
|
+
|
184
|
+
You can migrate prompts between different adapters:
|
185
|
+
|
186
|
+
```ruby
|
187
|
+
# Migration helper
|
188
|
+
class AdapterMigration
|
189
|
+
def self.migrate(from_adapter, to_adapter)
|
190
|
+
prompt_ids = from_adapter.list
|
191
|
+
|
192
|
+
prompt_ids.each do |id|
|
193
|
+
text, parameters = from_adapter.get(id)
|
194
|
+
to_adapter.save(id, text, parameters)
|
195
|
+
puts "Migrated: #{id}"
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# Usage
|
201
|
+
old_adapter = FileSystemAdapter.new(prompts_dir: 'old_prompts')
|
202
|
+
new_adapter = ActiveRecordAdapter.new(model: PromptModel)
|
203
|
+
|
204
|
+
AdapterMigration.migrate(old_adapter, new_adapter)
|
205
|
+
```
|
206
|
+
|
207
|
+
## Performance Considerations
|
208
|
+
|
209
|
+
### FileSystemAdapter Performance
|
210
|
+
|
211
|
+
**Strengths:**
|
212
|
+
- Fast individual prompt access
|
213
|
+
- No database overhead
|
214
|
+
- Works with external search tools (ripgrep, grep)
|
215
|
+
|
216
|
+
**Limitations:**
|
217
|
+
- Slow listing operations with many files
|
218
|
+
- No built-in indexing
|
219
|
+
- Limited concurrent write support
|
220
|
+
|
221
|
+
**Optimization tips:**
|
222
|
+
```ruby
|
223
|
+
# Use custom search for better performance
|
224
|
+
adapter.config.search_proc = ->(query) {
|
225
|
+
`rg -l "#{query}" #{prompts_dir}`.split("\n").map { |f|
|
226
|
+
File.basename(f, '.txt')
|
227
|
+
}
|
228
|
+
}
|
229
|
+
```
|
230
|
+
|
231
|
+
### ActiveRecordAdapter Performance
|
232
|
+
|
233
|
+
**Strengths:**
|
234
|
+
- SQL-based querying and indexing
|
235
|
+
- Optimized for concurrent access
|
236
|
+
- Built-in caching support
|
237
|
+
|
238
|
+
**Limitations:**
|
239
|
+
- Database connection overhead
|
240
|
+
- More complex deployment
|
241
|
+
|
242
|
+
**Optimization tips:**
|
243
|
+
```ruby
|
244
|
+
# Add database indexes
|
245
|
+
class CreatePrompts < ActiveRecord::Migration[7.0]
|
246
|
+
def change
|
247
|
+
create_table :prompts do |t|
|
248
|
+
t.string :name, null: false
|
249
|
+
t.text :content
|
250
|
+
t.json :parameters
|
251
|
+
t.timestamps
|
252
|
+
end
|
253
|
+
|
254
|
+
add_index :prompts, :name, unique: true
|
255
|
+
add_index :prompts, :created_at
|
256
|
+
# Add full-text search index for content
|
257
|
+
add_index :prompts, :content, using: :gin if adapter_name == 'PostgreSQL'
|
258
|
+
end
|
259
|
+
end
|
260
|
+
```
|
261
|
+
|
262
|
+
## Error Handling
|
263
|
+
|
264
|
+
All adapters should handle common error scenarios:
|
265
|
+
|
266
|
+
```ruby
|
267
|
+
begin
|
268
|
+
prompt = PromptManager::Prompt.new(id: 'example')
|
269
|
+
text = prompt.to_s
|
270
|
+
rescue PromptManager::StorageError => e
|
271
|
+
case e.message
|
272
|
+
when /not found/
|
273
|
+
puts "Prompt doesn't exist: #{e.id}"
|
274
|
+
when /permission denied/
|
275
|
+
puts "Cannot access storage: #{e.message}"
|
276
|
+
when /connection failed/
|
277
|
+
puts "Storage backend unavailable: #{e.message}"
|
278
|
+
else
|
279
|
+
puts "Storage error: #{e.message}"
|
280
|
+
end
|
281
|
+
end
|
282
|
+
```
|
283
|
+
|
284
|
+
## Testing with Adapters
|
285
|
+
|
286
|
+
### Test Adapter
|
287
|
+
For testing, create a simple in-memory adapter:
|
288
|
+
|
289
|
+
```ruby
|
290
|
+
class TestAdapter
|
291
|
+
def initialize
|
292
|
+
@storage = {}
|
293
|
+
end
|
294
|
+
|
295
|
+
def get(id)
|
296
|
+
@storage[id] || raise("Prompt not found: #{id}")
|
297
|
+
end
|
298
|
+
|
299
|
+
def save(id, text, parameters)
|
300
|
+
@storage[id] = [text, parameters]
|
301
|
+
end
|
302
|
+
|
303
|
+
def list
|
304
|
+
@storage.keys
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
# In your tests
|
309
|
+
RSpec.describe "PromptManager" do
|
310
|
+
let(:adapter) { TestAdapter.new }
|
311
|
+
|
312
|
+
before do
|
313
|
+
PromptManager::Prompt.storage_adapter = adapter
|
314
|
+
adapter.save('test', 'Hello [NAME]', { '[NAME]' => 'World' })
|
315
|
+
end
|
316
|
+
|
317
|
+
it "processes prompts correctly" do
|
318
|
+
prompt = PromptManager::Prompt.new(id: 'test')
|
319
|
+
expect(prompt.to_s).to eq('Hello World')
|
320
|
+
end
|
321
|
+
end
|
322
|
+
```
|
323
|
+
|
324
|
+
### Adapter Testing
|
325
|
+
Test your custom adapters with a shared test suite:
|
326
|
+
|
327
|
+
```ruby
|
328
|
+
# Shared examples for adapter testing
|
329
|
+
RSpec.shared_examples "storage adapter" do |adapter_factory|
|
330
|
+
let(:adapter) { adapter_factory.call }
|
331
|
+
|
332
|
+
it "saves and retrieves prompts" do
|
333
|
+
adapter.save('test', 'Hello [NAME]', { '[NAME]' => 'World' })
|
334
|
+
text, params = adapter.get('test')
|
335
|
+
|
336
|
+
expect(text).to eq('Hello [NAME]')
|
337
|
+
expect(params).to eq({ '[NAME]' => 'World' })
|
338
|
+
end
|
339
|
+
|
340
|
+
it "lists available prompts" do
|
341
|
+
adapter.save('test1', 'Content 1', {})
|
342
|
+
adapter.save('test2', 'Content 2', {})
|
343
|
+
|
344
|
+
expect(adapter.list).to contain_exactly('test1', 'test2')
|
345
|
+
end
|
346
|
+
|
347
|
+
it "raises error for missing prompts" do
|
348
|
+
expect { adapter.get('missing') }.to raise_error(PromptManager::StorageError)
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
# Test your adapters
|
353
|
+
RSpec.describe FileSystemAdapter do
|
354
|
+
include_examples "storage adapter", -> {
|
355
|
+
FileSystemAdapter.new(prompts_dir: temp_dir)
|
356
|
+
}
|
357
|
+
end
|
358
|
+
```
|
359
|
+
|
360
|
+
## Best Practices
|
361
|
+
|
362
|
+
### 1. Configuration Management
|
363
|
+
```ruby
|
364
|
+
# Use environment variables for sensitive config
|
365
|
+
database_config = {
|
366
|
+
host: ENV.fetch('DATABASE_HOST', 'localhost'),
|
367
|
+
password: ENV['DATABASE_PASSWORD'],
|
368
|
+
pool: ENV.fetch('DATABASE_POOL', 5).to_i
|
369
|
+
}
|
370
|
+
```
|
371
|
+
|
372
|
+
### 2. Error Recovery
|
373
|
+
```ruby
|
374
|
+
class ResilientAdapter
|
375
|
+
def initialize(primary, fallback)
|
376
|
+
@primary = primary
|
377
|
+
@fallback = fallback
|
378
|
+
end
|
379
|
+
|
380
|
+
def get(id)
|
381
|
+
@primary.get(id)
|
382
|
+
rescue => e
|
383
|
+
Rails.logger.warn "Primary adapter failed: #{e.message}"
|
384
|
+
@fallback.get(id)
|
385
|
+
end
|
386
|
+
end
|
387
|
+
```
|
388
|
+
|
389
|
+
### 3. Caching Layer
|
390
|
+
```ruby
|
391
|
+
class CachingAdapter
|
392
|
+
def initialize(adapter, cache_store = Rails.cache)
|
393
|
+
@adapter = adapter
|
394
|
+
@cache = cache_store
|
395
|
+
end
|
396
|
+
|
397
|
+
def get(id)
|
398
|
+
@cache.fetch("prompt:#{id}", expires_in: 1.hour) do
|
399
|
+
@adapter.get(id)
|
400
|
+
end
|
401
|
+
end
|
402
|
+
end
|
403
|
+
```
|
404
|
+
|
405
|
+
### 4. Monitoring and Logging
|
406
|
+
```ruby
|
407
|
+
class InstrumentedAdapter
|
408
|
+
def initialize(adapter)
|
409
|
+
@adapter = adapter
|
410
|
+
end
|
411
|
+
|
412
|
+
def get(id)
|
413
|
+
start_time = Time.current
|
414
|
+
result = @adapter.get(id)
|
415
|
+
duration = Time.current - start_time
|
416
|
+
|
417
|
+
Rails.logger.info "Prompt loaded: #{id} (#{duration}s)"
|
418
|
+
result
|
419
|
+
end
|
420
|
+
end
|
421
|
+
```
|
422
|
+
|
423
|
+
## Next Steps
|
424
|
+
|
425
|
+
- **Learn specific adapters**: [FileSystemAdapter](filesystem-adapter.md) | [ActiveRecordAdapter](activerecord-adapter.md)
|
426
|
+
- **Build custom adapters**: [Custom Adapters Guide](custom-adapters.md)
|
427
|
+
- **See examples**: [Real-world Usage](../examples/real-world.md)
|
@@ -0,0 +1,52 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/inline'
|
5
|
+
|
6
|
+
gemfile do
|
7
|
+
source 'https://rubygems.org'
|
8
|
+
gem 'prompt_manager'
|
9
|
+
gem 'ruby-openai'
|
10
|
+
gem 'tty-spinner'
|
11
|
+
end
|
12
|
+
|
13
|
+
require 'prompt_manager'
|
14
|
+
require 'openai'
|
15
|
+
require 'erb'
|
16
|
+
require 'time'
|
17
|
+
require 'tty-spinner'
|
18
|
+
|
19
|
+
# Configure PromptManager with filesystem adapter
|
20
|
+
PromptManager::Prompt.storage_adapter = PromptManager::Storage::FileSystemAdapter.config do |config|
|
21
|
+
config.prompts_dir = File.join(__dir__, 'prompts_dir')
|
22
|
+
end.new
|
23
|
+
|
24
|
+
# Configure OpenAI client
|
25
|
+
client = OpenAI::Client.new(
|
26
|
+
access_token: ENV['OPENAI_API_KEY']
|
27
|
+
)
|
28
|
+
|
29
|
+
# Get prompt instance and process with LLM
|
30
|
+
prompt = PromptManager::Prompt.new(
|
31
|
+
id: 'advanced_demo',
|
32
|
+
erb_flag: true,
|
33
|
+
envar_flag: true
|
34
|
+
)
|
35
|
+
|
36
|
+
spinner = TTY::Spinner.new("[:spinner] Waiting for response...")
|
37
|
+
spinner.auto_spin
|
38
|
+
|
39
|
+
response = client.chat(
|
40
|
+
parameters: {
|
41
|
+
model: 'gpt-4o-mini',
|
42
|
+
messages: [{ role: 'user', content: prompt.to_s }],
|
43
|
+
stream: proc do |chunk, _bytesize|
|
44
|
+
spinner.stop
|
45
|
+
content = chunk.dig("choices", 0, "delta", "content")
|
46
|
+
print content if content
|
47
|
+
$stdout.flush
|
48
|
+
end
|
49
|
+
}
|
50
|
+
)
|
51
|
+
|
52
|
+
puts
|