prompt_manager 0.4.0 → 0.4.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 60ae8d4c5f669935906248aa0e5ffc628e6ef0fbcc28eb31c889eb85b200d370
4
- data.tar.gz: 44e51b38634667930b5524232cefd63fe0dab3ef334500714361680009186bfd
3
+ metadata.gz: 6c566743ba6741a0787db6ea680e77676ab18282013e3475cad1c30b85b9ce1b
4
+ data.tar.gz: 1e9e57eecaf97d05799d6b173b1ce90518dc02abeeddbd50eed90c34c376509f
5
5
  SHA512:
6
- metadata.gz: 634cc2d1cfdedaf1ca5f5a232334b7e8ccbad25dbb48e669daddf831e43eb45f03367f2921561cdb8ddcf1675ea48f1dcf8cc40defb479aa1eced3bb493442f9
7
- data.tar.gz: f04cc92a7ac3c286fb3958eaa44358c402899a121e89ca41aa7bddd30c41bd704bed55dd57775340c622671d0fbf36eef48171ffc3d290f220d6306622486ef3
6
+ metadata.gz: e1bcda6797d895b4dd78baf6c6c6cd2b83e2f06bb3446f9112795a6d216eb9450af9f1c2d0b3f762d4f21ee40a1257fec8610eadec33f7f19dabf078e7a859bf
7
+ data.tar.gz: f69ed112a2b22801dc830d87b11f0a231755141fa4eccded5ee2dc8019343cf6cf61705af560fed211866869940029bbde88cca7c821423da245593839515e03
data/.irbrc ADDED
@@ -0,0 +1,14 @@
1
+ require_relative 'lib/prompt_manager'
2
+ require_relative 'lib/prompt_manager/storage/file_system_adapter'
3
+
4
+ HERE = Pathname.new __dir__
5
+
6
+ PromptManager::Storage::FileSystemAdapter.config do |config|
7
+ config.prompts_dir = HERE + 'examples/prompts_dir'
8
+ # config.search_proc = nil # default
9
+ # config.prompt_extension = '.txt' # default
10
+ # config.parms+_extension = '.json' # default
11
+ end
12
+
13
+ PromptManager::Prompt.storage_adapter = PromptManager::Storage::FileSystemAdapter.new
14
+
data/CHANGELOG.md CHANGED
@@ -1,23 +1,34 @@
1
- ## [0.4.0] = 2023-12-19
1
+ ## Unreleased
2
+
3
+ ## Released
4
+
5
+ ### [0.4.2] = 2024-10-26
6
+ - Added configurable parameter_regex to customize keyword pattern
7
+
8
+ ### [0.4.1] = 2023-12-29
9
+ - Changed @directives from Hash to an Array
10
+ - Fixed keywords not being substituted in directives
11
+
12
+ ### [0.4.0] = 2023-12-19
2
13
  - Add "//directives param(s)" with keywords just like the prompt text.
3
14
 
4
- ## [0.3.3] = 2023-12-01
15
+ ### [0.3.3] = 2023-12-01
5
16
  - Added example of using the `search_proc` config parameter with the FileSystemAdapter.
6
17
 
7
- ## [0.3.2] = 2023-12-01
18
+ ### [0.3.2] = 2023-12-01
8
19
 
9
20
  - The ActiveRecordAdapter is passing its unit tests
10
21
  - Dropped the concept of an sqlite3 adapter since active record can be used to access sqlite3 databases as well as the big boys.
11
22
 
12
- ## [0.3.0] = 2023-11-28
23
+ ### [0.3.0] = 2023-11-28
13
24
 
14
25
  - **Breaking change** The value of the parameters Hash for a keyword is now an Array instead of a single value. The last value in the Array is always the most recent value used for the given keyword. This was done to support the use of a Readline::History object editing in the [aia](https://github.com/MadBomber/aia) CLI tool
15
26
 
16
- ## [0.2.0] - 2023-11-21
27
+ ### [0.2.0] - 2023-11-21
17
28
 
18
29
  - **Breaking change to FileSystemAdapter config process**
19
30
  - added list and path as extra methods in FileSystemAdapter
20
31
 
21
- ## [0.1.0] - 2023-11-16
32
+ ### [0.1.0] - 2023-11-16
22
33
 
23
34
  - Initial release using the FileSystemAdapter
data/README.md CHANGED
@@ -15,6 +15,10 @@ Manage the parameterized prompts (text) used in generative AI (aka chatGPT, Open
15
15
  - [Generative AI (gen-AI)](#generative-ai-gen-ai)
16
16
  - [What does a keyword look like?](#what-does-a-keyword-look-like)
17
17
  - [All about directives](#all-about-directives)
18
+ - [Example Prompt with Directives](#example-prompt-with-directives)
19
+ - [Accessing Directives](#accessing-directives)
20
+ - [Dynamic Directives](#dynamic-directives)
21
+ - [Executing Directives](#executing-directives)
18
22
  - [Comments Are Ignored](#comments-are-ignored)
19
23
  - [Storage Adapters](#storage-adapters)
20
24
  - [FileSystemAdapter](#filesystemadapter)
@@ -66,17 +70,25 @@ The prompt_manager uses a regular expression to identify these keywords within t
66
70
 
67
71
  #### What does a keyword look like?
68
72
 
69
- The current hard-coded REGEX for a [KEYWORD] identifies any all [UPPERCASE_TEXT] enclosed in square brackets as a keyword. [KEYWORDS CAN ALSO HAVE SPACES] as well as the underscore character.
73
+ By default, any text matching `[UPPERCASE_TEXT]` enclosed in square brackets is treated as a keyword. [KEYWORDS CAN ALSO HAVE SPACES] as well as the underscore character.
70
74
 
71
- This is just the initial convention adopted by prompt_manager. It is intended that this REGEX be configurable so that the prompt_manager can be used with other conventions.
75
+ You can customize the keyword pattern by setting a different regular expression:
72
76
 
77
+ ```ruby
78
+ # Use {{param}} style instead of [PARAM]
79
+ PromptManager::Prompt.parameter_regex = /(\{\{[A-Za-z_]+\}\})/
80
+ ```
81
+
82
+ The regex must include capturing parentheses () to extract the keyword. The default regex is `/(\[[A-Z _|]+\])/`.
73
83
  #### All about directives
74
84
 
75
85
  A directive is a line in the prompt text that starts with the two characters '//' - slash slash - just like in the old days of IBM JCL - Job Control Language. A prompt can have zero or more directives. Directives can have parameters and can make use of keywords.
76
86
 
77
- The `prompt_manager` only collections directives. It extracts keywords from directive lines and provides the substitution of those keywords with other text just like it does for the prompt. Remember a directive is part of the prompt.
87
+ The `prompt_manager` only collects directives. It extracts keywords from directive lines and provides the substitution of those keywords with other text just like it does for the prompt.
88
+
89
+ ##### Example Prompt with Directives
78
90
 
79
- Here is an example problem with comments, directives and keywords:
91
+ Here is an example prompt text file with comments, directives and keywords:
80
92
 
81
93
  ```
82
94
  # prompts/sing_a_song.txt
@@ -90,12 +102,14 @@ __END__
90
102
  Computers will never replace Frank Sinatra
91
103
  ```
92
104
 
105
+ ##### Accessing Directives
106
+
93
107
  Getting directives from a prompt is as easy as getting the kewyords:
94
108
 
95
109
  ```ruby
96
110
  prompt = PromptManager::Prompt.new(...)
97
111
  prompt.keywords #=> an Array
98
- prompt.directives #=> a Hash
112
+ prompt.directives #=> an Array of entries like: ['directive', 'parameters']
99
113
 
100
114
  # to_s builds the prompt by substituting
101
115
  # values for keywords amd removing comments.
@@ -104,9 +118,33 @@ prompt.directives #=> a Hash
104
118
  puts prompt.to_s
105
119
  ```
106
120
 
107
- The Hash returned by the `prompt.directives` method has as its key the directive name (without the double-slash). Its also case-sensitive. So any typo made in editing the prompt with regard to the case of the directive's name will impact the backend processing of the prompt.
121
+ The entries in the Array returned by the `prompt.directives` method is in the order that the directives were defined within the prompt. Each entry has two elements:
122
+
123
+ - directive name (without the // characters)
124
+ - parameter string for the directive
125
+
126
+ ##### Dynamic Directives
127
+
128
+ Since directies are collected after the keywords in the prompt have been substituted for their values, it is possible to have dynamically generated directives as part of a prompt. For example:
129
+
130
+ ```
131
+ //[COMMAND] [OPTIONS]
132
+ # or
133
+ [SOMETHING]
134
+ ```
135
+ ... where [COMMAND] gets replaced by some directive name. [SOMETHING] could be replaced by "//directive options"
136
+
137
+ ##### Executing Directives
138
+
139
+ The `prompt_manager` gem only collects directives. Executing those directives is left up to some down stream process. Here are some ideas on how directives could be used in prompt downstream process:
140
+
141
+ - "//model gpt-5" could be used to set the LLM model to be used for a specific prompt.
142
+ - "//backend mods" could be used to set the backend prompt processor on the command line to be the `mods` utility.
143
+ - "//include path_to_file" could be used to add the contents of a file to the prompt.
144
+ - "//chat" could be used to send the prompts and then start up a chat session about the prompt and its response.
145
+
146
+ Its all up to how your application wants to support directives or not.
108
147
 
109
- The value for each key is a String exactly as entered in the prompt file.
110
148
 
111
149
  #### Comments Are Ignored
112
150
 
data/Rakefile CHANGED
@@ -8,7 +8,6 @@ end
8
8
 
9
9
  Tocer::Rake::Register.call
10
10
 
11
-
12
11
  require "bundler/gem_tasks"
13
12
  require "rake/testtask"
14
13
 
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+ # frozen_string_literal: true
4
+ # warn_indent: true
5
+ ##########################################################
6
+ ###
7
+ ## File: directives.rb
8
+ ## Desc: Demo of the PromptManager and FileStorageAdapter
9
+ ## By: Dewayne VanHoozer (dvanhoozer@gmail.com)
10
+ ##
11
+ #
12
+
13
+ param1 = "param_one"
14
+ param2 = "param_two"
15
+
16
+
17
+ class MyDirectives
18
+ def self.good_directive(*args)
19
+ puts "inside #{__method__} with these parameters:"
20
+ puts args.join(",\n")
21
+ end
22
+ end
23
+
24
+ def concept_break = print "\n------------------------\n\n\n"
25
+
26
+ require_relative '../lib/prompt_manager'
27
+ require_relative '../lib/prompt_manager/storage/file_system_adapter'
28
+
29
+ require 'amazing_print'
30
+ require 'pathname'
31
+
32
+ require 'debug_me'
33
+ include DebugMe
34
+
35
+ HERE = Pathname.new( __dir__ )
36
+ PROMPTS_DIR = HERE + "prompts_dir"
37
+
38
+
39
+ ######################################################
40
+ # Main
41
+
42
+ at_exit do
43
+ puts
44
+ puts "Done."
45
+ puts
46
+ end
47
+
48
+ # Configure the Storage Adapter to use
49
+ PromptManager::Storage::FileSystemAdapter.config do |config|
50
+ config.prompts_dir = PROMPTS_DIR
51
+ # config.search_proc = nil # default
52
+ # config.prompt_extension = '.txt' # default
53
+ # config.parms+_extension = '.json' # default
54
+ end
55
+
56
+ PromptManager::Prompt.storage_adapter = PromptManager::Storage::FileSystemAdapter.new
57
+
58
+ # Use {parameter name} brackets to define a parameter
59
+ PromptManager::Prompt.parameter_regex = /\{[A-Za-z _|]+\}/
60
+
61
+ # Retrieve a prompt
62
+ prompt = PromptManager::Prompt.get(id: 'directive_example')
63
+
64
+ # Shows prompt without comments or directives
65
+ # It still has its parameter placeholders
66
+ puts prompt
67
+ concept_break
68
+
69
+ puts "Directives in the prompt:"
70
+ ap prompt.directives
71
+
72
+ puts "Processing directives ..."
73
+ prompt.directives.each do |entry|
74
+ if MyDirectives.respond_to? entry.first.to_sym
75
+ ruby = "MyDirectives.#{entry.first}(#{entry.last.gsub(' ', ',')})"
76
+ eval "#{ruby}"
77
+ else
78
+ puts "ERROR: there is no method: #{entry.first}"
79
+ end
80
+ end
81
+
82
+ concept_break
83
+
84
+
85
+
86
+ puts "Parameters in the prompt:"
87
+ ap prompt.parameters
88
+ puts "-"*16
89
+
90
+ puts "keywords:"
91
+ ap prompt.keywords
92
+ concept_break
93
+
94
+ prompt.parameters['{language}'] << 'French'
95
+
96
+ puts "After Substitution"
97
+ puts prompt
98
+
@@ -0,0 +1,14 @@
1
+ # directive_example.txt
2
+ # Desc: Shows how directives work
3
+
4
+ //good_directive param1 param2
5
+ //bad_directive param3 param4
6
+
7
+ say hello to me in {language} as well as English.
8
+
9
+ # The default parameter is delimited by square brackets
10
+ # and is all uppercase
11
+
12
+ write a [PROGRAMMING LANGUAGE] program that predicts the lottery.
13
+
14
+
@@ -10,9 +10,6 @@
10
10
  ##
11
11
  #
12
12
 
13
- require 'debug_me'
14
- include DebugMe
15
-
16
13
  require 'prompt_manager'
17
14
  require 'prompt_manager/storage/file_system_adapter'
18
15
 
@@ -6,23 +6,27 @@
6
6
  # comment removal. It communicates with a storage system through a storage
7
7
  # adapter.
8
8
  #
9
- # Directives can be anything required by the backend
10
- # prompt processing functionality. Directives are
11
- # available along with the prompt text w/o comments.
12
- # Keywords can be used in the directives.
9
+ # Directives are collected into an Array where each entry is an Array
10
+ # of two elements. The first is the directive name as a String. The
11
+ # second is a string of parameters used by the directive.
12
+ #
13
+ # Directives are collected from the prompt after keyword
14
+ # substitution has occured. This means that directives within a
15
+ # prompt can be dynamic.
13
16
  #
14
- # It is expected that the backend prompt processes will
15
- # act on and remove the directives before passing the
16
- # prompt text on through the LLM.
17
+ # PromptManager does not execute directives. They
18
+ # are made available to be passed on to down stream
19
+ # process.
17
20
 
18
21
  class PromptManager::Prompt
19
22
  COMMENT_SIGNAL = '#' # lines beginning with this are a comment
20
23
  DIRECTIVE_SIGNAL = '//' # Like the old IBM JCL
21
- PARAMETER_REGEX = /(\[[A-Z _|]+\])/
24
+ DEFAULT_PARAMETER_REGEX = /(\[[A-Z _|]+\])/
22
25
  @storage_adapter = nil
26
+ @parameter_regex = DEFAULT_PARAMETER_REGEX
23
27
 
24
28
  class << self
25
- attr_accessor :storage_adapter
29
+ attr_accessor :storage_adapter, :parameter_regex
26
30
 
27
31
  alias_method :get, :new
28
32
 
@@ -58,7 +62,7 @@ class PromptManager::Prompt
58
62
 
59
63
  # SMELL: Does the db (aka storage adapter) really need
60
64
  # to be accessible by the main program?
61
- attr_accessor :db, :id, :text, :parameters
65
+ attr_accessor :db, :id, :text, :parameters, :directives
62
66
 
63
67
 
64
68
  # Retrieve the specific prompt ID from the Storage system.
@@ -76,10 +80,9 @@ class PromptManager::Prompt
76
80
  @text = @record[:text]
77
81
  @parameters = @record[:parameters]
78
82
  @keywords = [] # Array of String
79
- @directives = {} # Hash with directive as key, parameters as value
83
+ @directives = [] # Array of arrays. directive is first entry, rest are parameters
80
84
 
81
85
  update_keywords
82
- update_directives
83
86
 
84
87
  build
85
88
  end
@@ -121,11 +124,12 @@ class PromptManager::Prompt
121
124
  # the comments.
122
125
  #
123
126
  def build
124
- @prompt = text.gsub(PARAMETER_REGEX) do |match|
127
+ @prompt = text.gsub(self.class.parameter_regex) do |match|
125
128
  param_name = match
126
129
  Array(parameters[param_name]).last || match
127
130
  end
128
-
131
+
132
+ save_directives(@prompt)
129
133
  remove_comments
130
134
  end
131
135
 
@@ -135,16 +139,11 @@ class PromptManager::Prompt
135
139
  end
136
140
 
137
141
 
138
- def directives
139
- update_directives
140
- end
141
-
142
-
143
142
  ######################################
144
143
  private
145
144
 
146
145
  def update_keywords
147
- @keywords = @text.scan(PARAMETER_REGEX).flatten.uniq
146
+ @keywords = @text.scan(self.class.parameter_regex).flatten.uniq
148
147
  @keywords.each do |kw|
149
148
  @parameters[kw] = [] unless @parameters.has_key?(kw)
150
149
  end
@@ -153,14 +152,16 @@ class PromptManager::Prompt
153
152
  end
154
153
 
155
154
 
156
- def update_directives
157
- @text.split("\n").each do |a_line|
155
+ def save_directives(keyword_substituted_string)
156
+ @directives = []
157
+
158
+ keyword_substituted_string.split("\n").each do |a_line|
158
159
  line = a_line.strip
159
160
  next unless line.start_with?(DIRECTIVE_SIGNAL)
160
161
 
161
- parts = line.split(' ')
162
- directive = parts.shift()[DIRECTIVE_SIGNAL.length..] # drop the directive signal
163
- @directives[directive] = parts.join(' ')
162
+ parts = line.split(' ')
163
+ directive = parts.shift[DIRECTIVE_SIGNAL.length..] # drop the directive signal
164
+ @directives << [directive, parts.join(' ')]
164
165
  end
165
166
 
166
167
  @directives
@@ -171,7 +172,8 @@ class PromptManager::Prompt
171
172
  lines = @prompt
172
173
  .split("\n")
173
174
  .reject{|a_line|
174
- a_line.strip.start_with?(COMMENT_SIGNAL)
175
+ a_line.strip.start_with?(COMMENT_SIGNAL) ||
176
+ a_line.strip.start_with?(DIRECTIVE_SIGNAL)
175
177
  }
176
178
 
177
179
  # Remove empty lines at the start of the prompt
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PromptManager
4
- VERSION = "0.4.0"
4
+ VERSION = "0.4.2"
5
5
  end
@@ -2,6 +2,8 @@
2
2
  #
3
3
  # frozen_string_literal: true
4
4
 
5
+ require 'ostruct'
6
+
5
7
  require_relative "prompt_manager/version"
6
8
  require_relative "prompt_manager/storage"
7
9
  require_relative "prompt_manager/prompt"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prompt_manager
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.4.2
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-12-19 00:00:00.000000000 Z
11
+ date: 2024-10-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: ostruct
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: tocer
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -80,8 +94,36 @@ dependencies:
80
94
  - - ">="
81
95
  - !ruby/object:Gem::Version
82
96
  version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: sqlite3
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
83
125
  description: "Manage the parameterized prompts (text) used in generative AI (aka chatGPT,
84
- \nOpenAI, et.al.) using storage adapters such as FileSystemAdapter, \nSqliteAdapter
126
+ \nOpenAI, et.al.) using storage adapters such as FileSystemAdapter, \nSqliteAdapter,
85
127
  and ActiveRecordAdapter.\n"
86
128
  email:
87
129
  - dvanhoozer@gmail.com
@@ -90,11 +132,14 @@ extensions: []
90
132
  extra_rdoc_files: []
91
133
  files:
92
134
  - ".envrc"
135
+ - ".irbrc"
93
136
  - CHANGELOG.md
94
137
  - LICENSE
95
138
  - LICENSE.txt
96
139
  - README.md
97
140
  - Rakefile
141
+ - examples/directives.rb
142
+ - examples/prompts_dir/directive_example.txt
98
143
  - examples/prompts_dir/todo.json
99
144
  - examples/prompts_dir/todo.txt
100
145
  - examples/prompts_dir/toy/8-ball.txt
@@ -130,7 +175,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
130
175
  - !ruby/object:Gem::Version
131
176
  version: '0'
132
177
  requirements: []
133
- rubygems_version: 3.5.1
178
+ rubygems_version: 3.5.22
134
179
  signing_key:
135
180
  specification_version: 4
136
181
  summary: Manage prompts for use with gen-AI processes