prompt_manager 0.0.1 → 0.1.0

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: 0e3ba20b4c51adf033fc6124346bbb568be751fb3820000773dd51b496c6bc64
4
- data.tar.gz: 950e2dd314ac1eec127c6ea3887ddd727fc55b6b5bb5735cac32c175de4135e5
3
+ metadata.gz: 2772d50f835a71c26631b2109af906008fec84b62e7ccf435a188fb856415268
4
+ data.tar.gz: c1aa3889826246a79df35ad74cd221c1374af161083a34f50d64ef09f5606ade
5
5
  SHA512:
6
- metadata.gz: 1dc004fae9eba3c212014102a89897a0a7301e3157efd6b662e75766e387b057110c58556f918c97dc729edf4968963245d86736a1b8e80795fc7258620eacc2
7
- data.tar.gz: ce46bbfa158650fabfa9741f3b3b9ab42e4acf6022d6dedbd62b377f9659dab72d9ed241ba575c489b6fabab6f550af03a5afa4fc9074303b866c96984f3d769
6
+ metadata.gz: c43cc115f2dfa8e847c499386b482d29f3454ca068b0d161d8c14350002d955461962e1009af0a932a92388eef39b134515bca6f694dabd41110150301339b56
7
+ data.tar.gz: 55b0e4f827d1950f39ad87909954660cb5ae0f6da44a4de0b43aad637dc1d99d878d2cb1876ac4a001cd42c190e1160a76593c6f4cba3b8411c250bd42e97e35
data/CHANGELOG.md CHANGED
@@ -2,4 +2,4 @@
2
2
 
3
3
  ## [0.1.0] - 2023-11-16
4
4
 
5
- - Initial release
5
+ - Initial release using the FileSystemAdapter
data/README.md CHANGED
@@ -1,99 +1,69 @@
1
1
  # PromptManager
2
2
 
3
- **Under Development**
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
- Extracting the prompt management functionality fro the aip.rb file into a new gem that will provide a generic management service for other programs.
6
-
7
- ## AIP.RB Legacy Summary of Capability
8
-
9
- This is just some source material for later documentation.
10
-
11
- ### README for aip.rb
12
-
13
- #### Overview
14
-
15
- The `aip.rb` Ruby script is a command-line interface (CLI) tool designed to leverage generative AI with saved parameterized prompts. It integrates with the `mods` command-line tool that uses a GPT-based model to generate responses based on user-provided prompts. The script offers an array of features that make interacting with AI models more convenient and streamlined.
16
-
17
- #### Features
18
-
19
- - **Prompt Management**
20
- - Users can select prompts from a saved collection with the help of command-line searching and filtering.
21
- - Prompts can be edited by the user to better fit their specific context or requirement.
22
- - Support for reading input from files to provide context for AI generation is included.
23
-
24
- - **AI Integration**
25
- - The script interacts with `mods`, a generative AI utilizing GPT-based models, to produce outputs from the prompts.
26
-
27
- - **Output Handling**
28
- - Generated content is saved to a specified file for record-keeping and further use.
29
-
30
- - **Activity Logging**
31
- - All actions, including prompt usage and AI output, are logged with timestamps for review and auditing purposes.
32
-
33
- #### Dependencies
5
+ ## Installation
34
6
 
35
- The script requires the installation of the following command-line tools:
7
+ Install the gem and add to the application's Gemfile by executing:
36
8
 
37
- - `fzf`: a powerful command-line fuzzy finder.
38
- - `mods`: an AI-powered CLI tool for generative AI interactions.
39
- - `the_silver_searcher (ag)`: a code-searching tool similar to ack and used for searching prompts.
9
+ bundle add prompt_manager
40
10
 
41
- #### Usage
11
+ If bundler is not being used to manage dependencies, install the gem by executing:
42
12
 
43
- The `aip.rb` script offers a set of command-line options to guide the interaction with AI:
13
+ gem install prompt_manager
44
14
 
45
- - `-p, --prompt`: Specify the prompt name to be used.
46
- - `-e, --edit`: Open the prompt text for editing before generation.
47
- - `-f, --fuzzy`: Allows fuzzy matching for prompt selection.
48
- - `-o, --output`: Sets the output file for the generated content.
15
+ ## Usage
49
16
 
50
- Additional flags and options can be passed to the `mods` tool by appending them after a `--` separator.
17
+ See [examples/simple.rb](examples.simple.rb)
51
18
 
52
- #### Installation
19
+ ## Overview
53
20
 
54
- Before using the script, one must ensure the required command-line tools (`fzf`, `mods`, and `the_silver_searcher`) are installed, and the Ruby environment is correctly set up with the necessary gems.
21
+ ### Generative AI (gen-AI)
55
22
 
56
- #### Development Notes
23
+ Gen-AI deals with the conversion (some would say execution) of a human natural language text (the "prompt") into somthing else using what are known as large language models (LLM) such as those available from OpenAI. A parameterized prompt is one in whcih there are embedded keywords (parameters) which are place holders for other text to be inserted into the prompt.
57
24
 
58
- The author suggests that the script has matured enough to be converted into a Ruby gem for easier distribution and installation.
25
+ The prompt_manager uses a regurlar expression to identify these keywords within the prompt. It uses the keywords as keys in a `parameters` Hash which is stored with the prompt text in a serialized form - for example as JSON.
59
26
 
60
- #### Getting Help
27
+ #### What does a keyword look like?
61
28
 
62
- For help with using the CLI tool or further understanding the `mods` command, users can refer to the AI CLI Program help section included in the script or by invoking the `--help` flag.
29
+ The current hard-coded REGEX for a [KEYWORD] identifies any all [UPPERCASE_TEXT] enclosed in squal brackes as a keyword. [KEY WORDS CAN ALSO HAVE SPACES] as well as the underscore character.
63
30
 
64
- #### Conclusion
31
+ This is just the initial convention adopted by prompt_manager. It is intended that this REGEX be configurable so that the promp_manager can be used with other conventions.
65
32
 
66
- The `aip.rb` script is designed to offer a user-friendly and flexible approach to integrating generative AI into content creation processes. It streamlines the interactions and management of AI-generated content by providing prompt management, AI integration, and logging capabilities, packaged inside a simple command-line utility.
33
+ ### Storage Adapters
67
34
 
35
+ A storage adapter is a class instance that ties the `PromptManager::Prompt` class to a storage facility that holds the actual prompts. Currentlyt there are 3 storage adapters planned for implementation.
68
36
 
37
+ #### FileSystemAdapter
69
38
 
39
+ This is the first storage adapter developed. It saves prompts as text files within the file system inside a designated `prompts_dir` (directory) such as `~/,prompts` or where it makes the most sense to you. Another example would be to have your directory on a shared file system so that others can use the same prompts.
70
40
 
41
+ The `promp ID` is the basename of the text file. For example `todo.txt` is the file for the prompt ID `todo` (see the examples directory.)
71
42
 
72
- ## Installation
43
+ The parameters for the `todo` prompt ID are saved in the same directory as `todo.txt` in a JSON file named `todo.json` (also in the examples directory.)
73
44
 
74
- TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
45
+ #### SqliteAdapter
75
46
 
76
- Install the gem and add to the application's Gemfile by executing:
47
+ TODO: This may be the next adapter to be implemented.
77
48
 
78
- $ bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
49
+ #### ActiveRecordAdapter
79
50
 
80
- If bundler is not being used to manage dependencies, install the gem by executing:
51
+ TODO: Still looking for requirements on how to integrate with an existing `rails` app. Looking for ideas.
81
52
 
82
- $ gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
53
+ #### Other Potential Storage Adapters
83
54
 
84
- ## Usage
55
+ There are many possibilities to example this plugin concept of the storage adapter. Here are some for consideration:
85
56
 
86
- TODO: Write usage instructions here
57
+ * RedisAdapter - Not sure; isn't redis more temporary oriented?
58
+ * ApiAdapter - use some end-point to CRUD a prompt
87
59
 
88
60
  ## Development
89
61
 
90
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
91
-
92
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
62
+ Looking for feedback and contributors to enhance the capability of prompt_manager.
93
63
 
94
64
  ## Contributing
95
65
 
96
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/prompt_manager.
66
+ Bug reports and pull requests are welcome on GitHub at https://github.com/MadBomber/prompt_manager.
97
67
 
98
68
  ## License
99
69
 
data/Rakefile CHANGED
@@ -6,7 +6,7 @@ require "rake/testtask"
6
6
  Rake::TestTask.new(:test) do |t|
7
7
  t.libs << "test"
8
8
  t.libs << "lib"
9
- t.test_files = FileList["test/**/test_*.rb"]
9
+ t.test_files = FileList["test/**/*_test.rb"]
10
10
  end
11
11
 
12
12
  task default: :test
@@ -0,0 +1 @@
1
+ {"[LANGUAGE]":"ruby"}
@@ -0,0 +1,7 @@
1
+ # prompts_dir/todo.txt
2
+ # Desc: Let the robot fix the TODO items.
3
+ #
4
+
5
+ As an experienced [LANGUAGE] software engineer write some [LANGUAGE] source code. Consider the following [LANGUAGE] file. For each comment line that contains the word "[KEYWORD_AKA_TODO]" take the text that follows that word as a requirement to be implemented in [LANGUAGE]. Remove the "[KEYWORD_AKA_TODO]" word from the comment line. After the line insert the [LANGUAGE] code that implements the requirement.
6
+
7
+ __END__
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+ # frozen_string_literal: true
4
+ # warn_indent: true
5
+ ##########################################################
6
+ ###
7
+ ## File: simple.rb
8
+ ## Desc: Simple demo of the PromptManager and FileStorageAdapter
9
+ ## By: Dewayne VanHoozer (dvanhoozer@gmail.com)
10
+ ##
11
+ #
12
+
13
+
14
+ require 'prompt_manager'
15
+ require 'prompt_manager/storage/file_system_adapter'
16
+
17
+ require 'amazing_print'
18
+ require 'pathname'
19
+
20
+ HERE = Pathname.new( __dir__ )
21
+ PROMPTS_DIR = HERE + "prompts_dir"
22
+
23
+
24
+ ######################################################
25
+ # Main
26
+
27
+ at_exit do
28
+ puts
29
+ puts "Done."
30
+ puts
31
+ end
32
+
33
+ # Configure the Storage Adapter to use
34
+
35
+ PromptManager::Prompt.storage_adapter =
36
+ PromptManager::Storage::FileSystemAdapter.new(
37
+ prompts_dir: PROMPTS_DIR
38
+ )
39
+
40
+ # Get a prompt
41
+
42
+ todo = PromptManager::Prompt.get(id: 'todo')
43
+
44
+ # This sequence simulates presenting each of the previously
45
+ # used values for each keyword to the user to accept or
46
+ # edit.
47
+
48
+ # ap todo.keywords
49
+
50
+ # This is a new keyword that was added after the current
51
+ # todo.json file was created. Simulate the user setting
52
+ # its value.
53
+
54
+ todo.parameters["[KEYWORD_AKA_TODO]"] = "TODO"
55
+
56
+ # When the parameter values change, the prompt must
57
+ # must be rebuilt using the build method.
58
+
59
+ todo.build
60
+
61
+
62
+ puts <<~EOS
63
+
64
+ Raw Text from Prompt File
65
+ includes all lines
66
+ =========================
67
+ EOS
68
+
69
+ puts todo.text
70
+
71
+
72
+ puts <<~EOS
73
+
74
+ Last Set of Parameters Used
75
+ Includes those recently added
76
+ =============================
77
+ EOS
78
+
79
+ ap todo.parameters
80
+
81
+
82
+ puts <<~EOS
83
+
84
+ Prompt Ready to Send to gen-AI
85
+ ==============================
86
+ EOS
87
+
88
+ puts todo.to_s
89
+
@@ -0,0 +1,151 @@
1
+ # prompt_manager/lib/prompt_manager/prompt.rb
2
+
3
+ # This class is responsible for managing prompts which can be utilized by
4
+ # generative AI processes. This includes creation, retrieval, storage management,
5
+ # as well as building prompts with replacement of parameterized values and
6
+ # comment removal. It communicates with a storage system through a storage
7
+ # adapter.
8
+
9
+ class PromptManager::Prompt
10
+ PARAMETER_REGEX = /(\[[A-Z _|]+\])/ # NOTE: old from aip.rb
11
+ @storage_adapter = nil
12
+
13
+ class << self
14
+ attr_accessor :storage_adapter
15
+
16
+ alias_method :get, :new
17
+
18
+ def create(id:, text: "", parameters: {})
19
+ storage_adapter.save(
20
+ id: id,
21
+ text: text,
22
+ parameters: parameters
23
+ )
24
+
25
+ new(id: id)
26
+ end
27
+
28
+ def search(for_what)
29
+ storage_adapter.search(for_what)
30
+ end
31
+ end
32
+
33
+ # SMELL: Does the db (aka storage adapter) really need
34
+ # to be accessible by the main program?
35
+ attr_accessor :db, :id, :text, :parameters, :keywords
36
+
37
+
38
+ # Retrieve the specific prompt ID from the Storage system.
39
+ def initialize(
40
+ id: nil, # A String name for the prompt
41
+ context: [] # FIXME: Array of Strings or Pathname?
42
+ )
43
+
44
+ @id = id
45
+ @db = self.class.storage_adapter
46
+
47
+ validate_arguments(@id, @db)
48
+
49
+ @record = db.get(id: id)
50
+ @text = @record[:text]
51
+ @parameters = @record[:parameters]
52
+ @keywords = []
53
+
54
+ update_keywords
55
+ build
56
+ end
57
+
58
+
59
+ # Make sure the ID and DB are good-to-go
60
+ def validate_arguments(prompt_id, prompts_db)
61
+ raise ArgumentError, 'id cannot be blank' if prompt_id.nil? || id.strip.empty?
62
+ raise(ArgumentError, 'storage_adapter is not set') if prompts_db.nil?
63
+ end
64
+
65
+
66
+ # Return tje prompt text suitable for passing to a
67
+ # gen-AI process.
68
+ def to_s
69
+ @prompt
70
+ end
71
+
72
+
73
+ # Save the prompt to the Storage system
74
+ def save
75
+ db.save(
76
+ id: id,
77
+ text: text,
78
+ parameters: parameters
79
+ )
80
+ end
81
+
82
+
83
+ # Delete this prompt from the Storage system
84
+ def delete
85
+ db.delete(id: id)
86
+ end
87
+
88
+
89
+ # Build the @prompt String by replacing the keywords
90
+ # with there parameterized values and removing all
91
+ # the comments.
92
+ #
93
+ def build
94
+ @prompt = text.gsub(PARAMETER_REGEX) do |match|
95
+ param_name = match
96
+ parameters[param_name] || match
97
+ end
98
+
99
+ remove_comments
100
+ end
101
+
102
+ ######################################
103
+ private
104
+
105
+ def update_keywords
106
+ @keywords = @text.scan(PARAMETER_REGEX).flatten.uniq
107
+ keywords.each do |kw|
108
+ @parameters[kw] = "" unless @parameters.has_key?(kw)
109
+ end
110
+ end
111
+
112
+
113
+ def remove_comments
114
+ lines = @prompt
115
+ .split("\n")
116
+ .reject{|a_line| a_line.strip.start_with?('#')}
117
+
118
+ # Remove empty lines at the start of the prompt
119
+ #
120
+ lines = lines.drop_while(&:empty?)
121
+
122
+ # Drop all the lines at __END__ and after
123
+ #
124
+ logical_end_inx = lines.index("__END__")
125
+
126
+ @prompt = if logical_end_inx
127
+ lines[0...logical_end_inx] # NOTE: ... means to not include last index
128
+ else
129
+ lines
130
+ end.join("\n")
131
+ end
132
+
133
+
134
+ # Handle storage errors
135
+ # SMELL: Just raise them or get out of their way and let the
136
+ # main program do tje job.
137
+ def handle_storage_error(error)
138
+ # Log the error message, notify, or take appropriate action
139
+ log_error("Storage operation failed: #{error.message}")
140
+ # Re-raise the error if necessary, or define recovery steps
141
+ raise error
142
+ end
143
+
144
+
145
+ # SMELL: should this gem log errors or is that a function of
146
+ # main program? I believe its the main program's job.
147
+ def log_error(message)
148
+ puts "ERROR: #{message}"
149
+ end
150
+ end
151
+
@@ -0,0 +1,66 @@
1
+ # prompt_manager/lib/prompt_manager/storage/active_record_adapter.rb
2
+
3
+ require 'active_record'
4
+
5
+ # TODO: Will need a database.yml file
6
+ # will need to know the column names that coorespond
7
+ # with the things that the Prompt class wants.
8
+
9
+ class PromptManager::Storage::ActiveRecordAdapter
10
+ attr_reader :model_class
11
+
12
+ def initialize(model_class)
13
+ @model_class = model_class
14
+ end
15
+
16
+
17
+ def prompt_text(prompt_id)
18
+ prompt = find_prompt(prompt_id)
19
+ prompt.text
20
+ end
21
+
22
+
23
+ def parameter_values(prompt_id)
24
+ prompt = find_prompt(prompt_id)
25
+ JSON.parse(prompt.params, symbolize_names: true)
26
+ end
27
+
28
+
29
+ def save(prompt_id, prompt_text, parameter_values)
30
+ prompt = model_class.find_or_initialize_by(id: prompt_id)
31
+ prompt.text = prompt_text
32
+ prompt.params = parameter_values.to_json
33
+ prompt.save!
34
+ end
35
+
36
+
37
+ def delete(prompt_id)
38
+ prompt = find_prompt(prompt_id)
39
+ prompt.destroy
40
+ end
41
+
42
+
43
+ def search(for_what)
44
+ # TODO: search through all prompts. Return an Array of
45
+ # prompt_id where the text of the prompt contains
46
+ # for_what is being searched.
47
+
48
+ []
49
+ end
50
+
51
+
52
+ class << self
53
+ def config
54
+ # TODO: establish a connection to the database
55
+ # maybe define the prompts table and its
56
+ # columns of interest.
57
+ end
58
+ end
59
+
60
+ ###############################################
61
+ private
62
+
63
+ def find_prompt(prompt_id)
64
+ model_class.find_by(id: prompt_id) || raise('Prompt not found')
65
+ end
66
+ end
@@ -0,0 +1,149 @@
1
+ # prompt_manager/lib/prompt_manager/storage/file_system_adapter.rb
2
+
3
+
4
+ require 'json'
5
+
6
+ class PromptManager::Storage::FileSystemAdapter
7
+ PARAMS_EXTENSION = '.json'.freeze
8
+ PROMPT_EXTENSION = '.txt'.freeze
9
+
10
+ attr_reader :prompts_dir
11
+
12
+ def initialize(
13
+ prompts_dir: '.prompts',
14
+ search_proc: nil # Example: ->(q) {`ag -l #{q}`}
15
+ )
16
+
17
+ # validate that prompts_dir exist and is in fact a directory.
18
+ unless Dir.exist?(prompts_dir)
19
+ raise "Directory #{prompts_dir} does not exist or is not a directory"
20
+ end
21
+
22
+ @prompts_dir = prompts_dir
23
+ @search_proc = search_proc
24
+ end
25
+
26
+
27
+ def get(id:)
28
+ validate_id(id)
29
+
30
+ {
31
+ id: id,
32
+ text: prompt_text(id),
33
+ parameters: parameter_values(id)
34
+ }
35
+ end
36
+
37
+
38
+ # Retrieve prompt text by its id
39
+ def prompt_text(prompt_id)
40
+ read_file(file_path(prompt_id, PROMPT_EXTENSION))
41
+ end
42
+
43
+
44
+ # Retrieve parameter values by its id
45
+ def parameter_values(prompt_id)
46
+ json_content = read_file(file_path(prompt_id, PARAMS_EXTENSION))
47
+ deserialize(json_content)
48
+ end
49
+
50
+
51
+ # Save prompt text and parameter values to corresponding files
52
+ def save(
53
+ id:,
54
+ text: "",
55
+ parameters: {}
56
+ )
57
+ validate_id(id)
58
+
59
+ prompt_filepath = file_path(id, PROMPT_EXTENSION)
60
+ params_filepath = file_path(id, PARAMS_EXTENSION)
61
+
62
+ write_with_error_handling(prompt_filepath, text)
63
+ write_with_error_handling(params_filepath, serialize(parameters))
64
+ end
65
+
66
+
67
+ # Delete prompted text and parameter values files
68
+ def delete(id:)
69
+ validate_id(id)
70
+
71
+ prompt_filepath = file_path(id, PROMPT_EXTENSION)
72
+ params_filepath = file_path(id, PARAMS_EXTENSION)
73
+
74
+ delete_with_error_handling(prompt_filepath)
75
+ delete_with_error_handling(params_filepath)
76
+ end
77
+
78
+
79
+ def search(for_what)
80
+ if @search_proc
81
+ @search_proc.call(for_what)
82
+ else
83
+ search_prompts(for_what)
84
+ end
85
+ end
86
+
87
+
88
+ ##########################################
89
+ private
90
+
91
+ def validate_id(id)
92
+ raise ArgumentError, 'Invalid ID format' unless id =~ /^[a-zA-Z0-9\-_]+$/
93
+ end
94
+
95
+
96
+ def write_with_error_handling(file_path, content)
97
+ begin
98
+ File.write(file_path, content)
99
+ rescue IOError => e
100
+ raise "Failed to write to file: #{e.message}"
101
+ end
102
+ end
103
+
104
+
105
+ def delete_with_error_handling(file_path)
106
+ begin
107
+ FileUtils.rm_f(file_path)
108
+ rescue IOError => e
109
+ raise "Failed to delete file: #{e.message}"
110
+ end
111
+ end
112
+
113
+
114
+ def file_path(id, extension)
115
+ File.join(@prompts_dir, "#{id}#{extension}")
116
+ end
117
+
118
+
119
+ def read_file(full_path)
120
+ raise IOError, 'File does not exist' unless File.exist?(full_path)
121
+ File.read(full_path)
122
+ end
123
+
124
+
125
+ def search_prompts(search_term)
126
+ query_term = search_term.downcase
127
+
128
+ Dir.glob(File.join(@prompts_dir, "*#{PROMPT_EXTENSION}")).each_with_object([]) do |file_path, ids|
129
+ File.open(file_path) do |file|
130
+ file.each_line do |line|
131
+ if line.downcase.include?(query_term)
132
+ ids << File.basename(file_path, PROMPT_EXTENSION)
133
+ next
134
+ end
135
+ end
136
+ end
137
+ end.uniq
138
+ end
139
+
140
+
141
+ def serialize(data)
142
+ data.to_json
143
+ end
144
+
145
+
146
+ def deserialize(data)
147
+ JSON.parse(data)
148
+ end
149
+ end
@@ -0,0 +1,65 @@
1
+ # prompt_manager/lib/prompt_manager/storage/sqlite_adapter.rb
2
+
3
+ require 'json'
4
+ require 'sqlite3'
5
+
6
+ class PromptManager::Storage::SqliteAdapter
7
+ def initialize(db_path = 'prompts.db')
8
+ @db = SQLite3::Database.new(db_path)
9
+ create_tables
10
+ end
11
+
12
+ def save_prompt(prompt_id, text, params)
13
+ @db.execute("INSERT INTO prompts (id, text, params) VALUES (?, ?, ?)",
14
+ [prompt_id, text, params.to_json])
15
+ end
16
+
17
+ def prompt_text(prompt_id)
18
+ result = @db.get_first_value("SELECT text FROM prompts WHERE id = ?", [prompt_id])
19
+ raise 'Prompt not found' if result.nil?
20
+ result
21
+ end
22
+
23
+ def parameter_values(prompt_id)
24
+ json_content = @db.get_first_value("SELECT params FROM prompts WHERE id = ?", [prompt_id])
25
+ raise 'Parameters not found' if json_content.nil?
26
+ JSON.parse(json_content, symbolize_names: true)
27
+ end
28
+
29
+ def save(prompt_id, prompt_text, parameter_values)
30
+ @db.execute(
31
+ 'REPLACE INTO prompts (id, text, params) VALUES (?, ?, ?)',
32
+ [prompt_id, prompt_text, parameter_values.to_json]
33
+ )
34
+ end
35
+
36
+ def delete(prompt_id)
37
+ @db.execute('DELETE FROM prompts WHERE id = ?', [prompt_id])
38
+ end
39
+
40
+
41
+ def search(for_what)
42
+ # TODO: search through all prompts. Return an Array of
43
+ # prompt_id where the text of the prompt contains
44
+ # for_what is being searched.
45
+
46
+ []
47
+ end
48
+
49
+ ###################################################
50
+ private
51
+
52
+ def create_tables
53
+ @db.execute <<-SQL
54
+ CREATE TABLE IF NOT EXISTS prompts (
55
+ id TEXT PRIMARY KEY,
56
+ text TEXT NOT NULL,
57
+ params TEXT NOT NULL
58
+ );
59
+ SQL
60
+ end
61
+ end
62
+
63
+
64
+
65
+
@@ -0,0 +1,7 @@
1
+ # prompt_manager/lib/prompt_manager/storage.rb
2
+
3
+ module PromptManager
4
+ module Storage
5
+ end
6
+ end
7
+
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PromptManager
4
- VERSION = "0.0.1"
4
+ VERSION = "0.1.0"
5
5
  end
@@ -1,8 +1,12 @@
1
+ # prompt_manager/lib/prompt_manager.rb
2
+ #
1
3
  # frozen_string_literal: true
2
4
 
3
5
  require_relative "prompt_manager/version"
6
+ require_relative "prompt_manager/storage"
7
+ require_relative "prompt_manager/prompt"
4
8
 
5
9
  module PromptManager
6
10
  class Error < StandardError; end
7
- # Your code goes here...
11
+ # TODO: Add some more module specific errors here
8
12
  end
metadata CHANGED
@@ -1,16 +1,60 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prompt_manager
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
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-16 00:00:00.000000000 Z
12
- dependencies: []
13
- description: Manage parameterized prompt text for use with GPT/LLM
11
+ date: 2023-11-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: amazing_print
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: fakefs
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: "Manage the parameterized prompts (text) used in generative AI (aka chatGPT,
56
+ \npen AI, et.al.) using storage adapters such as FileSystemAdapter, \nSqliteAdapter
57
+ and ActiveRecordAdapter.\n"
14
58
  email:
15
59
  - dvanhoozer@gmail.com
16
60
  executables: []
@@ -23,10 +67,16 @@ files:
23
67
  - LICENSE.txt
24
68
  - README.md
25
69
  - Rakefile
26
- - lib/aip.rb
70
+ - examples/prompts_dir/todo.json
71
+ - examples/prompts_dir/todo.txt
72
+ - examples/simple.rb
27
73
  - lib/prompt_manager.rb
74
+ - lib/prompt_manager/prompt.rb
75
+ - lib/prompt_manager/storage.rb
76
+ - lib/prompt_manager/storage/active_record_adapter.rb
77
+ - lib/prompt_manager/storage/file_system_adapter.rb
78
+ - lib/prompt_manager/storage/sqlite_adapter.rb
28
79
  - lib/prompt_manager/version.rb
29
- - lib/temp.md
30
80
  homepage: https://github.com/MadBomber/prompt_manager
31
81
  licenses:
32
82
  - MIT
@@ -50,8 +100,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
50
100
  - !ruby/object:Gem::Version
51
101
  version: '0'
52
102
  requirements: []
53
- rubygems_version: 3.5.0.dev
103
+ rubygems_version: 3.4.22
54
104
  signing_key:
55
105
  specification_version: 4
56
- summary: Manage prompts for use with chatGPT LLMs
106
+ summary: Manage prompts for use with gen-AI processes
57
107
  test_files: []
data/lib/aip.rb DELETED
@@ -1,402 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # encoding: utf-8
3
- # frozen_string_literal: true
4
- # warn_indent: true
5
- ##########################################################
6
- ###
7
- ## File: aip.rb
8
- ## Desc: Use generative AI with saved parameterized prompts
9
- ## By: Dewayne VanHoozer (dvanhoozer@gmail.com)
10
- ##
11
- ## This program makes use of the gem word_wrap's
12
- ## CLI tool: ww
13
- #
14
-
15
-
16
- =begin
17
-
18
- brew install fzf mods the_silver_searcher
19
-
20
- fzf Command-line fuzzy finder written in Go
21
- |__ https://github.com/junegunn/fzf
22
-
23
- mods AI on the command-line
24
- |__ https://github.com/charmbracelet/mods
25
-
26
- the_silver_searcher Code-search similar to ack
27
- |__ https://github.com/ggreer/the_silver_searcher
28
-
29
- Program Summary
30
-
31
- The program is a Ruby script that integrates with the `mods` CLI tool, which is built on a GPT-based generative AI model. This script is designed to make use of generative AI through a set of saved, parameterized prompts. Users can easily interact with the following features:
32
-
33
- - **Prompt Selection**: Users have the ability to choose a prompt from a curated list. This selection process is streamlined by allowing users to search and filter prompts using keywords.
34
-
35
- - **Prompt Editing**: There is functionality for a user to modify the text of an existing prompt, tailoring it to better meet their specific needs.
36
-
37
- - **File Input**: The script can read in data from input files, providing the necessary context or information required for the AI to generate relevant content.
38
-
39
- - **AI Integration**: Utilizing the `mods` GPT-based CLI tool, the script takes the chosen edited prompt to guide the AI in generating its output.
40
-
41
- - **Output Management**: After the generative process, the resulting output is saved to a designated file, ensuring that the user has a record of the AI's creations.
42
-
43
- - **Logging**: For tracking and accountability, the program records the details of each session, including the prompt used, the AI-generated output, and the precise timestamp when the generation occurred.
44
-
45
- This robust tool is excellent for users who wish to harness the power of generative AI for creating content, with an efficient and user-friendly system for managing the creation process.
46
-
47
- =end
48
-
49
- #
50
- # TODO: I think this script has reached the point where
51
- # it is ready to become a proper gem.
52
- #
53
-
54
- require 'pathname'
55
- HOME = Pathname.new( ENV['HOME'] )
56
-
57
-
58
- MODS_MODEL = ENV['MODS_MODEL'] || 'gpt-4-1106-preview'
59
-
60
- AI_CLI_PROGRAM = "mods"
61
- ai_default_opts = "-m #{MODS_MODEL} --no-limit -f"
62
- ai_options = ai_default_opts.dup
63
-
64
- extra_inx = ARGV.index('--')
65
-
66
- if extra_inx
67
- ai_options += " " + ARGV[extra_inx+1..].join(' ')
68
- ARGV.pop(ARGV.size - extra_inx)
69
- end
70
-
71
- AI_COMMAND = "#{AI_CLI_PROGRAM} #{ai_options} "
72
- EDITOR = ENV['EDITOR']
73
- PROMPT_DIR = HOME + ".prompts"
74
- PROMPT_LOG = PROMPT_DIR + "_prompts.log"
75
- PROMPT_EXTNAME = ".txt"
76
- DEFAULTS_EXTNAME = ".json"
77
- # SEARCH_COMMAND = "ag -l"
78
- KEYWORD_REGEX = /(\[[A-Z _|]+\])/
79
-
80
- AVAILABLE_PROMPTS = PROMPT_DIR
81
- .children
82
- .select{|c| PROMPT_EXTNAME == c.extname}
83
- .map{|c| c.basename.to_s.split('.')[0]}
84
-
85
- AVAILABLE_PROMPTS_HELP = AVAILABLE_PROMPTS
86
- .map{|c| " * " + c}
87
- .join("\n")
88
-
89
- require 'amazing_print'
90
- require 'json'
91
- require 'readline' # TODO: or reline ??
92
- require 'word_wrap'
93
- require 'word_wrap/core_ext'
94
-
95
-
96
- require 'debug_me'
97
- include DebugMe
98
-
99
- require 'cli_helper'
100
- include CliHelper
101
-
102
- configatron.version = '1.1.0'
103
-
104
- AI_CLI_PROGRAM_HELP = `#{AI_CLI_PROGRAM} --help`
105
-
106
- HELP = <<EOHELP
107
- AI CLI Program
108
- ==============
109
-
110
- The AI cli program being used is: #{AI_CLI_PROGRAM}
111
-
112
- The defaul options to #{AI_CLI_PROGRAM} are:
113
- "#{ai_default_opts}"
114
-
115
- You can pass additional CLI options to #{AI_CLI_PROGRAM} like this:
116
- "#{my_name} my options -- options for #{AI_CLI_PROGRAM}"
117
-
118
- #{AI_CLI_PROGRAM_HELP}
119
-
120
- EOHELP
121
-
122
- cli_helper("Use generative AI with saved parameterized prompts") do |o|
123
-
124
- o.string '-p', '--prompt', 'The prompt name', default: ""
125
- o.bool '-e', '--edit', 'Edit the prompt text', default: false
126
- o.bool '-f', '--fuzzy', 'Allow fuzzy matching', default: false
127
- o.path '-o', '--output', 'The output file', default: Pathname.pwd + "temp.md"
128
- end
129
-
130
-
131
- AG_COMMAND = "ag --file-search-regex '\.txt$' e"
132
- CD_COMMAND = "cd #{PROMPT_DIR}"
133
- FIND_COMMAND = "find . -name '*.txt'"
134
-
135
- FZF_OPTIONS = [
136
- "--tabstop=2", # 2 soaces for a tab
137
- "--header='Prompt contents below'",
138
- "--header-first",
139
- "--prompt='Search term: '",
140
- '--delimiter :',
141
- "--preview 'ww {1}'", # ww comes from the word_wrap gem
142
- "--preview-window=down:50%:wrap"
143
- ].join(' ')
144
-
145
- FZF_OPTIONS += "--exact" unless fuzzy?
146
-
147
- FZF_COMMAND = "#{CD_COMMAND} ; #{FIND_COMMAND} | fzf #{FZF_OPTIONS}"
148
- AG_FZF_COMMAND = "#{CD_COMMAND} ; #{AG_COMMAND} | fzf #{FZF_OPTIONS}"
149
-
150
- # use `ag` ti build a list of text lines from each prompt
151
- # use `fzf` to search through that list to select a prompt file
152
-
153
- def ag_fzf = `#{AG_FZF_COMMAND}`.split(':')&.first&.strip&.gsub('.txt','')
154
-
155
-
156
- configatron.input_files = get_pathnames_from( configatron.arguments, %w[.rb .txt .md])
157
-
158
-
159
- # TODO: Make the use of the "-p" flag optional.
160
- # I find myself many times forgetting to use it
161
- # and this program rejecting it because
162
- # "the file does not exist" thinging that it
163
- # was an input file file rather than a prompt
164
- # name.
165
-
166
- if configatron.prompt.empty?
167
- configatron.prompt = ag_fzf
168
- end
169
-
170
- unless edit?
171
- if configatron.prompt.nil? || configatron.prompt.empty?
172
- error "No prompt provided"
173
- end
174
- end
175
-
176
- abort_if_errors
177
-
178
- configatron.prompt_path = PROMPT_DIR + (configatron.prompt + PROMPT_EXTNAME)
179
- configatron.defaults_path = PROMPT_DIR + (configatron.prompt + DEFAULTS_EXTNAME)
180
-
181
- if !configatron.prompt_path.exist? && !edit?
182
- error "This prompt does not exist: #{configatron.prompt}\n"
183
- end
184
-
185
- configatron.prompt_path = PROMPT_DIR + (configatron.prompt + PROMPT_EXTNAME)
186
- configatron.defaults_path = PROMPT_DIR + (configatron.prompt + DEFAULTS_EXTNAME)
187
-
188
- abort_if_errors
189
-
190
- if edit?
191
- unless configatron.prompt_path.exist?
192
- configatron.prompt_path.write <<~PROMPT
193
- # #{configatron.prompt_path.relative_path_from(HOME)}
194
- # DESC:
195
-
196
- PROMPT
197
- end
198
-
199
- `#{EDITOR} #{configatron.prompt_path}`
200
- end
201
-
202
- ######################################################
203
- # Local methods
204
-
205
- def extract_raw_prompt
206
- array_of_strings = ignore_after_end
207
- print_header_comment(array_of_strings)
208
-
209
- array_of_strings.reject do |a_line|
210
- a_line.chomp.strip.start_with?('#')
211
- end
212
- .join("\n")
213
- end
214
-
215
-
216
- def ignore_after_end
217
- array_of_strings = configatron.prompt_path.readlines
218
- .map{|a_line| a_line.chomp.strip}
219
-
220
- x = array_of_strings.index("__END__")
221
-
222
- unless x.nil?
223
- array_of_strings = array_of_strings[..x-1]
224
- end
225
-
226
- array_of_strings
227
- end
228
-
229
-
230
- def print_header_comment(array_of_strings)
231
- print "\n\n" if verbose?
232
-
233
- x = 0
234
-
235
- while array_of_strings[x].start_with?('#') do
236
- puts array_of_strings[x]
237
- x += 1
238
- end
239
-
240
- print "\n\n" if x>0 && verbose?
241
- end
242
-
243
-
244
- # Returns an Array of keywords or phrases that look like:
245
- # [KEYWORD]
246
- # [KEYWORD|KEYWORD]
247
- # [KEY PHRASE]
248
- # [KEY PHRASE | KEY PHRASE | KEY_WORD]
249
- #
250
- def extract_keywords_from(prompt_raw)
251
- prompt_raw.scan(KEYWORD_REGEX).flatten.uniq
252
- end
253
-
254
- # get the replacements for the keywords
255
- def replacements_for(keywords)
256
- replacements = load_default_replacements
257
-
258
- keywords.each do |kw|
259
- default = replacements[kw]
260
- print "#{kw} (#{default}) ? "
261
- a_string = Readline.readline("\n> ", false)
262
- replacements[kw] = a_string unless a_string.empty?
263
- end
264
-
265
- save_default_replacements(replacements)
266
-
267
- replacements
268
- end
269
-
270
-
271
- def load_default_replacements
272
- if configatron.defaults_path.exist?
273
- JSON.parse(configatron.defaults_path.read)
274
- else
275
- {}
276
- end
277
- end
278
-
279
-
280
- def save_default_replacements(a_hash)
281
- return if a_hash.empty?
282
- defaults = a_hash.to_json
283
- configatron.defaults_path.write defaults
284
- end
285
-
286
- # substitute the replacements for the keywords
287
- def replace_keywords_with replacements, prompt_raw
288
- prompt = prompt_raw.dup
289
-
290
- replacements.each_pair do |keyword, replacement|
291
- prompt.gsub!(keyword, replacement)
292
- end
293
-
294
- prompt
295
- end
296
-
297
-
298
- def log(prompt_path, prompt_raw, answer)
299
- f = File.open(PROMPT_LOG, "ab")
300
-
301
- f.write <<~EOS
302
- =======================================
303
- == #{Time.now}
304
- == #{prompt_path}
305
-
306
- PROMPT: #{prompt_raw}
307
-
308
- RESULT:
309
- #{answer}
310
-
311
- EOS
312
- end
313
-
314
-
315
- ######################################################
316
- # Main
317
-
318
- at_exit do
319
- puts
320
- puts "Done."
321
- puts
322
- end
323
-
324
- ap configatron.to_h if debug?
325
-
326
- configatron.prompt_raw = extract_raw_prompt
327
-
328
- puts
329
- puts "PROMPT:"
330
- puts configatron.prompt_raw.wrap
331
- puts
332
-
333
-
334
- keywords = extract_keywords_from configatron.prompt_raw
335
- replacements = replacements_for keywords
336
-
337
- prompt = replace_keywords_with replacements, configatron.prompt_raw
338
- ptompt = %Q{prompt}
339
-
340
- command = AI_COMMAND + '"' + prompt + '"'
341
-
342
- configatron.input_files.each do |input_file|
343
- command += " < #{input_file}"
344
- end
345
-
346
-
347
- print "\n\n" if verbose? && !keywords.empty?
348
-
349
- if verbose?
350
- puts "="*42
351
- puts command
352
- puts "="*42
353
- print "\n\n"
354
- end
355
-
356
- result = `#{command}`
357
-
358
- configatron.output.write result
359
-
360
- log configatron.prompt_path, prompt, result
361
-
362
-
363
- __END__
364
-
365
- To specify a history and autocomplete options with the readline method in Ruby using the readline gem, you can follow these steps:
366
-
367
- 1. **History** - To enable history functionality, create a Readline::HISTORY object:
368
- ```ruby
369
- history = Readline::HISTORY
370
- ```
371
- You can then use the `history` object to add and manipulate history entries.
372
-
373
- 2. **Autocomplete** - To enable autocomplete functionality, you need to provide a completion proc to `Readline.completion_proc`:
374
- ```ruby
375
- Readline.completion_proc = proc { |input| ... }
376
- ```
377
- You should replace `...` with the logic for determining the autocomplete options based on the input.
378
-
379
- For example, you can define a method that provides autocomplete options based on a predefined array:
380
- ```ruby
381
- def autocomplete_options(input)
382
- available_options = ['apple', 'banana', 'cherry']
383
- available_options.grep(/^#{Regexp.escape(input)}/)
384
- end
385
-
386
- Readline.completion_proc = proc { |input| autocomplete_options(input) }
387
- ```
388
-
389
- In this example, the `autocomplete_options` method takes the user's input and uses the `grep` method to filter the available options based on the input prefix.
390
-
391
- Remember to require the readline gem before using these features:
392
- ```ruby
393
- require 'readline'
394
- ```
395
-
396
- With the above steps in place, you can use the readline method in your code, and the specified history and autocomplete options will be available.
397
-
398
- Note: Keep in mind that autocomplete options will only appear when tab is pressed while entering input.
399
-
400
-
401
-
402
-
data/lib/temp.md DELETED
@@ -1,57 +0,0 @@
1
- ### README for aip.rb Ruby Script
2
-
3
- #### Overview
4
-
5
- The `aip.rb` Ruby script is a command-line interface (CLI) tool designed to leverage generative AI with saved parameterized prompts. It integrates with the `mods` command-line tool that uses a GPT-based model to generate responses based on user-provided prompts. The script offers an array of features that make interacting with AI models more convenient and streamlined.
6
-
7
- #### Features
8
-
9
- - **Prompt Management**
10
- - Users can select prompts from a saved collection with the help of command-line searching and filtering.
11
- - Prompts can be edited by the user to better fit their specific context or requirement.
12
- - Support for reading input from files to provide context for AI generation is included.
13
-
14
- - **AI Integration**
15
- - The script interacts with `mods`, a generative AI utilizing GPT-based models, to produce outputs from the prompts.
16
-
17
- - **Output Handling**
18
- - Generated content is saved to a specified file for record-keeping and further use.
19
-
20
- - **Activity Logging**
21
- - All actions, including prompt usage and AI output, are logged with timestamps for review and auditing purposes.
22
-
23
- #### Dependencies
24
-
25
- The script requires the installation of the following command-line tools:
26
-
27
- - `fzf`: a powerful command-line fuzzy finder.
28
- - `mods`: an AI-powered CLI tool for generative AI interactions.
29
- - `the_silver_searcher (ag)`: a code-searching tool similar to ack and used for searching prompts.
30
-
31
- #### Usage
32
-
33
- The `aip.rb` script offers a set of command-line options to guide the interaction with AI:
34
-
35
- - `-p, --prompt`: Specify the prompt name to be used.
36
- - `-e, --edit`: Open the prompt text for editing before generation.
37
- - `-f, --fuzzy`: Allows fuzzy matching for prompt selection.
38
- - `-o, --output`: Sets the output file for the generated content.
39
-
40
- Additional flags and options can be passed to the `mods` tool by appending them after a `--` separator.
41
-
42
- #### Installation
43
-
44
- Before using the script, one must ensure the required command-line tools (`fzf`, `mods`, and `the_silver_searcher`) are installed, and the Ruby environment is correctly set up with the necessary gems.
45
-
46
- #### Development Notes
47
-
48
- The author suggests that the script has matured enough to be converted into a Ruby gem for easier distribution and installation.
49
-
50
- #### Getting Help
51
-
52
- For help with using the CLI tool or further understanding the `mods` command, users can refer to the AI CLI Program help section included in the script or by invoking the `--help` flag.
53
-
54
- #### Conclusion
55
-
56
- The `aip.rb` script is designed to offer a user-friendly and flexible approach to integrating generative AI into content creation processes. It streamlines the interactions and management of AI-generated content by providing prompt management, AI integration, and logging capabilities, packaged inside a simple command-line utility.
57
-