boxcars 0.2.2 → 0.2.3
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 +9 -1
- data/Gemfile.lock +1 -1
- data/lib/boxcars/boxcar/active_record.rb +48 -33
- data/lib/boxcars/boxcar/engine_boxcar.rb +20 -3
- data/lib/boxcars/boxcar/sql.rb +7 -3
- data/lib/boxcars/train/zero_shot.rb +15 -10
- data/lib/boxcars/train.rb +5 -0
- data/lib/boxcars/version.rb +1 -1
- data/lib/boxcars.rb +2 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ea9c5df0584f588d618c22fc44f8bba2638c0946ba32cea8487429ad6df694f5
|
|
4
|
+
data.tar.gz: a7cd6609da5d63c1b41763fc7c6e8b4e1c8df41992fd133d0bf359b28d50dd0c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: aac8bd34e6ddca629d26ff0cb892e46de30e1f20db188b56477422e5526e03c80a22e1b06ab7d8263e8db067db6ec1966f34450ab63e98cea3e857aa7eb27286
|
|
7
|
+
data.tar.gz: 10ce3be58a020a5de80c27fdf46cbd7811afc45ac28e0712479b113eb6a34ca6a2f7043711659bb4b0a152e0981ded5a8f76fe0704f1a89751b3c8ec0a843ea8
|
data/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased](https://github.com/BoxcarsAI/boxcars/tree/HEAD)
|
|
4
4
|
|
|
5
|
-
[Full Changelog](https://github.com/BoxcarsAI/boxcars/compare/v0.2.
|
|
5
|
+
[Full Changelog](https://github.com/BoxcarsAI/boxcars/compare/v0.2.2...HEAD)
|
|
6
|
+
|
|
7
|
+
**Merged pull requests:**
|
|
8
|
+
|
|
9
|
+
- better error handling and retry for Active Record Boxcar [\#39](https://github.com/BoxcarsAI/boxcars/pull/39) ([francis](https://github.com/francis))
|
|
10
|
+
|
|
11
|
+
## [v0.2.2](https://github.com/BoxcarsAI/boxcars/tree/v0.2.2) (2023-03-16)
|
|
12
|
+
|
|
13
|
+
[Full Changelog](https://github.com/BoxcarsAI/boxcars/compare/v0.2.1...v0.2.2)
|
|
6
14
|
|
|
7
15
|
**Implemented enhancements:**
|
|
8
16
|
|
data/Gemfile.lock
CHANGED
|
@@ -71,13 +71,21 @@ module Boxcars
|
|
|
71
71
|
|
|
72
72
|
# to be safe, we wrap the code in a transaction and rollback
|
|
73
73
|
def rollback_after_running
|
|
74
|
-
|
|
74
|
+
result = nil
|
|
75
|
+
runtime_exception = nil
|
|
75
76
|
::ActiveRecord::Base.transaction do
|
|
76
|
-
|
|
77
|
+
begin
|
|
78
|
+
result = yield
|
|
79
|
+
rescue ::NameError, ::Error => e
|
|
80
|
+
Boxcars.error("Error while running code: #{e.message[0..60]} ...", :red)
|
|
81
|
+
runtime_exception = e
|
|
82
|
+
end
|
|
77
83
|
ensure
|
|
78
84
|
raise ::ActiveRecord::Rollback
|
|
79
85
|
end
|
|
80
|
-
|
|
86
|
+
raise runtime_exception if runtime_exception
|
|
87
|
+
|
|
88
|
+
result
|
|
81
89
|
end
|
|
82
90
|
|
|
83
91
|
# check for dangerous code that is outside of ActiveRecord
|
|
@@ -122,8 +130,11 @@ module Boxcars
|
|
|
122
130
|
return true unless changes&.positive?
|
|
123
131
|
|
|
124
132
|
Boxcars.debug "#{name}(Pending Changes): #{changes}", :yellow
|
|
125
|
-
|
|
126
|
-
|
|
133
|
+
if read_only?
|
|
134
|
+
change_str = "#{changes} change#{'s' if changes.to_i > 1}"
|
|
135
|
+
Boxcars.error("Can not run code that makes #{change_str} in read-only mode", :red)
|
|
136
|
+
return false
|
|
137
|
+
end
|
|
127
138
|
|
|
128
139
|
return approval_callback.call(changes, code) if approval_callback.is_a?(Proc)
|
|
129
140
|
|
|
@@ -151,30 +162,32 @@ module Boxcars
|
|
|
151
162
|
output
|
|
152
163
|
end
|
|
153
164
|
|
|
154
|
-
def
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
when /^`(.+)`/
|
|
159
|
-
::Regexp.last_match(1)
|
|
160
|
-
else
|
|
161
|
-
code
|
|
162
|
-
end
|
|
165
|
+
def error_message(err, stage)
|
|
166
|
+
msg = err.message
|
|
167
|
+
msg = ::Regexp.last_match(1) if msg =~ /^(.+)' for #<Boxcars::ActiveRecord/
|
|
168
|
+
"#{stage} Error: #{msg} - please fix \"#{stage}:\" to not have this error"
|
|
163
169
|
end
|
|
164
170
|
|
|
165
171
|
def get_active_record_answer(text)
|
|
166
|
-
|
|
167
|
-
|
|
172
|
+
changes_code = extract_code text.split('ARCode:').first.split('ARChanges:').last.strip if text =~ /^ARChanges:/
|
|
173
|
+
code = extract_code text.split('ARCode:').last.strip
|
|
168
174
|
return Result.new(status: :ok, explanation: "code to run", code: code, changes_code: changes_code) if code_only?
|
|
169
175
|
|
|
170
|
-
|
|
176
|
+
have_approval = false
|
|
177
|
+
begin
|
|
178
|
+
have_approval = approved?(changes_code, code)
|
|
179
|
+
rescue NameError, ::Error => e
|
|
180
|
+
return Result.new(status: :error, explanation: error_message(e, "ARChanges"), changes_code: changes_code)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
raise SecurityError, "Permission to run code that makes changes denied" unless have_approval
|
|
171
184
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
185
|
+
begin
|
|
186
|
+
output = clean_up_output(run_active_record_code(code))
|
|
187
|
+
Result.new(status: :ok, answer: output, explanation: "Answer: #{output.to_json}", code: code)
|
|
188
|
+
rescue NameError, ::Error => e
|
|
189
|
+
Result.new(status: :error, answer: nil, explanation: error_message(e, "ARCode"), code: code)
|
|
190
|
+
end
|
|
178
191
|
end
|
|
179
192
|
|
|
180
193
|
def get_answer(text)
|
|
@@ -184,7 +197,8 @@ module Boxcars
|
|
|
184
197
|
when /^Answer:/
|
|
185
198
|
Result.from_text(text)
|
|
186
199
|
else
|
|
187
|
-
Result.from_error("
|
|
200
|
+
Result.from_error("Error: Your answer wasn't formatted properly - try again. I expected your answer to " \
|
|
201
|
+
"start with \"ARChanges:\" or \"ARCode:\"")
|
|
188
202
|
end
|
|
189
203
|
end
|
|
190
204
|
|
|
@@ -196,18 +210,19 @@ module Boxcars
|
|
|
196
210
|
"to at most %<top_k>s results.\n",
|
|
197
211
|
"Never query for all the columns from a specific model, ",
|
|
198
212
|
"only ask for the relevant attributes given the question.\n",
|
|
199
|
-
"Also, pay attention to which attribute is in which model
|
|
200
|
-
|
|
213
|
+
"Also, pay attention to which attribute is in which model.\n\n",
|
|
214
|
+
"Use the following format:\n",
|
|
201
215
|
"Question: ${{Question here}}\n",
|
|
202
|
-
"ARCode: ${{Active Record code to run}} - make sure you use valid code\n",
|
|
203
216
|
"ARChanges: ${{Active Record code to compute the number of records going to change}} - ",
|
|
204
|
-
"Only add this line if the ARCode on the line
|
|
205
|
-
"
|
|
206
|
-
|
|
207
|
-
"
|
|
208
|
-
"
|
|
217
|
+
"Only add this line if the ARCode on the next line will make data changes.\n",
|
|
218
|
+
"ARCode: ${{Active Record code to run}} - make sure you use valid code\n",
|
|
219
|
+
"Answer: ${{Final answer here}}\n\n",
|
|
220
|
+
"Only use the following Active Record models: %<model_info>s\n",
|
|
221
|
+
"Pay attention to use only the attribute names that you can see in the model description.\n",
|
|
222
|
+
"Do not make up variable or attribute names, and do not share variables between the code in ARChanges and ARCode\n",
|
|
223
|
+
"Be careful to not query for attributes that do not exist, and to use the format specified above.\n"
|
|
209
224
|
),
|
|
210
|
-
|
|
225
|
+
user("Question: %<question>s")
|
|
211
226
|
].freeze
|
|
212
227
|
|
|
213
228
|
# The prompt to use for the engine.
|
|
@@ -65,7 +65,9 @@ module Boxcars
|
|
|
65
65
|
# @param kwargs [Hash] A hash of input values to use for the prompt.
|
|
66
66
|
# @return [String] The output value.
|
|
67
67
|
def predict(current_conversation: nil, **kwargs)
|
|
68
|
-
apply(current_conversation: current_conversation, input_list: [kwargs])[output_keys.first]
|
|
68
|
+
prediction = apply(current_conversation: current_conversation, input_list: [kwargs])[output_keys.first]
|
|
69
|
+
Boxcars.debug(prediction, :white) if Boxcars.configuration.log_generated
|
|
70
|
+
prediction
|
|
69
71
|
end
|
|
70
72
|
|
|
71
73
|
# check that there is exactly one output key
|
|
@@ -84,11 +86,12 @@ module Boxcars
|
|
|
84
86
|
conversation = nil
|
|
85
87
|
answer = nil
|
|
86
88
|
4.times do
|
|
87
|
-
|
|
88
|
-
answer = get_answer(
|
|
89
|
+
text = predict(current_conversation: conversation, **prediction_variables(inputs)).strip
|
|
90
|
+
answer = get_answer(text)
|
|
89
91
|
if answer.status == :error
|
|
90
92
|
Boxcars.debug "have error, trying again: #{answer.answer}", :red
|
|
91
93
|
conversation ||= Conversation.new
|
|
94
|
+
conversation.add_assistant(text)
|
|
92
95
|
conversation.add_user(answer.answer)
|
|
93
96
|
else
|
|
94
97
|
Boxcars.debug answer.to_json, :magenta
|
|
@@ -120,5 +123,19 @@ module Boxcars
|
|
|
120
123
|
def prediction_variables(inputs)
|
|
121
124
|
prediction_input(inputs).merge(prediction_additional)
|
|
122
125
|
end
|
|
126
|
+
|
|
127
|
+
# remove backticks or triple backticks from the code
|
|
128
|
+
# @param code [String] The code to remove backticks from.
|
|
129
|
+
# @return [String] The code without backticks.
|
|
130
|
+
def extract_code(code)
|
|
131
|
+
case code
|
|
132
|
+
when /^```\w*/
|
|
133
|
+
code.split(/```\w*\n/).last.split('```').first.strip
|
|
134
|
+
when /^`(.+)`/
|
|
135
|
+
::Regexp.last_match(1)
|
|
136
|
+
else
|
|
137
|
+
code.gsub("`", "")
|
|
138
|
+
end
|
|
139
|
+
end
|
|
123
140
|
end
|
|
124
141
|
end
|
data/lib/boxcars/boxcar/sql.rb
CHANGED
|
@@ -20,6 +20,8 @@ module Boxcars
|
|
|
20
20
|
kwargs[:name] ||= "Database"
|
|
21
21
|
kwargs[:description] ||= format(SQLDESC, name: name)
|
|
22
22
|
kwargs[:prompt] ||= my_prompt
|
|
23
|
+
kwargs[:stop] ||= ["SQLResult:"]
|
|
24
|
+
|
|
23
25
|
super(**kwargs)
|
|
24
26
|
end
|
|
25
27
|
|
|
@@ -73,6 +75,7 @@ module Boxcars
|
|
|
73
75
|
|
|
74
76
|
def get_embedded_sql_answer(text)
|
|
75
77
|
code = text[/^SQLQuery: (.*)/, 1]
|
|
78
|
+
code = extract_code text.split('SQLQuery:').last.strip
|
|
76
79
|
Boxcars.debug code, :yellow
|
|
77
80
|
output = clean_up_output(connection.exec_query(code))
|
|
78
81
|
Result.new(status: :ok, answer: output, explanation: "Answer: #{output.to_json}", code: code)
|
|
@@ -87,7 +90,8 @@ module Boxcars
|
|
|
87
90
|
when /^Answer:/
|
|
88
91
|
Result.from_text(text)
|
|
89
92
|
else
|
|
90
|
-
Result.from_error("
|
|
93
|
+
Result.from_error("Your answer wasn't formatted properly - try again. I expected your answer to " \
|
|
94
|
+
"start with \"SQLQuery:\".")
|
|
91
95
|
end
|
|
92
96
|
end
|
|
93
97
|
|
|
@@ -99,8 +103,8 @@ module Boxcars
|
|
|
99
103
|
"to return the most interesting examples in the database.\n",
|
|
100
104
|
"Never query for all the columns from a specific table, only ask for the elevant columns given the question.\n",
|
|
101
105
|
"Pay attention to use only the column names that you can see in the schema description. Be careful to ",
|
|
102
|
-
"not query for columns that do not exist. Also, pay attention to which column is in which table
|
|
103
|
-
|
|
106
|
+
"not query for columns that do not exist. Also, pay attention to which column is in which table.\n",
|
|
107
|
+
"Use the following format:\n",
|
|
104
108
|
"Question: 'Question here'\n",
|
|
105
109
|
"SQLQuery: 'SQL Query to run'\n",
|
|
106
110
|
"SQLResult: 'Result of the SQLQuery'\n",
|
|
@@ -49,11 +49,15 @@ module Boxcars
|
|
|
49
49
|
else
|
|
50
50
|
# the thought should be the frist line here if it doesn't start with "Action:"
|
|
51
51
|
thought = engine_output.split(/\n+/).reject(&:empty?).first
|
|
52
|
-
Boxcars.debug("
|
|
53
|
-
regex = /Action: (?<action>.*)\
|
|
52
|
+
Boxcars.debug("Thought: #{thought}", :yellow)
|
|
53
|
+
regex = /Action: (?<action>.*)\n+Action Input: (?<action_input>.*)/
|
|
54
54
|
match = regex.match(engine_output)
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
|
|
56
|
+
# we have an unexpected output from the engine
|
|
57
|
+
unless match
|
|
58
|
+
return [:error, "You gave me an improperly fomatted answer - try again. For example, if you know the final anwwer, " \
|
|
59
|
+
"start with #{FINAL_ANSWER_ACTION.inspect}"]
|
|
60
|
+
end
|
|
57
61
|
|
|
58
62
|
action = match[:action].strip
|
|
59
63
|
action_input = match[:action_input].strip.delete_prefix('"').delete_suffix('"')
|
|
@@ -63,24 +67,25 @@ module Boxcars
|
|
|
63
67
|
|
|
64
68
|
CTEMPLATE = [
|
|
65
69
|
syst("Answer the following questions as best you can. You have access to the following actions:\n",
|
|
66
|
-
"%<boxcar_descriptions>s"
|
|
67
|
-
|
|
70
|
+
"%<boxcar_descriptions>s\n",
|
|
71
|
+
"Use the following format:\n",
|
|
68
72
|
"Question: the input question you must answer\n",
|
|
69
73
|
"Thought: you should always think about what to do\n",
|
|
70
|
-
"Action: the action to take, should be one
|
|
74
|
+
"Action: the action to take, should be one from this list: %<boxcar_names>s\n",
|
|
71
75
|
"Action Input: the input to the action\n",
|
|
72
76
|
"Observation: the result of the action\n",
|
|
73
77
|
"... (this Thought/Action/Action Input/Observation sequence can repeat N times)\n",
|
|
74
|
-
"Thought: I
|
|
78
|
+
"Thought: I know the final answer\n",
|
|
75
79
|
"Final Answer: the final answer to the original input question\n",
|
|
76
|
-
"Next Actions:
|
|
80
|
+
"Next Actions: Up to 3 logical suggested next questions for the user to ask after getting this answer.\n",
|
|
81
|
+
"Remember to start a line with \"Final Answer:\" to give me the final answer.\n",
|
|
77
82
|
"Begin!"),
|
|
78
83
|
user("Question: %<input>s"),
|
|
79
84
|
assi("Thought: %<agent_scratchpad>s")
|
|
80
85
|
].freeze
|
|
81
86
|
|
|
82
87
|
def boxcar_names
|
|
83
|
-
@boxcar_names ||= boxcars.map(&:name)
|
|
88
|
+
@boxcar_names ||= "[#{boxcars.map(&:name).join(', ')}]"
|
|
84
89
|
end
|
|
85
90
|
|
|
86
91
|
def boxcar_descriptions
|
data/lib/boxcars/train.rb
CHANGED
|
@@ -27,6 +27,7 @@ module Boxcars
|
|
|
27
27
|
# Extract the boxcar name and input from the text.
|
|
28
28
|
# @param text [String] The text to extract from.
|
|
29
29
|
def extract_boxcar_and_input(text)
|
|
30
|
+
Result.new(status: :ok, answer: text, explanation: engine_output)
|
|
30
31
|
end
|
|
31
32
|
|
|
32
33
|
# build the scratchpad for the engine
|
|
@@ -56,6 +57,7 @@ module Boxcars
|
|
|
56
57
|
end
|
|
57
58
|
if parsed_output.is_a?(Result)
|
|
58
59
|
TrainAction.from_result(boxcar: "Final Answer", result: parsed_output, log: full_output)
|
|
60
|
+
# elsif parsed_output[0] == "Error"
|
|
59
61
|
else
|
|
60
62
|
TrainAction.new(boxcar: parsed_output[0], boxcar_input: parsed_output[1], log: full_output)
|
|
61
63
|
end
|
|
@@ -198,6 +200,9 @@ module Boxcars
|
|
|
198
200
|
Boxcars.error "Error in #{boxcar.name} boxcar#call: #{e}", :red
|
|
199
201
|
observation = "Error - #{e}, correct and try again."
|
|
200
202
|
end
|
|
203
|
+
elsif output.boxcar == :error
|
|
204
|
+
observation = output.log
|
|
205
|
+
return_direct = false
|
|
201
206
|
else
|
|
202
207
|
observation = "#{output.boxcar} is not a valid boxcar, try another one."
|
|
203
208
|
return_direct = false
|
data/lib/boxcars/version.rb
CHANGED
data/lib/boxcars.rb
CHANGED
|
@@ -25,12 +25,13 @@ module Boxcars
|
|
|
25
25
|
# Configuration contains gem settings
|
|
26
26
|
class Configuration
|
|
27
27
|
attr_writer :openai_access_token, :serpapi_api_key
|
|
28
|
-
attr_accessor :organization_id, :logger, :log_prompts, :default_train, :default_engine
|
|
28
|
+
attr_accessor :organization_id, :logger, :log_prompts, :log_generated, :default_train, :default_engine
|
|
29
29
|
|
|
30
30
|
def initialize
|
|
31
31
|
@organization_id = nil
|
|
32
32
|
@logger = Rails.logger if defined?(Rails)
|
|
33
33
|
@log_prompts = ENV.fetch("LOG_PROMPTS", false)
|
|
34
|
+
@log_generated = ENV.fetch("LOG_GEN", false)
|
|
34
35
|
end
|
|
35
36
|
|
|
36
37
|
# @return [String] The OpenAI Access Token either from arg or env.
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: boxcars
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Francis Sullivan
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: exe
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2023-03-
|
|
12
|
+
date: 2023-03-20 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: debug
|