boxcars 0.2.2 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 967f04279864f1419fbe3e9f02817cc8f5594317c6f2b06dd49d1c5e1baa67a9
4
- data.tar.gz: 156ca722488b91cefce4d03026490434004e1dcbd1f7c7cd714f1d3f6871cbc0
3
+ metadata.gz: 6a9d94c026cf616e590d293d4ef2b0c655ddc6e3ae40115b36ebbfbbee1cb9a4
4
+ data.tar.gz: 549a877fdf1b329f402f3a159420bc6599677bcd40f2776c8bea57fade099b14
5
5
  SHA512:
6
- metadata.gz: 14158dbd32be6d95e35c2331e26d2e8883bc7621585d5f24b54d1fce6ba78b15e3d67df73b9ed13faf1fd5c306b9341cd4705a3ecf9e5beaf9d47f05e7c1f438
7
- data.tar.gz: 90722b9f3b7f34607cd4ee4afe5888c625c5724904943a5160021b48ca1944af5a87269613aca554b595c73c32c923ee9fc442eb8b0271a712fe6aac5702f2ac
6
+ metadata.gz: '082a569848bf46276dadf9e2461e1bded9e107a2ba344fed06382c98548aa78b5012c63cdeeb27f394783b25bb6dea6d6bd10382d1f0113b8de548a366ca2b4e'
7
+ data.tar.gz: 25452562685c57ba7ded3de67a4c27f7d15e9373f5370f061d75d9d4f32e4e1836e9ea6947bfef19d7a32fe6c130d4f816fe5d9529ea1db5f38f7b0bb2837ccb
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.1...HEAD)
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- boxcars (0.2.2)
4
+ boxcars (0.2.4)
5
5
  google_search_results (~> 2.2)
6
6
  ruby-openai (~> 3.0)
7
7
 
data/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
  <a href="https://github.com/BoxcarsAI/boxcars/blob/main/LICENSE.txt"><img src="https://img.shields.io/badge/license-MIT-informational" alt="License"></a>
12
12
  </p>
13
13
 
14
- Boxcars is a gem that enables you to create new systems with AI composability, using various concepts such as OpenAI, Search, SQL, Rails Active Record and more. This can even be extended with your concepts as well.(including your concepts).
14
+ Boxcars is a gem that enables you to create new systems with AI composability, using various concepts such as OpenAI, Search, SQL, Rails Active Record and more. This can even be extended with your concepts as well (including your concepts).
15
15
 
16
16
  This gem was inspired by the popular Python library Langchain. However, we wanted to give it a Ruby spin and make it more user-friendly for beginners to get started.
17
17
 
@@ -23,6 +23,11 @@ All of these concepts are in a module named Boxcars:
23
23
  - Prompt - used by an Engine to generate text results. Most of the Boxcars have built-in prompts, so you only need to worry about these if you are extending the system.
24
24
  - Engine - an entity that generates text from a Prompt. OpenAI's LLM text generator is the default Engine if no other is specified.
25
25
 
26
+ ## Security
27
+ Currently, our system is designed for individuals who already possess administrative privileges for their project. It is likely possible to manipulate the system's prompts to carry out malicious actions, but if you already have administrative access, you can perform such actions without requiring boxcars in the first place.
28
+
29
+ *Note:* We are actively seeking ways to improve our system's ability to identify and prevent any nefarious attempts from occurring. If you have any suggestions or recommendations, please feel free to share them with us by either finding an existing issue or creating a new one and providing us with your feedback.
30
+
26
31
  ## Installation
27
32
 
28
33
  Add this line to your application's Gemfile:
@@ -66,10 +71,10 @@ Produces:
66
71
  ```text
67
72
  > Entering Calculator#run
68
73
  what is pi to the forth power divided by 22.1?
69
- RubyREPL: puts(Math::PI**4 / 22.1)
74
+ RubyREPL: puts (Math::PI**4)/22.1
70
75
  Answer: 4.407651178009159
71
76
 
72
- 4.407651178009159
77
+ {"status":"ok","answer":"4.407651178009159","explanation":"Answer: 4.407651178009159","code":"puts (Math::PI**4)/22.1"}
73
78
  < Exiting Calculator#run
74
79
  4.407651178009159
75
80
  ```
@@ -94,27 +99,34 @@ Here is what we have so far, but please put up a PR with your new ideas.
94
99
  # run a Train for a calculator, and search using default Engine
95
100
  boxcars = [Boxcars::Calculator.new, Boxcars::Serp.new]
96
101
  train = Boxcars.train.new(boxcars: boxcars)
97
- puts train.run "What is pi times the square root of the average temperature in Austin TX in January?"
102
+ train.run "What is pi times the square root of the average temperature in Austin TX in January?"
98
103
  ```
99
104
  Produces:
100
105
  ```text
101
106
  > Entering Zero Shot#run
102
107
  What is pi times the square root of the average temperature in Austin TX in January?
108
+ Thought: We need to find the average temperature in Austin TX in January and then multiply it by pi and the square root of that value. We can use a search engine to find the average temperature and a calculator to perform the multiplication.
103
109
  Question: Average temperature in Austin TX in January
104
110
  Answer: increase from 62°F to 64°F
105
- #Observation: increase from 62°F to 64°F
111
+ Observation: increase from 62°F to 64°F
112
+ Thought: The average temperature in Austin TX in January is around 63°F.
106
113
  > Entering Calculator#run
107
- 64°F x pi
108
- RubyREPL: puts (64 * Math::PI).round(2)
109
- Answer: 201.06
114
+ pi * sqrt(63)
115
+ RubyREPL: puts(Math::PI * Math.sqrt(63))
116
+ Answer: 24.935618646198247
110
117
 
111
- 201.06
118
+ {"status":"ok","answer":"24.935618646198247","explanation":"Answer: 24.935618646198247","code":"puts(Math::PI * Math.sqrt(63))"}
112
119
  < Exiting Calculator#run
113
- #Observation: 201.06
114
- I now know the final answer
115
- Final Answer: 201.06
120
+ Observation: 24.935618646198247
121
+ The result of pi times the square root of the average temperature in Austin TX in January is approximately 24.94.
122
+
123
+ Final Answer: 24.94
124
+
125
+ Next Actions:
126
+ 1. What is the average temperature in Austin TX in July?
127
+ 2. What is the formula for calculating the area of a circle?
128
+ 3. What is the value of pi to 10 decimal places?
116
129
  < Exiting Zero Shot#run
117
- 201.06
118
130
  ```
119
131
  ### More Examples
120
132
  See [this](https://github.com/BoxcarsAI/boxcars/blob/main/notebooks/boxcars_examples.ipynb) Jupyter Notebook for more examples.
@@ -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
- rv = nil
74
+ result = nil
75
+ runtime_exception = nil
75
76
  ::ActiveRecord::Base.transaction do
76
- rv = yield
77
+ begin
78
+ result = yield
79
+ rescue SecurityError, ::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
- rv
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
@@ -85,13 +93,22 @@ module Boxcars
85
93
  bad_words = %w[commit drop_constraint drop_constraint! drop_extension drop_extension! drop_foreign_key drop_foreign_key! \
86
94
  drop_index drop_index! drop_join_table drop_join_table! drop_materialized_view drop_materialized_view! \
87
95
  drop_partition drop_partition! drop_schema drop_schema! drop_table drop_table! drop_trigger drop_trigger! \
88
- drop_view drop_view! eval execute reset revoke rollback truncate].freeze
96
+ drop_view drop_view! eval instance_eval send system execute reset revoke rollback truncate \
97
+ encrypted_password].freeze
89
98
  without_strings = code.gsub(/('([^'\\]*(\\.[^'\\]*)*)'|"([^"\\]*(\\.[^"\\]*)*"))/, 'XX')
90
- word_list = without_strings.split(/[.,()]/)
99
+
100
+ if without_strings.include?("`")
101
+ Boxcars.info "code included possibly destructive backticks #{code}", :red
102
+ return false
103
+ end
104
+
105
+ word_list = without_strings.split(/[.,() :]/)
106
+
107
+ puts word_list.inspect
91
108
 
92
109
  bad_words.each do |w|
93
110
  if word_list.include?(w)
94
- Boxcars.info "code included destructive instruction: #{w} #{code}", :red
111
+ Boxcars.info "code included possibly destructive instruction: '#{w}' in #{code}", :red
95
112
  return false
96
113
  end
97
114
  end
@@ -122,8 +139,11 @@ module Boxcars
122
139
  return true unless changes&.positive?
123
140
 
124
141
  Boxcars.debug "#{name}(Pending Changes): #{changes}", :yellow
125
- change_str = "#{changes} change#{'s' if changes.to_i > 1}"
126
- raise SecurityError, "Can not run code that makes #{change_str} in read-only mode" if read_only?
142
+ if read_only?
143
+ change_str = "#{changes} change#{'s' if changes.to_i > 1}"
144
+ Boxcars.error("Can not run code that makes #{change_str} in read-only mode", :red)
145
+ return false
146
+ end
127
147
 
128
148
  return approval_callback.call(changes, code) if approval_callback.is_a?(Proc)
129
149
 
@@ -151,30 +171,34 @@ module Boxcars
151
171
  output
152
172
  end
153
173
 
154
- def extract_code(code)
155
- case code
156
- when /^```ruby/
157
- code.split('```ruby').last.split('```').first.strip
158
- when /^`(.+)`/
159
- ::Regexp.last_match(1)
160
- else
161
- code
162
- end
174
+ def error_message(err, stage)
175
+ msg = err.message
176
+ msg = ::Regexp.last_match(1) if msg =~ /^(.+)' for #<Boxcars::ActiveRecord/
177
+ "#{stage} Error: #{msg} - please fix \"#{stage}:\" to not have this error"
163
178
  end
164
179
 
165
180
  def get_active_record_answer(text)
166
- code = extract_code text.split('ARChanges:').first.strip.split('ARCode:').last.strip
167
- changes_code = extract_code text.split('ARChanges:').last.strip if text =~ /^ARChanges:/
181
+ changes_code = extract_code text.split('ARCode:').first.split('ARChanges:').last.strip if text =~ /^ARChanges:/
182
+ code = extract_code text.split('ARCode:').last.strip
168
183
  return Result.new(status: :ok, explanation: "code to run", code: code, changes_code: changes_code) if code_only?
169
184
 
170
- raise SecurityError, "Permission to run code that makes changes denied" unless approved?(changes_code, code)
185
+ have_approval = false
186
+ begin
187
+ have_approval = approved?(changes_code, code)
188
+ rescue NameError, Error => e
189
+ return Result.new(status: :error, explanation: error_message(e, "ARChanges"), changes_code: changes_code)
190
+ end
191
+
192
+ raise SecurityError, "Permission to run code that makes changes denied" unless have_approval
171
193
 
172
- output = clean_up_output(run_active_record_code(code))
173
- Result.new(status: :ok, answer: output, explanation: "Answer: #{output.to_json}", code: code)
174
- rescue SecurityError, ConfigurationError => e
175
- raise e
176
- rescue Boxcars::Error => e
177
- Result.new(status: :error, answer: nil, explanation: "Error: #{e.message}", code: code)
194
+ begin
195
+ output = clean_up_output(run_active_record_code(code))
196
+ Result.new(status: :ok, answer: output, explanation: "Answer: #{output.to_json}", code: code)
197
+ rescue SecurityError => e
198
+ raise e
199
+ rescue ::StandardError => e
200
+ Result.new(status: :error, answer: nil, explanation: error_message(e, "ARCode"), code: code)
201
+ end
178
202
  end
179
203
 
180
204
  def get_answer(text)
@@ -184,7 +208,8 @@ module Boxcars
184
208
  when /^Answer:/
185
209
  Result.from_text(text)
186
210
  else
187
- Result.from_error("Try answering again. Expected your answer to start with 'ARCode:'. You gave me:\n#{text}")
211
+ Result.from_error("Error: Your answer wasn't formatted properly - try again. I expected your answer to " \
212
+ "start with \"ARChanges:\" or \"ARCode:\"")
188
213
  end
189
214
  end
190
215
 
@@ -196,18 +221,19 @@ module Boxcars
196
221
  "to at most %<top_k>s results.\n",
197
222
  "Never query for all the columns from a specific model, ",
198
223
  "only ask for the relevant attributes given the question.\n",
199
- "Also, pay attention to which attribute is in which model."),
200
- syst("Use the following format:\n",
224
+ "Also, pay attention to which attribute is in which model.\n\n",
225
+ "Use the following format:\n",
201
226
  "Question: ${{Question here}}\n",
202
- "ARCode: ${{Active Record code to run}} - make sure you use valid code\n",
203
227
  "ARChanges: ${{Active Record code to compute the number of records going to change}} - ",
204
- "Only add this line if the ARCode on the line before will make data changes.\n",
205
- "Answer: ${{Final answer here}}"),
206
- syst("Only use the following Active Record models: %<model_info>s\n",
207
- "Pay attention to use only the attribute names that you can see in the model description. ",
208
- "Be careful to not query for attributes that do not exist.\n"
228
+ "Only add this line if the ARCode on the next line will make data changes.\n",
229
+ "ARCode: ${{Active Record code to run}} - make sure you use valid code\n",
230
+ "Answer: ${{Final answer here}}\n\n",
231
+ "Only use the following Active Record models: %<model_info>s\n",
232
+ "Pay attention to use only the attribute names that you can see in the model description.\n",
233
+ "Do not make up variable or attribute names, and do not share variables between the code in ARChanges and ARCode\n",
234
+ "Be careful to not query for attributes that do not exist, and to use the format specified above.\n"
209
235
  ),
210
- assi("Question: %<question>s")
236
+ user("Question: %<question>s")
211
237
  ].freeze
212
238
 
213
239
  # 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
- t = predict(current_conversation: conversation, **prediction_variables(inputs)).strip
88
- answer = get_answer(t)
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
@@ -97,7 +100,7 @@ module Boxcars
97
100
  end
98
101
  Boxcars.error answer.to_json, :red
99
102
  { output_key => "Error: #{answer}" }
100
- rescue Boxcars::ConfigurationError => e
103
+ rescue Boxcars::ConfigurationError, Boxcars::SecurityError => e
101
104
  raise e
102
105
  rescue Boxcars::Error => e
103
106
  Boxcars.error e.message, :red
@@ -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
@@ -49,8 +49,8 @@ module Boxcars
49
49
  [:answer_box, :snippet_highlighted_words, 0],
50
50
  %i[sports_results game_spotlight],
51
51
  %i[knowledge_graph description],
52
- [:organic_results, 0, :snippet_highlighted_words, 0],
53
- [:organic_results, 0, :snippet]
52
+ [:organic_results, 0, :snippet],
53
+ [:organic_results, 0, :snippet_highlighted_words, 0]
54
54
  ].freeze
55
55
 
56
56
  def find_answer(res)
@@ -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("Try answering again. Expected your answer to start with 'SQLQuery:'. You gave me:\n#{text}")
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
- syst("Use the following format:\n",
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("Though: #{thought}", :cyan)
53
- regex = /Action: (?<action>.*)\nAction Input: (?<action_input>.*)/
52
+ Boxcars.debug("Thought: #{thought}", :yellow)
53
+ regex = /Action(?<extra>[\s\d]*): (?<action>.+?)\n+Action Input:(?<action_input>.+)/m
54
54
  match = regex.match(engine_output)
55
- # TODO: this should return an error to the results that can be used for corrections
56
- raise ValueError, "Could not parse engine output: #{engine_output}" unless match
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
- syst("Use the following format:\n",
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 of [%<boxcar_names>s]\n",
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 now know the final answer\n",
78
+ "Thought: I know the final answer\n",
75
79
  "Final Answer: the final answer to the original input question\n",
76
- "Next Actions: If you have them, up to 3 suggested actions for the user to take after getting this answer.\n",
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
@@ -194,10 +196,15 @@ module Boxcars
194
196
  begin
195
197
  observation = boxcar.run(output.boxcar_input)
196
198
  return_direct = boxcar.return_direct
199
+ rescue Boxcars::ConfigurationError, Boxcars::SecurityError => e
200
+ raise e
197
201
  rescue StandardError => e
198
202
  Boxcars.error "Error in #{boxcar.name} boxcar#call: #{e}", :red
199
203
  observation = "Error - #{e}, correct and try again."
200
204
  end
205
+ elsif output.boxcar == :error
206
+ observation = output.log
207
+ return_direct = false
201
208
  else
202
209
  observation = "#{output.boxcar} is not a valid boxcar, try another one."
203
210
  return_direct = false
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Boxcars
4
4
  # The current version of the gem.
5
- VERSION = "0.2.2"
5
+ VERSION = "0.2.4"
6
6
  end
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.2
4
+ version: 0.2.4
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-16 00:00:00.000000000 Z
12
+ date: 2023-03-28 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: debug