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 +4 -4
- data/CHANGELOG.md +46 -0
- data/Gemfile.lock +1 -1
- data/lib/boxcars/boxcar/active_record.rb +77 -51
- data/lib/boxcars/boxcar/calculator.rb +34 -47
- data/lib/boxcars/boxcar/engine_boxcar.rb +43 -47
- data/lib/boxcars/boxcar/sql.rb +59 -43
- data/lib/boxcars/boxcar.rb +34 -7
- data/lib/boxcars/conversation.rb +98 -0
- data/lib/boxcars/conversation_prompt.rb +40 -0
- data/lib/boxcars/engine/openai.rb +23 -15
- data/lib/boxcars/prompt.rb +25 -38
- data/lib/boxcars/result.rb +68 -0
- data/lib/boxcars/ruby_repl.rb +7 -6
- data/lib/boxcars/train/train_action.rb +16 -2
- data/lib/boxcars/train/zero_shot.rb +52 -46
- data/lib/boxcars/train.rb +21 -22
- data/lib/boxcars/version.rb +1 -1
- data/lib/boxcars.rb +7 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 967f04279864f1419fbe3e9f02817cc8f5594317c6f2b06dd49d1c5e1baa67a9
|
4
|
+
data.tar.gz: 156ca722488b91cefce4d03026490434004e1dcbd1f7c7cd714f1d3f6871cbc0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
@@ -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 :
|
19
|
-
def initialize(
|
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
|
-
|
25
|
-
|
26
|
-
kwargs[:
|
27
|
-
|
28
|
-
|
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
|
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
|
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
|
144
|
-
|
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
|
-
|
153
|
-
|
154
|
-
|
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
|
-
|
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
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
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
|
-
@
|
194
|
-
|
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
|
-
|
17
|
-
|
18
|
-
|
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
|
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)
|
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
|
-
|
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
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
-
@
|
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
|
-
|
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
|
18
|
-
@stop = kwargs
|
19
|
-
super(
|
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
|
-
|
43
|
-
input_list.
|
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, "
|
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
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
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.
|