prompt_manager 0.2.1 → 0.3.1
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/README.md +34 -1
- data/lib/prompt_manager/prompt.rb +12 -4
- data/lib/prompt_manager/storage/active_record_adapter.rb +96 -1
- data/lib/prompt_manager/storage/file_system_adapter.rb +2 -0
- data/lib/prompt_manager/storage/sqlite_adapter.rb +91 -0
- data/lib/prompt_manager/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: add8f0261dd0fd2e91e8195330d923e8806c2e3a7243cfb51ce70a589fc76ef2
|
4
|
+
data.tar.gz: 73b69061afdd242679c231949fba6a3b075f486feb20113cb838d0345dd2ffcf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a65630d9623b49c34265b137f03cfb032ec645f736e9ca9327e2aaf045cdd0e2818e28e1b000bc80b32016f32a59192861db6cc7596c8ea6834b2c386ce1aac2
|
7
|
+
data.tar.gz: ec16e90d781c354875932d454d04783bcd46b10f7a815ffe94204fd17475bc2930cb6627e55072ec9b937512031033bdf01eda83d62ed126d2833801fd156e0c
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.3.0] = 2023-11-28
|
4
|
+
|
5
|
+
- **Breaking change** The value of the parameters Hash for a keyword is now an Array instead of a single value. The last value in the Array is always the most recent value used for the given keyword. This was done to support the use of a Readline::History object editing in the [aia](https://github.com/MadBomber/aia) CLI tool
|
6
|
+
|
3
7
|
## [0.2.0] - 2023-11-21
|
4
8
|
|
5
9
|
- **Breaking change to FileSystemAdapter config process**
|
data/README.md
CHANGED
@@ -2,7 +2,8 @@
|
|
2
2
|
|
3
3
|
Manage the parameterized prompts (text) used in generative AI (aka chatGPT, OpenAI, _et.al._) using storage adapters such as FileSystemAdapter, SqliteAdapter and ActiveRecordAdapter.
|
4
4
|
|
5
|
-
**Breaking Change** in version 0.
|
5
|
+
**Breaking Change** in version 0.3.0 - The value of the parameters Hash for a keyword is now an Array instead of a single value. The last value in the Array is always the most recent value used for the given keyword. This was done to support the use of a Readline::History object editing in the [aia](https://github.com/MadBomber/aia) CLI tool
|
6
|
+
|
6
7
|
|
7
8
|
<!-- Tocer[start]: Auto-generated, don't remove. -->
|
8
9
|
|
@@ -19,6 +20,8 @@ Manage the parameterized prompts (text) used in generative AI (aka chatGPT, Open
|
|
19
20
|
- [prompts_dir](#prompts_dir)
|
20
21
|
- [search_proc](#search_proc)
|
21
22
|
- [File Extensions](#file-extensions)
|
23
|
+
- [Example Prompt Text File](#example-prompt-text-file)
|
24
|
+
- [Example Prompt Parameters JSON File](#example-prompt-parameters-json-file)
|
22
25
|
- [Extra Functionality](#extra-functionality)
|
23
26
|
- [SqliteAdapter](#sqliteadapter)
|
24
27
|
- [ActiveRecordAdapter](#activerecordadapter)
|
@@ -118,6 +121,36 @@ Currently the `FileSystemAdapter` only supports a JSON serializer for its parame
|
|
118
121
|
|
119
122
|
They exist so that there is a platform on to which other storage adapters can be built or serializers added. This is not currently on the roadmap.
|
120
123
|
|
124
|
+
##### Example Prompt Text File
|
125
|
+
|
126
|
+
```text
|
127
|
+
# ~/.prompts/joke.txt
|
128
|
+
# Desc: Tell some jokes
|
129
|
+
|
130
|
+
Tell me a few [KIND] jokes about [SUBJECT]
|
131
|
+
```
|
132
|
+
|
133
|
+
Note the command lines at the top. This is a convention I use. It is not part of the software. I find it helpful in documenting the prompt.
|
134
|
+
|
135
|
+
##### Example Prompt Parameters JSON File
|
136
|
+
|
137
|
+
```json
|
138
|
+
{
|
139
|
+
"[KIND]": [
|
140
|
+
"pun",
|
141
|
+
"family friendly"
|
142
|
+
],
|
143
|
+
"[SUBJECT]": [
|
144
|
+
"parrot",
|
145
|
+
"garbage man",
|
146
|
+
"snowman",
|
147
|
+
"weather girl"
|
148
|
+
]
|
149
|
+
}
|
150
|
+
```
|
151
|
+
|
152
|
+
The last value in the keyword's Array is the most recent value used for that keyword. This is a functionality established since v0.3.0. Its purpose is to provide a history of values from which a user can select to repeat a previous value or to select ta previous value and edit it into something new.
|
153
|
+
|
121
154
|
##### Extra Functionality
|
122
155
|
|
123
156
|
The `FileSystemAdapter` adds two new methods for use by the `Prompt` class:
|
@@ -81,8 +81,9 @@ class PromptManager::Prompt
|
|
81
81
|
# Return tje prompt text suitable for passing to a
|
82
82
|
# gen-AI process.
|
83
83
|
def to_s
|
84
|
-
|
84
|
+
build
|
85
85
|
end
|
86
|
+
alias_method :prompt, :to_s
|
86
87
|
|
87
88
|
|
88
89
|
# Save the prompt to the Storage system
|
@@ -108,20 +109,27 @@ class PromptManager::Prompt
|
|
108
109
|
def build
|
109
110
|
@prompt = text.gsub(PARAMETER_REGEX) do |match|
|
110
111
|
param_name = match
|
111
|
-
parameters[param_name] || match
|
112
|
+
Array(parameters[param_name]).last || match
|
112
113
|
end
|
113
114
|
|
114
115
|
remove_comments
|
115
116
|
end
|
116
117
|
|
118
|
+
|
119
|
+
def keywords
|
120
|
+
update_keywords
|
121
|
+
end
|
122
|
+
|
117
123
|
######################################
|
118
124
|
private
|
119
125
|
|
120
126
|
def update_keywords
|
121
127
|
@keywords = @text.scan(PARAMETER_REGEX).flatten.uniq
|
122
|
-
keywords.each do |kw|
|
123
|
-
@parameters[kw] =
|
128
|
+
@keywords.each do |kw|
|
129
|
+
@parameters[kw] = [] unless @parameters.has_key?(kw)
|
124
130
|
end
|
131
|
+
|
132
|
+
@keywords
|
125
133
|
end
|
126
134
|
|
127
135
|
|
@@ -63,4 +63,99 @@ class PromptManager::Storage::ActiveRecordAdapter
|
|
63
63
|
def find_prompt(prompt_id)
|
64
64
|
model_class.find_by(id: prompt_id) || raise('Prompt not found')
|
65
65
|
end
|
66
|
-
end
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
__END__
|
70
|
+
|
71
|
+
# prompt_manager/lib/prompt_manager/storage/active_record_adapter.rb
|
72
|
+
|
73
|
+
require 'active_record'
|
74
|
+
|
75
|
+
module PromptManager
|
76
|
+
module Storage
|
77
|
+
class ActiveRecordAdapter
|
78
|
+
|
79
|
+
# Define models for ActiveRecord
|
80
|
+
class Prompt < ActiveRecord::Base
|
81
|
+
validates :unique_id, presence: true
|
82
|
+
validates :text, presence: true
|
83
|
+
end
|
84
|
+
|
85
|
+
class PromptParameter < ActiveRecord::Base
|
86
|
+
belongs_to :prompt
|
87
|
+
validates :key, presence: true
|
88
|
+
serialize :value
|
89
|
+
end
|
90
|
+
|
91
|
+
def initialize
|
92
|
+
unless ActiveRecord::Base.connected?
|
93
|
+
raise ArgumentError, "ActiveRecord is not connected"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def get(id:)
|
98
|
+
prompt = Prompt.find_by(unique_id: id)
|
99
|
+
return nil unless prompt
|
100
|
+
|
101
|
+
parameters = prompt.prompt_parameters.index_by(&:key)
|
102
|
+
|
103
|
+
{
|
104
|
+
id: prompt.unique_id,
|
105
|
+
text: prompt.text,
|
106
|
+
parameters: parameters.transform_values(&:value)
|
107
|
+
}
|
108
|
+
end
|
109
|
+
|
110
|
+
def save(id:, text: "", parameters: {})
|
111
|
+
prompt = Prompt.find_or_initialize_by(unique_id: id)
|
112
|
+
prompt.text = text
|
113
|
+
prompt.save!
|
114
|
+
|
115
|
+
parameters.each do |key, value|
|
116
|
+
parameter = PromptParameter.find_or_initialize_by(prompt: prompt, key: key)
|
117
|
+
parameter.value = value
|
118
|
+
parameter.save!
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def delete(id:)
|
123
|
+
prompt = Prompt.find_by(unique_id: id)
|
124
|
+
return unless prompt
|
125
|
+
|
126
|
+
prompt.prompt_parameters.destroy_all
|
127
|
+
prompt.destroy
|
128
|
+
end
|
129
|
+
|
130
|
+
def search(for_what)
|
131
|
+
query = '%' + for_what.downcase + '%'
|
132
|
+
Prompt.where('LOWER(text) LIKE ?', query).pluck(:unique_id)
|
133
|
+
end
|
134
|
+
|
135
|
+
def list(*)
|
136
|
+
Prompt.pluck(:unique_id)
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
|
141
|
+
# This is an example of how the database connection setup could look like,
|
142
|
+
# but it should be handled externally in the actual application setup.
|
143
|
+
def self.setup_database_connection
|
144
|
+
ActiveRecord::Base.establish_connection(
|
145
|
+
adapter: 'sqlite3',
|
146
|
+
database: 'prompts.db'
|
147
|
+
)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# After this, you would need to create a database migration to generate the required tables.
|
154
|
+
# Additionally, you have to establish an ActiveRecord connection before using this adapter,
|
155
|
+
# typically in the environment setup of your application.
|
156
|
+
|
157
|
+
# Keep in mind you need to create migrations for both the Prompt and PromptParameter models,
|
158
|
+
# and manage the database schema using ActiveRecord migrations. This adapter assumes that the
|
159
|
+
# database structure is already in place and follows the schema inferred by the models in the adapter.
|
160
|
+
|
161
|
+
|
@@ -258,6 +258,7 @@ class PromptManager::Storage::FileSystemAdapter
|
|
258
258
|
def write_with_error_handling(file_path, content)
|
259
259
|
begin
|
260
260
|
file_path.write content
|
261
|
+
true
|
261
262
|
rescue IOError => e
|
262
263
|
raise "Failed to write to file: #{e.message}"
|
263
264
|
end
|
@@ -268,6 +269,7 @@ class PromptManager::Storage::FileSystemAdapter
|
|
268
269
|
def delete_with_error_handling(file_path)
|
269
270
|
begin
|
270
271
|
file_path.delete
|
272
|
+
true
|
271
273
|
rescue IOError => e
|
272
274
|
raise "Failed to delete file: #{e.message}"
|
273
275
|
end
|
@@ -61,5 +61,96 @@ class PromptManager::Storage::SqliteAdapter
|
|
61
61
|
end
|
62
62
|
|
63
63
|
|
64
|
+
__END__
|
65
|
+
|
66
|
+
|
67
|
+
require 'sequel'
|
68
|
+
require 'pathname'
|
69
|
+
require 'json'
|
70
|
+
|
71
|
+
module PromptManager
|
72
|
+
module Storage
|
73
|
+
class SqliteAdapter
|
74
|
+
DEFAULT_DB_FILE = 'prompts.sqlite3'.freeze
|
75
|
+
|
76
|
+
class << self
|
77
|
+
attr_accessor :db_file, :db
|
78
|
+
|
79
|
+
def config
|
80
|
+
if block_given?
|
81
|
+
yield self
|
82
|
+
else
|
83
|
+
raise ArgumentError, 'No block given to config'
|
84
|
+
end
|
85
|
+
|
86
|
+
self.db = Sequel.sqlite(db_file || DEFAULT_DB_FILE) # Use provided db_file or default
|
87
|
+
create_tables unless db.table_exists?(:prompts)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Define the necessary tables within the SQLite database if they don't exist
|
91
|
+
def create_tables
|
92
|
+
db.create_table :prompts do
|
93
|
+
primary_key :id
|
94
|
+
String :prompt_id, unique: true, null: false
|
95
|
+
Text :text
|
96
|
+
Json :parameters
|
97
|
+
|
98
|
+
index :prompt_id
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def initialize
|
104
|
+
@db = self.class.db
|
105
|
+
end
|
106
|
+
|
107
|
+
def get(id:)
|
108
|
+
validate_id(id)
|
109
|
+
result = @db[:prompts].where(prompt_id: id).first
|
110
|
+
raise ArgumentError, 'Prompt not found' unless result
|
111
|
+
|
112
|
+
{
|
113
|
+
id: result[:prompt_id],
|
114
|
+
text: result[:text],
|
115
|
+
parameters: result[:parameters]
|
116
|
+
}
|
117
|
+
end
|
118
|
+
|
119
|
+
def save(id:, text: '', parameters: {})
|
120
|
+
validate_id(id)
|
121
|
+
rec = @db[:prompts].where(prompt_id: id).first
|
122
|
+
if rec
|
123
|
+
@db[:prompts].where(prompt_id: id).update(text: text, parameters: Sequel.pg_json(parameters))
|
124
|
+
else
|
125
|
+
@db[:prompts].insert(prompt_id: id, text: text, parameters: Sequel.pg_json(parameters))
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def delete(id:)
|
130
|
+
validate_id(id)
|
131
|
+
@db[:prompts].where(prompt_id: id).delete
|
132
|
+
end
|
133
|
+
|
134
|
+
# Return an Array of prompt IDs
|
135
|
+
def list()
|
136
|
+
@db[:prompts].select_map(:prompt_id)
|
137
|
+
end
|
138
|
+
|
139
|
+
def search(for_what)
|
140
|
+
search_term = for_what.downcase
|
141
|
+
@db[:prompts].where(Sequel.ilike(:text, "%#{search_term}%")).select_map(:prompt_id)
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
# Validate that the ID contains good characters.
|
147
|
+
def validate_id(id)
|
148
|
+
raise ArgumentError, "Invalid ID format id: #{id}" unless id =~ /^[a-zA-Z0-9\-\/_]+$/
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
|
64
155
|
|
65
156
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: prompt_manager
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dewayne VanHoozer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-11-
|
11
|
+
date: 2023-11-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: amazing_print
|