prompt_manager 0.0.2 → 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: 74f0d17c5e89ae990b91f2cc8632751ecb1664f1dfc248de9967828f088101b3
4
- data.tar.gz: 47e60cd381bf459a34b1a6a6350cd0ca600125677535321d36e2d6d8395fcd2c
3
+ metadata.gz: 2772d50f835a71c26631b2109af906008fec84b62e7ccf435a188fb856415268
4
+ data.tar.gz: c1aa3889826246a79df35ad74cd221c1374af161083a34f50d64ef09f5606ade
5
5
  SHA512:
6
- metadata.gz: e5033e585f72c925f0ca3461288d7429362a184567eb6713676ba213ac91e6dcced8ae48475346bf0a2b411890b143b7ad22c35ca4e8f3be0f4f4a08b9685db0
7
- data.tar.gz: 72b4b277108805d56c7322c862eba84eff11379ba7c3efb116b9276de96ab8de77726c67b102a008bac5046fa697854ba3274bb6d0d15782d1474ae07d6a4945
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,101 +1,69 @@
1
1
  # PromptManager
2
2
 
3
- **Under Development** Not ready for use.
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
- I'm looking for some contributors to help define the API between the Prompt class and the Storage adapters. I'm focusing on the FileSystemAdapter since the majority of my work is on the command line.
6
-
7
- Extracting the prompt management functionality fro the aip.rb file into a new gem that will provide a generic management service for other programs.
8
-
9
- ## AIP.RB Legacy Summary of Capability
10
-
11
- This is just some source material for later documentation.
12
-
13
- ### README for aip.rb
14
-
15
- #### Overview
16
-
17
- 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.
18
-
19
- #### Features
20
-
21
- - **Prompt Management**
22
- - Users can select prompts from a saved collection with the help of command-line searching and filtering.
23
- - Prompts can be edited by the user to better fit their specific context or requirement.
24
- - Support for reading input from files to provide context for AI generation is included.
25
-
26
- - **AI Integration**
27
- - The script interacts with `mods`, a generative AI utilizing GPT-based models, to produce outputs from the prompts.
28
-
29
- - **Output Handling**
30
- - Generated content is saved to a specified file for record-keeping and further use.
31
-
32
- - **Activity Logging**
33
- - All actions, including prompt usage and AI output, are logged with timestamps for review and auditing purposes.
34
-
35
- #### Dependencies
5
+ ## Installation
36
6
 
37
- The script requires the installation of the following command-line tools:
7
+ Install the gem and add to the application's Gemfile by executing:
38
8
 
39
- - `fzf`: a powerful command-line fuzzy finder.
40
- - `mods`: an AI-powered CLI tool for generative AI interactions.
41
- - `the_silver_searcher (ag)`: a code-searching tool similar to ack and used for searching prompts.
9
+ bundle add prompt_manager
42
10
 
43
- #### Usage
11
+ If bundler is not being used to manage dependencies, install the gem by executing:
44
12
 
45
- The `aip.rb` script offers a set of command-line options to guide the interaction with AI:
13
+ gem install prompt_manager
46
14
 
47
- - `-p, --prompt`: Specify the prompt name to be used.
48
- - `-e, --edit`: Open the prompt text for editing before generation.
49
- - `-f, --fuzzy`: Allows fuzzy matching for prompt selection.
50
- - `-o, --output`: Sets the output file for the generated content.
15
+ ## Usage
51
16
 
52
- 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)
53
18
 
54
- #### Installation
19
+ ## Overview
55
20
 
56
- 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)
57
22
 
58
- #### 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.
59
24
 
60
- 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.
61
26
 
62
- #### Getting Help
27
+ #### What does a keyword look like?
63
28
 
64
- 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.
65
30
 
66
- #### 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.
67
32
 
68
- 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
69
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.
70
36
 
37
+ #### FileSystemAdapter
71
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.
72
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.)
73
42
 
74
- ## 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.)
75
44
 
76
- 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
77
46
 
78
- Install the gem and add to the application's Gemfile by executing:
47
+ TODO: This may be the next adapter to be implemented.
79
48
 
80
- $ bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
49
+ #### ActiveRecordAdapter
81
50
 
82
- 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.
83
52
 
84
- $ gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
53
+ #### Other Potential Storage Adapters
85
54
 
86
- ## Usage
55
+ There are many possibilities to example this plugin concept of the storage adapter. Here are some for consideration:
87
56
 
88
- 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
89
59
 
90
60
  ## Development
91
61
 
92
- 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.
93
-
94
- 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.
95
63
 
96
64
  ## Contributing
97
65
 
98
- 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.
99
67
 
100
68
  ## License
101
69
 
@@ -1 +1 @@
1
- {"language":"ruby"}
1
+ {"[LANGUAGE]":"ruby"}
@@ -1,8 +1,7 @@
1
- # .prompts/todo.txt
2
- # Desc: replace NotImpleted.needs_to "prompt_string" with the
3
- # results of sending the prompt_string to an LLM
1
+ # prompts_dir/todo.txt
2
+ # Desc: Let the robot fix the TODO items.
4
3
  #
5
4
 
6
- As an experienced [LANGUAGE] software engineer write some [LANGUAGE] source code. Consider the following [LANGUAGE] file. For each comment line that contains the word "todo" take the text that follows that word as a requirement to be implemented in [LANGUAGE]. Remove the 'todo' word from the comment line. After the line insert the [LANGUAGE] code that implements the requirement.
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.
7
6
 
8
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
+
@@ -1,10 +1,13 @@
1
1
  # prompt_manager/lib/prompt_manager/prompt.rb
2
2
 
3
- # TODO: Consider an ActiveModel ??
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.
4
8
 
5
9
  class PromptManager::Prompt
6
- PARAMETER_REGEX = /\[([A-Z _]+)\]/.freeze
7
- # KEYWORD_REGEX = /(\[[A-Z _|]+\])/ # NOTE: old from aip.rb
10
+ PARAMETER_REGEX = /(\[[A-Z _|]+\])/ # NOTE: old from aip.rb
8
11
  @storage_adapter = nil
9
12
 
10
13
  class << self
@@ -27,34 +30,47 @@ class PromptManager::Prompt
27
30
  end
28
31
  end
29
32
 
30
- attr_accessor :db, :id, :text, :parameters
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
31
36
 
32
- # FIXME: Assumes that the prompt ID exists in storage,
33
- # wo how do we create a new one?
37
+
38
+ # Retrieve the specific prompt ID from the Storage system.
34
39
  def initialize(
35
40
  id: nil, # A String name for the prompt
36
41
  context: [] # FIXME: Array of Strings or Pathname?
37
42
  )
38
43
 
39
- raise ArgumentError, 'id cannot be blank' if id.nil? || id.strip.empty?
40
-
41
44
  @id = id
42
45
  @db = self.class.storage_adapter
43
46
 
44
- raise(ArgumentError, 'storage_adapter is not set') if db.nil?
45
-
47
+ validate_arguments(@id, @db)
48
+
46
49
  @record = db.get(id: id)
47
50
  @text = @record[:text]
48
51
  @parameters = @record[:parameters]
52
+ @keywords = []
53
+
54
+ update_keywords
55
+ build
56
+ end
57
+
49
58
 
50
- @prompt = interpolate_parameters
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?
51
63
  end
52
64
 
53
- # Displays the prompt text after parameter interpolation.
65
+
66
+ # Return tje prompt text suitable for passing to a
67
+ # gen-AI process.
54
68
  def to_s
55
69
  @prompt
56
70
  end
57
71
 
72
+
73
+ # Save the prompt to the Storage system
58
74
  def save
59
75
  db.save(
60
76
  id: id,
@@ -64,90 +80,72 @@ class PromptManager::Prompt
64
80
  end
65
81
 
66
82
 
83
+ # Delete this prompt from the Storage system
67
84
  def delete
68
85
  db.delete(id: id)
69
86
  end
70
87
 
71
88
 
72
- ######################################
73
- private
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
74
98
 
75
- # Converts keys in the hash to lowercase symbols for easy parameter replacement.
76
- def symbolize_and_downcase_keys(hash)
77
- hash.map { |key, value| [key.to_s.downcase.to_sym, value] }.to_h
99
+ remove_comments
78
100
  end
79
101
 
80
- # Interpolate the parameters within the prompt.
81
- def interpolate_parameters
82
- text.gsub(PARAMETER_REGEX) do |match|
83
- param_name = match[1..-2].downcase
84
- parameters[param_name] || match
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)
85
109
  end
86
110
  end
87
111
 
88
-
89
- # TODO: Implement and integrate ignore_after_end and apply the logic within initialize.
90
-
91
- # TODO: Implement and integrate extract_raw_prompt and apply the logic within initialize.
92
-
93
- # TODO: Implement a better error handling strategy for the storage methods (save, search, get).
94
-
95
- # TODO: Refactor class to support more explicit and semantic configuration and setup.
96
-
97
- # TODO: Consider adding a method to refresh the parameters and re-interpolate the prompt text.
98
-
99
- # TODO: Check the responsibility of the save method; should it deal with the parameters directly or leave it to storage?
100
-
101
- # TODO: Check overall consistency and readability of the code.
102
- end
103
112
 
104
- # Usage of the fixed class would change as follows:
105
- # Assuming Storage is a defined class that manages storing and retrieving prompts.
106
- # storage_instance = Storage.new(...)
107
- # PromptManager::Prompt.storage_adapter = storage_instance
113
+ def remove_comments
114
+ lines = @prompt
115
+ .split("\n")
116
+ .reject{|a_line| a_line.strip.start_with?('#')}
108
117
 
109
- # prompt = PromptManager::Prompt.new(id: 'my_prompt_id')
110
- # puts prompt.to_s
111
- # Expected output would depend on the parameters stored with 'my_prompt_id'
112
-
113
- __END__
114
-
115
- def extract_raw_prompt
116
- array_of_strings = ignore_after_end
117
- print_header_comment(array_of_strings)
118
-
119
- array_of_strings.reject do |a_line|
120
- a_line.chomp.strip.start_with?('#')
121
- end
122
- .join("\n")
123
- end
118
+ # Remove empty lines at the start of the prompt
119
+ #
120
+ lines = lines.drop_while(&:empty?)
124
121
 
122
+ # Drop all the lines at __END__ and after
123
+ #
124
+ logical_end_inx = lines.index("__END__")
125
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
+
126
133
 
127
- def ignore_after_end
128
- array_of_strings = configatron.prompt_path.readlines
129
- .map{|a_line| a_line.chomp.strip}
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
130
143
 
131
- x = array_of_strings.index("__END__")
132
144
 
133
- unless x.nil?
134
- array_of_strings = array_of_strings[..x-1]
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}"
135
149
  end
136
-
137
- array_of_strings
138
150
  end
139
151
 
140
-
141
-
142
-
143
-
144
-
145
- # Usage example:
146
- prompt_text = "Hello, [NAME]! You are logged in as [ROLE]."
147
- parameter_hash = { 'NAME' => 'Alice', 'ROLE' => 'Admin' }
148
- file_paths = ['path/to/first_context', 'path/to/second_context']
149
-
150
- prompt = Prompt.new(prompt_text, parameter_hash, *file_paths)
151
- puts prompt.show
152
- # Expected output: Hello, Alice! You are logged in as Admin.
153
-
@@ -144,6 +144,6 @@ class PromptManager::Storage::FileSystemAdapter
144
144
 
145
145
 
146
146
  def deserialize(data)
147
- JSON.parse(data, symbolize_names: true)
147
+ JSON.parse(data)
148
148
  end
149
149
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PromptManager
4
- VERSION = "0.0.2"
4
+ VERSION = "0.1.0"
5
5
  end
@@ -2,13 +2,11 @@
2
2
  #
3
3
  # frozen_string_literal: true
4
4
 
5
- require 'debug_me'
6
- include DebugMe
7
-
8
5
  require_relative "prompt_manager/version"
9
6
  require_relative "prompt_manager/storage"
10
7
  require_relative "prompt_manager/prompt"
11
8
 
12
9
  module PromptManager
13
10
  class Error < StandardError; end
11
+ # TODO: Add some more module specific errors here
14
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.2
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-18 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,9 +67,9 @@ files:
23
67
  - LICENSE.txt
24
68
  - README.md
25
69
  - Rakefile
26
- - examples/aip.rb
27
70
  - examples/prompts_dir/todo.json
28
71
  - examples/prompts_dir/todo.txt
72
+ - examples/simple.rb
29
73
  - lib/prompt_manager.rb
30
74
  - lib/prompt_manager/prompt.rb
31
75
  - lib/prompt_manager/storage.rb
@@ -59,5 +103,5 @@ requirements: []
59
103
  rubygems_version: 3.4.22
60
104
  signing_key:
61
105
  specification_version: 4
62
- summary: Manage prompts for use with chatGPT LLMs
106
+ summary: Manage prompts for use with gen-AI processes
63
107
  test_files: []
data/examples/aip.rb DELETED
@@ -1,421 +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
- require_relative '../lib/prompt_manager'
17
-
18
-
19
- =begin
20
-
21
- brew install fzf mods the_silver_searcher
22
-
23
- fzf Command-line fuzzy finder written in Go
24
- |__ https://github.com/junegunn/fzf
25
-
26
- mods AI on the command-line
27
- |__ https://github.com/charmbracelet/mods
28
-
29
- the_silver_searcher Code-search similar to ack
30
- |__ https://github.com/ggreer/the_silver_searcher
31
-
32
- Program Summary
33
-
34
- 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:
35
-
36
- - **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.
37
-
38
- - **Prompt Editing**: There is functionality for a user to modify the text of an existing prompt, tailoring it to better meet their specific needs.
39
-
40
- - **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.
41
-
42
- - **AI Integration**: Utilizing the `mods` GPT-based CLI tool, the script takes the chosen edited prompt to guide the AI in generating its output.
43
-
44
- - **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.
45
-
46
- - **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.
47
-
48
- 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.
49
-
50
- =end
51
-
52
- #
53
- # TODO: I think this script has reached the point where
54
- # it is ready to become a proper gem. This would
55
- # be a different gem than prompt_manager.
56
- #
57
-
58
-
59
- require 'pathname'
60
- HOME = Pathname.new( ENV['HOME'] )
61
-
62
-
63
- MODS_MODEL = ENV['MODS_MODEL'] || 'gpt-4-1106-preview'
64
-
65
- AI_CLI_PROGRAM = "mods"
66
- ai_default_opts = "-m #{MODS_MODEL} --no-limit -f"
67
- ai_options = ai_default_opts.dup
68
-
69
- extra_inx = ARGV.index('--')
70
-
71
- if extra_inx
72
- ai_options += " " + ARGV[extra_inx+1..].join(' ')
73
- ARGV.pop(ARGV.size - extra_inx)
74
- end
75
-
76
- AI_COMMAND = "#{AI_CLI_PROGRAM} #{ai_options} "
77
- EDITOR = ENV['EDITOR']
78
- PROMPT_DIR = HOME + ".prompts"
79
- PROMPT_LOG = PROMPT_DIR + "_prompts.log"
80
-
81
-
82
- require 'amazing_print'
83
- # require 'json'
84
- require 'readline' # TODO: or reline ??
85
- require 'word_wrap'
86
- require 'word_wrap/core_ext'
87
-
88
-
89
- require 'debug_me'
90
- include DebugMe
91
-
92
- require 'cli_helper'
93
- include CliHelper
94
-
95
- configatron.version = '1.1.0'
96
-
97
- AI_CLI_PROGRAM_HELP = `#{AI_CLI_PROGRAM} --help`
98
-
99
- HELP = <<EOHELP
100
- AI CLI Program
101
- ==============
102
-
103
- The AI cli program being used is: #{AI_CLI_PROGRAM}
104
-
105
- The defaul options to #{AI_CLI_PROGRAM} are:
106
- "#{ai_default_opts}"
107
-
108
- You can pass additional CLI options to #{AI_CLI_PROGRAM} like this:
109
- "#{my_name} my options -- options for #{AI_CLI_PROGRAM}"
110
-
111
- #{AI_CLI_PROGRAM_HELP}
112
-
113
- EOHELP
114
-
115
- cli_helper("Use generative AI with saved parameterized prompts") do |o|
116
-
117
- o.string '-p', '--prompt', 'The prompt name', default: ""
118
- o.bool '-e', '--edit', 'Edit the prompt text', default: false
119
- o.bool '-f', '--fuzzy', 'Allow fuzzy matching', default: false
120
- o.path '-o', '--output', 'The output file', default: Pathname.pwd + "temp.md"
121
- o.string '-s', '--storage','How are the prompts stored', default: 'FileSystemAdapter'
122
- end
123
-
124
-
125
- AG_COMMAND = "ag --file-search-regex '\.txt$' e"
126
- CD_COMMAND = "cd #{PROMPT_DIR}"
127
- FIND_COMMAND = "find . -name '*.txt'"
128
-
129
- FZF_OPTIONS = [
130
- "--tabstop=2", # 2 soaces for a tab
131
- "--header='Prompt contents below'",
132
- "--header-first",
133
- "--prompt='Search term: '",
134
- '--delimiter :',
135
- "--preview 'ww {1}'", # ww comes from the word_wrap gem
136
- "--preview-window=down:50%:wrap"
137
- ].join(' ')
138
-
139
- FZF_OPTIONS += "--exact" unless fuzzy?
140
-
141
- FZF_COMMAND = "#{CD_COMMAND} ; #{FIND_COMMAND} | fzf #{FZF_OPTIONS}"
142
- AG_FZF_COMMAND = "#{CD_COMMAND} ; #{AG_COMMAND} | fzf #{FZF_OPTIONS}"
143
-
144
- # use `ag` ti build a list of text lines from each prompt
145
- # use `fzf` to search through that list to select a prompt file
146
-
147
- def ag_fzf = `#{AG_FZF_COMMAND}`.split(':')&.first&.strip&.gsub('.txt','')
148
-
149
-
150
- configatron.input_files = get_pathnames_from( configatron.arguments, %w[.rb .txt .md])
151
-
152
-
153
- # TODO: Make the use of the "-p" flag optional.
154
- # I find myself many times forgetting to use it
155
- # and this program rejecting it because
156
- # "the file does not exist" thinging that it
157
- # was an input file file rather than a prompt
158
- # name.
159
-
160
- if configatron.prompt.empty?
161
- configatron.prompt = ag_fzf
162
- end
163
-
164
- unless edit?
165
- if configatron.prompt.nil? || configatron.prompt.empty?
166
- error "No prompt provided"
167
- end
168
- end
169
-
170
- valid_storage = %w[
171
- FileSystemAdapter
172
- SqliteAdapter
173
- ActiveRecordAdapter
174
- ]
175
-
176
- if valid_storange.include? configatron.storage
177
- require_relative "../lib/storage/#{configatron.storage}"
178
- STORAGE = "PromptManager::Storage::#{configatron.storage}".constantize
179
- else
180
- error "Unknow storage: #{configatron.storage}"
181
- end
182
-
183
- abort_if_errors
184
-
185
- # configatron.prompt_path = PROMPT_DIR + (configatron.prompt + PROMPT_EXTNAME)
186
- # configatron.defaults_path = PROMPT_DIR + (configatron.prompt + DEFAULTS_EXTNAME)
187
-
188
- begin
189
- PromptManager::Prompt.storage_adapter = STORAGE.new
190
- rescue StandardError => e
191
- error "Unknown storage adapter: #{configatron.storage}\n#{e}"
192
- end
193
-
194
- abort_if_errors
195
-
196
- begin
197
- PROMPT = PromptManager::Prompt.get(id: configatron.prompt)
198
- rescue Exception => e
199
- error "Storage (#{configatron.storage}) does not have: #{configatron.prompt}\n#{e}"
200
- end
201
-
202
- abort_if_errors
203
-
204
- # TODO: This will have to change.
205
- #
206
- # if edit?
207
- # unless configatron.prompt_path.exist?
208
- # configatron.prompt_path.write <<~PROMPT
209
- # # #{configatron.prompt_path.relative_path_from(HOME)}
210
- # # DESC:
211
- #
212
- # PROMPT
213
- # end
214
- #
215
- # `#{EDITOR} #{configatron.prompt_path}`
216
- # end
217
-
218
- ######################################################
219
- # Local methods
220
-
221
- # def extract_raw_prompt
222
- # array_of_strings = ignore_after_end
223
- # print_header_comment(array_of_strings)
224
- #
225
- # array_of_strings.reject do |a_line|
226
- # a_line.chomp.strip.start_with?('#')
227
- # end
228
- # .join("\n")
229
- # end
230
-
231
-
232
- # def ignore_after_end
233
- # array_of_strings = configatron.prompt_path.readlines
234
- # .map{|a_line| a_line.chomp.strip}
235
- #
236
- # x = array_of_strings.index("__END__")
237
- #
238
- # unless x.nil?
239
- # array_of_strings = array_of_strings[..x-1]
240
- # end
241
- #
242
- # array_of_strings
243
- # end
244
-
245
-
246
- # SMELL: This is based upon a convention that not everyone
247
- # may want to follow.
248
- def print_header_comment(array_of_strings)
249
- print "\n\n" if verbose?
250
-
251
- x = 0
252
-
253
- while array_of_strings[x].start_with?('#') do
254
- puts array_of_strings[x]
255
- x += 1
256
- end
257
-
258
- print "\n\n" if x>0 && verbose?
259
- end
260
-
261
-
262
- # Returns an Array of keywords or phrases that look like:
263
- # [KEYWORD]
264
- # [KEYWORD|KEYWORD]
265
- # [KEY PHRASE]
266
- # [KEY PHRASE | KEY PHRASE | KEY_WORD]
267
- #
268
- # def extract_keywords_from(prompt_raw)
269
- # prompt_raw.scan(KEYWORD_REGEX).flatten.uniq
270
- # end
271
-
272
- # get the replacements for the keywords
273
- def replacements_for(keywords)
274
- replacements = load_default_replacements
275
-
276
- keywords.each do |kw|
277
- default = replacements[kw]
278
- print "#{kw} (#{default}) ? "
279
- a_string = Readline.readline("\n> ", false)
280
- replacements[kw] = a_string unless a_string.empty?
281
- end
282
-
283
- save_default_replacements(replacements)
284
-
285
- replacements
286
- end
287
-
288
-
289
- # def load_default_replacements
290
- # if configatron.defaults_path.exist?
291
- # JSON.parse(configatron.defaults_path.read)
292
- # else
293
- # {}
294
- # end
295
- # end
296
-
297
-
298
- # def save_default_replacements(a_hash)
299
- # return if a_hash.empty?
300
- # defaults = a_hash.to_json
301
- # configatron.defaults_path.write defaults
302
- # end
303
-
304
-
305
- # substitute the replacements for the keywords
306
- # def replace_keywords_with replacements, prompt_raw
307
- # prompt = prompt_raw.dup
308
- #
309
- # replacements.each_pair do |keyword, replacement|
310
- # prompt.gsub!(keyword, replacement)
311
- # end
312
- #
313
- # prompt
314
- # end
315
-
316
-
317
- def log(prompt_path, prompt_raw, answer)
318
- f = File.open(PROMPT_LOG, "ab")
319
-
320
- f.write <<~EOS
321
- =======================================
322
- == #{Time.now}
323
- == #{prompt_path}
324
-
325
- PROMPT: #{prompt_raw}
326
-
327
- RESULT:
328
- #{answer}
329
-
330
- EOS
331
- end
332
-
333
-
334
- ######################################################
335
- # Main
336
-
337
- at_exit do
338
- puts
339
- puts "Done."
340
- puts
341
- end
342
-
343
- ap configatron.to_h if debug?
344
-
345
- # configatron.prompt_raw = extract_raw_prompt
346
-
347
- puts
348
- puts "PROMPT:"
349
- puts prompt.raw_text.wrap
350
- puts
351
-
352
-
353
- keywords = PROMPT.parameters.keys
354
- replacements = PROMPT.parameters
355
-
356
- prompt = replace_keywords_with replacements, configatron.prompt_raw
357
- ptompt = %Q{prompt}
358
-
359
- command = AI_COMMAND + '"' + prompt + '"'
360
-
361
- configatron.input_files.each do |input_file|
362
- command += " < #{input_file}"
363
- end
364
-
365
-
366
- print "\n\n" if verbose? && !keywords.empty?
367
-
368
- if verbose?
369
- puts "="*42
370
- puts command
371
- puts "="*42
372
- print "\n\n"
373
- end
374
-
375
- result = `#{command}`
376
-
377
- configatron.output.write result
378
-
379
- log configatron.prompt_path, prompt, result
380
-
381
-
382
- __END__
383
-
384
- To specify a history and autocomplete options with the readline method in Ruby using the readline gem, you can follow these steps:
385
-
386
- 1. **History** - To enable history functionality, create a Readline::HISTORY object:
387
- ```ruby
388
- history = Readline::HISTORY
389
- ```
390
- You can then use the `history` object to add and manipulate history entries.
391
-
392
- 2. **Autocomplete** - To enable autocomplete functionality, you need to provide a completion proc to `Readline.completion_proc`:
393
- ```ruby
394
- Readline.completion_proc = proc { |input| ... }
395
- ```
396
- You should replace `...` with the logic for determining the autocomplete options based on the input.
397
-
398
- For example, you can define a method that provides autocomplete options based on a predefined array:
399
- ```ruby
400
- def autocomplete_options(input)
401
- available_options = ['apple', 'banana', 'cherry']
402
- available_options.grep(/^#{Regexp.escape(input)}/)
403
- end
404
-
405
- Readline.completion_proc = proc { |input| autocomplete_options(input) }
406
- ```
407
-
408
- 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.
409
-
410
- Remember to require the readline gem before using these features:
411
- ```ruby
412
- require 'readline'
413
- ```
414
-
415
- 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.
416
-
417
- Note: Keep in mind that autocomplete options will only appear when tab is pressed while entering input.
418
-
419
-
420
-
421
-