prompt_manager 0.2.1 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6c96927dad3be1d7e793e3edd5d3df3e3a12999fcec57ad01d3d773d9b40e674
4
- data.tar.gz: 4aa47ce704540fa933dbdf30d3afddbb3704bd541b4dab1f7a751e0e461f3c1e
3
+ metadata.gz: add8f0261dd0fd2e91e8195330d923e8806c2e3a7243cfb51ce70a589fc76ef2
4
+ data.tar.gz: 73b69061afdd242679c231949fba6a3b075f486feb20113cb838d0345dd2ffcf
5
5
  SHA512:
6
- metadata.gz: '0857d6d0532c0c1293799e760933e5bf2ee1a82f8cfdce6dd649b187f724e31935bf959b04c8058a90877525751cf99deaa4fd51a9cc64d0259a3d4b1e40de38'
7
- data.tar.gz: 59d2e1cabed3a87766092f56eae6ed7e7d30a2c0d0b3e3dc2efd93fe9ce79d97ce7ee5af3d1beae6896aed3a1754649dc7a7f3bbdec1e0566015640d8f7f2128
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.2.0 for `FileSystemAdapter` configuration. See [Configuration](#configuration) to see how the new `config` block works.
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
- @prompt
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] = "" unless @parameters.has_key?(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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PromptManager
4
- VERSION = "0.2.1"
4
+ VERSION = "0.3.1"
5
5
  end
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.2.1
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-23 00:00:00.000000000 Z
11
+ date: 2023-11-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: amazing_print