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 +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
|
-
|