boxcars 0.1.8 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/Gemfile.lock +2 -2
- data/lib/boxcars/boxcar/active_record.rb +33 -24
- data/lib/boxcars/boxcar/calculator.rb +7 -10
- data/lib/boxcars/boxcar/engine_boxcar.rb +3 -3
- data/lib/boxcars/boxcar/sql.rb +39 -15
- data/lib/boxcars/boxcar.rb +22 -6
- data/lib/boxcars/engine/openai.rb +13 -6
- data/lib/boxcars/result.rb +68 -0
- data/lib/boxcars/ruby_repl.rb +11 -3
- data/lib/boxcars/train/train_action.rb +16 -2
- data/lib/boxcars/train/zero_shot.rb +7 -4
- data/lib/boxcars/train.rb +7 -4
- data/lib/boxcars/version.rb +1 -1
- data/lib/boxcars.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 50a47cf449c063e7ca02cb36ad315008fa6a5813063a24d92ed4c0f94743c12e
|
4
|
+
data.tar.gz: fad47ad6fefc7d580251edf1675ae575a9f4eebf28fb903a1c82ba2868310bc4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '073919902e84d95158573400ebe593d1e5aa72cc1ab6efb0c4cfccaf9806a91a0c55913d9d9ec47dd8b56f7fb81660fdfd81753092e2a3bd590e4ef782c51ba8'
|
7
|
+
data.tar.gz: 87f1ec995889c7ca107ccf187f1b01874f136787861e5116abc13da5f204f4f3e7e8cf101692fed7285ac875f405e1f8e08701f3785e0c5e83f25d0e5a0b9b15
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,24 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v0.2.0](https://github.com/BoxcarsAI/boxcars/tree/v0.2.0) (2023-03-07)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/BoxcarsAI/boxcars/compare/v0.1.8...v0.2.0)
|
6
|
+
|
7
|
+
**Merged pull requests:**
|
8
|
+
|
9
|
+
- Default to chatgpt [\#35](https://github.com/BoxcarsAI/boxcars/pull/35) ([francis](https://github.com/francis))
|
10
|
+
|
11
|
+
## [v0.1.8](https://github.com/BoxcarsAI/boxcars/tree/v0.1.8) (2023-03-02)
|
12
|
+
|
13
|
+
[Full Changelog](https://github.com/BoxcarsAI/boxcars/compare/v0.1.7...v0.1.8)
|
14
|
+
|
15
|
+
**Merged pull requests:**
|
16
|
+
|
17
|
+
- return JSON from the Active Record boxcar [\#34](https://github.com/BoxcarsAI/boxcars/pull/34) ([francis](https://github.com/francis))
|
18
|
+
- validate return values from Open AI API [\#33](https://github.com/BoxcarsAI/boxcars/pull/33) ([francis](https://github.com/francis))
|
19
|
+
- simplify prompting and parameters used. refs \#29 [\#30](https://github.com/BoxcarsAI/boxcars/pull/30) ([francis](https://github.com/francis))
|
20
|
+
- \[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))
|
21
|
+
|
3
22
|
## [v0.1.7](https://github.com/BoxcarsAI/boxcars/tree/v0.1.7) (2023-02-27)
|
4
23
|
|
5
24
|
[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.1
|
4
|
+
boxcars (0.2.1)
|
5
5
|
google_search_results (~> 2.2)
|
6
6
|
ruby-openai (~> 3.0)
|
7
7
|
|
@@ -140,7 +140,7 @@ GEM
|
|
140
140
|
rubocop-rspec (2.18.1)
|
141
141
|
rubocop (~> 1.33)
|
142
142
|
rubocop-capybara (~> 2.17)
|
143
|
-
ruby-openai (3.
|
143
|
+
ruby-openai (3.5.0)
|
144
144
|
httparty (>= 0.18.1)
|
145
145
|
ruby-progressbar (1.11.0)
|
146
146
|
ruby2_keywords (0.0.5)
|
@@ -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
|
|
@@ -140,18 +141,26 @@ module Boxcars
|
|
140
141
|
end
|
141
142
|
end
|
142
143
|
|
144
|
+
def clean_up_output(output)
|
145
|
+
output = output.as_json if output.is_a?(::ActiveRecord::Result)
|
146
|
+
output = 0 if output.is_a?(Array) && output.empty?
|
147
|
+
output = output.first if output.is_a?(Array) && output.length == 1
|
148
|
+
output = output[output.keys.first] if output.is_a?(Hash) && output.length == 1
|
149
|
+
output = output.as_json if output.is_a?(::ActiveRecord::Relation)
|
150
|
+
output
|
151
|
+
end
|
152
|
+
|
143
153
|
def get_active_record_answer(text)
|
144
154
|
code = text[/^ARCode: (.*)/, 1]
|
145
155
|
changes_code = text[/^ARChanges: (.*)/, 1]
|
156
|
+
return Result.new(status: :ok, explanation: "code to run", code: code, changes_code: changes_code) if code_only?
|
157
|
+
|
146
158
|
raise SecurityError, "Permission to run code that makes changes denied" unless approved?(changes_code, code)
|
147
159
|
|
148
|
-
output = run_active_record_code(code)
|
149
|
-
output
|
150
|
-
output = output.first if output.is_a?(Array) && output.length == 1
|
151
|
-
output = output[output.keys.first] if output.is_a?(Hash) && output.length == 1
|
152
|
-
"Answer: #{output.to_json}"
|
160
|
+
output = clean_up_output(run_active_record_code(code))
|
161
|
+
Result.new(status: :ok, answer: output, explanation: "Answer: #{output.to_json}", code: code)
|
153
162
|
rescue StandardError => e
|
154
|
-
"Error: #{e.message}"
|
163
|
+
Result.new(status: :error, answer: nil, explanation: "Error: #{e.message}", code: code)
|
155
164
|
end
|
156
165
|
|
157
166
|
def get_answer(text)
|
@@ -159,9 +168,9 @@ module Boxcars
|
|
159
168
|
when /^ARCode:/
|
160
169
|
get_active_record_answer(text)
|
161
170
|
when /^Answer:/
|
162
|
-
text
|
171
|
+
Result.from_text(text)
|
163
172
|
else
|
164
|
-
|
173
|
+
Result.from_error("Unknown format from engine: #{text}")
|
165
174
|
end
|
166
175
|
end
|
167
176
|
|
@@ -11,14 +11,11 @@ module Boxcars
|
|
11
11
|
# @param prompt [Boxcars::Prompt] The prompt to use for this boxcar. Defaults to built-in prompt.
|
12
12
|
# @param kwargs [Hash] Any other keyword arguments to pass to the parent class.
|
13
13
|
def initialize(engine: nil, prompt: nil, **kwargs)
|
14
|
-
# def initialize(engine:, prompt: my_prompt, output_key: :answer, **kwargs)
|
15
14
|
the_prompt = prompt || my_prompt
|
16
15
|
kwargs[:stop] ||= ["```output"]
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
prompt: the_prompt,
|
21
|
-
**kwargs)
|
16
|
+
kwargs[:name] ||= "Calculator"
|
17
|
+
kwargs[:description] ||= CALCDESC
|
18
|
+
super(engine: engine, prompt: the_prompt, **kwargs)
|
22
19
|
end
|
23
20
|
|
24
21
|
private
|
@@ -26,7 +23,7 @@ module Boxcars
|
|
26
23
|
def get_embedded_ruby_answer(text)
|
27
24
|
code = text[8..-4].split("```").first.strip
|
28
25
|
ruby_executor = Boxcars::RubyREPL.new
|
29
|
-
ruby_executor.call(code: code)
|
26
|
+
ruby_executor.call(code: code)
|
30
27
|
end
|
31
28
|
|
32
29
|
def get_answer(text)
|
@@ -34,9 +31,9 @@ module Boxcars
|
|
34
31
|
when /^```ruby/
|
35
32
|
get_embedded_ruby_answer(text)
|
36
33
|
when /^Answer:/
|
37
|
-
text
|
34
|
+
Result.from_text(text)
|
38
35
|
else
|
39
|
-
|
36
|
+
Result.new(status: :error, explanation: "Unknown format from engine: #{text}")
|
40
37
|
end
|
41
38
|
end
|
42
39
|
|
@@ -45,7 +42,7 @@ module Boxcars
|
|
45
42
|
TEMPLATE = <<~'IPT'
|
46
43
|
You are GPT-3, and you can't do math.
|
47
44
|
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.
|
48
|
-
So we hooked you up to a Ruby 3 kernel, and now you can execute
|
45
|
+
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:
|
49
46
|
|
50
47
|
Question: ${{Question with hard calculation.}}
|
51
48
|
```ruby
|
@@ -16,7 +16,7 @@ module Boxcars
|
|
16
16
|
@engine = engine || Boxcars.engine.new
|
17
17
|
@top_k = kwargs[:top_k] || 5
|
18
18
|
@stop = kwargs[:stop] || ["Answer:"]
|
19
|
-
super(name: name, description: description)
|
19
|
+
super(name: name, description: description, return_direct: kwargs[:return_direct])
|
20
20
|
end
|
21
21
|
|
22
22
|
# input keys for the prompt
|
@@ -95,7 +95,7 @@ module Boxcars
|
|
95
95
|
def check_output_keys
|
96
96
|
return unless output_keys.length != 1
|
97
97
|
|
98
|
-
raise Boxcars::ArgumentError, "
|
98
|
+
raise Boxcars::ArgumentError, "not supported when there is not exactly one output key. Got #{output_keys}."
|
99
99
|
end
|
100
100
|
|
101
101
|
# call the boxcar
|
@@ -104,7 +104,7 @@ module Boxcars
|
|
104
104
|
def call(inputs:)
|
105
105
|
t = predict(**prediction_variables(inputs)).strip
|
106
106
|
answer = get_answer(t)
|
107
|
-
Boxcars.debug answer, :magenta
|
107
|
+
Boxcars.debug answer.to_json, :magenta
|
108
108
|
{ output_keys.first => answer }
|
109
109
|
end
|
110
110
|
|
data/lib/boxcars/boxcar/sql.rb
CHANGED
@@ -6,21 +6,21 @@ module Boxcars
|
|
6
6
|
class SQL < EngineBoxcar
|
7
7
|
# the description of this engine boxcar
|
8
8
|
SQLDESC = "useful for when you need to query a database for %<name>s."
|
9
|
+
LOCKED_OUT_TABLES = %w[schema_migrations ar_internal_metadata].freeze
|
9
10
|
attr_accessor :connection
|
10
11
|
|
11
12
|
# @param connection [ActiveRecord::Connection] The SQL connection to use for this boxcar.
|
12
|
-
# @param
|
13
|
+
# @param tables [Array<String>] The tables to use for this boxcar. Will use all if nil.
|
14
|
+
# @param except_tables [Array<String>] The tables to exclude from this boxcar. Will exclude none if nil.
|
13
15
|
# @param kwargs [Hash] Any other keyword arguments to pass to the parent class. This can include
|
14
|
-
# :name, :description, :prompt and :
|
15
|
-
def initialize(connection: nil,
|
16
|
+
# :name, :description, :prompt, :top_k, :stop, and :engine
|
17
|
+
def initialize(connection: nil, tables: nil, except_tables: nil, **kwargs)
|
16
18
|
@connection = connection || ::ActiveRecord::Base.connection
|
17
|
-
|
18
|
-
kwargs[:
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
engine: engine,
|
23
|
-
prompt: the_prompt)
|
19
|
+
check_tables(tables, except_tables)
|
20
|
+
kwargs[:name] ||= "Database"
|
21
|
+
kwargs[:description] ||= format(SQLDESC, name: name)
|
22
|
+
kwargs[:prompt] ||= my_prompt
|
23
|
+
super(**kwargs)
|
24
24
|
end
|
25
25
|
|
26
26
|
# @return Hash The additional variables for this boxcar.
|
@@ -30,6 +30,19 @@ module Boxcars
|
|
30
30
|
|
31
31
|
private
|
32
32
|
|
33
|
+
def check_tables(rtables, exceptions)
|
34
|
+
if rtables.is_a?(Array) && tables.length.positive?
|
35
|
+
@requested_tables = rtables
|
36
|
+
all_tables = tables
|
37
|
+
rtables.each do |t|
|
38
|
+
raise ArgumentError, "table #{t} needs to be an Active Record model" unless all_tables.include?(t)
|
39
|
+
end
|
40
|
+
elsif rtables
|
41
|
+
raise ArgumentError, "tables needs to be an array of Strings"
|
42
|
+
end
|
43
|
+
@except_models = LOCKED_OUT_TABLES + exceptions.to_a
|
44
|
+
end
|
45
|
+
|
33
46
|
def tables
|
34
47
|
connection&.tables
|
35
48
|
end
|
@@ -49,11 +62,22 @@ module Boxcars
|
|
49
62
|
connection.class.name.split("::").last.sub("Adapter", "")
|
50
63
|
end
|
51
64
|
|
65
|
+
def clean_up_output(output)
|
66
|
+
output = output.as_json if output.is_a?(::ActiveRecord::Result)
|
67
|
+
output = 0 if output.is_a?(Array) && output.empty?
|
68
|
+
output = output.first if output.is_a?(Array) && output.length == 1
|
69
|
+
output = output[output.keys.first] if output.is_a?(Hash) && output.length == 1
|
70
|
+
output = output.as_json if output.is_a?(::ActiveRecord::Relation)
|
71
|
+
output
|
72
|
+
end
|
73
|
+
|
52
74
|
def get_embedded_sql_answer(text)
|
53
75
|
code = text[/^SQLQuery: (.*)/, 1]
|
54
76
|
Boxcars.debug code, :yellow
|
55
|
-
output = connection.exec_query(code)
|
56
|
-
"Answer: #{output}"
|
77
|
+
output = clean_up_output(connection.exec_query(code))
|
78
|
+
Result.new(status: :ok, answer: output, explanation: "Answer: #{output.to_json}", code: code)
|
79
|
+
rescue StandardError => e
|
80
|
+
Result.new(status: :error, answer: nil, explanation: "Error: #{e.message}", code: code)
|
57
81
|
end
|
58
82
|
|
59
83
|
def get_answer(text)
|
@@ -61,14 +85,14 @@ module Boxcars
|
|
61
85
|
when /^SQLQuery:/
|
62
86
|
get_embedded_sql_answer(text)
|
63
87
|
when /^Answer:/
|
64
|
-
text
|
88
|
+
Result.from_text(text)
|
65
89
|
else
|
66
|
-
|
90
|
+
Result.from_error("Unknown format from engine: #{text}")
|
67
91
|
end
|
68
92
|
end
|
69
93
|
|
70
94
|
TEMPLATE = <<~IPT
|
71
|
-
Given an input question, first create a syntactically correct %<dialect>s query to run,
|
95
|
+
Given an input question, first create a syntactically correct %<dialect>s SQL query to run,
|
72
96
|
then look at the results of the query and return the answer. Unless the user specifies
|
73
97
|
in his question a specific number of examples he wishes to obtain, always limit your query
|
74
98
|
to at most %<top_k>s results using a LIMIT clause. You can order the results by a relevant column
|
data/lib/boxcars/boxcar.rb
CHANGED
@@ -62,8 +62,18 @@ module Boxcars
|
|
62
62
|
# you can pass one or the other, but not both.
|
63
63
|
# @return [String] The answer to the question.
|
64
64
|
def run(*args, **kwargs)
|
65
|
+
rv = conduct(*args, **kwargs)
|
66
|
+
rv.is_a?(Result) ? rv.to_answer : rv
|
67
|
+
end
|
68
|
+
|
69
|
+
# Get an extended answer from the boxcar.
|
70
|
+
# @param args [Array] The positional arguments to pass to the boxcar.
|
71
|
+
# @param kwargs [Hash] The keyword arguments to pass to the boxcar.
|
72
|
+
# you can pass one or the other, but not both.
|
73
|
+
# @return [Boxcars::Result] The answer to the question.
|
74
|
+
def conduct(*args, **kwargs)
|
65
75
|
Boxcars.info "> Entering #{name}#run", :gray, style: :bold
|
66
|
-
rv =
|
76
|
+
rv = depart(*args, **kwargs)
|
67
77
|
Boxcars.info "< Exiting #{name}#run", :gray, style: :bold
|
68
78
|
rv
|
69
79
|
end
|
@@ -71,7 +81,7 @@ module Boxcars
|
|
71
81
|
private
|
72
82
|
|
73
83
|
# Get an answer from the boxcar.
|
74
|
-
def
|
84
|
+
def run_boxcar(inputs:, return_only_outputs: false)
|
75
85
|
inputs = our_inputs(inputs)
|
76
86
|
output = nil
|
77
87
|
begin
|
@@ -81,19 +91,19 @@ module Boxcars
|
|
81
91
|
raise e
|
82
92
|
end
|
83
93
|
validate_outputs(outputs: output.keys)
|
84
|
-
# memory&.save_convext(inputs: inputs, outputs: outputs)
|
85
94
|
return output if return_only_outputs
|
86
95
|
|
87
96
|
inputs.merge(output)
|
88
97
|
end
|
89
98
|
|
90
|
-
|
99
|
+
# line up parameters and run boxcar
|
100
|
+
def depart(*args, **kwargs)
|
91
101
|
if kwargs.empty?
|
92
102
|
raise Boxcars::ArgumentError, "run supports only one positional argument." if args.length != 1
|
93
103
|
|
94
|
-
return
|
104
|
+
return run_boxcar(inputs: args[0])[output_keys.first]
|
95
105
|
end
|
96
|
-
return
|
106
|
+
return run_boxcar(**kwargs)[output_keys].first if args.empty?
|
97
107
|
|
98
108
|
raise Boxcars::ArgumentError, "run supported with either positional or keyword arguments but not both. Got args" \
|
99
109
|
": #{args} and kwargs: #{kwargs}."
|
@@ -111,9 +121,15 @@ module Boxcars
|
|
111
121
|
end
|
112
122
|
validate_inputs(inputs: inputs)
|
113
123
|
end
|
124
|
+
|
125
|
+
# the default answer is the text passed in
|
126
|
+
def get_answer(text)
|
127
|
+
Result.from_text(text)
|
128
|
+
end
|
114
129
|
end
|
115
130
|
end
|
116
131
|
|
132
|
+
require "boxcars/result"
|
117
133
|
require "boxcars/boxcar/engine_boxcar"
|
118
134
|
require "boxcars/boxcar/calculator"
|
119
135
|
require "boxcars/boxcar/google_search"
|
@@ -9,9 +9,9 @@ module Boxcars
|
|
9
9
|
|
10
10
|
# The default parameters to use when asking the engine.
|
11
11
|
DEFAULT_PARAMS = {
|
12
|
-
model: "
|
12
|
+
model: "gpt-3.5-turbo",
|
13
13
|
temperature: 0.7,
|
14
|
-
max_tokens:
|
14
|
+
max_tokens: 512
|
15
15
|
}.freeze
|
16
16
|
|
17
17
|
# the default name of the engine
|
@@ -43,7 +43,13 @@ module Boxcars
|
|
43
43
|
organization_id = Boxcars.configuration.organization_id
|
44
44
|
clnt = ::OpenAI::Client.new(access_token: access_token, organization_id: organization_id)
|
45
45
|
the_params = { prompt: prompt }.merge(open_ai_params).merge(kwargs)
|
46
|
-
|
46
|
+
if the_params[:model] == "gpt-3.5-turbo"
|
47
|
+
prompt = prompt.first if prompt.is_a?(Array)
|
48
|
+
the_params = { messages: [{ role: "user", content: prompt }] }.merge(open_ai_params).merge(kwargs)
|
49
|
+
clnt.chat(parameters: the_params)
|
50
|
+
else
|
51
|
+
clnt.completions(parameters: the_params)
|
52
|
+
end
|
47
53
|
end
|
48
54
|
|
49
55
|
# get an answer from the engine for a question.
|
@@ -51,7 +57,7 @@ module Boxcars
|
|
51
57
|
# @param kwargs [Hash] Additional parameters to pass to the engine if wanted.
|
52
58
|
def run(question, **kwargs)
|
53
59
|
response = client(prompt: question, **kwargs)
|
54
|
-
answer = response["choices"].map { |c| c["text"] }.join("\n").strip
|
60
|
+
answer = response["choices"].map { |c| c.dig("message", "content") || c["text"] }.join("\n").strip
|
55
61
|
puts answer
|
56
62
|
answer
|
57
63
|
end
|
@@ -74,7 +80,7 @@ module Boxcars
|
|
74
80
|
def generation_info(sub_choices)
|
75
81
|
sub_choices.map do |choice|
|
76
82
|
Generation.new(
|
77
|
-
text: choice["text"],
|
83
|
+
text: choice.dig("message", "content") || choice["text"],
|
78
84
|
generation_info: {
|
79
85
|
finish_reason: choice.fetch("finish_reason", nil),
|
80
86
|
logprobs: choice.fetch("logprobs", nil)
|
@@ -160,7 +166,8 @@ module Boxcars
|
|
160
166
|
'text-babbage-001': 2048,
|
161
167
|
'text-ada-001': 2048,
|
162
168
|
'code-davinci-002': 8000,
|
163
|
-
'code-cushman-001': 2048
|
169
|
+
'code-cushman-001': 2048,
|
170
|
+
'gpt-3.5-turbo-1': 4096
|
164
171
|
}.freeze
|
165
172
|
model_lookup[modelname] || 4097
|
166
173
|
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Boxcars
|
4
|
+
# used by Boxcars to return structured result and additional context
|
5
|
+
class Result
|
6
|
+
attr_reader :status, :answer, :explanation, :suggestions, :added_context
|
7
|
+
|
8
|
+
# @param status [Symbol] :ok or :error
|
9
|
+
# @param answer [String] The answer to the question
|
10
|
+
# @param explanation [String] The explanation of the answer
|
11
|
+
# @param suggestions [Array<String>] The next suggestions for the user
|
12
|
+
# @param added_context [Hash] Any additional context to add to the result
|
13
|
+
def initialize(status:, answer: nil, explanation: nil, suggestions: nil, **added_context)
|
14
|
+
@status = status
|
15
|
+
@answer = answer || explanation
|
16
|
+
@explanation = explanation
|
17
|
+
@suggestions = suggestions
|
18
|
+
@added_context = added_context
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [Hash] The result as a hash
|
22
|
+
def to_h
|
23
|
+
{
|
24
|
+
status: status,
|
25
|
+
answer: answer,
|
26
|
+
explanation: explanation,
|
27
|
+
suggestions: suggestions
|
28
|
+
}.merge(added_context).compact
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [String] The result as a json string
|
32
|
+
def to_json(*args)
|
33
|
+
JSON.generate(to_h, *args)
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [String] An explanation of the result
|
37
|
+
def to_s
|
38
|
+
explanation
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [String] The answer data to the question
|
42
|
+
def to_answer
|
43
|
+
answer
|
44
|
+
end
|
45
|
+
|
46
|
+
# create a new Result from a text string
|
47
|
+
# @param text [String] The text to use for the result
|
48
|
+
# @param kwargs [Hash] Any additional kwargs to pass to the result
|
49
|
+
# @return [Boxcars::Result] The result
|
50
|
+
def self.from_text(text, **kwargs)
|
51
|
+
answer = text.delete_prefix('"').delete_suffix('"').strip
|
52
|
+
answer = Regexp.last_match(:answer) if answer =~ /^Answer:\s*(?<answer>.*)$/
|
53
|
+
explanation = "Answer: #{answer}"
|
54
|
+
new(status: :ok, answer: answer, explanation: explanation, **kwargs)
|
55
|
+
end
|
56
|
+
|
57
|
+
# create a new Result from an error string
|
58
|
+
# @param error [String] The error to use for the result
|
59
|
+
# @param kwargs [Hash] Any additional kwargs to pass to the result
|
60
|
+
# @return [Boxcars::Result] The error result
|
61
|
+
def self.from_error(error, **kwargs)
|
62
|
+
answer = error
|
63
|
+
answer = Regexp.last_match(:answer) if answer =~ /^Error:\s*(?<answer>.*)$/
|
64
|
+
explanation = "Error: #{answer}"
|
65
|
+
new(status: :error, answer: answer, explanation: explanation, **kwargs)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/boxcars/ruby_repl.rb
CHANGED
@@ -7,14 +7,22 @@ module Boxcars
|
|
7
7
|
# @param code [String] The code to run
|
8
8
|
def call(code:)
|
9
9
|
Boxcars.debug "RubyREPL: #{code}", :yellow
|
10
|
+
|
11
|
+
# wrap the code in an excption block so we can catch errors
|
12
|
+
wrapped = "begin\n#{code}\nrescue Exception => e\n puts 'Error: ' + e.message\nend"
|
10
13
|
output = ""
|
11
14
|
IO.popen("ruby", "r+") do |io|
|
12
|
-
io.puts
|
15
|
+
io.puts wrapped
|
13
16
|
io.close_write
|
14
17
|
output = io.read
|
15
18
|
end
|
16
|
-
|
17
|
-
|
19
|
+
if output =~ /^Error: /
|
20
|
+
Boxcars.debug output, :red
|
21
|
+
Result.from_error(output, code: code)
|
22
|
+
else
|
23
|
+
Boxcars.debug "Answer: #{output}", :yellow, style: :bold
|
24
|
+
Result.from_text(output, code: code)
|
25
|
+
end
|
18
26
|
end
|
19
27
|
|
20
28
|
# Execute ruby code
|
@@ -5,10 +5,24 @@ module Boxcars
|
|
5
5
|
class TrainAction
|
6
6
|
attr_accessor :boxcar, :boxcar_input, :log
|
7
7
|
|
8
|
-
|
9
|
-
|
8
|
+
# record for a train action
|
9
|
+
# @param boxcar [String] The boxcar to run.
|
10
|
+
# @param log [String] The log of the action.
|
11
|
+
# @param boxcar_input [String] The input to the boxcar.
|
12
|
+
# @return [Boxcars::TrainAction] The train action.
|
13
|
+
def initialize(boxcar:, log:, boxcar_input: nil)
|
10
14
|
@boxcar_input = boxcar_input
|
15
|
+
@boxcar = boxcar
|
11
16
|
@log = log
|
12
17
|
end
|
18
|
+
|
19
|
+
# build a train action from a result
|
20
|
+
# @param result [Boxcars::Result] The result to build from.
|
21
|
+
# @param boxcar [String] The boxcar to run.
|
22
|
+
# @param log [String] The log of the action.
|
23
|
+
# @return [Boxcars::TrainAction] The train action.
|
24
|
+
def self.from_result(result:, boxcar:, log:)
|
25
|
+
new(boxcar: boxcar, boxcar_input: result.to_answer, log: log)
|
26
|
+
end
|
13
27
|
end
|
14
28
|
end
|
@@ -19,7 +19,7 @@ module Boxcars
|
|
19
19
|
... (this Thought/Action/Action Input/Observation sequence can repeat N times)
|
20
20
|
Thought: I now know the final answer
|
21
21
|
Final Answer: the final answer to the original input question
|
22
|
-
Next Actions: up to three suggested actions for the user to take
|
22
|
+
Next Actions: If you have them, up to three suggested actions for the user to take after getting this answer.
|
23
23
|
FINPUT
|
24
24
|
|
25
25
|
# default prompt suffix
|
@@ -69,15 +69,18 @@ module Boxcars
|
|
69
69
|
# with "Action Input:" should be separated by a newline.
|
70
70
|
if engine_output.include?(FINAL_ANSWER_ACTION)
|
71
71
|
answer = engine_output.split(FINAL_ANSWER_ACTION).last.strip
|
72
|
-
|
72
|
+
Result.new(status: :ok, answer: answer, explanation: engine_output)
|
73
|
+
# ['Final Answer', answer]
|
73
74
|
else
|
75
|
+
# the thought should be the frist line here if it doesn't start with "Action:"
|
76
|
+
thought = engine_output.split(/\n+/).reject(&:empty?).first
|
77
|
+
Boxcars.debug("Though: #{thought}", :cyan)
|
74
78
|
regex = /Action: (?<action>.*)\nAction Input: (?<action_input>.*)/
|
75
79
|
match = regex.match(engine_output)
|
76
80
|
raise ValueError, "Could not parse engine output: #{engine_output}" unless match
|
77
81
|
|
78
82
|
action = match[:action].strip
|
79
|
-
action_input = match[:action_input].strip
|
80
|
-
# [action, action_input.strip(" ").strip('"')]
|
83
|
+
action_input = match[:action_input].strip.delete_prefix('"').delete_suffix('"')
|
81
84
|
[action, action_input]
|
82
85
|
end
|
83
86
|
end
|
data/lib/boxcars/train.rb
CHANGED
@@ -38,7 +38,7 @@ module Boxcars
|
|
38
38
|
def construct_scratchpad(intermediate_steps)
|
39
39
|
thoughts = ""
|
40
40
|
intermediate_steps.each do |action, observation|
|
41
|
-
thoughts += action.is_a?(String) ? action : action.log
|
41
|
+
thoughts += action.is_a?(String) ? action : " #{action.log}"
|
42
42
|
thoughts += "\n#{observation_prefix}#{observation}\n#{engine_prefix}"
|
43
43
|
end
|
44
44
|
thoughts
|
@@ -51,13 +51,16 @@ module Boxcars
|
|
51
51
|
full_output = predict(**full_inputs)
|
52
52
|
parsed_output = extract_boxcar_and_input(full_output)
|
53
53
|
while parsed_output.nil?
|
54
|
-
full_output = _fix_text(full_output)
|
55
54
|
full_inputs[:agent_scratchpad] += full_output
|
56
55
|
output = predict(**full_inputs)
|
57
56
|
full_output += output
|
58
57
|
parsed_output = extract_boxcar_and_input(full_output)
|
59
58
|
end
|
60
|
-
|
59
|
+
if parsed_output.is_a?(Result)
|
60
|
+
TrainAction.from_result(boxcar: "Final Answer", result: parsed_output, log: full_output)
|
61
|
+
else
|
62
|
+
TrainAction.new(boxcar: parsed_output[0], boxcar_input: parsed_output[1], log: full_output)
|
63
|
+
end
|
61
64
|
end
|
62
65
|
|
63
66
|
# Given input, decided what to do.
|
@@ -196,7 +199,7 @@ module Boxcars
|
|
196
199
|
observation = boxcar.run(output.boxcar_input)
|
197
200
|
return_direct = boxcar.return_direct
|
198
201
|
rescue StandardError => e
|
199
|
-
error "Error in #{boxcar.name} boxcar#call: #{e}", :red
|
202
|
+
Boxcars.error "Error in #{boxcar.name} boxcar#call: #{e}", :red
|
200
203
|
observation = "Error - #{e}, correct and try again."
|
201
204
|
end
|
202
205
|
else
|
data/lib/boxcars/version.rb
CHANGED
data/lib/boxcars.rb
CHANGED
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.1
|
4
|
+
version: 0.2.1
|
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-
|
12
|
+
date: 2023-03-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: debug
|
@@ -114,6 +114,7 @@ files:
|
|
114
114
|
- lib/boxcars/engine/openai.rb
|
115
115
|
- lib/boxcars/generation.rb
|
116
116
|
- lib/boxcars/prompt.rb
|
117
|
+
- lib/boxcars/result.rb
|
117
118
|
- lib/boxcars/ruby_repl.rb
|
118
119
|
- lib/boxcars/train.rb
|
119
120
|
- lib/boxcars/train/train_action.rb
|