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 +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.
|