boxcars 0.2.0 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -1,35 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Agent for the MRKL chain
|
2
4
|
module Boxcars
|
3
5
|
# A Train using the zero-shot react method.
|
4
6
|
class ZeroShot < Train
|
5
7
|
attr_reader :boxcars, :observation_prefix, :engine_prefix
|
6
8
|
|
7
|
-
# default prompt prefix
|
8
|
-
PREFIX = "Answer the following questions as best you can. You have access to the following actions:".freeze
|
9
|
-
|
10
|
-
# default prompt instructions
|
11
|
-
FORMAT_INSTRUCTIONS = <<~FINPUT.freeze
|
12
|
-
Use the following format:
|
13
|
-
|
14
|
-
Question: the input question you must answer
|
15
|
-
Thought: you should always think about what to do
|
16
|
-
Action: the action to take, should be one of [%<boxcar_names>s]
|
17
|
-
Action Input: the input to the action
|
18
|
-
Observation: the result of the action
|
19
|
-
... (this Thought/Action/Action Input/Observation sequence can repeat N times)
|
20
|
-
Thought: I now know the final answer
|
21
|
-
Final Answer: the final answer to the original input question
|
22
|
-
Next Actions: If you have them, up to three suggested actions for the user to take after getting this answer.
|
23
|
-
FINPUT
|
24
|
-
|
25
|
-
# default prompt suffix
|
26
|
-
SUFFIX = <<~SINPUT.freeze
|
27
|
-
Begin!
|
28
|
-
|
29
|
-
Question: %<input>s
|
30
|
-
Thought:%<agent_scratchpad>s
|
31
|
-
SINPUT
|
32
|
-
|
33
9
|
# @param boxcars [Array<Boxcars::Boxcar>] The boxcars to run.
|
34
10
|
# @param engine [Boxcars::Engine] The engine to use for this train.
|
35
11
|
# @param name [String] The name of the train. Defaults to 'Zero Shot'.
|
@@ -38,26 +14,26 @@ module Boxcars
|
|
38
14
|
def initialize(boxcars:, engine: nil, name: 'Zero Shot', description: 'Zero Shot Train', prompt: nil)
|
39
15
|
@observation_prefix = 'Observation: '
|
40
16
|
@engine_prefix = 'Thought:'
|
41
|
-
prompt ||=
|
17
|
+
prompt ||= my_prompt
|
42
18
|
super(engine: engine, boxcars: boxcars, prompt: prompt, name: name, description: description)
|
43
19
|
end
|
44
20
|
|
45
|
-
#
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
#
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
template = [prefix, boxcar_strings, format_instructions, suffix].join("\n\n")
|
56
|
-
Prompt.new(template: template, input_variables: input_variables)
|
21
|
+
# @return Hash The additional variables for this boxcar.
|
22
|
+
def prediction_additional
|
23
|
+
{ boxcar_names: boxcar_names, boxcar_descriptions: boxcar_descriptions }.merge super
|
24
|
+
end
|
25
|
+
|
26
|
+
# Extract the boxcar and input from the engine output.
|
27
|
+
# @param text [String] The output from the engine.
|
28
|
+
# @return [Array<Boxcars::Boxcar, String>] The boxcar and input.
|
29
|
+
def extract_boxcar_and_input(text)
|
30
|
+
get_action_and_input(engine_output: text)
|
57
31
|
end
|
58
32
|
|
33
|
+
private
|
34
|
+
|
59
35
|
# the final answer action string
|
60
|
-
FINAL_ANSWER_ACTION = "Final Answer:"
|
36
|
+
FINAL_ANSWER_ACTION = "Final Answer:"
|
61
37
|
|
62
38
|
# Parse out the action and input from the engine output.
|
63
39
|
# @param engine_output [String] The output from the engine.
|
@@ -69,13 +45,14 @@ module Boxcars
|
|
69
45
|
# with "Action Input:" should be separated by a newline.
|
70
46
|
if engine_output.include?(FINAL_ANSWER_ACTION)
|
71
47
|
answer = engine_output.split(FINAL_ANSWER_ACTION).last.strip
|
72
|
-
|
48
|
+
Result.new(status: :ok, answer: answer, explanation: engine_output)
|
73
49
|
else
|
74
50
|
# the thought should be the frist line here if it doesn't start with "Action:"
|
75
51
|
thought = engine_output.split(/\n+/).reject(&:empty?).first
|
76
52
|
Boxcars.debug("Though: #{thought}", :cyan)
|
77
53
|
regex = /Action: (?<action>.*)\nAction Input: (?<action_input>.*)/
|
78
54
|
match = regex.match(engine_output)
|
55
|
+
# TODO: this should return an error to the results that can be used for corrections
|
79
56
|
raise ValueError, "Could not parse engine output: #{engine_output}" unless match
|
80
57
|
|
81
58
|
action = match[:action].strip
|
@@ -84,11 +61,40 @@ module Boxcars
|
|
84
61
|
end
|
85
62
|
end
|
86
63
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
64
|
+
CTEMPLATE = [
|
65
|
+
syst("Answer the following questions as best you can. You have access to the following actions:\n",
|
66
|
+
"%<boxcar_descriptions>s"),
|
67
|
+
syst("Use the following format:\n",
|
68
|
+
"Question: the input question you must answer\n",
|
69
|
+
"Thought: you should always think about what to do\n",
|
70
|
+
"Action: the action to take, should be one of [%<boxcar_names>s]\n",
|
71
|
+
"Action Input: the input to the action\n",
|
72
|
+
"Observation: the result of the action\n",
|
73
|
+
"... (this Thought/Action/Action Input/Observation sequence can repeat N times)\n",
|
74
|
+
"Thought: I now know the final answer\n",
|
75
|
+
"Final Answer: the final answer to the original input question\n",
|
76
|
+
"Next Actions: If you have them, up to 3 suggested actions for the user to take after getting this answer.\n",
|
77
|
+
"Begin!"),
|
78
|
+
user("Question: %<input>s"),
|
79
|
+
assi("Thought: %<agent_scratchpad>s")
|
80
|
+
].freeze
|
81
|
+
|
82
|
+
def boxcar_names
|
83
|
+
@boxcar_names ||= boxcars.map(&:name)
|
84
|
+
end
|
85
|
+
|
86
|
+
def boxcar_descriptions
|
87
|
+
@boxcar_descriptions ||= boxcars.map { |boxcar| "#{boxcar.name}: #{boxcar.description}" }.join("\n")
|
88
|
+
end
|
89
|
+
|
90
|
+
# The prompt to use for the train.
|
91
|
+
def my_prompt
|
92
|
+
@conversation ||= Conversation.new(lines: CTEMPLATE)
|
93
|
+
@my_prompt ||= ConversationPrompt.new(
|
94
|
+
conversation: @conversation,
|
95
|
+
input_variables: [:input],
|
96
|
+
other_inputs: [:boxcar_names, :boxcar_descriptions, :agent_scratchpad],
|
97
|
+
output_variables: [:answer])
|
92
98
|
end
|
93
99
|
end
|
94
100
|
end
|
data/lib/boxcars/train.rb
CHANGED
@@ -3,23 +3,25 @@
|
|
3
3
|
module Boxcars
|
4
4
|
# @abstract
|
5
5
|
class Train < EngineBoxcar
|
6
|
-
attr_reader :
|
7
|
-
:max_iterations, :early_stopping_method
|
6
|
+
attr_reader :boxcars, :return_values, :return_intermediate_steps,
|
7
|
+
:max_iterations, :early_stopping_method, :name_to_boxcar_map
|
8
8
|
|
9
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
10
|
# @param boxcars [Array<Boxcars::Boxcar>] The boxcars to run.
|
12
|
-
# @param prompt [
|
11
|
+
# @param prompt [Boxcars::Prompt] The prompt to use.
|
12
|
+
# @param engine [Boxcars::Engine] The engine to use for this train.
|
13
|
+
# @param kwargs [Hash] Additional arguments including: name, description, top_k, return_direct, and stop
|
13
14
|
# @abstract
|
14
15
|
def initialize(boxcars:, prompt:, engine: nil, **kwargs)
|
15
16
|
@boxcars = boxcars
|
16
|
-
@
|
17
|
+
@name_to_boxcar_map = boxcars.to_h { |boxcar| [boxcar.name, boxcar] }
|
17
18
|
@return_values = [:output]
|
18
|
-
@return_intermediate_steps = kwargs
|
19
|
-
@max_iterations = kwargs
|
20
|
-
@early_stopping_method = kwargs
|
19
|
+
@return_intermediate_steps = kwargs.delete(:return_intermediate_steps) || false
|
20
|
+
@max_iterations = kwargs.delete(:max_iterations) || 25
|
21
|
+
@early_stopping_method = kwargs.delete(:early_stopping_method) || "force"
|
22
|
+
kwargs[:stop] ||= ["\n#{observation_prefix}"]
|
21
23
|
|
22
|
-
super(prompt: prompt, engine: engine,
|
24
|
+
super(prompt: prompt, engine: engine, **kwargs)
|
23
25
|
end
|
24
26
|
|
25
27
|
# Extract the boxcar name and input from the text.
|
@@ -27,11 +29,6 @@ module Boxcars
|
|
27
29
|
def extract_boxcar_and_input(text)
|
28
30
|
end
|
29
31
|
|
30
|
-
# the stop strings list
|
31
|
-
def stop
|
32
|
-
["\n#{observation_prefix}"]
|
33
|
-
end
|
34
|
-
|
35
32
|
# build the scratchpad for the engine
|
36
33
|
# @param intermediate_steps [Array] The intermediate steps to build the scratchpad from.
|
37
34
|
# @return [String] The scratchpad.
|
@@ -48,16 +45,20 @@ module Boxcars
|
|
48
45
|
# @param full_inputs [Hash] The inputs to the engine.
|
49
46
|
# @return [Boxcars::Action] The next action.
|
50
47
|
def get_next_action(full_inputs)
|
51
|
-
full_output =
|
52
|
-
parsed_output =
|
53
|
-
|
54
|
-
full_output = _fix_text(full_output)
|
48
|
+
full_output = ""
|
49
|
+
parsed_output = nil
|
50
|
+
loop do
|
55
51
|
full_inputs[:agent_scratchpad] += full_output
|
56
52
|
output = predict(**full_inputs)
|
57
53
|
full_output += output
|
58
54
|
parsed_output = extract_boxcar_and_input(full_output)
|
55
|
+
break unless parsed_output.nil?
|
56
|
+
end
|
57
|
+
if parsed_output.is_a?(Result)
|
58
|
+
TrainAction.from_result(boxcar: "Final Answer", result: parsed_output, log: full_output)
|
59
|
+
else
|
60
|
+
TrainAction.new(boxcar: parsed_output[0], boxcar_input: parsed_output[1], log: full_output)
|
59
61
|
end
|
60
|
-
TrainAction.new(boxcar: parsed_output[0], boxcar_input: parsed_output[1], log: full_output)
|
61
62
|
end
|
62
63
|
|
63
64
|
# Given input, decided what to do.
|
@@ -66,8 +67,7 @@ module Boxcars
|
|
66
67
|
# @return [Boxcars::Action] Action specifying what boxcar to use.
|
67
68
|
def plan(intermediate_steps, **kwargs)
|
68
69
|
thoughts = construct_scratchpad(intermediate_steps)
|
69
|
-
|
70
|
-
full_inputs = kwargs.merge(new_inputs)
|
70
|
+
full_inputs = prediction_additional.merge(kwargs).merge(agent_scratchpad: thoughts)
|
71
71
|
action = get_next_action(full_inputs)
|
72
72
|
return TrainFinish.new({ output: action.boxcar_input }, log: action.log) if action.boxcar == finish_boxcar_name
|
73
73
|
|
@@ -184,7 +184,6 @@ module Boxcars
|
|
184
184
|
# @return [Hash] The output.
|
185
185
|
def call(inputs:)
|
186
186
|
prepare_for_new_call
|
187
|
-
name_to_boxcar_map = boxcars.to_h { |boxcar| [boxcar.name, boxcar] }
|
188
187
|
intermediate_steps = []
|
189
188
|
iterations = 0
|
190
189
|
while should_continue?(iterations)
|
data/lib/boxcars/version.rb
CHANGED
data/lib/boxcars.rb
CHANGED
@@ -107,6 +107,7 @@ module Boxcars
|
|
107
107
|
end
|
108
108
|
|
109
109
|
# Logging system
|
110
|
+
# debug log
|
110
111
|
def self.debug(msg, color = nil, **options)
|
111
112
|
msg = colorize(msg.to_s, color, **options) if color
|
112
113
|
if logger
|
@@ -116,6 +117,7 @@ module Boxcars
|
|
116
117
|
end
|
117
118
|
end
|
118
119
|
|
120
|
+
# info log
|
119
121
|
def self.info(msg, color = nil, **options)
|
120
122
|
msg = colorize(msg.to_s, color, **options) if color
|
121
123
|
if logger
|
@@ -125,6 +127,7 @@ module Boxcars
|
|
125
127
|
end
|
126
128
|
end
|
127
129
|
|
130
|
+
# warn log
|
128
131
|
def self.warn(msg, color = nil, **options)
|
129
132
|
msg = colorize(msg.to_s, color, **options) if color
|
130
133
|
if logger
|
@@ -134,6 +137,7 @@ module Boxcars
|
|
134
137
|
end
|
135
138
|
end
|
136
139
|
|
140
|
+
# error log
|
137
141
|
def self.error(msg, color = nil, **options)
|
138
142
|
msg = colorize(msg.to_s, color, **options) if color
|
139
143
|
if logger
|
@@ -143,6 +147,7 @@ module Boxcars
|
|
143
147
|
end
|
144
148
|
end
|
145
149
|
|
150
|
+
# simple colorization
|
146
151
|
def self.colorize(str, color, **options)
|
147
152
|
background = options[:background] || options[:bg] || false
|
148
153
|
style = options[:style]
|
@@ -157,6 +162,8 @@ end
|
|
157
162
|
|
158
163
|
require "boxcars/version"
|
159
164
|
require "boxcars/prompt"
|
165
|
+
require "boxcars/conversation_prompt"
|
166
|
+
require "boxcars/conversation"
|
160
167
|
require "boxcars/generation"
|
161
168
|
require "boxcars/ruby_repl"
|
162
169
|
require "boxcars/engine"
|
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.2.
|
4
|
+
version: 0.2.2
|
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-16 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: debug
|
@@ -109,11 +109,14 @@ files:
|
|
109
109
|
- lib/boxcars/boxcar/engine_boxcar.rb
|
110
110
|
- lib/boxcars/boxcar/google_search.rb
|
111
111
|
- lib/boxcars/boxcar/sql.rb
|
112
|
+
- lib/boxcars/conversation.rb
|
113
|
+
- lib/boxcars/conversation_prompt.rb
|
112
114
|
- lib/boxcars/engine.rb
|
113
115
|
- lib/boxcars/engine/engine_result.rb
|
114
116
|
- lib/boxcars/engine/openai.rb
|
115
117
|
- lib/boxcars/generation.rb
|
116
118
|
- lib/boxcars/prompt.rb
|
119
|
+
- lib/boxcars/result.rb
|
117
120
|
- lib/boxcars/ruby_repl.rb
|
118
121
|
- lib/boxcars/train.rb
|
119
122
|
- lib/boxcars/train/train_action.rb
|