boxcars 0.2.0 → 0.2.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: 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.