Class: Boxcars::Train Abstract
- Inherits:
-
EngineBoxcar
- Object
- Boxcar
- EngineBoxcar
- Boxcars::Train
- Defined in:
- lib/boxcars/train.rb
Overview
Constant Summary
Constants inherited from Boxcar
Boxcar::SCHEMA_KEY_ALIASES, Boxcar::TYPE_ALIASES
Instance Attribute Summary collapse
-
#boxcars ⇒ Object
readonly
Returns the value of attribute boxcars.
-
#early_stopping_method ⇒ Object
readonly
Returns the value of attribute early_stopping_method.
-
#engine_prefix ⇒ Object
readonly
Returns the value of attribute engine_prefix.
-
#final_answer_prefix ⇒ Object
readonly
Returns the value of attribute final_answer_prefix.
-
#max_iterations ⇒ Object
readonly
Returns the value of attribute max_iterations.
-
#name_to_boxcar_map ⇒ Object
readonly
Returns the value of attribute name_to_boxcar_map.
-
#observation_prefix ⇒ Object
readonly
Returns the value of attribute observation_prefix.
-
#question_prefix ⇒ Object
readonly
Returns the value of attribute question_prefix.
-
#return_intermediate_steps ⇒ Object
readonly
Returns the value of attribute return_intermediate_steps.
-
#return_values ⇒ Object
readonly
Returns the value of attribute return_values.
-
#thought_prefix ⇒ Object
readonly
Returns the value of attribute thought_prefix.
-
#using_xml ⇒ Object
readonly
Returns the value of attribute using_xml.
Attributes inherited from EngineBoxcar
#engine, #prompt, #stop, #top_k
Attributes inherited from Boxcar
#description, #name, #parameters, #return_direct
Instance Method Summary collapse
- #boxcar_descriptions ⇒ Object
- #boxcar_names ⇒ Object
-
#call(inputs:) ⇒ Hash
execute the train train.
-
#construct_scratchpad(intermediate_steps) ⇒ String
build the scratchpad for the engine.
-
#extract_boxcar_and_input(text) ⇒ Object
Callback to process the action/action input of a train.
-
#finish_boxcar_name ⇒ Object
Name of the boxcar to use to finish the chain.
- #get_boxcar_result(boxcar, boxcar_input) ⇒ Object
-
#get_next_action(full_inputs) ⇒ Boxcars::Action
determine the next action.
- #init_prefixes ⇒ Object
-
#initialize(boxcars:, prompt:, engine: nil, **kwargs) ⇒ Train
constructor
abstract
A Train will use a engine to run a series of boxcars.
-
#input_keys ⇒ Array<Symbol>
the input keys.
- #key_and_value_text(key, value) ⇒ Object
- #next_actions ⇒ Object
-
#observation_text(observation) ⇒ Object
this is for the scratchpad.
-
#output_keys ⇒ Object
the output keys.
-
#plan(intermediate_steps, **kwargs) ⇒ Boxcars::Action
Given input, decided what to do.
-
#pre_return(output, intermediate_steps) ⇒ Hash
handler before returning.
-
#prepare_for_new_call ⇒ Object
Prepare the agent for new call, if needed.
-
#return_stopped_response(early_stopping_method, intermediate_steps, **kwargs) ⇒ Boxcars::Action
get the stopped response.
-
#should_continue?(iterations) ⇒ Boolean
should we continue to run?.
Methods inherited from EngineBoxcar
#apply, #extract_code, #generate, #output_key, #predict, #prediction_additional, #prediction_variables
Methods inherited from Boxcar
#apply, assi, #conduct, #conduct_result, hist, #parameters_json_schema, #run, #run_result, #schema, syst, #tool_call_name, #tool_definition, #tool_spec, user, #validate_inputs, #validate_outputs
Constructor Details
#initialize(boxcars:, prompt:, engine: nil, **kwargs) ⇒ Train
A Train will use a engine to run a series of boxcars.
16 17 18 19 20 21 22 23 24 25 26 27 28 |
# File 'lib/boxcars/train.rb', line 16 def initialize(boxcars:, prompt:, engine: nil, **kwargs) @boxcars = boxcars @name_to_boxcar_map = boxcars.to_h { |boxcar| [boxcar.name, boxcar] } @return_values = [:output] @return_intermediate_steps = kwargs.fetch(:return_intermediate_steps, true) kwargs.delete(:return_intermediate_steps) @max_iterations = kwargs.delete(:max_iterations) || 25 @early_stopping_method = kwargs.delete(:early_stopping_method) || "force" init_prefixes kwargs[:stop] = ["\n#{observation_prefix}"] unless kwargs.key?(:stop) super(prompt:, engine:, **kwargs) end |
Instance Attribute Details
#boxcars ⇒ Object (readonly)
Returns the value of attribute boxcars.
6 7 8 |
# File 'lib/boxcars/train.rb', line 6 def boxcars @boxcars end |
#early_stopping_method ⇒ Object (readonly)
Returns the value of attribute early_stopping_method.
6 7 8 |
# File 'lib/boxcars/train.rb', line 6 def early_stopping_method @early_stopping_method end |
#engine_prefix ⇒ Object (readonly)
Returns the value of attribute engine_prefix.
6 7 8 |
# File 'lib/boxcars/train.rb', line 6 def engine_prefix @engine_prefix end |
#final_answer_prefix ⇒ Object (readonly)
Returns the value of attribute final_answer_prefix.
6 7 8 |
# File 'lib/boxcars/train.rb', line 6 def final_answer_prefix @final_answer_prefix end |
#max_iterations ⇒ Object (readonly)
Returns the value of attribute max_iterations.
6 7 8 |
# File 'lib/boxcars/train.rb', line 6 def max_iterations @max_iterations end |
#name_to_boxcar_map ⇒ Object (readonly)
Returns the value of attribute name_to_boxcar_map.
6 7 8 |
# File 'lib/boxcars/train.rb', line 6 def name_to_boxcar_map @name_to_boxcar_map end |
#observation_prefix ⇒ Object (readonly)
Returns the value of attribute observation_prefix.
6 7 8 |
# File 'lib/boxcars/train.rb', line 6 def observation_prefix @observation_prefix end |
#question_prefix ⇒ Object (readonly)
Returns the value of attribute question_prefix.
6 7 8 |
# File 'lib/boxcars/train.rb', line 6 def question_prefix @question_prefix end |
#return_intermediate_steps ⇒ Object (readonly)
Returns the value of attribute return_intermediate_steps.
6 7 8 |
# File 'lib/boxcars/train.rb', line 6 def return_intermediate_steps @return_intermediate_steps end |
#return_values ⇒ Object (readonly)
Returns the value of attribute return_values.
6 7 8 |
# File 'lib/boxcars/train.rb', line 6 def return_values @return_values end |
#thought_prefix ⇒ Object (readonly)
Returns the value of attribute thought_prefix.
6 7 8 |
# File 'lib/boxcars/train.rb', line 6 def thought_prefix @thought_prefix end |
#using_xml ⇒ Object (readonly)
Returns the value of attribute using_xml.
6 7 8 |
# File 'lib/boxcars/train.rb', line 6 def using_xml @using_xml end |
Instance Method Details
#boxcar_descriptions ⇒ Object
238 239 240 |
# File 'lib/boxcars/train.rb', line 238 def boxcar_descriptions @boxcar_descriptions ||= boxcars.map { |boxcar| "#{boxcar.name}: #{boxcar.description}" }.join("\n") end |
#boxcar_names ⇒ Object
234 235 236 |
# File 'lib/boxcars/train.rb', line 234 def boxcar_names @boxcar_names ||= boxcars.map(&:name).join(', ') end |
#call(inputs:) ⇒ Hash
execute the train train
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 |
# File 'lib/boxcars/train.rb', line 182 def call(inputs:) prepare_for_new_call intermediate_steps = [] iterations = 0 while should_continue?(iterations) output = plan(intermediate_steps, **inputs) return pre_return(output, intermediate_steps) if output.is_a?(TrainFinish) if (boxcar = name_to_boxcar_map[output.boxcar]) begin observation = Observation.ok(get_boxcar_result(boxcar, output.boxcar_input)) return_direct = boxcar.return_direct rescue Boxcars::ConfigurationError, Boxcars::SecurityError => e raise e rescue StandardError => e Boxcars.error "Error in #{boxcar.name} train#call: #{e}\nbt:#{caller[0..5].join("\n ")}", :red observation = Observation.err("Error - #{e}, correct and try again.") end elsif output.boxcar == :error observation = output.log return_direct = false else observation = Observation.err("Error - #{output.boxcar} is not a valid action, try again.") return_direct = false end Boxcars.debug "Observation: #{observation}", :green intermediate_steps.append([output, observation]) if return_direct output = TrainFinish.new({ return_values[0] => observation }, "") return pre_return(output, intermediate_steps) end iterations += 1 end output = return_stopped_response(early_stopping_method, intermediate_steps, **inputs) pre_return(output, intermediate_steps) end |
#construct_scratchpad(intermediate_steps) ⇒ String
build the scratchpad for the engine
46 47 48 49 50 51 52 53 |
# File 'lib/boxcars/train.rb', line 46 def construct_scratchpad(intermediate_steps) thoughts = engine_prefix.to_s intermediate_steps.each do |action, observation| thoughts += action.is_a?(String) ? action : " #{action.log}" thoughts += "\n#{observation_text(observation)}\n#{engine_prefix}" end thoughts end |
#extract_boxcar_and_input(text) ⇒ Object
Callback to process the action/action input of a train.
39 40 41 |
# File 'lib/boxcars/train.rb', line 39 def extract_boxcar_and_input(text) raise NotImplementedError, "#{self.class} must implement #extract_boxcar_and_input" end |
#finish_boxcar_name ⇒ Object
Name of the boxcar to use to finish the chain
94 95 96 |
# File 'lib/boxcars/train.rb', line 94 def finish_boxcar_name "Final Answer" end |
#get_boxcar_result(boxcar, boxcar_input) ⇒ Object
167 168 169 170 171 172 173 174 175 176 177 |
# File 'lib/boxcars/train.rb', line 167 def get_boxcar_result(boxcar, boxcar_input) boxcar_result = boxcar.run(boxcar_input) return boxcar_result unless using_xml if boxcar_result.is_a?(Result) boxcar_result.answer = boxcar_result.answer.encode(xml: :text) if boxcar_result.answer.is_a?(String) elsif boxcar_result.is_a?(String) boxcar_result = boxcar_result.encode(xml: :text) end boxcar_result end |
#get_next_action(full_inputs) ⇒ Boxcars::Action
determine the next action
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/boxcars/train.rb', line 58 def get_next_action(full_inputs) full_output = "" parsed_output = nil loop do full_inputs[:agent_scratchpad] += full_output output = predict(**full_inputs) full_output += output.to_s parsed_output = extract_boxcar_and_input(full_output) break unless parsed_output.nil? end if parsed_output.is_a?(Result) TrainAction.from_result(boxcar: "Final Answer", result: parsed_output, log: full_output) # elsif parsed_output[0] == "Error" else TrainAction.new(boxcar: parsed_output[0], boxcar_input: parsed_output[1], log: full_output) end end |
#init_prefixes ⇒ Object
30 31 32 33 34 35 |
# File 'lib/boxcars/train.rb', line 30 def init_prefixes @thought_prefix ||= "Thought: " @observation_prefix ||= "Observation: " @final_answer_prefix ||= "Final Answer: " @question_prefix ||= "Question: " end |
#input_keys ⇒ Array<Symbol>
the input keys
100 101 102 |
# File 'lib/boxcars/train.rb', line 100 def input_keys prompt.input_variables - [:agent_scratchpad] end |
#key_and_value_text(key, value) ⇒ Object
219 220 221 222 223 224 225 226 227 |
# File 'lib/boxcars/train.rb', line 219 def key_and_value_text(key, value) value = value.to_s if key =~ /^<(?<tag_name>[[:word:]]+)>$/ # we need a close tag too "#{key}#{value}</#{Regexp.last_match[:tag_name]}>" else "#{key}#{value}" end end |
#next_actions ⇒ Object
242 243 244 245 246 247 248 |
# File 'lib/boxcars/train.rb', line 242 def next_actions if wants_next_actions "Next Actions: Up to 3 logical suggested next questions for the user to ask after getting this answer.\n" else "" end end |
#observation_text(observation) ⇒ Object
this is for the scratchpad
230 231 232 |
# File 'lib/boxcars/train.rb', line 230 def observation_text(observation) key_and_value_text(observation_prefix, observation) end |
#output_keys ⇒ Object
the output keys
105 106 107 108 109 |
# File 'lib/boxcars/train.rb', line 105 def output_keys return return_values + [:intermediate_steps] if return_intermediate_steps return_values end |
#plan(intermediate_steps, **kwargs) ⇒ Boxcars::Action
Given input, decided what to do.
80 81 82 83 84 85 86 87 |
# File 'lib/boxcars/train.rb', line 80 def plan(intermediate_steps, **kwargs) thoughts = construct_scratchpad(intermediate_steps) full_inputs = prediction_additional(kwargs).merge(kwargs).merge(agent_scratchpad: thoughts) action = get_next_action(full_inputs) return TrainFinish.new({ output: action.boxcar_input }, log: action.log) if action.boxcar == finish_boxcar_name action end |
#pre_return(output, intermediate_steps) ⇒ Hash
handler before returning
124 125 126 127 128 129 |
# File 'lib/boxcars/train.rb', line 124 def pre_return(output, intermediate_steps) Boxcars.debug output.log, :yellow, style: :bold final_output = output.return_values final_output[:intermediate_steps] = intermediate_steps if return_intermediate_steps final_output end |
#prepare_for_new_call ⇒ Object
Prepare the agent for new call, if needed
90 91 |
# File 'lib/boxcars/train.rb', line 90 def prepare_for_new_call end |
#return_stopped_response(early_stopping_method, intermediate_steps, **kwargs) ⇒ Boxcars::Action
get the stopped response
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/boxcars/train.rb', line 136 def return_stopped_response(early_stopping_method, intermediate_steps, **kwargs) case early_stopping_method when "force" TrainFinish.new({ output: "Agent stopped due to max iterations." }, "") when "generate" thoughts = "" intermediate_steps.each do |action, observation| thoughts += action.log thoughts += "\n#{observation_text(observation)}\n#{engine_prefix}" end thoughts += "\n\nI now need to return a final answer based on the previous steps:" new_inputs = { agent_scratchpad: thoughts, stop: _stop } full_inputs = kwargs.merge(new_inputs) full_output = predict(**full_inputs) parsed_output = extract_boxcar_and_input(full_output) if parsed_output.nil? TrainFinish.new({ output: full_output }, full_output) else boxcar, boxcar_input = parsed_output Boxcars.debug "Got boxcar #{boxcar} and input #{boxcar_input}" if boxcar == finish_boxcar_name TrainFinish.new({ output: boxcar_input }, full_output) else TrainFinish.new({ output: full_output }, full_output) end end else raise "early_stopping_method should be one of `force` or `generate`, got #{early_stopping_method}" end end |
#should_continue?(iterations) ⇒ Boolean
should we continue to run?
114 115 116 117 118 |
# File 'lib/boxcars/train.rb', line 114 def should_continue?(iterations) return true if max_iterations.nil? iterations < max_iterations end |