Class: Boxcars::Train Abstract
- Inherits:
-
EngineBoxcar
- Object
- Boxcar
- EngineBoxcar
- Boxcars::Train
- Defined in:
- lib/boxcars/train.rb
Overview
Instance Attribute Summary collapse
-
#answer_prefix ⇒ Object
readonly
Returns the value of attribute answer_prefix.
-
#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.
- #question_text(question) ⇒ Object
-
#return_stopped_response(early_stopping_method, intermediate_steps, **kwargs) ⇒ Boxcars::Action
get the stopped response.
-
#should_continue?(iterations) ⇒ Boolean
should we continue to run?.
-
#validate_prompt(values:) ⇒ Hash
validate the prompt.
Methods inherited from EngineBoxcar
#apply, #check_output_keys, #extract_code, #generate, #input_key, #output_key, #predict, #prediction_additional, #prediction_input, #prediction_variables
Methods inherited from Boxcar
#apply, assi, #conduct, hist, #load, #run, #save, #schema, syst, 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: prompt, engine: engine, **kwargs) end |
Instance Attribute Details
#answer_prefix ⇒ Object (readonly)
Returns the value of attribute answer_prefix.
6 7 8 |
# File 'lib/boxcars/train.rb', line 6 def answer_prefix @answer_prefix end |
#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
262 263 264 |
# File 'lib/boxcars/train.rb', line 262 def boxcar_descriptions @boxcar_descriptions ||= boxcars.map { |boxcar| "#{boxcar.name}: #{boxcar.description}" }.join("\n") end |
#boxcar_names ⇒ Object
258 259 260 |
# File 'lib/boxcars/train.rb', line 258 def boxcar_names @boxcar_names ||= boxcars.map(&:name).join(', ') end |
#call(inputs:) ⇒ Hash
execute the train train
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 |
# File 'lib/boxcars/train.rb', line 202 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
47 48 49 50 51 52 53 54 |
# File 'lib/boxcars/train.rb', line 47 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.
40 41 42 |
# File 'lib/boxcars/train.rb', line 40 def extract_boxcar_and_input(text) Result.new(status: :ok, answer: text, explanation: engine_output) end |
#finish_boxcar_name ⇒ Object
Name of the boxcar to use to finish the chain
95 96 97 |
# File 'lib/boxcars/train.rb', line 95 def finish_boxcar_name "Final Answer" end |
#get_boxcar_result(boxcar, boxcar_input) ⇒ Object
187 188 189 190 191 192 193 194 195 196 197 |
# File 'lib/boxcars/train.rb', line 187 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
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/boxcars/train.rb', line 59 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 36 |
# File 'lib/boxcars/train.rb', line 30 def init_prefixes @thought_prefix ||= "Thought: " @observation_prefix ||= "Observation: " @final_answer_prefix ||= "Final Answer: " @answer_prefix ||= "Answer:" @question_prefix ||= "Question: " end |
#input_keys ⇒ Array<Symbol>
the input keys
101 102 103 |
# File 'lib/boxcars/train.rb', line 101 def input_keys prompt.input_variables - [:agent_scratchpad] end |
#key_and_value_text(key, value) ⇒ Object
239 240 241 242 243 244 245 246 247 |
# File 'lib/boxcars/train.rb', line 239 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
266 267 268 269 270 271 272 |
# File 'lib/boxcars/train.rb', line 266 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
250 251 252 |
# File 'lib/boxcars/train.rb', line 250 def observation_text(observation) key_and_value_text(observation_prefix, observation) end |
#output_keys ⇒ Object
the output keys
106 107 108 109 110 |
# File 'lib/boxcars/train.rb', line 106 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.
81 82 83 84 85 86 87 88 |
# File 'lib/boxcars/train.rb', line 81 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
125 126 127 128 129 130 |
# File 'lib/boxcars/train.rb', line 125 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
91 92 |
# File 'lib/boxcars/train.rb', line 91 def prepare_for_new_call end |
#question_text(question) ⇒ Object
254 255 256 |
# File 'lib/boxcars/train.rb', line 254 def question_text(question) key_and_value_text(question_prefix, question) end |
#return_stopped_response(early_stopping_method, intermediate_steps, **kwargs) ⇒ Boxcars::Action
get the stopped response
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
# File 'lib/boxcars/train.rb', line 156 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?
115 116 117 118 119 |
# File 'lib/boxcars/train.rb', line 115 def should_continue?(iterations) return true if max_iterations.nil? iterations < max_iterations end |
#validate_prompt(values:) ⇒ Hash
validate the prompt
136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/boxcars/train.rb', line 136 def validate_prompt(values:) prompt = values["engine_chain"].prompt unless prompt.input_variables.include?(:agent_scratchpad) logger.warning("`agent_scratchpad` should be a variable in prompt.input_variables. Not found, adding it at the end.") prompt.input_variables.append(:agent_scratchpad) case prompt when Prompt prompt.template += "\n%<agent_scratchpad>s" else raise ValueError, "Got unexpected prompt type #{type(prompt)}" end end values end |