prompt_manager 0.0.2 → 0.1.0
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 +1 -1
- data/README.md +31 -63
- data/examples/prompts_dir/todo.json +1 -1
- data/examples/prompts_dir/todo.txt +3 -4
- data/examples/simple.rb +89 -0
- data/lib/prompt_manager/prompt.rb +76 -78
- data/lib/prompt_manager/storage/file_system_adapter.rb +1 -1
- data/lib/prompt_manager/version.rb +1 -1
- data/lib/prompt_manager.rb +1 -3
- metadata +50 -6
- data/examples/aip.rb +0 -421
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2772d50f835a71c26631b2109af906008fec84b62e7ccf435a188fb856415268
|
4
|
+
data.tar.gz: c1aa3889826246a79df35ad74cd221c1374af161083a34f50d64ef09f5606ade
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c43cc115f2dfa8e847c499386b482d29f3454ca068b0d161d8c14350002d955461962e1009af0a932a92388eef39b134515bca6f694dabd41110150301339b56
|
7
|
+
data.tar.gz: 55b0e4f827d1950f39ad87909954660cb5ae0f6da44a4de0b43aad637dc1d99d878d2cb1876ac4a001cd42c190e1160a76593c6f4cba3b8411c250bd42e97e35
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -1,101 +1,69 @@
|
|
1
1
|
# PromptManager
|
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
|
-
|
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
|
-
|
7
|
+
Install the gem and add to the application's Gemfile by executing:
|
38
8
|
|
39
|
-
|
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
|
-
|
11
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
44
12
|
|
45
|
-
|
13
|
+
gem install prompt_manager
|
46
14
|
|
47
|
-
|
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
|
-
|
17
|
+
See [examples/simple.rb](examples.simple.rb)
|
53
18
|
|
54
|
-
|
19
|
+
## Overview
|
55
20
|
|
56
|
-
|
21
|
+
### Generative AI (gen-AI)
|
57
22
|
|
58
|
-
|
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
|
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
|
-
####
|
27
|
+
#### What does a keyword look like?
|
63
28
|
|
64
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
45
|
+
#### SqliteAdapter
|
77
46
|
|
78
|
-
|
47
|
+
TODO: This may be the next adapter to be implemented.
|
79
48
|
|
80
|
-
|
49
|
+
#### ActiveRecordAdapter
|
81
50
|
|
82
|
-
|
51
|
+
TODO: Still looking for requirements on how to integrate with an existing `rails` app. Looking for ideas.
|
83
52
|
|
84
|
-
|
53
|
+
#### Other Potential Storage Adapters
|
85
54
|
|
86
|
-
|
55
|
+
There are many possibilities to example this plugin concept of the storage adapter. Here are some for consideration:
|
87
56
|
|
88
|
-
|
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
|
-
|
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/
|
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
|
-
{"
|
1
|
+
{"[LANGUAGE]":"ruby"}
|
@@ -1,8 +1,7 @@
|
|
1
|
-
#
|
2
|
-
# Desc:
|
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 "
|
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__
|
data/examples/simple.rb
ADDED
@@ -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
|
-
#
|
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 =
|
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
|
-
|
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
|
-
|
33
|
-
#
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
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
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
113
|
+
def remove_comments
|
114
|
+
lines = @prompt
|
115
|
+
.split("\n")
|
116
|
+
.reject{|a_line| a_line.strip.start_with?('#')}
|
108
117
|
|
109
|
-
#
|
110
|
-
#
|
111
|
-
|
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
|
-
|
128
|
-
|
129
|
-
|
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
|
-
|
134
|
-
|
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
|
-
|
data/lib/prompt_manager.rb
CHANGED
@@ -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
|
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-
|
12
|
-
dependencies:
|
13
|
-
|
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
|
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
|
-
|