boxcars 0.2.0 → 0.2.2

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: 34001190ca60f2b9fe68d603267ffccaaef398cae2f8054e34d6d0926fd8bf9d
4
- data.tar.gz: 7918d0bad50e6758490a9baa58393e38071e524b0d284c43dd479cf1d091deab
3
+ metadata.gz: 967f04279864f1419fbe3e9f02817cc8f5594317c6f2b06dd49d1c5e1baa67a9
4
+ data.tar.gz: 156ca722488b91cefce4d03026490434004e1dcbd1f7c7cd714f1d3f6871cbc0
5
5
  SHA512:
6
- metadata.gz: dacd721802478a783ac9c50c4ebd959764b7601254c3a4b94546f093177cd94bdb4636d9f635154e6419a7d8505152ac7092c491238baab8ac0e47277323a7af
7
- data.tar.gz: 73c638b8486681e3268ff94e390550f565c011891e9c025556f41df4e0e741ae705fa9fdeb6c3d1996919344abbfff84a8f1454e22a5d108dcae6d65e4718403
6
+ metadata.gz: 14158dbd32be6d95e35c2331e26d2e8883bc7621585d5f24b54d1fce6ba78b15e3d67df73b9ed13faf1fd5c306b9341cd4705a3ecf9e5beaf9d47f05e7c1f438
7
+ data.tar.gz: 90722b9f3b7f34607cd4ee4afe5888c625c5724904943a5160021b48ca1944af5a87269613aca554b595c73c32c923ee9fc442eb8b0271a712fe6aac5702f2ac
data/CHANGELOG.md CHANGED
@@ -1,5 +1,51 @@
1
1
  # Changelog
2
2
 
3
+ ## [Unreleased](https://github.com/BoxcarsAI/boxcars/tree/HEAD)
4
+
5
+ [Full Changelog](https://github.com/BoxcarsAI/boxcars/compare/v0.2.1...HEAD)
6
+
7
+ **Implemented enhancements:**
8
+
9
+ - return a structure from boxcars instead of just a string [\#31](https://github.com/BoxcarsAI/boxcars/issues/31)
10
+ - modify SQL boxcar to take a list of Tables. Handy if you have a large database with many tables. [\#19](https://github.com/BoxcarsAI/boxcars/issues/19)
11
+
12
+ **Closed issues:**
13
+
14
+ - make a new type of prompt that uses conversations [\#38](https://github.com/BoxcarsAI/boxcars/issues/38)
15
+ - Add test coverage [\#8](https://github.com/BoxcarsAI/boxcars/issues/8)
16
+
17
+ ## [v0.2.1](https://github.com/BoxcarsAI/boxcars/tree/v0.2.1) (2023-03-08)
18
+
19
+ [Full Changelog](https://github.com/BoxcarsAI/boxcars/compare/v0.2.0...v0.2.1)
20
+
21
+ **Implemented enhancements:**
22
+
23
+ - add the ability to use the ChatGPT API - a ChatGPT Boxcar::Engine [\#32](https://github.com/BoxcarsAI/boxcars/issues/32)
24
+ - Prompt simplification [\#29](https://github.com/BoxcarsAI/boxcars/issues/29)
25
+
26
+ **Merged pull requests:**
27
+
28
+ - use structured results internally and bring SQL up to parity with ActiveRecord boxcar. [\#36](https://github.com/BoxcarsAI/boxcars/pull/36) ([francis](https://github.com/francis))
29
+
30
+ ## [v0.2.0](https://github.com/BoxcarsAI/boxcars/tree/v0.2.0) (2023-03-07)
31
+
32
+ [Full Changelog](https://github.com/BoxcarsAI/boxcars/compare/v0.1.8...v0.2.0)
33
+
34
+ **Merged pull requests:**
35
+
36
+ - Default to chatgpt [\#35](https://github.com/BoxcarsAI/boxcars/pull/35) ([francis](https://github.com/francis))
37
+
38
+ ## [v0.1.8](https://github.com/BoxcarsAI/boxcars/tree/v0.1.8) (2023-03-02)
39
+
40
+ [Full Changelog](https://github.com/BoxcarsAI/boxcars/compare/v0.1.7...v0.1.8)
41
+
42
+ **Merged pull requests:**
43
+
44
+ - return JSON from the Active Record boxcar [\#34](https://github.com/BoxcarsAI/boxcars/pull/34) ([francis](https://github.com/francis))
45
+ - validate return values from Open AI API [\#33](https://github.com/BoxcarsAI/boxcars/pull/33) ([francis](https://github.com/francis))
46
+ - simplify prompting and parameters used. refs \#29 [\#30](https://github.com/BoxcarsAI/boxcars/pull/30) ([francis](https://github.com/francis))
47
+ - \[infra\] Added sample .env file and updated the lookup to save the key [\#27](https://github.com/BoxcarsAI/boxcars/pull/27) ([AKovtunov](https://github.com/AKovtunov))
48
+
3
49
  ## [v0.1.7](https://github.com/BoxcarsAI/boxcars/tree/v0.1.7) (2023-02-27)
4
50
 
5
51
  [Full Changelog](https://github.com/BoxcarsAI/boxcars/compare/v0.1.6...v0.1.7)
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- boxcars (0.2.0)
4
+ boxcars (0.2.2)
5
5
  google_search_results (~> 2.2)
6
6
  ruby-openai (~> 3.0)
7
7
 
@@ -7,28 +7,24 @@ module Boxcars
7
7
  # the description of this engine boxcar
8
8
  ARDESC = "useful for when you need to query a database for an application named %<name>s."
9
9
  LOCKED_OUT_MODELS = %w[ActiveRecord::SchemaMigration ActiveRecord::InternalMetadata ApplicationRecord].freeze
10
- attr_accessor :connection, :requested_models, :read_only, :approval_callback
10
+ attr_accessor :connection, :requested_models, :read_only, :approval_callback, :code_only
11
11
  attr_reader :except_models
12
12
 
13
- # @param engine [Boxcars::Engine] The engine to user for this boxcar. Can be inherited from a train if nil.
14
13
  # @param models [Array<ActiveRecord::Model>] The models to use for this boxcar. Will use all if nil.
14
+ # @param except_models [Array<ActiveRecord::Model>] The models to exclude from this boxcar. Will exclude none if nil.
15
15
  # @param read_only [Boolean] Whether to use read only models. Defaults to true unless you pass an approval function.
16
16
  # @param approval_callback [Proc] A function to call to approve changes. Defaults to nil.
17
17
  # @param kwargs [Hash] Any other keyword arguments. These can include:
18
- # :name, :description, :prompt, :except_models, :top_k, and :stop
19
- def initialize(engine: nil, models: nil, read_only: nil, approval_callback: nil, **kwargs)
20
- check_models(models)
21
- @except_models = LOCKED_OUT_MODELS + kwargs[:except_models].to_a
18
+ # :name, :description, :prompt, :except_models, :top_k, :stop, :code_only and :engine
19
+ def initialize(models: nil, except_models: nil, read_only: nil, approval_callback: nil, **kwargs)
20
+ check_models(models, except_models)
22
21
  @approval_callback = approval_callback
23
22
  @read_only = read_only.nil? ? !approval_callback : read_only
24
- the_prompt = kwargs[prompt] || my_prompt
25
- name = kwargs[:name] || "Data"
26
- kwargs[:stop] ||= ["Answer:"]
27
- super(name: name,
28
- description: kwargs[:description] || format(ARDESC, name: name),
29
- engine: engine,
30
- prompt: the_prompt,
31
- **kwargs)
23
+ @code_only = kwargs.delete(:code_only) || false
24
+ kwargs[:name] ||= "Data"
25
+ kwargs[:description] ||= format(ARDESC, name: name)
26
+ kwargs[:prompt] ||= my_prompt
27
+ super(**kwargs)
32
28
  end
33
29
 
34
30
  # @return Hash The additional variables for this boxcar.
@@ -42,7 +38,11 @@ module Boxcars
42
38
  read_only
43
39
  end
44
40
 
45
- def check_models(models)
41
+ def code_only?
42
+ code_only
43
+ end
44
+
45
+ def check_models(models, exceptions)
46
46
  if models.is_a?(Array) && models.length.positive?
47
47
  @requested_models = models
48
48
  models.each do |m|
@@ -51,6 +51,7 @@ module Boxcars
51
51
  elsif models
52
52
  raise ArgumentError, "models needs to be an array of Active Record models"
53
53
  end
54
+ @except_models = LOCKED_OUT_MODELS + exceptions.to_a
54
55
  end
55
56
 
56
57
  def wanted_models
@@ -120,7 +121,7 @@ module Boxcars
120
121
  changes = change_count(changes_code)
121
122
  return true unless changes&.positive?
122
123
 
123
- Boxcars.debug "Pending Changes: #{changes}", :yellow, style: :bold
124
+ Boxcars.debug "#{name}(Pending Changes): #{changes}", :yellow
124
125
  change_str = "#{changes} change#{'s' if changes.to_i > 1}"
125
126
  raise SecurityError, "Can not run code that makes #{change_str} in read-only mode" if read_only?
126
127
 
@@ -130,6 +131,7 @@ module Boxcars
130
131
  end
131
132
 
132
133
  def run_active_record_code(code)
134
+ code = ::Regexp.last_match(1) if code =~ /`(.+)`/
133
135
  Boxcars.debug code, :yellow
134
136
  if read_only?
135
137
  rollback_after_running do
@@ -140,18 +142,39 @@ module Boxcars
140
142
  end
141
143
  end
142
144
 
143
- def get_active_record_answer(text)
144
- code = text[/^ARCode: (.*)/, 1]
145
- changes_code = text[/^ARChanges: (.*)/, 1]
146
- raise SecurityError, "Permission to run code that makes changes denied" unless approved?(changes_code, code)
147
-
148
- output = run_active_record_code(code)
145
+ def clean_up_output(output)
146
+ output = output.as_json if output.is_a?(::ActiveRecord::Result)
149
147
  output = 0 if output.is_a?(Array) && output.empty?
150
148
  output = output.first if output.is_a?(Array) && output.length == 1
151
149
  output = output[output.keys.first] if output.is_a?(Hash) && output.length == 1
152
- "Answer: #{output.to_json}"
153
- rescue StandardError => e
154
- "Error: #{e.message}"
150
+ output = output.as_json if output.is_a?(::ActiveRecord::Relation)
151
+ output
152
+ end
153
+
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
163
+ end
164
+
165
+ 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:/
168
+ return Result.new(status: :ok, explanation: "code to run", code: code, changes_code: changes_code) if code_only?
169
+
170
+ raise SecurityError, "Permission to run code that makes changes denied" unless approved?(changes_code, code)
171
+
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)
155
178
  end
156
179
 
157
180
  def get_answer(text)
@@ -159,39 +182,42 @@ module Boxcars
159
182
  when /^ARCode:/
160
183
  get_active_record_answer(text)
161
184
  when /^Answer:/
162
- text
185
+ Result.from_text(text)
163
186
  else
164
- raise Boxcars::Error "Unknown format from engine: #{text}"
187
+ Result.from_error("Try answering again. Expected your answer to start with 'ARCode:'. You gave me:\n#{text}")
165
188
  end
166
189
  end
167
190
 
168
- TEMPLATE = <<~IPT
169
- Given an input question, first create a syntactically correct Rails Active Record code to run,
170
- then look at the results of the code and return the answer. Unless the user specifies
171
- in her question a specific number of examples she wishes to obtain, limit your code
172
- to at most %<top_k>s results.
173
-
174
- Never query for all the columns from a specific model, only ask for a the few relevant attributes given the question.
175
-
176
- Pay attention to use only the attribute names that you can see in the model description. Be careful to not query for attributes that do not exist.
177
- Also, pay attention to which attribute is in which model.
178
-
179
- Use the following format:
180
- Question: "Question here"
181
- ARCode: "Active Record code to run"
182
- ARChanges: "Active Record code to compute the number of records going to change" - Only add this line if the ARCode on the line before will make data changes
183
- Answer: "Final answer here"
184
-
185
- Only use the following Active Record models:
186
- %<model_info>s
187
-
188
- Question: %<question>s
189
- IPT
191
+ CTEMPLATE = [
192
+ syst("You are a Ruby on Rails Active Record code generator"),
193
+ syst("Given an input question, first create a syntactically correct Rails Active Record code to run, ",
194
+ "then look at the results of the code and return the answer. Unless the user specifies ",
195
+ "in her question a specific number of examples she wishes to obtain, limit your code ",
196
+ "to at most %<top_k>s results.\n",
197
+ "Never query for all the columns from a specific model, ",
198
+ "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",
201
+ "Question: ${{Question here}}\n",
202
+ "ARCode: ${{Active Record code to run}} - make sure you use valid code\n",
203
+ "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"
209
+ ),
210
+ assi("Question: %<question>s")
211
+ ].freeze
190
212
 
191
213
  # The prompt to use for the engine.
192
214
  def my_prompt
193
- @my_prompt ||= Prompt.new(input_variables: [:question], other_inputs: [:top_k], output_variables: [:answer],
194
- template: TEMPLATE)
215
+ @conversation ||= Conversation.new(lines: CTEMPLATE)
216
+ @my_prompt ||= ConversationPrompt.new(
217
+ conversation: @conversation,
218
+ input_variables: [:question],
219
+ other_inputs: [:top_k],
220
+ output_variables: [:answer])
195
221
  end
196
222
  end
197
223
  end
@@ -13,19 +13,18 @@ module Boxcars
13
13
  def initialize(engine: nil, prompt: nil, **kwargs)
14
14
  the_prompt = prompt || my_prompt
15
15
  kwargs[:stop] ||= ["```output"]
16
- super(name: kwargs[:name] || "Calculator",
17
- description: kwargs[:description] || CALCDESC,
18
- engine: engine,
19
- prompt: the_prompt,
20
- **kwargs)
16
+ kwargs[:name] ||= "Calculator"
17
+ kwargs[:description] ||= CALCDESC
18
+ super(engine: engine, prompt: the_prompt, **kwargs)
21
19
  end
22
20
 
23
21
  private
24
22
 
25
23
  def get_embedded_ruby_answer(text)
26
- code = text[8..-4].split("```").first.strip
24
+ code = text.split("```ruby\n").last.split("```").first.strip
25
+ # code = text[8..-4].split("```").first.strip
27
26
  ruby_executor = Boxcars::RubyREPL.new
28
- ruby_executor.call(code: code).strip
27
+ ruby_executor.call(code: code)
29
28
  end
30
29
 
31
30
  def get_answer(text)
@@ -33,54 +32,42 @@ module Boxcars
33
32
  when /^```ruby/
34
33
  get_embedded_ruby_answer(text)
35
34
  when /^Answer:/
36
- text
35
+ Result.from_text(text)
37
36
  else
38
- raise Boxcars::Error "Unknown format from engine: #{text}"
37
+ Result.new(status: :error,
38
+ explanation: "Error: expecting your response to begin with '```ruby'. Try answering the question again.")
39
39
  end
40
40
  end
41
41
 
42
42
  # our template
43
- # rubocop:disable Style/RedundantHeredocDelimiterQuotes
44
- TEMPLATE = <<~'IPT'
45
- You are GPT-3, and you can't do math.
46
- You can do basic math, and your memorization abilities are impressive, but you can't do any complex calculations that a human could not do in their head. You also have an annoying tendency to just make up highly specific, but wrong, answers.
47
- So we hooked you up to a Ruby 3 kernel, and now you can execute code written in the Ruby programming language. If anyone gives you a hard math problem, just use this format and we’ll take care of the rest:
48
-
49
- Question: ${{Question with hard calculation.}}
50
- ```ruby
51
- ${{Code that prints what you need to know}}
52
- ```
53
- ```output
54
- ${{Output of your code}}
55
- ```
56
- Answer: ${{Answer}}
57
-
58
- Otherwise, use this simpler format:
59
-
60
- Question: ${{Question without hard calculation}}
61
- Answer: ${{Answer}}
62
-
63
- Begin.
64
-
65
- Question: What is 37593 * 67?
66
- ```ruby
67
- puts(37593 * 67)
68
- ```
69
- ```output
70
- 2518731
71
- ```
72
- Answer: 2518731
73
-
74
- Question: what is 2518731 + 0?
75
- Answer: 2518731
76
-
77
- Question: %<question>s
78
- IPT
79
- # rubocop:enable Style/RedundantHeredocDelimiterQuotes
43
+ CTEMPLATE = [
44
+ syst("You can do basic math, but for any hard calculations that a human could not do ",
45
+ "in their head, use the following approach instead. ",
46
+ "Return code written in the Ruby programming language that prints the results. ",
47
+ "If anyone gives you a hard math problem, just ",
48
+ "use the following format and we’ll take care of the rest:\n",
49
+ "${{Question with hard calculation.}}\n",
50
+ "reply only with the following format:\n",
51
+ "```ruby\n${{only Ruby code that prints the answer}}\n```\n",
52
+ "```output\n${{Output of your code}}\n```\n\n",
53
+ "Otherwise, you should use this simpler format:\n",
54
+ "${{Question without hard calculation}}\n",
55
+ "Answer: ${{Answer}}\n\n",
56
+ "Do not give an explanation of the answer and make sure your answer starts with either 'Answer:' or '```ruby'."),
57
+ syst("here is a hard example:\n", "the user asks: What is 37593 * 67?\n",
58
+ "your answer: ```ruby\nputs(37593 * 67)\n```\n```output\n2518731\n```\nAnswer: 2518731"),
59
+ syst("basic example:\n", "user asks: What is 2518731 + 0?\n", "you answer: Answer: 2518731"),
60
+ syst("Begin."),
61
+ user("%<question>s")
62
+ ].freeze
80
63
 
81
64
  # The prompt to use for the engine.
82
65
  def my_prompt
83
- @my_prompt ||= Prompt.new(input_variables: [:question], output_variables: [:answer], template: TEMPLATE)
66
+ @conversation ||= Conversation.new(lines: CTEMPLATE)
67
+ @my_prompt ||= ConversationPrompt.new(
68
+ conversation: @conversation,
69
+ input_variables: [:question],
70
+ output_variables: [:answer])
84
71
  end
85
72
  end
86
73
  end
@@ -8,15 +8,14 @@ module Boxcars
8
8
 
9
9
  # A Boxcar is a container for a single tool to run.
10
10
  # @param prompt [Boxcars::Prompt] The prompt to use for this boxcar with sane defaults.
11
- # @param name [String] The name of the boxcar. Defaults to classname.
12
- # @param description [String] A description of the boxcar.
13
11
  # @param engine [Boxcars::Engine] The engine to user for this boxcar. Can be inherited from a train if nil.
14
- def initialize(prompt:, engine: nil, name: nil, description: nil, **kwargs)
12
+ # @param kwargs [Hash] Additional arguments including: name, description, top_k, return_direct, and stop
13
+ def initialize(prompt:, engine: nil, **kwargs)
15
14
  @prompt = prompt
16
15
  @engine = engine || Boxcars.engine.new
17
- @top_k = kwargs[:top_k] || 5
18
- @stop = kwargs[:stop] || ["Answer:"]
19
- super(name: name, description: description)
16
+ @top_k = kwargs.delete(:top_k) || 5
17
+ @stop = kwargs.delete(:stop) || ["Answer:"]
18
+ super(**kwargs)
20
19
  end
21
20
 
22
21
  # input keys for the prompt
@@ -34,60 +33,39 @@ module Boxcars
34
33
  prompt.output_variables
35
34
  end
36
35
 
36
+ # the first output key
37
+ def output_key
38
+ output_keys.first
39
+ end
40
+
37
41
  # generate a response from the engine
38
42
  # @param input_list [Array<Hash>] A list of hashes of input values to use for the prompt.
43
+ # @param current_conversation [Boxcars::Conversation] Optional ongoing conversation to use for the prompt.
39
44
  # @return [Boxcars::EngineResult] The result from the engine.
40
- def generate(input_list:)
45
+ def generate(input_list:, current_conversation: nil)
41
46
  stop = input_list[0][:stop]
42
- prompts = []
43
- input_list.each do |inputs|
44
- # prompt.missing_variables?(inputs)
45
- new_prompt = prompt.format(**inputs)
46
- Boxcars.debug("Prompt after formatting:\n#{new_prompt}", :cyan) if Boxcars.configuration.log_prompts
47
- prompts.push(new_prompt)
48
- end
47
+ the_prompt = current_conversation ? prompt.with_conversation(current_conversation) : prompt
48
+ prompts = input_list.map { |inputs| [the_prompt, inputs] }
49
49
  engine.generate(prompts: prompts, stop: stop)
50
50
  end
51
51
 
52
52
  # apply a response from the engine
53
53
  # @param input_list [Array<Hash>] A list of hashes of input values to use for the prompt.
54
+ # @param current_conversation [Boxcars::Conversation] Optional ongoing conversation to use for the prompt.
54
55
  # @return [Hash] A hash of the output key and the output value.
55
- def apply(input_list:)
56
- response = generate(input_list: input_list)
56
+ def apply(input_list:, current_conversation: nil)
57
+ response = generate(input_list: input_list, current_conversation: current_conversation)
57
58
  response.generations.to_h do |generation|
58
59
  [output_keys.first, generation[0].text]
59
60
  end
60
61
  end
61
62
 
62
63
  # predict a response from the engine
64
+ # @param current_conversation [Boxcars::Conversation] Optional ongoing conversation to use for the prompt.
63
65
  # @param kwargs [Hash] A hash of input values to use for the prompt.
64
66
  # @return [String] The output value.
65
- def predict(**kwargs)
66
- apply(input_list: [kwargs])[output_keys.first]
67
- end
68
-
69
- # predict a response from the engine and parse it
70
- # @param kwargs [Hash] A hash of input values to use for the prompt.
71
- # @return [String] The output value.
72
- def predict_and_parse(**kwargs)
73
- result = predict(**kwargs)
74
- if prompt.output_parser
75
- prompt.output_parser.parse(result)
76
- else
77
- result
78
- end
79
- end
80
-
81
- # apply a response from the engine and parse it
82
- # @param input_list [Array<Hash>] A list of hashes of input values to use for the prompt.
83
- # @return [Array<String>] The output values.
84
- def apply_and_parse(input_list:)
85
- result = apply(input_list: input_list)
86
- if prompt.output_parser
87
- result.map { |r| prompt.output_parser.parse(r[output_keys.first]) }
88
- else
89
- result
90
- end
67
+ def predict(current_conversation: nil, **kwargs)
68
+ apply(current_conversation: current_conversation, input_list: [kwargs])[output_keys.first]
91
69
  end
92
70
 
93
71
  # check that there is exactly one output key
@@ -95,17 +73,35 @@ module Boxcars
95
73
  def check_output_keys
96
74
  return unless output_keys.length != 1
97
75
 
98
- raise Boxcars::ArgumentError, "run not supported when there is not exactly one output key. Got #{output_keys}."
76
+ raise Boxcars::ArgumentError, "not supported when there is not exactly one output key. Got #{output_keys}."
99
77
  end
100
78
 
101
79
  # call the boxcar
102
80
  # @param inputs [Hash] The inputs to the boxcar.
103
81
  # @return [Hash] The outputs from the boxcar.
104
82
  def call(inputs:)
105
- t = predict(**prediction_variables(inputs)).strip
106
- answer = get_answer(t)
107
- Boxcars.debug answer, :magenta
108
- { output_keys.first => answer }
83
+ # if we get errors back, try predicting again giving the errors with the inputs
84
+ conversation = nil
85
+ answer = nil
86
+ 4.times do
87
+ t = predict(current_conversation: conversation, **prediction_variables(inputs)).strip
88
+ answer = get_answer(t)
89
+ if answer.status == :error
90
+ Boxcars.debug "have error, trying again: #{answer.answer}", :red
91
+ conversation ||= Conversation.new
92
+ conversation.add_user(answer.answer)
93
+ else
94
+ Boxcars.debug answer.to_json, :magenta
95
+ return { output_keys.first => answer }
96
+ end
97
+ end
98
+ Boxcars.error answer.to_json, :red
99
+ { output_key => "Error: #{answer}" }
100
+ rescue Boxcars::ConfigurationError => e
101
+ raise e
102
+ rescue Boxcars::Error => e
103
+ Boxcars.error e.message, :red
104
+ { output_key => "Error: #{e.message}" }
109
105
  end
110
106
 
111
107
  # @param inputs [Hash] The inputs to the boxcar.