boxcars 0.1.1 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +34 -5
- data/Gemfile +2 -0
- data/Gemfile.lock +68 -2
- data/README.md +77 -3
- data/Rakefile +7 -0
- data/boxcars.gemspec +1 -1
- data/lib/boxcars/boxcar/calculator.rb +13 -5
- data/lib/boxcars/boxcar/engine_boxcar.rb +24 -20
- data/lib/boxcars/boxcar/serp.rb +2 -2
- data/lib/boxcars/boxcar/sql.rb +11 -4
- data/lib/boxcars/boxcar.rb +28 -17
- data/lib/boxcars/engine/openai.rb +24 -22
- data/lib/boxcars/ruby_repl.rb +4 -0
- data/lib/boxcars/{conductor/conductor_action.rb → train/train_action.rb} +2 -2
- data/lib/boxcars/{conductor/conductor_finish.rb → train/train_finish.rb} +2 -2
- data/lib/boxcars/{conductor → train}/zero_shot.rb +22 -13
- data/lib/boxcars/train.rb +224 -0
- data/lib/boxcars/version.rb +2 -1
- data/lib/boxcars.rb +22 -2
- metadata +7 -8
- data/lib/boxcars/conductor/conductor_executer.rb +0 -96
- data/lib/boxcars/conductor.rb +0 -147
@@ -1,18 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'openai'
|
3
4
|
# Boxcars is a framework for running a series of tools to get an answer to a question.
|
4
5
|
module Boxcars
|
5
6
|
# A engine that uses OpenAI's API.
|
6
7
|
class Openai < Engine
|
7
8
|
attr_reader :prompts, :open_ai_params, :model_kwargs, :batch_size
|
8
9
|
|
10
|
+
# The default parameters to use when asking the engine.
|
9
11
|
DEFAULT_PARAMS = {
|
10
12
|
model: "text-davinci-003",
|
11
13
|
temperature: 0.7,
|
12
14
|
max_tokens: 256
|
13
15
|
}.freeze
|
14
16
|
|
17
|
+
# the default name of the engine
|
15
18
|
DEFAULT_NAME = "OpenAI engine"
|
19
|
+
# the default description of the engine
|
16
20
|
DEFAULT_DESCRIPTION = "useful for when you need to use AI to answer questions. " \
|
17
21
|
"You should ask targeted questions"
|
18
22
|
|
@@ -30,7 +34,9 @@ module Boxcars
|
|
30
34
|
end
|
31
35
|
|
32
36
|
# Get an answer from the engine.
|
33
|
-
# @param
|
37
|
+
# @param prompt [String] The prompt to use when asking the engine.
|
38
|
+
# @param openai_access_token [String] The access token to use when asking the engine.
|
39
|
+
# Defaults to Boxcars.configuration.openai_access_token.
|
34
40
|
# @param kwargs [Hash] Additional parameters to pass to the engine if wanted.
|
35
41
|
def client(prompt:, openai_access_token: 'not set', **kwargs)
|
36
42
|
access_token = Boxcars.configuration.openai_access_token(openai_access_token: openai_access_token)
|
@@ -51,15 +57,20 @@ module Boxcars
|
|
51
57
|
end
|
52
58
|
|
53
59
|
# Build extra kwargs from additional params that were passed in.
|
60
|
+
# @param values [Hash] The values to build extra kwargs from.
|
54
61
|
def build_extra(values:)
|
55
62
|
values[:model_kw_args] = @open_ai_params.merge(values)
|
56
63
|
values
|
57
64
|
end
|
58
65
|
|
66
|
+
# Get the default parameters for the engine.
|
59
67
|
def default_params
|
60
68
|
open_ai_params
|
61
69
|
end
|
62
70
|
|
71
|
+
# Get generation informaton
|
72
|
+
# @param sub_choices [Array<Hash>] The choices to get generation info for.
|
73
|
+
# @return [Array<Generation>] The generation information.
|
63
74
|
def generation_info(sub_choices)
|
64
75
|
sub_choices.map do |choice|
|
65
76
|
Generation.new(
|
@@ -73,18 +84,9 @@ module Boxcars
|
|
73
84
|
end
|
74
85
|
|
75
86
|
# Call out to OpenAI's endpoint with k unique prompts.
|
76
|
-
|
77
|
-
#
|
78
|
-
#
|
79
|
-
# stop: Optional list of stop words to use when generating.
|
80
|
-
|
81
|
-
# Returns:
|
82
|
-
# The full engine output.
|
83
|
-
|
84
|
-
# Example:
|
85
|
-
# .. code-block:: ruby
|
86
|
-
|
87
|
-
# response = openai.generate(["Tell me a joke."])
|
87
|
+
# @param prompts [Array<String>] The prompts to pass into the model.
|
88
|
+
# @param stop [Array<String>] Optional list of stop words to use when generating.
|
89
|
+
# @return [EngineResult] The full engine output.
|
88
90
|
def generate(prompts:, stop: nil)
|
89
91
|
params = {}
|
90
92
|
params[:stop] = stop if stop
|
@@ -112,21 +114,25 @@ module Boxcars
|
|
112
114
|
# rubocop:enable Metrics/AbcSize
|
113
115
|
end
|
114
116
|
|
117
|
+
# the identifying parameters for the engine
|
115
118
|
def identifying_params
|
116
119
|
params = { model_name: model_name }
|
117
120
|
params.merge!(default_params)
|
118
121
|
params
|
119
122
|
end
|
120
123
|
|
124
|
+
# the engine type
|
121
125
|
def engine_type
|
122
126
|
"openai"
|
123
127
|
end
|
124
128
|
|
125
129
|
# calculate the number of tokens used
|
126
130
|
def get_num_tokens(text:)
|
127
|
-
text.split.length
|
131
|
+
text.split.length # TODO: hook up to token counting gem
|
128
132
|
end
|
129
133
|
|
134
|
+
# lookup the context size for a model by name
|
135
|
+
# @param modelname [String] The name of the model to lookup.
|
130
136
|
def modelname_to_contextsize(modelname)
|
131
137
|
model_lookup = {
|
132
138
|
'text-davinci-003': 4097,
|
@@ -140,14 +146,10 @@ module Boxcars
|
|
140
146
|
end
|
141
147
|
|
142
148
|
# Calculate the maximum number of tokens possible to generate for a prompt.
|
143
|
-
|
144
|
-
#
|
145
|
-
|
146
|
-
|
147
|
-
# Returns:
|
148
|
-
# The maximum number of tokens possible to generate for a prompt.
|
149
|
-
def max_tokens_for_prompt(prompt)
|
150
|
-
num_tokens = get_num_tokens(prompt)
|
149
|
+
# @param prompt_text [String] The prompt text to use.
|
150
|
+
# @return [Integer] the number of tokens possible to generate.
|
151
|
+
def max_tokens_for_prompt(prompt_text)
|
152
|
+
num_tokens = get_num_tokens(prompt_text)
|
151
153
|
|
152
154
|
# get max context size for model by name
|
153
155
|
max_size = modelname_to_contextsize(model_name)
|
data/lib/boxcars/ruby_repl.rb
CHANGED
@@ -3,6 +3,8 @@
|
|
3
3
|
module Boxcars
|
4
4
|
# used by Boxcars to run ruby code
|
5
5
|
class RubyREPL
|
6
|
+
# Execute ruby code
|
7
|
+
# @param code [String] The code to run
|
6
8
|
def call(code:)
|
7
9
|
puts "RubyREPL: #{code}".colorize(:red)
|
8
10
|
output = ""
|
@@ -15,6 +17,8 @@ module Boxcars
|
|
15
17
|
output
|
16
18
|
end
|
17
19
|
|
20
|
+
# Execute ruby code
|
21
|
+
# @param command [String] The code to run
|
18
22
|
def run(command)
|
19
23
|
call(code: command)
|
20
24
|
end
|
@@ -1,10 +1,13 @@
|
|
1
1
|
# Agent for the MRKL chain
|
2
2
|
module Boxcars
|
3
|
-
# A
|
4
|
-
class ZeroShot <
|
3
|
+
# A Train using the zero-shot react method.
|
4
|
+
class ZeroShot < Train
|
5
5
|
attr_reader :boxcars, :observation_prefix, :engine_prefix
|
6
6
|
|
7
|
+
# default prompt prefix
|
7
8
|
PREFIX = "Answer the following questions as best you can. You have access to the following actions:".freeze
|
9
|
+
|
10
|
+
# default prompt instructions
|
8
11
|
FORMAT_INSTRUCTIONS = <<~FINPUT.freeze
|
9
12
|
Use the following format:
|
10
13
|
|
@@ -18,6 +21,7 @@ module Boxcars
|
|
18
21
|
Final Answer: the final answer to the original input question
|
19
22
|
FINPUT
|
20
23
|
|
24
|
+
# default prompt suffix
|
21
25
|
SUFFIX = <<~SINPUT.freeze
|
22
26
|
Begin!
|
23
27
|
|
@@ -25,7 +29,11 @@ module Boxcars
|
|
25
29
|
Thought:%<agent_scratchpad>s
|
26
30
|
SINPUT
|
27
31
|
|
28
|
-
|
32
|
+
# @param boxcars [Array<Boxcars::Boxcar>] The boxcars to run.
|
33
|
+
# @param engine [Boxcars::Engine] The engine to use for this train.
|
34
|
+
# @param name [String] The name of the train. Defaults to 'Zero Shot'.
|
35
|
+
# @param description [String] The description of the train. Defaults to 'Zero Shot Train'.
|
36
|
+
def initialize(boxcars:, engine: nil, name: 'Zero Shot', description: 'Zero Shot Train')
|
29
37
|
@observation_prefix = 'Observation: '
|
30
38
|
@engine_prefix = 'Thought:'
|
31
39
|
prompt = self.class.create_prompt(boxcars: boxcars)
|
@@ -33,16 +41,11 @@ module Boxcars
|
|
33
41
|
end
|
34
42
|
|
35
43
|
# Create prompt in the style of the zero shot agent.
|
36
|
-
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
# input_variables: List of input variables the final prompt will expect.
|
42
|
-
|
43
|
-
# Returns:
|
44
|
-
# A Prompt with the template assembled from the pieces here.
|
45
|
-
|
44
|
+
# @param boxcars [Array<Boxcars::Boxcar>] List of boxcars the agent will have access to, used to format the prompt.
|
45
|
+
# @param prefix [String] String to put before the main prompt.
|
46
|
+
# @param suffix [String] String to put after the main prompt.
|
47
|
+
# @param input_variables [Array<Symbol>] List of input variables the final prompt will expect.
|
48
|
+
# @return [Boxcars::Prompt] A Prompt with the template assembled from the pieces here.
|
46
49
|
def self.create_prompt(boxcars:, prefix: PREFIX, suffix: SUFFIX, input_variables: [:input, :agent_scratchpad])
|
47
50
|
boxcar_strings = boxcars.map { |boxcar| "#{boxcar.name}: #{boxcar.description}" }.join("\n")
|
48
51
|
boxcar_names = boxcars.map(&:name)
|
@@ -51,9 +54,12 @@ module Boxcars
|
|
51
54
|
Prompt.new(template: template, input_variables: input_variables)
|
52
55
|
end
|
53
56
|
|
57
|
+
# the final answer action string
|
54
58
|
FINAL_ANSWER_ACTION = "Final Answer:".freeze
|
55
59
|
|
56
60
|
# Parse out the action and input from the engine output.
|
61
|
+
# @param engine_output [String] The output from the engine.
|
62
|
+
# @return [Array<String>] The action and input.
|
57
63
|
def get_action_and_input(engine_output:)
|
58
64
|
# NOTE: if you're specifying a custom prompt for the ZeroShotAgent,
|
59
65
|
# you will need to ensure that it meets the following Regex requirements.
|
@@ -74,6 +80,9 @@ module Boxcars
|
|
74
80
|
end
|
75
81
|
end
|
76
82
|
|
83
|
+
# Extract the boxcar and input from the engine output.
|
84
|
+
# @param text [String] The output from the engine.
|
85
|
+
# @return [Array<Boxcars::Boxcar, String>] The boxcar and input.
|
77
86
|
def extract_boxcar_and_input(text)
|
78
87
|
get_action_and_input(engine_output: text)
|
79
88
|
end
|
@@ -0,0 +1,224 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Boxcars
|
4
|
+
# @abstract
|
5
|
+
class Train < EngineBoxcar
|
6
|
+
attr_reader :engine, :boxcars, :name, :description, :prompt, :return_values, :return_intermediate_steps,
|
7
|
+
:max_iterations, :early_stopping_method
|
8
|
+
|
9
|
+
# A Train will use a engine to run a series of boxcars.
|
10
|
+
# @param engine [Boxcars::Engine] The engine to use for this train.
|
11
|
+
# @param boxcars [Array<Boxcars::Boxcar>] The boxcars to run.
|
12
|
+
# @param prompt [String] The prompt to use.
|
13
|
+
# @abstract
|
14
|
+
def initialize(boxcars:, prompt:, engine: nil, **kwargs)
|
15
|
+
@boxcars = boxcars
|
16
|
+
@name = name || self.class.name
|
17
|
+
@return_values = [:output]
|
18
|
+
@return_intermediate_steps = kwargs[:return_intermediate_steps] || false
|
19
|
+
@max_iterations = kwargs[:max_iterations]
|
20
|
+
@early_stopping_method = kwargs[:early_stopping_method] || "force"
|
21
|
+
|
22
|
+
super(prompt: prompt, engine: engine, name: kwargs[:name], description: kwargs[:description])
|
23
|
+
end
|
24
|
+
|
25
|
+
# Extract the boxcar name and input from the text.
|
26
|
+
# @param text [String] The text to extract from.
|
27
|
+
def extract_boxcar_and_input(text)
|
28
|
+
end
|
29
|
+
|
30
|
+
# the stop strings list
|
31
|
+
def stop
|
32
|
+
["\n#{observation_prefix}"]
|
33
|
+
end
|
34
|
+
|
35
|
+
# build the scratchpad for the engine
|
36
|
+
# @param intermediate_steps [Array] The intermediate steps to build the scratchpad from.
|
37
|
+
# @return [String] The scratchpad.
|
38
|
+
def construct_scratchpad(intermediate_steps)
|
39
|
+
thoughts = ""
|
40
|
+
intermediate_steps.each do |action, observation|
|
41
|
+
thoughts += action.is_a?(String) ? action : action.log
|
42
|
+
thoughts += "\n#{observation_prefix}#{observation}\n#{engine_prefix}"
|
43
|
+
end
|
44
|
+
thoughts
|
45
|
+
end
|
46
|
+
|
47
|
+
# determine the next action
|
48
|
+
# @param full_inputs [Hash] The inputs to the engine.
|
49
|
+
# @return [Boxcars::Action] The next action.
|
50
|
+
def get_next_action(full_inputs)
|
51
|
+
full_output = predict(**full_inputs)
|
52
|
+
parsed_output = extract_boxcar_and_input(full_output)
|
53
|
+
while parsed_output.nil?
|
54
|
+
full_output = _fix_text(full_output)
|
55
|
+
full_inputs[:agent_scratchpad] += full_output
|
56
|
+
output = predict(**full_inputs)
|
57
|
+
full_output += output
|
58
|
+
parsed_output = extract_boxcar_and_input(full_output)
|
59
|
+
end
|
60
|
+
TrainAction.new(boxcar: parsed_output[0], boxcar_input: parsed_output[1], log: full_output)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Given input, decided what to do.
|
64
|
+
# @param intermediate_steps [Array<Hash>] The intermediate steps taken so far along with observations.
|
65
|
+
# @param kwargs [Hash] User inputs.
|
66
|
+
# @return [Boxcars::Action] Action specifying what boxcar to use.
|
67
|
+
def plan(intermediate_steps, **kwargs)
|
68
|
+
thoughts = construct_scratchpad(intermediate_steps)
|
69
|
+
new_inputs = { agent_scratchpad: thoughts, stop: stop }
|
70
|
+
full_inputs = kwargs.merge(new_inputs)
|
71
|
+
action = get_next_action(full_inputs)
|
72
|
+
return TrainFinish.new({ output: action.boxcar_input }, log: action.log) if action.boxcar == finish_boxcar_name
|
73
|
+
|
74
|
+
action
|
75
|
+
end
|
76
|
+
|
77
|
+
# Prepare the agent for new call, if needed
|
78
|
+
def prepare_for_new_call
|
79
|
+
end
|
80
|
+
|
81
|
+
# Name of the boxcar to use to finish the chain
|
82
|
+
def finish_boxcar_name
|
83
|
+
"Final Answer"
|
84
|
+
end
|
85
|
+
|
86
|
+
# the input keys
|
87
|
+
# @return [Array<Symbol>] The input keys.
|
88
|
+
def input_keys
|
89
|
+
list = prompt.input_variables
|
90
|
+
list.delete(:agent_scratchpad)
|
91
|
+
list
|
92
|
+
end
|
93
|
+
|
94
|
+
# the output keys
|
95
|
+
def output_keys
|
96
|
+
return return_values + ["intermediate_steps"] if return_intermediate_steps
|
97
|
+
|
98
|
+
return_values
|
99
|
+
end
|
100
|
+
|
101
|
+
# should we continue to run?
|
102
|
+
# @param iterations [Integer] The number of iterations.
|
103
|
+
# @return [Boolean] Whether to continue.
|
104
|
+
def should_continue?(iterations)
|
105
|
+
return true if max_iterations.nil?
|
106
|
+
|
107
|
+
iterations < max_iterations
|
108
|
+
end
|
109
|
+
|
110
|
+
# handler before returning
|
111
|
+
# @param output [Boxcars::TrainFinish] The output.
|
112
|
+
# @param intermediate_steps [Array<Hash>] The intermediate steps.
|
113
|
+
# @return [Hash] The final output.
|
114
|
+
def pre_return(output, intermediate_steps)
|
115
|
+
puts output.log.colorize(:yellow)
|
116
|
+
final_output = output.return_values
|
117
|
+
final_output["intermediate_steps"] = intermediate_steps if return_intermediate_steps
|
118
|
+
final_output
|
119
|
+
end
|
120
|
+
|
121
|
+
# the prefix for the engine
|
122
|
+
# @param return_direct [Boolean] Whether to return directly.
|
123
|
+
# @return [String] The prefix.
|
124
|
+
def engine_prefix(return_direct)
|
125
|
+
return_direct ? "" : engine_prefix
|
126
|
+
end
|
127
|
+
|
128
|
+
# validate the prompt
|
129
|
+
# @param values [Hash] The values to validate.
|
130
|
+
# @return [Hash] The validated values.
|
131
|
+
# @raise [RuntimeError] If the prompt is invalid.
|
132
|
+
def validate_prompt(values:)
|
133
|
+
prompt = values["engine_chain"].prompt
|
134
|
+
unless prompt.input_variables.include?(:agent_scratchpad)
|
135
|
+
logger.warning("`agent_scratchpad` should be a variable in prompt.input_variables. Not found, adding it at the end.")
|
136
|
+
prompt.input_variables.append(:agent_scratchpad)
|
137
|
+
case prompt
|
138
|
+
when Prompt
|
139
|
+
prompt.template += "\n%<agent_scratchpad>s"
|
140
|
+
# when FewShotPromptTemplate
|
141
|
+
# prompt.suffix += "\n%<agent_scratchpad>s"
|
142
|
+
else
|
143
|
+
raise ValueError, "Got unexpected prompt type #{type(prompt)}"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
values
|
147
|
+
end
|
148
|
+
|
149
|
+
# get the stopped response
|
150
|
+
# @param early_stopping_method [String] The early stopping method.
|
151
|
+
# @param intermediate_steps [Array] The intermediate steps.
|
152
|
+
# @param kwargs [Hash] extra keword arguments.
|
153
|
+
# @return [Boxcars::Action] The action to take.
|
154
|
+
def return_stopped_response(early_stopping_method, intermediate_steps, **kwargs)
|
155
|
+
case early_stopping_method
|
156
|
+
when "force"
|
157
|
+
TrainFinish({ output: "Agent stopped due to max iterations." }, "")
|
158
|
+
when "generate"
|
159
|
+
thoughts = ""
|
160
|
+
intermediate_steps.each do |action, observation|
|
161
|
+
thoughts += action.log
|
162
|
+
thoughts += "\n#{observation_prefix}#{observation}\n#{engine_prefix}"
|
163
|
+
end
|
164
|
+
thoughts += "\n\nI now need to return a final answer based on the previous steps:"
|
165
|
+
new_inputs = { agent_scratchpad: thoughts, stop: _stop }
|
166
|
+
full_inputs = kwargs.merge(new_inputs)
|
167
|
+
full_output = predict(**full_inputs)
|
168
|
+
parsed_output = extract_boxcar_and_input(full_output)
|
169
|
+
if parsed_output.nil?
|
170
|
+
TrainFinish({ output: full_output }, full_output)
|
171
|
+
else
|
172
|
+
boxcar, boxcar_input = parsed_output
|
173
|
+
if boxcar == finish_boxcar_name
|
174
|
+
TrainFinish({ output: boxcar_input }, full_output)
|
175
|
+
else
|
176
|
+
TrainFinish({ output: full_output }, full_output)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
else
|
180
|
+
raise "early_stopping_method should be one of `force` or `generate`, got #{early_stopping_method}"
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# execute the train train
|
185
|
+
# @param inputs [Hash] The inputs.
|
186
|
+
# @return [Hash] The output.
|
187
|
+
def call(inputs:)
|
188
|
+
prepare_for_new_call
|
189
|
+
name_to_boxcar_map = boxcars.to_h { |boxcar| [boxcar.name, boxcar] }
|
190
|
+
intermediate_steps = []
|
191
|
+
iterations = 0
|
192
|
+
while should_continue?(iterations)
|
193
|
+
output = plan(intermediate_steps, **inputs)
|
194
|
+
return pre_return(output, intermediate_steps) if output.is_a?(TrainFinish)
|
195
|
+
|
196
|
+
if (boxcar = name_to_boxcar_map[output.boxcar])
|
197
|
+
begin
|
198
|
+
observation = boxcar.run(output.boxcar_input)
|
199
|
+
return_direct = boxcar.return_direct
|
200
|
+
rescue StandardError => e
|
201
|
+
puts "Error in #{boxcar.name} boxcar#call: #{e}".colorize(:red)
|
202
|
+
raise e
|
203
|
+
end
|
204
|
+
else
|
205
|
+
observation = "#{output.boxcar} is not a valid boxcar, try another one."
|
206
|
+
return_direct = false
|
207
|
+
end
|
208
|
+
puts "#Observation: #{observation}".colorize(:green)
|
209
|
+
intermediate_steps.append([output, observation])
|
210
|
+
if return_direct
|
211
|
+
output = TrainFinish.new({ return_values[0] => observation }, "")
|
212
|
+
return pre_return(output, intermediate_steps)
|
213
|
+
end
|
214
|
+
iterations += 1
|
215
|
+
end
|
216
|
+
output = return_stopped_response(early_stopping_method, intermediate_steps, **inputs)
|
217
|
+
pre_return(output, intermediate_steps)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
require "boxcars/train/train_action"
|
223
|
+
require "boxcars/train/train_finish"
|
224
|
+
require "boxcars/train/zero_shot"
|
data/lib/boxcars/version.rb
CHANGED
data/lib/boxcars.rb
CHANGED
@@ -6,12 +6,21 @@ require 'logger'
|
|
6
6
|
module Boxcars
|
7
7
|
# Error class for all Boxcars errors.
|
8
8
|
class Error < StandardError; end
|
9
|
+
|
10
|
+
# Error class for all Boxcars configuration errors.
|
9
11
|
class ConfigurationError < Error; end
|
12
|
+
|
13
|
+
# Error class for all Boxcars argument errors.
|
10
14
|
class ArgumentError < Error; end
|
15
|
+
|
16
|
+
# Error class for all Boxcars value errors.
|
11
17
|
class ValueError < Error; end
|
12
18
|
|
13
19
|
# simple string colorization
|
14
20
|
class ::String
|
21
|
+
# colorize a string
|
22
|
+
# @param color [Symbol] The color to use.
|
23
|
+
# @param options [Hash] The options to use.
|
15
24
|
def colorize(color, options = {})
|
16
25
|
background = options[:background] || options[:bg] || false
|
17
26
|
style = options[:style]
|
@@ -27,12 +36,13 @@ module Boxcars
|
|
27
36
|
# Configuration contains gem settings
|
28
37
|
class Configuration
|
29
38
|
attr_writer :openai_access_token, :serpapi_api_key
|
30
|
-
attr_accessor :organization_id, :logger
|
39
|
+
attr_accessor :organization_id, :logger, :log_prompts
|
31
40
|
|
32
41
|
def initialize
|
33
42
|
@organization_id = nil
|
34
43
|
@logger = Rails.logger if defined?(Rails)
|
35
44
|
@logger ||= Logger.new($stdout)
|
45
|
+
@log_prompts = false
|
36
46
|
end
|
37
47
|
|
38
48
|
# @return [String] The OpenAI Access Token either from arg or env.
|
@@ -84,6 +94,16 @@ module Boxcars
|
|
84
94
|
def self.configure
|
85
95
|
yield(configuration)
|
86
96
|
end
|
97
|
+
|
98
|
+
# Return the default Train class.
|
99
|
+
def self.default_train
|
100
|
+
Boxcars::ZeroShot
|
101
|
+
end
|
102
|
+
|
103
|
+
# Return the default Engine class.
|
104
|
+
def self.default_engine
|
105
|
+
Boxcars::Openai
|
106
|
+
end
|
87
107
|
end
|
88
108
|
|
89
109
|
require "boxcars/version"
|
@@ -92,4 +112,4 @@ require "boxcars/generation"
|
|
92
112
|
require "boxcars/ruby_repl"
|
93
113
|
require "boxcars/engine"
|
94
114
|
require "boxcars/boxcar"
|
95
|
-
require "boxcars/
|
115
|
+
require "boxcars/train"
|
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.1.3
|
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-02-
|
12
|
+
date: 2023-02-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: debug
|
@@ -81,7 +81,7 @@ dependencies:
|
|
81
81
|
- - "~>"
|
82
82
|
- !ruby/object:Gem::Version
|
83
83
|
version: '3.0'
|
84
|
-
description: You simply give a number of boxcars to a
|
84
|
+
description: You simply give a number of boxcars to a train, and it does the magic.
|
85
85
|
email:
|
86
86
|
- hi@boxcars.ai
|
87
87
|
executables: []
|
@@ -106,17 +106,16 @@ files:
|
|
106
106
|
- lib/boxcars/boxcar/engine_boxcar.rb
|
107
107
|
- lib/boxcars/boxcar/serp.rb
|
108
108
|
- lib/boxcars/boxcar/sql.rb
|
109
|
-
- lib/boxcars/conductor.rb
|
110
|
-
- lib/boxcars/conductor/conductor_action.rb
|
111
|
-
- lib/boxcars/conductor/conductor_executer.rb
|
112
|
-
- lib/boxcars/conductor/conductor_finish.rb
|
113
|
-
- lib/boxcars/conductor/zero_shot.rb
|
114
109
|
- lib/boxcars/engine.rb
|
115
110
|
- lib/boxcars/engine/engine_result.rb
|
116
111
|
- lib/boxcars/engine/openai.rb
|
117
112
|
- lib/boxcars/generation.rb
|
118
113
|
- lib/boxcars/prompt.rb
|
119
114
|
- lib/boxcars/ruby_repl.rb
|
115
|
+
- lib/boxcars/train.rb
|
116
|
+
- lib/boxcars/train/train_action.rb
|
117
|
+
- lib/boxcars/train/train_finish.rb
|
118
|
+
- lib/boxcars/train/zero_shot.rb
|
120
119
|
- lib/boxcars/version.rb
|
121
120
|
homepage: https://github.com/BoxcarsAI/boxcars
|
122
121
|
licenses:
|
@@ -1,96 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Boxcars
|
4
|
-
# Consists of an conductor using boxcars.
|
5
|
-
class ConductorExecuter < EngineBoxcar
|
6
|
-
attr_accessor :conductor, :boxcars, :return_intermediate_steps, :max_iterations, :early_stopping_method
|
7
|
-
|
8
|
-
# @param conductor [Boxcars::Conductor] The conductor to use.
|
9
|
-
# @param boxcars [Array<Boxcars::Boxcar>] The boxcars to use.
|
10
|
-
# @param return_intermediate_steps [Boolean] Whether to return the intermediate steps. Defaults to false.
|
11
|
-
# @param max_iterations [Integer] The maximum number of iterations to run. Defaults to nil.
|
12
|
-
# @param early_stopping_method [String] The early stopping method to use. Defaults to "force".
|
13
|
-
def initialize(conductor:, boxcars:, return_intermediate_steps: false, max_iterations: nil,
|
14
|
-
early_stopping_method: "force")
|
15
|
-
@conductor = conductor
|
16
|
-
@boxcars = boxcars
|
17
|
-
@return_intermediate_steps = return_intermediate_steps
|
18
|
-
@max_iterations = max_iterations
|
19
|
-
@early_stopping_method = early_stopping_method
|
20
|
-
# def initialize(prompt:, engine:, output_key: "text", name: nil, description: nil)
|
21
|
-
super(prompt: conductor.prompt, engine: conductor.engine, name: conductor.name, description: conductor.description)
|
22
|
-
end
|
23
|
-
|
24
|
-
def same_boxcars?(boxcar_names)
|
25
|
-
conductor.allowed_boxcars.sort == boxcar_names
|
26
|
-
end
|
27
|
-
|
28
|
-
def validate_boxcars
|
29
|
-
boxcar_names = boxcars.map(&:name).sort
|
30
|
-
return if same_boxcars?(boxcar_names)
|
31
|
-
|
32
|
-
raise "Allowed boxcars (#{conductor.allowed_boxcars}) different than provided boxcars (#{boxcar_names})"
|
33
|
-
end
|
34
|
-
|
35
|
-
def input_keys
|
36
|
-
conductor.input_keys
|
37
|
-
end
|
38
|
-
|
39
|
-
def output_keys
|
40
|
-
return conductor.return_values + ["intermediate_steps"] if return_intermediate_steps
|
41
|
-
|
42
|
-
conductor.return_values
|
43
|
-
end
|
44
|
-
|
45
|
-
def should_continue?(iterations)
|
46
|
-
return true if max_iterations.nil?
|
47
|
-
|
48
|
-
iterations < max_iterations
|
49
|
-
end
|
50
|
-
|
51
|
-
# handler before returning
|
52
|
-
def pre_return(output, intermediate_steps)
|
53
|
-
puts output.log.colorize(:yellow)
|
54
|
-
final_output = output.return_values
|
55
|
-
final_output["intermediate_steps"] = intermediate_steps if return_intermediate_steps
|
56
|
-
final_output
|
57
|
-
end
|
58
|
-
|
59
|
-
def engine_prefix(return_direct)
|
60
|
-
return_direct ? "" : conductor.engine_prefix
|
61
|
-
end
|
62
|
-
|
63
|
-
def call(inputs:)
|
64
|
-
conductor.prepare_for_new_call
|
65
|
-
name_to_boxcar_map = boxcars.to_h { |boxcar| [boxcar.name, boxcar] }
|
66
|
-
intermediate_steps = []
|
67
|
-
iterations = 0
|
68
|
-
while should_continue?(iterations)
|
69
|
-
output = conductor.plan(intermediate_steps, **inputs)
|
70
|
-
return pre_return(output, intermediate_steps) if output.is_a?(ConductorFinish)
|
71
|
-
|
72
|
-
if (boxcar = name_to_boxcar_map[output.boxcar])
|
73
|
-
begin
|
74
|
-
observation = boxcar.run(output.boxcar_input)
|
75
|
-
return_direct = boxcar.return_direct
|
76
|
-
rescue StandardError => e
|
77
|
-
puts "Error in #{boxcar.name} boxcar#call: #{e}".colorize(:red)
|
78
|
-
raise e
|
79
|
-
end
|
80
|
-
else
|
81
|
-
observation = "#{output.boxcar} is not a valid boxcar, try another one."
|
82
|
-
return_direct = false
|
83
|
-
end
|
84
|
-
puts "#Observation: #{observation}".colorize(:green)
|
85
|
-
intermediate_steps.append([output, observation])
|
86
|
-
if return_direct
|
87
|
-
output = ConductorFinish.new({ conductor.return_values[0] => observation }, "")
|
88
|
-
return pre_return(output, intermediate_steps)
|
89
|
-
end
|
90
|
-
iterations += 1
|
91
|
-
end
|
92
|
-
output = conductor.return_stopped_response(early_stopping_method, intermediate_steps, **inputs)
|
93
|
-
pre_return(output, intermediate_steps)
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|